1 module config.value;
2 
3 import std.traits;
4 
5 enum isPrimitiveValue(T) = is(T : typeof(null)) || is(T : bool) || is(T : string) || isIntegral!T || isFloatingPoint!T;
6 
7 enum isValue(T) = is(T : const(Value)) || isPrimitiveValue!T || isArrayValue!T || isObjectValue!T || isMapValue!T;
8 
9 enum isArrayValue(X) = false;
10 enum isArrayValue(A : E[], E) = isValue!E;
11 
12 enum isObjectValue(X) = false;
13 enum isObjectValue(A : E[string], E) = isValue!E;
14 
15 enum isMapValue(X) = false;
16 enum isMapValue(A : V[K], K, V) = !isObjectValue!A && isValue!K && isValue!V;
17 
18 unittest {
19 	import std.meta;
20 	alias PrimitiveTypes = AliasSeq!(typeof(null), bool, byte, ubyte, short, ushort, long, ulong, string);
21 	
22 	foreach (T; PrimitiveTypes) {
23 		assert(isValue!T);
24 		assert(isPrimitiveValue!T);
25 		assert(!isArrayValue!T);
26 		assert(!isObjectValue!T);
27 		assert(!isMapValue!T);
28 		
29 		alias A = T[];
30 		assert(isValue!A);
31 		assert(!isPrimitiveValue!A);
32 		assert(isArrayValue!A);
33 		assert(!isObjectValue!A);
34 		assert(!isMapValue!A);
35 		
36 		alias O = T[string];
37 		assert(isValue!O);
38 		assert(!isPrimitiveValue!O);
39 		assert(!isArrayValue!O);
40 		assert(isObjectValue!O);
41 		assert(!isMapValue!O);
42 		
43 		alias M = T[O];
44 		assert(isValue!M);
45 		assert(!isPrimitiveValue!M);
46 		assert(!isArrayValue!M);
47 		assert(!isObjectValue!M);
48 		assert(isMapValue!M);
49 		
50 		alias MM = M[M];
51 		assert(isValue!MM);
52 		assert(!isPrimitiveValue!MM);
53 		assert(!isArrayValue!MM);
54 		assert(!isObjectValue!MM);
55 		assert(isMapValue!MM);
56 		
57 		alias AA = A[];
58 		assert(isValue!AA);
59 		assert(!isPrimitiveValue!AA);
60 		assert(isArrayValue!AA);
61 		assert(!isObjectValue!AA);
62 		assert(!isMapValue!AA);
63 		
64 		alias AO = A[string];
65 		assert(isValue!AO);
66 		assert(!isPrimitiveValue!AO);
67 		assert(!isArrayValue!AO);
68 		assert(isObjectValue!AO);
69 		assert(!isMapValue!AO);
70 		
71 		alias OA = O[];
72 		assert(isValue!OA);
73 		assert(!isPrimitiveValue!OA);
74 		assert(isArrayValue!OA);
75 		assert(!isObjectValue!OA);
76 		assert(!isMapValue!OA);
77 		
78 		alias OO = O[string];
79 		assert(isValue!OO);
80 		assert(!isPrimitiveValue!OO);
81 		assert(!isArrayValue!OO);
82 		assert(isObjectValue!OO);
83 		assert(!isMapValue!OO);
84 	}
85 }
86 
87 struct Value {
88 	enum Kind : byte {
89 	    Null,
90 	    Boolean,
91 	    Integer,
92 	    Floating,
93 	    String,
94 	    Array,
95 	    Object,
96 	    Map,
97 	}
98 
99 private:
100 	Kind _kind;
101 	
102     union {
103         bool _boolean;
104         long _integer;
105         double _floating;
106         string _str;
107         Value[] _array;
108         Value[string] _obj;
109         Value[Value] _map;
110     }
111     
112 public:
113 	this(T)(T t) {
114 		this = t;
115 	}
116 	
117 	@property
118 	Kind kind() const nothrow {
119 		return _kind;
120 	}
121 	
122 	@property
123 	bool boolean() const nothrow in {
124 		assert(kind == Kind.Boolean);
125 	} do {
126 		return _boolean;
127 	}
128 	
129 	@property
130 	long integer() const nothrow in {
131 		assert(kind == Kind.Integer);
132 	} do {
133 		return _integer;
134 	}
135 	
136 	@property
137 	double floating() const nothrow in {
138 		assert(kind == Kind.Floating);
139 	} do {
140 		return _floating;
141 	}
142 	
143 	@property
144 	string str() const nothrow in {
145 		assert(kind == Kind.String);
146 	} do {
147 		return _str;
148 	}
149 	
150 	@property
151 	inout(Value)[] array() inout nothrow in {
152 		assert(kind == Kind.Array);
153 	} do {
154 		return _array;
155 	}
156 	
157 	@property
158 	inout(Value[string]) obj() inout nothrow in {
159 		assert(kind == Kind.Object);
160 	} do {
161 		return _obj;
162 	}
163 	
164 	@property
165 	inout(Value[Value]) map() inout nothrow in {
166 		assert(kind == Kind.Map);
167 	} do {
168 		return _map;
169 	}
170 	
171 	@property
172 	size_t length() const nothrow in {
173 		assert(kind == Kind.String || kind == Kind.Array
174 			|| kind == Kind.Object || kind == Kind.Map);
175 	} do {
176 		switch (kind) with (Kind) {
177 			case String:
178 				return str.length;
179 			case Array:
180 				return array.length;
181 			case Object:
182 				return obj.length;
183 			case Map:
184 				return map.length;
185 			default:
186 				assert(0);
187 		}
188 	}
189 	
190 	/**
191 	 * Map and Object features
192 	 */
193 	inout(Value)* opBinaryRight(string op : "in")(string key) inout in {
194 		assert(kind == Kind.Object || kind == Kind.Map);
195 	} do {
196 		return kind == Kind.Map
197 			? Value(key) in map
198 			: key in obj;
199 	}
200 	
201 	/**
202 	 * Misc
203 	 */
204 	string toString() const {
205 		return this.visit!(function string(v) {
206 			alias T = typeof(v);
207 			static if (is(T : typeof(null)))  {
208 				return "null";
209 			} else static if (is(T == bool)) {
210 				return v ? "true" : "false";
211 			} else static if (is(T == string)) {
212 				// This is retarded, but I can't find another way to do it.
213 				import std.conv;
214 				return to!string([v])[1 .. $ - 1];
215 			} else {
216 				import std.conv;
217 				return to!string(v);
218 			}
219 		})();
220 	}
221 	
222 	@trusted
223 	size_t toHash() const nothrow {
224 		return this.visit!(x => is(typeof(x) : typeof(null)) ? -1 : hashOf(x))();
225 	}
226 	
227 	/**
228 	 * Assignement
229 	 */
230 	Value opAssign()(typeof(null) nothing) {
231 		_kind = Kind.Null;
232 		_str = null;
233 		return this;
234 	}
235 	
236 	Value opAssign(B : bool)(B b) {
237 		_kind = Kind.Boolean;
238 		_boolean = b;
239 		return this;
240 	}
241 	
242 	Value opAssign(I : long)(I i) {
243 		_kind = Kind.Integer;
244 		_integer = i;
245 		return this;
246 	}
247 	
248 	Value opAssign(F : double)(F f) {
249 		_kind = Kind.Floating;
250 		_floating = f;
251 		return this;
252 	}
253 	
254 	Value opAssign(S : string)(S s) {
255 		_kind = Kind.String;
256 		_str = s;
257 		return this;
258 	}
259 	
260 	Value opAssign(A)(A a) if (isArrayValue!A) {
261 		_kind = Kind.Array;
262 		_array = [];
263 		_array.reserve(a.length);
264 		
265 		foreach (ref e; a) {
266 			_array ~= Value(e);
267 		}
268 		
269 		return this;
270 	}
271 	
272 	Value opAssign(O)(O o) if (isObjectValue!O) {
273 		_kind = Kind.Object;
274 		_obj = null;
275 		
276 		foreach (k, ref e; o) {
277 			_obj[k] = Value(e);
278 		}
279 		
280 		return this;
281 	}
282 	
283 	Value opAssign(M)(M m) if (isMapValue!M) {
284 		_kind = Kind.Map;
285 		_map = null;
286 		
287 		foreach (ref k, ref e; m) {
288 			_map[Value(k)] = Value(e);
289 		}
290 		
291 		return this;
292 	}
293 	
294 	/**
295 	 * Equality
296 	 */
297 	bool opEquals(const ref Value rhs) const {
298 		return this.visit!((x, const ref Value rhs) => rhs == x)(rhs);
299 	}
300 	
301 	bool opEquals(T : typeof(null))(T t) const {
302 		return kind == Kind.Null;
303 	}
304 	
305 	bool opEquals(B : bool)(B b) const {
306 		return kind == Kind.Boolean && boolean == b;
307 	}
308 	
309 	bool opEquals(I : long)(I i) const {
310 		return kind == Kind.Integer && integer == i;
311 	}
312 	
313 	bool opEquals(F : double)(F f) const {
314 		return kind == Kind.Floating && floating == f;
315 	}
316 	
317 	bool opEquals(S : string)(S s) const {
318 		return kind == Kind.String && str == s;
319 	}
320 	
321 	bool opEquals(A)(A a) const if (isArrayValue!A) {
322 		// Wrong type.
323 		if (kind != Kind.Array) {
324 			return false;
325 		}
326 		
327 		// Wrong length.
328 		if (array.length != a.length) {
329 			return false;
330 		}
331 		
332 		foreach (i, ref _; a) {
333 			if (array[i] != a[i]) {
334 				return false;
335 			}
336 		}
337 		
338 		return true;
339 	}
340 	
341 	bool opEquals(O)(O o) const if (isObjectValue!O) {
342 		// Wrong type.
343 		if (kind != Kind.Object) {
344 			return false;
345 		}
346 		
347 		// Wrong length.
348 		if (obj.length != o.length) {
349 			return false;
350 		}
351 		
352 		// Compare all the values.
353 		foreach (k, ref v; o) {
354 			auto vPtr = k in obj;
355 			if (vPtr is null || *vPtr != v) {
356 				return false;
357 			}
358 		}
359 		
360 		return true;
361 	}
362 	
363 	bool opEquals(M)(M m) const if (isMapValue!M) {
364 		// Wrong type.
365 		if (kind != Kind.Map) {
366 			return false;
367 		}
368 		
369 		// Wrong length.
370 		if (map.length != m.length) {
371 			return false;
372 		}
373 		
374 		// Compare all the values.
375 		foreach (ref k, ref v; m) {
376 			auto vPtr = Value(k) in map;
377 			if (vPtr is null || *vPtr != v) {
378 				return false;
379 			}
380 		}
381 		
382 		return true;
383 	}
384 }
385 
386 auto visit(alias fun, Args...)(const ref Value v, auto ref Args args) {
387 	final switch (v.kind) with (Value.Kind) {
388 		case Null:
389 			return fun(null, args);
390 		
391 		case Boolean:
392 			return fun(v.boolean, args);
393 		
394 		case Integer:
395 			return fun(v.integer, args);
396 		
397 		case Floating:
398 			return fun(v.floating, args);
399 		
400 		case String:
401 			return fun(v.str, args);
402 		
403 		case Array:
404 			return fun(v.array, args);
405 		
406 		case Object:
407 			return fun(v.obj, args);
408 		
409 		case Map:
410 			return fun(v.map, args);
411 	}
412 }
413 
414 // Assignement and comparison.
415 unittest {
416 	import std.meta;
417 	alias Cases = AliasSeq!(
418 		null, true, false, 0, 1, 42,
419 		0., 3.141592, float.infinity, -float.infinity,
420 		"", "foobar",
421 		[1, 2, 3],
422 		[1, 2, 3, 4],
423 		["y": true, "n": false],
424 		["x": 3, "y": 5],
425 		["foo": "bar"],
426 		["fizz": "buzz"],
427 		["first": [1, 2], "second": [3, 4]],
428 		[["a", "b"]: [1, 2], ["c", "d"]: [3, 4]],
429 	);
430 	
431 	static testAllValues(E)(Value v, E expected, Value.Kind k) {
432 		assert(v.kind == k);
433 		
434 		bool found = false;
435 		foreach (I; Cases) {
436 			static if (!is(E == typeof(I))) {
437 				assert(v != I);
438 			} else if (expected == I) {
439 				found = true;
440 				assert(v == I);
441 			} else {
442 				assert(v != I);
443 			}
444 		}
445 		
446 		assert(found);
447 	}
448 	
449 	Value initVar;
450 	testAllValues(initVar, null, Value.Kind.Null);
451 	
452 	static testValue(E)(E expected, Value.Kind k) {
453 		Value v = expected;
454 		testAllValues(v, expected, k);
455 	}
456 	
457 	testValue(null, Value.Kind.Null);
458 	testValue(true, Value.Kind.Boolean);
459 	testValue(false, Value.Kind.Boolean);
460 	testValue(0, Value.Kind.Integer);
461 	testValue(1, Value.Kind.Integer);
462 	testValue(42, Value.Kind.Integer);
463 	testValue(0., Value.Kind.Floating);
464 	testValue(3.141592, Value.Kind.Floating);
465 	testValue(float.infinity, Value.Kind.Floating);
466 	testValue(-float.infinity, Value.Kind.Floating);
467 	testValue("", Value.Kind.String);
468 	testValue("foobar", Value.Kind.String);
469 	testValue([1, 2, 3], Value.Kind.Array);
470 	testValue([1, 2, 3, 4], Value.Kind.Array);
471 	testValue(["y": true, "n": false], Value.Kind.Object);
472 	testValue(["x": 3, "y": 5], Value.Kind.Object);
473 	testValue(["foo": "bar"], Value.Kind.Object);
474 	testValue(["fizz": "buzz"], Value.Kind.Object);
475 	testValue(["first": [1, 2], "second": [3, 4]], Value.Kind.Object);
476 	testValue([["a", "b"]: [1, 2], ["c", "d"]: [3, 4]], Value.Kind.Map);
477 }
478 
479 // length
480 unittest {
481 	assert(Value("").length == 0);
482 	assert(Value("abc").length == 3);
483 	assert(Value([1, 2, 3]).length == 3);
484 	assert(Value([1, 2, 3, 4, 5]).length == 5);
485 	assert(Value(["foo", "bar"]).length == 2);
486 	assert(Value([3.2, 37.5]).length == 2);
487 	assert(Value([3.2: "a", 37.5: "b", 1.1: "c"]).length == 3);
488 }
489 
490 // toString
491 unittest {
492 	assert(Value().toString() == "null");
493 	assert(Value(true).toString() == "true");
494 	assert(Value(false).toString() == "false");
495 	assert(Value(0).toString() == "0");
496 	assert(Value(1).toString() == "1");
497 	assert(Value(42).toString() == "42");
498 	// FIXME: I have not found how to write down float in a compact form that is
499 	// not ambiguous with an integer in some cases. Here, D writes '1' by default.
500 	// std.format is not of great help on that one.
501 	// assert(Value(1.0).toString() == "1.0");
502 	assert(Value(4.2).toString() == "4.2");
503 	assert(Value(0.5).toString() == "0.5");
504 	
505 	assert(Value("").toString() == `""`);
506 	assert(Value("abc").toString() == `"abc"`);
507 	assert(Value("\n\t\n").toString() == `"\n\t\n"`);
508 	
509 	assert(Value([1, 2, 3]).toString() == "[1, 2, 3]");
510 	assert(Value(["y": true, "n": false]).toString() == `["y":true, "n":false]`);
511 	assert(Value([["a", "b"]: [1, 2], ["c", "d"]: [3, 4]]).toString() == `[["a", "b"]:[1, 2], ["c", "d"]:[3, 4]]`);
512 }