From 5a32bca48ded8e168a3a77de1ea21086f68904c8 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Tue, 21 Feb 2012 07:31:01 -0800 Subject: [PATCH 1/5] vim: add "to" as a keyword. --- src/etc/vim/syntax/rust.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/etc/vim/syntax/rust.vim b/src/etc/vim/syntax/rust.vim index 3e7fb347084f3..06c4952ddf0f3 100644 --- a/src/etc/vim/syntax/rust.vim +++ b/src/etc/vim/syntax/rust.vim @@ -17,8 +17,8 @@ endif syn keyword rustKeyword alt as assert be bind break syn keyword rustKeyword check claim cont const copy do else enum export fail syn keyword rustKeyword fn for if iface impl import in inline lambda let log -syn keyword rustKeyword mod mutable native note of prove pure -syn keyword rustKeyword resource ret self syntax type unchecked +syn keyword rustKeyword mod mut mutable native note of prove pure +syn keyword rustKeyword resource ret self syntax to type unchecked syn keyword rustKeyword unsafe use while with " Reserved words From 215af95a86300831595d70da24aa578a9ab3ccab Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Tue, 21 Feb 2012 09:22:50 -0800 Subject: [PATCH 2/5] std: Add helpers to simplify making str io::writers --- src/libstd/io.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libstd/io.rs b/src/libstd/io.rs index 1efd91d54dbd8..253853c676292 100644 --- a/src/libstd/io.rs +++ b/src/libstd/io.rs @@ -508,6 +508,19 @@ fn mem_buffer_buf(b: mem_buffer) -> [u8] { vec::from_mut(b.buf) } fn mem_buffer_str(b: mem_buffer) -> str { let b_ = vec::from_mut(b.buf); str::from_bytes(b_) + +fn with_str_writer(f: fn(writer)) -> str { + let buf = mk_mem_buffer(); + let wr = mem_buffer_writer(buf); + f(wr); + io::mem_buffer_str(buf) +} + +fn with_buf_writer(f: fn(writer)) -> [u8] { + let buf = mk_mem_buffer(); + let wr = mem_buffer_writer(buf); + f(wr); + io::mem_buffer_buf(buf) } // Utility functions From 51b55cec1222fc67938dc09582c628f4a6ac8b99 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Tue, 21 Feb 2012 09:23:01 -0800 Subject: [PATCH 3/5] core/std: whitespace fixes. --- src/libcore/core.rs | 2 +- src/libstd/io.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libcore/core.rs b/src/libcore/core.rs index 0c6d4daa4aa77..c68e4ddf61751 100644 --- a/src/libcore/core.rs +++ b/src/libcore/core.rs @@ -3,7 +3,7 @@ // Export type option as a synonym for option and export the some and none // enum constructors. -import option::{some, none}; +import option::{some, none}; import option = option::t; import vec::vec_len; export option, some, none, vec_len; diff --git a/src/libstd/io.rs b/src/libstd/io.rs index 253853c676292..133a2e83b57bc 100644 --- a/src/libstd/io.rs +++ b/src/libstd/io.rs @@ -506,8 +506,9 @@ fn mk_mem_buffer() -> mem_buffer { fn mem_buffer_writer(b: mem_buffer) -> writer { b as writer } fn mem_buffer_buf(b: mem_buffer) -> [u8] { vec::from_mut(b.buf) } fn mem_buffer_str(b: mem_buffer) -> str { - let b_ = vec::from_mut(b.buf); - str::from_bytes(b_) + let b_ = vec::from_mut(b.buf); + str::from_bytes(b_) +} fn with_str_writer(f: fn(writer)) -> str { let buf = mk_mem_buffer(); From f7e6213cfed7bf2ac7ba7351cbe6b181d2fe2ecd Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Fri, 24 Feb 2012 00:07:05 -0800 Subject: [PATCH 4/5] expose float::pow_with_uint. --- src/libcore/float.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libcore/float.rs b/src/libcore/float.rs index cea46dedf44d6..6f23349d2a997 100644 --- a/src/libcore/float.rs +++ b/src/libcore/float.rs @@ -19,6 +19,7 @@ export mul_add, fmax, fmin, nextafter, frexp, hypot, ldexp; export lgamma, ln, log_radix, ln1p, log10, log2, ilog_radix; export modf, pow, round, sin, sinh, sqrt, tan, tanh, tgamma, trunc; export signbit; +export pow_with_uint; // export when m_float == c_double @@ -55,7 +56,7 @@ fn to_str_common(num: float, digits: uint, exact: bool) -> str { if (frac < epsilon && !exact) || digits == 0u { ret accum; } accum += "."; let i = digits; - let epsilon = 1. / pow_uint_to_uint_as_float(10u, i); + let epsilon = 1. / pow_with_uint(10u, i); while i > 0u && (frac >= epsilon || exact) { frac *= 10.0; epsilon *= 10.0; @@ -228,7 +229,7 @@ fn from_str(num: str) -> option { } pos = char_range.next; } - let multiplier = pow_uint_to_uint_as_float(10u, exponent); + let multiplier = pow_with_uint(10u, exponent); //Note: not [int::pow], otherwise, we'll quickly //end up with a nice overflow if neg_exponent { @@ -256,7 +257,7 @@ fn from_str(num: str) -> option { */ /* -Function: pow_uint_to_uint_as_float +Function: pow_with_uint Compute the exponentiation of an integer by another integer as a float. @@ -267,8 +268,8 @@ pow - The exponent. Returns: of both `x` and `pow` are `0u`, otherwise `x^pow`. */ -fn pow_uint_to_uint_as_float(x: uint, pow: uint) -> float { - if x == 0u { +fn pow_with_uint(base: uint, pow: uint) -> float { + if base == 0u { if pow == 0u { ret NaN; } @@ -276,7 +277,7 @@ fn pow_uint_to_uint_as_float(x: uint, pow: uint) -> float { } let my_pow = pow; let total = 1f; - let multiplier = x as float; + let multiplier = base as float; while (my_pow > 0u) { if my_pow % 2u == 1u { total = total * multiplier; From 17e8b11714d5def6b893f93aa593e8265fee2d2e Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Sat, 25 Feb 2012 16:39:32 -0800 Subject: [PATCH 5/5] 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. --- src/cargo/cargo.rs | 28 +- src/libstd/json.rs | 867 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 669 insertions(+), 226 deletions(-) diff --git a/src/cargo/cargo.rs b/src/cargo/cargo.rs index ea5953fdf8906..a4816fedf970e 100644 --- a/src/cargo/cargo.rs +++ b/src/cargo/cargo.rs @@ -9,6 +9,7 @@ import rustc::util::filesearch::{get_cargo_root, get_cargo_root_nearest, get_cargo_sysroot, libdir}; import rustc::driver::diagnostic; +import result::{ok, err}; import std::fs; import std::io; 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) { if !fs::path_exists(filename) { ret; } let c = io::read_whole_file_str(filename); - let j = json::from_str(result::get(c)); - alt j { - some(json::dict(_j)) { - _j.items { |k, v| + alt json::from_str(result::get(c)) { + ok(json::dict(j)) { + j.items { |k, v| sources.insert(k, parse_source(k, v)); #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) { let tags = []; alt p.find("tags") { some(json::list(js)) { - for j in *js { + for j in js { alt 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"); if !fs::path_exists(pkgfile) { ret; } let pkgstr = io::read_whole_file_str(pkgfile); - let j = json::from_str(result::get(pkgstr)); - alt j { - some(json::list(js)) { - for _j: json::json in *js { + alt json::from_str(result::get(pkgstr)) { + ok(json::list(js)) { + for _j: json::json in js { alt _j { json::dict(_p) { load_one_source_package(src, _p); @@ -331,8 +331,12 @@ fn load_source_packages(&c: cargo, &src: source) { } } } - _ { - warn("Malformed source json: " + src.name); + ok(_) { + 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)); } }; } diff --git a/src/libstd/json.rs b/src/libstd/json.rs index 8de15a60c8744..d2bbc7dc5e736 100644 --- a/src/libstd/json.rs +++ b/src/libstd/json.rs @@ -1,18 +1,24 @@ // Rust JSON serialization library // Copyright (c) 2011 Google Inc. -import float; +import result::{ok, err}; +import io; +import io::{reader_util, writer_util}; import map; export json; +export to_writer; export to_str; +export from_reader; export from_str; +export eq; export num; export string; export boolean; export list; export dict; +export null; /* Tag: json @@ -27,233 +33,453 @@ enum json { /* Variant: boolean */ boolean(bool), /* Variant: list */ - list(@[json]), + list([json]), /* Variant: dict */ dict(map::map), /* Variant: null */ null, } +type error = { + line: uint, + col: uint, + msg: str, +}; + /* -Function: to_str +Function: to_writer -Serializes a json value into a string. +Serializes a json value into a io::writer. */ -fn to_str(j: json) -> str { +fn to_writer(wr: io::writer, j: json) { alt 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::(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, ", "), " }"]) + 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; } - null { "null" } + + 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"); + } } } -fn rest(s: str) -> str { - assert(str::len(s) >= 1u); - str::slice(s, 1u, str::len(s)) +/* +Function: to_str + +Serializes a json value into a string. +*/ +fn to_str(j: json) -> str { + io::with_str_writer { |wr| to_writer(wr, j) } } -fn from_str_str(s: str) -> (option, str) { - let pos = 0u; - let len = str::len(s); - let escape = false; - let res = ""; +type parser = { + rdr: io::reader, + mutable ch: char, + mutable line: uint, + mutable col: uint, +}; - alt str::char_at(s, 0u) { - '"' { pos = 1u; } - _ { ret (none, s); } - } +impl parser for parser { + fn eof() -> bool { self.ch == -1 as char } + + fn bump() { + self.ch = self.rdr.read_char(); - while (pos < len) { - let chr = str::char_range_at(s, pos); - let c = chr.ch; - pos = chr.next; - if (escape) { - res = res + str::from_char(c); - escape = false; - cont; + if self.ch == '\n' { + self.line += 1u; + self.col = 1u; + } else { + self.col += 1u; } - if (c == '\\') { - escape = true; - cont; - } else if (c == '"') { - ret (some(string(res)), - str::slice(s, pos, str::len(s))); + } + + fn next_char() -> char { + self.bump(); + self.ch + } + + fn error(msg: str) -> result::t { + err({ line: self.line, col: self.col, msg: msg }) + } + + fn parse() -> result::t { + alt self.parse_value() { + ok(value) { + // Make sure there is no trailing characters. + if self.eof() { + ok(value) + } else { + self.error("trailing characters") + } + } + e { e } } - res = res + str::from_char(c); } - ret (none, s); -} + fn parse_value() -> result::t { + self.parse_whitespace(); + + if self.eof() { ret self.error("EOF while parsing value"); } -fn from_str_list(s: str) -> (option, 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); } + 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") } } - 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); } + } + + fn parse_whitespace() { + while char::is_whitespace(self.ch) { self.bump(); } + } + + fn parse_ident(ident: str, value: json) -> result::t { + if str::all(ident, { |c| c == self.next_char() }) { + self.bump(); + ok(value) + } else { + self.error("invalid syntax") } - s0 = rest(s0); } - ret (none, s0); -} -fn from_str_dict(s: str) -> (option, str) { - if str::char_at(s, 0u) != '{' { ret (none, s); } - let s0 = str::trim_left(rest(s)); - let vals = map::new_str_hash::(); - if str::is_empty(s0) { ret (none, s0); } - if str::char_at(s0, 0u) == '}' { ret (some(dict(vals)), rest(s0)); } - while str::is_not_empty(s0) { - s0 = str::trim_left(s0); - let (next, s1) = from_str_helper(s0); // key - let key = ""; - s0 = s1; - alt next { - some(string(k)) { key = k; } - _ { ret (none, s0); } + fn parse_number() -> result::t { + let neg = 1f; + + if self.ch == '-' { + self.bump(); + neg = -1f; } - 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); } + + let res = alt self.parse_integer() { + ok(res) { res } + err(e) { ret err(e); } + }; + + if self.ch == '.' { + alt self.parse_decimal(res) { + ok(r) { res = r; } + err(e) { ret err(e); } + } } - 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); } + + if self.ch == 'e' || self.ch == 'E' { + alt self.parse_exponent(res) { + ok(r) { res = r; } + err(e) { ret err(e); } + } } - s0 = str::trim_left(rest(s0)); + + ok(num(neg * res)) } - (none, s) -} -fn from_str_float(s: str) -> (option, str) { - let pos = 0u; - let len = str::len(s); - let res = 0f; - let neg = 1f; + fn parse_integer() -> result::t { + let res = 0f; - alt str::char_at(s, 0u) { - '-' { - neg = -1f; - pos = 1u; - } - '+' { - pos = 1u; + 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"); } } - '0' to '9' | '.' { } - _ { ret (none, s); } + + ok(res) } - while (pos < len) { - let opos = pos; - let chr = str::char_range_at(s, pos); - let c = chr.ch; - pos = chr.next; - alt c { - '0' to '9' { - res = res * 10f; - res += ((c as int) - ('0' as int)) as float; + fn parse_decimal(res: float) -> result::t { + self.bump(); + + // Make sure a digit follows the decimal place. + alt self.ch { + '0' to '9' {} + _ { ret self.error("invalid number"); } + } + + let res = res; + let dec = 1f; + while !self.eof() { + alt self.ch { + '0' to '9' { + dec /= 10f; + res += (((self.ch as int) - ('0' as int)) as float) * dec; + + self.bump(); + } + _ { break; } } - '.' { break; } - _ { ret (some(num(neg * res)), - str::slice(s, opos, str::len(s))); } } + + ok(res) } - if pos == len { - ret (some(num(neg * res)), - str::slice(s, pos, str::len(s))); + fn parse_exponent(res: float) -> result::t { + self.bump(); + + 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) } - let dec = 1f; - while (pos < len) { - let opos = pos; - let chr = str::char_range_at(s, pos); - let c = chr.ch; - pos = chr.next; - alt c { - '0' to '9' { - dec /= 10f; - res += (((c as int) - ('0' as int)) as float) * dec; + fn parse_str() -> result::t { + 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); } - _ { ret (some(num(neg * res)), - str::slice(s, opos, str::len(s))); } } + + self.error("EOF while parsing string") } - ret (some(num(neg * res)), str::slice(s, pos, str::len(s))); -} -fn from_str_bool(s: str) -> (option, str) { - if (str::starts_with(s, "true")) { - (some(boolean(true)), str::slice(s, 4u, str::len(s))) - } else if (str::starts_with(s, "false")) { - (some(boolean(false)), str::slice(s, 5u, str::len(s))) - } else { - (none, s) + fn parse_list() -> result::t { + 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 from_str_null(s: str) -> (option, str) { - if (str::starts_with(s, "null")) { - (some(null), str::slice(s, 4u, str::len(s))) - } else { - (none, s) + fn parse_object() -> result::t { + 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_helper(s: str) -> (option, str) { - let s = str::trim_left(s); - if str::is_empty(s) { ret (none, s); } - let start = str::char_at(s, 0u); - alt start { - '"' { from_str_str(s) } - '[' { from_str_list(s) } - '{' { from_str_dict(s) } - '0' to '9' | '-' | '+' | '.' { from_str_float(s) } - 't' | 'f' { from_str_bool(s) } - 'n' { from_str_null(s) } - _ { ret (none, s); } - } +/* +Function: from_reader + +Deserializes a json value from an io::reader. +*/ + +fn from_reader(rdr: io::reader) -> result::t { + let parser = { + rdr: rdr, + mutable ch: rdr.read_char(), + mutable line: 1u, + mutable col: 1u, + }; + + parser.parse() } /* @@ -261,62 +487,275 @@ Function: from_str Deserializes a json value from a string. */ -fn from_str(s: str) -> option { - let (j, _) = from_str_helper(s); - j +fn from_str(s: str) -> result::t { + from_reader(io::string_reader(s)) +} + +/* +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)] mod tests { + fn mk_dict(items: [(str, json)]) -> json { + let d = map::new_str_hash(); + + vec::iter(items) { |item| + let (key, value) = item; + d.insert(key, value); + }; + + dict(d) + } + + #[test] + fn test_write_null() { + assert to_str(null) == "null"; + } + + #[test] + fn test_write_num() { + assert to_str(num(3f)) == "3"; + assert to_str(num(3.1f)) == "3.1"; + assert to_str(num(-1.5f)) == "-1.5"; + assert to_str(num(0.5f)) == "0.5"; + } + + #[test] + fn test_write_str() { + assert to_str(string("")) == "\"\""; + assert to_str(string("foo")) == "\"foo\""; + } + + #[test] + fn test_write_bool() { + assert to_str(boolean(true)) == "true"; + assert to_str(boolean(false)) == "false"; + } + + #[test] + fn test_write_list() { + assert to_str(list([])) == "[]"; + assert to_str(list([boolean(true)])) == "[true]"; + assert to_str(list([ + boolean(false), + null, + list([string("foo\nbar"), num(3.5f)]) + ])) == "[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_from_str_null() { - assert(from_str("null") == some(null)); + 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_from_str_num() { - assert(from_str("3") == some(num(3f))); - 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))); + 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_from_str_str() { - assert(from_str("\"foo\"") == some(string("foo"))); - assert(from_str("\"\\\"\"") == some(string("\""))); - assert(from_str("\"lol") == none); + 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_from_str_bool() { - assert(from_str("true") == some(boolean(true))); - assert(from_str("false") == some(boolean(false))); - assert(from_str("truz") == none); + 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_from_str_list() { - assert(from_str("[]") == some(list(@[]))); - assert(from_str("[true]") == some(list(@[boolean(true)]))); - 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); + 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_from_str_dict() { - assert(from_str("{}") != none); - assert(from_str("{\"a\": 3}") != none); - assert(from_str("{\"a\": null}") != none); - assert(from_str("{\"a\": }") == none); - assert(from_str("{\"a\" }") == none); - assert(from_str("{\"a\"") == none); - assert(from_str("{") == none); + fn test_multiline_errors() { + assert from_str("{\n \"foo\":\n \"bar\"") == + err({line: 3u, col: 8u, msg: "EOF while parsing object"}); } }