std: rewrite json.rs to fix bugs and use readers/writers
Our json implementation did not conform to the spec, and was missing support for escpaed characters and exponental numbers. This fixes it, and adds support for reading/writing json directly from/to a stream. There are two things left unimplemented. We could use a "to_json" iface/impl, but that really needs traits to cut down on code duplication. The other is it wouldn't be that not that hard to turn this implementation into a event driven parser like YAJL, but I ran into some type-inference bugs, so I cut that out. It'd be nice to revisit this in the future though.
This commit is contained in:
parent
8cc23aab6d
commit
012dec5e57
2 changed files with 678 additions and 235 deletions
|
@ -9,6 +9,7 @@ import rustc::util::filesearch::{get_cargo_root, get_cargo_root_nearest,
|
||||||
get_cargo_sysroot, libdir};
|
get_cargo_sysroot, libdir};
|
||||||
import rustc::driver::diagnostic;
|
import rustc::driver::diagnostic;
|
||||||
|
|
||||||
|
import result::{ok, err};
|
||||||
import std::fs;
|
import std::fs;
|
||||||
import std::io;
|
import std::io;
|
||||||
import io::writer_util;
|
import io::writer_util;
|
||||||
|
@ -225,15 +226,15 @@ fn parse_source(name: str, j: json::json) -> source {
|
||||||
fn try_parse_sources(filename: str, sources: map::hashmap<str, source>) {
|
fn try_parse_sources(filename: str, sources: map::hashmap<str, source>) {
|
||||||
if !fs::path_exists(filename) { ret; }
|
if !fs::path_exists(filename) { ret; }
|
||||||
let c = io::read_whole_file_str(filename);
|
let c = io::read_whole_file_str(filename);
|
||||||
let j = json::from_str(result::get(c));
|
alt json::from_str(result::get(c)) {
|
||||||
alt j {
|
ok(json::dict(j)) {
|
||||||
some(json::dict(_j)) {
|
j.items { |k, v|
|
||||||
_j.items { |k, v|
|
|
||||||
sources.insert(k, parse_source(k, v));
|
sources.insert(k, parse_source(k, v));
|
||||||
#debug("source: %s", k);
|
#debug("source: %s", k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ { fail "malformed sources.json"; }
|
ok(_) { fail "malformed sources.json"; }
|
||||||
|
err(e) { fail #fmt("%s:%u:%u: %s", filename, e.line, e.col, e.msg); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +279,7 @@ fn load_one_source_package(&src: source, p: map::hashmap<str, json::json>) {
|
||||||
let tags = [];
|
let tags = [];
|
||||||
alt p.find("tags") {
|
alt p.find("tags") {
|
||||||
some(json::list(js)) {
|
some(json::list(js)) {
|
||||||
for j in *js {
|
for j in js {
|
||||||
alt j {
|
alt j {
|
||||||
json::string(_j) { vec::grow(tags, 1u, _j); }
|
json::string(_j) { vec::grow(tags, 1u, _j); }
|
||||||
_ { }
|
_ { }
|
||||||
|
@ -316,10 +317,9 @@ fn load_source_packages(&c: cargo, &src: source) {
|
||||||
let pkgfile = fs::connect(dir, "packages.json");
|
let pkgfile = fs::connect(dir, "packages.json");
|
||||||
if !fs::path_exists(pkgfile) { ret; }
|
if !fs::path_exists(pkgfile) { ret; }
|
||||||
let pkgstr = io::read_whole_file_str(pkgfile);
|
let pkgstr = io::read_whole_file_str(pkgfile);
|
||||||
let j = json::from_str(result::get(pkgstr));
|
alt json::from_str(result::get(pkgstr)) {
|
||||||
alt j {
|
ok(json::list(js)) {
|
||||||
some(json::list(js)) {
|
for _j: json::json in js {
|
||||||
for _j: json::json in *js {
|
|
||||||
alt _j {
|
alt _j {
|
||||||
json::dict(_p) {
|
json::dict(_p) {
|
||||||
load_one_source_package(src, _p);
|
load_one_source_package(src, _p);
|
||||||
|
@ -331,8 +331,12 @@ fn load_source_packages(&c: cargo, &src: source) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ {
|
ok(_) {
|
||||||
warn("Malformed source json: " + src.name);
|
warn("Malformed source json: " + src.name +
|
||||||
|
"(packages is not a list)");
|
||||||
|
}
|
||||||
|
err(e) {
|
||||||
|
warn(#fmt("%s:%u:%u: %s", src.name, e.line, e.col, e.msg));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
// Rust JSON serialization library
|
// Rust JSON serialization library
|
||||||
// Copyright (c) 2011 Google Inc.
|
// Copyright (c) 2011 Google Inc.
|
||||||
|
|
||||||
import float;
|
import result::{ok, err};
|
||||||
|
import io;
|
||||||
|
import io::{reader_util, writer_util};
|
||||||
import map;
|
import map;
|
||||||
|
|
||||||
export json;
|
export json;
|
||||||
|
export to_writer;
|
||||||
export to_str;
|
export to_str;
|
||||||
|
export from_reader;
|
||||||
export from_str;
|
export from_str;
|
||||||
|
export eq;
|
||||||
|
|
||||||
export num;
|
export num;
|
||||||
export string;
|
export string;
|
||||||
export boolean;
|
export boolean;
|
||||||
export list;
|
export list;
|
||||||
export dict;
|
export dict;
|
||||||
|
export null;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Tag: json
|
Tag: json
|
||||||
|
@ -27,233 +33,453 @@ enum json {
|
||||||
/* Variant: boolean */
|
/* Variant: boolean */
|
||||||
boolean(bool),
|
boolean(bool),
|
||||||
/* Variant: list */
|
/* Variant: list */
|
||||||
list(@[json]),
|
list([json]),
|
||||||
/* Variant: dict */
|
/* Variant: dict */
|
||||||
dict(map::map<str,json>),
|
dict(map::map<str,json>),
|
||||||
/* Variant: null */
|
/* Variant: null */
|
||||||
null,
|
null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type error = {
|
||||||
|
line: uint,
|
||||||
|
col: uint,
|
||||||
|
msg: str,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Function: to_writer
|
||||||
|
|
||||||
|
Serializes a json value into a io::writer.
|
||||||
|
*/
|
||||||
|
fn to_writer(wr: io::writer, j: json) {
|
||||||
|
alt j {
|
||||||
|
num(n) { wr.write_str(float::to_str(n, 6u)); }
|
||||||
|
string(s) {
|
||||||
|
wr.write_char('"');
|
||||||
|
let escaped = "";
|
||||||
|
str::chars_iter(s) { |c|
|
||||||
|
alt c {
|
||||||
|
'"' { escaped += "\\\""; }
|
||||||
|
'\\' { escaped += "\\\\"; }
|
||||||
|
'\x08' { escaped += "\\b"; }
|
||||||
|
'\x0c' { escaped += "\\f"; }
|
||||||
|
'\n' { escaped += "\\n"; }
|
||||||
|
'\r' { escaped += "\\r"; }
|
||||||
|
'\t' { escaped += "\\t"; }
|
||||||
|
_ { escaped += str::from_char(c); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
wr.write_str(escaped);
|
||||||
|
wr.write_char('"');
|
||||||
|
}
|
||||||
|
boolean(b) {
|
||||||
|
wr.write_str(if b { "true" } else { "false" });
|
||||||
|
}
|
||||||
|
list(v) {
|
||||||
|
wr.write_char('[');
|
||||||
|
let first = true;
|
||||||
|
vec::iter(v) { |item|
|
||||||
|
if !first {
|
||||||
|
wr.write_str(", ");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
to_writer(wr, item);
|
||||||
|
};
|
||||||
|
wr.write_char(']');
|
||||||
|
}
|
||||||
|
dict(d) {
|
||||||
|
if d.size() == 0u {
|
||||||
|
wr.write_str("{}");
|
||||||
|
ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
wr.write_str("{ ");
|
||||||
|
let first = true;
|
||||||
|
d.items { |key, value|
|
||||||
|
if !first {
|
||||||
|
wr.write_str(", ");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
to_writer(wr, string(key));
|
||||||
|
wr.write_str(": ");
|
||||||
|
to_writer(wr, value);
|
||||||
|
};
|
||||||
|
wr.write_str(" }");
|
||||||
|
}
|
||||||
|
null {
|
||||||
|
wr.write_str("null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Function: to_str
|
Function: to_str
|
||||||
|
|
||||||
Serializes a json value into a string.
|
Serializes a json value into a string.
|
||||||
*/
|
*/
|
||||||
fn to_str(j: json) -> str {
|
fn to_str(j: json) -> str {
|
||||||
alt j {
|
io::with_str_writer { |wr| to_writer(wr, j) }
|
||||||
num(f) { float::to_str(f, 6u) }
|
|
||||||
string(s) { #fmt["\"%s\"", s] } // XXX: escape
|
|
||||||
boolean(true) { "true" }
|
|
||||||
boolean(false) { "false" }
|
|
||||||
list(@js) {
|
|
||||||
str::concat(["[",
|
|
||||||
str::connect(
|
|
||||||
vec::map::<json,str>(js, { |e| to_str(e) }),
|
|
||||||
", "),
|
|
||||||
"]"])
|
|
||||||
}
|
|
||||||
dict(m) {
|
|
||||||
let parts = [];
|
|
||||||
m.items({ |k, v|
|
|
||||||
vec::grow(parts, 1u,
|
|
||||||
str::concat(["\"", k, "\": ", to_str(v)])
|
|
||||||
)
|
|
||||||
});
|
|
||||||
str::concat(["{ ", str::connect(parts, ", "), " }"])
|
|
||||||
}
|
|
||||||
null { "null" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rest(s: str) -> str {
|
type parser = {
|
||||||
assert(str::len(s) >= 1u);
|
rdr: io::reader,
|
||||||
str::slice(s, 1u, str::len(s))
|
mutable ch: char,
|
||||||
}
|
mutable line: uint,
|
||||||
|
mutable col: uint,
|
||||||
|
};
|
||||||
|
|
||||||
fn from_str_str(s: str) -> (option<json>, str) {
|
impl parser for parser {
|
||||||
let pos = 0u;
|
fn eof() -> bool { self.ch == -1 as char }
|
||||||
let len = str::len(s);
|
|
||||||
let escape = false;
|
|
||||||
let res = "";
|
|
||||||
|
|
||||||
alt str::char_at(s, 0u) {
|
fn bump() {
|
||||||
'"' { pos = 1u; }
|
self.ch = self.rdr.read_char();
|
||||||
_ { ret (none, s); }
|
|
||||||
|
if self.ch == '\n' {
|
||||||
|
self.line += 1u;
|
||||||
|
self.col = 1u;
|
||||||
|
} else {
|
||||||
|
self.col += 1u;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (pos < len) {
|
fn next_char() -> char {
|
||||||
let chr = str::char_range_at(s, pos);
|
self.bump();
|
||||||
let c = chr.ch;
|
self.ch
|
||||||
pos = chr.next;
|
|
||||||
if (escape) {
|
|
||||||
res = res + str::from_char(c);
|
|
||||||
escape = false;
|
|
||||||
cont;
|
|
||||||
}
|
|
||||||
if (c == '\\') {
|
|
||||||
escape = true;
|
|
||||||
cont;
|
|
||||||
} else if (c == '"') {
|
|
||||||
ret (some(string(res)),
|
|
||||||
str::slice(s, pos, str::len(s)));
|
|
||||||
}
|
|
||||||
res = res + str::from_char(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret (none, s);
|
fn error<T>(msg: str) -> result::t<T, error> {
|
||||||
}
|
err({ line: self.line, col: self.col, msg: msg })
|
||||||
|
|
||||||
fn from_str_list(s: str) -> (option<json>, str) {
|
|
||||||
if str::char_at(s, 0u) != '[' { ret (none, s); }
|
|
||||||
let s0 = str::trim_left(rest(s));
|
|
||||||
let vals = [];
|
|
||||||
if str::is_empty(s0) { ret (none, s0); }
|
|
||||||
if str::char_at(s0, 0u) == ']' { ret (some(list(@[])), rest(s0)); }
|
|
||||||
while str::is_not_empty(s0) {
|
|
||||||
s0 = str::trim_left(s0);
|
|
||||||
let (next, s1) = from_str_helper(s0);
|
|
||||||
s0 = s1;
|
|
||||||
alt next {
|
|
||||||
some(j) { vec::grow(vals, 1u, j); }
|
|
||||||
none { ret (none, s0); }
|
|
||||||
}
|
|
||||||
s0 = str::trim_left(s0);
|
|
||||||
if str::is_empty(s0) { ret (none, s0); }
|
|
||||||
alt str::char_at(s0, 0u) {
|
|
||||||
',' { }
|
|
||||||
']' { ret (some(list(@vals)), rest(s0)); }
|
|
||||||
_ { ret (none, s0); }
|
|
||||||
}
|
|
||||||
s0 = rest(s0);
|
|
||||||
}
|
}
|
||||||
ret (none, s0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_str_dict(s: str) -> (option<json>, str) {
|
fn parse() -> result::t<json, error> {
|
||||||
if str::char_at(s, 0u) != '{' { ret (none, s); }
|
alt self.parse_value() {
|
||||||
let s0 = str::trim_left(rest(s));
|
ok(value) {
|
||||||
let vals = map::new_str_hash::<json>();
|
// Make sure there is no trailing characters.
|
||||||
if str::is_empty(s0) { ret (none, s0); }
|
if self.eof() {
|
||||||
if str::char_at(s0, 0u) == '}' { ret (some(dict(vals)), rest(s0)); }
|
ok(value)
|
||||||
while str::is_not_empty(s0) {
|
} else {
|
||||||
s0 = str::trim_left(s0);
|
self.error("trailing characters")
|
||||||
let (next, s1) = from_str_helper(s0); // key
|
}
|
||||||
let key = "";
|
}
|
||||||
s0 = s1;
|
e { e }
|
||||||
alt next {
|
|
||||||
some(string(k)) { key = k; }
|
|
||||||
_ { ret (none, s0); }
|
|
||||||
}
|
}
|
||||||
s0 = str::trim_left(s0);
|
|
||||||
if str::is_empty(s0) { ret (none, s0); }
|
|
||||||
if str::char_at(s0, 0u) != ':' { ret (none, s0); }
|
|
||||||
s0 = str::trim_left(rest(s0));
|
|
||||||
let (next, s1) = from_str_helper(s0); // value
|
|
||||||
s0 = s1;
|
|
||||||
alt next {
|
|
||||||
some(j) { vals.insert(key, j); }
|
|
||||||
_ { ret (none, s0); }
|
|
||||||
}
|
|
||||||
s0 = str::trim_left(s0);
|
|
||||||
if str::is_empty(s0) { ret (none, s0); }
|
|
||||||
alt str::char_at(s0, 0u) {
|
|
||||||
',' { }
|
|
||||||
'}' { ret (some(dict(vals)), rest(s0)); }
|
|
||||||
_ { ret (none, s0); }
|
|
||||||
}
|
|
||||||
s0 = str::trim_left(rest(s0));
|
|
||||||
}
|
}
|
||||||
(none, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_str_float(s: str) -> (option<json>, str) {
|
fn parse_value() -> result::t<json, error> {
|
||||||
let pos = 0u;
|
self.parse_whitespace();
|
||||||
let len = str::len(s);
|
|
||||||
let res = 0f;
|
|
||||||
let neg = 1f;
|
|
||||||
|
|
||||||
alt str::char_at(s, 0u) {
|
if self.eof() { ret self.error("EOF while parsing value"); }
|
||||||
'-' {
|
|
||||||
|
alt self.ch {
|
||||||
|
'n' { self.parse_ident("ull", null) }
|
||||||
|
't' { self.parse_ident("rue", boolean(true)) }
|
||||||
|
'f' { self.parse_ident("alse", boolean(false)) }
|
||||||
|
'0' to '9' | '-' { self.parse_number() }
|
||||||
|
'"' {
|
||||||
|
alt self.parse_str() {
|
||||||
|
ok(s) { ok(string(s)) }
|
||||||
|
err(e) { err(e) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'[' { self.parse_list() }
|
||||||
|
'{' { self.parse_object() }
|
||||||
|
_ { self.error("invalid syntax") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_whitespace() {
|
||||||
|
while char::is_whitespace(self.ch) { self.bump(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ident(ident: str, value: json) -> result::t<json, error> {
|
||||||
|
if str::all(ident, { |c| c == self.next_char() }) {
|
||||||
|
self.bump();
|
||||||
|
ok(value)
|
||||||
|
} else {
|
||||||
|
self.error("invalid syntax")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_number() -> result::t<json, error> {
|
||||||
|
let neg = 1f;
|
||||||
|
|
||||||
|
if self.ch == '-' {
|
||||||
|
self.bump();
|
||||||
neg = -1f;
|
neg = -1f;
|
||||||
pos = 1u;
|
|
||||||
}
|
}
|
||||||
'+' {
|
|
||||||
pos = 1u;
|
|
||||||
}
|
|
||||||
'0' to '9' | '.' { }
|
|
||||||
_ { ret (none, s); }
|
|
||||||
}
|
|
||||||
|
|
||||||
while (pos < len) {
|
let res = alt self.parse_integer() {
|
||||||
let opos = pos;
|
ok(res) { res }
|
||||||
let chr = str::char_range_at(s, pos);
|
err(e) { ret err(e); }
|
||||||
let c = chr.ch;
|
};
|
||||||
pos = chr.next;
|
|
||||||
alt c {
|
if self.ch == '.' {
|
||||||
'0' to '9' {
|
alt self.parse_decimal(res) {
|
||||||
res = res * 10f;
|
ok(r) { res = r; }
|
||||||
res += ((c as int) - ('0' as int)) as float;
|
err(e) { ret err(e); }
|
||||||
}
|
}
|
||||||
'.' { break; }
|
|
||||||
_ { ret (some(num(neg * res)),
|
|
||||||
str::slice(s, opos, str::len(s))); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.ch == 'e' || self.ch == 'E' {
|
||||||
|
alt self.parse_exponent(res) {
|
||||||
|
ok(r) { res = r; }
|
||||||
|
err(e) { ret err(e); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(num(neg * res))
|
||||||
}
|
}
|
||||||
|
|
||||||
if pos == len {
|
fn parse_integer() -> result::t<float, error> {
|
||||||
ret (some(num(neg * res)),
|
let res = 0f;
|
||||||
str::slice(s, pos, str::len(s)));
|
|
||||||
|
alt self.ch {
|
||||||
|
'0' {
|
||||||
|
self.bump();
|
||||||
|
|
||||||
|
// There can be only one leading '0'.
|
||||||
|
alt self.ch {
|
||||||
|
'0' to '9' { ret self.error("invalid number"); }
|
||||||
|
_ {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'1' to '9' {
|
||||||
|
while !self.eof() {
|
||||||
|
alt self.ch {
|
||||||
|
'0' to '9' {
|
||||||
|
res *= 10f;
|
||||||
|
res += ((self.ch as int) - ('0' as int)) as float;
|
||||||
|
|
||||||
|
self.bump();
|
||||||
|
}
|
||||||
|
_ { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ { ret self.error("invalid number"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
let dec = 1f;
|
fn parse_decimal(res: float) -> result::t<float, error> {
|
||||||
while (pos < len) {
|
self.bump();
|
||||||
let opos = pos;
|
|
||||||
let chr = str::char_range_at(s, pos);
|
// Make sure a digit follows the decimal place.
|
||||||
let c = chr.ch;
|
alt self.ch {
|
||||||
pos = chr.next;
|
'0' to '9' {}
|
||||||
alt c {
|
_ { ret self.error("invalid number"); }
|
||||||
'0' to '9' {
|
}
|
||||||
|
|
||||||
|
let res = res;
|
||||||
|
let dec = 1f;
|
||||||
|
while !self.eof() {
|
||||||
|
alt self.ch {
|
||||||
|
'0' to '9' {
|
||||||
dec /= 10f;
|
dec /= 10f;
|
||||||
res += (((c as int) - ('0' as int)) as float) * dec;
|
res += (((self.ch as int) - ('0' as int)) as float) * dec;
|
||||||
|
|
||||||
|
self.bump();
|
||||||
|
}
|
||||||
|
_ { break; }
|
||||||
}
|
}
|
||||||
_ { ret (some(num(neg * res)),
|
|
||||||
str::slice(s, opos, str::len(s))); }
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ret (some(num(neg * res)), str::slice(s, pos, str::len(s)));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_str_bool(s: str) -> (option<json>, str) {
|
ok(res)
|
||||||
if (str::starts_with(s, "true")) {
|
}
|
||||||
(some(boolean(true)), str::slice(s, 4u, str::len(s)))
|
|
||||||
} else if (str::starts_with(s, "false")) {
|
fn parse_exponent(res: float) -> result::t<float, error> {
|
||||||
(some(boolean(false)), str::slice(s, 5u, str::len(s)))
|
self.bump();
|
||||||
} else {
|
|
||||||
(none, s)
|
let res = res;
|
||||||
|
let exp = 0u;
|
||||||
|
let neg_exp = false;
|
||||||
|
|
||||||
|
alt self.ch {
|
||||||
|
'+' { self.bump(); }
|
||||||
|
'-' { self.bump(); neg_exp = true; }
|
||||||
|
_ {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure a digit follows the exponent place.
|
||||||
|
alt self.ch {
|
||||||
|
'0' to '9' {}
|
||||||
|
_ { ret self.error("invalid number"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
while !self.eof() {
|
||||||
|
alt self.ch {
|
||||||
|
'0' to '9' {
|
||||||
|
exp *= 10u;
|
||||||
|
exp += (self.ch as uint) - ('0' as uint);
|
||||||
|
|
||||||
|
self.bump();
|
||||||
|
}
|
||||||
|
_ { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let exp = float::pow_with_uint(10u, exp);
|
||||||
|
if neg_exp {
|
||||||
|
res /= exp;
|
||||||
|
} else {
|
||||||
|
res *= exp;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_str() -> result::t<str, error> {
|
||||||
|
let escape = false;
|
||||||
|
let res = "";
|
||||||
|
|
||||||
|
while !self.eof() {
|
||||||
|
self.bump();
|
||||||
|
|
||||||
|
if (escape) {
|
||||||
|
alt self.ch {
|
||||||
|
'"' { str::push_char(res, '"'); }
|
||||||
|
'\\' { str::push_char(res, '\\'); }
|
||||||
|
'/' { str::push_char(res, '/'); }
|
||||||
|
'b' { str::push_char(res, '\x08'); }
|
||||||
|
'f' { str::push_char(res, '\x0c'); }
|
||||||
|
'n' { str::push_char(res, '\n'); }
|
||||||
|
'r' { str::push_char(res, '\r'); }
|
||||||
|
't' { str::push_char(res, '\t'); }
|
||||||
|
'u' {
|
||||||
|
// Parse \u1234.
|
||||||
|
let i = 0u;
|
||||||
|
let n = 0u;
|
||||||
|
while i < 4u {
|
||||||
|
alt self.next_char() {
|
||||||
|
'0' to '9' {
|
||||||
|
n = n * 10u +
|
||||||
|
(self.ch as uint) - ('0' as uint);
|
||||||
|
}
|
||||||
|
_ { ret self.error("invalid \\u escape"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error out if we didn't parse 4 digits.
|
||||||
|
if i != 4u {
|
||||||
|
ret self.error("invalid \\u escape");
|
||||||
|
}
|
||||||
|
|
||||||
|
str::push_char(res, n as char);
|
||||||
|
}
|
||||||
|
_ { ret self.error("invalid escape"); }
|
||||||
|
}
|
||||||
|
escape = false;
|
||||||
|
} else if self.ch == '\\' {
|
||||||
|
escape = true;
|
||||||
|
} else {
|
||||||
|
if self.ch == '"' {
|
||||||
|
self.bump();
|
||||||
|
ret ok(res);
|
||||||
|
}
|
||||||
|
str::push_char(res, self.ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.error("EOF while parsing string")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_list() -> result::t<json, error> {
|
||||||
|
self.bump();
|
||||||
|
self.parse_whitespace();
|
||||||
|
|
||||||
|
let values = [];
|
||||||
|
|
||||||
|
if self.ch == ']' {
|
||||||
|
self.bump();
|
||||||
|
ret ok(list(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
while true {
|
||||||
|
alt self.parse_value() {
|
||||||
|
ok(v) { vec::push(values, v); }
|
||||||
|
e { ret e; }
|
||||||
|
}
|
||||||
|
|
||||||
|
self.parse_whitespace();
|
||||||
|
if self.eof() { break; }
|
||||||
|
|
||||||
|
alt self.ch {
|
||||||
|
',' { self.bump(); }
|
||||||
|
']' { self.bump(); ret ok(list(values)); }
|
||||||
|
_ { ret self.error("expecting ',' or ']'"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret self.error("EOF while parsing list");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_object() -> result::t<json, error> {
|
||||||
|
self.bump();
|
||||||
|
self.parse_whitespace();
|
||||||
|
|
||||||
|
let values = map::new_str_hash();
|
||||||
|
|
||||||
|
if self.ch == '}' {
|
||||||
|
self.bump();
|
||||||
|
ret ok(dict(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
while !self.eof() {
|
||||||
|
self.parse_whitespace();
|
||||||
|
|
||||||
|
if self.ch != '"' {
|
||||||
|
ret self.error("key must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = alt self.parse_str() {
|
||||||
|
ok(key) { key }
|
||||||
|
err(e) { ret err(e); }
|
||||||
|
};
|
||||||
|
|
||||||
|
self.parse_whitespace();
|
||||||
|
|
||||||
|
if self.ch != ':' {
|
||||||
|
if self.eof() { break; }
|
||||||
|
ret self.error("expecting ':'");
|
||||||
|
}
|
||||||
|
self.bump();
|
||||||
|
|
||||||
|
alt self.parse_value() {
|
||||||
|
ok(value) { values.insert(key, value); }
|
||||||
|
e { ret e; }
|
||||||
|
}
|
||||||
|
self.parse_whitespace();
|
||||||
|
|
||||||
|
alt self.ch {
|
||||||
|
',' { self.bump(); }
|
||||||
|
'}' { self.bump(); ret ok(dict(values)); }
|
||||||
|
_ {
|
||||||
|
if self.eof() { break; }
|
||||||
|
ret self.error("expecting ',' or '}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret self.error("EOF while parsing object");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_str_null(s: str) -> (option<json>, str) {
|
/*
|
||||||
if (str::starts_with(s, "null")) {
|
Function: from_reader
|
||||||
(some(null), str::slice(s, 4u, str::len(s)))
|
|
||||||
} else {
|
|
||||||
(none, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_str_helper(s: str) -> (option<json>, str) {
|
Deserializes a json value from an io::reader.
|
||||||
let s = str::trim_left(s);
|
*/
|
||||||
if str::is_empty(s) { ret (none, s); }
|
|
||||||
let start = str::char_at(s, 0u);
|
fn from_reader(rdr: io::reader) -> result::t<json, error> {
|
||||||
alt start {
|
let parser = {
|
||||||
'"' { from_str_str(s) }
|
rdr: rdr,
|
||||||
'[' { from_str_list(s) }
|
mutable ch: rdr.read_char(),
|
||||||
'{' { from_str_dict(s) }
|
mutable line: 1u,
|
||||||
'0' to '9' | '-' | '+' | '.' { from_str_float(s) }
|
mutable col: 1u,
|
||||||
't' | 'f' { from_str_bool(s) }
|
};
|
||||||
'n' { from_str_null(s) }
|
|
||||||
_ { ret (none, s); }
|
parser.parse()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -261,62 +487,275 @@ Function: from_str
|
||||||
|
|
||||||
Deserializes a json value from a string.
|
Deserializes a json value from a string.
|
||||||
*/
|
*/
|
||||||
fn from_str(s: str) -> option<json> {
|
fn from_str(s: str) -> result::t<json, error> {
|
||||||
let (j, _) = from_str_helper(s);
|
from_reader(io::string_reader(s))
|
||||||
j
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Function: eq
|
||||||
|
|
||||||
|
Test if two json values are equal.
|
||||||
|
*/
|
||||||
|
fn eq(value0: json, value1: json) -> bool {
|
||||||
|
alt (value0, value1) {
|
||||||
|
(num(f0), num(f1)) { f0 == f1 }
|
||||||
|
(string(s0), string(s1)) { s0 == s1 }
|
||||||
|
(boolean(b0), boolean(b1)) { b0 == b1 }
|
||||||
|
(list(l0), list(l1)) { vec::all2(l0, l1, eq) }
|
||||||
|
(dict(d0), dict(d1)) {
|
||||||
|
if d0.size() == d1.size() {
|
||||||
|
let equal = true;
|
||||||
|
d0.items { |k, v0|
|
||||||
|
alt d1.find(k) {
|
||||||
|
some(v1) {
|
||||||
|
if !eq(v0, v1) { equal = false; } }
|
||||||
|
none { equal = false; }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
equal
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(null, null) { true }
|
||||||
|
_ { false }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#[test]
|
fn mk_dict(items: [(str, json)]) -> json {
|
||||||
fn test_from_str_null() {
|
let d = map::new_str_hash();
|
||||||
assert(from_str("null") == some(null));
|
|
||||||
|
vec::iter(items) { |item|
|
||||||
|
let (key, value) = item;
|
||||||
|
d.insert(key, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
dict(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_str_num() {
|
fn test_write_null() {
|
||||||
assert(from_str("3") == some(num(3f)));
|
assert to_str(null) == "null";
|
||||||
assert(from_str("3.1") == some(num(3.1f)));
|
|
||||||
assert(from_str("-1.2") == some(num(-1.2f)));
|
|
||||||
assert(from_str(".4") == some(num(0.4f)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_str_str() {
|
fn test_write_num() {
|
||||||
assert(from_str("\"foo\"") == some(string("foo")));
|
assert to_str(num(3f)) == "3";
|
||||||
assert(from_str("\"\\\"\"") == some(string("\"")));
|
assert to_str(num(3.1f)) == "3.1";
|
||||||
assert(from_str("\"lol") == none);
|
assert to_str(num(-1.5f)) == "-1.5";
|
||||||
|
assert to_str(num(0.5f)) == "0.5";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_str_bool() {
|
fn test_write_str() {
|
||||||
assert(from_str("true") == some(boolean(true)));
|
assert to_str(string("")) == "\"\"";
|
||||||
assert(from_str("false") == some(boolean(false)));
|
assert to_str(string("foo")) == "\"foo\"";
|
||||||
assert(from_str("truz") == none);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_str_list() {
|
fn test_write_bool() {
|
||||||
assert(from_str("[]") == some(list(@[])));
|
assert to_str(boolean(true)) == "true";
|
||||||
assert(from_str("[true]") == some(list(@[boolean(true)])));
|
assert to_str(boolean(false)) == "false";
|
||||||
assert(from_str("[null]") == some(list(@[null])));
|
|
||||||
assert(from_str("[3, 1]") == some(list(@[num(3f), num(1f)])));
|
|
||||||
assert(from_str("[2, [4, 1]]") ==
|
|
||||||
some(list(@[num(2f), list(@[num(4f), num(1f)])])));
|
|
||||||
assert(from_str("[2, ]") == none);
|
|
||||||
assert(from_str("[5, ") == none);
|
|
||||||
assert(from_str("[6 7]") == none);
|
|
||||||
assert(from_str("[3") == none);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_str_dict() {
|
fn test_write_list() {
|
||||||
assert(from_str("{}") != none);
|
assert to_str(list([])) == "[]";
|
||||||
assert(from_str("{\"a\": 3}") != none);
|
assert to_str(list([boolean(true)])) == "[true]";
|
||||||
assert(from_str("{\"a\": null}") != none);
|
assert to_str(list([
|
||||||
assert(from_str("{\"a\": }") == none);
|
boolean(false),
|
||||||
assert(from_str("{\"a\" }") == none);
|
null,
|
||||||
assert(from_str("{\"a\"") == none);
|
list([string("foo\nbar"), num(3.5f)])
|
||||||
assert(from_str("{") == none);
|
])) == "[false, null, [\"foo\\nbar\", 3.5]]";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_dict() {
|
||||||
|
assert to_str(mk_dict([])) == "{}";
|
||||||
|
assert to_str(mk_dict([("a", boolean(true))])) == "{ \"a\": true }";
|
||||||
|
assert to_str(mk_dict([
|
||||||
|
("a", boolean(true)),
|
||||||
|
("b", list([
|
||||||
|
mk_dict([("c", string("\x0c\r"))]),
|
||||||
|
mk_dict([("d", string(""))])
|
||||||
|
]))
|
||||||
|
])) ==
|
||||||
|
"{ " +
|
||||||
|
"\"a\": true, " +
|
||||||
|
"\"b\": [" +
|
||||||
|
"{ \"c\": \"\\f\\r\" }, " +
|
||||||
|
"{ \"d\": \"\" }" +
|
||||||
|
"]" +
|
||||||
|
" }";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trailing_characters() {
|
||||||
|
assert from_str("nulla") ==
|
||||||
|
err({line: 1u, col: 5u, msg: "trailing characters"});
|
||||||
|
assert from_str("truea") ==
|
||||||
|
err({line: 1u, col: 5u, msg: "trailing characters"});
|
||||||
|
assert from_str("falsea") ==
|
||||||
|
err({line: 1u, col: 6u, msg: "trailing characters"});
|
||||||
|
assert from_str("1a") ==
|
||||||
|
err({line: 1u, col: 2u, msg: "trailing characters"});
|
||||||
|
assert from_str("[]a") ==
|
||||||
|
err({line: 1u, col: 3u, msg: "trailing characters"});
|
||||||
|
assert from_str("{}a") ==
|
||||||
|
err({line: 1u, col: 3u, msg: "trailing characters"});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_identifiers() {
|
||||||
|
assert from_str("n") ==
|
||||||
|
err({line: 1u, col: 2u, msg: "invalid syntax"});
|
||||||
|
assert from_str("nul") ==
|
||||||
|
err({line: 1u, col: 4u, msg: "invalid syntax"});
|
||||||
|
|
||||||
|
assert from_str("t") ==
|
||||||
|
err({line: 1u, col: 2u, msg: "invalid syntax"});
|
||||||
|
assert from_str("truz") ==
|
||||||
|
err({line: 1u, col: 4u, msg: "invalid syntax"});
|
||||||
|
|
||||||
|
assert from_str("f") ==
|
||||||
|
err({line: 1u, col: 2u, msg: "invalid syntax"});
|
||||||
|
assert from_str("faz") ==
|
||||||
|
err({line: 1u, col: 3u, msg: "invalid syntax"});
|
||||||
|
|
||||||
|
assert from_str("null") == ok(null);
|
||||||
|
assert from_str("true") == ok(boolean(true));
|
||||||
|
assert from_str("false") == ok(boolean(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_num() {
|
||||||
|
assert from_str("+") ==
|
||||||
|
err({line: 1u, col: 1u, msg: "invalid syntax"});
|
||||||
|
assert from_str(".") ==
|
||||||
|
err({line: 1u, col: 1u, msg: "invalid syntax"});
|
||||||
|
|
||||||
|
assert from_str("-") ==
|
||||||
|
err({line: 1u, col: 2u, msg: "invalid number"});
|
||||||
|
assert from_str("00") ==
|
||||||
|
err({line: 1u, col: 2u, msg: "invalid number"});
|
||||||
|
assert from_str("1.") ==
|
||||||
|
err({line: 1u, col: 3u, msg: "invalid number"});
|
||||||
|
assert from_str("1e") ==
|
||||||
|
err({line: 1u, col: 3u, msg: "invalid number"});
|
||||||
|
assert from_str("1e+") ==
|
||||||
|
err({line: 1u, col: 4u, msg: "invalid number"});
|
||||||
|
|
||||||
|
assert from_str("3") == ok(num(3f));
|
||||||
|
assert from_str("3.1") == ok(num(3.1f));
|
||||||
|
assert from_str("-1.2") == ok(num(-1.2f));
|
||||||
|
assert from_str("0.4") == ok(num(0.4f));
|
||||||
|
assert from_str("0.4e5") == ok(num(0.4e5f));
|
||||||
|
assert from_str("0.4e+15") == ok(num(0.4e15f));
|
||||||
|
assert from_str("0.4e-01") == ok(num(0.4e-01f));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_str() {
|
||||||
|
assert from_str("\"") ==
|
||||||
|
err({line: 1u, col: 2u, msg: "EOF while parsing string"});
|
||||||
|
assert from_str("\"lol") ==
|
||||||
|
err({line: 1u, col: 5u, msg: "EOF while parsing string"});
|
||||||
|
|
||||||
|
assert from_str("\"\"") == ok(string(""));
|
||||||
|
assert from_str("\"foo\"") == ok(string("foo"));
|
||||||
|
assert from_str("\"\\\"\"") == ok(string("\""));
|
||||||
|
assert from_str("\"\\b\"") == ok(string("\x08"));
|
||||||
|
assert from_str("\"\\n\"") == ok(string("\n"));
|
||||||
|
assert from_str("\"\\r\"") == ok(string("\r"));
|
||||||
|
assert from_str("\"\\t\"") == ok(string("\t"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_list() {
|
||||||
|
assert from_str("[") ==
|
||||||
|
err({line: 1u, col: 2u, msg: "EOF while parsing value"});
|
||||||
|
assert from_str("[1") ==
|
||||||
|
err({line: 1u, col: 3u, msg: "EOF while parsing list"});
|
||||||
|
assert from_str("[1,") ==
|
||||||
|
err({line: 1u, col: 4u, msg: "EOF while parsing value"});
|
||||||
|
assert from_str("[1,]") ==
|
||||||
|
err({line: 1u, col: 4u, msg: "invalid syntax"});
|
||||||
|
assert from_str("[6 7]") ==
|
||||||
|
err({line: 1u, col: 4u, msg: "expecting ',' or ']'"});
|
||||||
|
|
||||||
|
assert from_str("[]") == ok(list([]));
|
||||||
|
assert from_str("[ ]") == ok(list([]));
|
||||||
|
assert from_str("[true]") == ok(list([boolean(true)]));
|
||||||
|
assert from_str("[ false ]") == ok(list([boolean(false)]));
|
||||||
|
assert from_str("[null]") == ok(list([null]));
|
||||||
|
assert from_str("[3, 1]") == ok(list([num(3f), num(1f)]));
|
||||||
|
assert from_str("[2, [4, 1]]") ==
|
||||||
|
ok(list([num(2f), list([num(4f), num(1f)])]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_dict() {
|
||||||
|
assert from_str("{") ==
|
||||||
|
err({line: 1u, col: 2u, msg: "EOF while parsing object"});
|
||||||
|
assert from_str("{ ") ==
|
||||||
|
err({line: 1u, col: 3u, msg: "EOF while parsing object"});
|
||||||
|
assert from_str("{1") ==
|
||||||
|
err({line: 1u, col: 2u, msg: "key must be a string"});
|
||||||
|
assert from_str("{ \"a\"") ==
|
||||||
|
err({line: 1u, col: 6u, msg: "EOF while parsing object"});
|
||||||
|
assert from_str("{\"a\"") ==
|
||||||
|
err({line: 1u, col: 5u, msg: "EOF while parsing object"});
|
||||||
|
assert from_str("{\"a\" ") ==
|
||||||
|
err({line: 1u, col: 6u, msg: "EOF while parsing object"});
|
||||||
|
|
||||||
|
assert from_str("{\"a\" 1") ==
|
||||||
|
err({line: 1u, col: 6u, msg: "expecting ':'"});
|
||||||
|
assert from_str("{\"a\":") ==
|
||||||
|
err({line: 1u, col: 6u, msg: "EOF while parsing value"});
|
||||||
|
assert from_str("{\"a\":1") ==
|
||||||
|
err({line: 1u, col: 7u, msg: "EOF while parsing object"});
|
||||||
|
assert from_str("{\"a\":1 1") ==
|
||||||
|
err({line: 1u, col: 8u, msg: "expecting ',' or '}'"});
|
||||||
|
assert from_str("{\"a\":1,") ==
|
||||||
|
err({line: 1u, col: 8u, msg: "EOF while parsing object"});
|
||||||
|
|
||||||
|
assert eq(result::get(from_str("{}")), mk_dict([]));
|
||||||
|
assert eq(result::get(from_str("{\"a\": 3}")),
|
||||||
|
mk_dict([("a", num(3.0f))]));
|
||||||
|
|
||||||
|
assert eq(result::get(from_str("{ \"a\": null, \"b\" : true }")),
|
||||||
|
mk_dict([("a", null), ("b", boolean(true))]));
|
||||||
|
assert eq(result::get(from_str("{\"a\" : 1.0 ,\"b\": [ true ]}")),
|
||||||
|
mk_dict([
|
||||||
|
("a", num(1.0)),
|
||||||
|
("b", list([boolean(true)]))
|
||||||
|
]));
|
||||||
|
assert eq(result::get(from_str(
|
||||||
|
"{" +
|
||||||
|
"\"a\": 1.0, " +
|
||||||
|
"\"b\": [" +
|
||||||
|
"true," +
|
||||||
|
"\"foo\\nbar\", " +
|
||||||
|
"{ \"c\": {\"d\": null} } " +
|
||||||
|
"]" +
|
||||||
|
"}")),
|
||||||
|
mk_dict([
|
||||||
|
("a", num(1.0f)),
|
||||||
|
("b", list([
|
||||||
|
boolean(true),
|
||||||
|
string("foo\nbar"),
|
||||||
|
mk_dict([
|
||||||
|
("c", mk_dict([("d", null)]))
|
||||||
|
])
|
||||||
|
]))
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiline_errors() {
|
||||||
|
assert from_str("{\n \"foo\":\n \"bar\"") ==
|
||||||
|
err({line: 3u, col: 8u, msg: "EOF while parsing object"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue