xref: /inferno-os/appl/lib/json.b (revision 1981fff245dfce579ef416fa767eb69d462039e9)
1implement JSON;
2
3#
4# Javascript `Object' Notation (JSON): RFC4627
5#
6
7include "sys.m";
8	sys: Sys;
9
10include "bufio.m";
11	bufio: Bufio;
12	Iobuf: import bufio;
13
14include "json.m";
15
16init(b: Bufio)
17{
18	sys = load Sys Sys->PATH;
19	bufio = b;
20}
21
22jvarray(a: array of ref JValue): ref JValue.Array
23{
24	return ref JValue.Array(a);
25}
26
27jvbig(i: big): ref JValue.Int
28{
29	return ref JValue.Int(i);
30}
31
32jvfalse(): ref JValue.False
33{
34	return ref JValue.False;
35}
36
37jvint(i: int): ref JValue.Int
38{
39	return ref JValue.Int(big i);
40}
41
42jvnull(): ref JValue.Null
43{
44	return ref JValue.Null;
45}
46
47jvobject(m: list of (string, ref JValue)): ref JValue.Object
48{
49	# could `uniq' the labels
50	return ref JValue.Object(m);
51}
52
53jvreal(r: real): ref JValue.Real
54{
55	return ref JValue.Real(r);
56}
57
58jvstring(s: string): ref JValue.String
59{
60	return ref JValue.String(s);
61}
62
63jvtrue(): ref JValue.True
64{
65	return ref JValue.True;
66}
67
68Syntax: exception(string);
69Badwrite: exception;
70
71readjson(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
86writejson(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#
100readval(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
206readstring(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]
243hex(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]+
258readdigits(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
271writeval(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, r.text());
283		Real =>
284			puts(out, r.text());
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
319writestring(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
337escout(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
352puts(out: ref Iobuf, s: string) raises(Badwrite)
353{
354	if(out.puts(s) == Bufio->ERROR)
355		raise Badwrite;
356}
357
358putc(out: ref Iobuf, c: int) raises(Badwrite)
359{
360	if(out.putc(c) == Bufio->ERROR)
361		raise Badwrite;
362}
363
364Parse: 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
374Parse.mk(io: ref Iobuf): ref Parse
375{
376	return ref Parse(io, 0);
377}
378
379Parse.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
389Parse.unget(p: self ref Parse, c: int)
390{
391	if(c >= 0)
392		p.input.ungetc();
393}
394
395# skip white space
396Parse.getns(p: self ref Parse): int
397{
398	while((c := p.getc()) == ' ' || c == '\t' || c == '\n' || c == '\r')
399		{}
400	return c;
401}
402
403JValue.isarray(v: self ref JValue): int
404{
405	return tagof v == tagof JValue.Array;
406}
407
408JValue.isint(v: self ref JValue): int
409{
410	return tagof v == tagof JValue.Int;
411}
412
413JValue.isnumber(v: self ref JValue): int
414{
415	return tagof v == tagof JValue.Int || tagof v == tagof JValue.Real;
416}
417
418JValue.isobject(v: self ref JValue): int
419{
420	return tagof v == tagof JValue.Object;
421}
422
423JValue.isreal(v: self ref JValue): int
424{
425	return tagof v == tagof JValue.Real;
426}
427
428JValue.isstring(v: self ref JValue): int
429{
430	return tagof v == tagof JValue.String;
431}
432
433JValue.istrue(v: self ref JValue): int
434{
435	return tagof v == tagof JValue.True;
436}
437
438JValue.isfalse(v: self ref JValue): int
439{
440	return tagof v == tagof JValue.False;
441}
442
443JValue.isnull(v: self ref JValue): int
444{
445	return tagof v == tagof JValue.Null;
446}
447
448JValue.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
470JValue.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
530JValue.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
545JValue.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
565JValue.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
603quote(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
616needesc(c: int): int
617{
618	return c == '"' || c == '\\' || c == '/' || c <= 16r1F;  # '/' is escaped to prevent "</xyz>" looking like an XML end tag(!)
619}
620