1 implement JSON; 2 3 # 4 # Javascript `Object' Notation (JSON): RFC4627 5 # 6 7 include "sys.m"; 8 sys: Sys; 9 10 include "bufio.m"; 11 bufio: Bufio; 12 Iobuf: import bufio; 13 14 include "json.m"; 15 16 init(b: Bufio) 17 { 18 sys = load Sys Sys->PATH; 19 bufio = b; 20 } 21 22 jvarray(a: array of ref JValue): ref JValue.Array 23 { 24 return ref JValue.Array(a); 25 } 26 27 jvbig(i: big): ref JValue.Int 28 { 29 return ref JValue.Int(i); 30 } 31 32 jvfalse(): ref JValue.False 33 { 34 return ref JValue.False; 35 } 36 37 jvint(i: int): ref JValue.Int 38 { 39 return ref JValue.Int(big i); 40 } 41 42 jvnull(): ref JValue.Null 43 { 44 return ref JValue.Null; 45 } 46 47 jvobject(m: list of (string, ref JValue)): ref JValue.Object 48 { 49 # could `uniq' the labels 50 return ref JValue.Object(m); 51 } 52 53 jvreal(r: real): ref JValue.Real 54 { 55 return ref JValue.Real(r); 56 } 57 58 jvstring(s: string): ref JValue.String 59 { 60 return ref JValue.String(s); 61 } 62 63 jvtrue(): ref JValue.True 64 { 65 return ref JValue.True; 66 } 67 68 Syntax: exception(string); 69 Badwrite: exception; 70 71 readjson(fd: ref Iobuf): (ref JValue, string) 72 { 73 { 74 p := Parse.mk(fd); 75 c := p.getns(); 76 if(c == Bufio->EOF) 77 return (nil, nil); 78 p.unget(c); 79 return (readval(p), nil); 80 }exception e{ 81 Syntax => 82 return (nil, sys->sprint("JSON syntax error (offset %bd): %s", fd.offset(), e)); 83 } 84 } 85 86 writejson(fd: ref Iobuf, val: ref JValue): int 87 { 88 { 89 writeval(fd, val); 90 return 0; 91 }exception{ 92 Badwrite => 93 return -1; 94 } 95 } 96 97 # 98 # value ::= string | number | object | array | 'true' | 'false' | 'null' 99 # 100 readval(p: ref Parse): ref JValue raises(Syntax) 101 { 102 { 103 while((c := p.getc()) == ' ' || c == '\t' || c == '\n' || c == '\r') 104 {} 105 if(c < 0){ 106 if(c == Bufio->EOF) 107 raise Syntax("unexpected end-of-input"); 108 raise Syntax(sys->sprint("read error: %r")); 109 } 110 case c { 111 '{' => 112 # object ::= '{' [pair (',' pair)*] '}' 113 l: list of (string, ref JValue); 114 if((c = p.getns()) != '}'){ 115 p.unget(c); 116 rl: list of (string, ref JValue); 117 do{ 118 # pair ::= string ':' value 119 c = p.getns(); 120 if(c != '"') 121 raise Syntax("missing member name"); 122 name := readstring(p, c); 123 if(p.getns() != ':') 124 raise Syntax("missing ':'"); 125 rl = (name, readval(p)) :: rl; 126 }while((c = p.getns()) == ','); 127 for(; rl != nil; rl = tl rl) 128 l = hd rl :: l; 129 } 130 if(c != '}') 131 raise Syntax("missing '}' at end of object"); 132 return ref JValue.Object(l); 133 '[' => 134 # array ::= '[' [value (',' value)*] ']' 135 l: list of ref JValue; 136 n := 0; 137 if((c = p.getns()) != ']'){ 138 p.unget(c); 139 do{ 140 l = readval(p) :: l; 141 n++; 142 }while((c = p.getns()) == ','); 143 } 144 if(c != ']') 145 raise Syntax("missing ']' at end of array"); 146 a := array[n] of ref JValue; 147 for(; --n >= 0; l = tl l) 148 a[n] = hd l; 149 return ref JValue.Array(a); 150 '"' => 151 return ref JValue.String(readstring(p, c)); 152 '-' or '0' to '9' => 153 # number ::= int frac? exp? 154 # int ::= '-'? [0-9] | [1-9][0-9]+ 155 # frac ::= '.' [0-9]+ 156 # exp ::= [eE][-+]? [0-9]+ 157 if(c == '-') 158 intp := "-"; 159 else 160 p.unget(c); 161 intp += readdigits(p); # we don't enforce the absence of leading zeros 162 fracp: string; 163 c = p.getc(); 164 if(c == '.'){ 165 fracp = readdigits(p); 166 c = p.getc(); 167 } 168 exp := ""; 169 if(c == 'e' || c == 'E'){ 170 exp[0] = c; 171 c = p.getc(); 172 if(c == '-' || c == '+') 173 exp[1] = c; 174 else 175 p.unget(c); 176 exp += readdigits(p); 177 }else 178 p.unget(c); 179 if(fracp != nil || exp != nil) 180 return ref JValue.Real(real (intp+"."+fracp+exp)); 181 return ref JValue.Int(big intp); 182 'a' to 'z' => 183 # 'true' | 'false' | 'null' 184 s: string; 185 do{ 186 s[len s] = c; 187 }while((c = p.getc()) >= 'a' && c <= 'z'); 188 p.unget(c); 189 case s { 190 "true" => return ref JValue.True(); 191 "false" => return ref JValue.False(); 192 "null" => return ref JValue.Null(); 193 * => raise Syntax("invalid literal: "+s); 194 } 195 * => 196 raise Syntax(sys->sprint("unexpected character #%.4ux", c)); 197 } 198 }exception{ 199 Syntax => 200 raise; 201 } 202 } 203 204 # string ::= '"' char* '"' 205 # char ::= [^\x00-\x1F"\\] | '\"' | '\/' | '\b' | '\f' | '\n' | '\r' | '\t' | '\u' hex hex hex hex 206 readstring(p: ref Parse, delim: int): string raises(Syntax) 207 { 208 { 209 s := ""; 210 while((c := p.getc()) != delim && c >= 0){ 211 if(c == '\\'){ 212 c = p.getc(); 213 if(c < 0) 214 break; 215 case c { 216 'b' => c = '\b'; 217 'f' => c = '\f'; 218 'n' => c = '\n'; 219 'r' => c = '\r'; 220 't' => c = '\t'; 221 'u' => 222 c = 0; 223 for(i := 0; i < 4; i++) 224 c = (c<<4) | hex(p.getc()); 225 * => ; # identity, including '"', '/', and '\' 226 } 227 } 228 s[len s] = c; 229 } 230 if(c < 0){ 231 if(c == Bufio->ERROR) 232 raise Syntax(sys->sprint("read error: %r")); 233 raise Syntax("unterminated string"); 234 } 235 return s; 236 }exception{ 237 Syntax => 238 raise; 239 } 240 } 241 242 # hex ::= [0-9a-fA-F] 243 hex(c: int): int raises(Syntax) 244 { 245 case c { 246 '0' to '9' => 247 return c-'0'; 248 'a' to 'f' => 249 return 10+(c-'a'); 250 'A' to 'F' => 251 return 10+(c-'A'); 252 * => 253 raise Syntax("invalid hex digit"); 254 } 255 } 256 257 # digits ::= [0-9]+ 258 readdigits(p: ref Parse): string raises(Syntax) 259 { 260 c := p.getc(); 261 if(!(c >= '0' && c <= '9')) 262 raise Syntax("expected integer literal"); 263 s := ""; 264 s[0] = c; 265 while((c = p.getc()) >= '0' && c <= '9') 266 s[len s] = c; 267 p.unget(c); 268 return s; 269 } 270 271 writeval(out: ref Iobuf, o: ref JValue) raises(Badwrite) 272 { 273 { 274 if(o == nil){ 275 puts(out, "null"); 276 return; 277 } 278 pick r := o { 279 String => 280 writestring(out, r.s); 281 Int => 282 puts(out, string r.value); 283 Real => 284 puts(out, string r.value); 285 Object => # '{' [pair (',' pair)*] '}' 286 putc(out, '{'); 287 for(l := r.mem; l != nil; l = tl l){ 288 if(l != r.mem) 289 putc(out, ','); 290 (n, v) := hd l; 291 writestring(out, n); 292 putc(out, ':'); 293 writeval(out, v); 294 } 295 putc(out, '}'); 296 Array => # '[' [value (',' value)*] ']' 297 putc(out, '['); 298 for(i := 0; i < len r.a; i++){ 299 if(i != 0) 300 putc(out, ','); 301 writeval(out, r.a[i]); 302 } 303 putc(out, ']'); 304 True => 305 puts(out, "true"); 306 False => 307 puts(out, "false"); 308 Null => 309 puts(out, "null"); 310 * => 311 raise "writeval: unknown value"; # can't happen 312 } 313 }exception{ 314 Badwrite => 315 raise; 316 } 317 } 318 319 writestring(out: ref Iobuf, s: string) raises(Badwrite) 320 { 321 { 322 putc(out, '"'); 323 for(i := 0; i < len s; i++){ 324 c := s[i]; 325 if(needesc(c)) 326 puts(out, escout(c)); 327 else 328 putc(out, c); 329 } 330 putc(out, '"'); 331 }exception{ 332 Badwrite => 333 raise; 334 } 335 } 336 337 escout(c: int): string 338 { 339 case c { 340 '"' => return "\\\""; 341 '\\' => return "\\\\"; 342 '/' => return "\\/"; 343 '\b' => return "\\b"; 344 '\f' => return "\\f"; 345 '\n' => return "\\n"; 346 '\t' => return "\\t"; 347 '\r' => return "\\r"; 348 * => return sys->sprint("\\u%.4ux", c); 349 } 350 } 351 352 puts(out: ref Iobuf, s: string) raises(Badwrite) 353 { 354 if(out.puts(s) == Bufio->ERROR) 355 raise Badwrite; 356 } 357 358 putc(out: ref Iobuf, c: int) raises(Badwrite) 359 { 360 if(out.putc(c) == Bufio->ERROR) 361 raise Badwrite; 362 } 363 364 Parse: adt { 365 input: ref Iobuf; 366 eof: int; 367 368 mk: fn(io: ref Iobuf): ref Parse; 369 getc: fn(nil: self ref Parse): int; 370 unget: fn(nil: self ref Parse, c: int); 371 getns: fn(nil: self ref Parse): int; 372 }; 373 374 Parse.mk(io: ref Iobuf): ref Parse 375 { 376 return ref Parse(io, 0); 377 } 378 379 Parse.getc(p: self ref Parse): int 380 { 381 if(p.eof) 382 return p.eof; 383 c := p.input.getc(); 384 if(c < 0) 385 p.eof = c; 386 return c; 387 } 388 389 Parse.unget(p: self ref Parse, c: int) 390 { 391 if(c >= 0) 392 p.input.ungetc(); 393 } 394 395 # skip white space 396 Parse.getns(p: self ref Parse): int 397 { 398 while((c := p.getc()) == ' ' || c == '\t' || c == '\n' || c == '\r') 399 {} 400 return c; 401 } 402 403 JValue.isarray(v: self ref JValue): int 404 { 405 return tagof v == tagof JValue.Array; 406 } 407 408 JValue.isint(v: self ref JValue): int 409 { 410 return tagof v == tagof JValue.Int; 411 } 412 413 JValue.isnumber(v: self ref JValue): int 414 { 415 return tagof v == tagof JValue.Int || tagof v == tagof JValue.Real; 416 } 417 418 JValue.isobject(v: self ref JValue): int 419 { 420 return tagof v == tagof JValue.Object; 421 } 422 423 JValue.isreal(v: self ref JValue): int 424 { 425 return tagof v == tagof JValue.Real; 426 } 427 428 JValue.isstring(v: self ref JValue): int 429 { 430 return tagof v == tagof JValue.String; 431 } 432 433 JValue.istrue(v: self ref JValue): int 434 { 435 return tagof v == tagof JValue.True; 436 } 437 438 JValue.isfalse(v: self ref JValue): int 439 { 440 return tagof v == tagof JValue.False; 441 } 442 443 JValue.isnull(v: self ref JValue): int 444 { 445 return tagof v == tagof JValue.Null; 446 } 447 448 JValue.copy(v: self ref JValue): ref JValue 449 { 450 pick r := v { 451 True or False or Null => 452 return ref *r; 453 Int => 454 return ref *r; 455 Real => 456 return ref *r; 457 String => 458 return ref *r; 459 Array => 460 a := array[len r.a] of ref JValue; 461 a[0:] = r.a; 462 return ref JValue.Array(a); 463 Object => 464 return ref *r; 465 * => 466 raise "json: bad copy"; # can't happen 467 } 468 } 469 470 JValue.eq(a: self ref JValue, b: ref JValue): int 471 { 472 if(a == b) 473 return 1; 474 if(a == nil || b == nil || tagof a != tagof b) 475 return 0; 476 pick r := a { 477 True or False or Null => 478 return 1; # tags were equal above 479 Int => 480 pick s := b { 481 Int => 482 return r.value == s.value; 483 } 484 Real => 485 pick s := b { 486 Real => 487 return r.value == s.value; 488 } 489 String => 490 pick s := b { 491 String => 492 return r.s == s.s; 493 } 494 Array => 495 pick s := b { 496 Array => 497 if(len r.a != len s.a) 498 return 0; 499 for(i := 0; i < len r.a; i++) 500 if(r.a[i] == nil){ 501 if(s.a[i] != nil) 502 return 0; 503 }else if(!r.a[i].eq(s.a[i])) 504 return 0; 505 return 1; 506 } 507 Object => 508 pick s := b { 509 Object => 510 ls := s.mem; 511 for(lr := r.mem; lr != nil; lr = tl lr){ 512 if(ls == nil) 513 return 0; 514 (rn, rv) := hd lr; 515 (sn, sv) := hd ls; 516 if(rn != sn) 517 return 0; 518 if(rv == nil){ 519 if(sv != nil) 520 return 0; 521 }else if(!rv.eq(sv)) 522 return 0; 523 } 524 return ls == nil; 525 } 526 } 527 return 0; 528 } 529 530 JValue.get(v: self ref JValue, mem: string): ref JValue 531 { 532 pick r := v { 533 Object => 534 for(l := r.mem; l != nil; l = tl l) 535 if((hd l).t0 == mem) 536 return (hd l).t1; 537 return nil; 538 * => 539 return nil; 540 } 541 } 542 543 # might be better if the interface were applicative? 544 # this is similar to behaviour of Limbo's own ref adt, though 545 JValue.set(v: self ref JValue, mem: string, val: ref JValue) 546 { 547 pick j := v { 548 Object => 549 ol: list of (string, ref JValue); 550 for(l := j.mem; l != nil; l = tl l) 551 if((hd l).t0 == mem){ 552 l = tl l; 553 for(; ol != nil; ol = tl ol) 554 l = hd ol :: l; 555 j.mem = l; 556 return; 557 }else 558 ol = hd l :: ol; 559 j.mem = (mem, val) :: j.mem; 560 * => 561 raise "json: set non-object"; 562 } 563 } 564 565 JValue.text(v: self ref JValue): string 566 { 567 if(v == nil) 568 return "null"; 569 pick r := v { 570 True => 571 return "true"; 572 False => 573 return "false"; 574 Null => 575 return "null"; 576 Int => 577 return string r.value; 578 Real => 579 return sys->sprint("%f", r.value); 580 String => 581 return quote(r.s); # quoted, or not? 582 Array => 583 s := "["; 584 for(i := 0; i < len r.a; i++){ 585 if(i != 0) 586 s += ", "; 587 s += r.a[i].text(); 588 } 589 return s+"]"; 590 Object => 591 s := "{"; 592 for(l := r.mem; l != nil; l = tl l){ 593 if(l != r.mem) 594 s += ", "; 595 s += quote((hd l).t0)+": "+(hd l).t1.text(); 596 } 597 return s+"}"; 598 * => 599 return nil; 600 } 601 } 602 603 quote(s: string): string 604 { 605 ns := "\""; 606 for(i := 0; i < len s; i++){ 607 c := s[i]; 608 if(needesc(c)) 609 ns += escout(c); 610 else 611 ns[len ns] = c; 612 } 613 return ns+"\""; 614 } 615 616 needesc(c: int): int 617 { 618 return c == '"' || c == '\\' || c == '/' || c <= 16r1F; # '/' is escaped to prevent "</xyz>" looking like an XML end tag(!) 619 } 620