From bd7262624ca7b396cfb254dc638f62d07e381681 Mon Sep 17 00:00:00 2001 From: Elly Jones Date: Mon, 7 Nov 2011 14:01:28 -0500 Subject: [PATCH] stdlib: add json. Add a json serializer and deserializer. Signed-off-by: Elly Jones --- src/lib/json.rs | 293 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib/std.rc | 1 + 2 files changed, 294 insertions(+) create mode 100644 src/lib/json.rs diff --git a/src/lib/json.rs b/src/lib/json.rs new file mode 100644 index 000000000000..02d5d3d5e7e3 --- /dev/null +++ b/src/lib/json.rs @@ -0,0 +1,293 @@ +#[link(name = "json", + vers = "0.1", + uuid = "09d7f2fc-1fad-48b2-9f9d-a65512342f16", + url = "http://www.leptoquark.net/~elly/rust/")]; +#[copyright = "Google Inc. 2011"]; +#[comment = "JSON serialization format library"]; +#[license = "BSD"]; + +import float; +import map; +import option; +import option::{some, none}; +import str; +import vec; + +export json; +export tostr; +export fromstr; + +tag json { + num(float); + string(str); + boolean(bool); + list(@[json]); + dict(map::hashmap); +} + +fn tostr(j: json) -> str { + 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::({ |e| tostr(e) }, js), + ", "), + "]"]) + } + dict(m) { + let parts = []; + m.items({ |k, v| + vec::grow(parts, 1u, + str::concat(["\"", k, "\": ", tostr(v)]) + ) + }); + str::concat(["{ ", str::connect(parts, ", "), " }"]) + } + } +} + +fn rest(s: str) -> str { + str::char_slice(s, 1u, str::char_len(s)) +} + +fn fromstr_str(s: str) -> (option::t, str) { + let pos = 0u; + let len = str::byte_len(s); + let escape = false; + let res = ""; + + alt str::char_at(s, 0u) { + '"' { pos = 1u; } + _ { ret (none, s); } + } + + 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 (c == '\\') { + escape = true; + cont; + } else if (c == '"') { + ret (some(string(res)), str::char_slice(s, pos, str::char_len(s))); + } + res = res + str::from_char(c); + } + + ret (none, s); +} + +fn fromstr_list(s: str) -> (option::t, 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) = fromstr_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 fromstr_dict(s: str) -> (option::t, 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) = fromstr_helper(s0); // key + let key = ""; + s0 = s1; + 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) = fromstr_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 fromstr_float(s: str) -> (option::t, str) { + let pos = 0u; + let len = str::byte_len(s); + let res = 0f; + let neg = 1.f; + + alt str::char_at(s, 0u) { + '-' { + neg = -1.f; + pos = 1u; + } + '+' { + pos = 1u; + } + '0' to '9' | '.' { } + _ { ret (none, s); } + } + + 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; + } + '.' { break; } + _ { ret (some(num(neg * res)), + str::char_slice(s, opos, str::char_len(s))); } + } + } + + if pos == len { + ret (some(num(neg * res)), str::char_slice(s, pos, str::char_len(s))); + } + + let dec = 1.f; + 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 /= 10.f; + res += (((c as int) - ('0' as int)) as float) * dec; + } + _ { ret (some(num(neg * res)), + str::char_slice(s, opos, str::char_len(s))); } + } + } + ret (some(num(neg * res)), str::char_slice(s, pos, str::char_len(s))); +} + +fn fromstr_bool(s: str) -> (option::t, str) { + if (str::starts_with(s, "true")) { + (some(boolean(true)), str::slice(s, 4u, str::byte_len(s))) + } else if (str::starts_with(s, "false")) { + (some(boolean(false)), str::slice(s, 5u, str::byte_len(s))) + } else { + (none, s) + } +} + +fn fromstr_helper(s: str) -> (option::t, str) { + let s = str::trim_left(s); + if str::is_empty(s) { ret (none, s); } + let start = str::char_at(s, 0u); + alt start { + '"' { fromstr_str(s) } + '[' { fromstr_list(s) } + '{' { fromstr_dict(s) } + '0' to '9' | '-' | '+' | '.' { fromstr_float(s) } + 't' | 'f' { fromstr_bool(s) } + _ { ret (none, s); } + } +} + +fn fromstr(s: str) -> option::t { + let (j, _) = fromstr_helper(s); + j +} + +fn main() { + let j = fromstr("{ \"foo\": [ 4, 5 ], \"bar\": { \"baz\": true}}"); + alt j { + some(j0) { + log tostr(j0); + } + _ { } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_fromstr_num() { + assert(fromstr("3") == some(num(3f))); + assert(fromstr("3.1") == some(num(3.1f))); + assert(fromstr("-1.2") == some(num(-1.2f))); + assert(fromstr(".4") == some(num(0.4f))); + } + + #[test] + fn test_fromstr_str() { + assert(fromstr("\"foo\"") == some(string("foo"))); + assert(fromstr("\"\\\"\"") == some(string("\""))); + assert(fromstr("\"lol") == none); + } + + #[test] + fn test_fromstr_bool() { + assert(fromstr("true") == some(boolean(true))); + assert(fromstr("false") == some(boolean(false))); + assert(fromstr("truz") == none); + } + + #[test] + fn test_fromstr_list() { + assert(fromstr("[]") == some(list(@[]))); + assert(fromstr("[true]") == some(list(@[boolean(true)]))); + assert(fromstr("[3, 1]") == some(list(@[num(3f), num(1f)]))); + assert(fromstr("[2, [4, 1]]") == + some(list(@[num(2f), list(@[num(4f), num(1f)])]))); + assert(fromstr("[2, ]") == none); + assert(fromstr("[5, ") == none); + assert(fromstr("[6 7]") == none); + assert(fromstr("[3") == none); + } + + #[test] + fn test_fromstr_dict() { + assert(fromstr("{}") != none); + assert(fromstr("{\"a\": 3}") != none); + assert(fromstr("{\"a\": }") == none); + assert(fromstr("{\"a\" }") == none); + assert(fromstr("{\"a\"") == none); + assert(fromstr("{") == none); + } +} diff --git a/src/lib/std.rc b/src/lib/std.rc index 6906b7f7f830..86335d6312ea 100644 --- a/src/lib/std.rc +++ b/src/lib/std.rc @@ -74,6 +74,7 @@ mod ufind; mod ebml; mod dbg; mod getopts; +mod json; mod math; mod rand; mod sha1;