Skip to content

Commit bd72626

Browse files
Elly Jonesbrson
Elly Jones
authored andcommitted
stdlib: add json.
Add a json serializer and deserializer. Signed-off-by: Elly Jones <[email protected]>
1 parent fcd39b1 commit bd72626

File tree

2 files changed

+294
-0
lines changed

2 files changed

+294
-0
lines changed

src/lib/json.rs

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
#[link(name = "json",
2+
vers = "0.1",
3+
uuid = "09d7f2fc-1fad-48b2-9f9d-a65512342f16",
4+
url = "http://www.leptoquark.net/~elly/rust/")];
5+
#[copyright = "Google Inc. 2011"];
6+
#[comment = "JSON serialization format library"];
7+
#[license = "BSD"];
8+
9+
import float;
10+
import map;
11+
import option;
12+
import option::{some, none};
13+
import str;
14+
import vec;
15+
16+
export json;
17+
export tostr;
18+
export fromstr;
19+
20+
tag json {
21+
num(float);
22+
string(str);
23+
boolean(bool);
24+
list(@[json]);
25+
dict(map::hashmap<str,json>);
26+
}
27+
28+
fn tostr(j: json) -> str {
29+
alt j {
30+
num(f) { float::to_str(f, 6u) }
31+
string(s) { #fmt["\"%s\"", s] } // XXX: escape
32+
boolean(true) { "true" }
33+
boolean(false) { "false" }
34+
list(@js) {
35+
str::concat(["[",
36+
str::connect(
37+
vec::map::<json,str>({ |e| tostr(e) }, js),
38+
", "),
39+
"]"])
40+
}
41+
dict(m) {
42+
let parts = [];
43+
m.items({ |k, v|
44+
vec::grow(parts, 1u,
45+
str::concat(["\"", k, "\": ", tostr(v)])
46+
)
47+
});
48+
str::concat(["{ ", str::connect(parts, ", "), " }"])
49+
}
50+
}
51+
}
52+
53+
fn rest(s: str) -> str {
54+
str::char_slice(s, 1u, str::char_len(s))
55+
}
56+
57+
fn fromstr_str(s: str) -> (option::t<json>, str) {
58+
let pos = 0u;
59+
let len = str::byte_len(s);
60+
let escape = false;
61+
let res = "";
62+
63+
alt str::char_at(s, 0u) {
64+
'"' { pos = 1u; }
65+
_ { ret (none, s); }
66+
}
67+
68+
while (pos < len) {
69+
let chr = str::char_range_at(s, pos);
70+
let c = chr.ch;
71+
pos = chr.next;
72+
if (escape) {
73+
res = res + str::from_char(c);
74+
escape = false;
75+
cont;
76+
}
77+
if (c == '\\') {
78+
escape = true;
79+
cont;
80+
} else if (c == '"') {
81+
ret (some(string(res)), str::char_slice(s, pos, str::char_len(s)));
82+
}
83+
res = res + str::from_char(c);
84+
}
85+
86+
ret (none, s);
87+
}
88+
89+
fn fromstr_list(s: str) -> (option::t<json>, str) {
90+
if str::char_at(s, 0u) != '[' { ret (none, s); }
91+
let s0 = str::trim_left(rest(s));
92+
let vals = [];
93+
if str::is_empty(s0) { ret (none, s0); }
94+
if str::char_at(s0, 0u) == ']' { ret (some(list(@[])), rest(s0)); }
95+
while str::is_not_empty(s0) {
96+
s0 = str::trim_left(s0);
97+
let (next, s1) = fromstr_helper(s0);
98+
s0 = s1;
99+
alt next {
100+
some(j) { vec::grow(vals, 1u, j); }
101+
none { ret (none, s0); }
102+
}
103+
s0 = str::trim_left(s0);
104+
if str::is_empty(s0) { ret (none, s0); }
105+
alt str::char_at(s0, 0u) {
106+
',' { }
107+
']' { ret (some(list(@vals)), rest(s0)); }
108+
_ { ret (none, s0); }
109+
}
110+
s0 = rest(s0);
111+
}
112+
ret (none, s0);
113+
}
114+
115+
fn fromstr_dict(s: str) -> (option::t<json>, str) {
116+
if str::char_at(s, 0u) != '{' { ret (none, s); }
117+
let s0 = str::trim_left(rest(s));
118+
let vals = map::new_str_hash::<json>();
119+
if str::is_empty(s0) { ret (none, s0); }
120+
if str::char_at(s0, 0u) == '}' { ret (some(dict(vals)), rest(s0)); }
121+
while str::is_not_empty(s0) {
122+
s0 = str::trim_left(s0);
123+
let (next, s1) = fromstr_helper(s0); // key
124+
let key = "";
125+
s0 = s1;
126+
alt next {
127+
some(string(k)) { key = k; }
128+
_ { ret (none, s0); }
129+
}
130+
s0 = str::trim_left(s0);
131+
if str::is_empty(s0) { ret (none, s0); }
132+
if str::char_at(s0, 0u) != ':' { ret (none, s0); }
133+
s0 = str::trim_left(rest(s0));
134+
let (next, s1) = fromstr_helper(s0); // value
135+
s0 = s1;
136+
alt next {
137+
some(j) { vals.insert(key, j); }
138+
_ { ret (none, s0); }
139+
}
140+
s0 = str::trim_left(s0);
141+
if str::is_empty(s0) { ret (none, s0); }
142+
alt str::char_at(s0, 0u) {
143+
',' { }
144+
'}' { ret (some(dict(vals)), rest(s0)); }
145+
_ { ret (none, s0); }
146+
}
147+
s0 = str::trim_left(rest(s0));
148+
}
149+
(none, s)
150+
}
151+
152+
fn fromstr_float(s: str) -> (option::t<json>, str) {
153+
let pos = 0u;
154+
let len = str::byte_len(s);
155+
let res = 0f;
156+
let neg = 1.f;
157+
158+
alt str::char_at(s, 0u) {
159+
'-' {
160+
neg = -1.f;
161+
pos = 1u;
162+
}
163+
'+' {
164+
pos = 1u;
165+
}
166+
'0' to '9' | '.' { }
167+
_ { ret (none, s); }
168+
}
169+
170+
while (pos < len) {
171+
let opos = pos;
172+
let chr = str::char_range_at(s, pos);
173+
let c = chr.ch;
174+
pos = chr.next;
175+
alt c {
176+
'0' to '9' {
177+
res = res * 10f;
178+
res += ((c as int) - ('0' as int)) as float;
179+
}
180+
'.' { break; }
181+
_ { ret (some(num(neg * res)),
182+
str::char_slice(s, opos, str::char_len(s))); }
183+
}
184+
}
185+
186+
if pos == len {
187+
ret (some(num(neg * res)), str::char_slice(s, pos, str::char_len(s)));
188+
}
189+
190+
let dec = 1.f;
191+
while (pos < len) {
192+
let opos = pos;
193+
let chr = str::char_range_at(s, pos);
194+
let c = chr.ch;
195+
pos = chr.next;
196+
alt c {
197+
'0' to '9' {
198+
dec /= 10.f;
199+
res += (((c as int) - ('0' as int)) as float) * dec;
200+
}
201+
_ { ret (some(num(neg * res)),
202+
str::char_slice(s, opos, str::char_len(s))); }
203+
}
204+
}
205+
ret (some(num(neg * res)), str::char_slice(s, pos, str::char_len(s)));
206+
}
207+
208+
fn fromstr_bool(s: str) -> (option::t<json>, str) {
209+
if (str::starts_with(s, "true")) {
210+
(some(boolean(true)), str::slice(s, 4u, str::byte_len(s)))
211+
} else if (str::starts_with(s, "false")) {
212+
(some(boolean(false)), str::slice(s, 5u, str::byte_len(s)))
213+
} else {
214+
(none, s)
215+
}
216+
}
217+
218+
fn fromstr_helper(s: str) -> (option::t<json>, str) {
219+
let s = str::trim_left(s);
220+
if str::is_empty(s) { ret (none, s); }
221+
let start = str::char_at(s, 0u);
222+
alt start {
223+
'"' { fromstr_str(s) }
224+
'[' { fromstr_list(s) }
225+
'{' { fromstr_dict(s) }
226+
'0' to '9' | '-' | '+' | '.' { fromstr_float(s) }
227+
't' | 'f' { fromstr_bool(s) }
228+
_ { ret (none, s); }
229+
}
230+
}
231+
232+
fn fromstr(s: str) -> option::t<json> {
233+
let (j, _) = fromstr_helper(s);
234+
j
235+
}
236+
237+
fn main() {
238+
let j = fromstr("{ \"foo\": [ 4, 5 ], \"bar\": { \"baz\": true}}");
239+
alt j {
240+
some(j0) {
241+
log tostr(j0);
242+
}
243+
_ { }
244+
}
245+
}
246+
247+
#[cfg(test)]
248+
mod tests {
249+
#[test]
250+
fn test_fromstr_num() {
251+
assert(fromstr("3") == some(num(3f)));
252+
assert(fromstr("3.1") == some(num(3.1f)));
253+
assert(fromstr("-1.2") == some(num(-1.2f)));
254+
assert(fromstr(".4") == some(num(0.4f)));
255+
}
256+
257+
#[test]
258+
fn test_fromstr_str() {
259+
assert(fromstr("\"foo\"") == some(string("foo")));
260+
assert(fromstr("\"\\\"\"") == some(string("\"")));
261+
assert(fromstr("\"lol") == none);
262+
}
263+
264+
#[test]
265+
fn test_fromstr_bool() {
266+
assert(fromstr("true") == some(boolean(true)));
267+
assert(fromstr("false") == some(boolean(false)));
268+
assert(fromstr("truz") == none);
269+
}
270+
271+
#[test]
272+
fn test_fromstr_list() {
273+
assert(fromstr("[]") == some(list(@[])));
274+
assert(fromstr("[true]") == some(list(@[boolean(true)])));
275+
assert(fromstr("[3, 1]") == some(list(@[num(3f), num(1f)])));
276+
assert(fromstr("[2, [4, 1]]") ==
277+
some(list(@[num(2f), list(@[num(4f), num(1f)])])));
278+
assert(fromstr("[2, ]") == none);
279+
assert(fromstr("[5, ") == none);
280+
assert(fromstr("[6 7]") == none);
281+
assert(fromstr("[3") == none);
282+
}
283+
284+
#[test]
285+
fn test_fromstr_dict() {
286+
assert(fromstr("{}") != none);
287+
assert(fromstr("{\"a\": 3}") != none);
288+
assert(fromstr("{\"a\": }") == none);
289+
assert(fromstr("{\"a\" }") == none);
290+
assert(fromstr("{\"a\"") == none);
291+
assert(fromstr("{") == none);
292+
}
293+
}

src/lib/std.rc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ mod ufind;
7474
mod ebml;
7575
mod dbg;
7676
mod getopts;
77+
mod json;
7778
mod math;
7879
mod rand;
7980
mod sha1;

0 commit comments

Comments
 (0)