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