diff --git a/dyaml/composer.d b/dyaml/composer.d index 36d701a..70c363e 100644 --- a/dyaml/composer.d +++ b/dyaml/composer.d @@ -265,7 +265,7 @@ final class Composer enforce(node.isType!(Node.Pair[]), new ConstructorException("While constructing a mapping, " ~ "expected a mapping for merging, but found" - ~ node.value_.type.toString() ~ + ~ node.typeString() ~ "NOTE: line/column shows topmost parent " "to which the content is being merged", startMark, endMark)); @@ -278,7 +278,7 @@ final class Composer throw new ConstructorException("While constructing a mapping, " ~ "expected a mapping or a list of mappings for " "merging, but found: " - ~ root.value_.type.toString() ~ + ~ root.typeString() ~ "NOTE: line/column shows topmost parent " "to which the content is being merged", startMark, endMark); diff --git a/dyaml/constructor.d b/dyaml/constructor.d index 55df342..943820f 100644 --- a/dyaml/constructor.d +++ b/dyaml/constructor.d @@ -25,6 +25,7 @@ import std.utf; import dyaml.node; import dyaml.exception; +import dyaml.tag; import dyaml.token; @@ -179,7 +180,7 @@ final class Constructor enforce((tag in fromScalar_) !is null, new ConstructorException("Could not determine a constructor from " "scalar for tag " ~ tag, start, end)); - return Node(fromScalar_[tag](start, end, value), start, end); + return Node(fromScalar_[tag](start, end, value), start, Tag(tag)); } /* @@ -196,7 +197,7 @@ final class Constructor enforce((tag in fromSequence_) !is null, new ConstructorException("Could not determine a constructor from " "sequence for tag " ~ tag, start, end)); - return Node(fromSequence_[tag](start, end, value), start, end); + return Node(fromSequence_[tag](start, end, value), start, Tag(tag)); } /* @@ -213,7 +214,7 @@ final class Constructor enforce((tag in fromMapping_) !is null, new ConstructorException("Could not determine a constructor from " "mapping for tag " ~ tag, start, end)); - return Node(fromMapping_[tag](start, end, value), start, end); + return Node(fromMapping_[tag](start, end, value), start, Tag(tag)); } } diff --git a/dyaml/node.d b/dyaml/node.d index 87e1a9a..2024e8d 100644 --- a/dyaml/node.d +++ b/dyaml/node.d @@ -22,6 +22,7 @@ import std.variant; import dyaml.event; import dyaml.exception; +import dyaml.tag; ///Exception thrown at node related errors. @@ -33,11 +34,10 @@ class NodeException : YAMLException * * Params: msg = Error message. * start = Start position of the node. - * end = End position of the node. */ - this(string msg, Mark start, Mark end) + this(string msg, Mark start) { - super(msg ~ "\nstart:" ~ start.toString() ~ "\nend:" ~ end.toString()); + super(msg ~ "\nNode at:" ~ start.toString()); } } @@ -63,7 +63,7 @@ private abstract class YAMLObject @property TypeInfo type() const; ///Test for equality with another YAMLObject. - bool equals(YAMLObject rhs); + bool equals(const YAMLObject rhs) const; } //Stores a user defined YAML data type. @@ -81,13 +81,14 @@ private class YAMLContainer(T) : YAMLObject @property override TypeInfo type() const {return typeid(T);} //Test for equality with another YAMLObject. - override bool equals(YAMLObject rhs) + override bool equals(const YAMLObject rhs) const { if(rhs.type !is typeid(T)){return false;} return value_ == (cast(YAMLContainer)rhs).value_; } } + /** * YAML node. * @@ -97,7 +98,6 @@ private class YAMLContainer(T) : YAMLObject */ struct Node { - public: ///Pair of YAML nodes, used in mappings. struct Pair @@ -113,19 +113,19 @@ struct Node return key == rhs.key && value == rhs.value; } } + package: //YAML value type. alias Algebraic!(YAMLNull, YAMLMerge, bool, long, real, ubyte[], SysTime, string, Node.Pair[], Node[], YAMLObject) Value; + private: //Stored value. Value value_; - - private: ///Start position of the node. Mark startMark_; - ///End position of the node. - Mark endMark_; + ///Tag of the node. + Tag tag_; public: ///Is this node valid (initialized)? @@ -165,59 +165,7 @@ struct Node */ bool opEquals(T)(ref T rhs) { - static if(is(T == Node)) - { - if(!isValid){return !rhs.isValid;} - if(!rhs.isValid || (value_.type !is rhs.value_.type)) - { - return false; - } - if(isSequence) - { - auto seq1 = get!(Node[]); - auto seq2 = rhs.get!(Node[]); - if(seq1.length != seq2.length){return false;} - foreach(node; 0 .. seq1.length) - { - if(seq1[node] != seq2[node]){return false;} - } - return true; - } - if(isMapping) - { - auto map1 = get!(Node.Pair[]); - auto map2 = rhs.get!(Node.Pair[]); - if(map1.length != map2.length){return false;} - foreach(pair; 0 .. map1.length) - { - if(!map1[pair].equals(map2[pair])){return false;} - } - return true; - } - if(isScalar) - { - if(isUserType) - { - if(!rhs.isUserType){return false;} - return get!YAMLObject.equals(rhs.get!YAMLObject); - } - if(isFloat) - { - if(!rhs.isFloat){return false;} - real r1 = get!real; - real r2 = rhs.get!real; - if(isNaN(r1)){return isNaN(r2);} - return r1 == r2; - } - else{return value_ == rhs.value_;} - } - assert(false, "Unknown kind of node"); - } - else - { - try{return rhs == get!T;} - catch(NodeException e){return false;} - } + return valueEquals(rhs); } /** @@ -305,7 +253,7 @@ struct Node catch(VariantException e) { throw new NodeException("Unable to convert node value to a string", - startMark_, endMark_); + startMark_); } } else static if(isFloatingPoint!T) @@ -331,7 +279,7 @@ struct Node { throw new NodeException("Integer value out of range of type " ~ typeid(T).toString ~ "Value: " ~ - to!string(temp), startMark_, endMark_); + to!string(temp), startMark_); } target = to!T(temp); return; @@ -340,8 +288,8 @@ struct Node else { //Can't get the value. - throw new NodeException("Node has unexpected type " ~ value_.type.toString ~ - ". Expected " ~ typeid(T).toString, startMark_, endMark_); + throw new NodeException("Node has unexpected type " ~ typeString ~ + ". Expected " ~ typeid(T).toString, startMark_); } } @@ -359,7 +307,7 @@ struct Node if(isSequence) {return get!(Node[]).length;} else if(isMapping){return get!(Pair[]).length;} throw new NodeException("Trying to get length of a node that is not a collection", - startMark_, endMark_); + startMark_); } /** @@ -387,13 +335,13 @@ struct Node auto nodes = value_.get!(Node[]); enforce(index >= 0 && index < nodes.length, new NodeException("Index to a sequence out of range: " - ~ to!string(index), startMark_, endMark_)); + ~ to!string(index), startMark_)); return nodes[index]; } else { throw new NodeException("Indexing a sequence with a non-integer type.", - startMark_, endMark_); + startMark_); } } else if(isMapping) @@ -418,10 +366,10 @@ struct Node } throw new NodeException("Mapping index not found" ~ isSomeString!T ? ": " ~ to!string(index) : "", - startMark_, endMark_); + startMark_); } throw new NodeException("Trying to index node that does not support indexing", - startMark_, endMark_); + startMark_); } unittest { @@ -465,7 +413,7 @@ struct Node { enforce(isSequence, new NodeException("Trying to iterate over a node that is not a sequence", - startMark_, endMark_)); + startMark_)); int result = 0; foreach(ref node; get!(Node[])) @@ -522,7 +470,7 @@ struct Node { enforce(isMapping, new NodeException("Trying to iterate over a node that is not a mapping", - startMark_, endMark_)); + startMark_)); int result = 0; foreach(ref pair; get!(Node.Pair[])) @@ -604,6 +552,78 @@ struct Node } package: + /* + * Construct a node from raw data. + * + * Params: value = Value of the node. + * startMark = Start position of the node in file. + * tag = Tag of the node. + */ + this(Value value, in Mark startMark = Mark(), in Tag tag = Tag("DUMMY_TAG")) + { + value_ = value; + startMark_ = startMark; + tag_ = tag; + } + + ///Equality test without taking tag into account. + bool valueEquals(T)(ref T rhs) + { + static if(is(T == Node)) + { + if(!isValid){return !rhs.isValid;} + if(!rhs.isValid || !hasEqualType(rhs)) + { + return false; + } + if(isSequence) + { + auto seq1 = get!(Node[]); + auto seq2 = rhs.get!(Node[]); + if(seq1.length != seq2.length){return false;} + foreach(node; 0 .. seq1.length) + { + if(seq1[node] != seq2[node]){return false;} + } + return true; + } + if(isMapping) + { + auto map1 = get!(Node.Pair[]); + auto map2 = rhs.get!(Node.Pair[]); + if(map1.length != map2.length){return false;} + foreach(pair; 0 .. map1.length) + { + if(!map1[pair].equals(map2[pair])){return false;} + } + return true; + } + if(isScalar) + { + if(isUserType) + { + if(!rhs.isUserType){return false;} + return get!YAMLObject.equals(rhs.get!YAMLObject); + } + if(isFloat) + { + if(!rhs.isFloat){return false;} + real r1 = get!real; + real r2 = rhs.get!real; + if(isNaN(r1)){return isNaN(r2);} + return r1 == r2; + } + else{return value_ == rhs.value_;} + } + assert(false, "Unknown kind of node"); + } + else + { + try{return rhs == get!T;} + catch(NodeException e){return false;} + } + } + /* * Get a string representation of the node tree. Used for debugging. * @@ -641,7 +661,7 @@ struct Node if(isScalar) { return indent ~ "scalar(" ~ - (convertsTo!string ? get!string : value_.type.toString) ~ ")\n"; + (convertsTo!string ? get!string : typeString) ~ ")\n"; } assert(false); } @@ -652,6 +672,9 @@ struct Node return Value(cast(YAMLObject)new YAMLContainer!T(value)); } + //Return string representation of the type of the node. + @property string typeString() const {return to!string(value_.type);} + private: /* * Determine if the value stored by the node is of specified type. @@ -666,6 +689,12 @@ struct Node ///Is the value a floating point number of some kind? alias isType!real isFloat; + ///Does given node have the same type as this node? + bool hasEqualType(ref Node node) + { + return value_.type is node.value_.type; + } + //Determine if the value can be converted to specified type. bool convertsTo(T)() { diff --git a/dyaml/tag.d b/dyaml/tag.d new file mode 100644 index 0000000..c6b8f5a --- /dev/null +++ b/dyaml/tag.d @@ -0,0 +1,55 @@ + +// Copyright Ferdinand Majerech 2011. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +///YAML tag. +module dyaml.tag; + +import core.sync.mutex; + + +///YAML tag (data type) struct. Encapsulates a tag to save memory and speed-up comparison. +struct Tag +{ + private: + ///Index of the tag in tags_. + uint index_; + + /** + * All known tags are in this array. + * + * Note that this is not shared among threads. + * Working the same YAML file in multiple threads is NOT safe with D:YAML. + */ + static string[] tags_; + + public: + ///Construct a tag from a string representation. + this(string tag) + { + foreach(uint index, knownTag; tags_) + { + if(tag == knownTag) + { + index_ = index; + return; + } + } + index_ = cast(uint)tags_.length; + tags_ ~= tag; + } + + ///Get string representation of the tag. + string toString() const + { + return tags_[index_]; + } + + ///Test for equality with another tag. + bool opEquals(const ref Tag tag) const + { + return tag.index_ == index_; + } +} diff --git a/test/src/compare.d b/test/src/compare.d index d35e6d1..5e10765 100644 --- a/test/src/compare.d +++ b/test/src/compare.d @@ -45,10 +45,21 @@ void testLoader(bool verbose, string dataFilename, string canonicalFilename) auto data = loadAll(dataFilename); auto canonical = loadAll(canonicalFilename); - assert(data.length == canonical.length); + assert(data.length == canonical.length, "Unequal node count"); foreach(n; 0 .. data.length) { - assert(data[n] == canonical[n]); + if(data[n] != canonical[n]) + { + if(verbose) + { + writeln("Normal value:"); + writeln(data[n].debugString); + writeln("\n"); + writeln("Canonical value:"); + writeln(canonical[n].debugString); + } + assert(false); + } } } diff --git a/test/src/constructor.d b/test/src/constructor.d index 82419f5..8d8d64d 100644 --- a/test/src/constructor.d +++ b/test/src/constructor.d @@ -11,6 +11,7 @@ import std.datetime; import std.exception; import std.path; +import dyaml.tag; import dyaml.testcommon; @@ -383,7 +384,7 @@ void testConstructor(bool verbose, string dataFilename, string codeDummy) size_t i = 0; foreach(node; loader) { - if(node != expected[base][i]) + if(!node.valueEquals(expected[base][i])) { if(verbose) {