From 7192503fe61f3c370f2126a06ebe030402afde30 Mon Sep 17 00:00:00 2001 From: Ferdinand Majerech Date: Sat, 20 Aug 2011 22:15:20 +0200 Subject: [PATCH] Implemented a Tag struct taking as little memory as possible. Removed endMark from Node to keep it in 32 bytes on 64bit. This will result in slightly worse debugging messages, but we still have the start position of a node. Tag is needed for better compliance with the spec and emitting support for multiple tags with the same D data type. --- dyaml/composer.d | 4 +- dyaml/constructor.d | 7 +- dyaml/node.d | 179 ++++++++++++++++++++++++----------------- dyaml/tag.d | 55 +++++++++++++ test/src/compare.d | 15 +++- test/src/constructor.d | 3 +- 6 files changed, 180 insertions(+), 83 deletions(-) create mode 100644 dyaml/tag.d 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) {