1 module dub.internal.vibecompat.data.json;
2 
3 version (Have_vibe_d) public import vibe.data.json;
4 else:
5 
6 import dub.internal.vibecompat.data.utils;
7 
8 import std.array;
9 import std.conv;
10 import std.datetime;
11 import std.exception;
12 import std.format;
13 import std..string;
14 import std.range;
15 import std.traits;
16 
17 
18 /******************************************************************************/
19 /* public types                                                               */
20 /******************************************************************************/
21 
22 /**
23 	Represents a single JSON value.
24 
25 	Json values can have one of the types defined in the Json.Type enum. They
26 	behave mostly like values in ECMA script in the way that you can
27 	transparently perform operations on them. However, strict typechecking is
28 	done, so that operations between differently typed JSON values will throw
29 	an exception. Additionally, an explicit cast or using get!() or to!() is
30 	required to convert a JSON value to the corresponding static D type.
31 */
32 struct Json {
33 	private {
34 		union {
35 			bool m_bool;
36 			long m_int;
37 			double m_float;
38 			string m_string;
39 			Json[] m_array;
40 			Json[string] m_object;
41 		};
42 		Type m_type = Type.undefined;
43 		uint m_magic = 0x1337f00d; // workaround for Appender bug
44 		string m_name;
45 	}
46 
47 	/** Represents the run time type of a JSON value.
48 	*/
49 	enum Type {
50 		/// A non-existent value in a JSON object
51 		undefined,
52 		/// Null value
53 		null_,
54 		/// Boolean value
55 		bool_,
56 		/// 64-bit integer value
57 		int_,
58 		/// 64-bit floating point value
59 		float_,
60 		/// UTF-8 string
61 		string,
62 		/// Array of JSON values
63 		array,
64 		/// JSON object aka. dictionary from string to Json
65 		object
66 	}
67 
68 	/// New JSON value of Type.undefined
69 	static @property Json undefined() { return Json(); }
70 
71 	/// New JSON value of Type.object
72 	static @property Json emptyObject() { return Json(cast(Json[string])null); }
73 
74 	/// New JSON value of Type.array
75 	static @property Json emptyArray() { return Json(cast(Json[])null); }
76 
77 	version(JsonLineNumbers) int line;
78 
79 	/**
80 		Constructor for a JSON object.
81 	*/
82 	this(typeof(null)) { m_type = Type.null_; }
83 	/// ditto
84 	this(bool v) { m_type = Type.bool_; m_bool = v; }
85 	/// ditto
86 	this(int v) { m_type = Type.int_; m_int = v; }
87 	/// ditto
88 	this(long v) { m_type = Type.int_; m_int = v; }
89 	/// ditto
90 	this(double v) { m_type = Type.float_; m_float = v; }
91 	/// ditto
92 	this(string v) { m_type = Type..string; m_string = v; }
93 	/// ditto
94 	this(Json[] v) { m_type = Type.array; m_array = v; }
95 	/// ditto
96 	this(Json[string] v) { m_type = Type.object; m_object = v; }
97 
98 	/**
99 		Allows assignment of D values to a JSON value.
100 	*/
101 	ref Json opAssign(Json v){
102 		m_type = v.m_type;
103 		final switch(m_type){
104 			case Type.undefined: m_string = null; break;
105 			case Type.null_: m_string = null; break;
106 			case Type.bool_: m_bool = v.m_bool; break;
107 			case Type.int_: m_int = v.m_int; break;
108 			case Type.float_: m_float = v.m_float; break;
109 			case Type..string: m_string = v.m_string; break;
110 			case Type.array:
111 				m_array = v.m_array;
112 				if (m_magic == 0x1337f00d) { foreach (ref av; m_array) av.m_name = m_name; } else m_name = null;
113 				break;
114 			case Type.object:
115 				m_object = v.m_object;
116 				if (m_magic == 0x1337f00d) { foreach (k, ref av; m_object) av.m_name = m_name ~ "." ~ k; } else m_name = null;
117 				break;
118 		}
119 		return this;
120 	}
121 	/// ditto
122 	void opAssign(typeof(null)) { m_type = Type.null_; m_string = null; }
123 	/// ditto
124 	bool opAssign(bool v) { m_type = Type.bool_; m_bool = v; return v; }
125 	/// ditto
126 	int opAssign(int v) { m_type = Type.int_; m_int = v; return v; }
127 	/// ditto
128 	long opAssign(long v) { m_type = Type.int_; m_int = v; return v; }
129 	/// ditto
130 	double opAssign(double v) { m_type = Type.float_; m_float = v; return v; }
131 	/// ditto
132 	string opAssign(string v) { m_type = Type..string; m_string = v; return v; }
133 	/// ditto
134 	Json[] opAssign(Json[] v)
135 	{
136 		m_type = Type.array;
137 		m_array = v;
138 		if (m_magic == 0x1337f00d) foreach (ref av; m_array) av.m_name = m_name;
139 		return v;
140 	}
141 	/// ditto
142 	Json[string] opAssign(Json[string] v)
143 	{
144 		m_type = Type.object;
145 		m_object = v;
146 		if (m_magic == 0x1337f00d) foreach (k, ref av; m_object) av.m_name = m_name ~ "." ~ k;
147 		return v;
148 	}
149 
150 	/**
151 		The current type id of this JSON object.
152 	*/
153 	@property Type type() const { return m_type; }
154 
155 	/**
156 		Allows direct indexing of array typed JSON values.
157 	*/
158 	ref inout(Json) opIndex(size_t idx) inout { checkType!(Json[])(); return m_array[idx]; }
159 
160 	/**
161 		Allows direct indexing of object typed JSON values using a string as
162 		the key.
163 	*/
164 	const(Json) opIndex(string key) const {
165 		checkType!(Json[string])();
166 		if( auto pv = key in m_object ) return *pv;
167 		Json ret = Json.undefined;
168 		ret.m_string = key;
169 		return ret;
170 	}
171 	/// ditto
172 	ref Json opIndex(string key){
173 		checkType!(Json[string])();
174 		if( auto pv = key in m_object )
175 			return *pv;
176 		m_object[key] = Json();
177 		m_object[key].m_type = Type.undefined; // DMDBUG: AAs are teh $H1T!!!11
178 		assert(m_object[key].type == Type.undefined);
179 		m_object[key].m_name = m_name ~ "." ~ key;
180 		m_object[key].m_string = key;
181 		return m_object[key];
182 	}
183 
184 	/**
185 		Returns a slice of a JSON array.
186 	*/
187 	inout(Json[]) opSlice() inout { checkType!(Json[])(); return m_array; }
188 	///
189 	inout(Json[]) opSlice(size_t from, size_t to) inout { checkType!(Json[])(); return m_array[from .. to]; }
190 
191 	/**
192 		Removes an entry from an object.
193 	*/
194 	void remove(string item) { checkType!(Json[string])(); m_object.remove(item); }
195 
196 	/**
197 		Returns the number of entries of string, array or object typed JSON values.
198 	*/
199 	@property size_t length()
200 	const {
201 		checkType!(string, Json[], Json[string]);
202 		switch(m_type){
203 			case Type..string: return m_string.length;
204 			case Type.array: return m_array.length;
205 			case Type.object: return m_object.length;
206 			default: assert(false);
207 		}
208 	}
209 
210 	/**
211 		Allows foreach iterating over JSON objects and arrays.
212 	*/
213 	int opApply(int delegate(ref Json obj) del)
214 	{
215 		checkType!(Json[], Json[string]);
216 		if( m_type == Type.array ){
217 			foreach( ref v; m_array )
218 				if( auto ret = del(v) )
219 					return ret;
220 			return 0;
221 		} else {
222 			foreach( ref v; m_object )
223 				if( v.type != Type.undefined )
224 					if( auto ret = del(v) )
225 						return ret;
226 			return 0;
227 		}
228 	}
229 	/// ditto
230 	int opApply(int delegate(ref const Json obj) del)
231 	const {
232 		checkType!(Json[], Json[string]);
233 		if( m_type == Type.array ){
234 			foreach( ref v; m_array )
235 				if( auto ret = del(v) )
236 					return ret;
237 			return 0;
238 		} else {
239 			foreach( ref v; m_object )
240 				if( v.type != Type.undefined )
241 					if( auto ret = del(v) )
242 						return ret;
243 			return 0;
244 		}
245 	}
246 	/// ditto
247 	int opApply(int delegate(ref size_t idx, ref Json obj) del)
248 	{
249 		checkType!(Json[]);
250 		foreach( idx, ref v; m_array )
251 			if( auto ret = del(idx, v) )
252 				return ret;
253 		return 0;
254 	}
255 	/// ditto
256 	int opApply(int delegate(ref size_t idx, ref const Json obj) del)
257 	const {
258 		checkType!(Json[]);
259 		foreach( idx, ref v; m_array )
260 			if( auto ret = del(idx, v) )
261 				return ret;
262 		return 0;
263 	}
264 	/// ditto
265 	int opApply(int delegate(ref string idx, ref Json obj) del)
266 	{
267 		checkType!(Json[string]);
268 		foreach( idx, ref v; m_object )
269 			if( v.type != Type.undefined )
270 				if( auto ret = del(idx, v) )
271 					return ret;
272 		return 0;
273 	}
274 	/// ditto
275 	int opApply(int delegate(ref string idx, ref const Json obj) del)
276 	const {
277 		checkType!(Json[string]);
278 		foreach( idx, ref v; m_object )
279 			if( v.type != Type.undefined )
280 				if( auto ret = del(idx, v) )
281 					return ret;
282 		return 0;
283 	}
284 
285 	/**
286 		Converts the JSON value to the corresponding D type - types must match exactly.
287 	*/
288 	inout(T) opCast(T)() inout { return get!T; }
289 	/// ditto
290 	@property inout(T) get(T)()
291 	inout {
292 		checkType!T();
293 		static if( is(T == bool) ) return m_bool;
294 		else static if( is(T == double) ) return m_float;
295 		else static if( is(T == float) ) return cast(T)m_float;
296 		else static if( is(T == long) ) return m_int;
297 		else static if( is(T : long) ){ enforce(m_int <= T.max && m_int >= T.min); return cast(T)m_int; }
298 		else static if( is(T == string) ) return m_string;
299 		else static if( is(T == Json[]) ) return m_array;
300 		else static if( is(T == Json[string]) ) return m_object;
301 		else static assert("JSON can only be casted to (bool, long, double, string, Json[] or Json[string]. Not "~T.stringof~".");
302 	}
303 	/// ditto
304 	@property const(T) opt(T)(const(T) def = T.init)
305 	const {
306 		if( typeId!T != m_type ) return def;
307 		return get!T;
308 	}
309 	/// ditto
310 	@property T opt(T)(T def = T.init)
311 	{
312 		if( typeId!T != m_type ) return def;
313 		return get!T;
314 	}
315 
316 	/**
317 		Converts the JSON value to the corresponding D type - types are converted as neccessary.
318 	*/
319 	@property inout(T) to(T)()
320 	inout {
321 		static if( is(T == bool) ){
322 			final switch( m_type ){
323 				case Type.undefined: return false;
324 				case Type.null_: return false;
325 				case Type.bool_: return m_bool;
326 				case Type.int_: return m_int != 0;
327 				case Type.float_: return m_float != 0;
328 				case Type..string: return m_string.length > 0;
329 				case Type.array: return m_array.length > 0;
330 				case Type.object: return m_object.length > 0;
331 			}
332 		} else static if( is(T == double) ){
333 			final switch( m_type ){
334 				case Type.undefined: return T.init;
335 				case Type.null_: return 0;
336 				case Type.bool_: return m_bool ? 1 : 0;
337 				case Type.int_: return m_int;
338 				case Type.float_: return m_float;
339 				case Type..string: return .to!double(cast(string)m_string);
340 				case Type.array: return double.init;
341 				case Type.object: return double.init;
342 			}
343 		} else static if( is(T == float) ){
344 			final switch( m_type ){
345 				case Type.undefined: return T.init;
346 				case Type.null_: return 0;
347 				case Type.bool_: return m_bool ? 1 : 0;
348 				case Type.int_: return m_int;
349 				case Type.float_: return m_float;
350 				case Type..string: return .to!float(cast(string)m_string);
351 				case Type.array: return float.init;
352 				case Type.object: return float.init;
353 			}
354 		}
355 		else static if( is(T == long) ){
356 			final switch( m_type ){
357 				case Type.undefined: return 0;
358 				case Type.null_: return 0;
359 				case Type.bool_: return m_bool ? 1 : 0;
360 				case Type.int_: return m_int;
361 				case Type.float_: return cast(long)m_float;
362 				case Type..string: return .to!long(m_string);
363 				case Type.array: return 0;
364 				case Type.object: return 0;
365 			}
366 		} else static if( is(T : long) ){
367 			final switch( m_type ){
368 				case Type.undefined: return 0;
369 				case Type.null_: return 0;
370 				case Type.bool_: return m_bool ? 1 : 0;
371 				case Type.int_: return cast(T)m_int;
372 				case Type.float_: return cast(T)m_float;
373 				case Type..string: return cast(T).to!long(cast(string)m_string);
374 				case Type.array: return 0;
375 				case Type.object: return 0;
376 			}
377 		} else static if( is(T == string) ){
378 			switch( m_type ){
379 				default: return toString();
380 				case Type..string: return m_string;
381 			}
382 		} else static if( is(T == Json[]) ){
383 			switch( m_type ){
384 				default: return Json([this]);
385 				case Type.array: return m_array;
386 			}
387 		} else static if( is(T == Json[string]) ){
388 			switch( m_type ){
389 				default: return Json(["value": this]);
390 				case Type.object: return m_object;
391 			}
392 		} else static assert("JSON can only be casted to (bool, long, double, string, Json[] or Json[string]. Not "~T.stringof~".");
393 	}
394 
395 	/**
396 		Performs unary operations on the JSON value.
397 
398 		The following operations are supported for each type:
399 
400 		$(DL
401 			$(DT Null)   $(DD none)
402 			$(DT Bool)   $(DD ~)
403 			$(DT Int)    $(DD +, -, ++, --)
404 			$(DT Float)  $(DD +, -, ++, --)
405 			$(DT String) $(DD none)
406 			$(DT Array)  $(DD none)
407 			$(DT Object) $(DD none)
408 		)
409 	*/
410 	Json opUnary(string op)()
411 	const {
412 		static if( op == "~" ){
413 			checkType!bool();
414 			return Json(~m_bool);
415 		} else static if( op == "+" || op == "-" || op == "++" || op == "--" ){
416 			checkType!(long, double);
417 			if( m_type == Type.int_ ) mixin("return Json("~op~"m_int);");
418 			else if( m_type == Type.float_ ) mixin("return Json("~op~"m_float);");
419 			else assert(false);
420 		} else static assert("Unsupported operator '"~op~"' for type JSON.");
421 	}
422 
423 	/**
424 		Performs binary operations between JSON values.
425 
426 		The two JSON values must be of the same run time type or an exception
427 		will be thrown. Only the operations listed are allowed for each of the
428 		types.
429 
430 		$(DL
431 			$(DT Null)   $(DD none)
432 			$(DT Bool)   $(DD &&, ||)
433 			$(DT Int)    $(DD +, -, *, /, %)
434 			$(DT Float)  $(DD +, -, *, /, %)
435 			$(DT String) $(DD ~)
436 			$(DT Array)  $(DD ~)
437 			$(DT Object) $(DD none)
438 		)
439 	*/
440 	Json opBinary(string op)(ref const(Json) other)
441 	const {
442 		enforce(m_type == other.m_type, "Binary operation '"~op~"' between "~.to!string(m_type)~" and "~.to!string(other.m_type)~" JSON objects.");
443 		static if( op == "&&" ){
444 			checkType!(bool)("'&&'"); other.checkType!(bool)("'&&'");
445 			return Json(m_bool && other.m_bool);
446 		} else static if( op == "||" ){
447 			checkType!(bool)("'||'"); other.checkType!(bool)("'||'");
448 			return Json(m_bool || other.m_bool);
449 		} else static if( op == "+" ){
450 			checkType!(double, long)("'+'"); other.checkType!(double, long)("'+'");
451 			if( m_type == Type.int_ ) return Json(m_int + other.m_int);
452 			else if( m_type == Type.float_ ) return Json(m_float + other.m_float);
453 			else assert(false);
454 		} else static if( op == "-" ){
455 			checkType!(double, long)("'-'"); other.checkType!(double, long)("'-'");
456 			if( m_type == Type.int_ ) return Json(m_int - other.m_int);
457 			else if( m_type == Type.float_ ) return Json(m_float - other.m_float);
458 			else assert(false);
459 		} else static if( op == "*" ){
460 			checkType!(double, long)("'*'"); other.checkType!(double, long)("'*'");
461 			if( m_type == Type.int_ ) return Json(m_int * other.m_int);
462 			else if( m_type == Type.float_ ) return Json(m_float * other.m_float);
463 			else assert(false);
464 		} else static if( op == "/" ){
465 			checkType!(double, long)("'/'"); other.checkType!(double, long)("'/'");
466 			if( m_type == Type.int_ ) return Json(m_int / other.m_int);
467 			else if( m_type == Type.float_ ) return Json(m_float / other.m_float);
468 			else assert(false);
469 		} else static if( op == "%" ){
470 			checkType!(double, long)("'%'"); other.checkType!(double, long)("'%'");
471 			if( m_type == Type.int_ ) return Json(m_int % other.m_int);
472 			else if( m_type == Type.float_ ) return Json(m_float % other.m_float);
473 			else assert(false);
474 		} else static if( op == "~" ){
475 			checkType!(string)("'~'"); other.checkType!(string)("'~'");
476 			if( m_type == Type..string ) return Json(m_string ~ other.m_string);
477 			else assert(false);
478 		} else static assert("Unsupported operator '"~op~"' for type JSON.");
479 	}
480 	/// ditto
481 	Json opBinary(string op)(Json other)
482 		if( op == "~" )
483 	{
484 		static if( op == "~" ){
485 			checkType!(string, Json[])("'~'"); other.checkType!(string, Json[])("'~'");
486 			if( m_type == Type..string ) return Json(m_string ~ other.m_string);
487 			else if( m_type == Type.array ) return Json(m_array ~ other.m_array);
488 			else assert(false);
489 		} else static assert("Unsupported operator '"~op~"' for type JSON.");
490 	}
491 	/// ditto
492 	void opOpAssign(string op)(Json other)
493 		if( op == "+" || op == "-" || op == "*" ||op == "/" || op == "%" )
494 	{
495 		enforce(m_type == other.m_type, "Binary operation '"~op~"' between "~.to!string(m_type)~" and "~.to!string(other.m_type)~" JSON objects.");
496 		static if( op == "+" ){
497 			if( m_type == Type.int_ ) m_int += other.m_int;
498 			else if( m_type == Type.float_ ) m_float += other.m_float;
499 			else enforce(false, "'+' only allowed for scalar types, not "~.to!string(m_type)~".");
500 		} else static if( op == "-" ){
501 			if( m_type == Type.int_ ) m_int -= other.m_int;
502 			else if( m_type == Type.float_ ) m_float -= other.m_float;
503 			else enforce(false, "'-' only allowed for scalar types, not "~.to!string(m_type)~".");
504 		} else static if( op == "*" ){
505 			if( m_type == Type.int_ ) m_int *= other.m_int;
506 			else if( m_type == Type.float_ ) m_float *= other.m_float;
507 			else enforce(false, "'*' only allowed for scalar types, not "~.to!string(m_type)~".");
508 		} else static if( op == "/" ){
509 			if( m_type == Type.int_ ) m_int /= other.m_int;
510 			else if( m_type == Type.float_ ) m_float /= other.m_float;
511 			else enforce(false, "'/' only allowed for scalar types, not "~.to!string(m_type)~".");
512 		} else static if( op == "%" ){
513 			if( m_type == Type.int_ ) m_int %= other.m_int;
514 			else if( m_type == Type.float_ ) m_float %= other.m_float;
515 			else enforce(false, "'%' only allowed for scalar types, not "~.to!string(m_type)~".");
516 		} /*else static if( op == "~" ){
517 			if( m_type == Type.string ) m_string ~= other.m_string;
518 			else if( m_type == Type.array ) m_array ~= other.m_array;
519 			else enforce(false, "'%' only allowed for scalar types, not "~.to!string(m_type)~".");
520 		}*/ else static assert("Unsupported operator '"~op~"' for type JSON.");
521 		assert(false);
522 	}
523 	/// ditto
524 	Json opBinary(string op)(bool other) const { checkType!bool(); mixin("return Json(m_bool "~op~" other);"); }
525 	/// ditto
526 	Json opBinary(string op)(long other) const { checkType!long(); mixin("return Json(m_int "~op~" other);"); }
527 	/// ditto
528 	Json opBinary(string op)(double other) const { checkType!double(); mixin("return Json(m_float "~op~" other);"); }
529 	/// ditto
530 	Json opBinary(string op)(string other) const { checkType!string(); mixin("return Json(m_string "~op~" other);"); }
531 	/// ditto
532 	Json opBinary(string op)(Json other)
533 	if (op == "~") {
534 		if (m_type == Type.array) return Json(m_array ~ other);
535 		else return Json(this ~ other);
536 	}
537 	/// ditto
538 	Json opBinaryRight(string op)(bool other) const { checkType!bool(); mixin("return Json(other "~op~" m_bool);"); }
539 	/// ditto
540 	Json opBinaryRight(string op)(long other) const { checkType!long(); mixin("return Json(other "~op~" m_int);"); }
541 	/// ditto
542 	Json opBinaryRight(string op)(double other) const { checkType!double(); mixin("return Json(other "~op~" m_float);"); }
543 	/// ditto
544 	Json opBinaryRight(string op)(string other) const if(op == "~") { checkType!string(); return Json(other ~ m_string); }
545 	/// ditto
546 	inout(Json)* opBinaryRight(string op)(string other) inout if(op == "in") {
547 		checkType!(Json[string])();
548 		auto pv = other in m_object;
549 		if( !pv ) return null;
550 		if( pv.type == Type.undefined ) return null;
551 		return pv;
552 	}
553 
554 	/**
555 		Allows to access existing fields of a JSON object using dot syntax.
556 	*/
557 	@property const(Json) opDispatch(string prop)() const { return opIndex(prop); }
558 	/// ditto
559 	@property ref Json opDispatch(string prop)() { return opIndex(prop); }
560 
561 	/**
562 		Compares two JSON values for equality.
563 
564 		If the two values have different types, they are considered unequal.
565 		This differs with ECMA script, which performs a type conversion before
566 		comparing the values.
567 	*/
568 	bool opEquals(ref const Json other)
569 	const {
570 		if( m_type != other.m_type ) return false;
571 		final switch(m_type){
572 			case Type.undefined: return false;
573 			case Type.null_: return true;
574 			case Type.bool_: return m_bool == other.m_bool;
575 			case Type.int_: return m_int == other.m_int;
576 			case Type.float_: return m_float == other.m_float;
577 			case Type..string: return m_string == other.m_string;
578 			case Type.array: return m_array == other.m_array;
579 			case Type.object: return m_object == other.m_object;
580 		}
581 	}
582 	/// ditto
583 	bool opEquals(const Json other) const { return opEquals(other); }
584 	/// ditto
585 	bool opEquals(typeof(null)) const { return m_type == Type.null_; }
586 	/// ditto
587 	bool opEquals(bool v) const { return m_type == Type.bool_ && m_bool == v; }
588 	/// ditto
589 	bool opEquals(long v) const { return m_type == Type.int_ && m_int == v; }
590 	/// ditto
591 	bool opEquals(double v) const { return m_type == Type.float_ && m_float == v; }
592 	/// ditto
593 	bool opEquals(string v) const { return m_type == Type..string && m_string == v; }
594 
595 	/**
596 		Compares two JSON values.
597 
598 		If the types of the two values differ, the value with the smaller type
599 		id is considered the smaller value. This differs from ECMA script, which
600 		performs a type conversion before comparing the values.
601 
602 		JSON values of type Object cannot be compared and will throw an
603 		exception.
604 	*/
605 	int opCmp(ref const Json other)
606 	const {
607 		if( m_type != other.m_type ) return m_type < other.m_type ? -1 : 1;
608 		final switch(m_type){
609 			case Type.undefined: return 0;
610 			case Type.null_: return 0;
611 			case Type.bool_: return m_bool < other.m_bool ? -1 : m_bool == other.m_bool ? 0 : 1;
612 			case Type.int_: return m_int < other.m_int ? -1 : m_int == other.m_int ? 0 : 1;
613 			case Type.float_: return m_float < other.m_float ? -1 : m_float == other.m_float ? 0 : 1;
614 			case Type..string: return m_string < other.m_string ? -1 : m_string == other.m_string ? 0 : 1;
615 			case Type.array: return m_array < other.m_array ? -1 : m_array == other.m_array ? 0 : 1;
616 			case Type.object:
617 				enforce(false, "JSON objects cannot be compared.");
618 				assert(false);
619 		}
620 	}
621 
622 
623 
624 	/**
625 		Returns the type id corresponding to the given D type.
626 	*/
627 	static @property Type typeId(T)() {
628 		static if( is(T == typeof(null)) ) return Type.null_;
629 		else static if( is(T == bool) ) return Type.bool_;
630 		else static if( is(T == double) ) return Type.float_;
631 		else static if( is(T == float) ) return Type.float_;
632 		else static if( is(T : long) ) return Type.int_;
633 		else static if( is(T == string) ) return Type..string;
634 		else static if( is(T == Json[]) ) return Type.array;
635 		else static if( is(T == Json[string]) ) return Type.object;
636 		else static assert(false, "Unsupported JSON type '"~T.stringof~"'. Only bool, long, double, string, Json[] and Json[string] are allowed.");
637 	}
638 
639 	/**
640 		Returns the JSON object as a string.
641 
642 		For large JSON values use writeJsonString instead as this function will store the whole string
643 		in memory, whereas writeJsonString writes it out bit for bit.
644 
645 		See_Also: writeJsonString, toPrettyString
646 	*/
647 	string toString()
648 	const {
649 		auto ret = appender!string();
650 		writeJsonString(ret, this);
651 		return ret.data;
652 	}
653 
654 	/**
655 		Returns the JSON object as a "pretty" string.
656 
657 		---
658 		auto json = Json(["foo": Json("bar")]);
659 		writeln(json.toPrettyString());
660 
661 		// output:
662 		// {
663 		//     "foo": "bar"
664 		// }
665 		---
666 
667 		Params:
668 			level = Specifies the base amount of indentation for the output. Indentation  is always
669 				done using tab characters.
670 
671 		See_Also: writePrettyJsonString, toString
672 	*/
673 	string toPrettyString(int level = 0)
674 	const {
675 		auto ret = appender!string();
676 		writePrettyJsonString(ret, this, level);
677 		return ret.data;
678 	}
679 
680 	private void checkType(T...)(string op = null)
681 	const {
682 		bool found = false;
683 		foreach (t; T) if (m_type == typeId!t) found = true;
684 		if (found) return;
685 		if (T.length == 1) {
686 			throw new Exception(format("Got %s - expected %s.", this.displayName, typeId!(T[0]).to!string));
687 		} else {
688 			string types;
689 			foreach (t; T) {
690 				if (types.length) types ~= ", ";
691 				types ~= typeId!t.to!string;
692 			}
693 			throw new Exception(format("Got %s - expected one of %s.", this.displayName, types));
694 		}
695 	}
696 
697 	private @property string displayName()
698 	const {
699 		if (m_name.length) return m_name ~ " of type " ~ m_type.to!string();
700 		else return "JSON of type " ~ m_type.to!string();
701 	}
702 
703 	/*invariant()
704 	{
705 		assert(m_type >= Type.undefined && m_type <= Type.object);
706 	}*/
707 }
708 
709 
710 /******************************************************************************/
711 /* public functions                                                           */
712 /******************************************************************************/
713 
714 /**
715 	Parses the given range as a JSON string and returns the corresponding Json object.
716 
717 	The range is shrunk during parsing, leaving any remaining text that is not part of
718 	the JSON contents.
719 
720 	Throws an Exception if any parsing error occured.
721 */
722 Json parseJson(R)(ref R range, int* line = null)
723 	if( is(R == string) )
724 {
725 	Json ret;
726 	enforce(!range.empty, "JSON string is empty.");
727 
728 	skipWhitespace(range, line);
729 
730 	version(JsonLineNumbers){
731 		import vibecompat.core.log;
732 		int curline = line ? *line : 0;
733 		scope(failure) logError("Error in line: %d", curline);
734 	}
735 
736 	switch( range.front ){
737 		case 'f':
738 			enforce(range[1 .. $].startsWith("alse"), "Expected 'false', got '"~range[0 .. 5]~"'.");
739 			range.popFrontN(5);
740 			ret = false;
741 			break;
742 		case 'n':
743 			enforce(range[1 .. $].startsWith("ull"), "Expected 'null', got '"~range[0 .. 4]~"'.");
744 			range.popFrontN(4);
745 			ret = null;
746 			break;
747 		case 't':
748 			enforce(range[1 .. $].startsWith("rue"), "Expected 'true', got '"~range[0 .. 4]~"'.");
749 			range.popFrontN(4);
750 			ret = true;
751 			break;
752 		case '0': .. case '9'+1:
753 		case '-':
754 			bool is_float;
755 			auto num = skipNumber(range, is_float);
756 			if( is_float ) ret = to!double(num);
757 			else ret = to!long(num);
758 			break;
759 		case '\"':
760 			ret = skipJsonString(range);
761 			break;
762 		case '[':
763 			Json[] arr;
764 			range.popFront();
765 			while(true) {
766 				skipWhitespace(range, line);
767 				enforce(!range.empty);
768 				if(range.front == ']') break;
769 				arr ~= parseJson(range, line);
770 				skipWhitespace(range, line);
771 				enforce(!range.empty && (range.front == ',' || range.front == ']'), "Expected ']' or ','.");
772 				if( range.front == ']' ) break;
773 				else range.popFront();
774 			}
775 			range.popFront();
776 			ret = arr;
777 			break;
778 		case '{':
779 			Json[string] obj;
780 			range.popFront();
781 			while(true) {
782 				skipWhitespace(range, line);
783 				enforce(!range.empty);
784 				if(range.front == '}') break;
785 				string key = skipJsonString(range);
786 				skipWhitespace(range, line);
787 				enforce(range.startsWith(":"), "Expected ':' for key '" ~ key ~ "'");
788 				range.popFront();
789 				skipWhitespace(range, line);
790 				Json itm = parseJson(range, line);
791 				obj[key] = itm;
792 				skipWhitespace(range, line);
793 				enforce(!range.empty && (range.front == ',' || range.front == '}'), "Expected '}' or ',' - got '"~range[0]~"'.");
794 				if( range.front == '}' ) break;
795 				else range.popFront();
796 			}
797 			range.popFront();
798 			ret = obj;
799 			break;
800 		default:
801 			enforce(false, "Expected valid json token, got '"~to!string(range.length)~range[0 .. range.length>12?12:range.length]~"'.");
802 	}
803 
804 	assert(ret.type != Json.Type.undefined);
805 	version(JsonLineNumbers) ret.line = curline;
806 	return ret;
807 }
808 
809 /**
810 	Parses the given JSON string and returns the corresponding Json object.
811 
812 	Throws an Exception if any parsing error occurs.
813 */
814 Json parseJsonString(string str)
815 {
816 	auto ret = parseJson(str);
817 	enforce(str.strip().length == 0, "Expected end of string after JSON value.");
818 	return ret;
819 }
820 
821 unittest {
822 	assert(parseJsonString("null") == Json(null));
823 	assert(parseJsonString("true") == Json(true));
824 	assert(parseJsonString("false") == Json(false));
825 	assert(parseJsonString("1") == Json(1));
826 	assert(parseJsonString("2.0") == Json(2.0));
827 	assert(parseJsonString("\"test\"") == Json("test"));
828 	assert(parseJsonString("[1, 2, 3]") == Json([Json(1), Json(2), Json(3)]));
829 	assert(parseJsonString("{\"a\": 1}") == Json(["a": Json(1)]));
830 	assert(parseJsonString(`"\\\/\b\f\n\r\t\u1234"`).get!string == "\\/\b\f\n\r\t\u1234");
831 }
832 
833 
834 /**
835 	Serializes the given value to JSON.
836 
837 	The following types of values are supported:
838 
839 	$(DL
840 		$(DT Json)            $(DD Used as-is)
841 		$(DT null)            $(DD Converted to Json.Type.null_)
842 		$(DT bool)            $(DD Converted to Json.Type.bool_)
843 		$(DT float, double)   $(DD Converted to Json.Type.Double)
844 		$(DT short, ushort, int, uint, long, ulong) $(DD Converted to Json.Type.int_)
845 		$(DT string)          $(DD Converted to Json.Type.string)
846 		$(DT T[])             $(DD Converted to Json.Type.array)
847 		$(DT T[string])       $(DD Converted to Json.Type.object)
848 		$(DT struct)          $(DD Converted to Json.Type.object)
849 		$(DT class)           $(DD Converted to Json.Type.object or Json.Type.null_)
850 	)
851 
852 	All entries of an array or an associative array, as well as all R/W properties and
853 	all public fields of a struct/class are recursively serialized using the same rules.
854 
855 	Fields ending with an underscore will have the last underscore stripped in the
856 	serialized output. This makes it possible to use fields with D keywords as their name
857 	by simply appending an underscore.
858 
859 	The following methods can be used to customize the serialization of structs/classes:
860 
861 	---
862 	Json toJson() const;
863 	static T fromJson(Json src);
864 
865 	string toString() const;
866 	static T fromString(string src);
867 	---
868 
869 	The methods will have to be defined in pairs. The first pair that is implemented by
870 	the type will be used for serialization (i.e. toJson overrides toString).
871 */
872 Json serializeToJson(T)(T value)
873 {
874 	alias Unqual!T TU;
875 	static if( is(TU == Json) ) return value;
876 	else static if( is(TU == typeof(null)) ) return Json(null);
877 	else static if( is(TU == bool) ) return Json(value);
878 	else static if( is(TU == float) ) return Json(cast(double)value);
879 	else static if( is(TU == double) ) return Json(value);
880 	else static if( is(TU == DateTime) ) return Json(value.toISOExtString());
881 	else static if( is(TU == SysTime) ) return Json(value.toISOExtString());
882 	else static if( is(TU : long) ) return Json(cast(long)value);
883 	else static if( is(TU == string) ) return Json(value);
884 	else static if( isArray!T ){
885 		auto ret = new Json[value.length];
886 		foreach( i; 0 .. value.length )
887 			ret[i] = serializeToJson(value[i]);
888 		return Json(ret);
889 	} else static if( isAssociativeArray!TU ){
890 		Json[string] ret;
891 		foreach( string key, value; value )
892 			ret[key] = serializeToJson(value);
893 		return Json(ret);
894 	} else static if( __traits(compiles, value = T.fromJson(value.toJson())) ){
895 		return value.toJson();
896 	} else static if( __traits(compiles, value = T.fromString(value.toString())) ){
897 		return Json(value.toString());
898 	} else static if( is(TU == struct) ){
899 		Json[string] ret;
900 		foreach( m; __traits(allMembers, T) ){
901 			static if( isRWField!(TU, m) ){
902 				auto mv = __traits(getMember, value, m);
903 				ret[underscoreStrip(m)] = serializeToJson(mv);
904 			}
905 		}
906 		return Json(ret);
907 	} else static if( is(TU == class) ){
908 		if( value is null ) return Json(null);
909 		Json[string] ret;
910 		foreach( m; __traits(allMembers, T) ){
911 			static if( isRWField!(TU, m) ){
912 				auto mv = __traits(getMember, value, m);
913 				ret[underscoreStrip(m)] = serializeToJson(mv);
914 			}
915 		}
916 		return Json(ret);
917 	} else static if( isPointer!TU ){
918 		if( value is null ) return Json(null);
919 		return serializeToJson(*value);
920 	} else {
921 		static assert(false, "Unsupported type '"~T.stringof~"' for JSON serialization.");
922 	}
923 }
924 
925 
926 /**
927 	Deserializes a JSON value into the destination variable.
928 
929 	The same types as for serializeToJson() are supported and handled inversely.
930 */
931 void deserializeJson(T)(ref T dst, Json src)
932 {
933 	dst = deserializeJson!T(src);
934 }
935 /// ditto
936 T deserializeJson(T)(Json src)
937 {
938 	static if( is(T == Json) ) return src;
939 	else static if( is(T == typeof(null)) ){ return null; }
940 	else static if( is(T == bool) ) return src.get!bool;
941 	else static if( is(T == float) ) return src.to!float;   // since doubles are frequently serialized without
942 	else static if( is(T == double) ) return src.to!double; // a decimal point, we allow conversions here
943 	else static if( is(T == DateTime) ) return DateTime.fromISOExtString(src.get!string);
944 	else static if( is(T == SysTime) ) return SysTime.fromISOExtString(src.get!string);
945 	else static if( is(T : long) ) return cast(T)src.get!long;
946 	else static if( is(T == string) ) return src.get!string;
947 	else static if( isArray!T ){
948 		alias typeof(T.init[0]) TV;
949 		auto dst = new Unqual!TV[src.length];
950 		foreach( size_t i, v; src )
951 			dst[i] = deserializeJson!(Unqual!TV)(v);
952 		return dst;
953 	} else static if( isAssociativeArray!T ){
954 		alias typeof(T.init.values[0]) TV;
955 		Unqual!TV[string] dst;
956 		foreach( string key, value; src )
957 			dst[key] = deserializeJson!(Unqual!TV)(value);
958 		return dst;
959 	} else static if( __traits(compiles, { T dst; dst = T.fromJson(dst.toJson()); }()) ){
960 		return T.fromJson(src);
961 	} else static if( __traits(compiles, { T dst; dst = T.fromString(dst.toString()); }()) ){
962 		return T.fromString(src.get!string);
963 	} else static if( is(T == struct) ){
964 		T dst;
965 		foreach( m; __traits(allMembers, T) ){
966 			static if( isRWPlainField!(T, m) || isRWField!(T, m) ){
967 				alias typeof(__traits(getMember, dst, m)) TM;
968 				__traits(getMember, dst, m) = deserializeJson!TM(src[underscoreStrip(m)]);
969 			}
970 		}
971 		return dst;
972 	} else static if( is(T == class) ){
973 		if( src.type == Json.Type.null_ ) return null;
974 		auto dst = new T;
975 		foreach( m; __traits(allMembers, T) ){
976 			static if( isRWPlainField!(T, m) || isRWField!(T, m) ){
977 				alias typeof(__traits(getMember, dst, m)) TM;
978 				__traits(getMember, dst, m) = deserializeJson!TM(src[underscoreStrip(m)]);
979 			}
980 		}
981 		return dst;
982 	} else static if( isPointer!T ){
983 		if( src.type == Json.Type.null_ ) return null;
984 		alias typeof(*T.init) TD;
985 		dst = new TD;
986 		*dst = deserializeJson!TD(src);
987 		return dst;
988 	} else {
989 		static assert(false, "Unsupported type '"~T.stringof~"' for JSON serialization.");
990 	}
991 }
992 
993 unittest {
994 	import std.stdio;
995 	static struct S { float a; double b; bool c; int d; string e; byte f; ubyte g; long h; ulong i; float[] j; }
996 	immutable S t = {1.5, -3.0, true, int.min, "Test", -128, 255, long.min, ulong.max, [1.1, 1.2, 1.3]};
997 	S u;
998 	deserializeJson(u, serializeToJson(t));
999 	assert(t.a == u.a);
1000 	assert(t.b == u.b);
1001 	assert(t.c == u.c);
1002 	assert(t.d == u.d);
1003 	assert(t.e == u.e);
1004 	assert(t.f == u.f);
1005 	assert(t.g == u.g);
1006 	assert(t.h == u.h);
1007 	assert(t.i == u.i);
1008 	assert(t.j == u.j);
1009 }
1010 
1011 unittest {
1012 	static class C {
1013 		int a;
1014 		private int _b;
1015 		@property int b() const { return _b; }
1016 		@property void b(int v) { _b = v; }
1017 
1018 		@property int test() const { return 10; }
1019 
1020 		void test2() {}
1021 	}
1022 	C c = new C;
1023 	c.a = 1;
1024 	c.b = 2;
1025 
1026 	C d;
1027 	deserializeJson(d, serializeToJson(c));
1028 	assert(c.a == d.a);
1029 	assert(c.b == d.b);
1030 }
1031 
1032 
1033 /**
1034 	Writes the given JSON object as a JSON string into the destination range.
1035 
1036 	This function will convert the given JSON value to a string without adding
1037 	any white space between tokens (no newlines, no indentation and no padding).
1038 	The output size is thus minizized, at the cost of bad human readability.
1039 
1040 	Params:
1041 		dst   = References the string output range to which the result is written.
1042 		json  = Specifies the JSON value that is to be stringified.
1043 
1044 	See_Also: Json.toString, writePrettyJsonString
1045 */
1046 void writeJsonString(R)(ref R dst, in Json json)
1047 //	if( isOutputRange!R && is(ElementEncodingType!R == char) )
1048 {
1049 	final switch( json.type ){
1050 		case Json.Type.undefined: dst.put("undefined"); break;
1051 		case Json.Type.null_: dst.put("null"); break;
1052 		case Json.Type.bool_: dst.put(cast(bool)json ? "true" : "false"); break;
1053 		case Json.Type.int_: formattedWrite(dst, "%d", json.get!long); break;
1054 		case Json.Type.float_: formattedWrite(dst, "%.16g", json.get!double); break;
1055 		case Json.Type..string:
1056 			dst.put("\"");
1057 			jsonEscape(dst, cast(string)json);
1058 			dst.put("\"");
1059 			break;
1060 		case Json.Type.array:
1061 			dst.put("[");
1062 			bool first = true;
1063 			foreach( ref const Json e; json ){
1064 				if( e.type == Json.Type.undefined ) continue;
1065 				if( !first ) dst.put(",");
1066 				first = false;
1067 				writeJsonString(dst, e);
1068 			}
1069 			dst.put("]");
1070 			break;
1071 		case Json.Type.object:
1072 			dst.put("{");
1073 			bool first = true;
1074 			foreach( string k, ref const Json e; json ){
1075 				if( e.type == Json.Type.undefined ) continue;
1076 				if( !first ) dst.put(",");
1077 				first = false;
1078 				dst.put("\"");
1079 				jsonEscape(dst, k);
1080 				dst.put("\":");
1081 				writeJsonString(dst, e);
1082 			}
1083 			dst.put("}");
1084 			break;
1085 	}
1086 }
1087 
1088 /**
1089 	Writes the given JSON object as a prettified JSON string into the destination range.
1090 
1091 	The output will contain newlines and indents to make the output human readable.
1092 
1093 	Params:
1094 		dst   = References the string output range to which the result is written.
1095 		json  = Specifies the JSON value that is to be stringified.
1096 		level = Specifies the base amount of indentation for the output. Indentation  is always
1097 		        done using tab characters.
1098 
1099 	See_Also: Json.toPrettyString, writeJsonString
1100 */
1101 void writePrettyJsonString(R)(ref R dst, in Json json, int level = 0)
1102 //	if( isOutputRange!R && is(ElementEncodingType!R == char) )
1103 {
1104 	final switch( json.type ){
1105 		case Json.Type.undefined: dst.put("undefined"); break;
1106 		case Json.Type.null_: dst.put("null"); break;
1107 		case Json.Type.bool_: dst.put(cast(bool)json ? "true" : "false"); break;
1108 		case Json.Type.int_: formattedWrite(dst, "%d", json.get!long); break;
1109 		case Json.Type.float_: formattedWrite(dst, "%.16g", json.get!double); break;
1110 		case Json.Type..string:
1111 			dst.put("\"");
1112 			jsonEscape(dst, cast(string)json);
1113 			dst.put("\"");
1114 			break;
1115 		case Json.Type.array:
1116 			dst.put("[");
1117 			bool first = true;
1118 			foreach( e; json ){
1119 				if( e.type == Json.Type.undefined ) continue;
1120 				if( !first ) dst.put(",");
1121 				first = false;
1122 				dst.put("\n");
1123 				foreach( tab; 0 .. level+1 ) dst.put('\t');
1124 				writePrettyJsonString(dst, e, level+1);
1125 			}
1126 			if( json.length > 0 ) {
1127 				dst.put('\n');
1128 				foreach( tab; 0 .. level ) dst.put('\t');
1129 			}
1130 			dst.put("]");
1131 			break;
1132 		case Json.Type.object:
1133 			dst.put("{");
1134 			bool first = true;
1135 			foreach( string k, e; json ){
1136 				if( e.type == Json.Type.undefined ) continue;
1137 				if( !first ) dst.put(",");
1138 				dst.put("\n");
1139 				first = false;
1140 				foreach( tab; 0 .. level+1 ) dst.put('\t');
1141 				dst.put("\"");
1142 				jsonEscape(dst, k);
1143 				dst.put("\": ");
1144 				writePrettyJsonString(dst, e, level+1);
1145 			}
1146 			if( json.length > 0 ) {
1147 				dst.put('\n');
1148 				foreach( tab; 0 .. level ) dst.put('\t');
1149 			}
1150 			dst.put("}");
1151 			break;
1152 	}
1153 }
1154 
1155 /// private
1156 private void jsonEscape(R)(ref R dst, string s)
1157 {
1158 	foreach( ch; s ){
1159 		switch(ch){
1160 			default: dst.put(ch); break;
1161 			case '\\': dst.put("\\\\"); break;
1162 			case '\r': dst.put("\\r"); break;
1163 			case '\n': dst.put("\\n"); break;
1164 			case '\t': dst.put("\\t"); break;
1165 			case '\"': dst.put("\\\""); break;
1166 		}
1167 	}
1168 }
1169 
1170 /// private
1171 private string jsonUnescape(R)(ref R range)
1172 {
1173 	auto ret = appender!string();
1174 	while(!range.empty){
1175 		auto ch = range.front;
1176 		switch( ch ){
1177 			case '"': return ret.data;
1178 			case '\\':
1179 				range.popFront();
1180 				enforce(!range.empty, "Unterminated string escape sequence.");
1181 				switch(range.front){
1182 					default: enforce("Invalid string escape sequence."); break;
1183 					case '"': ret.put('\"'); range.popFront(); break;
1184 					case '\\': ret.put('\\'); range.popFront(); break;
1185 					case '/': ret.put('/'); range.popFront(); break;
1186 					case 'b': ret.put('\b'); range.popFront(); break;
1187 					case 'f': ret.put('\f'); range.popFront(); break;
1188 					case 'n': ret.put('\n'); range.popFront(); break;
1189 					case 'r': ret.put('\r'); range.popFront(); break;
1190 					case 't': ret.put('\t'); range.popFront(); break;
1191 					case 'u':
1192 						range.popFront();
1193 						dchar uch = 0;
1194 						foreach( i; 0 .. 4 ){
1195 							uch *= 16;
1196 							enforce(!range.empty, "Unicode sequence must be '\\uXXXX'.");
1197 							auto dc = range.front;
1198 							range.popFront();
1199 							if( dc >= '0' && dc <= '9' ) uch += dc - '0';
1200 							else if( dc >= 'a' && dc <= 'f' ) uch += dc - 'a' + 10;
1201 							else if( dc >= 'A' && dc <= 'F' ) uch += dc - 'A' + 10;
1202 							else enforce(false, "Unicode sequence must be '\\uXXXX'.");
1203 						}
1204 						ret.put(uch);
1205 						break;
1206 				}
1207 				break;
1208 			default:
1209 				ret.put(ch);
1210 				range.popFront();
1211 				break;
1212 		}
1213 	}
1214 	return ret.data;
1215 }
1216 
1217 private string skipNumber(ref string s, out bool is_float)
1218 {
1219 	size_t idx = 0;
1220 	is_float = false;
1221 	if( s[idx] == '-' ) idx++;
1222 	if( s[idx] == '0' ) idx++;
1223 	else {
1224 		enforce(isDigit(s[idx++]), "Digit expected at beginning of number.");
1225 		while( idx < s.length && isDigit(s[idx]) ) idx++;
1226 	}
1227 
1228 	if( idx < s.length && s[idx] == '.' ){
1229 		idx++;
1230 		is_float = true;
1231 		while( idx < s.length && isDigit(s[idx]) ) idx++;
1232 	}
1233 
1234 	if( idx < s.length && (s[idx] == 'e' || s[idx] == 'E') ){
1235 		idx++;
1236 		is_float = true;
1237 		if( idx < s.length && (s[idx] == '+' || s[idx] == '-') ) idx++;
1238 		enforce( idx < s.length && isDigit(s[idx]), "Expected exponent." ~ s[0 .. idx]);
1239 		idx++;
1240 		while( idx < s.length && isDigit(s[idx]) ) idx++;
1241 	}
1242 
1243 	string ret = s[0 .. idx];
1244 	s = s[idx .. $];
1245 	return ret;
1246 }
1247 
1248 private string skipJsonString(ref string s, int* line = null)
1249 {
1250 	enforce(s.length >= 2 && s[0] == '\"', "too small: '" ~ s ~ "'");
1251 	s = s[1 .. $];
1252 	string ret = jsonUnescape(s);
1253 	enforce(s.length > 0 && s[0] == '\"', "Unterminated string literal.");
1254 	s = s[1 .. $];
1255 	return ret;
1256 }
1257 
1258 private void skipWhitespace(ref string s, int* line = null)
1259 {
1260 	while( s.length > 0 ){
1261 		switch( s[0] ){
1262 			default: return;
1263 			case ' ', '\t': s = s[1 .. $]; break;
1264 			case '\n':
1265 				s = s[1 .. $];
1266 				if( s.length > 0 && s[0] == '\r' ) s = s[1 .. $];
1267 				if( line ) (*line)++;
1268 				break;
1269 			case '\r':
1270 				s = s[1 .. $];
1271 				if( s.length > 0 && s[0] == '\n' ) s = s[1 .. $];
1272 				if( line ) (*line)++;
1273 				break;
1274 		}
1275 	}
1276 }
1277 
1278 /// private
1279 private bool isDigit(T)(T ch){ return ch >= '0' && ch <= '9'; }
1280 
1281 private string underscoreStrip(string field_name)
1282 {
1283 	if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name;
1284 	else return field_name[0 .. $-1];
1285 }