1 /** 2 DataNode abstraction 3 4 Copyright: 5 Copyright © 2020-2026, Inochi2D Project 6 7 License: 8 $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License) 9 10 Authors: 11 Luna Nielsen 12 */ 13 module inp.format.node; 14 import inp.format.dict; 15 import inp.format.array; 16 import nulib.collections; 17 import numem.core.traits; 18 import numem.core.meta; 19 import numem.rc; 20 import numem; 21 22 enum DataNodeType : uint { 23 undefined = 0, 24 boolean_ = 1, 25 int_ = 2, 26 uint_ = 3, 27 float_ = 4, 28 string_ = 5, 29 array_ = 6, 30 object_ = 7, 31 blob_ = 8, 32 } 33 34 /** 35 The compile-time $(D DataNodeType) corrosponding to type $(D T). 36 */ 37 template dataNodeTypeOf(T) { 38 static if (is(T == bool)) { 39 enum dataNodeTypeOf = DataNodeType.boolean_; 40 } else static if (__traits(isIntegral, T)) { 41 enum dataNodeTypeOf = __traits(isUnsigned, T) ? DataNodeType.uint_ : DataNodeType.int_; 42 } else static if (__traits(isFloating, T)) { 43 enum dataNodeTypeOf = DataNodeType.float_; 44 } else static if (is(T == ubyte[])) { 45 enum dataNodeTypeOf = DataNodeType.blob_; 46 } else static if (is(T : string)) { 47 enum dataNodeTypeOf = DataNodeType.string_; 48 } else static if (is(T == U[], U)) { 49 enum dataNodeTypeOf = DataNodeType.array_; 50 } else static if (is(T == class) || is(T == struct)) { 51 enum dataNodeTypeOf = DataNodeType.object_; 52 } else { 53 enum dataNodeTypeOf = DataNodeType.undefined; 54 } 55 } 56 57 /** 58 A node containing data for (de)serialization. 59 */ 60 struct DataNode { 61 private: 62 @nogc: 63 import nulib.collections.internal.marray : ManagedArray; 64 __gshared DataNode UNDEF = DataNode.init; 65 66 static 67 union DataNodeStore { 68 @nogc: 69 70 void* undefined; 71 bool boolean_; 72 long int_; 73 ulong uint_; 74 float float_; 75 string string_; 76 RcArray!DataNode array_; 77 RcOrderedDictionary!(string, DataNode) object_; 78 ubyte[] blob_; 79 } 80 81 DataNodeType dataType = DataNodeType.undefined; 82 DataNodeStore dataStore; 83 84 template isSameType(T) { 85 enum isSameType(U) = is(T == U); 86 } 87 public: 88 89 /** 90 The type of data stored within the node. 91 */ 92 @property DataNodeType type() nothrow pure => dataType; 93 94 /** 95 Whether the DataNode contains a nil value. 96 */ 97 @property bool isNull() nothrow pure => dataType == DataNodeType.undefined; 98 99 /** 100 Whether the DataNode contains a numeric value. 101 */ 102 @property bool isNumber() nothrow pure => dataType >= DataNodeType.int_ && dataType <= DataNodeType.float_; 103 104 /** 105 Whether the DataNode is an object. 106 */ 107 @property bool isObject() nothrow pure => dataType == DataNodeType.object_; 108 109 /** 110 Whether the DataNode is an array. 111 */ 112 @property bool isArray() nothrow pure => dataType == DataNodeType.array_; 113 114 /** 115 Whether the DataNode is a byte blob. 116 */ 117 @property bool isBlob() nothrow pure => dataType == DataNodeType.blob_; 118 119 /** 120 The text content of the node, or null. 121 */ 122 @property string text() nothrow pure => isType(DataNodeType.string_) ? dataStore.string_[] : null; 123 124 /** 125 A blob of binary data, or null. 126 */ 127 @property ubyte[] blob() nothrow pure => isType(DataNodeType.blob_) ? dataStore.blob_[] : null; 128 129 /** 130 The boolean content of the datanode, or false. 131 */ 132 @property bool boolean() nothrow pure => tryCoerce!bool(false); 133 134 /** 135 The number content of the data node, or NaN 136 */ 137 @property float number() nothrow pure => tryCoerce!float(float.nan); 138 139 /** 140 The key-value pairs in the DataNode object, or null. 141 */ 142 @property ref RcOrderedDictionary!(string, DataNode) object() nothrow pure => dataStore.object_; 143 144 /** 145 The array in the DataNode object, or null. 146 */ 147 @property ref RcArray!DataNode array() nothrow pure => dataStore.array_; 148 149 /// Destructor 150 ~this() @trusted nothrow { 151 switch(dataType) { 152 default: 153 this.dataType = DataNodeType.undefined; 154 break; 155 156 case DataNodeType.string_: 157 this.dataType = DataNodeType.undefined; 158 nu_freea(this.dataStore.string_); 159 break; 160 161 case DataNodeType.blob_: 162 this.dataType = DataNodeType.undefined; 163 nu_freea(this.dataStore.blob_); 164 break; 165 166 case DataNodeType.array_: 167 this.dataType = DataNodeType.undefined; 168 nogc_trydelete(this.dataStore.array_); 169 break; 170 171 case DataNodeType.object_: 172 this.dataType = DataNodeType.undefined; 173 nogc_trydelete(this.dataStore.object_); 174 break; 175 } 176 177 } 178 179 /** 180 Creates a new object node. 181 */ 182 static DataNode createObject() @trusted nothrow { 183 DataNode v; 184 v.dataType = DataNodeType.object_; 185 v.dataStore.object_ = RcOrderedDictionary!(string, DataNode).make(); 186 return v; 187 } 188 189 /** 190 Creates a new array node. 191 */ 192 static DataNode createArray() @trusted nothrow { 193 DataNode v; 194 v.dataType = DataNodeType.array_; 195 v.dataStore.array_ = RcArray!(DataNode).make(); 196 return v; 197 } 198 199 /** 200 Constructs a boolean data node. 201 */ 202 this(T)(auto ref T value) @safe nothrow 203 if (is(T == bool)) { 204 this.dataType = DataNodeType.boolean_; 205 this.dataStore = DataNodeStore(boolean_: cast(bool)value); 206 } 207 208 /** 209 Constructs a signed integer data node. 210 */ 211 this(T)(auto ref T value) @safe nothrow 212 if (anySatisfy!(isSameType!T, AliasSeq!(byte, short, int, long))) { 213 this.dataType = DataNodeType.int_; 214 this.dataStore = DataNodeStore(int_: cast(long)value); 215 } 216 217 /** 218 Constructs an unsigned integer data node. 219 */ 220 this(T)(auto ref T value) @safe nothrow 221 if (anySatisfy!(isSameType!T, AliasSeq!(ubyte, ushort, uint, ulong))) { 222 this.dataType = DataNodeType.uint_; 223 this.dataStore = DataNodeStore(uint_: cast(ulong)value); 224 } 225 226 /** 227 Constructs a floating point data node. 228 */ 229 this(T)(auto ref T value) @safe nothrow 230 if (anySatisfy!(isSameType!T, AliasSeq!(float, double))) { 231 this.dataType = DataNodeType.float_; 232 this.dataStore = DataNodeStore(float_: cast(double)value); 233 } 234 235 /** 236 Constructs a string data node. 237 */ 238 this()(auto ref string value) @safe nothrow { 239 this.dataType = DataNodeType.string_; 240 this.dataStore = DataNodeStore(string_: value.nu_dup()); 241 } 242 243 /** 244 Constructs a string data node. 245 */ 246 this()(auto ref ubyte[] value) @safe nothrow { 247 this.dataType = DataNodeType.blob_; 248 this.dataStore = DataNodeStore(blob_: value.nu_dup()); 249 } 250 251 /** 252 Copy constructor 253 */ 254 this()(ref return scope typeof(this) rhs) @trusted { 255 this.dataType = rhs.dataType; 256 switch(rhs.dataType) { 257 case DataNodeType.string_: 258 this.dataStore.string_ = cast(typeof(dataStore.string_))rhs.dataStore.string_.nu_dup(); 259 break; 260 261 case DataNodeType.blob_: 262 this.dataStore.blob_ = cast(typeof(dataStore.blob_))rhs.dataStore.blob_.nu_dup(); 263 break; 264 265 case DataNodeType.array_: 266 this.dataStore.array_ = cast(typeof(dataStore.array_))rhs.dataStore.array_; 267 break; 268 269 case DataNodeType.object_: 270 this.dataStore.object_ = cast(typeof(dataStore.object_))rhs.dataStore.object_; 271 break; 272 273 default: 274 this.dataStore = cast(typeof(dataStore))rhs.dataStore; 275 break; 276 } 277 } 278 279 /** 280 Coerces the value of the DataNode to the given type, if possible. 281 */ 282 T tryCoerce(T)(T defaultValue = T.init) nothrow pure { 283 switch(dataType) { 284 case DataNodeType.string_: 285 static if (is(T == string)) 286 return dataStore.string_; 287 else 288 return defaultValue; 289 290 case DataNodeType.boolean_: 291 static if (__traits(isScalar, T)) 292 return cast(T)dataStore.boolean_; 293 else 294 return defaultValue; 295 296 case DataNodeType.uint_: 297 static if (isNumeric!T) 298 return cast(T)dataStore.uint_; 299 else 300 return defaultValue; 301 302 case DataNodeType.int_: 303 static if (isNumeric!T) 304 return cast(T)dataStore.int_; 305 else 306 return defaultValue; 307 308 case DataNodeType.float_: 309 static if (isNumeric!T) 310 return cast(T)dataStore.float_; 311 else 312 return defaultValue; 313 314 default: 315 return defaultValue; 316 } 317 } 318 319 /** 320 Gets whether this DataNode contains data of the given type. 321 322 Params: 323 type = The type to check for. 324 325 Returns: 326 $(D true) if the type of the data in the node matches, 327 $(D false) otherwise. 328 */ 329 bool isType(inout(DataNodeType) type) inout nothrow pure => this.dataType == type; 330 331 /** 332 Length of the node. 333 */ 334 @property size_t length() { 335 switch(dataType) { 336 case DataNodeType.array_: 337 return dataStore.array_.length; 338 case DataNodeType.object_: 339 return dataStore.object_.length; 340 case DataNodeType.string_: 341 return dataStore.string_.length; 342 default: 343 return 0; 344 } 345 } 346 347 /** 348 Removes the given key from the object. 349 350 Params: 351 key = The key to remove. 352 */ 353 void remove(string key) { 354 if (this.isType(DataNodeType.object_)) { 355 dataStore.object_.remove(key); 356 } 357 } 358 359 /** 360 Removes the given index from the array. 361 362 Params: 363 idx = The index to remove. 364 */ 365 void remove(size_t idx) { 366 if (this.isType(DataNodeType.array_)) { 367 dataStore.array_.remove(idx); 368 } 369 } 370 371 /** 372 Adds the given entry into the array. 373 374 Params: 375 rhs = Value to append 376 */ 377 void opOpAssign(string op)(DataNode rhs) 378 if (op == "~") { 379 if (this.isType(DataNodeType.array_)) { 380 dataStore.array_.opOpAssign!op(rhs); 381 } 382 } 383 384 /** 385 Assigns an element of the object node. 386 387 Params: 388 key = The key to query. 389 value = The value to set. 390 */ 391 void opIndexAssign(T)(auto ref T value, string key) { 392 if (this.isType(DataNodeType.object_)) { 393 static if (is(T == DataNode)) { 394 dataStore.object_.opIndexAssign(value, key); 395 } else { 396 dataStore.object_.opIndexAssign(DataNode(value), key); 397 } 398 } 399 } 400 401 /** 402 Assigns an element of the array node. 403 404 Params: 405 rhs = The value to set. 406 idx = The idx to set. 407 */ 408 void opIndexAssign(T)(auto ref T rhs, size_t idx) { 409 if (this.isType(DataNodeType.array_)) { 410 dataStore.array_.opIndexAssign!T(rhs, idx); 411 } 412 } 413 414 /** 415 Gets whether the given key is present in the object. 416 417 Params: 418 key = The key to query. 419 420 Returns: 421 $(D true) if the object contains a value with the given key, 422 $(D false) otherwise. 423 */ 424 inout(DataNode)* opBinaryRight(string op)(string key) inout nothrow 425 if (op == "in") { 426 return this.isType(DataNodeType.object_) ? key in dataStore.object_ : null; 427 } 428 429 /** 430 Gets the given entry in the object. 431 432 Params: 433 key = The key to query. 434 435 Returns: 436 The $(D DataNode) with the given key. 437 */ 438 ref DataNode opIndex(string key) { 439 return dataStore.object_.opIndex(key); 440 } 441 442 /** 443 Gets the given entry in the array. 444 445 Params: 446 idx = The index to query. 447 448 Returns: 449 The $(D DataNode) with the given index. 450 */ 451 ref DataNode opIndex(size_t idx) { 452 return dataStore.array_.opIndex(idx); 453 } 454 455 bool opEquals(DataNode other) { 456 if (dataType == DataNodeType.int_ && other.dataType == DataNodeType.uint_) { 457 return dataStore.int_ >= 0 ? cast(uint)dataStore.int_ == other.dataStore.uint_ : false; 458 } else if (dataType == DataNodeType.uint_ && other.dataType == DataNodeType.int_) { 459 return other.dataStore.int_ >= 0 ? dataStore.uint_ == cast(uint)other.dataStore.int_ : false; 460 } 461 462 if (dataType != other.dataType) { 463 return false; 464 } 465 466 switch (dataType) { 467 case DataNodeType.undefined: 468 return dataStore.undefined == other.dataStore.undefined; 469 case DataNodeType.boolean_: 470 return dataStore.boolean_ == other.dataStore.boolean_; 471 case DataNodeType.int_: 472 return dataStore.int_ == other.dataStore.int_; 473 case DataNodeType.uint_: 474 return dataStore.uint_ == other.dataStore.uint_; 475 case DataNodeType.float_: 476 return dataStore.float_ == other.dataStore.float_; 477 case DataNodeType.string_: 478 return dataStore.string_ == other.dataStore.string_; 479 // case DataNodeType.array_: 480 // return dataStore.array_ == other.dataStore.array_; 481 // case DataNodeType.object_: 482 // return dataStore.object_ == other.dataStore.object_; 483 case DataNodeType.blob_: 484 return dataStore.blob_ == other.dataStore.blob_; 485 default: 486 return false; 487 } 488 } 489 490 bool opEquals(T)(T other) { 491 static if (__traits(isIntegral, T)) { 492 if (dataType == DataNodeType.int_ && dataNodeTypeOf!T == DataNodeType.uint_) { 493 return dataStore.int_ >= 0 ? cast(uint)dataStore.int_ == other : false; 494 } else if (dataType == DataNodeType.uint_ && dataNodeTypeOf!T == DataNodeType.int_) { 495 return other >= 0 ? dataStore.uint_ == cast(uint)other : false; 496 } 497 } 498 499 if (dataType != dataNodeTypeOf!T) { 500 return false; 501 } 502 503 // TODO: handle array & dictionary 504 static if (dataNodeTypeOf!T == DataNodeType.undefined) { 505 return dataStore.undefined == other; 506 } else static if (dataNodeTypeOf!T == DataNodeType.boolean_) { 507 return dataStore.boolean_ == other; 508 } else static if (dataNodeTypeOf!T == DataNodeType.int_) { 509 return dataStore.int_ == other; 510 } else static if (dataNodeTypeOf!T == DataNodeType.uint_) { 511 return dataStore.uint_ == other; 512 } else static if (dataNodeTypeOf!T == DataNodeType.float_) { 513 return dataStore.float_ == other; 514 } else static if (dataNodeTypeOf!T == DataNodeType.string_) { 515 return dataStore.string_ == other; 516 } else static if (dataNodeTypeOf!T == DataNodeType.blob_) { 517 return dataStore.blob_ == other; 518 } else { 519 return false; 520 } 521 } 522 523 /** 524 Converts the DataNode to a string. 525 */ 526 string toString() const @trusted nothrow { 527 import nulib.string : nstring; 528 import nulib.conv : to; 529 final switch(dataType) { 530 531 case DataNodeType.string_: 532 return dataStore.string_; 533 534 case DataNodeType.boolean_: 535 return dataStore.boolean_ ? "true" : "false"; 536 537 case DataNodeType.int_: 538 return to!nstring(dataStore.int_).take(); 539 540 case DataNodeType.uint_: 541 return to!nstring(dataStore.uint_).take(); 542 543 case DataNodeType.float_: 544 return to!nstring(dataStore.float_).take(); 545 546 case DataNodeType.array_: 547 return "<array>"; 548 549 case DataNodeType.object_: 550 return "<object>"; 551 552 case DataNodeType.blob_: 553 return "<blob>"; 554 555 case DataNodeType.undefined: 556 return "<undefined>"; 557 } 558 } 559 } 560 561 /** 562 Gets the type name for a given DataNodeType. 563 564 Params: 565 type = The data node type. 566 567 Returns: 568 The name of the type. 569 */ 570 string toTypeName(DataNodeType type) @nogc nothrow pure { 571 final switch(type) { 572 573 case DataNodeType.string_: 574 return "string"; 575 576 case DataNodeType.boolean_: 577 return "boolean"; 578 579 case DataNodeType.int_: 580 return "int32"; 581 582 case DataNodeType.uint_: 583 return "uint32"; 584 585 case DataNodeType.float_: 586 return "float32"; 587 588 case DataNodeType.array_: 589 return "array"; 590 591 case DataNodeType.object_: 592 return "object"; 593 594 case DataNodeType.blob_: 595 return "blob"; 596 597 case DataNodeType.undefined: 598 return "undefined"; 599 } 600 } 601 602 @("Create node.") 603 unittest { 604 DataNode n = DataNode.createObject(); 605 n["a"] = 42; 606 607 assert(n["a"].number == 42); 608 n.remove("a"); 609 assert("a" !in n); 610 }