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 }