xref: /inferno-os/appl/lib/json.b (revision cd17ce433410c01516d7ba8f052a6c5b67b0e2d5)
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 string 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