diff options
Diffstat (limited to 'appl/lib')
| -rw-r--r-- | appl/lib/json.b | 618 | ||||
| -rw-r--r-- | appl/lib/mkfile | 2 |
2 files changed, 620 insertions, 0 deletions
diff --git a/appl/lib/json.b b/appl/lib/json.b new file mode 100644 index 00000000..3e86fa4c --- /dev/null +++ b/appl/lib/json.b @@ -0,0 +1,618 @@ +implement JSON; + +# +# Javascript `Object' Notation (JSON): RFC4627 +# + +include "sys.m"; + sys: Sys; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "json.m"; + +init(b: Bufio) +{ + sys = load Sys Sys->PATH; + bufio = b; +} + +jvarray(a: array of ref JValue): ref JValue.Array +{ + return ref JValue.Array(a); +} + +jvbig(i: big): ref JValue.Int +{ + return ref JValue.Int(i); +} + +jvfalse(): ref JValue.False +{ + return ref JValue.False; +} + +jvint(i: int): ref JValue.Int +{ + return ref JValue.Int(big i); +} + +jvnull(): ref JValue.Null +{ + return ref JValue.Null; +} + +jvobject(m: list of (string, ref JValue)): ref JValue.Object +{ + # could `uniq' the labels + return ref JValue.Object(m); +} + +jvreal(r: real): ref JValue.Real +{ + return ref JValue.Real(r); +} + +jvstring(s: string): ref JValue.String +{ + return ref JValue.String(s); +} + +jvtrue(): ref JValue.True +{ + return ref JValue.True; +} + +Syntax: exception(string); +Badwrite: exception; + +readjson(fd: ref Iobuf): (ref JValue, string) +{ + { + p := Parse.mk(fd); + c := p.getns(); + if(c == Bufio->EOF) + return (nil, nil); + p.unget(c); + return (readval(p), nil); + }exception e{ + Syntax => + return (nil, sys->sprint("JSON syntax error (offset %bd): %s", fd.offset(), e)); + } +} + +writejson(fd: ref Iobuf, val: ref JValue): int +{ + { + writeval(fd, val); + return 0; + }exception{ + Badwrite => + return -1; + } +} + +# +# value ::= string | number | object | array | 'true' | 'false' | 'null' +# +readval(p: ref Parse): ref JValue raises(Syntax) +{ + { + while((c := p.getc()) == ' ' || c == '\t' || c == '\n' || c == '\r') + {} + if(c < 0){ + if(c == Bufio->EOF) + raise Syntax("unexpected end-of-input"); + raise Syntax(sys->sprint("read error: %r")); + } + case c { + '{' => + # object ::= '{' [pair (',' pair)*] '}' + l: list of (string, ref JValue); + if((c = p.getns()) != '}'){ + p.unget(c); + rl: list of (string, ref JValue); + do{ + # pair ::= string ':' value + c = p.getns(); + if(c != '"') + raise Syntax("missing member name"); + name := readstring(p, c); + if(p.getns() != ':') + raise Syntax("missing ':'"); + rl = (name, readval(p)) :: rl; + }while((c = p.getns()) == ','); + for(; rl != nil; rl = tl rl) + l = hd rl :: l; + } + if(c != '}') + raise Syntax("missing '}' at end of object"); + return ref JValue.Object(l); + '[' => + # array ::= '[' [value (',' value)*] ']' + l: list of ref JValue; + n := 0; + if((c = p.getns()) != ']'){ + p.unget(c); + do{ + l = readval(p) :: l; + n++; + }while((c = p.getns()) == ','); + } + if(c != ']') + raise Syntax("missing ']' at end of array"); + a := array[n] of ref JValue; + for(; --n >= 0; l = tl l) + a[n] = hd l; + return ref JValue.Array(a); + '"' => + return ref JValue.String(readstring(p, c)); + '-' or '0' to '9' => + # number ::= int frac? exp? + # int ::= '-'? [0-9] | [1-9][0-9]+ + # frac ::= '.' [0-9]+ + # exp ::= [eE][-+]? [0-9]+ + if(c == '-') + intp := "-"; + else + p.unget(c); + intp += readdigits(p); # we don't enforce the absence of leading zeros + fracp: string; + c = p.getc(); + if(c == '.'){ + fracp = readdigits(p); + c = p.getc(); + } + exp := ""; + if(c == 'e' || c == 'E'){ + exp[0] = c; + c = p.getc(); + if(c == '-' || c == '+') + exp[1] = c; + else + p.unget(c); + exp += readdigits(p); + }else + p.unget(c); + if(fracp != nil || exp != nil) + return ref JValue.Real(real (intp+"."+fracp+exp)); + return ref JValue.Int(big intp); + 'a' to 'z' => + # 'true' | 'false' | 'null' + s: string; + do{ + s[len s] = c; + }while((c = p.getc()) >= 'a' && c <= 'z'); + p.unget(c); + case s { + "true" => return ref JValue.True(); + "false" => return ref JValue.False(); + "null" => return ref JValue.Null(); + * => raise Syntax("invalid literal: "+s); + } + * => + raise Syntax(sys->sprint("unexpected character #%.4ux", c)); + } + }exception{ + Syntax => + raise; + } +} + +# string ::= '"' char* '"' +# char ::= [^\x00-\x1F"\\] | '\"' | '\/' | '\b' | '\f' | '\n' | '\r' | '\t' | '\u' hex hex hex hex +readstring(p: ref Parse, delim: int): string raises(Syntax) +{ + { + s := ""; + while((c := p.getc()) != delim && c >= 0){ + if(c == '\\'){ + c = p.getc(); + if(c < 0) + break; + case c { + 'b' => c = '\b'; + 'f' => c = '\f'; + 'n' => c = '\n'; + 'r' => c = '\r'; + 't' => c = '\t'; + 'u' => + c = 0; + for(i := 0; i < 4; i++) + c = (c<<4) | hex(p.getc()); + * => ; # identity, including '"', '/', and '\' + } + } + s[len s] = c; + } + if(c < 0){ + if(c == Bufio->ERROR) + raise Syntax(sys->sprint("read error: %r")); + raise Syntax("unterminated string"); + } + return s; + }exception{ + Syntax => + raise; + } +} + +# hex ::= [0-9a-fA-F] +hex(c: int): int raises(Syntax) +{ + case c { + '0' to '9' => + return c-'0'; + 'a' to 'f' => + return 10+(c-'a'); + 'A' to 'F' => + return 10+(c-'A'); + * => + raise Syntax("invalid hex digit"); + } +} + +# digits ::= [0-9]+ +readdigits(p: ref Parse): string raises(Syntax) +{ + c := p.getc(); + if(!(c >= '0' && c <= '9')) + raise Syntax("expected integer literal"); + s := ""; + s[0] = c; + while((c = p.getc()) >= '0' && c <= '9') + s[len s] = c; + p.unget(c); + return s; +} + +writeval(out: ref Iobuf, o: ref JValue) raises(Badwrite) +{ + { + if(o == nil){ + puts(out, "null"); + return; + } + pick r := o { + String => + writestring(out, r.s); + Int => + puts(out, string r.value); + Real => + puts(out, string r.value); + Object => # '{' [pair (',' pair)*] '}' + putc(out, '{'); + for(l := r.mem; l != nil; l = tl l){ + if(l != r.mem) + putc(out, ','); + (n, v) := hd l; + writestring(out, n); + putc(out, ':'); + writeval(out, v); + } + putc(out, '}'); + Array => # '[' [value (',' value)*] ']' + putc(out, '['); + for(i := 0; i < len r.a; i++){ + if(i != 0) + putc(out, ','); + writeval(out, r.a[i]); + } + putc(out, ']'); + True => + puts(out, "true"); + False => + puts(out, "false"); + Null => + puts(out, "null"); + * => + raise "writeval: unknown value"; # can't happen + } + }exception{ + Badwrite => + raise; + } +} + +writestring(out: ref Iobuf, s: string) raises(Badwrite) +{ + { + putc(out, '"'); + for(i := 0; i < len s; i++){ + c := s[i]; + if(needesc(c)) + puts(out, escout(c)); + else + putc(out, c); + } + putc(out, '"'); + }exception{ + Badwrite => + raise; + } +} + +escout(c: int): string +{ + case c { + '"' => return "\\\""; + '\\' => return "\\\\"; + '/' => return "\\/"; + '\b' => return "\\b"; + '\f' => return "\\f"; + '\n' => return "\\n"; + '\t' => return "\\t"; + '\r' => return "\\r"; + * => return sys->sprint("\\u%.4ux", c); + } +} + +puts(out: ref Iobuf, s: string) raises(Badwrite) +{ + if(out.puts(s) == Bufio->ERROR) + raise Badwrite; +} + +putc(out: ref Iobuf, c: int) raises(Badwrite) +{ + if(out.putc(c) == Bufio->ERROR) + raise Badwrite; +} + +Parse: adt { + input: ref Iobuf; + eof: int; + + mk: fn(io: ref Iobuf): ref Parse; + getc: fn(nil: self ref Parse): int; + unget: fn(nil: self ref Parse, c: int); + getns: fn(nil: self ref Parse): int; +}; + +Parse.mk(io: ref Iobuf): ref Parse +{ + return ref Parse(io, 0); +} + +Parse.getc(p: self ref Parse): int +{ + if(p.eof) + return p.eof; + c := p.input.getc(); + if(c < 0) + p.eof = c; + return c; +} + +Parse.unget(p: self ref Parse, c: int) +{ + if(c >= 0) + p.input.ungetc(); +} + +# skip white space +Parse.getns(p: self ref Parse): int +{ + while((c := p.getc()) == ' ' || c == '\t' || c == '\n' || c == '\r') + {} + return c; +} + +JValue.isarray(v: self ref JValue): int +{ + return tagof v == tagof JValue.Array; +} + +JValue.isint(v: self ref JValue): int +{ + return tagof v == tagof JValue.Int; +} + +JValue.isnumber(v: self ref JValue): int +{ + return tagof v == tagof JValue.Int || tagof v == tagof JValue.Real; +} + +JValue.isobject(v: self ref JValue): int +{ + return tagof v == tagof JValue.Object; +} + +JValue.isreal(v: self ref JValue): int +{ + return tagof v == tagof JValue.Real; +} + +JValue.isstring(v: self ref JValue): int +{ + return tagof v == tagof JValue.String; +} + +JValue.istrue(v: self ref JValue): int +{ + return tagof v == tagof JValue.True; +} + +JValue.isfalse(v: self ref JValue): int +{ + return tagof v == tagof JValue.False; +} + +JValue.isnull(v: self ref JValue): int +{ + return tagof v == tagof JValue.Null; +} + +JValue.copy(v: self ref JValue): ref JValue +{ + pick r := v { + True or False or Null => + return ref *r; + Int => + return ref *r; + Real => + return ref *r; + String => + return ref *r; + Array => + a := array[len r.a] of ref JValue; + a[0:] = r.a; + return ref JValue.Array(a); + Object => + return ref *r; + * => + raise "json: bad copy"; # can't happen + } +} + +JValue.eq(a: self ref JValue, b: ref JValue): int +{ + if(a == b) + return 1; + if(a == nil || b == nil || tagof a != tagof b) + return 0; + pick r := a { + True or False or Null => + return 1; # tags were equal above + Int => + pick s := b { + Int => + return r.value == s.value; + } + Real => + pick s := b { + Real => + return r.value == s.value; + } + String => + pick s := b { + String => + return r.s == s.s; + } + Array => + pick s := b { + Array => + if(len r.a != len s.a) + return 0; + for(i := 0; i < len r.a; i++) + if(r.a[i] == nil){ + if(s.a[i] != nil) + return 0; + }else if(!r.a[i].eq(s.a[i])) + return 0; + return 1; + } + Object => + pick s := b { + Object => + ls := s.mem; + for(lr := r.mem; lr != nil; lr = tl lr){ + if(ls == nil) + return 0; + (rn, rv) := hd lr; + (sn, sv) := hd ls; + if(rn != sn) + return 0; + if(rv == nil){ + if(sv != nil) + return 0; + }else if(!rv.eq(sv)) + return 0; + } + return ls == nil; + } + } + return 0; +} + +JValue.get(v: self ref JValue, mem: string): ref JValue +{ + pick r := v { + Object => + for(l := r.mem; l != nil; l = tl l) + if((hd l).t0 == mem) + return (hd l).t1; + * => + return nil; + } +} + +# might be better if the interface were applicative? +# this is similar to behaviour of Limbo's own ref adt, though +JValue.set(v: self ref JValue, mem: string, val: ref JValue) +{ + pick j := v { + Object => + ol: list of (string, ref JValue); + for(l := j.mem; l != nil; l = tl l) + if((hd l).t0 == mem){ + l = tl l; + for(; ol != nil; ol = tl ol) + l = hd ol :: l; + j.mem = l; + return; + }else + ol = hd l :: ol; + j.mem = (mem, val) :: j.mem; + * => + raise "json: set non-object"; + } +} + +JValue.text(v: self ref JValue): string +{ + if(v == nil) + return "null"; + pick r := v { + True => + return "true"; + False => + return "false"; + Null => + return "null"; + Int => + return string r.value; + Real => + return string r.value; + String => + return quote(r.s); # quoted, or not? + Array => + s := "["; + for(i := 0; i < len r.a; i++){ + if(i != 0) + s += ", "; + s += r.a[i].text(); + } + return s+"]"; + Object => + s := "{"; + for(l := r.mem; l != nil; l = tl l){ + if(l != r.mem) + s += ", "; + s += quote((hd l).t0)+": "+(hd l).t1.text(); + } + return s+"}"; + * => + return nil; + } +} + +quote(s: string): string +{ + ns := "\""; + for(i := 0; i < len s; i++){ + c := s[i]; + if(needesc(c)) + ns += escout(c); + else + ns[len ns] = c; + } + return ns+"\""; +} + +needesc(c: int): int +{ + return c == '"' || c == '\\' || c == '/' || c <= 16r1F; # '/' is escaped to prevent "</xyz>" looking like an XML end tag(!) +} diff --git a/appl/lib/mkfile b/appl/lib/mkfile index 8aea08c2..7b2587d5 100644 --- a/appl/lib/mkfile +++ b/appl/lib/mkfile @@ -62,6 +62,7 @@ TARG=\ irsage.dis\ irsim.dis\ itslib.dis\ + json.dis\ keyset.dis\ libc.dis\ libc0.dis\ @@ -228,3 +229,4 @@ secstore.dis: $ROOT/module/secstore.m ida.dis: $ROOT/module/ida.m rfc822.dis: $ROOT/module/rfc822.m csv.dis: $ROOT/module/csv.m +json.dis: $ROOT/module/json.m |
