1 /**
2  * JSON encoding.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.sd.json;
15 
16 import std.conv;
17 import std.exception;
18 import std.format;
19 import std.string : format;
20 import std.traits;
21 import std.utf;
22 
23 import ae.utils.meta;
24 import ae.utils.text;
25 
26 import ae.utils.sd.sd;
27 
28 /// Serialization source which parses a JSON stream.
29 struct JsonParser(C)
30 {
31 	alias Char = C;
32 
33 	C[] s;
34 	size_t p;
35 
36 	C next()
37 	{
38 		enforce(p < s.length);
39 		return s[p++];
40 	}
41 
42 	void skip()
43 	{
44 		p++;
45 	}
46 
47 	C[] readN(size_t n)
48 	{
49 		auto end = p + n;
50 		enforce(end <= s.length);
51 		C[] result = s[p .. end];
52 		p = end;
53 		return result;
54 	}
55 
56 	C peek()
57 	{
58 		enforce(p < s.length);
59 		return s[p];
60 	}
61 
62 	size_t mark()
63 	{
64 		return p;
65 	}
66 
67 	C[] slice(size_t a, size_t b)
68 	{
69 		return s[a..b];
70 	}
71 
72 	@property bool eof() { return p == s.length; }
73 
74 	// *******************************************************************
75 
76 	static bool isWhite(C c)
77 	{
78 		return c == ' ' || c == '\t';
79 	}
80 
81 	void skipWhitespace()
82 	{
83 		while (isWhite(peek()))
84 			skip();
85 	}
86 
87 	void expect(C c)
88 	{
89 		auto n = next();
90 		enforce(n==c, "Expected %s, got %s".format(c, n));
91 	}
92 
93 	// *******************************************************************
94 
95 	void read(Sink)(Sink sink)
96 	{
97 		skipWhitespace();
98 		switch (peek())
99 		{
100 			case '[':
101 				skip();
102 				sink.handleArray(boundFunctorOf!readArray);
103 				break;
104 			case '"':
105 				skip();
106 				sink.handleStringFragments(boundFunctorOf!readString);
107 				break;
108 			case 't':
109 				skip();
110 				expect('r');
111 				expect('u');
112 				expect('e');
113 				sink.handleBoolean(true);
114 				break;
115 			case 'f':
116 				skip();
117 				expect('a');
118 				expect('l');
119 				expect('s');
120 				expect('e');
121 				sink.handleBoolean(false);
122 				break;
123 			case 'n':
124 				skip();
125 				expect('u');
126 				expect('l');
127 				expect('l');
128 				sink.handleNull();
129 				break;
130 			case '-':
131 			case '0':
132 				..
133 			case '9':
134 				sink.handleNumeric(readNumeric());
135 				break;
136 			case '{':
137 				skip();
138 				sink.handleObject(boundFunctorOf!readObject);
139 				break;
140 			default:
141 				throw new Exception("Unknown JSON symbol: %s".format(peek()));
142 		}
143 	}
144 
145 	void readArray(Sink)(Sink sink)
146 	{
147 		if (peek()==']')
148 		{
149 			skip();
150 			return;
151 		}
152 		while (true)
153 		{
154 			read(sink);
155 			skipWhitespace();
156 			if (peek()==']')
157 			{
158 				skip();
159 				return;
160 			}
161 			else
162 				expect(',');
163 		}
164 	}
165 
166 	void readObject(Sink)(Sink sink)
167 	{
168 		skipWhitespace();
169 		if (peek()=='}')
170 		{
171 			skip();
172 			return;
173 		}
174 
175 		while (true)
176 		{
177 			sink.handleField(boundFunctorOf!read, boundFunctorOf!readObjectValue);
178 
179 			skipWhitespace();
180 			if (peek()=='}')
181 			{
182 				skip();
183 				return;
184 			}
185 			else
186 				expect(',');
187 		}
188 	}
189 
190 	void readObjectValue(Sink)(Sink sink)
191 	{
192 		skipWhitespace();
193 		expect(':');
194 		read(sink);
195 	}
196 
197 	/// This will call sink.handleStringFragment multiple times.
198 	void readString(Sink)(Sink sink)
199 	{
200 		auto start = mark();
201 
202 		void flush()
203 		{
204 			auto end = mark();
205 			if (start != end)
206 				sink.handleStringFragment(slice(start, end));
207 		}
208 
209 		void oneConst(C c)()
210 		{
211 			static C[1] arr = [c];
212 			sink.handleStringFragment(arr[]);
213 		}
214 
215 		while (true)
216 		{
217 			C c = peek();
218 			if (c=='"')
219 			{
220 				flush();
221 				skip();
222 				return;
223 			}
224 			else
225 			if (c=='\\')
226 			{
227 				flush();
228 				skip();
229 				switch (next())
230 				{
231 					case '"':  oneConst!('"'); break;
232 					case '/':  oneConst!('/'); break;
233 					case '\\': oneConst!('\\'); break;
234 					case 'b':  oneConst!('\b'); break;
235 					case 'f':  oneConst!('\f'); break;
236 					case 'n':  oneConst!('\n'); break;
237 					case 'r':  oneConst!('\r'); break;
238 					case 't':  oneConst!('\t'); break;
239 					case 'u':
240 					{
241 						auto w = cast(wchar)fromHex!ushort(readN(4));
242 						static if (C.sizeof == 1)
243 						{
244 							char[4] buf;
245 							sink.handleStringFragment(buf[0..encode(buf, w)]);
246 						}
247 						else
248 						{
249 							Unqual!C[1] buf;
250 							buf[0] = w;
251 							sink.handleStringFragment(buf[]);
252 						}
253 						break;
254 					}
255 					default: enforce(false, "Unknown escape");
256 				}
257 				start = mark();
258 			}
259 			else
260 				skip();
261 		}
262 	}
263 
264 	C[] readNumeric()
265 	{
266 		auto p = mark();
267 
268 		static immutable bool[256] numeric =
269 		[
270 			'0':true,
271 			'1':true,
272 			'2':true,
273 			'3':true,
274 			'4':true,
275 			'5':true,
276 			'6':true,
277 			'7':true,
278 			'8':true,
279 			'9':true,
280 			'.':true,
281 			'-':true,
282 			'+':true,
283 			'e':true,
284 			'E':true,
285 		];
286 
287 		while (!eof() && numeric[peek()])
288 			skip();
289 		return slice(p, mark());
290 	}
291 }
292 
293 // /// Encapsulates a reference to some field of a type.
294 // struct FieldRef(
295 // 	/// The type holding the field
296 // 	T,
297 // 	/// Field resolver - should return a pointer to the field given a T*
298 // 	alias resolve,
299 // )
300 // {
301 // 	T* _FieldRef_ptr;
302 // 	ref T _FieldRef_ref() { return *resolve(_FieldRef_ptr); }
303 // 	alias _FieldRef_ref this;
304 // }
305 
306 struct JsonDeserializer(C)
307 {
308 	JsonParser!C.Data jsonData;
309 	alias DataRef = FieldRef!(typeof(jsonData), p => p);
310 	void[0] anchor;
311 	alias Deserializer!anchor deserializer;
312 
313 	this(C[] s)
314 	{
315 		jsonData.s = s;
316 	}
317 
318 	T deserialize(T)()
319 	{
320 		T t;
321 		auto sink = deserializer.makeSink(&t);
322 		alias JsonImpl = JsonParser!C.Impl!DataRef;
323 		auto jsonImpl = JsonImpl(DataRef(&jsonData));
324 		jsonImpl.read(sink);
325 		return t;
326 	}
327 }
328 
329 /// Parse JSON from a string and deserialize it into the given type.
330 T jsonParse(T, C)(C[] s)
331 {
332 	auto parser = JsonDeserializer!C(s);
333 //	mixin(exceptionContext(q{format("Error at position %d", parser.p)}));
334 	return parser.deserialize!T();
335 }
336 
337 // ***************************************************************************
338 
339 struct Escapes
340 {
341 	string[256] chars;
342 	bool[256] escaped;
343 
344 	void populate()
345 	{
346 		import std.string;
347 
348 		escaped[] = true;
349 		foreach (c; 0..256)
350 			if (c=='\\')
351 				chars[c] = `\\`;
352 			else
353 			if (c=='\"')
354 				chars[c] = `\"`;
355 			else
356 			if (c=='\b')
357 				chars[c] = `\b`;
358 			else
359 			if (c=='\f')
360 				chars[c] = `\f`;
361 			else
362 			if (c=='\n')
363 				chars[c] = `\n`;
364 			else
365 			if (c=='\r')
366 				chars[c] = `\r`;
367 			else
368 			if (c=='\t')
369 				chars[c] = `\t`;
370 			else
371 			if (c<'\x20' || c == '\x7F' || c=='<' || c=='>' || c=='&')
372 				chars[c] = format(`\u%04x`, c);
373 			else
374 				chars[c] = [cast(char)c],
375 				escaped[c] = false;
376 	}
377 }
378 immutable Escapes escapes = { Escapes escapes; escapes.populate(); return escapes; }();
379 
380 /// Serialization target which writes a JSON stream.
381 struct JsonWriter
382 {
383 	static template Impl(alias source, alias output)
384 	{
385 		alias Parent = RefType!(thisOf!source);
386 
387 		static template Sink(alias output)
388 		{
389 			void handleNumeric(C)(C[] str)
390 			{
391 				output.put(str);
392 			}
393 
394 			void handleString(C)(C[] str)
395 			{
396 				output.put('"');
397 				handleStringFragment(str);
398 				output.put('"');
399 			}
400 
401 			void handleNull()
402 			{
403 				output.put("null");
404 			}
405 
406 			void handleBoolean(bool v)
407 			{
408 				output.put(v ? "true" : "false");
409 			}
410 
411 			void handleStringFragment(C)(C[] s)
412 			{
413 				auto start = s.ptr, p = start, end = start+s.length;
414 
415 				while (p < end)
416 				{
417 					auto c = *p++;
418 					if (escapes.escaped[c])
419 						output.put(start[0..p-start-1], escapes.chars[c]),
420 						start = p;
421 				}
422 
423 				output.put(start[0..p-start]);
424 			}
425 
426 			void handleArray(Reader)(Reader reader)
427 			{
428 				needComma = false;
429 				output.put('[');
430 				reader(scopeProxy!arraySink);
431 				output.put(']');
432 			}
433 
434 			void handleStringFragments(Reader)(Reader reader)
435 			{
436 				output.put('"');
437 				reader(scopeProxy!sink);
438 				output.put('"');
439 			}
440 
441 			void handleObject(Reader)(Reader reader)
442 			{
443 				needComma = false;
444 				output.put('{');
445 				reader(scopeProxy!objectSink);
446 				output.put('}');
447 			}
448 		}
449 		alias sink = Sink!output;
450 
451 		static bool needComma; // Yes, a global
452 
453 		static template ArraySink(alias output)
454 		{
455 			alias handleObject = opDispatch!"handleObject";
456 			alias handleStringFragments = opDispatch!"handleStringFragments";
457 			alias handleStringFragment = opDispatch!"handleStringFragment";
458 			alias handleBoolean = opDispatch!"handleBoolean";
459 			alias handleNull = opDispatch!"handleNull";
460 			alias handleNumeric = opDispatch!"handleNumeric";
461 			alias handleString = opDispatch!"handleString";
462 			alias handleArray = opDispatch!"handleArray";
463 
464 			template opDispatch(string name)
465 			{
466 				void opDispatch(Args...)(auto ref Args args)
467 				{
468 					if (needComma)
469 					{
470 						output.put(',');
471 						needComma = false;
472 					}
473 
474 					mixin("Sink!output." ~ name ~ "(args);");
475 					needComma = true;
476 				}
477 			}
478 		}
479 		alias arraySink = ArraySink!output;
480 
481 		static template ObjectSink(alias output)
482 		{
483 			void handleField(NameReader, ValueReader)(NameReader nameReader, ValueReader valueReader)
484 			{
485 				if (needComma)
486 				{
487 					output.put(',');
488 					needComma = false;
489 				}
490 
491 				nameReader (scopeProxy!sink);
492 				output.put(':');
493 				valueReader(scopeProxy!sink);
494 
495 				needComma = true;
496 			}
497 		}
498 		alias objectSink = ObjectSink!output;
499 
500 		auto makeSink()
501 		{
502 			auto s = scopeProxy!sink;
503 			return s;
504 		}
505 	}
506 }
507 
508 struct JsonSerializer(C)
509 {
510 	static assert(is(C == char), "TODO");
511 	import ae.utils.textout;
512 
513 	void[0] anchor;
514 	alias Serializer.Impl!anchor serializer;
515 
516 	StringBuilder sb;
517 	alias JsonWriter.Impl!(serializer, sb) writer;
518 
519 	void serialize(T)(auto ref T v)
520 	{
521 		auto sink = writer.makeSink();
522 		serializer.read(sink, v);
523 	}
524 }
525 
526 S toJson(S = string, T)(auto ref T v)
527 {
528 	JsonSerializer!(Unqual!(typeof(S.init[0]))) s;
529 	s.serialize(v);
530 	return s.sb.get();
531 }
532 
533 // ***************************************************************************
534