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/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 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/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; diff --git a/src/libstd/io.rs b/src/libstd/io.rs index 1efd91d54dbd8..133a2e83b57bc 100644 --- a/src/libstd/io.rs +++ b/src/libstd/io.rs @@ -506,8 +506,22 @@ 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(); + 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 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"}); } }