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