- // SDLang-D
- // Written in the D programming language.
-
- module dub.internal.sdlang.ast;
-
- version (Have_sdlang_d) public import sdlang.ast;
- else:
-
- import std.algorithm;
- import std.array;
- import std.conv;
- import std.range;
- import std.string;
-
- version(sdlangUnittest)
- version(unittest)
- {
- import std.stdio;
- import std.exception;
- }
-
- import dub.internal.sdlang.exception;
- import dub.internal.sdlang.token;
- import dub.internal.sdlang.util;
-
- import dub.internal.dyaml.stdsumtype;
-
- class Attribute
- {
- Value value;
- Location location;
-
- private Tag _parent;
- /// Get parent tag. To set a parent, attach this Attribute to its intended
- /// parent tag by calling 'Tag.add(...)', or by passing it to
- /// the parent tag's constructor.
- @property Tag parent()
- {
- return _parent;
- }
-
- private string _namespace;
- @property string namespace()
- {
- return _namespace;
- }
- /// Not particularly efficient, but it works.
- @property void namespace(string value)
- {
- if(_parent && _namespace != value)
- {
- // Remove
- auto saveParent = _parent;
- if(_parent)
- this.remove();
-
- // Change namespace
- _namespace = value;
-
- // Re-add
- if(saveParent)
- saveParent.add(this);
- }
- else
- _namespace = value;
- }
-
- private string _name;
- /// Not including namespace. Use 'fullName' if you want the namespace included.
- @property string name()
- {
- return _name;
- }
- /// Not the most efficient, but it works.
- @property void name(string value)
- {
- if(_parent && _name != value)
- {
- _parent.updateId++;
-
- void removeFromGroupedLookup(string ns)
- {
- // Remove from _parent._attributes[ns]
- auto sameNameAttrs = _parent._attributes[ns][_name];
- auto targetIndex = sameNameAttrs.countUntil(this);
- _parent._attributes[ns][_name].removeIndex(targetIndex);
- }
-
- // Remove from _parent._tags
- removeFromGroupedLookup(_namespace);
- removeFromGroupedLookup("*");
-
- // Change name
- _name = value;
-
- // Add to new locations in _parent._attributes
- _parent._attributes[_namespace][_name] ~= this;
- _parent._attributes["*"][_name] ~= this;
- }
- else
- _name = value;
- }
-
- @property string fullName()
- {
- return _namespace==""? _name : text(_namespace, ":", _name);
- }
-
- this(string namespace, string name, Value value, Location location = Location(0, 0, 0))
- {
- this._namespace = namespace;
- this._name = name;
- this.location = location;
- this.value = value;
- }
-
- this(string name, Value value, Location location = Location(0, 0, 0))
- {
- this._namespace = "";
- this._name = name;
- this.location = location;
- this.value = value;
- }
-
- /// Removes 'this' from its parent, if any. Returns 'this' for chaining.
- /// Inefficient ATM, but it works.
- Attribute remove()
- {
- if(!_parent)
- return this;
-
- void removeFromGroupedLookup(string ns)
- {
- // Remove from _parent._attributes[ns]
- auto sameNameAttrs = _parent._attributes[ns][_name];
- auto targetIndex = sameNameAttrs.countUntil(this);
- _parent._attributes[ns][_name].removeIndex(targetIndex);
- }
-
- // Remove from _parent._attributes
- removeFromGroupedLookup(_namespace);
- removeFromGroupedLookup("*");
-
- // Remove from _parent.allAttributes
- auto allAttrsIndex = _parent.allAttributes.countUntil(this);
- _parent.allAttributes.removeIndex(allAttrsIndex);
-
- // Remove from _parent.attributeIndices
- auto sameNamespaceAttrs = _parent.attributeIndices[_namespace];
- auto attrIndicesIndex = sameNamespaceAttrs.countUntil(allAttrsIndex);
- _parent.attributeIndices[_namespace].removeIndex(attrIndicesIndex);
-
- // Fixup other indices
- foreach(ns, ref nsAttrIndices; _parent.attributeIndices)
- foreach(k, ref v; nsAttrIndices)
- if(v > allAttrsIndex)
- v--;
-
- _parent.removeNamespaceIfEmpty(_namespace);
- _parent.updateId++;
- _parent = null;
- return this;
- }
-
- override bool opEquals(Object o)
- {
- auto a = cast(Attribute)o;
- if(!a)
- return false;
-
- return
- _namespace == a._namespace &&
- _name == a._name &&
- value == a.value;
- }
-
- string toSDLString()()
- {
- Appender!string sink;
- this.toSDLString(sink);
- return sink.data;
- }
-
- void toSDLString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char))
- {
- if(_namespace != "")
- {
- sink.put(_namespace);
- sink.put(':');
- }
-
- sink.put(_name);
- sink.put('=');
- value.toSDLString(sink);
- }
- }
-
- class Tag
- {
- Location location;
- Value[] values;
-
- private Tag _parent;
- /// Get parent tag. To set a parent, attach this Tag to its intended
- /// parent tag by calling 'Tag.add(...)', or by passing it to
- /// the parent tag's constructor.
- @property Tag parent()
- {
- return _parent;
- }
-
- private string _namespace;
- @property string namespace()
- {
- return _namespace;
- }
- /// Not particularly efficient, but it works.
- @property void namespace(string value)
- {
- if(_parent && _namespace != value)
- {
- // Remove
- auto saveParent = _parent;
- if(_parent)
- this.remove();
-
- // Change namespace
- _namespace = value;
-
- // Re-add
- if(saveParent)
- saveParent.add(this);
- }
- else
- _namespace = value;
- }
-
- private string _name;
- /// Not including namespace. Use 'fullName' if you want the namespace included.
- @property string name()
- {
- return _name;
- }
- /// Not the most efficient, but it works.
- @property void name(string value)
- {
- if(_parent && _name != value)
- {
- _parent.updateId++;
-
- void removeFromGroupedLookup(string ns)
- {
- // Remove from _parent._tags[ns]
- auto sameNameTags = _parent._tags[ns][_name];
- auto targetIndex = sameNameTags.countUntil(this);
- _parent._tags[ns][_name].removeIndex(targetIndex);
- }
-
- // Remove from _parent._tags
- removeFromGroupedLookup(_namespace);
- removeFromGroupedLookup("*");
-
- // Change name
- _name = value;
-
- // Add to new locations in _parent._tags
- _parent._tags[_namespace][_name] ~= this;
- _parent._tags["*"][_name] ~= this;
- }
- else
- _name = value;
- }
-
- /// This tag's name, including namespace if one exists.
- @property string fullName()
- {
- return _namespace==""? _name : text(_namespace, ":", _name);
- }
-
- // Tracks dirtiness. This is incremented every time a change is made which
- // could invalidate existing ranges. This way, the ranges can detect when
- // they've been invalidated.
- private size_t updateId=0;
-
- this(Tag parent = null)
- {
- if(parent)
- parent.add(this);
- }
-
- this(
- string namespace, string name,
- Value[] values=null, Attribute[] attributes=null, Tag[] children=null
- )
- {
- this(null, namespace, name, values, attributes, children);
- }
-
- this(
- Tag parent, string namespace, string name,
- Value[] values=null, Attribute[] attributes=null, Tag[] children=null
- )
- {
- this._namespace = namespace;
- this._name = name;
-
- if(parent)
- parent.add(this);
-
- this.values = values;
- this.add(attributes);
- this.add(children);
- }
-
- private Attribute[] allAttributes; // In same order as specified in SDL file.
- private Tag[] allTags; // In same order as specified in SDL file.
- private string[] allNamespaces; // In same order as specified in SDL file.
-
- private size_t[][string] attributeIndices; // allAttributes[ attributes[namespace][i] ]
- private size_t[][string] tagIndices; // allTags[ tags[namespace][i] ]
-
- private Attribute[][string][string] _attributes; // attributes[namespace or "*"][name][i]
- private Tag[][string][string] _tags; // tags[namespace or "*"][name][i]
-
- /// Adds a Value, Attribute, Tag (or array of such) as a member/child of this Tag.
- /// Returns 'this' for chaining.
- /// Throws 'SDLangValidationException' if trying to add an Attribute or Tag
- /// that already has a parent.
- Tag add(Value val)
- {
- values ~= val;
- updateId++;
- return this;
- }
-
- ///ditto
- Tag add(Value[] vals)
- {
- foreach(val; vals)
- add(val);
-
- return this;
- }
-
- ///ditto
- Tag add(Attribute attr)
- {
- if(attr._parent)
- {
- throw new SDLangValidationException(
- "Attribute is already attached to a parent tag. "~
- "Use Attribute.remove() before adding it to another tag."
- );
- }
-
- if(!allNamespaces.canFind(attr._namespace))
- allNamespaces ~= attr._namespace;
-
- attr._parent = this;
-
- allAttributes ~= attr;
- attributeIndices[attr._namespace] ~= allAttributes.length-1;
- _attributes[attr._namespace][attr._name] ~= attr;
- _attributes["*"] [attr._name] ~= attr;
-
- updateId++;
- return this;
- }
-
- ///ditto
- Tag add(Attribute[] attrs)
- {
- foreach(attr; attrs)
- add(attr);
-
- return this;
- }
-
- ///ditto
- Tag add(Tag tag)
- {
- if(tag._parent)
- {
- throw new SDLangValidationException(
- "Tag is already attached to a parent tag. "~
- "Use Tag.remove() before adding it to another tag."
- );
- }
-
- if(!allNamespaces.canFind(tag._namespace))
- allNamespaces ~= tag._namespace;
-
- tag._parent = this;
-
- allTags ~= tag;
- tagIndices[tag._namespace] ~= allTags.length-1;
- _tags[tag._namespace][tag._name] ~= tag;
- _tags["*"] [tag._name] ~= tag;
-
- updateId++;
- return this;
- }
-
- ///ditto
- Tag add(Tag[] tags)
- {
- foreach(tag; tags)
- add(tag);
-
- return this;
- }
-
- /// Removes 'this' from its parent, if any. Returns 'this' for chaining.
- /// Inefficient ATM, but it works.
- Tag remove()
- {
- if(!_parent)
- return this;
-
- void removeFromGroupedLookup(string ns)
- {
- // Remove from _parent._tags[ns]
- auto sameNameTags = _parent._tags[ns][_name];
- auto targetIndex = sameNameTags.countUntil(this);
- _parent._tags[ns][_name].removeIndex(targetIndex);
- }
-
- // Remove from _parent._tags
- removeFromGroupedLookup(_namespace);
- removeFromGroupedLookup("*");
-
- // Remove from _parent.allTags
- auto allTagsIndex = _parent.allTags.countUntil(this);
- _parent.allTags.removeIndex(allTagsIndex);
-
- // Remove from _parent.tagIndices
- auto sameNamespaceTags = _parent.tagIndices[_namespace];
- auto tagIndicesIndex = sameNamespaceTags.countUntil(allTagsIndex);
- _parent.tagIndices[_namespace].removeIndex(tagIndicesIndex);
-
- // Fixup other indices
- foreach(ns, ref nsTagIndices; _parent.tagIndices)
- foreach(k, ref v; nsTagIndices)
- if(v > allTagsIndex)
- v--;
-
- _parent.removeNamespaceIfEmpty(_namespace);
- _parent.updateId++;
- _parent = null;
- return this;
- }
-
- private void removeNamespaceIfEmpty(string namespace)
- {
- // If namespace has no attributes, remove it from attributeIndices/_attributes
- if(namespace in attributeIndices && attributeIndices[namespace].length == 0)
- {
- attributeIndices.remove(namespace);
- _attributes.remove(namespace);
- }
-
- // If namespace has no tags, remove it from tagIndices/_tags
- if(namespace in tagIndices && tagIndices[namespace].length == 0)
- {
- tagIndices.remove(namespace);
- _tags.remove(namespace);
- }
-
- // If namespace is now empty, remove it from allNamespaces
- if(
- namespace !in tagIndices &&
- namespace !in attributeIndices
- )
- {
- auto allNamespacesIndex = allNamespaces.length - allNamespaces.find(namespace).length;
- allNamespaces = allNamespaces[0..allNamespacesIndex] ~ allNamespaces[allNamespacesIndex+1..$];
- }
- }
-
- struct NamedMemberRange(T, string membersGrouped)
- {
- private Tag tag;
- private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name)
- private string name;
- private size_t updateId; // Tag's updateId when this range was created.
-
- this(Tag tag, string namespace, string name, size_t updateId)
- {
- this.tag = tag;
- this.namespace = namespace;
- this.name = name;
- this.updateId = updateId;
- frontIndex = 0;
-
- if(
- namespace in mixin("tag."~membersGrouped) &&
- name in mixin("tag."~membersGrouped~"[namespace]")
- )
- endIndex = mixin("tag."~membersGrouped~"[namespace][name].length");
- else
- endIndex = 0;
- }
-
- invariant()
- {
- assert(
- this.updateId == tag.updateId,
- "This range has been invalidated by a change to the tag."
- );
- }
-
- @property bool empty()
- {
- return frontIndex == endIndex;
- }
-
- private size_t frontIndex;
- @property T front()
- {
- return this[0];
- }
- void popFront()
- {
- if(empty)
- throw new SDLangRangeException("Range is empty");
-
- frontIndex++;
- }
-
- private size_t endIndex; // One past the last element
- @property T back()
- {
- return this[$-1];
- }
- void popBack()
- {
- if(empty)
- throw new SDLangRangeException("Range is empty");
-
- endIndex--;
- }
-
- alias length opDollar;
- @property size_t length()
- {
- return endIndex - frontIndex;
- }
-
- @property typeof(this) save()
- {
- auto r = typeof(this)(this.tag, this.namespace, this.name, this.updateId);
- r.frontIndex = this.frontIndex;
- r.endIndex = this.endIndex;
- return r;
- }
-
- typeof(this) opSlice()
- {
- return save();
- }
-
- typeof(this) opSlice(size_t start, size_t end)
- {
- auto r = save();
- r.frontIndex = this.frontIndex + start;
- r.endIndex = this.frontIndex + end;
-
- if(
- r.frontIndex > this.endIndex ||
- r.endIndex > this.endIndex ||
- r.frontIndex > r.endIndex
- )
- throw new SDLangRangeException("Slice out of range");
-
- return r;
- }
-
- T opIndex(size_t index)
- {
- if(empty)
- throw new SDLangRangeException("Range is empty");
-
- return mixin("tag."~membersGrouped~"[namespace][name][frontIndex+index]");
- }
- }
-
- struct MemberRange(T, string allMembers, string memberIndices, string membersGrouped)
- {
- private Tag tag;
- private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name)
- private bool isMaybe;
- private size_t updateId; // Tag's updateId when this range was created.
- private size_t initialEndIndex;
-
- this(Tag tag, string namespace, bool isMaybe)
- {
- this.tag = tag;
- this.namespace = namespace;
- this.updateId = tag.updateId;
- this.isMaybe = isMaybe;
- frontIndex = 0;
-
- if(namespace == "*")
- initialEndIndex = mixin("tag."~allMembers~".length");
- else if(namespace in mixin("tag."~memberIndices))
- initialEndIndex = mixin("tag."~memberIndices~"[namespace].length");
- else
- initialEndIndex = 0;
-
- endIndex = initialEndIndex;
- }
-
- invariant()
- {
- assert(
- this.updateId == tag.updateId,
- "This range has been invalidated by a change to the tag."
- );
- }
-
- @property bool empty()
- {
- return frontIndex == endIndex;
- }
-
- private size_t frontIndex;
- @property T front()
- {
- return this[0];
- }
- void popFront()
- {
- if(empty)
- throw new SDLangRangeException("Range is empty");
-
- frontIndex++;
- }
-
- private size_t endIndex; // One past the last element
- @property T back()
- {
- return this[$-1];
- }
- void popBack()
- {
- if(empty)
- throw new SDLangRangeException("Range is empty");
-
- endIndex--;
- }
-
- alias length opDollar;
- @property size_t length()
- {
- return endIndex - frontIndex;
- }
-
- @property typeof(this) save()
- {
- auto r = typeof(this)(this.tag, this.namespace, this.isMaybe);
- r.frontIndex = this.frontIndex;
- r.endIndex = this.endIndex;
- r.initialEndIndex = this.initialEndIndex;
- r.updateId = this.updateId;
- return r;
- }
-
- typeof(this) opSlice()
- {
- return save();
- }
-
- typeof(this) opSlice(size_t start, size_t end)
- {
- auto r = save();
- r.frontIndex = this.frontIndex + start;
- r.endIndex = this.frontIndex + end;
-
- if(
- r.frontIndex > this.endIndex ||
- r.endIndex > this.endIndex ||
- r.frontIndex > r.endIndex
- )
- throw new SDLangRangeException("Slice out of range");
-
- return r;
- }
-
- T opIndex(size_t index)
- {
- if(empty)
- throw new SDLangRangeException("Range is empty");
-
- if(namespace == "*")
- return mixin("tag."~allMembers~"[ frontIndex+index ]");
- else
- return mixin("tag."~allMembers~"[ tag."~memberIndices~"[namespace][frontIndex+index] ]");
- }
-
- alias NamedMemberRange!(T,membersGrouped) ThisNamedMemberRange;
- ThisNamedMemberRange opIndex(string name)
- {
- if(frontIndex != 0 || endIndex != initialEndIndex)
- {
- throw new SDLangRangeException(
- "Cannot lookup tags/attributes by name on a subset of a range, "~
- "only across the entire tag. "~
- "Please make sure you haven't called popFront or popBack on this "~
- "range and that you aren't using a slice of the range."
- );
- }
-
- if(!isMaybe && empty)
- throw new SDLangRangeException("Range is empty");
-
- if(!isMaybe && name !in this)
- throw new SDLangRangeException(`No such `~T.stringof~` named: "`~name~`"`);
-
- return ThisNamedMemberRange(tag, namespace, name, updateId);
- }
-
- bool opBinaryRight(string op)(string name) if(op=="in")
- {
- if(frontIndex != 0 || endIndex != initialEndIndex)
- {
- throw new SDLangRangeException(
- "Cannot lookup tags/attributes by name on a subset of a range, "~
- "only across the entire tag. "~
- "Please make sure you haven't called popFront or popBack on this "~
- "range and that you aren't using a slice of the range."
- );
- }
-
- return
- namespace in mixin("tag."~membersGrouped) &&
- name in mixin("tag."~membersGrouped~"[namespace]") &&
- mixin("tag."~membersGrouped~"[namespace][name].length") > 0;
- }
- }
-
- struct NamespaceRange
- {
- private Tag tag;
- private bool isMaybe;
- private size_t updateId; // Tag's updateId when this range was created.
-
- this(Tag tag, bool isMaybe)
- {
- this.tag = tag;
- this.isMaybe = isMaybe;
- this.updateId = tag.updateId;
- frontIndex = 0;
- endIndex = tag.allNamespaces.length;
- }
-
- invariant()
- {
- assert(
- this.updateId == tag.updateId,
- "This range has been invalidated by a change to the tag."
- );
- }
-
- @property bool empty()
- {
- return frontIndex == endIndex;
- }
-
- private size_t frontIndex;
- @property NamespaceAccess front()
- {
- return this[0];
- }
- void popFront()
- {
- if(empty)
- throw new SDLangRangeException("Range is empty");
-
- frontIndex++;
- }
-
- private size_t endIndex; // One past the last element
- @property NamespaceAccess back()
- {
- return this[$-1];
- }
- void popBack()
- {
- if(empty)
- throw new SDLangRangeException("Range is empty");
-
- endIndex--;
- }
-
- alias length opDollar;
- @property size_t length()
- {
- return endIndex - frontIndex;
- }
-
- @property NamespaceRange save()
- {
- auto r = NamespaceRange(this.tag, this.isMaybe);
- r.frontIndex = this.frontIndex;
- r.endIndex = this.endIndex;
- r.updateId = this.updateId;
- return r;
- }
-
- typeof(this) opSlice()
- {
- return save();
- }
-
- typeof(this) opSlice(size_t start, size_t end)
- {
- auto r = save();
- r.frontIndex = this.frontIndex + start;
- r.endIndex = this.frontIndex + end;
-
- if(
- r.frontIndex > this.endIndex ||
- r.endIndex > this.endIndex ||
- r.frontIndex > r.endIndex
- )
- throw new SDLangRangeException("Slice out of range");
-
- return r;
- }
-
- NamespaceAccess opIndex(size_t index)
- {
- if(empty)
- throw new SDLangRangeException("Range is empty");
-
- auto namespace = tag.allNamespaces[frontIndex+index];
- return NamespaceAccess(
- namespace,
- AttributeRange(tag, namespace, isMaybe),
- TagRange(tag, namespace, isMaybe)
- );
- }
-
- NamespaceAccess opIndex(string namespace)
- {
- if(!isMaybe && empty)
- throw new SDLangRangeException("Range is empty");
-
- if(!isMaybe && namespace !in this)
- throw new SDLangRangeException(`No such namespace: "`~namespace~`"`);
-
- return NamespaceAccess(
- namespace,
- AttributeRange(tag, namespace, isMaybe),
- TagRange(tag, namespace, isMaybe)
- );
- }
-
- /// Inefficient when range is a slice or has used popFront/popBack, but it works.
- bool opBinaryRight(string op)(string namespace) if(op=="in")
- {
- if(frontIndex == 0 && endIndex == tag.allNamespaces.length)
- {
- return
- namespace in tag.attributeIndices ||
- namespace in tag.tagIndices;
- }
- else
- // Slower fallback method
- return tag.allNamespaces[frontIndex..endIndex].canFind(namespace);
- }
- }
-
- struct NamespaceAccess
- {
- string name;
- AttributeRange attributes;
- TagRange tags;
- }
-
- alias MemberRange!(Attribute, "allAttributes", "attributeIndices", "_attributes") AttributeRange;
- alias MemberRange!(Tag, "allTags", "tagIndices", "_tags" ) TagRange;
- static assert(isRandomAccessRange!AttributeRange);
- static assert(isRandomAccessRange!TagRange);
- static assert(isRandomAccessRange!NamespaceRange);
-
- /// Access all attributes that don't have a namespace
- @property AttributeRange attributes()
- {
- return AttributeRange(this, "", false);
- }
-
- /// Access all direct-child tags that don't have a namespace
- @property TagRange tags()
- {
- return TagRange(this, "", false);
- }
-
- /// Access all namespaces in this tag, and the attributes/tags within them.
- @property NamespaceRange namespaces()
- {
- return NamespaceRange(this, false);
- }
-
- /// Access all attributes and tags regardless of namespace.
- @property NamespaceAccess all()
- {
- // "*" isn't a valid namespace name, so we can use it to indicate "all namespaces"
- return NamespaceAccess(
- "*",
- AttributeRange(this, "*", false),
- TagRange(this, "*", false)
- );
- }
-
- struct MaybeAccess
- {
- Tag tag;
-
- /// Access all attributes that don't have a namespace
- @property AttributeRange attributes()
- {
- return AttributeRange(tag, "", true);
- }
-
- /// Access all direct-child tags that don't have a namespace
- @property TagRange tags()
- {
- return TagRange(tag, "", true);
- }
-
- /// Access all namespaces in this tag, and the attributes/tags within them.
- @property NamespaceRange namespaces()
- {
- return NamespaceRange(tag, true);
- }
-
- /// Access all attributes and tags regardless of namespace.
- @property NamespaceAccess all()
- {
- // "*" isn't a valid namespace name, so we can use it to indicate "all namespaces"
- return NamespaceAccess(
- "*",
- AttributeRange(tag, "*", true),
- TagRange(tag, "*", true)
- );
- }
- }
-
- /// Access 'attributes', 'tags', 'namespaces' and 'all' like normal,
- /// except that looking up a non-existent name/namespace with
- /// opIndex(string) results in an empty array instead of a thrown SDLangRangeException.
- @property MaybeAccess maybe()
- {
- return MaybeAccess(this);
- }
-
- override bool opEquals(Object o)
- {
- auto t = cast(Tag)o;
- if(!t)
- return false;
-
- if(_namespace != t._namespace || _name != t._name)
- return false;
-
- if(
- values .length != t.values .length ||
- allAttributes .length != t.allAttributes.length ||
- allNamespaces .length != t.allNamespaces.length ||
- allTags .length != t.allTags .length
- )
- return false;
-
- if(values != t.values)
- return false;
-
- if(allNamespaces != t.allNamespaces)
- return false;
-
- if(allAttributes != t.allAttributes)
- return false;
-
- // Ok because cycles are not allowed
- //TODO: Actually check for or prevent cycles.
- return allTags == t.allTags;
- }
-
- /// Treats 'this' as the root tag. Note that root tags cannot have
- /// values or attributes, and cannot be part of a namespace.
- /// If this isn't a valid root tag, 'SDLangValidationException' will be thrown.
- string toSDLDocument()(string indent="\t", int indentLevel=0)
- {
- Appender!string sink;
- toSDLDocument(sink, indent, indentLevel);
- return sink.data;
- }
-
- ///ditto
- void toSDLDocument(Sink)(ref Sink sink, string indent="\t", int indentLevel=0)
- if(isOutputRange!(Sink,char))
- {
- if(values.length > 0)
- throw new SDLangValidationException("Root tags cannot have any values, only child tags.");
-
- if(allAttributes.length > 0)
- throw new SDLangValidationException("Root tags cannot have any attributes, only child tags.");
-
- if(_namespace != "")
- throw new SDLangValidationException("Root tags cannot have a namespace.");
-
- foreach(tag; allTags)
- tag.toSDLString(sink, indent, indentLevel);
- }
-
- /// Output this entire tag in SDL format. Does *not* treat 'this' as
- /// a root tag. If you intend this to be the root of a standard SDL
- /// document, use 'toSDLDocument' instead.
- string toSDLString()(string indent="\t", int indentLevel=0)
- {
- Appender!string sink;
- toSDLString(sink, indent, indentLevel);
- return sink.data;
- }
-
- ///ditto
- void toSDLString(Sink)(ref Sink sink, string indent="\t", int indentLevel=0)
- if(isOutputRange!(Sink,char))
- {
- if(_name == "" && values.length == 0)
- throw new SDLangValidationException("Anonymous tags must have at least one value.");
-
- if(_name == "" && _namespace != "")
- throw new SDLangValidationException("Anonymous tags cannot have a namespace.");
-
- // Indent
- foreach(i; 0..indentLevel)
- sink.put(indent);
-
- // Name
- if(_namespace != "")
- {
- sink.put(_namespace);
- sink.put(':');
- }
- sink.put(_name);
-
- // Values
- foreach(i, v; values)
- {
- // Omit the first space for anonymous tags
- if(_name != "" || i > 0)
- sink.put(' ');
-
- v.toSDLString(sink);
- }
-
- // Attributes
- foreach(attr; allAttributes)
- {
- sink.put(' ');
- attr.toSDLString(sink);
- }
-
- // Child tags
- bool foundChild=false;
- foreach(tag; allTags)
- {
- if(!foundChild)
- {
- sink.put(" {\n");
- foundChild = true;
- }
-
- tag.toSDLString(sink, indent, indentLevel+1);
- }
- if(foundChild)
- {
- foreach(i; 0..indentLevel)
- sink.put(indent);
-
- sink.put("}\n");
- }
- else
- sink.put("\n");
- }
-
- /// Not the most efficient, but it works.
- string toDebugString()
- {
- import std.algorithm : sort;
-
- Appender!string buf;
-
- buf.put("\n");
- buf.put("Tag ");
- if(_namespace != "")
- {
- buf.put("[");
- buf.put(_namespace);
- buf.put("]");
- }
- buf.put("'%s':\n".format(_name));
-
- // Values
- foreach(val; values)
- buf.put(" (%s): %s\n".format(
- val.match!(v => typeof(v).stringof),
- val
- ));
-
- // Attributes
- foreach(attrNamespace; _attributes.keys.sort())
- if(attrNamespace != "*")
- foreach(attrName; _attributes[attrNamespace].keys.sort())
- foreach(attr; _attributes[attrNamespace][attrName])
- {
- string namespaceStr;
- if(attr._namespace != "")
- namespaceStr = "["~attr._namespace~"]";
-
- buf.put(
- " %s%s(%s): %s\n".format(
- namespaceStr, attr._name,
- attr.value.match!(v => typeof(v).stringof),
- attr.value
- )
- );
- }
-
- // Children
- foreach(tagNamespace; _tags.keys.sort())
- if(tagNamespace != "*")
- foreach(tagName; _tags[tagNamespace].keys.sort())
- foreach(tag; _tags[tagNamespace][tagName])
- buf.put( tag.toDebugString().replace("\n", "\n ") );
-
- return buf.data;
- }
- }
-
- version(sdlangUnittest)
- {
- private void testRandomAccessRange(R, E)(R range, E[] expected, bool function(E, E) equals=null)
- {
- static assert(isRandomAccessRange!R);
- static assert(is(ElementType!R == E));
- static assert(hasLength!R);
- static assert(!isInfinite!R);
-
- assert(range.length == expected.length);
- if(range.length == 0)
- {
- assert(range.empty);
- return;
- }
-
- static bool defaultEquals(E e1, E e2)
- {
- return e1 == e2;
- }
- if(equals is null)
- equals = &defaultEquals;
-
- assert(equals(range.front, expected[0]));
- assert(equals(range.front, expected[0])); // Ensure consistent result from '.front'
- assert(equals(range.front, expected[0])); // Ensure consistent result from '.front'
-
- assert(equals(range.back, expected[$-1]));
- assert(equals(range.back, expected[$-1])); // Ensure consistent result from '.back'
- assert(equals(range.back, expected[$-1])); // Ensure consistent result from '.back'
-
- // Forward iteration
- auto original = range.save;
- auto r2 = range.save;
- foreach(i; 0..expected.length)
- {
- //trace("Forward iteration: ", i);
-
- // Test length/empty
- assert(range.length == expected.length - i);
- assert(range.length == r2.length);
- assert(!range.empty);
- assert(!r2.empty);
-
- // Test front
- assert(equals(range.front, expected[i]));
- assert(equals(range.front, r2.front));
-
- // Test back
- assert(equals(range.back, expected[$-1]));
- assert(equals(range.back, r2.back));
-
- // Test opIndex(0)
- assert(equals(range[0], expected[i]));
- assert(equals(range[0], r2[0]));
-
- // Test opIndex($-1)
- assert(equals(range[$-1], expected[$-1]));
- assert(equals(range[$-1], r2[$-1]));
-
- // Test popFront
- range.popFront();
- assert(range.length == r2.length - 1);
- r2.popFront();
- assert(range.length == r2.length);
- }
- assert(range.empty);
- assert(r2.empty);
- assert(original.length == expected.length);
-
- // Backwards iteration
- range = original.save;
- r2 = original.save;
- foreach(i; iota(0, expected.length).retro())
- {
- //trace("Backwards iteration: ", i);
-
- // Test length/empty
- assert(range.length == i+1);
- assert(range.length == r2.length);
- assert(!range.empty);
- assert(!r2.empty);
-
- // Test front
- assert(equals(range.front, expected[0]));
- assert(equals(range.front, r2.front));
-
- // Test back
- assert(equals(range.back, expected[i]));
- assert(equals(range.back, r2.back));
-
- // Test opIndex(0)
- assert(equals(range[0], expected[0]));
- assert(equals(range[0], r2[0]));
-
- // Test opIndex($-1)
- assert(equals(range[$-1], expected[i]));
- assert(equals(range[$-1], r2[$-1]));
-
- // Test popBack
- range.popBack();
- assert(range.length == r2.length - 1);
- r2.popBack();
- assert(range.length == r2.length);
- }
- assert(range.empty);
- assert(r2.empty);
- assert(original.length == expected.length);
-
- // Random access
- range = original.save;
- r2 = original.save;
- foreach(i; 0..expected.length)
- {
- //trace("Random access: ", i);
-
- // Test length/empty
- assert(range.length == expected.length);
- assert(range.length == r2.length);
- assert(!range.empty);
- assert(!r2.empty);
-
- // Test front
- assert(equals(range.front, expected[0]));
- assert(equals(range.front, r2.front));
-
- // Test back
- assert(equals(range.back, expected[$-1]));
- assert(equals(range.back, r2.back));
-
- // Test opIndex(i)
- assert(equals(range[i], expected[i]));
- assert(equals(range[i], r2[i]));
- }
- assert(!range.empty);
- assert(!r2.empty);
- assert(original.length == expected.length);
- }
- }
-
- version(sdlangUnittest)
- unittest
- {
- import sdlang.parser;
- writeln("Unittesting sdlang ast...");
- stdout.flush();
-
- Tag root;
- root = parseSource("");
- testRandomAccessRange(root.attributes, cast( Attribute[])[]);
- testRandomAccessRange(root.tags, cast( Tag[])[]);
- testRandomAccessRange(root.namespaces, cast(Tag.NamespaceAccess[])[]);
-
- root = parseSource(`
- blue 3 "Lee" isThree=true
- blue 5 "Chan" 12345 isThree=false
- stuff:orange 1 2 3 2 1
- stuff:square points=4 dimensions=2 points="Still four"
- stuff:triangle data:points=3 data:dimensions=2
- nothing
- namespaces small:A=1 med:A=2 big:A=3 small:B=10 big:B=30
-
- people visitor:a=1 b=2 {
- chiyo "Small" "Flies?" nemesis="Car" score=100
- yukari
- visitor:sana
- tomo
- visitor:hayama
- }
- `);
-
- auto blue3 = new Tag(
- null, "", "blue",
- [ Value(3), Value("Lee") ],
- [ new Attribute("isThree", Value(true)) ],
- null
- );
- auto blue5 = new Tag(
- null, "", "blue",
- [ Value(5), Value("Chan"), Value(12345) ],
- [ new Attribute("isThree", Value(false)) ],
- null
- );
- auto orange = new Tag(
- null, "stuff", "orange",
- [ Value(1), Value(2), Value(3), Value(2), Value(1) ],
- null,
- null
- );
- auto square = new Tag(
- null, "stuff", "square",
- null,
- [
- new Attribute("points", Value(4)),
- new Attribute("dimensions", Value(2)),
- new Attribute("points", Value("Still four")),
- ],
- null
- );
- auto triangle = new Tag(
- null, "stuff", "triangle",
- null,
- [
- new Attribute("data", "points", Value(3)),
- new Attribute("data", "dimensions", Value(2)),
- ],
- null
- );
- auto nothing = new Tag(
- null, "", "nothing",
- null, null, null
- );
- auto namespaces = new Tag(
- null, "", "namespaces",
- null,
- [
- new Attribute("small", "A", Value(1)),
- new Attribute("med", "A", Value(2)),
- new Attribute("big", "A", Value(3)),
- new Attribute("small", "B", Value(10)),
- new Attribute("big", "B", Value(30)),
- ],
- null
- );
- auto chiyo = new Tag(
- null, "", "chiyo",
- [ Value("Small"), Value("Flies?") ],
- [
- new Attribute("nemesis", Value("Car")),
- new Attribute("score", Value(100)),
- ],
- null
- );
- auto chiyo_ = new Tag(
- null, "", "chiyo_",
- [ Value("Small"), Value("Flies?") ],
- [
- new Attribute("nemesis", Value("Car")),
- new Attribute("score", Value(100)),
- ],
- null
- );
- auto yukari = new Tag(
- null, "", "yukari",
- null, null, null
- );
- auto sana = new Tag(
- null, "visitor", "sana",
- null, null, null
- );
- auto sana_ = new Tag(
- null, "visitor", "sana_",
- null, null, null
- );
- auto sanaVisitor_ = new Tag(
- null, "visitor_", "sana_",
- null, null, null
- );
- auto tomo = new Tag(
- null, "", "tomo",
- null, null, null
- );
- auto hayama = new Tag(
- null, "visitor", "hayama",
- null, null, null
- );
- auto people = new Tag(
- null, "", "people",
- null,
- [
- new Attribute("visitor", "a", Value(1)),
- new Attribute("b", Value(2)),
- ],
- [chiyo, yukari, sana, tomo, hayama]
- );
-
- assert(blue3 .opEquals( blue3 ));
- assert(blue5 .opEquals( blue5 ));
- assert(orange .opEquals( orange ));
- assert(square .opEquals( square ));
- assert(triangle .opEquals( triangle ));
- assert(nothing .opEquals( nothing ));
- assert(namespaces .opEquals( namespaces ));
- assert(people .opEquals( people ));
- assert(chiyo .opEquals( chiyo ));
- assert(yukari .opEquals( yukari ));
- assert(sana .opEquals( sana ));
- assert(tomo .opEquals( tomo ));
- assert(hayama .opEquals( hayama ));
-
- assert(!blue3.opEquals(orange));
- assert(!blue3.opEquals(people));
- assert(!blue3.opEquals(sana));
- assert(!blue3.opEquals(blue5));
- assert(!blue5.opEquals(blue3));
-
- alias Tag.NamespaceAccess NSA;
- static bool namespaceEquals(NSA n1, NSA n2)
- {
- return n1.name == n2.name;
- }
-
- testRandomAccessRange(root.attributes, cast(Attribute[])[]);
- testRandomAccessRange(root.tags, [blue3, blue5, nothing, namespaces, people]);
- testRandomAccessRange(root.namespaces, [NSA(""), NSA("stuff")], &namespaceEquals);
- testRandomAccessRange(root.namespaces[0].tags, [blue3, blue5, nothing, namespaces, people]);
- testRandomAccessRange(root.namespaces[1].tags, [orange, square, triangle]);
- assert("" in root.namespaces);
- assert("stuff" in root.namespaces);
- assert("foobar" !in root.namespaces);
- testRandomAccessRange(root.namespaces[ ""].tags, [blue3, blue5, nothing, namespaces, people]);
- testRandomAccessRange(root.namespaces["stuff"].tags, [orange, square, triangle]);
- testRandomAccessRange(root.all.attributes, cast(Attribute[])[]);
- testRandomAccessRange(root.all.tags, [blue3, blue5, orange, square, triangle, nothing, namespaces, people]);
- testRandomAccessRange(root.all.tags[], [blue3, blue5, orange, square, triangle, nothing, namespaces, people]);
- testRandomAccessRange(root.all.tags[3..6], [square, triangle, nothing]);
- assert("blue" in root.tags);
- assert("nothing" in root.tags);
- assert("people" in root.tags);
- assert("orange" !in root.tags);
- assert("square" !in root.tags);
- assert("foobar" !in root.tags);
- assert("blue" in root.all.tags);
- assert("nothing" in root.all.tags);
- assert("people" in root.all.tags);
- assert("orange" in root.all.tags);
- assert("square" in root.all.tags);
- assert("foobar" !in root.all.tags);
- assert("orange" in root.namespaces["stuff"].tags);
- assert("square" in root.namespaces["stuff"].tags);
- assert("square" in root.namespaces["stuff"].tags);
- assert("foobar" !in root.attributes);
- assert("foobar" !in root.all.attributes);
- assert("foobar" !in root.namespaces["stuff"].attributes);
- assert("blue" !in root.attributes);
- assert("blue" !in root.all.attributes);
- assert("blue" !in root.namespaces["stuff"].attributes);
- testRandomAccessRange(root.tags["nothing"], [nothing]);
- testRandomAccessRange(root.tags["blue"], [blue3, blue5]);
- testRandomAccessRange(root.namespaces["stuff"].tags["orange"], [orange]);
- testRandomAccessRange(root.all.tags["nothing"], [nothing]);
- testRandomAccessRange(root.all.tags["blue"], [blue3, blue5]);
- testRandomAccessRange(root.all.tags["orange"], [orange]);
-
- assertThrown!SDLangRangeException(root.tags["foobar"]);
- assertThrown!SDLangRangeException(root.all.tags["foobar"]);
- assertThrown!SDLangRangeException(root.attributes["foobar"]);
- assertThrown!SDLangRangeException(root.all.attributes["foobar"]);
-
- // DMD Issue #12585 causes a segfault in these two tests when using 2.064 or 2.065,
- // so work around it.
- //assertThrown!SDLangRangeException(root.namespaces["foobar"].tags["foobar"]);
- //assertThrown!SDLangRangeException(root.namespaces["foobar"].attributes["foobar"]);
- bool didCatch = false;
- try
- auto x = root.namespaces["foobar"].tags["foobar"];
- catch(SDLangRangeException e)
- didCatch = true;
- assert(didCatch);
-
- didCatch = false;
- try
- auto x = root.namespaces["foobar"].attributes["foobar"];
- catch(SDLangRangeException e)
- didCatch = true;
- assert(didCatch);
-
- testRandomAccessRange(root.maybe.tags["nothing"], [nothing]);
- testRandomAccessRange(root.maybe.tags["blue"], [blue3, blue5]);
- testRandomAccessRange(root.maybe.namespaces["stuff"].tags["orange"], [orange]);
- testRandomAccessRange(root.maybe.all.tags["nothing"], [nothing]);
- testRandomAccessRange(root.maybe.all.tags["blue"], [blue3, blue5]);
- testRandomAccessRange(root.maybe.all.tags["blue"][], [blue3, blue5]);
- testRandomAccessRange(root.maybe.all.tags["blue"][0..1], [blue3]);
- testRandomAccessRange(root.maybe.all.tags["blue"][1..2], [blue5]);
- testRandomAccessRange(root.maybe.all.tags["orange"], [orange]);
- testRandomAccessRange(root.maybe.tags["foobar"], cast(Tag[])[]);
- testRandomAccessRange(root.maybe.all.tags["foobar"], cast(Tag[])[]);
- testRandomAccessRange(root.maybe.namespaces["foobar"].tags["foobar"], cast(Tag[])[]);
- testRandomAccessRange(root.maybe.attributes["foobar"], cast(Attribute[])[]);
- testRandomAccessRange(root.maybe.all.attributes["foobar"], cast(Attribute[])[]);
- testRandomAccessRange(root.maybe.namespaces["foobar"].attributes["foobar"], cast(Attribute[])[]);
-
- testRandomAccessRange(blue3.attributes, [ new Attribute("isThree", Value(true)) ]);
- testRandomAccessRange(blue3.tags, cast(Tag[])[]);
- testRandomAccessRange(blue3.namespaces, [NSA("")], &namespaceEquals);
- testRandomAccessRange(blue3.all.attributes, [ new Attribute("isThree", Value(true)) ]);
- testRandomAccessRange(blue3.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(blue5.attributes, [ new Attribute("isThree", Value(false)) ]);
- testRandomAccessRange(blue5.tags, cast(Tag[])[]);
- testRandomAccessRange(blue5.namespaces, [NSA("")], &namespaceEquals);
- testRandomAccessRange(blue5.all.attributes, [ new Attribute("isThree", Value(false)) ]);
- testRandomAccessRange(blue5.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(orange.attributes, cast(Attribute[])[]);
- testRandomAccessRange(orange.tags, cast(Tag[])[]);
- testRandomAccessRange(orange.namespaces, cast(NSA[])[], &namespaceEquals);
- testRandomAccessRange(orange.all.attributes, cast(Attribute[])[]);
- testRandomAccessRange(orange.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(square.attributes, [
- new Attribute("points", Value(4)),
- new Attribute("dimensions", Value(2)),
- new Attribute("points", Value("Still four")),
- ]);
- testRandomAccessRange(square.tags, cast(Tag[])[]);
- testRandomAccessRange(square.namespaces, [NSA("")], &namespaceEquals);
- testRandomAccessRange(square.all.attributes, [
- new Attribute("points", Value(4)),
- new Attribute("dimensions", Value(2)),
- new Attribute("points", Value("Still four")),
- ]);
- testRandomAccessRange(square.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(triangle.attributes, cast(Attribute[])[]);
- testRandomAccessRange(triangle.tags, cast(Tag[])[]);
- testRandomAccessRange(triangle.namespaces, [NSA("data")], &namespaceEquals);
- testRandomAccessRange(triangle.namespaces[0].attributes, [
- new Attribute("data", "points", Value(3)),
- new Attribute("data", "dimensions", Value(2)),
- ]);
- assert("data" in triangle.namespaces);
- assert("foobar" !in triangle.namespaces);
- testRandomAccessRange(triangle.namespaces["data"].attributes, [
- new Attribute("data", "points", Value(3)),
- new Attribute("data", "dimensions", Value(2)),
- ]);
- testRandomAccessRange(triangle.all.attributes, [
- new Attribute("data", "points", Value(3)),
- new Attribute("data", "dimensions", Value(2)),
- ]);
- testRandomAccessRange(triangle.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(nothing.attributes, cast(Attribute[])[]);
- testRandomAccessRange(nothing.tags, cast(Tag[])[]);
- testRandomAccessRange(nothing.namespaces, cast(NSA[])[], &namespaceEquals);
- testRandomAccessRange(nothing.all.attributes, cast(Attribute[])[]);
- testRandomAccessRange(nothing.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(namespaces.attributes, cast(Attribute[])[]);
- testRandomAccessRange(namespaces.tags, cast(Tag[])[]);
- testRandomAccessRange(namespaces.namespaces, [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals);
- testRandomAccessRange(namespaces.namespaces[], [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals);
- testRandomAccessRange(namespaces.namespaces[1..2], [NSA("med")], &namespaceEquals);
- testRandomAccessRange(namespaces.namespaces[0].attributes, [
- new Attribute("small", "A", Value(1)),
- new Attribute("small", "B", Value(10)),
- ]);
- testRandomAccessRange(namespaces.namespaces[1].attributes, [
- new Attribute("med", "A", Value(2)),
- ]);
- testRandomAccessRange(namespaces.namespaces[2].attributes, [
- new Attribute("big", "A", Value(3)),
- new Attribute("big", "B", Value(30)),
- ]);
- testRandomAccessRange(namespaces.namespaces[1..2][0].attributes, [
- new Attribute("med", "A", Value(2)),
- ]);
- assert("small" in namespaces.namespaces);
- assert("med" in namespaces.namespaces);
- assert("big" in namespaces.namespaces);
- assert("foobar" !in namespaces.namespaces);
- assert("small" !in namespaces.namespaces[1..2]);
- assert("med" in namespaces.namespaces[1..2]);
- assert("big" !in namespaces.namespaces[1..2]);
- assert("foobar" !in namespaces.namespaces[1..2]);
- testRandomAccessRange(namespaces.namespaces["small"].attributes, [
- new Attribute("small", "A", Value(1)),
- new Attribute("small", "B", Value(10)),
- ]);
- testRandomAccessRange(namespaces.namespaces["med"].attributes, [
- new Attribute("med", "A", Value(2)),
- ]);
- testRandomAccessRange(namespaces.namespaces["big"].attributes, [
- new Attribute("big", "A", Value(3)),
- new Attribute("big", "B", Value(30)),
- ]);
- testRandomAccessRange(namespaces.all.attributes, [
- new Attribute("small", "A", Value(1)),
- new Attribute("med", "A", Value(2)),
- new Attribute("big", "A", Value(3)),
- new Attribute("small", "B", Value(10)),
- new Attribute("big", "B", Value(30)),
- ]);
- testRandomAccessRange(namespaces.all.attributes[], [
- new Attribute("small", "A", Value(1)),
- new Attribute("med", "A", Value(2)),
- new Attribute("big", "A", Value(3)),
- new Attribute("small", "B", Value(10)),
- new Attribute("big", "B", Value(30)),
- ]);
- testRandomAccessRange(namespaces.all.attributes[2..4], [
- new Attribute("big", "A", Value(3)),
- new Attribute("small", "B", Value(10)),
- ]);
- testRandomAccessRange(namespaces.all.tags, cast(Tag[])[]);
- assert("A" !in namespaces.attributes);
- assert("B" !in namespaces.attributes);
- assert("foobar" !in namespaces.attributes);
- assert("A" in namespaces.all.attributes);
- assert("B" in namespaces.all.attributes);
- assert("foobar" !in namespaces.all.attributes);
- assert("A" in namespaces.namespaces["small"].attributes);
- assert("B" in namespaces.namespaces["small"].attributes);
- assert("foobar" !in namespaces.namespaces["small"].attributes);
- assert("A" in namespaces.namespaces["med"].attributes);
- assert("B" !in namespaces.namespaces["med"].attributes);
- assert("foobar" !in namespaces.namespaces["med"].attributes);
- assert("A" in namespaces.namespaces["big"].attributes);
- assert("B" in namespaces.namespaces["big"].attributes);
- assert("foobar" !in namespaces.namespaces["big"].attributes);
- assert("foobar" !in namespaces.tags);
- assert("foobar" !in namespaces.all.tags);
- assert("foobar" !in namespaces.namespaces["small"].tags);
- assert("A" !in namespaces.tags);
- assert("A" !in namespaces.all.tags);
- assert("A" !in namespaces.namespaces["small"].tags);
- testRandomAccessRange(namespaces.namespaces["small"].attributes["A"], [
- new Attribute("small", "A", Value(1)),
- ]);
- testRandomAccessRange(namespaces.namespaces["med"].attributes["A"], [
- new Attribute("med", "A", Value(2)),
- ]);
- testRandomAccessRange(namespaces.namespaces["big"].attributes["A"], [
- new Attribute("big", "A", Value(3)),
- ]);
- testRandomAccessRange(namespaces.all.attributes["A"], [
- new Attribute("small", "A", Value(1)),
- new Attribute("med", "A", Value(2)),
- new Attribute("big", "A", Value(3)),
- ]);
- testRandomAccessRange(namespaces.all.attributes["B"], [
- new Attribute("small", "B", Value(10)),
- new Attribute("big", "B", Value(30)),
- ]);
-
- testRandomAccessRange(chiyo.attributes, [
- new Attribute("nemesis", Value("Car")),
- new Attribute("score", Value(100)),
- ]);
- testRandomAccessRange(chiyo.tags, cast(Tag[])[]);
- testRandomAccessRange(chiyo.namespaces, [NSA("")], &namespaceEquals);
- testRandomAccessRange(chiyo.all.attributes, [
- new Attribute("nemesis", Value("Car")),
- new Attribute("score", Value(100)),
- ]);
- testRandomAccessRange(chiyo.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(yukari.attributes, cast(Attribute[])[]);
- testRandomAccessRange(yukari.tags, cast(Tag[])[]);
- testRandomAccessRange(yukari.namespaces, cast(NSA[])[], &namespaceEquals);
- testRandomAccessRange(yukari.all.attributes, cast(Attribute[])[]);
- testRandomAccessRange(yukari.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(sana.attributes, cast(Attribute[])[]);
- testRandomAccessRange(sana.tags, cast(Tag[])[]);
- testRandomAccessRange(sana.namespaces, cast(NSA[])[], &namespaceEquals);
- testRandomAccessRange(sana.all.attributes, cast(Attribute[])[]);
- testRandomAccessRange(sana.all.tags, cast(Tag[])[]);
-
- testRandomAccessRange(people.attributes, [new Attribute("b", Value(2))]);
- testRandomAccessRange(people.tags, [chiyo, yukari, tomo]);
- testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals);
- testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("visitor", "a", Value(1))]);
- testRandomAccessRange(people.namespaces[1].attributes, [new Attribute("b", Value(2))]);
- testRandomAccessRange(people.namespaces[0].tags, [sana, hayama]);
- testRandomAccessRange(people.namespaces[1].tags, [chiyo, yukari, tomo]);
- assert("visitor" in people.namespaces);
- assert("" in people.namespaces);
- assert("foobar" !in people.namespaces);
- testRandomAccessRange(people.namespaces["visitor"].attributes, [new Attribute("visitor", "a", Value(1))]);
- testRandomAccessRange(people.namespaces[ ""].attributes, [new Attribute("b", Value(2))]);
- testRandomAccessRange(people.namespaces["visitor"].tags, [sana, hayama]);
- testRandomAccessRange(people.namespaces[ ""].tags, [chiyo, yukari, tomo]);
- testRandomAccessRange(people.all.attributes, [
- new Attribute("visitor", "a", Value(1)),
- new Attribute("b", Value(2)),
- ]);
- testRandomAccessRange(people.all.tags, [chiyo, yukari, sana, tomo, hayama]);
-
- people.attributes["b"][0].name = "b_";
- people.namespaces["visitor"].attributes["a"][0].name = "a_";
- people.tags["chiyo"][0].name = "chiyo_";
- people.namespaces["visitor"].tags["sana"][0].name = "sana_";
-
- assert("b_" in people.attributes);
- assert("a_" in people.namespaces["visitor"].attributes);
- assert("chiyo_" in people.tags);
- assert("sana_" in people.namespaces["visitor"].tags);
-
- assert(people.attributes["b_"][0] == new Attribute("b_", Value(2)));
- assert(people.namespaces["visitor"].attributes["a_"][0] == new Attribute("visitor", "a_", Value(1)));
- assert(people.tags["chiyo_"][0] == chiyo_);
- assert(people.namespaces["visitor"].tags["sana_"][0] == sana_);
-
- assert("b" !in people.attributes);
- assert("a" !in people.namespaces["visitor"].attributes);
- assert("chiyo" !in people.tags);
- assert("sana" !in people.namespaces["visitor"].tags);
-
- assert(people.maybe.attributes["b"].length == 0);
- assert(people.maybe.namespaces["visitor"].attributes["a"].length == 0);
- assert(people.maybe.tags["chiyo"].length == 0);
- assert(people.maybe.namespaces["visitor"].tags["sana"].length == 0);
-
- people.tags["tomo"][0].remove();
- people.namespaces["visitor"].tags["hayama"][0].remove();
- people.tags["chiyo_"][0].remove();
- testRandomAccessRange(people.tags, [yukari]);
- testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals);
- testRandomAccessRange(people.namespaces[0].tags, [sana_]);
- testRandomAccessRange(people.namespaces[1].tags, [yukari]);
- assert("visitor" in people.namespaces);
- assert("" in people.namespaces);
- assert("foobar" !in people.namespaces);
- testRandomAccessRange(people.namespaces["visitor"].tags, [sana_]);
- testRandomAccessRange(people.namespaces[ ""].tags, [yukari]);
- testRandomAccessRange(people.all.tags, [yukari, sana_]);
-
- people.attributes["b_"][0].namespace = "_";
- people.namespaces["visitor"].attributes["a_"][0].namespace = "visitor_";
- assert("_" in people.namespaces);
- assert("visitor_" in people.namespaces);
- assert("" in people.namespaces);
- assert("visitor" in people.namespaces);
- people.namespaces["visitor"].tags["sana_"][0].namespace = "visitor_";
- assert("_" in people.namespaces);
- assert("visitor_" in people.namespaces);
- assert("" in people.namespaces);
- assert("visitor" !in people.namespaces);
-
- assert(people.namespaces["_" ].attributes["b_"][0] == new Attribute("_", "b_", Value(2)));
- assert(people.namespaces["visitor_"].attributes["a_"][0] == new Attribute("visitor_", "a_", Value(1)));
- assert(people.namespaces["visitor_"].tags["sana_"][0] == sanaVisitor_);
-
- people.tags["yukari"][0].remove();
- people.namespaces["visitor_"].tags["sana_"][0].remove();
- people.namespaces["visitor_"].attributes["a_"][0].namespace = "visitor";
- people.namespaces["_"].attributes["b_"][0].namespace = "";
- testRandomAccessRange(people.tags, cast(Tag[])[]);
- testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals);
- testRandomAccessRange(people.namespaces[0].tags, cast(Tag[])[]);
- testRandomAccessRange(people.namespaces[1].tags, cast(Tag[])[]);
- assert("visitor" in people.namespaces);
- assert("" in people.namespaces);
- assert("foobar" !in people.namespaces);
- testRandomAccessRange(people.namespaces["visitor"].tags, cast(Tag[])[]);
- testRandomAccessRange(people.namespaces[ ""].tags, cast(Tag[])[]);
- testRandomAccessRange(people.all.tags, cast(Tag[])[]);
-
- people.namespaces["visitor"].attributes["a_"][0].remove();
- testRandomAccessRange(people.attributes, [new Attribute("b_", Value(2))]);
- testRandomAccessRange(people.namespaces, [NSA("")], &namespaceEquals);
- testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("b_", Value(2))]);
- assert("visitor" !in people.namespaces);
- assert("" in people.namespaces);
- assert("foobar" !in people.namespaces);
- testRandomAccessRange(people.namespaces[""].attributes, [new Attribute("b_", Value(2))]);
- testRandomAccessRange(people.all.attributes, [
- new Attribute("b_", Value(2)),
- ]);
-
- people.attributes["b_"][0].remove();
- testRandomAccessRange(people.attributes, cast(Attribute[])[]);
- testRandomAccessRange(people.namespaces, cast(NSA[])[], &namespaceEquals);
- assert("visitor" !in people.namespaces);
- assert("" !in people.namespaces);
- assert("foobar" !in people.namespaces);
- testRandomAccessRange(people.all.attributes, cast(Attribute[])[]);
- }
-
- // Regression test, issue #11: https://github.com/Abscissa/SDLang-D/issues/11
- version(sdlangUnittest)
- unittest
- {
- import sdlang.parser;
- writeln("ast: Regression test issue #11...");
- stdout.flush();
-
- auto root = parseSource(
- `//
- a`);
-
- assert("a" in root.tags);
-
- root = parseSource(
- `//
- parent {
- child
- }
- `);
-
- auto child = new Tag(
- null, "", "child",
- null, null, null
- );
-
- assert("parent" in root.tags);
- assert("child" !in root.tags);
- testRandomAccessRange(root.tags["parent"][0].tags, [child]);
- assert("child" in root.tags["parent"][0].tags);
- }