blob: 02d5d3d5e7e36b21252e694e1a9ca56768b3443a [file] [log] [blame]
Elly Jonesbd726262011-11-07 19:01:281#[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
9import float;
10import map;
11import option;
12import option::{some, none};
13import str;
14import vec;
15
16export json;
17export tostr;
18export fromstr;
19
20tag json {
21 num(float);
22 string(str);
23 boolean(bool);
24 list(@[json]);
25 dict(map::hashmap<str,json>);
26}
27
28fn 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
53fn rest(s: str) -> str {
54 str::char_slice(s, 1u, str::char_len(s))
55}
56
57fn 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
89fn 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
115fn 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
152fn 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
208fn 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
218fn 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
232fn fromstr(s: str) -> option::t<json> {
233 let (j, _) = fromstr_helper(s);
234 j
235}
236
237fn 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)]
248mod 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}