1 
2 //          Copyright Ferdinand Majerech 2011.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE_1_0.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 
7 /// Node of a YAML document. Used to read YAML data once it's loaded,
8 /// and to prepare data to emit.
9 module dyaml.node;
10 
11 import dyaml.event;
12 import dyaml.exception;
13 import dyaml.style;
14 import mir.conv: to;
15 import mir.format: text;
16 import mir.primitives;
17 import mir.timestamp;
18 import std.exception: assertThrown, assumeWontThrow, collectException;
19 import std.meta : AliasSeq;
20 import std.traits;
21 import std.typecons: Flag, Yes, No;
22 
23 /// Exception thrown at node related errors.
24 class NodeException : YAMLException
25 {
26     package:
27         // Construct a NodeException.
28         //
29         // Params:  msg   = Error message.
30         //          start = Start position of the node.
31         this(string msg, Mark start, string file = __FILE__, size_t line = __LINE__)
32             @safe
33         {
34             super(msg ~ "\nNode at: " ~ start.toString(), file, line);
35         }
36 }
37 
38 // Node kinds.
39 enum NodeID : ubyte
40 {
41     scalar,
42     sequence,
43     mapping,
44     invalid
45 }
46 
47 /// Null YAML type. Used in nodes with _null values.
48 alias YAMLNull = typeof(null);
49 
50 // Merge YAML type, used to support "tag:yaml.org,2002:merge".
51 package struct YAMLMerge{}
52 
53 // Key-value pair of YAML nodes, used in mappings.
54 private struct Pair
55 {
56     public:
57         /// Key node.
58         Node key;
59         /// Value node.
60         Node value;
61 
62         /// Construct a Pair from two values. Will be converted to Nodes if needed.
63         this(K, V)(K key, V value)
64         {
65             static if(is(Unqual!K == Node)){this.key = key;}
66             else                           {this.key = Node(key);}
67             static if(is(Unqual!V == Node)){this.value = value;}
68             else                           {this.value = Node(value);}
69         }
70 
71         /// Equality test with another Pair.
72         bool opEquals(const ref Pair rhs) const @safe
73         {
74             return key == rhs.key && value == rhs.value;
75         }
76 
77         // Comparison with another Pair.
78         int opCmp(ref const(Pair) rhs) const @safe
79         {
80             const keyCmp = key.opCmp(rhs.key);
81             return keyCmp != 0 ? keyCmp
82                                 : value.opCmp(rhs.value);
83         }
84 }
85 
86 alias NodeType = Node.Value.Kind;
87 
88 /** YAML node.
89  *
90  * This is a pseudo-dynamic type that can store any YAML value, including a
91  * sequence or mapping of nodes. You can get data from a Node directly or
92  * iterate over it if it's a collection.
93  */
94 struct Node
95 {
96     import std.meta: staticIndexOf;
97     import mir.algebraic: TaggedVariant, visit, tryVisit, optionalVisit;
98     import mir.serde: serdeIgnore;
99 
100     @serdeIgnore:
101     public:
102         alias Pair = .Pair;
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115     package:
116         // YAML value type.
117         alias Value = TaggedVariant!(
118             [
119                 "null_",
120                 "merge",
121                 "boolean",
122                 "integer",
123                 "decimal",
124                 "binary",
125                 "timestamp",
126                 "string",
127                 "mapping",
128                 "sequence",
129             ],
130             typeof(null),
131             YAMLMerge,
132             bool,
133             long,
134             double,
135             ubyte[],
136             Timestamp,
137             string,
138             Node.Pair[],
139             Node[],
140         );
141 
142         // Can Value hold this type naturally?
143         enum allowed(T) = isIntegral!T ||
144                        isFloatingPoint!T ||
145                        isSomeString!T ||
146                        is(Unqual!T == bool) ||
147                        staticIndexOf!(T, Value.AllowedTypes) >= 0 ||
148                        __traits(compiles, Timestamp(T.init)) ||
149                        is(T == Value);
150 
151         // Stored value.
152         Value value_;
153         // Start position of the node.
154         Mark startMark_;
155 
156         // Tag of the node.
157         string tag_;
158         // Node scalar style. Used to remember style this node was loaded with.
159         ScalarStyle scalarStyle = ScalarStyle.invalid;
160         // Node collection style. Used to remember style this node was loaded with.
161         CollectionStyle collectionStyle = CollectionStyle.invalid;
162 
163     public:
164         /** Construct a Node from a value.
165          *
166          * Any type except for Node can be stored in a Node, but default YAML
167          * types (integers, floats, strings, timestamps, etc.) will be stored
168          * more efficiently. To create a node representing a null value,
169          * construct it from YAMLNull.
170          *
171          * If value is a node, its value will be copied directly. The tag and
172          * other information attached to the original node will be discarded.
173          *
174          * If value is an array of nodes or pairs, it is stored directly.
175          * Otherwise, every value in the array is converted to a node, and
176          * those nodes are stored.
177          *
178          * Note that to emit any non-default types you store
179          * in a node, you need a Representer to represent them in YAML -
180          * otherwise emitting will fail.
181          *
182          * Params:  value = Value to store in the node.
183          *          tag   = Overrides tag of the node when emitted, regardless
184          *                  of tag determined by Representer. Representer uses
185          *                  this to determine YAML data type when a D data type
186          *                  maps to multiple different YAML data types. Tag must
187          *                  be in full form, e.g. "tag:yaml.org,2002:int", not
188          *                  a shortcut, like "!!int".
189          */
190         this(T)(T value, const string tag = null) @safe
191             if (allowed!T || isArray!T || isAssociativeArray!T || is(Unqual!T == Node) || castableToNode!T)
192         {
193             tag_ = tag;
194 
195             //Unlike with assignment, we're just copying the value.
196             static if (is(Unqual!T == Node))
197             {
198                 setValue(value.value_);
199             }
200             else static if(isSomeString!T)
201             {
202                 setValue(value.to!string);
203             }
204             else static if(is(Unqual!T == bool))
205             {
206                 setValue(cast(bool)value);
207             }
208             else static if(isIntegral!T)
209             {
210                 setValue(cast(long)value);
211             }
212             else static if(isFloatingPoint!T)
213             {
214                 setValue(cast(double)value);
215             }
216             else static if (isArray!T)
217             {
218                 alias ElementT = Unqual!(ElementType!T);
219                 // Construction from raw node or pair array.
220                 static if(is(ElementT == Node) || is(ElementT == Node.Pair))
221                 {
222                     setValue(value);
223                 }
224                 // Need to handle byte buffers separately.
225                 else static if(is(ElementT == byte) || is(ElementT == ubyte))
226                 {
227                     setValue(cast(ubyte[]) value);
228                 }
229                 else
230                 {
231                     Node[] nodes;
232                     foreach(ref v; value)
233                     {
234                         nodes ~= Node(v);
235                     }
236                     setValue(nodes);
237                 }
238             }
239             else static if (isAssociativeArray!T)
240             {
241                 Node.Pair[] pairs;
242                 foreach(k, ref v; value)
243                 {
244                     pairs ~= Pair(k, v);
245                 }
246                 setValue(pairs);
247             }
248             // User defined type.
249             else
250             {
251                 setValue(value);
252             }
253         }
254         /// Construct a scalar node
255         @safe unittest
256         {
257             // Integer
258             {
259                 auto node = Node(5);
260             }
261             // String
262             {
263                 auto node = Node("Hello world!");
264             }
265             // Floating point
266             {
267                 auto node = Node(5.0f);
268             }
269             // Boolean
270             {
271                 auto node = Node(true);
272             }
273             // Time
274             {
275                 auto node = Node(Timestamp(2005, 6, 15, 20, 0, 0));
276             }
277             // Integer, dumped as a string
278             {
279                 auto node = Node(5, "tag:yaml.org,2002:str");
280             }
281         }
282         /// Construct a sequence node
283         @safe unittest
284         {
285             // Will be emitted as a sequence (default for arrays)
286             {
287                 auto seq = Node([1, 2, 3, 4, 5]);
288             }
289             // Will be emitted as a set (overridden tag)
290             {
291                 auto set = Node([1, 2, 3, 4, 5], "tag:yaml.org,2002:set");
292             }
293             // Can also store arrays of arrays
294             {
295                 auto node = Node([[1,2], [3,4]]);
296             }
297         }
298         /// Construct a mapping node
299         @safe unittest
300         {
301             // Will be emitted as an unordered mapping (default for mappings)
302             auto map   = Node([1 : "a", 2 : "b"]);
303             // Will be emitted as an ordered map (overridden tag)
304             auto omap  = Node([1 : "a", 2 : "b"], "tag:yaml.org,2002:omap");
305             // Will be emitted as pairs (overridden tag)
306             auto pairs = Node([1 : "a", 2 : "b"], "tag:yaml.org,2002:pairs");
307         }
308         @safe unittest
309         {
310             {
311                 auto node = Node(42);
312                 assert(node.nodeID == NodeID.scalar);
313                 assert(node.as!int == 42 && node.as!float == 42.0f && node.as!string == "42");
314             }
315 
316             {
317                 auto node = Node("string");
318                 assert(node.as!string == "string");
319             }
320         }
321         @safe unittest
322         {
323             with(Node([1, 2, 3]))
324             {
325                 assert(nodeID == NodeID.sequence);
326                 assert(length == 3);
327                 assert(opIndex(2).as!int == 3);
328             }
329 
330         }
331         @safe unittest
332         {
333             int[string] aa;
334             aa["1"] = 1;
335             aa["2"] = 2;
336             with(Node(aa))
337             {
338                 assert(nodeID == NodeID.mapping);
339                 assert(length == 2);
340                 assert(opIndex("2").as!int == 2);
341             }
342         }
343         @safe unittest
344         {
345             auto node = Node(Node(4, "tag:yaml.org,2002:str"));
346             assert(node == 4);
347             assert(node.tag_ == "");
348         }
349 
350         /** Construct a node from arrays of _keys and _values.
351          *
352          * Constructs a mapping node with key-value pairs from
353          * _keys and _values, keeping their order. Useful when order
354          * is important (ordered maps, pairs).
355          *
356          *
357          * keys and values must have equal length.
358          *
359          *
360          * If _keys and/or _values are nodes, they are stored directly/
361          * Otherwise they are converted to nodes and then stored.
362          *
363          * Params:  keys   = Keys of the mapping, from first to last pair.
364          *          values = Values of the mapping, from first to last pair.
365          *          tag    = Overrides tag of the node when emitted, regardless
366          *                   of tag determined by Representer. Representer uses
367          *                   this to determine YAML data type when a D data type
368          *                   maps to multiple different YAML data types.
369          *                   This is used to differentiate between YAML unordered
370          *                   mappings ("!!map"), ordered mappings ("!!omap"), and
371          *                   pairs ("!!pairs") which are all internally
372          *                   represented as an array of node pairs. Tag must be
373          *                   in full form, e.g. "tag:yaml.org,2002:omap", not a
374          *                   shortcut, like "!!omap".
375          *
376          */
377         this(K, V)(K[] keys, V[] values, const string tag = null)
378         if(!(isSomeString!(K[]) || isSomeString!(V[])))
379         in(keys.length == values.length,
380            "Lengths of keys and values arrays to construct " ~
381            "a YAML node from don't match")
382         {
383             tag_ = tag;
384 
385             Node.Pair[] pairs;
386             foreach(i; 0 .. keys.length){pairs ~= Pair(keys[i], values[i]);}
387             setValue(pairs);
388         }
389         ///
390         @safe unittest
391         {
392             // Will be emitted as an unordered mapping (default for mappings)
393             auto map   = Node([1, 2], ["a", "b"]);
394             // Will be emitted as an ordered map (overridden tag)
395             auto omap  = Node([1, 2], ["a", "b"], "tag:yaml.org,2002:omap");
396             // Will be emitted as pairs (overriden tag)
397             auto pairs = Node([1, 2], ["a", "b"], "tag:yaml.org,2002:pairs");
398         }
399         @safe unittest
400         {
401             with(Node(["1", "2"], [1, 2]))
402             {
403                 assert(nodeID == NodeID.mapping);
404                 assert(length == 2);
405                 assert(opIndex("2").as!int == 2);
406             }
407 
408         }
409 
410         /// Return tag of the node.
411         @property string tag()      const @safe nothrow
412         {
413             return tag_;
414         }
415 
416         /// Return the start position of the node.
417         @property Mark startMark()  const @safe pure nothrow
418         {
419             return startMark_;
420         }
421 
422         /** Equality test.
423          *
424          * If T is Node, recursively compares all subnodes.
425          * This might be quite expensive if testing entire documents.
426          *
427          * If T is not Node, gets a value of type T from the node and tests
428          * equality with that.
429          *
430          * To test equality with a null YAML value, use YAMLNull.
431          *
432          * Params:  rhs = Variable to test equality with.
433          *
434          * Returns: true if equal, false otherwise.
435          */
436         bool opEquals(const Node rhs) const @safe
437         {
438             return opCmp(rhs) == 0;
439         }
440         bool opEquals(T)(const auto ref T rhs) const
441         {
442             try
443             {
444                 auto stored = get!(T, No.stringConversion);
445                 // NaNs aren't normally equal to each other, but we'll pretend they are.
446                 static if(isFloatingPoint!T)
447                 {
448                     return rhs == stored || (rhs != rhs && stored != stored);
449                 }
450                 else
451                 {
452                     return rhs == stored;
453                 }
454             }
455             catch(NodeException e)
456             {
457                 return false;
458             }
459         }
460         ///
461         @safe unittest
462         {
463             auto node = Node(42);
464 
465             assert(node == 42);
466             assert(node != "42");
467             assert(node != "43");
468 
469             auto node2 = Node(null);
470             assert(node2 == null);
471 
472             const node3 = Node(42);
473             assert(node3 == 42);
474         }
475 
476         /// Shortcut for get().
477         alias as = get;
478 
479         /** Get the value of the node as specified type.
480          *
481          * If the specifed type does not match type in the node,
482          * conversion is attempted. The stringConversion template
483          * parameter can be used to disable conversion from non-string
484          * types to strings.
485          *
486          * Numeric values are range checked, throwing if out of range of
487          * requested type.
488          *
489          * Timestamps are stored as mir.timestamp.Timestamp.
490          * Binary values are decoded and stored as ubyte[].
491          *
492          * To get a null value, use get!YAMLNull . This is to
493          * prevent getting null values for types such as strings or classes.
494          *
495          * $(BR)$(B Mapping default values:)
496          *
497          * $(PBR
498          * The '=' key can be used to denote the default value of a mapping.
499          * This can be used when a node is scalar in early versions of a program,
500          * but is replaced by a mapping later. Even if the node is a mapping, the
501          * get method can be used as if it was a scalar if it has a default value.
502          * This way, new YAML files where the node is a mapping can still be read
503          * by old versions of the program, which expect the node to be a scalar.
504          * )
505          *
506          * Returns: Value of the node as specified type.
507          *
508          * Throws:  NodeException if unable to convert to specified type, or if
509          *          the value is out of range of requested type.
510          */
511         inout(T) get(T, Flag!"stringConversion" stringConversion = Yes.stringConversion)() inout
512             if (allowed!(Unqual!T) || hasNodeConstructor!(inout(Unqual!T)) || (!hasIndirections!(Unqual!T) && hasNodeConstructor!(Unqual!T)))
513         {
514             static if(!allowed!(Unqual!T))
515             {
516                 static if (hasSimpleNodeConstructor!(Unqual!T) || hasSimpleNodeConstructor!(inout(Unqual!T)))
517                 {
518                     alias params = AliasSeq!(this);
519                 }
520                 else static if (hasExpandedNodeConstructor!(Unqual!T) || hasExpandedNodeConstructor!(inout(Unqual!T)))
521                 {
522                     alias params = AliasSeq!(this, tag_);
523                 }
524                 else
525                 {
526                     static assert(0, "Unknown Node constructor?");
527                 }
528 
529                 static if (is(T == class))
530                 {
531                     return new inout T(params);
532                 }
533                 else static if (is(T == struct))
534                 {
535                     return T(params);
536                 }
537                 else
538                 {
539                     static assert(0, "Unhandled user type");
540                 }
541             }
542             else
543             static if (is(typeof(Timestamp.init.opCast!(Unqual!T))))
544             {
545                 return value_.trustedGet!Timestamp.opCast!(Unqual!T);
546             }
547             else
548             {
549 
550                 // If we're getting from a mapping and we're not getting Node.Pair[],
551                 // we're getting the default value.
552                 static if (staticIndexOf!(Unqual!T, Value.AllowedTypes) >= 0)
553                 {
554                     if (_is!(Unqual!T))
555                         return value_.trustedGet!(Unqual!T);
556                 }
557 
558                 if(nodeID == NodeID.mapping){return this["="].get!( T, stringConversion);}
559 
560                 static if(isSomeString!T)
561                 {
562                     static if(!stringConversion)
563                     {
564                         if (type != NodeType..string)
565                             throw new NodeException(text("Node stores unexpected type: ", type, ". Expected: ", typeid(T)), startMark_);
566                         return to!T(getValue!string);
567                     }
568                     else
569                     {
570                         // Try to convert to string.
571                         try
572                         {
573                             return coerceValue!T();
574                         }
575                         catch(Exception e)
576                         {
577                             throw new NodeException("Unable to convert node value to string", startMark_);
578                         }
579                     }
580                 }
581                 else static if(isFloatingPoint!T)
582                 {
583                     final switch (type)
584                     {
585                         case NodeType.integer:
586                             return to!T(getValue!long);
587                         case NodeType.decimal:
588                             return to!T(getValue!double);
589                         case NodeType.binary:
590                         case NodeType..string:
591                         case NodeType.boolean:
592                         case NodeType.null_:
593                         case NodeType.merge:
594                         case NodeType.timestamp:
595                         case NodeType.mapping:
596                         case NodeType.sequence:
597                             throw new NodeException(text("Node stores unexpected type: ", type, ". Expected: ", typeid(T)), startMark_);
598                     }
599                 }
600                 else static if(isIntegral!T)
601                 {
602                     if (type != NodeType.integer)
603                         throw new NodeException(text("Node stores unexpected type: ", type, ". Expected: ", typeid(T)), startMark_);
604                     immutable temp = getValue!long;
605                     if(!(temp >= T.min && temp <= T.max))
606                         throw new NodeException(text("Integer value of type ", typeid(T), " out of range. Value: ", temp), startMark_);
607                     return temp.to!T;
608                 }
609                 else
610                     throw new NodeException(text("Node stores unexpected type: ", type, ". Expected: ", typeid(T)), startMark_);
611             }
612         }
613         /// ditto
614         T get(T)() const
615             if (hasIndirections!(Unqual!T) && hasNodeConstructor!(Unqual!T) && (!hasNodeConstructor!(inout(Unqual!T))))
616         {
617             static if (hasSimpleNodeConstructor!T)
618             {
619                 alias params = AliasSeq!(this);
620             }
621             else static if (hasExpandedNodeConstructor!T)
622             {
623                 alias params = AliasSeq!(this, tag_);
624             }
625             else
626             {
627                 static assert(0, "Unknown Node constructor?");
628             }
629             static if (is(T == class))
630             {
631                 return new T(params);
632             }
633             else static if (is(T == struct))
634             {
635                 return T(params);
636             }
637             else
638             {
639                 static assert(0, "Unhandled user type");
640             }
641         }
642         /// Automatic type conversion
643         @safe unittest
644         {
645             auto node = Node(42);
646 
647             assert(node.get!int == 42);
648             assert(node.get!string == "42");
649             assert(node.get!double == 42.0);
650         }
651         /// Scalar node to struct and vice versa
652         @safe unittest
653         {
654             import std.array: appender;
655             import mir.format: text;
656             import dyaml.dumper : dumper;
657             import dyaml.loader : Loader;
658             static struct MyStruct
659             {
660                 int x, y, z;
661 
662                 this(int x, int y, int z) @safe
663                 {
664                     this.x = x;
665                     this.y = y;
666                     this.z = z;
667                 }
668 
669                 this(Node node) @safe
670                 {
671                     import std..string: split;
672                     auto parts = node.as!string().split(":");
673                     x = parts[0].to!int;
674                     y = parts[1].to!int;
675                     z = parts[2].to!int;
676                 }
677 
678                 Node opCast(T: Node)() @safe
679                 {
680                     //Using custom scalar format, x:y:z.
681                     //Representing as a scalar, with custom tag to specify this data type.
682                     return Node(text!":"(x, y, z), "!mystruct.tag");
683                 }
684             }
685 
686             auto app = appender!(char[]);
687 
688             // Dump struct to yaml document
689             dumper().dump(app, Node(MyStruct(1,2,3)));
690 
691             // Read yaml document back as a MyStruct
692             auto loader = Loader.fromString(app.data);
693             Node node = loader.load();
694             assert(node.as!MyStruct == MyStruct(1,2,3));
695         }
696         /// Sequence node to struct and vice versa
697         @safe unittest
698         {
699             import std.array: Appender;
700             import dyaml.dumper : dumper;
701             import dyaml.loader : Loader;
702             static struct MyStruct
703             {
704                 int x, y, z;
705 
706                 this(int x, int y, int z) @safe
707                 {
708                     this.x = x;
709                     this.y = y;
710                     this.z = z;
711                 }
712 
713                 this(Node node) @safe
714                 {
715                     x = node[0].as!int;
716                     y = node[1].as!int;
717                     z = node[2].as!int;
718                 }
719 
720                 Node opCast(T: Node)()
721                 {
722                     return Node([x, y, z], "!mystruct.tag");
723                 }
724             }
725 
726             auto appender = new Appender!string;
727 
728             // Dump struct to yaml document
729             dumper().dump(appender, Node(MyStruct(1,2,3)));
730 
731             // Read yaml document back as a MyStruct
732             auto loader = Loader.fromString(appender.data);
733             Node node = loader.load();
734             assert(node.as!MyStruct == MyStruct(1,2,3));
735         }
736         /// Mapping node to struct and vice versa
737         @safe unittest
738         {
739             import std.array: Appender;
740             import dyaml.dumper : dumper;
741             import dyaml.loader : Loader;
742             static struct MyStruct
743             {
744                 int x, y, z;
745 
746                 Node opCast(T: Node)()
747                 {
748                     auto pairs = [Node.Pair("x", x),
749                         Node.Pair("y", y),
750                         Node.Pair("z", z)];
751                     return Node(pairs, "!mystruct.tag");
752                 }
753 
754                 this(int x, int y, int z)
755                 {
756                     this.x = x;
757                     this.y = y;
758                     this.z = z;
759                 }
760 
761                 this(Node node) @safe
762                 {
763                     x = node["x"].as!int;
764                     y = node["y"].as!int;
765                     z = node["z"].as!int;
766                 }
767             }
768 
769             auto appender = new Appender!string;
770 
771             // Dump struct to yaml document
772             dumper().dump(appender, Node(MyStruct(1,2,3)));
773 
774             // Read yaml document back as a MyStruct
775             auto loader = Loader.fromString(appender.data);
776             Node node = loader.load();
777             assert(node.as!MyStruct == MyStruct(1,2,3));
778         }
779         /// Classes can be used too
780         @system unittest {
781             import std.array: Appender;
782             import dyaml.dumper : dumper;
783             import dyaml.loader : Loader;
784 
785             static class MyClass
786             {
787                 int x, y, z;
788 
789                 this(int x, int y, int z)
790                 {
791                     this.x = x;
792                     this.y = y;
793                     this.z = z;
794                 }
795 
796                 this(Node node) @safe inout
797                 {
798                     import std..string: split;
799                     auto parts = node.as!string().split(":");
800                     x = parts[0].to!int;
801                     y = parts[1].to!int;
802                     z = parts[2].to!int;
803                 }
804 
805                 ///Useful for Node.as!string.
806                 override string toString()
807                 {
808                     return text("MyClass(", x,", ", y,", ", z,")");
809                 }
810 
811                 Node opCast(T: Node)() @safe
812                 {
813                     //Using custom scalar format, x:y:z.
814                     auto scalar = text!":"(x, y, z);
815                     //Representing as a scalar, with custom tag to specify this data type.
816                     return Node(scalar, "!myclass.tag");
817                 }
818                 override bool opEquals(Object o)
819                 {
820                     if (auto other = cast(MyClass)o)
821                     {
822                         return (other.x == x) && (other.y == y) && (other.z == z);
823                     }
824                     return false;
825                 }
826             }
827             auto appender = new Appender!string;
828 
829             // Dump class to yaml document
830             dumper().dump(appender, Node(new MyClass(1,2,3)));
831 
832             // Read yaml document back as a MyClass
833             auto loader = Loader.fromString(appender.data);
834             Node node = loader.load();
835             assert(node.as!MyClass == new MyClass(1,2,3));
836         }
837         // Make sure custom tags and styles are kept.
838         @safe unittest
839         {
840             static struct MyStruct
841             {
842                 Node opCast(T: Node)()
843                 {
844                     auto node = Node("hi", "!mystruct.tag");
845                     node.setStyle(ScalarStyle.doubleQuoted);
846                     return node;
847                 }
848             }
849 
850             auto node = Node(MyStruct.init);
851             assert(node.tag == "!mystruct.tag");
852             assert(node.scalarStyle == ScalarStyle.doubleQuoted);
853         }
854         // ditto, but for collection style
855         @safe unittest
856         {
857             static struct MyStruct
858             {
859                 Node opCast(T: Node)()
860                 {
861                     auto node = Node(["hi"], "!mystruct.tag");
862                     node.setStyle(CollectionStyle.flow);
863                     return node;
864                 }
865             }
866 
867             auto node = Node(MyStruct.init);
868             assert(node.tag == "!mystruct.tag");
869             assert(node.collectionStyle == CollectionStyle.flow);
870         }
871         @safe unittest
872         {
873             assertThrown!NodeException(Node("42").get!int);
874             assertThrown!NodeException(Node("42").get!double);
875             assertThrown!NodeException(Node(long.max).get!ushort);
876             Node(null).get!YAMLNull;
877         }
878         @safe unittest
879         {
880             const node = Node(42);
881             assert(node.get!int == 42);
882             assert(node.get!string == "42");
883             assert(node.get!double == 42.0);
884 
885             immutable node2 = Node(42);
886             assert(node2.get!int == 42);
887             assert(node2.get!(const int) == 42);
888             assert(node2.get!(immutable int) == 42);
889             assert(node2.get!string == "42");
890             assert(node2.get!(const string) == "42");
891             assert(node2.get!(immutable string) == "42");
892             assert(node2.get!double == 42.0);
893             assert(node2.get!(const double) == 42.0);
894             assert(node2.get!(immutable double) == 42.0);
895         }
896 
897         /** If this is a collection, return its _length.
898          *
899          * Otherwise, throw NodeException.
900          *
901          * Returns: Number of elements in a sequence or key-value pairs in a mapping.
902          *
903          * Throws: NodeException if this is not a sequence nor a mapping.
904          */
905         @property size_t length() const @safe
906         {
907             final switch(nodeID)
908             {
909                 case NodeID.sequence:
910                     return getValue!(Node[]).length;
911                 case NodeID.mapping:
912                     return getValue!(Pair[]).length;
913                 case NodeID.scalar:
914                 case NodeID.invalid:
915                     throw new NodeException(text("Trying to get length of a ", nodeID, " node"), startMark_);
916             }
917         }
918         @safe unittest
919         {
920             auto node = Node([1,2,3]);
921             assert(node.length == 3);
922             const cNode = Node([1,2,3]);
923             assert(cNode.length == 3);
924             immutable iNode = Node([1,2,3]);
925             assert(iNode.length == 3);
926         }
927 
928         /** Get the element at specified index.
929          *
930          * If the node is a sequence, index must be integral.
931          *
932          *
933          * If the node is a mapping, return the value corresponding to the first
934          * key equal to index. containsKey() can be used to determine if a mapping
935          * has a specific key.
936          *
937          * To get element at a null index, use YAMLNull for index.
938          *
939          * Params:  index = Index to use.
940          *
941          * Returns: Value corresponding to the index.
942          *
943          * Throws:  NodeException if the index could not be found,
944          *          non-integral index is used with a sequence or the node is
945          *          not a collection.
946          */
947         ref inout(Node) opIndex(T)(T index) inout @safe
948         {
949             final switch (nodeID)
950             {
951                 case NodeID.sequence:
952                     checkSequenceIndex(index);
953                     static if(isIntegral!T)
954                     {
955                         return getValue!(Node[])[index];
956                     }
957                     else
958                     {
959                         assert(false, "Only integers may index sequence nodes");
960                     }
961                 case NodeID.mapping:
962                     auto idx = findPair(index);
963                     if(idx >= 0)
964                     {
965                         return getValue!(Pair[])[idx].value;
966                     }
967 
968                     string msg = "Mapping index not found" ~ (isSomeString!T ? ": " ~ to!string(index) : "");
969                     throw new NodeException(msg, startMark_);
970                 case NodeID.scalar:
971                 case NodeID.invalid:
972                     throw new NodeException(text("Trying to index a ", nodeID, " node"), startMark_);
973             }
974         }
975         ///
976         @safe unittest
977         {
978             Node narray = Node([11, 12, 13, 14]);
979             Node nmap   = Node(["11", "12", "13", "14"], [11, 12, 13, 14]);
980 
981             assert(narray[0].as!int == 11);
982             assert(null !is collectException(narray[42]));
983             assert(nmap["11"].as!int == 11);
984             assert(nmap["14"].as!int == 14);
985         }
986         @safe unittest
987         {
988             Node narray = Node([11, 12, 13, 14]);
989             Node nmap   = Node(["11", "12", "13", "14"], [11, 12, 13, 14]);
990 
991             assert(narray[0].as!int == 11);
992             assert(null !is collectException(narray[42]));
993             assert(nmap["11"].as!int == 11);
994             assert(nmap["14"].as!int == 14);
995             assert(null !is collectException(nmap["42"]));
996 
997             narray.add(null);
998             nmap.add(null, "Nothing");
999             assert(narray[4].as!YAMLNull == null);
1000             assert(nmap[null].as!string == "Nothing");
1001 
1002             assertThrown!NodeException(nmap[11]);
1003             assertThrown!NodeException(nmap[14]);
1004         }
1005 
1006         /** Determine if a collection contains specified value.
1007          *
1008          * If the node is a sequence, check if it contains the specified value.
1009          * If it's a mapping, check if it has a value that matches specified value.
1010          *
1011          * Params:  rhs = Item to look for. Use YAMLNull to check for a null value.
1012          *
1013          * Returns: true if rhs was found, false otherwise.
1014          *
1015          * Throws:  NodeException if the node is not a collection.
1016          */
1017         bool contains(T)(T rhs) const
1018         {
1019             return contains_!(T, No.key, "contains")(rhs);
1020         }
1021         @safe unittest
1022         {
1023             auto mNode = Node(["1", "2", "3"]);
1024             assert(mNode.contains("2"));
1025             const cNode = Node(["1", "2", "3"]);
1026             assert(cNode.contains("2"));
1027             immutable iNode = Node(["1", "2", "3"]);
1028             assert(iNode.contains("2"));
1029         }
1030 
1031 
1032         /** Determine if a mapping contains specified key.
1033          *
1034          * Params:  rhs = Key to look for. Use YAMLNull to check for a null key.
1035          *
1036          * Returns: true if rhs was found, false otherwise.
1037          *
1038          * Throws:  NodeException if the node is not a mapping.
1039          */
1040         bool containsKey(T)(T rhs) const
1041         {
1042             return contains_!(T, Yes.key, "containsKey")(rhs);
1043         }
1044 
1045         // Unittest for contains() and containsKey().
1046         @safe unittest
1047         {
1048             auto seq = Node([1, 2, 3, 4, 5]);
1049             assert(seq.contains(3));
1050             assert(seq.contains(5));
1051             assert(!seq.contains("5"));
1052             assert(!seq.contains(6));
1053             assert(!seq.contains(float.nan));
1054             assertThrown!NodeException(seq.containsKey(5));
1055 
1056             auto seq2 = Node(["1", "2"]);
1057             assert(seq2.contains("1"));
1058             assert(!seq2.contains(1));
1059 
1060             auto map = Node(["1", "2", "3", "4"], [1, 2, 3, 4]);
1061             assert(map.contains(1));
1062             assert(!map.contains("1"));
1063             assert(!map.contains(5));
1064             assert(!map.contains(float.nan));
1065             assert(map.containsKey("1"));
1066             assert(map.containsKey("4"));
1067             assert(!map.containsKey(1));
1068             assert(!map.containsKey("5"));
1069 
1070             assert(!seq.contains(null));
1071             assert(!map.contains(null));
1072             assert(!map.containsKey(null));
1073             seq.add(null);
1074             map.add("Nothing", null);
1075             assert(seq.contains(null));
1076             assert(map.contains(null));
1077             assert(!map.containsKey(null));
1078             map.add(null, "Nothing");
1079             assert(map.containsKey(null));
1080 
1081             auto map2 = Node([1, 2, 3, 4], [1, 2, 3, 4]);
1082             assert(!map2.contains("1"));
1083             assert(map2.contains(1));
1084             assert(!map2.containsKey("1"));
1085             assert(map2.containsKey(1));
1086 
1087             // scalar
1088             assertThrown!NodeException(Node(1).contains(4));
1089             assertThrown!NodeException(Node(1).containsKey(4));
1090 
1091             auto mapNan = Node([1.0, 2, double.nan], [1, double.nan, 5]);
1092 
1093             assert(mapNan.contains(double.nan));
1094             assert(mapNan.containsKey(double.nan));
1095         }
1096 
1097         /// Assignment (shallow copy) by value.
1098         void opAssign()(auto ref Node rhs)
1099         {
1100             assumeWontThrow(setValue(rhs.value_));
1101             startMark_      = rhs.startMark_;
1102             tag_            = rhs.tag_;
1103             scalarStyle     = rhs.scalarStyle;
1104             collectionStyle = rhs.collectionStyle;
1105         }
1106         // Unittest for opAssign().
1107         @safe unittest
1108         {
1109             auto seq = Node([1, 2, 3, 4, 5]);
1110             auto assigned = seq;
1111             assert(seq == assigned,
1112                    "Node.opAssign() doesn't produce an equivalent copy");
1113         }
1114 
1115         /** Set element at specified index in a collection.
1116          *
1117          * This method can only be called on collection nodes.
1118          *
1119          * If the node is a sequence, index must be integral.
1120          *
1121          * If the node is a mapping, sets the _value corresponding to the first
1122          * key matching index (including conversion, so e.g. "42" matches 42).
1123          *
1124          * If the node is a mapping and no key matches index, a new key-value
1125          * pair is added to the mapping. In sequences the index must be in
1126          * range. This ensures behavior siilar to D arrays and associative
1127          * arrays.
1128          *
1129          * To set element at a null index, use YAMLNull for index.
1130          *
1131          * Params:
1132          *          value = Value to assign.
1133          *          index = Index of the value to set.
1134          *
1135          * Throws:  NodeException if the node is not a collection, index is out
1136          *          of range or if a non-integral index is used on a sequence node.
1137          */
1138         void opIndexAssign(K, V)(V value, K index)
1139         {
1140             final switch (nodeID)
1141             {
1142                 case NodeID.sequence:
1143                     checkSequenceIndex(index);
1144                     static if(isIntegral!K || is(Unqual!K == bool))
1145                     {
1146                         auto nodes = getValue!(Node[]);
1147                         static if(is(Unqual!V == Node)){nodes[index] = value;}
1148                         else                           {nodes[index] = Node(value);}
1149                         setValue(nodes);
1150                         return;
1151                     }
1152                     assert(false, "Only integers may index sequence nodes");
1153                 case NodeID.mapping:
1154                     const idx = findPair(index);
1155                     if(idx < 0){add(index, value);}
1156                     else
1157                     {
1158                         auto pairs = as!(Node.Pair[])();
1159                         static if(is(Unqual!V == Node)){pairs[idx].value = value;}
1160                         else                           {pairs[idx].value = Node(value);}
1161                         setValue(pairs);
1162                     }
1163                     return;
1164                 case NodeID.scalar:
1165                 case NodeID.invalid:
1166                     throw new NodeException(text("Trying to index a ", nodeID, " node"), startMark_);
1167             }
1168         }
1169         @safe unittest
1170         {
1171             with(Node([1, 2, 3, 4, 3]))
1172             {
1173                 opIndexAssign(42, 3);
1174                 assert(length == 5);
1175                 assert(opIndex(3).as!int == 42);
1176 
1177                 opIndexAssign(null, 0);
1178                 assert(opIndex(0) == null);
1179             }
1180             with(Node(["1", "2", "3"], [4, 5, 6]))
1181             {
1182                 opIndexAssign(42, "3");
1183                 opIndexAssign(123, 456);
1184                 assert(length == 4);
1185                 assert(opIndex("3").as!int == 42);
1186                 assert(opIndex(456).as!int == 123);
1187 
1188                 opIndexAssign(43, 3);
1189                 //3 and "3" should be different
1190                 assert(length == 5);
1191                 assert(opIndex("3").as!int == 42);
1192                 assert(opIndex(3).as!int == 43);
1193 
1194                 opIndexAssign(null, "2");
1195                 assert(opIndex("2") == null);
1196             }
1197         }
1198 
1199         /** Return a range object iterating over a sequence, getting each
1200           * element as T.
1201           *
1202           * If T is Node, simply iterate over the nodes in the sequence.
1203           * Otherwise, convert each node to T during iteration.
1204           *
1205           * Throws: NodeException if the node is not a sequence or an element
1206           *         could not be converted to specified type.
1207           */
1208         template sequence(T = Node)
1209         {
1210             auto sequence(this This)()
1211             {
1212                 import mir.ndslice.slice: sliced;
1213                 import mir.ndslice.topology: map;
1214                 if (nodeID != NodeID.sequence)
1215                     throw new NodeException(text("Trying to 'sequence'-iterate over a ", nodeID, " node"), startMark_);
1216                 static if (is(T == Node))
1217                     return get!(Node[]).sliced;
1218                 else
1219                     return get!(Node[]).map!((ref elem) => elem.as!T);
1220             }
1221         }
1222         @safe unittest
1223         {
1224             import mir.ndslice.topology: map;
1225             Node n1 = Node([1, 2, 3, 4]);
1226             int[int] array;
1227             Node n2 = Node(array);
1228             const n3 = Node([1, 2, 3, 4]);
1229 
1230             auto r = n1.sequence!int.map!(x => x * 10);
1231             assert(r == [10, 20, 30, 40]);
1232 
1233             assertThrown(n2.sequence);
1234 
1235             auto r2 = n3.sequence!int.map!(x => x * 10);
1236             assert(r2 == [10, 20, 30, 40]);
1237         }
1238 
1239         /** Return a range object iterating over mapping's pairs.
1240           *
1241           * Throws: NodeException if the node is not a mapping.
1242           *
1243           */
1244         template mapping()
1245         {
1246             auto mapping(this This)()
1247             {
1248                 import mir.ndslice.slice: sliced;
1249                 if (nodeID != NodeID.mapping)
1250                     throw new NodeException(text("Trying to 'mapping'-iterate over a ", nodeID, " node"), startMark_);
1251                 return get!(Node.Pair[]).sliced;
1252             }
1253         }
1254 
1255         @safe unittest
1256         {
1257             int[int] array;
1258             Node n = Node(array);
1259             n[1] = "foo";
1260             n[2] = "bar";
1261             n[3] = "baz";
1262 
1263             string[int] test;
1264             foreach (pair; n.mapping)
1265                 test[pair.key.as!int] = pair.value.as!string;
1266 
1267             assert(test[1] == "foo");
1268             assert(test[2] == "bar");
1269             assert(test[3] == "baz");
1270 
1271             int[int] constArray = [1: 2, 3: 4];
1272             const x = Node(constArray);
1273             foreach (pair; x.mapping)
1274                 assert(pair.value == constArray[pair.key.as!int]);
1275         }
1276 
1277         /** Return a range object iterating over mapping's keys.
1278           *
1279           * If K is Node, simply iterate over the keys in the mapping.
1280           * Otherwise, convert each key to T during iteration.
1281           *
1282           * Throws: NodeException if the nodes is not a mapping or an element
1283           *         could not be converted to specified type.
1284           */
1285         auto mappingKeys(K = Node)() const
1286         {
1287             import mir.ndslice.topology: map;
1288             if (nodeID != NodeID.mapping)
1289                 throw new NodeException(text("Trying to 'mappingKeys'-iterate over a ", nodeID, " node"), startMark_);
1290             static if (is(Unqual!K == Node))
1291                 return mapping.map!(pair => pair.key);
1292             else
1293                 return mapping.map!(pair => pair.key.as!K);
1294         }
1295         @safe unittest
1296         {
1297             int[int] array;
1298             Node m1 = Node(array);
1299             m1["foo"] = 2;
1300             m1["bar"] = 3;
1301 
1302             assert(m1.mappingKeys == ["foo", "bar"] || m1.mappingKeys == ["bar", "foo"]);
1303 
1304             const cm1 = Node(["foo": 2, "bar": 3]);
1305 
1306             assert(cm1.mappingKeys == ["foo", "bar"] || cm1.mappingKeys == ["bar", "foo"]);
1307         }
1308 
1309         /** Return a range object iterating over mapping's values.
1310           *
1311           * If V is Node, simply iterate over the values in the mapping.
1312           * Otherwise, convert each key to V during iteration.
1313           *
1314           * Throws: NodeException if the nodes is not a mapping or an element
1315           *         could not be converted to specified type.
1316           */
1317         auto mappingValues(V = Node)() const
1318         {
1319             import mir.ndslice.topology: map;
1320             if (nodeID != NodeID.mapping)
1321                 throw new NodeException(text("Trying to 'mappingValues'-iterate over a ", nodeID, " node"), startMark_);
1322             static if (is(Unqual!V == Node))
1323                 return mapping.map!(pair => pair.value);
1324             else
1325                 return mapping.map!(pair => pair.value.as!V);
1326         }
1327         @safe unittest
1328         {
1329             int[int] array;
1330             Node m1 = Node(array);
1331             m1["foo"] = 2;
1332             m1["bar"] = 3;
1333 
1334             assert(m1.mappingValues == [2, 3] || m1.mappingValues == [3, 2]);
1335 
1336             const cm1 = Node(["foo": 2, "bar": 3]);
1337 
1338             assert(cm1.mappingValues == [2, 3] || cm1.mappingValues == [3, 2]);
1339         }
1340 
1341 
1342         /** Foreach over a sequence, getting each element as T.
1343          *
1344          * If T is Node, simply iterate over the nodes in the sequence.
1345          * Otherwise, convert each node to T during iteration.
1346          *
1347          * Throws:  NodeException if the node is not a sequence or an
1348          *          element could not be converted to specified type.
1349          */
1350         int opApply(D)(D dg) if (isDelegate!D && (Parameters!D.length == 1))
1351         {
1352             if (nodeID != NodeID.sequence)
1353                 throw new NodeException(text("Trying to sequence-foreach over a ", nodeID, " node"), startMark_);
1354 
1355             int result;
1356             foreach(ref node; get!(Node[]))
1357             {
1358                 static if(is(Unqual!(Parameters!D[0]) == Node))
1359                 {
1360                     result = dg(node);
1361                 }
1362                 else
1363                 {
1364                     Parameters!D[0] temp = node.as!(Parameters!D[0]);
1365                     result = dg(temp);
1366                 }
1367                 if(result){break;}
1368             }
1369             return result;
1370         }
1371         /// ditto
1372         int opApply(D)(D dg) const if (isDelegate!D && (Parameters!D.length == 1))
1373         {
1374             if (nodeID != NodeID.sequence)
1375                 throw new NodeException(text("Trying to sequence-foreach over a ", nodeID, " node"), startMark_);
1376 
1377             int result;
1378             foreach(ref node; get!(Node[]))
1379             {
1380                 static if(is(Unqual!(Parameters!D[0]) == Node))
1381                 {
1382                     result = dg(node);
1383                 }
1384                 else
1385                 {
1386                     Parameters!D[0] temp = node.as!(Parameters!D[0]);
1387                     result = dg(temp);
1388                 }
1389                 if(result){break;}
1390             }
1391             return result;
1392         }
1393         @safe unittest
1394         {
1395             Node n1 = Node(11);
1396             Node n2 = Node(12);
1397             Node n3 = Node(13);
1398             Node n4 = Node(14);
1399             Node narray = Node([n1, n2, n3, n4]);
1400             const cNArray = narray;
1401 
1402             int[] array, array2, array3;
1403             foreach(int value; narray)
1404             {
1405                 array ~= value;
1406             }
1407             foreach(Node node; narray)
1408             {
1409                 array2 ~= node.as!int;
1410             }
1411             foreach (const Node node; cNArray)
1412             {
1413                 array3 ~= node.as!int;
1414             }
1415             assert(array == [11, 12, 13, 14]);
1416             assert(array2 == [11, 12, 13, 14]);
1417             assert(array3 == [11, 12, 13, 14]);
1418         }
1419         @safe unittest
1420         {
1421             string[] testStrs = ["1", "2", "3"];
1422             auto node1 = Node(testStrs);
1423             int i = 0;
1424             foreach (string elem; node1)
1425             {
1426                 assert(elem == testStrs[i]);
1427                 i++;
1428             }
1429             const node2 = Node(testStrs);
1430             i = 0;
1431             foreach (string elem; node2)
1432             {
1433                 assert(elem == testStrs[i]);
1434                 i++;
1435             }
1436             immutable node3 = Node(testStrs);
1437             i = 0;
1438             foreach (string elem; node3)
1439             {
1440                 assert(elem == testStrs[i]);
1441                 i++;
1442             }
1443         }
1444         @safe unittest
1445         {
1446             auto node = Node(["a":1, "b":2, "c":3]);
1447             const cNode = node;
1448             assertThrown({foreach (Node n; node) {}}());
1449             assertThrown({foreach (const Node n; cNode) {}}());
1450         }
1451 
1452         /** Foreach over a mapping, getting each key/value as K/V.
1453          *
1454          * If the K and/or V is Node, simply iterate over the nodes in the mapping.
1455          * Otherwise, convert each key/value to T during iteration.
1456          *
1457          * Throws:  NodeException if the node is not a mapping or an
1458          *          element could not be converted to specified type.
1459          */
1460         int opApply(DG)(DG dg) if (isDelegate!DG && (Parameters!DG.length == 2))
1461         {
1462             alias K = Parameters!DG[0];
1463             alias V = Parameters!DG[1];
1464             if (nodeID != NodeID.mapping)
1465                 throw new NodeException(text("Trying to mapping-foreach over a ", nodeID, " node"), startMark_);
1466 
1467             int result;
1468             foreach(ref pair; get!(Node.Pair[]))
1469             {
1470                 static if(is(Unqual!K == Node) && is(Unqual!V == Node))
1471                 {
1472                     result = dg(pair.key, pair.value);
1473                 }
1474                 else static if(is(Unqual!K == Node))
1475                 {
1476                     V tempValue = pair.value.as!V;
1477                     result = dg(pair.key, tempValue);
1478                 }
1479                 else static if(is(Unqual!V == Node))
1480                 {
1481                     K tempKey   = pair.key.as!K;
1482                     result = dg(tempKey, pair.value);
1483                 }
1484                 else
1485                 {
1486                     K tempKey   = pair.key.as!K;
1487                     V tempValue = pair.value.as!V;
1488                     result = dg(tempKey, tempValue);
1489                 }
1490 
1491                 if(result){break;}
1492             }
1493             return result;
1494         }
1495         /// ditto
1496         int opApply(DG)(DG dg) const if (isDelegate!DG && (Parameters!DG.length == 2))
1497         {
1498             alias K = Parameters!DG[0];
1499             alias V = Parameters!DG[1];
1500             if (nodeID != NodeID.mapping)
1501                 throw new NodeException(text("Trying to mapping-foreach over a ", nodeID, " node"), startMark_);
1502 
1503             int result;
1504             foreach(ref pair; get!(Node.Pair[]))
1505             {
1506                 static if(is(Unqual!K == Node) && is(Unqual!V == Node))
1507                 {
1508                     result = dg(pair.key, pair.value);
1509                 }
1510                 else static if(is(Unqual!K == Node))
1511                 {
1512                     V tempValue = pair.value.as!V;
1513                     result = dg(pair.key, tempValue);
1514                 }
1515                 else static if(is(Unqual!V == Node))
1516                 {
1517                     K tempKey   = pair.key.as!K;
1518                     result = dg(tempKey, pair.value);
1519                 }
1520                 else
1521                 {
1522                     K tempKey   = pair.key.as!K;
1523                     V tempValue = pair.value.as!V;
1524                     result = dg(tempKey, tempValue);
1525                 }
1526 
1527                 if(result){break;}
1528             }
1529             return result;
1530         }
1531         @safe unittest
1532         {
1533             Node n1 = Node(cast(long)11);
1534             Node n2 = Node(cast(long)12);
1535             Node n3 = Node(cast(long)13);
1536             Node n4 = Node(cast(long)14);
1537 
1538             Node k1 = Node("11");
1539             Node k2 = Node("12");
1540             Node k3 = Node("13");
1541             Node k4 = Node("14");
1542 
1543             Node nmap1 = Node([Pair(k1, n1),
1544                                Pair(k2, n2),
1545                                Pair(k3, n3),
1546                                Pair(k4, n4)]);
1547 
1548             int[string] expected = ["11" : 11,
1549                                     "12" : 12,
1550                                     "13" : 13,
1551                                     "14" : 14];
1552             int[string] array;
1553             foreach(string key, int value; nmap1)
1554             {
1555                 array[key] = value;
1556             }
1557             assert(array == expected);
1558 
1559             Node nmap2 = Node([Pair(k1, Node(cast(long)5)),
1560                                Pair(k2, Node(true)),
1561                                Pair(k3, Node(cast(double)1.0)),
1562                                Pair(k4, Node("yarly"))]);
1563 
1564             foreach(string key, Node value; nmap2)
1565             {
1566                 switch(key)
1567                 {
1568                     case "11": assert(value.as!int    == 5      ); break;
1569                     case "12": assert(value.as!bool   == true   ); break;
1570                     case "13": assert(value.as!float  == 1.0    ); break;
1571                     case "14": assert(value.as!string == "yarly"); break;
1572                     default:   assert(false);
1573                 }
1574             }
1575             const nmap3 = nmap2;
1576 
1577             foreach(const Node key, const Node value; nmap3)
1578             {
1579                 switch(key.as!string)
1580                 {
1581                     case "11": assert(value.as!int    == 5      ); break;
1582                     case "12": assert(value.as!bool   == true   ); break;
1583                     case "13": assert(value.as!float  == 1.0    ); break;
1584                     case "14": assert(value.as!string == "yarly"); break;
1585                     default:   assert(false);
1586                 }
1587             }
1588         }
1589         @safe unittest
1590         {
1591             string[int] testStrs = [0: "1", 1: "2", 2: "3"];
1592             auto node1 = Node(testStrs);
1593             foreach (const int i, string elem; node1)
1594             {
1595                 assert(elem == testStrs[i]);
1596             }
1597             const node2 = Node(testStrs);
1598             foreach (const int i, string elem; node2)
1599             {
1600                 assert(elem == testStrs[i]);
1601             }
1602             immutable node3 = Node(testStrs);
1603             foreach (const int i, string elem; node3)
1604             {
1605                 assert(elem == testStrs[i]);
1606             }
1607         }
1608         @safe unittest
1609         {
1610             auto node = Node(["a", "b", "c"]);
1611             const cNode = node;
1612             assertThrown({foreach (Node a, Node b; node) {}}());
1613             assertThrown({foreach (const Node a, const Node b; cNode) {}}());
1614         }
1615 
1616         /** Add an element to a sequence.
1617          *
1618          * This method can only be called on sequence nodes.
1619          *
1620          * If value is a node, it is copied to the sequence directly. Otherwise
1621          * value is converted to a node and then stored in the sequence.
1622          *
1623          * $(P When emitting, all values in the sequence will be emitted. When
1624          * using the !!set tag, the user needs to ensure that all elements in
1625          * the sequence are unique, otherwise $(B invalid) YAML code will be
1626          * emitted.)
1627          *
1628          * Params:  value = Value to _add to the sequence.
1629          */
1630         void add(T)(T value)
1631         {
1632             if (value_.isNull)
1633             {
1634                 setValue(Node[].init);
1635             }
1636             if (nodeID != NodeID.sequence)
1637                 throw new NodeException(text("Trying to add an element to a ", nodeID, " node"), startMark_);
1638 
1639             auto nodes = get!(Node[])();
1640             static if(is(Unqual!T == Node)){nodes ~= value;}
1641             else                           {nodes ~= Node(value);}
1642             setValue(nodes);
1643         }
1644         @safe unittest
1645         {
1646             with(Node([1, 2, 3, 4]))
1647             {
1648                 add(5.0f);
1649                 assert(opIndex(4).as!float == 5.0f);
1650             }
1651             with(Node())
1652             {
1653                 add(5.0f);
1654                 assert(opIndex(0).as!float == 5.0f);
1655             }
1656             with(Node(5.0f))
1657             {
1658                 assertThrown!NodeException(add(5.0f));
1659             }
1660             with(Node([5.0f : true]))
1661             {
1662                 assertThrown!NodeException(add(5.0f));
1663             }
1664         }
1665 
1666         /** Add a key-value pair to a mapping.
1667          *
1668          * This method can only be called on mapping nodes.
1669          *
1670          * If key and/or value is a node, it is copied to the mapping directly.
1671          * Otherwise it is converted to a node and then stored in the mapping.
1672          *
1673          * $(P It is possible for the same key to be present more than once in a
1674          * mapping. When emitting, all key-value pairs will be emitted.
1675          * This is useful with the "!!pairs" tag, but will result in
1676          * $(B invalid) YAML with "!!map" and "!!omap" tags.)
1677          *
1678          * Params:  key   = Key to _add.
1679          *          value = Value to _add.
1680          */
1681         void add(K, V)(K key, V value)
1682         {
1683             if (value_.isNull)
1684             {
1685                 setValue(Node.Pair[].init);
1686             }
1687             if (nodeID != NodeID.mapping)
1688                 throw new NodeException(text("Trying to add a key-value pair to a ", nodeID, " node"), startMark_);
1689 
1690             auto pairs = get!(Node.Pair[])();
1691             pairs ~= Pair(key, value);
1692             setValue(pairs);
1693         }
1694         @safe unittest
1695         {
1696             with(Node([1, 2], [3, 4]))
1697             {
1698                 add(5, "6");
1699                 assert(opIndex(5).as!string == "6");
1700             }
1701             with(Node())
1702             {
1703                 add(5, "6");
1704                 assert(opIndex(5).as!string == "6");
1705             }
1706             with(Node(5.0f))
1707             {
1708                 assertThrown!NodeException(add(5, "6"));
1709             }
1710             with(Node([5.0f]))
1711             {
1712                 assertThrown!NodeException(add(5, "6"));
1713             }
1714         }
1715 
1716         /** Determine whether a key is in a mapping, and access its value.
1717          *
1718          * This method can only be called on mapping nodes.
1719          *
1720          * Params:   key = Key to search for.
1721          *
1722          * Returns:  A pointer to the value (as a Node) corresponding to key,
1723          *           or null if not found.
1724          *
1725          * Note:     Any modification to the node can invalidate the returned
1726          *           pointer.
1727          *
1728          * See_Also: contains
1729          */
1730         inout(Node*) opBinaryRight(string op, K)(K key) inout
1731             if (op == "in")
1732         {
1733             if (nodeID != NodeID.mapping)
1734                 throw new NodeException(text("Trying to use 'in' on a ", nodeID, " node"), startMark_);
1735 
1736             auto idx = findPair(key);
1737             if(idx < 0)
1738             {
1739                 return null;
1740             }
1741             else
1742             {
1743                 return &(get!(Node.Pair[])[idx].value);
1744             }
1745         }
1746         @safe unittest
1747         {
1748             auto mapping = Node(["foo", "baz"], ["bar", "qux"]);
1749             assert("bad" !in mapping && ("bad" in mapping) is null);
1750             Node* foo = "foo" in mapping;
1751             assert(foo !is null);
1752             assert(*foo == Node("bar"));
1753             assert(foo.get!string == "bar");
1754             *foo = Node("newfoo");
1755             assert(mapping["foo"] == Node("newfoo"));
1756         }
1757         @safe unittest
1758         {
1759             auto mNode = Node(["a": 2]);
1760             assert("a" in mNode);
1761             const cNode = Node(["a": 2]);
1762             assert("a" in cNode);
1763             immutable iNode = Node(["a": 2]);
1764             assert("a" in iNode);
1765         }
1766 
1767         /** Remove first (if any) occurence of a value in a collection.
1768          *
1769          * This method can only be called on collection nodes.
1770          *
1771          * If the node is a sequence, the first node matching value is removed.
1772          * If the node is a mapping, the first key-value pair where _value
1773          * matches specified value is removed.
1774          *
1775          * Params:  rhs = Value to _remove.
1776          *
1777          * Throws:  NodeException if the node is not a collection.
1778          */
1779         void remove(T)(T rhs)
1780         {
1781             remove_!(T, No.key, "remove")(rhs);
1782         }
1783         @safe unittest
1784         {
1785             with(Node([1, 2, 3, 4, 3]))
1786             {
1787                 remove(3);
1788                 assert(length == 4);
1789                 assert(opIndex(2).as!int == 4);
1790                 assert(opIndex(3).as!int == 3);
1791 
1792                 add(null);
1793                 assert(length == 5);
1794                 remove(null);
1795                 assert(length == 4);
1796             }
1797             with(Node(["1", "2", "3"], [4, 5, 6]))
1798             {
1799                 remove(4);
1800                 assert(length == 2);
1801                 add("nullkey", null);
1802                 assert(length == 3);
1803                 remove(null);
1804                 assert(length == 2);
1805             }
1806         }
1807 
1808         /** Remove element at the specified index of a collection.
1809          *
1810          * This method can only be called on collection nodes.
1811          *
1812          * If the node is a sequence, index must be integral.
1813          *
1814          * If the node is a mapping, remove the first key-value pair where
1815          * key matches index.
1816          *
1817          * If the node is a mapping and no key matches index, nothing is removed
1818          * and no exception is thrown. This ensures behavior siilar to D arrays
1819          * and associative arrays.
1820          *
1821          * Params:  index = Index to remove at.
1822          *
1823          * Throws:  NodeException if the node is not a collection, index is out
1824          *          of range or if a non-integral index is used on a sequence node.
1825          */
1826         void removeAt(T)(T index)
1827         {
1828             remove_!(T, Yes.key, "removeAt")(index);
1829         }
1830         @safe unittest
1831         {
1832             with(Node([1, 2, 3, 4, 3]))
1833             {
1834                 removeAt(3);
1835                 assertThrown!NodeException(removeAt("3"));
1836                 assert(length == 4);
1837                 assert(opIndex(3).as!int == 3);
1838             }
1839             with(Node(["1", "2", "3"], [4, 5, 6]))
1840             {
1841                 // no integer 2 key, so don't remove anything
1842                 removeAt(2);
1843                 assert(length == 3);
1844                 removeAt("2");
1845                 assert(length == 2);
1846                 add(null, "nullval");
1847                 assert(length == 3);
1848                 removeAt(null);
1849                 assert(length == 2);
1850             }
1851         }
1852 
1853         /// Compare with another _node.
1854         int opCmp(const ref Node rhs) const @safe
1855         {
1856             // Compare tags - if equal or both null, we need to compare further.
1857             const tagCmp = (tag_ is null) ? (rhs.tag_ is null) ? 0 : -1
1858                                        : (rhs.tag_ is null) ? 1 : __cmp(tag_, rhs.tag_);
1859             if(tagCmp != 0){return tagCmp;}
1860 
1861             static int cmp(T1, T2)(T1 a, T2 b)
1862             {
1863                 return a > b ? 1  :
1864                        a < b ? -1 :
1865                                0;
1866             }
1867 
1868             // Compare validity: if both valid, we have to compare further.
1869 
1870             const typeCmp = cmp(type, rhs.type);
1871             if(typeCmp != 0){return typeCmp;}
1872 
1873             static int compareCollections(T)(const ref Node lhs, const ref Node rhs)
1874             {
1875                 const c1 = lhs.getValue!T;
1876                 const c2 = rhs.getValue!T;
1877                 if(c1 is c2){return 0;}
1878                 if(c1.length != c2.length)
1879                 {
1880                     return cmp(c1.length, c2.length);
1881                 }
1882                 // Equal lengths, compare items.
1883                 foreach(i; 0 .. c1.length)
1884                 {
1885                     const itemCmp = c1[i].opCmp(c2[i]);
1886                     if(itemCmp != 0){return itemCmp;}
1887                 }
1888                 return 0;
1889             }
1890 
1891             final switch(type)
1892             {
1893                 case NodeType..string:
1894                     return __cmp(getValue!string,
1895                                              rhs.getValue!string);
1896                 case NodeType.integer:
1897                     return cmp(getValue!long, rhs.getValue!long);
1898                 case NodeType.boolean:
1899                     const b1 = getValue!bool;
1900                     const b2 = rhs.getValue!bool;
1901                     return b1 ? b2 ? 0 : 1
1902                               : b2 ? -1 : 0;
1903                 case NodeType.binary:
1904                     const b1 = getValue!(ubyte[]);
1905                     const b2 = rhs.getValue!(ubyte[]);
1906                     return __cmp(b1, b2);
1907                 case NodeType.null_:
1908                     return 0;
1909                 case NodeType.decimal:
1910                     const r1 = getValue!double;
1911                     const r2 = rhs.getValue!double;
1912                     if(r1 != r1)
1913                     {
1914                         return r2 != r2 ? 0 : -1;
1915                     }
1916                     if(r2 != r2)
1917                     {
1918                         return 1;
1919                     }
1920                     // Fuzzy equality.
1921                     if(r1 <= r2 + double.epsilon && r1 >= r2 - double.epsilon)
1922                     {
1923                         return 0;
1924                     }
1925                     return cmp(r1, r2);
1926                 case NodeType.timestamp:
1927                     const t1 = getValue!Timestamp;
1928                     const t2 = rhs.getValue!Timestamp;
1929                     return cmp(t1, t2);
1930                 case NodeType.mapping:
1931                     return compareCollections!(Pair[])(this, rhs);
1932                 case NodeType.sequence:
1933                     return compareCollections!(Node[])(this, rhs);
1934                 case NodeType.merge:
1935                     assert(false, "Cannot compare merge nodes");
1936             }
1937         }
1938 
1939         // Ensure opCmp is symmetric for collections
1940         @safe unittest
1941         {
1942             auto node1 = Node(
1943                 [
1944                     Node("New York Yankees", "tag:yaml.org,2002:str"),
1945                     Node("Atlanta Braves", "tag:yaml.org,2002:str")
1946                 ], "tag:yaml.org,2002:seq"
1947             );
1948             auto node2 = Node(
1949                 [
1950                     Node("Detroit Tigers", "tag:yaml.org,2002:str"),
1951                     Node("Chicago cubs", "tag:yaml.org,2002:str")
1952                 ], "tag:yaml.org,2002:seq"
1953             );
1954             assert(node1 > node2);
1955             assert(node2 < node1);
1956         }
1957 
1958         // Compute hash of the node.
1959         hash_t toHash() nothrow const @trusted
1960         {
1961             const valueHash = value_.toHash();
1962 
1963             return tag_ is null ? valueHash : tag_.hashOf(valueHash);
1964         }
1965         @safe unittest
1966         {
1967             assert(Node(42).toHash() != Node(41).toHash());
1968             assert(Node(42).toHash() != Node(42, "some-tag").toHash());
1969         }
1970 
1971         /// Get type of the node value.
1972         @property NodeType type() const @safe pure nothrow
1973         {
1974             return value_.kind;
1975         }
1976 
1977         /// Get the kind of node this is.
1978         @property NodeID nodeID() const @safe nothrow
1979         {
1980             final switch (type)
1981             {
1982                 case NodeType.sequence:
1983                     return NodeID.sequence;
1984                 case NodeType.mapping:
1985                     return NodeID.mapping;
1986                 case NodeType.boolean:
1987                 case NodeType.integer:
1988                 case NodeType.binary:
1989                 case NodeType..string:
1990                 case NodeType.timestamp:
1991                 case NodeType.null_:
1992                 case NodeType.merge:
1993                 case NodeType.decimal:
1994                     return NodeID.scalar;
1995             }
1996         }
1997     package:
1998 
1999         // Get a string representation of the node tree. Used for debugging.
2000         //
2001         // Params:  level = Level of the node in the tree.
2002         //
2003         // Returns: String representing the node tree.
2004         @property string debugString(uint level = 0) const @safe
2005         {
2006             string indent;
2007             foreach(i; 0 .. level){indent ~= " ";}
2008 
2009             final switch (nodeID)
2010             {
2011                 case NodeID.invalid:
2012                     return indent ~ "invalid";
2013                 case NodeID.sequence:
2014                     string result = indent ~ "sequence:\n";
2015                     foreach(ref node; get!(Node[]))
2016                     {
2017                         result ~= node.debugString(level + 1);
2018                     }
2019                     return result;
2020                 case NodeID.mapping:
2021                     string result = indent ~ "mapping:\n";
2022                     foreach(ref pair; get!(Node.Pair[]))
2023                     {
2024                         result ~= indent ~ " pair\n";
2025                         result ~= pair.key.debugString(level + 2);
2026                         result ~= pair.value.debugString(level + 2);
2027                     }
2028                     return result;
2029                 case NodeID.scalar:
2030                     return text(indent, "scalar(", (convertsTo!string ? get!string : type.to!string), ")\n");
2031             }
2032         }
2033 
2034 
2035     public:
2036 
2037         // Determine if the value can be converted to specified type.
2038         @property bool convertsTo(T)() const
2039         {
2040             static if (staticIndexOf!(T, Value.AllowedTypes) >= 0)
2041                 if (value_._is!T)
2042                     return true;
2043 
2044             // Every type allowed in Value should be convertible to string.
2045             static if(isSomeString!T)        {return true;}
2046             else static if(isFloatingPoint!T){return type == NodeType.integer || type == NodeType.decimal;}
2047             else static if(isIntegral!T)     {return type == NodeType.integer;}
2048             else static if(is(Unqual!T==bool)){return type == NodeType.boolean;}
2049             else                             {return false;}
2050         }
2051         /**
2052         * Sets the style of this node when dumped.
2053         *
2054         * Params: style = Any valid style.
2055         */
2056         void setStyle(CollectionStyle style) @safe
2057         {
2058             if (nodeID != NodeID.mapping && nodeID != NodeID.sequence)
2059                 throw new NodeException("Cannot set collection style for non-collection nodes", startMark_);
2060             collectionStyle = style;
2061         }
2062         /// Ditto
2063         void setStyle(ScalarStyle style) @safe
2064         {
2065             if (nodeID != NodeID.scalar)
2066                 throw new NodeException("Cannot set scalar style for non-scalar nodes", startMark_);
2067             scalarStyle = style;
2068         }
2069         ///
2070         @safe unittest
2071         {
2072             import std.array: Appender;
2073             import dyaml.dumper;
2074             auto stream = new Appender!string();
2075             auto node = Node([1, 2, 3, 4, 5]);
2076             node.setStyle(CollectionStyle.block);
2077 
2078             auto dumper = dumper();
2079             dumper.dump(stream, node);
2080         }
2081         ///
2082         @safe unittest
2083         {
2084             import std.array: Appender;
2085             import dyaml.dumper;
2086             auto stream = new Appender!string();
2087             auto node = Node(4);
2088             node.setStyle(ScalarStyle.literal);
2089 
2090             auto dumper = dumper();
2091             dumper.dump(stream, node);
2092         }
2093         @safe unittest
2094         {
2095             assertThrown!NodeException(Node(4).setStyle(CollectionStyle.block));
2096             assertThrown!NodeException(Node([4]).setStyle(ScalarStyle.literal));
2097         }
2098         @safe unittest
2099         {
2100             import std.array: Appender;
2101             import dyaml.dumper;
2102             {
2103                 auto stream = new Appender!string();
2104                 auto node = Node([1, 2, 3, 4, 5]);
2105                 node.setStyle(CollectionStyle.block);
2106                 auto dumper = dumper();
2107                 dumper.explicitEnd = false;
2108                 dumper.explicitStart = false;
2109                 dumper.YAMLVersion = null;
2110                 dumper.dump(stream, node);
2111 
2112                 //Block style should start with a hyphen.
2113                 assert(stream.data[0] == '-');
2114             }
2115             {
2116                 auto stream = new Appender!string();
2117                 auto node = Node([1, 2, 3, 4, 5]);
2118                 node.setStyle(CollectionStyle.flow);
2119                 auto dumper = dumper();
2120                 dumper.explicitEnd = false;
2121                 dumper.explicitStart = false;
2122                 dumper.YAMLVersion = null;
2123                 dumper.dump(stream, node);
2124 
2125                 //Flow style should start with a bracket.
2126                 assert(stream.data[0] == '[');
2127             }
2128             {
2129                 auto stream = new Appender!string();
2130                 auto node = Node(1);
2131                 node.setStyle(ScalarStyle.singleQuoted);
2132                 auto dumper = dumper();
2133                 dumper.explicitEnd = false;
2134                 dumper.explicitStart = false;
2135                 dumper.YAMLVersion = null;
2136                 dumper.dump(stream, node);
2137 
2138                 assert(stream.data == "!!int '1'\n");
2139             }
2140             {
2141                 auto stream = new Appender!string();
2142                 auto node = Node(1);
2143                 node.setStyle(ScalarStyle.doubleQuoted);
2144                 auto dumper = dumper();
2145                 dumper.explicitEnd = false;
2146                 dumper.explicitStart = false;
2147                 dumper.YAMLVersion = null;
2148                 dumper.dump(stream, node);
2149 
2150                 assert(stream.data == "!!int \"1\"\n");
2151             }
2152         }
2153 
2154         /++
2155         Determine if the value stored by the node is of specified type.
2156         This only works for default YAML types, not for user defined types.
2157         +/
2158         @property bool _is(T)() const
2159         {
2160             return value_._is!T;
2161         }
2162 
2163     private:
2164 
2165         // Implementation of contains() and containsKey().
2166         bool contains_(T, Flag!"key" key, string func)(T rhs) const
2167         {
2168             final switch (nodeID)
2169             {
2170                 case NodeID.mapping:
2171                     return findPair!(T, key)(rhs) >= 0;
2172                 case NodeID.sequence:
2173                     static if(!key)
2174                     {
2175                         foreach(ref node; getValue!(Node[]))
2176                         {
2177                             if(node == rhs){return true;}
2178                         }
2179                         return false;
2180                     }
2181                     else
2182                     {
2183                         throw new NodeException(text("Trying to use ", func, "() on a ", nodeID, " node"), startMark_);
2184                     }
2185                 case NodeID.scalar:
2186                 case NodeID.invalid:
2187                     throw new NodeException(text("Trying to use ", func, "() on a ", nodeID, " node"), startMark_);
2188             }
2189 
2190         }
2191 
2192         // Implementation of remove() and removeAt()
2193         void remove_(T, Flag!"key" key, string func)(T rhs)
2194         {
2195             static void removeElem(E, I)(ref Node node, I index)
2196             {
2197                 auto elems = node.getValue!(E[]);
2198                 foreach(i; cast(size_t)index + 1 .. elems.length)
2199                     elems[i - 1] = elems[i];
2200                 elems.length = elems.length - 1;
2201                 node.setValue(elems);
2202             }
2203 
2204             final switch (nodeID)
2205             {
2206                 case NodeID.mapping:
2207                     const index = findPair!(T, key)(rhs);
2208                     if(index >= 0){removeElem!Pair(this, index);}
2209                     break;
2210                 case NodeID.sequence:
2211                     static long getIndex(ref Node node, ref T rhs)
2212                     {
2213                         foreach(idx, ref elem; node.get!(Node[]))
2214                         {
2215                             if(elem.convertsTo!T && elem.as!(T, No.stringConversion) == rhs)
2216                             {
2217                                 return idx;
2218                             }
2219                         }
2220                         return -1;
2221                     }
2222 
2223                     const index = select!key(rhs, getIndex(this, rhs));
2224 
2225                     // This throws if the index is not integral.
2226                     checkSequenceIndex(index);
2227 
2228                     static if(isIntegral!(typeof(index))){removeElem!Node(this, index); break; }
2229                     else                                 {assert(false, "Non-integral sequence index");}
2230                 case NodeID.scalar:
2231                 case NodeID.invalid:
2232                     throw new NodeException(text("Trying to " ~ func ~ "() from a ", nodeID, " node"), startMark_);
2233             }
2234         }
2235 
2236         // Get index of pair with key (or value, if key is false) matching index.
2237         // Cannot be inferred @safe due to https://issues.dlang.org/show_bug.cgi?id=16528
2238         sizediff_t findPair(T, Flag!"key" key = Yes.key)(const ref T index) const @safe
2239         {
2240             const pairs = getValue!(Pair[])();
2241             const(Node)* node;
2242             foreach(idx, ref const(Pair) pair; pairs)
2243             {
2244                 static if(key){node = &pair.key;}
2245                 else          {node = &pair.value;}
2246 
2247                 static if (staticIndexOf!(T, Value.AllowedTypes) >= 0)
2248                     const isT = node._is!T;
2249                 else
2250                     enum isT = false;
2251                 const bool typeMatch = isFloatingPoint!T && (node.type == NodeType.integer || node.type == NodeType.decimal) ||
2252                                  isIntegral!T && node.type == NodeType.integer ||
2253                                  is(Unqual!T==bool) && node.type == NodeType.boolean ||
2254                                  isSomeString!T && node.type == NodeType..string ||
2255                                  isT;
2256                 if(typeMatch && *node == index)
2257                 {
2258                     return idx;
2259                 }
2260             }
2261             return -1;
2262         }
2263 
2264         // Check if index is integral and in range.
2265         void checkSequenceIndex(T)(T index) const
2266         {
2267             assert(nodeID == NodeID.sequence, "checkSequenceIndex() called on a non-sequence node");
2268 
2269             static if(!isIntegral!T)
2270             {
2271                 throw new NodeException("Indexing a sequence with a non-integral type.", startMark_);
2272             }
2273             else
2274             {
2275                 if (!(index >= 0 && index < getValue!(Node[]).length))
2276                     throw new NodeException(text("Sequence index out of range: ", index), startMark_);
2277             }
2278         }
2279         // Safe wrapper for getting a value out of the variant.
2280         inout(T) getValue(T)() @trusted inout
2281         {
2282             return value_.get!T;
2283         }
2284         // Safe wrapper for coercing a value out of the variant.
2285         inout(T) coerceValue(T)() @trusted inout
2286         {
2287             import mir.conv: to;
2288             return value_.tryVisit!(to!T);
2289         }
2290         // Safe wrapper for setting a value for the variant.
2291         void setValue(T)(T value) @trusted
2292         {
2293             static if (allowed!T)
2294             {
2295                 value_ = Value(value);
2296             }
2297             else
2298             {
2299                 auto tmpNode = cast(Node)value;
2300                 tag_ = tmpNode.tag;
2301                 scalarStyle = tmpNode.scalarStyle;
2302                 collectionStyle = tmpNode.collectionStyle;
2303                 value_ = tmpNode.value_;
2304             }
2305         }
2306 
2307         /// Serialization support for mir-ion and asdf
2308         public void serialize(S)(ref S serializer) const @safe
2309         {
2310             value_.visit!(
2311                 (YAMLNull v) {
2312                     serializer.putValue(null);
2313                 },
2314                 (YAMLMerge v) {
2315                     serializer.putValue("tag:yaml.org,2002:merge");
2316                 },
2317                 (bool v) {
2318                     serializer.putValue(v);
2319                 },
2320                 (long v) {
2321                     serializer.putValue(v);
2322                 },
2323                 (double v) {
2324                     serializer.putValue(v);
2325                 },
2326                 (const(ubyte)[] v) {
2327                     import mir.lob: Blob;
2328                     serializer.putValue(Blob(v));
2329                 },
2330                 (Timestamp v) {
2331                     serializer.putValue(v);
2332                 },
2333                 (string v) {
2334                     serializer.putValue(v);
2335                 },
2336                 (const(Node.Pair)[] v) {
2337                     auto state = serializer.structBegin(v.length);
2338                     foreach (ref e; v)
2339                     {
2340                         serializer.putKey(e.key.get!string);
2341                         e.value.serialize(serializer);
2342                     }
2343                     serializer.structEnd(state);
2344                 },
2345                 (const(Node)[] v) {
2346                     auto state = serializer.listBegin(v.length);
2347                     foreach (ref e; v)
2348                     {
2349                         serializer.elemBegin;
2350                         e.serialize(serializer);
2351                     }
2352                     serializer.listEnd(state);
2353                 },
2354             );
2355         }
2356 
2357         /// Deserialization support for mir-ion
2358         public auto deserializeFromIon(IonDescribedValue)(scope const(char[])[] symbolTable, IonDescribedValue value) @trusted {
2359             import mir.ndslice.topology: map;
2360             import mir.array.allocation: array;
2361             return deserializeFromIon(symbolTable.map!idup.array, value);
2362         }
2363 
2364         /// ditto
2365         public auto deserializeFromIon(IonDescribedValue)(const(string)[] symbolTable, IonDescribedValue value) @trusted {
2366             import mir.ion.exception;
2367             import mir.ion.type_code;
2368             import mir.ion.value;
2369             import mir.lob;
2370             import mir.timestamp;
2371 
2372             if (value == null) {
2373                 // Ion has typed null values
2374                 value_ = YAMLNull.init;
2375                 return null;
2376             }
2377 
2378             final switch (value.descriptor.type) {
2379 
2380                 case IonTypeCode.null_:
2381                     assert(0); // handled above
2382                 case IonTypeCode.bool_:
2383                     value_ = value.trustedGet!bool;
2384                     return null;
2385                 case IonTypeCode.uInt:
2386                 case IonTypeCode.nInt:
2387                     value_ = value.trustedGet!IonInt.get!long;
2388                     return null;
2389                 case IonTypeCode.float_:
2390                     value_ = value.trustedGet!IonFloat.get!double;
2391                     return null;
2392                 case IonTypeCode.decimal:
2393                     value_ = value.trustedGet!IonDecimal.get!double;
2394                     return null;
2395                 case IonTypeCode.timestamp:
2396                     value_ = value.trustedGet!IonTimestamp.get!Timestamp;
2397                     return null;
2398                 case IonTypeCode.symbol: {
2399                     auto symbolId = value.trustedGet!IonSymbolID.get;
2400                     if (symbolId >= symbolTable.length)
2401                         return IonErrorCode.symbolIdIsTooLargeForTheCurrentSymbolTable.ionException;
2402                     value_ = symbolTable[symbolId];
2403                     return null;
2404                 }
2405                 case IonTypeCode..string:
2406                     value_ = value.trustedGet!(const(char)[]).idup;
2407                     return null;
2408                 case IonTypeCode.clob:
2409                     throw new IonException("Mir Ion: CLOBs aren't supported in DYAML");
2410                 case IonTypeCode.blob: // as ubyte[]
2411                     value_ = value.trustedGet!Blob.data.dup;
2412                     return null;
2413                 case IonTypeCode.list: {
2414                     auto list = value.trustedGet!IonList;
2415                     auto ret = new Node[list.walkLength];
2416                     size_t i;
2417                     foreach (IonDescribedValue elem; list) {
2418                         Node val;
2419                         if (auto exc = val.deserializeFromIon(symbolTable, elem))
2420                             return exc;
2421                         ret[i++] = val;
2422                     }
2423                     value_ = ret;
2424                     return null;
2425                 }
2426                 case IonTypeCode.sexp: {
2427                     // TODO: use special types for SEXP if needed.
2428                     auto sexp = value.trustedGet!IonSexp;
2429                     auto ret = new Node[sexp.walkLength];
2430                     size_t i;
2431                     foreach (IonDescribedValue elem; sexp) {
2432                         Node val;
2433                         if (auto exc = val.deserializeFromIon(symbolTable, elem))
2434                             return exc;
2435                         ret[i++] = val;
2436                     }
2437                     value_ = ret;
2438                     return null;
2439                 }
2440                 case IonTypeCode.struct_: {
2441                     auto obj = value.trustedGet!IonStruct;
2442                     auto ret = new Pair[obj.walkLength];
2443                     size_t i;
2444                     foreach (size_t symbolId, IonDescribedValue elem; obj) {
2445                         if (symbolId >= symbolTable.length)
2446                             return IonErrorCode.symbolIdIsTooLargeForTheCurrentSymbolTable.ionException;
2447                         auto key = symbolTable[symbolId];
2448                         Node val;
2449                         if (auto exc = val.deserializeFromIon(symbolTable, elem))
2450                             return exc;
2451                         ret[i++] = Pair(key, val);
2452                     }
2453                     value_ = ret;
2454                     return null;
2455                 }
2456                 case IonTypeCode.annotations: {
2457                     IonAnnotations annotations;
2458                     value.trustedGet!IonAnnotationWrapper.unwrap(annotations, value);
2459                     Node[] annotationValues;
2460                     annotationValues.reserve(1);
2461                     foreach (size_t symbolId; annotations) {
2462                         if (symbolId >= symbolTable.length)
2463                             return IonErrorCode.symbolIdIsTooLargeForTheCurrentSymbolTable.ionException;
2464                         annotationValues ~= symbolTable[symbolId].Node;
2465                     }
2466                     Node val;
2467                     if (auto exc = val.deserializeFromIon(symbolTable, value))
2468                         return exc;
2469                     value_ = [Pair(Node(annotationValues), val)];
2470                     return null;
2471                 }
2472             }
2473         }
2474 }
2475 
2476 package:
2477 // Merge pairs into an array of pairs based on merge rules in the YAML spec.
2478 //
2479 // Any new pair will only be added if there is not already a pair
2480 // with the same key.
2481 //
2482 // Params:  pairs   = Appender managing the array of pairs to merge into.
2483 //          toMerge = Pairs to merge.
2484 void merge(Appender)(ref Appender pairs, Node.Pair[] toMerge) @safe
2485 {
2486     import mir.algorithm.iteration: all;
2487     foreach(ref pair; toMerge)
2488         if(pairs.data.all!(p => p.key != pair.key))
2489             pairs.put(pair);
2490 }
2491 
2492 enum hasNodeConstructor(T) = hasSimpleNodeConstructor!T || hasExpandedNodeConstructor!T;
2493 template hasSimpleNodeConstructor(T)
2494 {
2495     static if (is(T == struct))
2496     {
2497         enum hasSimpleNodeConstructor = is(typeof(T(Node.init)));
2498     }
2499     else static if (is(T == class))
2500     {
2501         enum hasSimpleNodeConstructor = is(typeof(new T(Node.init)));
2502     }
2503     else enum hasSimpleNodeConstructor = false;
2504 }
2505 template hasExpandedNodeConstructor(T)
2506 {
2507     static if (is(T == struct))
2508     {
2509         enum hasExpandedNodeConstructor = is(typeof(T(Node.init, "")));
2510     }
2511     else static if (is(T == class))
2512     {
2513         enum hasExpandedNodeConstructor = is(typeof(new T(Node.init, "")));
2514     }
2515     else enum hasExpandedNodeConstructor = false;
2516 }
2517 enum castableToNode(T) = (is(T == struct) || is(T == class)) && is(typeof(T.opCast!Node()) : Node);
2518 
2519 @safe unittest
2520 {
2521     import dyaml : Loader, Node;
2522 
2523     static struct Foo
2524     {
2525         string[] bars;
2526 
2527         this(const Node node)
2528         {
2529             foreach(value; node["bars"].sequence)
2530             {
2531                 bars ~= value.as!string;
2532             }
2533         }
2534     }
2535 
2536     Loader.fromString(`{ bars: ["a", "b"] }`)
2537           .load
2538           .as!(Foo);
2539 }
2540 @safe unittest
2541 {
2542     import dyaml : Loader, Node;
2543     import std : split, to;
2544 
2545     static class MyClass
2546     {
2547         int x, y, z;
2548 
2549         this(Node node)
2550         {
2551             auto parts = node.as!string().split(":");
2552             x = parts[0].to!int;
2553             y = parts[1].to!int;
2554             z = parts[2].to!int;
2555         }
2556     }
2557 
2558     auto loader = Loader.fromString(`"1:2:3"`);
2559     Node node = loader.load();
2560     auto mc = node.get!MyClass;
2561 }
2562 @safe unittest
2563 {
2564     import dyaml : Loader, Node;
2565     import std : split, to;
2566 
2567     static class MyClass
2568     {
2569         int x, y, z;
2570 
2571         this(Node node)
2572         {
2573             auto parts = node.as!string().split(":");
2574             x = parts[0].to!int;
2575             y = parts[1].to!int;
2576             z = parts[2].to!int;
2577         }
2578     }
2579 
2580     auto loader = Loader.fromString(`"1:2:3"`);
2581     const node = loader.load();
2582     auto mc = node.get!MyClass;
2583 }