From 2b6c417006987296fb93856cfdbc7e69dbdccb8c Mon Sep 17 00:00:00 2001 From: Michail Pevnev Date: Tue, 1 Aug 2017 14:13:35 +0300 Subject: [PATCH 1/4] Added ranges-iterators to the Node struct. Added methods: sequence, mapping (iterates over pairs), mappingKeys, mappingValues. --- source/dyaml/node.d | 192 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/source/dyaml/node.d b/source/dyaml/node.d index 9d1a2db..42caa7b 100644 --- a/source/dyaml/node.d +++ b/source/dyaml/node.d @@ -1041,6 +1041,198 @@ struct Node } } + /** Return a range object iterating over a sequence, getting each + * element as T. + * + * If T is Node, simply iterate over the nodes in the sequence. + * Otherwise, convert each node to T during iteration. + * + * Throws: NodeException if the node is not a sequence or an element + * could not be converted to specified type. + */ + auto sequence(T = Node)() @trusted + { + enforce(isSequence, + new Error("Trying to 'sequence'-iterate over a " ~ nodeTypeString ~ " node", + startMark_)); + struct Range + { + Node[] subnodes; + size_t position; + + this(Node[] nodes) + { + subnodes = nodes; + position = 0; + } + + /* Input range functionality. */ + bool empty() { return position >= subnodes.length; } + void popFront() { position++; } + T front() + { + static if (is(Unqual!T == Node)) + return subnodes[position]; + else + return subnodes[position].as!T; + } + + /* Forward range functionality. */ + Range save() { return this; } + + /* Bidirectional range functionality. */ + void popBack() { subnodes = subnodes[0 .. $ - 1]; } + T back() + { + static if (is(Unqual!T == Node)) + return subnodes[$ - 1]; + else + return subnodes[$ - 1].as!T; + } + + /* Random-access range functionality. */ + T opIndex(size_t index) + { + static if (is(Unqual!T == Node)) + return subnodes[index]; + else + return subnodes[index].as!T; + } + } + return Range(get!(Node[])); + } + unittest + { + writeln("D:YAML Node sequence unittest"); + + Node n1 = Node([1, 2, 3, 4]); + int[int] array; + Node n2 = Node(array); + + auto r = n1.sequence!int.map!(x => x * 10); + assert(r.equal([10, 20, 30, 40])); + + assertThrown(n2.sequence); + } + + /** Return a range object iterating over mapping's pairs. + * + * Throws: NodeException if the node is not a mapping. + * + */ + auto mapping() @trusted + { + enforce(isMapping, + new Error("Trying to 'mapping'-iterate over a " + ~ nodeTypeString ~ " node", startMark_)); + struct Range + { + Node.Pair[] pairs; + size_t position; + + this(Node.Pair[] pairs) + { + this.pairs = pairs; + position = 0; + } + + /* Input range functionality. */ + bool empty() { return position >= pairs.length; } + void popFront() { position++; } + Pair front() { return pairs[position]; } + + /* Forward range functionality. */ + Range save() { return this; } + + /* Bidirectional range functionality. */ + void popBack() { pairs = pairs[0 .. $ - 1]; } + Pair back() { return pairs[$ - 1]; } + + /* Random-access range functionality. */ + Pair opIndex(size_t index) { return pairs[index]; } + } + return Range(get!(Node.Pair[])); + } + unittest + { + writeln("D:YAML Node mapping unittest"); + + int[int] array; + Node n = Node(array); + n[1] = "foo"; + n[2] = "bar"; + n[3] = "baz"; + + string[int] test; + foreach (pair; n.mapping) + test[pair.key.as!int] = pair.value.as!string; + + assert(test[1] == "foo"); + assert(test[2] == "bar"); + assert(test[3] == "baz"); + } + + /** Return a range object iterating over mapping's keys. + * + * If K is Node, simply iterate over the keys in the mapping. + * Otherwise, convert each key to T during iteration. + * + * Throws: NodeException if the nodes is not a mapping or an element + * could not be converted to specified type. + */ + auto mappingKeys(K = Node)() @trusted + { + enforce(isMapping, + new Error("Trying to 'mappingKeys'-iterate over a " + ~ nodeTypeString ~ " node", startMark_)); + static if (is(Unqual!K == Node)) + return mapping.map!(pair => pair.key); + else + return mapping.map!(pair => pair.key.as!K); + } + unittest + { + writeln("D:YAML Node mappingKeys unittest"); + + int[int] array; + Node m1 = Node(array); + m1["foo"] = 2; + m1["bar"] = 3; + + assert(m1.mappingKeys.equal(["foo", "bar"])); + } + + /** Return a range object iterating over mapping's values. + * + * If V is Node, simply iterate over the values in the mapping. + * Otherwise, convert each key to V during iteration. + * + * Throws: NodeException if the nodes is not a mapping or an element + * could not be converted to specified type. + */ + auto mappingValues(V = Node)() @trusted + { + enforce(isMapping, + new Error("Trying to 'mappingValues'-iterate over a " + ~ nodeTypeString ~ " node", startMark_)); + static if (is(Unqual!V == Node)) + return mapping.map!(pair => pair.value); + else + return mapping.map!(pair => pair.value.as!V); + } + unittest + { + writeln("D:YAML Node mappingValues unittest"); + + int[int] array; + Node m1 = Node(array); + m1["foo"] = 2; + m1["bar"] = 3; + + assert(m1.mappingValues.equal([2, 3])); + } + + /** Foreach over a sequence, getting each element as T. * * If T is Node, simply iterate over the nodes in the sequence. From c123e6afeeed4f6c03b56593ad53b83022507778 Mon Sep 17 00:00:00 2001 From: Michail Pevnev Date: Tue, 1 Aug 2017 14:37:28 +0300 Subject: [PATCH 2/4] Fixed new ranges not being random-access ranges. Forgot to add 'length' property. --- source/dyaml/node.d | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/source/dyaml/node.d b/source/dyaml/node.d index 42caa7b..cbae8c6 100644 --- a/source/dyaml/node.d +++ b/source/dyaml/node.d @@ -1067,9 +1067,9 @@ struct Node } /* Input range functionality. */ - bool empty() { return position >= subnodes.length; } + bool empty() @property { return position >= subnodes.length; } void popFront() { position++; } - T front() + T front() @property { static if (is(Unqual!T == Node)) return subnodes[position]; @@ -1091,6 +1091,7 @@ struct Node } /* Random-access range functionality. */ + size_t length() const @property { return subnodes.length; } T opIndex(size_t index) { static if (is(Unqual!T == Node)) @@ -1098,6 +1099,11 @@ struct Node else return subnodes[index].as!T; } + + static assert(isInputRange!Range); + static assert(isForwardRange!Range); + static assert(isBidirectionalRange!Range); + static assert(isRandomAccessRange!Range); } return Range(get!(Node[])); } @@ -1149,7 +1155,13 @@ struct Node Pair back() { return pairs[$ - 1]; } /* Random-access range functionality. */ + size_t length() const @property { return pairs.length; } Pair opIndex(size_t index) { return pairs[index]; } + + static assert(isInputRange!Range); + static assert(isForwardRange!Range); + static assert(isBidirectionalRange!Range); + static assert(isRandomAccessRange!Range); } return Range(get!(Node.Pair[])); } From e313fa1fbf4e492b787104241684ce2b4f2062c1 Mon Sep 17 00:00:00 2001 From: Michail Pevnev Date: Tue, 1 Aug 2017 18:08:12 +0300 Subject: [PATCH 3/4] Fix #28 From 6a381528a33dac75d615d26aa8bb6f12ac75c368 Mon Sep 17 00:00:00 2001 From: Michail Pevnev Date: Tue, 1 Aug 2017 18:31:34 +0300 Subject: [PATCH 4/4] Added 'enforce' checks on ranges' emptiness. --- source/dyaml/node.d | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/source/dyaml/node.d b/source/dyaml/node.d index cbae8c6..368cfe9 100644 --- a/source/dyaml/node.d +++ b/source/dyaml/node.d @@ -1068,9 +1068,16 @@ struct Node /* Input range functionality. */ bool empty() @property { return position >= subnodes.length; } - void popFront() { position++; } + + void popFront() + { + enforce(!empty, "Attempted to popFront an empty sequence"); + position++; + } + T front() @property { + enforce(!empty, "Attempted to take the front of an empty sequence"); static if (is(Unqual!T == Node)) return subnodes[position]; else @@ -1081,9 +1088,15 @@ struct Node Range save() { return this; } /* Bidirectional range functionality. */ - void popBack() { subnodes = subnodes[0 .. $ - 1]; } + void popBack() + { + enforce(!empty, "Attempted to popBack an empty sequence"); + subnodes = subnodes[0 .. $ - 1]; + } + T back() { + enforce(!empty, "Attempted to take the back of an empty sequence"); static if (is(Unqual!T == Node)) return subnodes[$ - 1]; else @@ -1144,15 +1157,34 @@ struct Node /* Input range functionality. */ bool empty() { return position >= pairs.length; } - void popFront() { position++; } - Pair front() { return pairs[position]; } + + void popFront() + { + enforce(!empty, "Attempted to popFront an empty mapping"); + position++; + } + + Pair front() + { + enforce(!empty, "Attempted to take the front of an empty mapping"); + return pairs[position]; + } /* Forward range functionality. */ Range save() { return this; } /* Bidirectional range functionality. */ - void popBack() { pairs = pairs[0 .. $ - 1]; } - Pair back() { return pairs[$ - 1]; } + void popBack() + { + enforce(!empty, "Attempted to popBack an empty mapping"); + pairs = pairs[0 .. $ - 1]; + } + + Pair back() + { + enforce(!empty, "Attempted to take the back of an empty mapping"); + return pairs[$ - 1]; + } /* Random-access range functionality. */ size_t length() const @property { return pairs.length; } @@ -1244,7 +1276,6 @@ struct Node assert(m1.mappingValues.equal([2, 3])); } - /** Foreach over a sequence, getting each element as T. * * If T is Node, simply iterate over the nodes in the sequence.