Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 1 | // Rust JSON serialization library |
| 2 | // Copyright (c) 2011 Google Inc. |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 3 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 4 | import result::{ok, err}; |
| 5 | import io; |
| 6 | import io::{reader_util, writer_util}; |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 7 | import map; |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 8 | |
| 9 | export json; |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 10 | export to_writer; |
Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 11 | export to_str; |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 12 | export from_reader; |
Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 13 | export from_str; |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 14 | export eq; |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 15 | |
Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 16 | export num; |
| 17 | export string; |
| 18 | export boolean; |
| 19 | export list; |
| 20 | export dict; |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 21 | export null; |
Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 22 | |
| 23 | /* |
| 24 | Tag: json |
| 25 | |
| 26 | Represents a json value. |
| 27 | */ |
Patrick Walton | c5a407b | 2012-01-19 23:20:57 | [diff] [blame] | 28 | enum json { |
Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 29 | /* Variant: num */ |
Patrick Walton | 194d8e3 | 2012-01-20 01:55:34 | [diff] [blame] | 30 | num(float), |
Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 31 | /* Variant: string */ |
Patrick Walton | 194d8e3 | 2012-01-20 01:55:34 | [diff] [blame] | 32 | string(str), |
Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 33 | /* Variant: boolean */ |
Patrick Walton | 194d8e3 | 2012-01-20 01:55:34 | [diff] [blame] | 34 | boolean(bool), |
Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 35 | /* Variant: list */ |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 36 | list([json]), |
Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 37 | /* Variant: dict */ |
Patrick Walton | 194d8e3 | 2012-01-20 01:55:34 | [diff] [blame] | 38 | dict(map::map<str,json>), |
Lenny222 | 6f5a0a3 | 2011-12-21 20:36:43 | [diff] [blame] | 39 | /* Variant: null */ |
Patrick Walton | 194d8e3 | 2012-01-20 01:55:34 | [diff] [blame] | 40 | null, |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 41 | } |
| 42 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 43 | type error = { |
| 44 | line: uint, |
| 45 | col: uint, |
| 46 | msg: str, |
| 47 | }; |
| 48 | |
| 49 | /* |
| 50 | Function: to_writer |
| 51 | |
| 52 | Serializes a json value into a io::writer. |
| 53 | */ |
| 54 | fn to_writer(wr: io::writer, j: json) { |
| 55 | alt j { |
| 56 | num(n) { wr.write_str(float::to_str(n, 6u)); } |
| 57 | string(s) { |
| 58 | wr.write_char('"'); |
| 59 | let escaped = ""; |
| 60 | str::chars_iter(s) { |c| |
| 61 | alt c { |
| 62 | '"' { escaped += "\\\""; } |
| 63 | '\\' { escaped += "\\\\"; } |
| 64 | '\x08' { escaped += "\\b"; } |
| 65 | '\x0c' { escaped += "\\f"; } |
| 66 | '\n' { escaped += "\\n"; } |
| 67 | '\r' { escaped += "\\r"; } |
| 68 | '\t' { escaped += "\\t"; } |
| 69 | _ { escaped += str::from_char(c); } |
| 70 | } |
| 71 | }; |
| 72 | wr.write_str(escaped); |
| 73 | wr.write_char('"'); |
| 74 | } |
| 75 | boolean(b) { |
| 76 | wr.write_str(if b { "true" } else { "false" }); |
| 77 | } |
| 78 | list(v) { |
| 79 | wr.write_char('['); |
| 80 | let first = true; |
| 81 | vec::iter(v) { |item| |
| 82 | if !first { |
| 83 | wr.write_str(", "); |
| 84 | } |
| 85 | first = false; |
| 86 | to_writer(wr, item); |
| 87 | }; |
| 88 | wr.write_char(']'); |
| 89 | } |
| 90 | dict(d) { |
| 91 | if d.size() == 0u { |
| 92 | wr.write_str("{}"); |
| 93 | ret; |
| 94 | } |
| 95 | |
| 96 | wr.write_str("{ "); |
| 97 | let first = true; |
| 98 | d.items { |key, value| |
| 99 | if !first { |
| 100 | wr.write_str(", "); |
| 101 | } |
| 102 | first = false; |
| 103 | to_writer(wr, string(key)); |
| 104 | wr.write_str(": "); |
| 105 | to_writer(wr, value); |
| 106 | }; |
| 107 | wr.write_str(" }"); |
| 108 | } |
| 109 | null { |
| 110 | wr.write_str("null"); |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 115 | /* |
| 116 | Function: to_str |
| 117 | |
| 118 | Serializes a json value into a string. |
| 119 | */ |
| 120 | fn to_str(j: json) -> str { |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 121 | io::with_str_writer { |wr| to_writer(wr, j) } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 122 | } |
| 123 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 124 | type parser = { |
| 125 | rdr: io::reader, |
| 126 | mutable ch: char, |
| 127 | mutable line: uint, |
| 128 | mutable col: uint, |
| 129 | }; |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 130 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 131 | impl parser for parser { |
| 132 | fn eof() -> bool { self.ch == -1 as char } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 133 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 134 | fn bump() { |
| 135 | self.ch = self.rdr.read_char(); |
| 136 | |
| 137 | if self.ch == '\n' { |
| 138 | self.line += 1u; |
| 139 | self.col = 1u; |
| 140 | } else { |
| 141 | self.col += 1u; |
| 142 | } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 143 | } |
| 144 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 145 | fn next_char() -> char { |
| 146 | self.bump(); |
| 147 | self.ch |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 148 | } |
| 149 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 150 | fn error<T>(msg: str) -> result::t<T, error> { |
| 151 | err({ line: self.line, col: self.col, msg: msg }) |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 152 | } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 153 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 154 | fn parse() -> result::t<json, error> { |
| 155 | alt self.parse_value() { |
| 156 | ok(value) { |
| 157 | // Make sure there is no trailing characters. |
| 158 | if self.eof() { |
| 159 | ok(value) |
| 160 | } else { |
| 161 | self.error("trailing characters") |
| 162 | } |
| 163 | } |
| 164 | e { e } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 165 | } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 166 | } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 167 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 168 | fn parse_value() -> result::t<json, error> { |
| 169 | self.parse_whitespace(); |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 170 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 171 | if self.eof() { ret self.error("EOF while parsing value"); } |
| 172 | |
| 173 | alt self.ch { |
| 174 | 'n' { self.parse_ident("ull", null) } |
| 175 | 't' { self.parse_ident("rue", boolean(true)) } |
| 176 | 'f' { self.parse_ident("alse", boolean(false)) } |
| 177 | '0' to '9' | '-' { self.parse_number() } |
| 178 | '"' { |
| 179 | alt self.parse_str() { |
| 180 | ok(s) { ok(string(s)) } |
| 181 | err(e) { err(e) } |
| 182 | } |
| 183 | } |
| 184 | '[' { self.parse_list() } |
| 185 | '{' { self.parse_object() } |
| 186 | _ { self.error("invalid syntax") } |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | fn parse_whitespace() { |
| 191 | while char::is_whitespace(self.ch) { self.bump(); } |
| 192 | } |
| 193 | |
| 194 | fn parse_ident(ident: str, value: json) -> result::t<json, error> { |
| 195 | if str::all(ident, { |c| c == self.next_char() }) { |
| 196 | self.bump(); |
| 197 | ok(value) |
| 198 | } else { |
| 199 | self.error("invalid syntax") |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | fn parse_number() -> result::t<json, error> { |
| 204 | let neg = 1f; |
| 205 | |
| 206 | if self.ch == '-' { |
| 207 | self.bump(); |
Marijn Haverbeke | 4f826d8 | 2011-12-16 09:11:00 | [diff] [blame] | 208 | neg = -1f; |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 209 | } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 210 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 211 | let res = alt self.parse_integer() { |
| 212 | ok(res) { res } |
| 213 | err(e) { ret err(e); } |
| 214 | }; |
| 215 | |
| 216 | if self.ch == '.' { |
| 217 | alt self.parse_decimal(res) { |
| 218 | ok(r) { res = r; } |
| 219 | err(e) { ret err(e); } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 220 | } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 221 | } |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 222 | |
| 223 | if self.ch == 'e' || self.ch == 'E' { |
| 224 | alt self.parse_exponent(res) { |
| 225 | ok(r) { res = r; } |
| 226 | err(e) { ret err(e); } |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | ok(num(neg * res)) |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 231 | } |
| 232 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 233 | fn parse_integer() -> result::t<float, error> { |
| 234 | let res = 0f; |
| 235 | |
| 236 | alt self.ch { |
| 237 | '0' { |
| 238 | self.bump(); |
| 239 | |
| 240 | // There can be only one leading '0'. |
| 241 | alt self.ch { |
| 242 | '0' to '9' { ret self.error("invalid number"); } |
| 243 | _ {} |
| 244 | } |
| 245 | } |
| 246 | '1' to '9' { |
| 247 | while !self.eof() { |
| 248 | alt self.ch { |
| 249 | '0' to '9' { |
| 250 | res *= 10f; |
| 251 | res += ((self.ch as int) - ('0' as int)) as float; |
| 252 | |
| 253 | self.bump(); |
| 254 | } |
| 255 | _ { break; } |
| 256 | } |
| 257 | } |
| 258 | } |
| 259 | _ { ret self.error("invalid number"); } |
| 260 | } |
| 261 | |
| 262 | ok(res) |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 263 | } |
| 264 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 265 | fn parse_decimal(res: float) -> result::t<float, error> { |
| 266 | self.bump(); |
| 267 | |
| 268 | // Make sure a digit follows the decimal place. |
| 269 | alt self.ch { |
| 270 | '0' to '9' {} |
| 271 | _ { ret self.error("invalid number"); } |
| 272 | } |
| 273 | |
| 274 | let res = res; |
| 275 | let dec = 1f; |
| 276 | while !self.eof() { |
| 277 | alt self.ch { |
| 278 | '0' to '9' { |
Marijn Haverbeke | 4f826d8 | 2011-12-16 09:11:00 | [diff] [blame] | 279 | dec /= 10f; |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 280 | res += (((self.ch as int) - ('0' as int)) as float) * dec; |
| 281 | |
| 282 | self.bump(); |
| 283 | } |
| 284 | _ { break; } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 285 | } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 286 | } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 287 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 288 | ok(res) |
| 289 | } |
| 290 | |
| 291 | fn parse_exponent(res: float) -> result::t<float, error> { |
| 292 | self.bump(); |
| 293 | |
| 294 | let res = res; |
| 295 | let exp = 0u; |
| 296 | let neg_exp = false; |
| 297 | |
| 298 | alt self.ch { |
| 299 | '+' { self.bump(); } |
| 300 | '-' { self.bump(); neg_exp = true; } |
| 301 | _ {} |
| 302 | } |
| 303 | |
| 304 | // Make sure a digit follows the exponent place. |
| 305 | alt self.ch { |
| 306 | '0' to '9' {} |
| 307 | _ { ret self.error("invalid number"); } |
| 308 | } |
| 309 | |
| 310 | while !self.eof() { |
| 311 | alt self.ch { |
| 312 | '0' to '9' { |
| 313 | exp *= 10u; |
| 314 | exp += (self.ch as uint) - ('0' as uint); |
| 315 | |
| 316 | self.bump(); |
| 317 | } |
| 318 | _ { break; } |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | let exp = float::pow_with_uint(10u, exp); |
| 323 | if neg_exp { |
| 324 | res /= exp; |
| 325 | } else { |
| 326 | res *= exp; |
| 327 | } |
| 328 | |
| 329 | ok(res) |
| 330 | } |
| 331 | |
| 332 | fn parse_str() -> result::t<str, error> { |
| 333 | let escape = false; |
| 334 | let res = ""; |
| 335 | |
| 336 | while !self.eof() { |
| 337 | self.bump(); |
| 338 | |
| 339 | if (escape) { |
| 340 | alt self.ch { |
| 341 | '"' { str::push_char(res, '"'); } |
| 342 | '\\' { str::push_char(res, '\\'); } |
| 343 | '/' { str::push_char(res, '/'); } |
| 344 | 'b' { str::push_char(res, '\x08'); } |
| 345 | 'f' { str::push_char(res, '\x0c'); } |
| 346 | 'n' { str::push_char(res, '\n'); } |
| 347 | 'r' { str::push_char(res, '\r'); } |
| 348 | 't' { str::push_char(res, '\t'); } |
| 349 | 'u' { |
| 350 | // Parse \u1234. |
| 351 | let i = 0u; |
| 352 | let n = 0u; |
| 353 | while i < 4u { |
| 354 | alt self.next_char() { |
| 355 | '0' to '9' { |
| 356 | n = n * 10u + |
| 357 | (self.ch as uint) - ('0' as uint); |
| 358 | } |
| 359 | _ { ret self.error("invalid \\u escape"); } |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | // Error out if we didn't parse 4 digits. |
| 364 | if i != 4u { |
| 365 | ret self.error("invalid \\u escape"); |
| 366 | } |
| 367 | |
| 368 | str::push_char(res, n as char); |
| 369 | } |
| 370 | _ { ret self.error("invalid escape"); } |
| 371 | } |
| 372 | escape = false; |
| 373 | } else if self.ch == '\\' { |
| 374 | escape = true; |
| 375 | } else { |
| 376 | if self.ch == '"' { |
| 377 | self.bump(); |
| 378 | ret ok(res); |
| 379 | } |
| 380 | str::push_char(res, self.ch); |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | self.error("EOF while parsing string") |
| 385 | } |
| 386 | |
| 387 | fn parse_list() -> result::t<json, error> { |
| 388 | self.bump(); |
| 389 | self.parse_whitespace(); |
| 390 | |
| 391 | let values = []; |
| 392 | |
| 393 | if self.ch == ']' { |
| 394 | self.bump(); |
| 395 | ret ok(list(values)); |
| 396 | } |
| 397 | |
| 398 | while true { |
| 399 | alt self.parse_value() { |
| 400 | ok(v) { vec::push(values, v); } |
| 401 | e { ret e; } |
| 402 | } |
| 403 | |
| 404 | self.parse_whitespace(); |
| 405 | if self.eof() { break; } |
| 406 | |
| 407 | alt self.ch { |
| 408 | ',' { self.bump(); } |
| 409 | ']' { self.bump(); ret ok(list(values)); } |
| 410 | _ { ret self.error("expecting ',' or ']'"); } |
| 411 | } |
| 412 | } |
| 413 | |
| 414 | ret self.error("EOF while parsing list"); |
| 415 | } |
| 416 | |
| 417 | fn parse_object() -> result::t<json, error> { |
| 418 | self.bump(); |
| 419 | self.parse_whitespace(); |
| 420 | |
| 421 | let values = map::new_str_hash(); |
| 422 | |
| 423 | if self.ch == '}' { |
| 424 | self.bump(); |
| 425 | ret ok(dict(values)); |
| 426 | } |
| 427 | |
| 428 | while !self.eof() { |
| 429 | self.parse_whitespace(); |
| 430 | |
| 431 | if self.ch != '"' { |
| 432 | ret self.error("key must be a string"); |
| 433 | } |
| 434 | |
| 435 | let key = alt self.parse_str() { |
| 436 | ok(key) { key } |
| 437 | err(e) { ret err(e); } |
| 438 | }; |
| 439 | |
| 440 | self.parse_whitespace(); |
| 441 | |
| 442 | if self.ch != ':' { |
| 443 | if self.eof() { break; } |
| 444 | ret self.error("expecting ':'"); |
| 445 | } |
| 446 | self.bump(); |
| 447 | |
| 448 | alt self.parse_value() { |
| 449 | ok(value) { values.insert(key, value); } |
| 450 | e { ret e; } |
| 451 | } |
| 452 | self.parse_whitespace(); |
| 453 | |
| 454 | alt self.ch { |
| 455 | ',' { self.bump(); } |
| 456 | '}' { self.bump(); ret ok(dict(values)); } |
| 457 | _ { |
| 458 | if self.eof() { break; } |
| 459 | ret self.error("expecting ',' or '}'"); |
| 460 | } |
| 461 | } |
| 462 | } |
| 463 | |
| 464 | ret self.error("EOF while parsing object"); |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 465 | } |
| 466 | } |
| 467 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 468 | /* |
| 469 | Function: from_reader |
Lenny222 | 6f5a0a3 | 2011-12-21 20:36:43 | [diff] [blame] | 470 | |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 471 | Deserializes a json value from an io::reader. |
| 472 | */ |
| 473 | |
| 474 | fn from_reader(rdr: io::reader) -> result::t<json, error> { |
| 475 | let parser = { |
| 476 | rdr: rdr, |
| 477 | mutable ch: rdr.read_char(), |
| 478 | mutable line: 1u, |
| 479 | mutable col: 1u, |
| 480 | }; |
| 481 | |
| 482 | parser.parse() |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 483 | } |
| 484 | |
Elly Jones | 656a2af | 2011-11-07 23:24:44 | [diff] [blame] | 485 | /* |
| 486 | Function: from_str |
| 487 | |
| 488 | Deserializes a json value from a string. |
| 489 | */ |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 490 | fn from_str(s: str) -> result::t<json, error> { |
| 491 | from_reader(io::string_reader(s)) |
| 492 | } |
| 493 | |
| 494 | /* |
| 495 | Function: eq |
| 496 | |
| 497 | Test if two json values are equal. |
| 498 | */ |
| 499 | fn eq(value0: json, value1: json) -> bool { |
| 500 | alt (value0, value1) { |
| 501 | (num(f0), num(f1)) { f0 == f1 } |
| 502 | (string(s0), string(s1)) { s0 == s1 } |
| 503 | (boolean(b0), boolean(b1)) { b0 == b1 } |
| 504 | (list(l0), list(l1)) { vec::all2(l0, l1, eq) } |
| 505 | (dict(d0), dict(d1)) { |
| 506 | if d0.size() == d1.size() { |
| 507 | let equal = true; |
| 508 | d0.items { |k, v0| |
| 509 | alt d1.find(k) { |
| 510 | some(v1) { |
| 511 | if !eq(v0, v1) { equal = false; } } |
| 512 | none { equal = false; } |
| 513 | } |
| 514 | }; |
| 515 | equal |
| 516 | } else { |
| 517 | false |
| 518 | } |
| 519 | } |
| 520 | (null, null) { true } |
| 521 | _ { false } |
| 522 | } |
Elly Jones | bd72626 | 2011-11-07 19:01:28 | [diff] [blame] | 523 | } |
Brian Anderson | 6e27b27 | 2012-01-18 03:05:07 | [diff] [blame] | 524 | |
| 525 | #[cfg(test)] |
| 526 | mod tests { |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 527 | fn mk_dict(items: [(str, json)]) -> json { |
| 528 | let d = map::new_str_hash(); |
| 529 | |
| 530 | vec::iter(items) { |item| |
| 531 | let (key, value) = item; |
| 532 | d.insert(key, value); |
| 533 | }; |
| 534 | |
| 535 | dict(d) |
Brian Anderson | 6e27b27 | 2012-01-18 03:05:07 | [diff] [blame] | 536 | } |
| 537 | |
| 538 | #[test] |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 539 | fn test_write_null() { |
| 540 | assert to_str(null) == "null"; |
Brian Anderson | 6e27b27 | 2012-01-18 03:05:07 | [diff] [blame] | 541 | } |
| 542 | |
| 543 | #[test] |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 544 | fn test_write_num() { |
| 545 | assert to_str(num(3f)) == "3"; |
| 546 | assert to_str(num(3.1f)) == "3.1"; |
| 547 | assert to_str(num(-1.5f)) == "-1.5"; |
| 548 | assert to_str(num(0.5f)) == "0.5"; |
Brian Anderson | 6e27b27 | 2012-01-18 03:05:07 | [diff] [blame] | 549 | } |
| 550 | |
| 551 | #[test] |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 552 | fn test_write_str() { |
| 553 | assert to_str(string("")) == "\"\""; |
| 554 | assert to_str(string("foo")) == "\"foo\""; |
Brian Anderson | 6e27b27 | 2012-01-18 03:05:07 | [diff] [blame] | 555 | } |
| 556 | |
| 557 | #[test] |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 558 | fn test_write_bool() { |
| 559 | assert to_str(boolean(true)) == "true"; |
| 560 | assert to_str(boolean(false)) == "false"; |
Brian Anderson | 6e27b27 | 2012-01-18 03:05:07 | [diff] [blame] | 561 | } |
| 562 | |
| 563 | #[test] |
Erick Tryzelaar | 012dec5 | 2012-02-26 00:39:32 | [diff] [blame^] | 564 | fn test_write_list() { |
| 565 | assert to_str(list([])) == "[]"; |
| 566 | assert to_str(list([boolean(true)])) == "[true]"; |
| 567 | assert to_str(list([ |
| 568 | boolean(false), |
| 569 | null, |
| 570 | list([string("foo\nbar"), num(3.5f)]) |
| 571 | ])) == "[false, null, [\"foo\\nbar\", 3.5]]"; |
| 572 | } |
| 573 | |
| 574 | #[test] |
| 575 | fn test_write_dict() { |
| 576 | assert to_str(mk_dict([])) == "{}"; |
| 577 | assert to_str(mk_dict([("a", boolean(true))])) == "{ \"a\": true }"; |
| 578 | assert to_str(mk_dict([ |
| 579 | ("a", boolean(true)), |
| 580 | ("b", list([ |
| 581 | mk_dict([("c", string("\x0c\r"))]), |
| 582 | mk_dict([("d", string(""))]) |
| 583 | ])) |
| 584 | ])) == |
| 585 | "{ " + |
| 586 | "\"a\": true, " + |
| 587 | "\"b\": [" + |
| 588 | "{ \"c\": \"\\f\\r\" }, " + |
| 589 | "{ \"d\": \"\" }" + |
| 590 | "]" + |
| 591 | " }"; |
| 592 | } |
| 593 | |
| 594 | #[test] |
| 595 | fn test_trailing_characters() { |
| 596 | assert from_str("nulla") == |
| 597 | err({line: 1u, col: 5u, msg: "trailing characters"}); |
| 598 | assert from_str("truea") == |
| 599 | err({line: 1u, col: 5u, msg: "trailing characters"}); |
| 600 | assert from_str("falsea") == |
| 601 | err({line: 1u, col: 6u, msg: "trailing characters"}); |
| 602 | assert from_str("1a") == |
| 603 | err({line: 1u, col: 2u, msg: "trailing characters"}); |
| 604 | assert from_str("[]a") == |
| 605 | err({line: 1u, col: 3u, msg: "trailing characters"}); |
| 606 | assert from_str("{}a") == |
| 607 | err({line: 1u, col: 3u, msg: "trailing characters"}); |
| 608 | } |
| 609 | |
| 610 | #[test] |
| 611 | fn test_read_identifiers() { |
| 612 | assert from_str("n") == |
| 613 | err({line: 1u, col: 2u, msg: "invalid syntax"}); |
| 614 | assert from_str("nul") == |
| 615 | err({line: 1u, col: 4u, msg: "invalid syntax"}); |
| 616 | |
| 617 | assert from_str("t") == |
| 618 | err({line: 1u, col: 2u, msg: "invalid syntax"}); |
| 619 | assert from_str("truz") == |
| 620 | err({line: 1u, col: 4u, msg: "invalid syntax"}); |
| 621 | |
| 622 | assert from_str("f") == |
| 623 | err({line: 1u, col: 2u, msg: "invalid syntax"}); |
| 624 | assert from_str("faz") == |
| 625 | err({line: 1u, col: 3u, msg: "invalid syntax"}); |
| 626 | |
| 627 | assert from_str("null") == ok(null); |
| 628 | assert from_str("true") == ok(boolean(true)); |
| 629 | assert from_str("false") == ok(boolean(false)); |
| 630 | } |
| 631 | |
| 632 | #[test] |
| 633 | fn test_read_num() { |
| 634 | assert from_str("+") == |
| 635 | err({line: 1u, col: 1u, msg: "invalid syntax"}); |
| 636 | assert from_str(".") == |
| 637 | err({line: 1u, col: 1u, msg: "invalid syntax"}); |
| 638 | |
| 639 | assert from_str("-") == |
| 640 | err({line: 1u, col: 2u, msg: "invalid number"}); |
| 641 | assert from_str("00") == |
| 642 | err({line: 1u, col: 2u, msg: "invalid number"}); |
| 643 | assert from_str("1.") == |
| 644 | err({line: 1u, col: 3u, msg: "invalid number"}); |
| 645 | assert from_str("1e") == |
| 646 | err({line: 1u, col: 3u, msg: "invalid number"}); |
| 647 | assert from_str("1e+") == |
| 648 | err({line: 1u, col: 4u, msg: "invalid number"}); |
| 649 | |
| 650 | assert from_str("3") == ok(num(3f)); |
| 651 | assert from_str("3.1") == ok(num(3.1f)); |
| 652 | assert from_str("-1.2") == ok(num(-1.2f)); |
| 653 | assert from_str("0.4") == ok(num(0.4f)); |
| 654 | assert from_str("0.4e5") == ok(num(0.4e5f)); |
| 655 | assert from_str("0.4e+15") == ok(num(0.4e15f)); |
| 656 | assert from_str("0.4e-01") == ok(num(0.4e-01f)); |
| 657 | } |
| 658 | |
| 659 | #[test] |
| 660 | fn test_read_str() { |
| 661 | assert from_str("\"") == |
| 662 | err({line: 1u, col: 2u, msg: "EOF while parsing string"}); |
| 663 | assert from_str("\"lol") == |
| 664 | err({line: 1u, col: 5u, msg: "EOF while parsing string"}); |
| 665 | |
| 666 | assert from_str("\"\"") == ok(string("")); |
| 667 | assert from_str("\"foo\"") == ok(string("foo")); |
| 668 | assert from_str("\"\\\"\"") == ok(string("\"")); |
| 669 | assert from_str("\"\\b\"") == ok(string("\x08")); |
| 670 | assert from_str("\"\\n\"") == ok(string("\n")); |
| 671 | assert from_str("\"\\r\"") == ok(string("\r")); |
| 672 | assert from_str("\"\\t\"") == ok(string("\t")); |
| 673 | } |
| 674 | |
| 675 | #[test] |
| 676 | fn test_read_list() { |
| 677 | assert from_str("[") == |
| 678 | err({line: 1u, col: 2u, msg: "EOF while parsing value"}); |
| 679 | assert from_str("[1") == |
| 680 | err({line: 1u, col: 3u, msg: "EOF while parsing list"}); |
| 681 | assert from_str("[1,") == |
| 682 | err({line: 1u, col: 4u, msg: "EOF while parsing value"}); |
| 683 | assert from_str("[1,]") == |
| 684 | err({line: 1u, col: 4u, msg: "invalid syntax"}); |
| 685 | assert from_str("[6 7]") == |
| 686 | err({line: 1u, col: 4u, msg: "expecting ',' or ']'"}); |
| 687 | |
| 688 | assert from_str("[]") == ok(list([])); |
| 689 | assert from_str("[ ]") == ok(list([])); |
| 690 | assert from_str("[true]") == ok(list([boolean(true)])); |
| 691 | assert from_str("[ false ]") == ok(list([boolean(false)])); |
| 692 | assert from_str("[null]") == ok(list([null])); |
| 693 | assert from_str("[3, 1]") == ok(list([num(3f), num(1f)])); |
| 694 | assert from_str("[2, [4, 1]]") == |
| 695 | ok(list([num(2f), list([num(4f), num(1f)])])); |
| 696 | } |
| 697 | |
| 698 | #[test] |
| 699 | fn test_read_dict() { |
| 700 | assert from_str("{") == |
| 701 | err({line: 1u, col: 2u, msg: "EOF while parsing object"}); |
| 702 | assert from_str("{ ") == |
| 703 | err({line: 1u, col: 3u, msg: "EOF while parsing object"}); |
| 704 | assert from_str("{1") == |
| 705 | err({line: 1u, col: 2u, msg: "key must be a string"}); |
| 706 | assert from_str("{ \"a\"") == |
| 707 | err({line: 1u, col: 6u, msg: "EOF while parsing object"}); |
| 708 | assert from_str("{\"a\"") == |
| 709 | err({line: 1u, col: 5u, msg: "EOF while parsing object"}); |
| 710 | assert from_str("{\"a\" ") == |
| 711 | err({line: 1u, col: 6u, msg: "EOF while parsing object"}); |
| 712 | |
| 713 | assert from_str("{\"a\" 1") == |
| 714 | err({line: 1u, col: 6u, msg: "expecting ':'"}); |
| 715 | assert from_str("{\"a\":") == |
| 716 | err({line: 1u, col: 6u, msg: "EOF while parsing value"}); |
| 717 | assert from_str("{\"a\":1") == |
| 718 | err({line: 1u, col: 7u, msg: "EOF while parsing object"}); |
| 719 | assert from_str("{\"a\":1 1") == |
| 720 | err({line: 1u, col: 8u, msg: "expecting ',' or '}'"}); |
| 721 | assert from_str("{\"a\":1,") == |
| 722 | err({line: 1u, col: 8u, msg: "EOF while parsing object"}); |
| 723 | |
| 724 | assert eq(result::get(from_str("{}")), mk_dict([])); |
| 725 | assert eq(result::get(from_str("{\"a\": 3}")), |
| 726 | mk_dict([("a", num(3.0f))])); |
| 727 | |
| 728 | assert eq(result::get(from_str("{ \"a\": null, \"b\" : true }")), |
| 729 | mk_dict([("a", null), ("b", boolean(true))])); |
| 730 | assert eq(result::get(from_str("{\"a\" : 1.0 ,\"b\": [ true ]}")), |
| 731 | mk_dict([ |
| 732 | ("a", num(1.0)), |
| 733 | ("b", list([boolean(true)])) |
| 734 | ])); |
| 735 | assert eq(result::get(from_str( |
| 736 | "{" + |
| 737 | "\"a\": 1.0, " + |
| 738 | "\"b\": [" + |
| 739 | "true," + |
| 740 | "\"foo\\nbar\", " + |
| 741 | "{ \"c\": {\"d\": null} } " + |
| 742 | "]" + |
| 743 | "}")), |
| 744 | mk_dict([ |
| 745 | ("a", num(1.0f)), |
| 746 | ("b", list([ |
| 747 | boolean(true), |
| 748 | string("foo\nbar"), |
| 749 | mk_dict([ |
| 750 | ("c", mk_dict([("d", null)])) |
| 751 | ]) |
| 752 | ])) |
| 753 | ])); |
| 754 | } |
| 755 | |
| 756 | #[test] |
| 757 | fn test_multiline_errors() { |
| 758 | assert from_str("{\n \"foo\":\n \"bar\"") == |
| 759 | err({line: 3u, col: 8u, msg: "EOF while parsing object"}); |
Brian Anderson | 6e27b27 | 2012-01-18 03:05:07 | [diff] [blame] | 760 | } |
| 761 | } |