diff options
author | Ralph Amissah <ralph@amissah.com> | 2016-10-01 14:12:13 -0400 |
---|---|---|
committer | Ralph Amissah <ralph@amissah.com> | 2019-04-10 15:14:13 -0400 |
commit | ba1712e77b31704fd9ba16d14e15518e7a7dd104 (patch) | |
tree | 1a0d3233fb611b68dbf43e098a41a0d9378e9ace /src | |
parent | update sdlang, start looking to using dub remote dependencies (diff) |
0.7.0 using dub remote dependencies (local src related to sdlang removed)
Diffstat (limited to 'src')
28 files changed, 41 insertions, 12796 deletions
diff --git a/src/sdlang/ast.d b/src/sdlang/ast.d deleted file mode 100644 index 87dd0bd..0000000 --- a/src/sdlang/ast.d +++ /dev/null @@ -1,2945 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.ast; - -import std.algorithm; -import std.array; -import std.conv; -import std.range; -import std.string; - -import sdlang.exception; -import sdlang.token; -import sdlang.util; - -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; - /++ - This tag's namespace. Empty string if no namespace. - - Note that setting this value is O(n) because internal lookup structures - need to be updated. - - Note also, that setting this may change where this tag is ordered among - its parent's list of tags. - +/ - @property string namespace() - { - return _namespace; - } - ///ditto - @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; - /++ - This attribute's name, not including namespace. - - Use `getFullName().toString` if you want the namespace included. - - Note that setting this value is O(n) because internal lookup structures - need to be updated. - - Note also, that setting this may change where this attribute is ordered - among its parent's list of tags. - +/ - @property string name() - { - return _name; - } - ///ditto - @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; - } - - /// This tag's name, including namespace if one exists. - deprecated("Use 'getFullName().toString()'") - @property string fullName() - { - return getFullName().toString(); - } - - /// This tag's name, including namespace if one exists. - FullName getFullName() - { - return FullName(_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; - } - - /// Copy this Attribute. - /// The clone does $(B $(I not)) have a parent, even if the original does. - Attribute clone() - { - return new Attribute(_namespace, _name, value, location); - } - - /// 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.attributeIndicies - auto sameNamespaceAttrs = _parent.attributeIndicies[_namespace]; - auto attrIndiciesIndex = sameNamespaceAttrs.countUntil(allAttrsIndex); - _parent.attributeIndicies[_namespace].removeIndex(attrIndiciesIndex); - - // Fixup other indicies - foreach(ns, ref nsAttrIndicies; _parent.attributeIndicies) - foreach(k, ref v; nsAttrIndicies) - 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); - } -} - -/// Deep-copy an array of Tag or Attribute. -/// The top-level clones are $(B $(I not)) attached to any parent, even if the originals are. -T[] clone(T)(T[] arr) if(is(T==Tag) || is(T==Attribute)) -{ - T[] newArr; - newArr.length = arr.length; - - foreach(i; 0..arr.length) - newArr[i] = arr[i].clone(); - - return newArr; -} - -class Tag -{ - /// File/Line/Column/Index information for where this tag was located in - /// its original SDLang file. - Location location; - - /// Access all this tag's values, as an array of type `sdlang.token.Value`. - 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; - /++ - This tag's namespace. Empty string if no namespace. - - Note that setting this value is O(n) because internal lookup structures - need to be updated. - - Note also, that setting this may change where this tag is ordered among - its parent's list of tags. - +/ - @property string namespace() - { - return _namespace; - } - ///ditto - @property void namespace(string value) - { - //TODO: Can we do this in-place, without removing/adding and thus - // modyfying the internal order? - 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; - /++ - This tag's name, not including namespace. - - Use `getFullName().toString` if you want the namespace included. - - Note that setting this value is O(n) because internal lookup structures - need to be updated. - - Note also, that setting this may change where this tag is ordered among - its parent's list of tags. - +/ - @property string name() - { - return _name; - } - ///ditto - @property void name(string value) - { - //TODO: Seriously? Can't we at least do the "*" modification *in-place*? - - if(_parent && _name != value) - { - _parent.updateId++; - - // Not the most efficient, but it works. - 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 - //TODO: Can we re-insert while preserving the original order? - _parent._tags[_namespace][_name] ~= this; - _parent._tags["*"][_name] ~= this; - } - else - _name = value; - } - - /// This tag's name, including namespace if one exists. - deprecated("Use 'getFullName().toString()'") - @property string fullName() - { - return getFullName().toString(); - } - - /// This tag's name, including namespace if one exists. - FullName getFullName() - { - return FullName(_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); - } - - /// Deep-copy this Tag. - /// The clone does $(B $(I not)) have a parent, even if the original does. - Tag clone() - { - auto newTag = new Tag(_namespace, _name, values.dup, allAttributes.clone(), allTags.clone()); - newTag.location = location; - return newTag; - } - - 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] attributeIndicies; // allAttributes[ attributes[namespace][i] ] - private size_t[][string] tagIndicies; // 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 `ValidationException` 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 ValidationException( - "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; - attributeIndicies[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 ValidationException( - "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; - tagIndicies[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.tagIndicies - auto sameNamespaceTags = _parent.tagIndicies[_namespace]; - auto tagIndiciesIndex = sameNamespaceTags.countUntil(allTagsIndex); - _parent.tagIndicies[_namespace].removeIndex(tagIndiciesIndex); - - // Fixup other indicies - foreach(ns, ref nsTagIndicies; _parent.tagIndicies) - foreach(k, ref v; nsTagIndicies) - 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 attributeIndicies/_attributes - if(namespace in attributeIndicies && attributeIndicies[namespace].length == 0) - { - attributeIndicies.remove(namespace); - _attributes.remove(namespace); - } - - // If namespace has no tags, remove it from tagIndicies/_tags - if(namespace in tagIndicies && tagIndicies[namespace].length == 0) - { - tagIndicies.remove(namespace); - _tags.remove(namespace); - } - - // If namespace is now empty, remove it from allNamespaces - if( - namespace !in tagIndicies && - namespace !in attributeIndicies - ) - { - 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( - tag !is null && - 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 tag is null || frontIndex == endIndex; - } - - private size_t frontIndex; - @property T front() - { - return this[0]; - } - void popFront() - { - if(empty) - throw new DOMRangeException(tag, "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 DOMRangeException(tag, "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 DOMRangeException(tag, "Slice out of range"); - - return r; - } - - T opIndex(size_t index) - { - if(empty) - throw new DOMRangeException(tag, "Range is empty"); - - return mixin("tag."~membersGrouped~"[namespace][name][frontIndex+index]"); - } - } - - struct MemberRange(T, string allMembers, string memberIndicies, 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(tag is null) - endIndex = 0; - else - { - - if(namespace == "*") - initialEndIndex = mixin("tag."~allMembers~".length"); - else if(namespace in mixin("tag."~memberIndicies)) - initialEndIndex = mixin("tag."~memberIndicies~"[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 tag is null || frontIndex == endIndex; - } - - private size_t frontIndex; - @property T front() - { - return this[0]; - } - void popFront() - { - if(empty) - throw new DOMRangeException(tag, "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 DOMRangeException(tag, "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 DOMRangeException(tag, "Slice out of range"); - - return r; - } - - T opIndex(size_t index) - { - if(empty) - throw new DOMRangeException(tag, "Range is empty"); - - if(namespace == "*") - return mixin("tag."~allMembers~"[ frontIndex+index ]"); - else - return mixin("tag."~allMembers~"[ tag."~memberIndicies~"[namespace][frontIndex+index] ]"); - } - - alias NamedMemberRange!(T,membersGrouped) ThisNamedMemberRange; - ThisNamedMemberRange opIndex(string name) - { - if(frontIndex != 0 || endIndex != initialEndIndex) - { - throw new DOMRangeException(tag, - "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 DOMRangeException(tag, "Range is empty"); - - if(!isMaybe && name !in this) - throw new DOMRangeException(tag, `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 DOMRangeException(tag, - "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(tag is null) - return false; - - 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 DOMRangeException(tag, "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 DOMRangeException(tag, "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 DOMRangeException(tag, "Slice out of range"); - - return r; - } - - NamespaceAccess opIndex(size_t index) - { - if(empty) - throw new DOMRangeException(tag, "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 DOMRangeException(tag, "Range is empty"); - - if(!isMaybe && namespace !in this) - throw new DOMRangeException(tag, `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.attributeIndicies || - namespace in tag.tagIndicies; - } - else - // Slower fallback method - return tag.allNamespaces[frontIndex..endIndex].canFind(namespace); - } - } - - static struct NamespaceAccess - { - string name; - AttributeRange attributes; - TagRange tags; - } - - alias MemberRange!(Attribute, "allAttributes", "attributeIndicies", "_attributes") AttributeRange; - alias MemberRange!(Tag, "allTags", "tagIndicies", "_tags" ) TagRange; - static assert(isRandomAccessRange!AttributeRange); - static assert(isRandomAccessRange!TagRange); - static assert(isRandomAccessRange!NamespaceRange); - - /++ - Access all attributes that don't have a namespace - - Returns a random access range of `Attribute` objects that supports - numeric-indexing, string-indexing, slicing and length. - - Since SDLang allows multiple attributes with the same name, - string-indexing returns a random access range of all attributes - with the given name. - - The string-indexing does $(B $(I not)) support namespace prefixes. - Use `namespace[string]`.`attributes` or `all`.`attributes` for that. - - See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) - for a high-level overview (and examples) of how to use this. - +/ - @property AttributeRange attributes() - { - return AttributeRange(this, "", false); - } - - /++ - Access all direct-child tags that don't have a namespace. - - Returns a random access range of `Tag` objects that supports - numeric-indexing, string-indexing, slicing and length. - - Since SDLang allows multiple tags with the same name, string-indexing - returns a random access range of all immediate child tags with the - given name. - - The string-indexing does $(B $(I not)) support namespace prefixes. - Use `namespace[string]`.`attributes` or `all`.`attributes` for that. - - See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) - for a high-level overview (and examples) of how to use this. - +/ - @property TagRange tags() - { - return TagRange(this, "", false); - } - - /++ - Access all namespaces in this tag, and the attributes/tags within them. - - Returns a random access range of `NamespaceAccess` elements that supports - numeric-indexing, string-indexing, slicing and length. - - See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) - for a high-level overview (and examples) of how to use this. - +/ - @property NamespaceRange namespaces() - { - return NamespaceRange(this, false); - } - - /// Access all attributes and tags regardless of namespace. - /// - /// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) - /// for a better understanding (and examples) of how to use this. - @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-existant name/namespace with - /// opIndex(string) results in an empty array instead of - /// a thrown `sdlang.exception.DOMRangeException`. - /// - /// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) - /// for a more information (and examples) of how to use this. - @property MaybeAccess maybe() - { - return MaybeAccess(this); - } - - // Internal implementations for the get/expect functions further below: - - private Tag getTagImpl(FullName tagFullName, Tag defaultValue=null, bool useDefaultValue=true) - { - auto tagNS = tagFullName.namespace; - auto tagName = tagFullName.name; - - // Can find namespace? - if(tagNS !in _tags) - { - if(useDefaultValue) - return defaultValue; - else - throw new TagNotFoundException(this, tagFullName, "No tags found in namespace '"~namespace~"'"); - } - - // Can find tag in namespace? - if(tagName !in _tags[tagNS] || _tags[tagNS][tagName].length == 0) - { - if(useDefaultValue) - return defaultValue; - else - throw new TagNotFoundException(this, tagFullName, "Can't find tag '"~tagFullName.toString()~"'"); - } - - // Return last matching tag found - return _tags[tagNS][tagName][$-1]; - } - - private T getValueImpl(T)(T defaultValue, bool useDefaultValue=true) - if(isValueType!T) - { - // Find value - foreach(value; this.values) - { - if(value.type == typeid(T)) - return value.get!T(); - } - - // No value of type T found - if(useDefaultValue) - return defaultValue; - else - { - throw new ValueNotFoundException( - this, - FullName(this.namespace, this.name), - typeid(T), - "No value of type "~T.stringof~" found." - ); - } - } - - private T getAttributeImpl(T)(FullName attrFullName, T defaultValue, bool useDefaultValue=true) - if(isValueType!T) - { - auto attrNS = attrFullName.namespace; - auto attrName = attrFullName.name; - - // Can find namespace and attribute name? - if(attrNS !in this._attributes || attrName !in this._attributes[attrNS]) - { - if(useDefaultValue) - return defaultValue; - else - { - throw new AttributeNotFoundException( - this, this.getFullName(), attrFullName, typeid(T), - "Can't find attribute '"~FullName.combine(attrNS, attrName)~"'" - ); - } - } - - // Find value with chosen type - foreach(attr; this._attributes[attrNS][attrName]) - { - if(attr.value.type == typeid(T)) - return attr.value.get!T(); - } - - // Chosen type not found - if(useDefaultValue) - return defaultValue; - else - { - throw new AttributeNotFoundException( - this, this.getFullName(), attrFullName, typeid(T), - "Can't find attribute '"~FullName.combine(attrNS, attrName)~"' of type "~T.stringof - ); - } - } - - // High-level interfaces for get/expect funtions: - - /++ - Lookup a child tag by name. Returns null if not found. - - Useful if you only expect one, and only one, child tag of a given name. - Only looks for immediate child tags of `this`, doesn't search recursively. - - If you expect multiple tags by the same name and want to get them all, - use `maybe`.`tags[string]` instead. - - The name can optionally include a namespace, as in `"namespace:name"`. - Or, you can search all namespaces using `"*:name"`. Use an empty string - to search for anonymous tags, or `"namespace:"` for anonymous tags inside - a namespace. Wildcard searching is only supported for namespaces, not names. - Use `maybe`.`tags[0]` if you don't care about the name. - - If there are multiple tags by the chosen name, the $(B $(I last tag)) will - always be chosen. That is, this function considers later tags with the - same name to override previous ones. - - If the tag cannot be found, and you provides a default value, the default - value is returned. Otherwise null is returned. If you'd prefer an - exception thrown, use `expectTag` instead. - +/ - Tag getTag(string fullTagName, Tag defaultValue=null) - { - auto parsedName = FullName.parse(fullTagName); - parsedName.ensureNoWildcardName( - "Instead, use 'Tag.maybe.tags[0]', 'Tag.maybe.all.tags[0]' or 'Tag.maybe.namespace[ns].tags[0]'." - ); - return getTagImpl(parsedName, defaultValue); - } - - /// - @("Tag.getTag") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo 1 - foo 2 // getTag considers this to override the first foo - - ns1:foo 3 - ns1:foo 4 // getTag considers this to override the first ns1:foo - ns2:foo 33 - ns2:foo 44 // getTag considers this to override the first ns2:foo - `); - assert( root.getTag("foo" ).values[0].get!int() == 2 ); - assert( root.getTag("ns1:foo").values[0].get!int() == 4 ); - assert( root.getTag("*:foo" ).values[0].get!int() == 44 ); // Search all namespaces - - // Not found - // If you'd prefer an exception, use `expectTag` instead. - assert( root.getTag("doesnt-exist") is null ); - - // Default value - auto foo = root.getTag("foo"); - assert( root.getTag("doesnt-exist", foo) is foo ); - } - - /++ - Lookup a child tag by name. Throws if not found. - - Useful if you only expect one, and only one, child tag of a given name. - Only looks for immediate child tags of `this`, doesn't search recursively. - - If you expect multiple tags by the same name and want to get them all, - use `tags[string]` instead. - - The name can optionally include a namespace, as in `"namespace:name"`. - Or, you can search all namespaces using `"*:name"`. Use an empty string - to search for anonymous tags, or `"namespace:"` for anonymous tags inside - a namespace. Wildcard searching is only supported for namespaces, not names. - Use `tags[0]` if you don't care about the name. - - If there are multiple tags by the chosen name, the $(B $(I last tag)) will - always be chosen. That is, this function considers later tags with the - same name to override previous ones. - - If no such tag is found, an `sdlang.exception.TagNotFoundException` will - be thrown. If you'd rather receive a default value, use `getTag` instead. - +/ - Tag expectTag(string fullTagName) - { - auto parsedName = FullName.parse(fullTagName); - parsedName.ensureNoWildcardName( - "Instead, use 'Tag.tags[0]', 'Tag.all.tags[0]' or 'Tag.namespace[ns].tags[0]'." - ); - return getTagImpl(parsedName, null, false); - } - - /// - @("Tag.expectTag") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo 1 - foo 2 // expectTag considers this to override the first foo - - ns1:foo 3 - ns1:foo 4 // expectTag considers this to override the first ns1:foo - ns2:foo 33 - ns2:foo 44 // expectTag considers this to override the first ns2:foo - `); - assert( root.expectTag("foo" ).values[0].get!int() == 2 ); - assert( root.expectTag("ns1:foo").values[0].get!int() == 4 ); - assert( root.expectTag("*:foo" ).values[0].get!int() == 44 ); // Search all namespaces - - // Not found - // If you'd rather receive a default value than an exception, use `getTag` instead. - assertThrown!TagNotFoundException( root.expectTag("doesnt-exist") ); - } - - /++ - Retrieve a value of type T from `this` tag. Returns a default value if not found. - - Useful if you only expect one value of type T from this tag. Only looks for - values of `this` tag, it does not search child tags. If you wish to search - for a value in a child tag (for example, if this current tag is a root tag), - try `getTagValue`. - - If you want to get more than one value from this tag, use `values` instead. - - If this tag has multiple values, the $(B $(I first)) value matching the - requested type will be returned. Ie, Extra values in the tag are ignored. - - You may provide a default value to be returned in case no value of - the requested type can be found. If you don't provide a default value, - `T.init` will be used. - - If you'd rather an exception be thrown when a value cannot be found, - use `expectValue` instead. - +/ - T getValue(T)(T defaultValue = T.init) if(isValueType!T) - { - return getValueImpl!T(defaultValue, true); - } - - /// - @("Tag.getValue") - unittest - { - import std.exception; - import std.math; - import sdlang.parser; - - auto root = parseSource(` - foo 1 true 2 false - `); - auto foo = root.getTag("foo"); - assert( foo.getValue!int() == 1 ); - assert( foo.getValue!bool() == true ); - - // Value found, default value ignored. - assert( foo.getValue!int(999) == 1 ); - - // No strings found - // If you'd prefer an exception, use `expectValue` instead. - assert( foo.getValue!string("Default") == "Default" ); - assert( foo.getValue!string() is null ); - - // No floats found - assert( foo.getValue!float(99.9).approxEqual(99.9) ); - assert( foo.getValue!float().isNaN() ); - } - - /++ - Retrieve a value of type T from `this` tag. Throws if not found. - - Useful if you only expect one value of type T from this tag. Only looks - for values of `this` tag, it does not search child tags. If you wish to - search for a value in a child tag (for example, if this current tag is a - root tag), try `expectTagValue`. - - If you want to get more than one value from this tag, use `values` instead. - - If this tag has multiple values, the $(B $(I first)) value matching the - requested type will be returned. Ie, Extra values in the tag are ignored. - - An `sdlang.exception.ValueNotFoundException` will be thrown if no value of - the requested type can be found. If you'd rather receive a default value, - use `getValue` instead. - +/ - T expectValue(T)() if(isValueType!T) - { - return getValueImpl!T(T.init, false); - } - - /// - @("Tag.expectValue") - unittest - { - import std.exception; - import std.math; - import sdlang.parser; - - auto root = parseSource(` - foo 1 true 2 false - `); - auto foo = root.getTag("foo"); - assert( foo.expectValue!int() == 1 ); - assert( foo.expectValue!bool() == true ); - - // No strings or floats found - // If you'd rather receive a default value than an exception, use `getValue` instead. - assertThrown!ValueNotFoundException( foo.expectValue!string() ); - assertThrown!ValueNotFoundException( foo.expectValue!float() ); - } - - /++ - Lookup a child tag by name, and retrieve a value of type T from it. - Returns a default value if not found. - - Useful if you only expect one value of type T from a given tag. Only looks - for immediate child tags of `this`, doesn't search recursively. - - This is a shortcut for `getTag().getValue()`, except if the tag isn't found, - then instead of a null reference error, it will return the requested - `defaultValue` (or T.init by default). - +/ - T getTagValue(T)(string fullTagName, T defaultValue = T.init) if(isValueType!T) - { - auto tag = getTag(fullTagName); - if(!tag) - return defaultValue; - - return tag.getValue!T(defaultValue); - } - - /// - @("Tag.getTagValue") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo 1 "a" 2 "b" - foo 3 "c" 4 "d" // getTagValue considers this to override the first foo - - bar "hi" - bar 379 // getTagValue considers this to override the first bar - `); - assert( root.getTagValue!int("foo") == 3 ); - assert( root.getTagValue!string("foo") == "c" ); - - // Value found, default value ignored. - assert( root.getTagValue!int("foo", 999) == 3 ); - - // Tag not found - // If you'd prefer an exception, use `expectTagValue` instead. - assert( root.getTagValue!int("doesnt-exist", 999) == 999 ); - assert( root.getTagValue!int("doesnt-exist") == 0 ); - - // The last "bar" tag doesn't have an int (only the first "bar" tag does) - assert( root.getTagValue!string("bar", "Default") == "Default" ); - assert( root.getTagValue!string("bar") is null ); - - // Using namespaces: - root = parseSource(` - ns1:foo 1 "a" 2 "b" - ns1:foo 3 "c" 4 "d" - ns2:foo 11 "aa" 22 "bb" - ns2:foo 33 "cc" 44 "dd" - - ns1:bar "hi" - ns1:bar 379 // getTagValue considers this to override the first bar - `); - assert( root.getTagValue!int("ns1:foo") == 3 ); - assert( root.getTagValue!int("*:foo" ) == 33 ); // Search all namespaces - - assert( root.getTagValue!string("ns1:foo") == "c" ); - assert( root.getTagValue!string("*:foo" ) == "cc" ); // Search all namespaces - - // The last "bar" tag doesn't have a string (only the first "bar" tag does) - assert( root.getTagValue!string("*:bar", "Default") == "Default" ); - assert( root.getTagValue!string("*:bar") is null ); - } - - /++ - Lookup a child tag by name, and retrieve a value of type T from it. - Throws if not found, - - Useful if you only expect one value of type T from a given tag. Only - looks for immediate child tags of `this`, doesn't search recursively. - - This is a shortcut for `expectTag().expectValue()`. - +/ - T expectTagValue(T)(string fullTagName) if(isValueType!T) - { - return expectTag(fullTagName).expectValue!T(); - } - - /// - @("Tag.expectTagValue") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo 1 "a" 2 "b" - foo 3 "c" 4 "d" // expectTagValue considers this to override the first foo - - bar "hi" - bar 379 // expectTagValue considers this to override the first bar - `); - assert( root.expectTagValue!int("foo") == 3 ); - assert( root.expectTagValue!string("foo") == "c" ); - - // The last "bar" tag doesn't have a string (only the first "bar" tag does) - // If you'd rather receive a default value than an exception, use `getTagValue` instead. - assertThrown!ValueNotFoundException( root.expectTagValue!string("bar") ); - - // Tag not found - assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist") ); - - // Using namespaces: - root = parseSource(` - ns1:foo 1 "a" 2 "b" - ns1:foo 3 "c" 4 "d" - ns2:foo 11 "aa" 22 "bb" - ns2:foo 33 "cc" 44 "dd" - - ns1:bar "hi" - ns1:bar 379 // expectTagValue considers this to override the first bar - `); - assert( root.expectTagValue!int("ns1:foo") == 3 ); - assert( root.expectTagValue!int("*:foo" ) == 33 ); // Search all namespaces - - assert( root.expectTagValue!string("ns1:foo") == "c" ); - assert( root.expectTagValue!string("*:foo" ) == "cc" ); // Search all namespaces - - // The last "bar" tag doesn't have a string (only the first "bar" tag does) - assertThrown!ValueNotFoundException( root.expectTagValue!string("*:bar") ); - - // Namespace not found - assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist:bar") ); - } - - /++ - Lookup an attribute of `this` tag by name, and retrieve a value of type T - from it. Returns a default value if not found. - - Useful if you only expect one attribute of the given name and type. - - Only looks for attributes of `this` tag, it does not search child tags. - If you wish to search for a value in a child tag (for example, if this - current tag is a root tag), try `getTagAttribute`. - - If you expect multiple attributes by the same name and want to get them all, - use `maybe`.`attributes[string]` instead. - - The attribute name can optionally include a namespace, as in - `"namespace:name"`. Or, you can search all namespaces using `"*:name"`. - (Note that unlike tags. attributes can't be anonymous - that's what - values are.) Wildcard searching is only supported for namespaces, not names. - Use `maybe`.`attributes[0]` if you don't care about the name. - - If this tag has multiple attributes, the $(B $(I first)) attribute - matching the requested name and type will be returned. Ie, Extra - attributes in the tag are ignored. - - You may provide a default value to be returned in case no attribute of - the requested name and type can be found. If you don't provide a default - value, `T.init` will be used. - - If you'd rather an exception be thrown when an attribute cannot be found, - use `expectAttribute` instead. - +/ - T getAttribute(T)(string fullAttributeName, T defaultValue = T.init) if(isValueType!T) - { - auto parsedName = FullName.parse(fullAttributeName); - parsedName.ensureNoWildcardName( - "Instead, use 'Attribute.maybe.tags[0]', 'Attribute.maybe.all.tags[0]' or 'Attribute.maybe.namespace[ns].tags[0]'." - ); - return getAttributeImpl!T(parsedName, defaultValue); - } - - /// - @("Tag.getAttribute") - unittest - { - import std.exception; - import std.math; - import sdlang.parser; - - auto root = parseSource(` - foo z=0 X=1 X=true X=2 X=false - `); - auto foo = root.getTag("foo"); - assert( foo.getAttribute!int("X") == 1 ); - assert( foo.getAttribute!bool("X") == true ); - - // Value found, default value ignored. - assert( foo.getAttribute!int("X", 999) == 1 ); - - // Attribute name not found - // If you'd prefer an exception, use `expectValue` instead. - assert( foo.getAttribute!int("doesnt-exist", 999) == 999 ); - assert( foo.getAttribute!int("doesnt-exist") == 0 ); - - // No strings found - assert( foo.getAttribute!string("X", "Default") == "Default" ); - assert( foo.getAttribute!string("X") is null ); - - // No floats found - assert( foo.getAttribute!float("X", 99.9).approxEqual(99.9) ); - assert( foo.getAttribute!float("X").isNaN() ); - - - // Using namespaces: - root = parseSource(` - foo ns1:z=0 ns1:X=1 ns1:X=2 ns2:X=3 ns2:X=4 - `); - foo = root.getTag("foo"); - assert( foo.getAttribute!int("ns2:X") == 3 ); - assert( foo.getAttribute!int("*:X") == 1 ); // Search all namespaces - - // Namespace not found - assert( foo.getAttribute!int("doesnt-exist:X", 999) == 999 ); - - // No attribute X is in the default namespace - assert( foo.getAttribute!int("X", 999) == 999 ); - - // Attribute name not found - assert( foo.getAttribute!int("ns1:doesnt-exist", 999) == 999 ); - } - - /++ - Lookup an attribute of `this` tag by name, and retrieve a value of type T - from it. Throws if not found. - - Useful if you only expect one attribute of the given name and type. - - Only looks for attributes of `this` tag, it does not search child tags. - If you wish to search for a value in a child tag (for example, if this - current tag is a root tag), try `expectTagAttribute`. - - If you expect multiple attributes by the same name and want to get them all, - use `attributes[string]` instead. - - The attribute name can optionally include a namespace, as in - `"namespace:name"`. Or, you can search all namespaces using `"*:name"`. - (Note that unlike tags. attributes can't be anonymous - that's what - values are.) Wildcard searching is only supported for namespaces, not names. - Use `attributes[0]` if you don't care about the name. - - If this tag has multiple attributes, the $(B $(I first)) attribute - matching the requested name and type will be returned. Ie, Extra - attributes in the tag are ignored. - - An `sdlang.exception.AttributeNotFoundException` will be thrown if no - value of the requested type can be found. If you'd rather receive a - default value, use `getAttribute` instead. - +/ - T expectAttribute(T)(string fullAttributeName) if(isValueType!T) - { - auto parsedName = FullName.parse(fullAttributeName); - parsedName.ensureNoWildcardName( - "Instead, use 'Attribute.tags[0]', 'Attribute.all.tags[0]' or 'Attribute.namespace[ns].tags[0]'." - ); - return getAttributeImpl!T(parsedName, T.init, false); - } - - /// - @("Tag.expectAttribute") - unittest - { - import std.exception; - import std.math; - import sdlang.parser; - - auto root = parseSource(` - foo z=0 X=1 X=true X=2 X=false - `); - auto foo = root.getTag("foo"); - assert( foo.expectAttribute!int("X") == 1 ); - assert( foo.expectAttribute!bool("X") == true ); - - // Attribute name not found - // If you'd rather receive a default value than an exception, use `getAttribute` instead. - assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist") ); - - // No strings found - assertThrown!AttributeNotFoundException( foo.expectAttribute!string("X") ); - - // No floats found - assertThrown!AttributeNotFoundException( foo.expectAttribute!float("X") ); - - - // Using namespaces: - root = parseSource(` - foo ns1:z=0 ns1:X=1 ns1:X=2 ns2:X=3 ns2:X=4 - `); - foo = root.getTag("foo"); - assert( foo.expectAttribute!int("ns2:X") == 3 ); - assert( foo.expectAttribute!int("*:X") == 1 ); // Search all namespaces - - // Namespace not found - assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist:X") ); - - // No attribute X is in the default namespace - assertThrown!AttributeNotFoundException( foo.expectAttribute!int("X") ); - - // Attribute name not found - assertThrown!AttributeNotFoundException( foo.expectAttribute!int("ns1:doesnt-exist") ); - } - - /++ - Lookup a child tag and attribute by name, and retrieve a value of type T - from it. Returns a default value if not found. - - Useful if you only expect one attribute of type T from given - the tag and attribute names. Only looks for immediate child tags of - `this`, doesn't search recursively. - - This is a shortcut for `getTag().getAttribute()`, except if the tag isn't - found, then instead of a null reference error, it will return the requested - `defaultValue` (or T.init by default). - +/ - T getTagAttribute(T)(string fullTagName, string fullAttributeName, T defaultValue = T.init) if(isValueType!T) - { - auto tag = getTag(fullTagName); - if(!tag) - return defaultValue; - - return tag.getAttribute!T(fullAttributeName, defaultValue); - } - - /// - @("Tag.getTagAttribute") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo X=1 X="a" X=2 X="b" - foo X=3 X="c" X=4 X="d" // getTagAttribute considers this to override the first foo - - bar X="hi" - bar X=379 // getTagAttribute considers this to override the first bar - `); - assert( root.getTagAttribute!int("foo", "X") == 3 ); - assert( root.getTagAttribute!string("foo", "X") == "c" ); - - // Value found, default value ignored. - assert( root.getTagAttribute!int("foo", "X", 999) == 3 ); - - // Tag not found - // If you'd prefer an exception, use `expectTagAttribute` instead of `getTagAttribute` - assert( root.getTagAttribute!int("doesnt-exist", "X", 999) == 999 ); - assert( root.getTagAttribute!int("doesnt-exist", "X") == 0 ); - assert( root.getTagAttribute!int("foo", "doesnt-exist", 999) == 999 ); - assert( root.getTagAttribute!int("foo", "doesnt-exist") == 0 ); - - // The last "bar" tag doesn't have a string (only the first "bar" tag does) - assert( root.getTagAttribute!string("bar", "X", "Default") == "Default" ); - assert( root.getTagAttribute!string("bar", "X") is null ); - - - // Using namespaces: - root = parseSource(` - ns1:foo X=1 X="a" X=2 X="b" - ns1:foo X=3 X="c" X=4 X="d" - ns2:foo X=11 X="aa" X=22 X="bb" - ns2:foo X=33 X="cc" X=44 X="dd" - - ns1:bar attrNS:X="hi" - ns1:bar attrNS:X=379 // getTagAttribute considers this to override the first bar - `); - assert( root.getTagAttribute!int("ns1:foo", "X") == 3 ); - assert( root.getTagAttribute!int("*:foo", "X") == 33 ); // Search all namespaces - - assert( root.getTagAttribute!string("ns1:foo", "X") == "c" ); - assert( root.getTagAttribute!string("*:foo", "X") == "cc" ); // Search all namespaces - - // bar's attribute X is't in the default namespace - assert( root.getTagAttribute!int("*:bar", "X", 999) == 999 ); - assert( root.getTagAttribute!int("*:bar", "X") == 0 ); - - // The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does) - assert( root.getTagAttribute!string("*:bar", "attrNS:X", "Default") == "Default" ); - assert( root.getTagAttribute!string("*:bar", "attrNS:X") is null); - } - - /++ - Lookup a child tag and attribute by name, and retrieve a value of type T - from it. Throws if not found. - - Useful if you only expect one attribute of type T from given - the tag and attribute names. Only looks for immediate child tags of - `this`, doesn't search recursively. - - This is a shortcut for `expectTag().expectAttribute()`. - +/ - T expectTagAttribute(T)(string fullTagName, string fullAttributeName) if(isValueType!T) - { - return expectTag(fullTagName).expectAttribute!T(fullAttributeName); - } - - /// - @("Tag.expectTagAttribute") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo X=1 X="a" X=2 X="b" - foo X=3 X="c" X=4 X="d" // expectTagAttribute considers this to override the first foo - - bar X="hi" - bar X=379 // expectTagAttribute considers this to override the first bar - `); - assert( root.expectTagAttribute!int("foo", "X") == 3 ); - assert( root.expectTagAttribute!string("foo", "X") == "c" ); - - // The last "bar" tag doesn't have an int attribute named "X" (only the first "bar" tag does) - // If you'd rather receive a default value than an exception, use `getAttribute` instead. - assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("bar", "X") ); - - // Tag not found - assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist", "X") ); - - // Using namespaces: - root = parseSource(` - ns1:foo X=1 X="a" X=2 X="b" - ns1:foo X=3 X="c" X=4 X="d" - ns2:foo X=11 X="aa" X=22 X="bb" - ns2:foo X=33 X="cc" X=44 X="dd" - - ns1:bar attrNS:X="hi" - ns1:bar attrNS:X=379 // expectTagAttribute considers this to override the first bar - `); - assert( root.expectTagAttribute!int("ns1:foo", "X") == 3 ); - assert( root.expectTagAttribute!int("*:foo", "X") == 33 ); // Search all namespaces - - assert( root.expectTagAttribute!string("ns1:foo", "X") == "c" ); - assert( root.expectTagAttribute!string("*:foo", "X") == "cc" ); // Search all namespaces - - // bar's attribute X is't in the default namespace - assertThrown!AttributeNotFoundException( root.expectTagAttribute!int("*:bar", "X") ); - - // The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does) - assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("*:bar", "attrNS:X") ); - - // Tag's namespace not found - assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist:bar", "attrNS:X") ); - } - - /++ - Lookup a child tag by name, and retrieve all values from it. - - This just like using `getTag()`.`values`, except if the tag isn't found, - it safely returns null (or an optional array of default values) instead of - a dereferencing null error. - - Note that, unlike `getValue`, this doesn't discriminate by the value's - type. It simply returns all values of a single tag as a `Value[]`. - - If you'd prefer an exception thrown when the tag isn't found, use - `expectTag`.`values` instead. - +/ - Value[] getTagValues(string fullTagName, Value[] defaultValues = null) - { - auto tag = getTag(fullTagName); - if(tag) - return tag.values; - else - return defaultValues; - } - - /// - @("getTagValues") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo 1 "a" 2 "b" - foo 3 "c" 4 "d" // getTagValues considers this to override the first foo - `); - assert( root.getTagValues("foo") == [Value(3), Value("c"), Value(4), Value("d")] ); - - // Tag not found - // If you'd prefer an exception, use `expectTag.values` instead. - assert( root.getTagValues("doesnt-exist") is null ); - assert( root.getTagValues("doesnt-exist", [ Value(999), Value("Not found") ]) == - [ Value(999), Value("Not found") ] ); - } - - /++ - Lookup a child tag by name, and retrieve all attributes in a chosen - (or default) namespace from it. - - This just like using `getTag()`.`attributes` (or - `getTag()`.`namespace[...]`.`attributes`, or `getTag()`.`all`.`attributes`), - except if the tag isn't found, it safely returns an empty range instead - of a dereferencing null error. - - If provided, the `attributeNamespace` parameter can be either the name of - a namespace, or an empty string for the default namespace (the default), - or `"*"` to retreive attributes from all namespaces. - - Note that, unlike `getAttributes`, this doesn't discriminate by the - value's type. It simply returns the usual `attributes` range. - - If you'd prefer an exception thrown when the tag isn't found, use - `expectTag`.`attributes` instead. - +/ - auto getTagAttributes(string fullTagName, string attributeNamespace = null) - { - auto tag = getTag(fullTagName); - if(tag) - { - if(attributeNamespace && attributeNamespace in tag.namespaces) - return tag.namespaces[attributeNamespace].attributes; - else if(attributeNamespace == "*") - return tag.all.attributes; - else - return tag.attributes; - } - - return AttributeRange(null, null, false); - } - - /// - @("getTagAttributes") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo X=1 X=2 - - // getTagAttributes considers this to override the first foo - foo X1=3 X2="c" namespace:bar=7 X3=4 X4="d" - `); - - auto fooAttrs = root.getTagAttributes("foo"); - assert( !fooAttrs.empty ); - assert( fooAttrs.length == 4 ); - assert( fooAttrs[0].name == "X1" && fooAttrs[0].value == Value(3) ); - assert( fooAttrs[1].name == "X2" && fooAttrs[1].value == Value("c") ); - assert( fooAttrs[2].name == "X3" && fooAttrs[2].value == Value(4) ); - assert( fooAttrs[3].name == "X4" && fooAttrs[3].value == Value("d") ); - - fooAttrs = root.getTagAttributes("foo", "namespace"); - assert( !fooAttrs.empty ); - assert( fooAttrs.length == 1 ); - assert( fooAttrs[0].name == "bar" && fooAttrs[0].value == Value(7) ); - - fooAttrs = root.getTagAttributes("foo", "*"); - assert( !fooAttrs.empty ); - assert( fooAttrs.length == 5 ); - assert( fooAttrs[0].name == "X1" && fooAttrs[0].value == Value(3) ); - assert( fooAttrs[1].name == "X2" && fooAttrs[1].value == Value("c") ); - assert( fooAttrs[2].name == "bar" && fooAttrs[2].value == Value(7) ); - assert( fooAttrs[3].name == "X3" && fooAttrs[3].value == Value(4) ); - assert( fooAttrs[4].name == "X4" && fooAttrs[4].value == Value("d") ); - - // Tag not found - // If you'd prefer an exception, use `expectTag.attributes` instead. - assert( root.getTagValues("doesnt-exist").empty ); - } - - @("*: Disallow wildcards for names") - unittest - { - import std.exception; - import std.math; - import sdlang.parser; - - auto root = parseSource(` - foo 1 X=2 - ns:foo 3 ns:X=4 - `); - auto foo = root.getTag("foo"); - auto nsfoo = root.getTag("ns:foo"); - - // Sanity check - assert( foo !is null ); - assert( foo.name == "foo" ); - assert( foo.namespace == "" ); - - assert( nsfoo !is null ); - assert( nsfoo.name == "foo" ); - assert( nsfoo.namespace == "ns" ); - - assert( foo.getValue !int() == 1 ); - assert( foo.expectValue !int() == 1 ); - assert( nsfoo.getValue !int() == 3 ); - assert( nsfoo.expectValue!int() == 3 ); - - assert( root.getTagValue !int("foo") == 1 ); - assert( root.expectTagValue!int("foo") == 1 ); - assert( root.getTagValue !int("ns:foo") == 3 ); - assert( root.expectTagValue!int("ns:foo") == 3 ); - - assert( foo.getAttribute !int("X") == 2 ); - assert( foo.expectAttribute !int("X") == 2 ); - assert( nsfoo.getAttribute !int("ns:X") == 4 ); - assert( nsfoo.expectAttribute!int("ns:X") == 4 ); - - assert( root.getTagAttribute !int("foo", "X") == 2 ); - assert( root.expectTagAttribute!int("foo", "X") == 2 ); - assert( root.getTagAttribute !int("ns:foo", "ns:X") == 4 ); - assert( root.expectTagAttribute!int("ns:foo", "ns:X") == 4 ); - - // No namespace - assertThrown!ArgumentException( root.getTag ("*") ); - assertThrown!ArgumentException( root.expectTag("*") ); - - assertThrown!ArgumentException( root.getTagValue !int("*") ); - assertThrown!ArgumentException( root.expectTagValue!int("*") ); - - assertThrown!ArgumentException( foo.getAttribute !int("*") ); - assertThrown!ArgumentException( foo.expectAttribute !int("*") ); - assertThrown!ArgumentException( root.getTagAttribute !int("*", "X") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("*", "X") ); - assertThrown!ArgumentException( root.getTagAttribute !int("foo", "*") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("foo", "*") ); - - // With namespace - assertThrown!ArgumentException( root.getTag ("ns:*") ); - assertThrown!ArgumentException( root.expectTag("ns:*") ); - - assertThrown!ArgumentException( root.getTagValue !int("ns:*") ); - assertThrown!ArgumentException( root.expectTagValue!int("ns:*") ); - - assertThrown!ArgumentException( nsfoo.getAttribute !int("ns:*") ); - assertThrown!ArgumentException( nsfoo.expectAttribute !int("ns:*") ); - assertThrown!ArgumentException( root.getTagAttribute !int("ns:*", "ns:X") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("ns:*", "ns:X") ); - assertThrown!ArgumentException( root.getTagAttribute !int("ns:foo", "ns:*") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("ns:foo", "ns:*") ); - - // With wildcard namespace - assertThrown!ArgumentException( root.getTag ("*:*") ); - assertThrown!ArgumentException( root.expectTag("*:*") ); - - assertThrown!ArgumentException( root.getTagValue !int("*:*") ); - assertThrown!ArgumentException( root.expectTagValue!int("*:*") ); - - assertThrown!ArgumentException( nsfoo.getAttribute !int("*:*") ); - assertThrown!ArgumentException( nsfoo.expectAttribute !int("*:*") ); - assertThrown!ArgumentException( root.getTagAttribute !int("*:*", "*:X") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("*:*", "*:X") ); - assertThrown!ArgumentException( root.getTagAttribute !int("*:foo", "*:*") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("*:foo", "*:*") ); - } - - 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, `sdlang.exception.ValidationException` - /// 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 ValidationException("Root tags cannot have any values, only child tags."); - - if(allAttributes.length > 0) - throw new ValidationException("Root tags cannot have any attributes, only child tags."); - - if(_namespace != "") - throw new ValidationException("Root tags cannot have a namespace."); - - foreach(tag; allTags) - tag.toSDLString(sink, indent, indentLevel); - } - - /// Output this entire tag in SDL format. Does $(B $(I 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 ValidationException("Anonymous tags must have at least one value."); - - if(_name == "" && _namespace != "") - throw new ValidationException("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"); - } - - /// Outputs full information on the tag. - 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(.toString(val.type), 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, .toString(attr.value.type), 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(unittest) -{ - 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); - } -} - -@("*: Test sdlang ast") -unittest -{ - import std.exception; - import sdlang.parser; - - 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!DOMRangeException(root.tags["foobar"]); - assertThrown!DOMRangeException(root.all.tags["foobar"]); - assertThrown!DOMRangeException(root.attributes["foobar"]); - assertThrown!DOMRangeException(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!DOMRangeException(root.namespaces["foobar"].tags["foobar"]); - //assertThrown!DOMRangeException(root.namespaces["foobar"].attributes["foobar"]); - bool didCatch = false; - try - auto x = root.namespaces["foobar"].tags["foobar"]; - catch(DOMRangeException e) - didCatch = true; - assert(didCatch); - - didCatch = false; - try - auto x = root.namespaces["foobar"].attributes["foobar"]; - catch(DOMRangeException 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[])[]); - - // Test clone() - auto rootClone = root.clone(); - assert(rootClone !is root); - assert(rootClone.parent is null); - assert(rootClone.name == root.name); - assert(rootClone.namespace == root.namespace); - assert(rootClone.location == root.location); - assert(rootClone.values == root.values); - assert(rootClone.toSDLDocument() == root.toSDLDocument()); - - auto peopleClone = people.clone(); - assert(peopleClone !is people); - assert(peopleClone.parent is null); - assert(peopleClone.name == people.name); - assert(peopleClone.namespace == people.namespace); - assert(peopleClone.location == people.location); - assert(peopleClone.values == people.values); - assert(peopleClone.toSDLString() == people.toSDLString()); -} - -// Regression test, issue #11: https://github.com/Abscissa/SDLang-D/issues/11 -@("*: Regression test issue #11") -unittest -{ - import sdlang.parser; - - 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); -} diff --git a/src/sdlang/dub.json b/src/sdlang/dub.json deleted file mode 100644 index d5a0493..0000000 --- a/src/sdlang/dub.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "sdlang-d", - "description": "An SDL (Simple Declarative Language) library for D.", - "homepage": "http://github.com/Abscissa/SDLang-D", - "authors": ["Nick Sabalausky"], - "license": "zlib/libpng", - "copyright": "©2012-2015 Nick Sabalausky", - "sourcePaths": ["."], - "importPaths": ["."], - "buildRequirements": ["allowWarnings"], - "dependencies": { - "libinputvisitor": "~>1.2.0" - }, - "subPackages": [ - "./libinputvisitor" - ], - "configurations": [ - { - "name": "test", - "targetType": "executable", - "versions": ["SDLang_TestApp"], - "targetPath": "../../bin/", - "targetName": "sdlang" - }, - { - "name": "library", - "targetType": "library" - }, - { - "name": "unittest", - "targetType": "executable", - "targetPath": "../../bin/", - "targetName": "sdlang-unittest", - - "versions": ["sdlangUnittest", "sdlangTrace"] - } - ] -} diff --git a/src/sdlang/exception.d b/src/sdlang/exception.d deleted file mode 100644 index 188991e..0000000 --- a/src/sdlang/exception.d +++ /dev/null @@ -1,190 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.exception; - -import std.array; -import std.exception; -import std.range; -import std.stdio; -import std.string; - -import sdlang.ast; -import sdlang.util; - -/// Abstract parent class of all SDLang-D defined exceptions. -abstract class SDLangException : Exception -{ - this(string msg, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line); - } -} - -/// Thrown when a syntax error is encounterd while parsing. -class ParseException : SDLangException -{ - Location location; - bool hasLocation; - - this(string msg, string file = __FILE__, size_t line = __LINE__) - { - hasLocation = false; - super(msg, file, line); - } - - this(Location location, string msg, string file = __FILE__, size_t line = __LINE__) - { - hasLocation = true; - super("%s: %s".format(location.toString(), msg), file, line); - } -} - -/// Compatibility alias -deprecated("The new name is ParseException") -alias SDLangParseException = ParseException; - -/++ -Thrown when attempting to do something in the DOM that's unsupported, such as: - -$(UL -$(LI Adding the same instance of a tag or attribute to more than one parent.) -$(LI Writing SDLang where: - $(UL - $(LI The root tag has values, attributes or a namespace. ) - $(LI An anonymous tag has a namespace. ) - $(LI An anonymous tag has no values. ) - $(LI A floating point value is infinity or NaN. ) - ) -)) -+/ -class ValidationException : SDLangException -{ - this(string msg, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line); - } -} - -/// Compatibility alias -deprecated("The new name is ValidationException") -alias SDLangValidationException = ValidationException; - -/// Thrown when someting is wrong with the provided arguments to a function. -class ArgumentException : SDLangException -{ - this(string msg, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line); - } -} - -/// Thrown by the DOM on empty range and out-of-range conditions. -abstract class DOMException : SDLangException -{ - Tag base; /// The tag searched from - - this(Tag base, string msg, string file = __FILE__, size_t line = __LINE__) - { - this.base = base; - super(msg, file, line); - } - - /// Prefixes a message with file/line information from the tag (if tag exists). - /// Optionally takes output range as a sink. - string customMsg(string msg) - { - if(!base) - return msg; - - Appender!string sink; - this.customMsg(sink, msg); - return sink.data; - } - - ///ditto - void customMsg(Sink)(ref Sink sink, string msg) if(isOutputRange!(Sink,char)) - { - if(base) - { - sink.put(base.location.toString()); - sink.put(": "); - sink.put(msg); - } - else - sink.put(msg); - } - - /// Outputs a message to stderr, prefixed with file/line information - void writeCustomMsg(string msg) - { - stderr.writeln( customMsg(msg) ); - } -} - -/// Thrown by the DOM on empty range and out-of-range conditions. -class DOMRangeException : DOMException -{ - this(Tag base, string msg, string file = __FILE__, size_t line = __LINE__) - { - super(base, msg, file, line); - } -} - -/// Compatibility alias -deprecated("The new name is DOMRangeException") -alias SDLangRangeException = DOMRangeException; - -/// Abstract parent class of `TagNotFoundException`, `ValueNotFoundException` -/// and `AttributeNotFoundException`. -/// -/// Thrown by the DOM's `sdlang.ast.Tag.expectTag`, etc. functions if a matching element isn't found. -abstract class DOMNotFoundException : DOMException -{ - FullName tagName; /// The tag searched for - - this(Tag base, FullName tagName, string msg, string file = __FILE__, size_t line = __LINE__) - { - this.tagName = tagName; - super(base, msg, file, line); - } -} - -/// Thrown by the DOM's `sdlang.ast.Tag.expectTag`, etc. functions if a Tag isn't found. -class TagNotFoundException : DOMNotFoundException -{ - this(Tag base, FullName tagName, string msg, string file = __FILE__, size_t line = __LINE__) - { - super(base, tagName, msg, file, line); - } -} - -/// Thrown by the DOM's `sdlang.ast.Tag.expectValue`, etc. functions if a Value isn't found. -class ValueNotFoundException : DOMNotFoundException -{ - /// Expected type for the not-found value. - TypeInfo valueType; - - this(Tag base, FullName tagName, TypeInfo valueType, string msg, string file = __FILE__, size_t line = __LINE__) - { - this.valueType = valueType; - super(base, tagName, msg, file, line); - } -} - -/// Thrown by the DOM's `sdlang.ast.Tag.expectAttribute`, etc. functions if an Attribute isn't found. -class AttributeNotFoundException : DOMNotFoundException -{ - FullName attributeName; /// The attribute searched for - - /// Expected type for the not-found attribute's value. - TypeInfo valueType; - - this(Tag base, FullName tagName, FullName attributeName, TypeInfo valueType, string msg, - string file = __FILE__, size_t line = __LINE__) - { - this.valueType = valueType; - this.attributeName = attributeName; - super(base, tagName, msg, file, line); - } -} diff --git a/src/sdlang/lexer.d b/src/sdlang/lexer.d deleted file mode 100644 index 3788188..0000000 --- a/src/sdlang/lexer.d +++ /dev/null @@ -1,2068 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.lexer; - -import std.algorithm; -import std.array; -static import std.ascii; -import std.base64; -import std.bigint; -import std.conv; -import std.datetime; -import std.file; -import std.format; -import std.traits; -import std.typecons; -import std.uni; -import std.utf; -import std.variant; - -import sdlang.exception; -import sdlang.symbol; -import sdlang.token; -import sdlang.util; - -alias sdlang.util.startsWith startsWith; - -Token[] lexFile(string filename) -{ - auto source = cast(string)read(filename); - return lexSource(source, filename); -} - -Token[] lexSource(string source, string filename=null) -{ - auto lexer = scoped!Lexer(source, filename); - - // Can't use 'std.array.array(Range)' because 'lexer' is scoped - // and therefore cannot have its reference copied. - Appender!(Token[]) tokens; - foreach(tok; lexer) - tokens.put(tok); - - return tokens.data; -} - -// Kind of a poor-man's yield, but fast. -// Only to be used inside Lexer.popFront (and Lexer.this). -private template accept(string symbolName) -{ - static assert(symbolName != "Value", "Value symbols must also take a value."); - enum accept = acceptImpl!(symbolName, "null"); -} -private template accept(string symbolName, string value) -{ - static assert(symbolName == "Value", "Only a Value symbol can take a value."); - enum accept = acceptImpl!(symbolName, value); -} -private template accept(string symbolName, string value, string startLocation, string endLocation) -{ - static assert(symbolName == "Value", "Only a Value symbol can take a value."); - enum accept = (" - { - _front = makeToken!"~symbolName.stringof~"; - _front.value = "~value~"; - _front.location = "~(startLocation==""? "tokenStart" : startLocation)~"; - _front.data = source[ - "~(startLocation==""? "tokenStart.index" : startLocation)~" - .. - "~(endLocation==""? "location.index" : endLocation)~" - ]; - return; - } - ").replace("\n", ""); -} -private template acceptImpl(string symbolName, string value) -{ - enum acceptImpl = (" - { - _front = makeToken!"~symbolName.stringof~"; - _front.value = "~value~"; - return; - } - ").replace("\n", ""); -} - -class Lexer -{ - string source; - string filename; - Location location; /// Location of current character in source - - private dchar ch; // Current character - private dchar nextCh; // Lookahead character - private size_t nextPos; // Position of lookahead character (an index into source) - private bool hasNextCh; // If false, then there's no more lookahead, just EOF - private size_t posAfterLookahead; // Position after lookahead character (an index into source) - - private Location tokenStart; // The starting location of the token being lexed - - // Length so far of the token being lexed, not including current char - private size_t tokenLength; // Length in UTF-8 code units - private size_t tokenLength32; // Length in UTF-32 code units - - // Slight kludge: - // If a numeric fragment is found after a Date (separated by arbitrary - // whitespace), it could be the "hours" part of a DateTime, or it could - // be a separate numeric literal that simply follows a plain Date. If the - // latter, then the Date must be emitted, but numeric fragment that was - // found after it needs to be saved for the the lexer's next iteration. - // - // It's a slight kludge, and could instead be implemented as a slightly - // kludgey parser hack, but it's the only situation where SDLang's lexing - // needs to lookahead more than one character, so this is good enough. - private struct LookaheadTokenInfo - { - bool exists = false; - string numericFragment = ""; - bool isNegative = false; - Location tokenStart; - } - private LookaheadTokenInfo lookaheadTokenInfo; - - this(string source=null, string filename=null) - { - this.filename = filename; - this.source = source; - - _front = Token(symbol!"Error", Location()); - lookaheadTokenInfo = LookaheadTokenInfo.init; - - if( source.startsWith( ByteOrderMarks[BOM.UTF8] ) ) - { - source = source[ ByteOrderMarks[BOM.UTF8].length .. $ ]; - this.source = source; - } - - foreach(bom; ByteOrderMarks) - if( source.startsWith(bom) ) - error(Location(filename,0,0,0), "SDL spec only supports UTF-8, not UTF-16 or UTF-32"); - - if(source == "") - mixin(accept!"EOF"); - - // Prime everything - hasNextCh = true; - nextCh = source.decode(posAfterLookahead); - advanceChar(ErrorOnEOF.Yes); - location = Location(filename, 0, 0, 0); - popFront(); - } - - @property bool empty() - { - return _front.symbol == symbol!"EOF"; - } - - Token _front; - @property Token front() - { - return _front; - } - - @property bool isEOF() - { - return location.index == source.length && !lookaheadTokenInfo.exists; - } - - private void error(string msg) - { - error(location, msg); - } - - //TODO: Take varargs and use output range sink. - private void error(Location loc, string msg) - { - throw new ParseException(loc, "Error: "~msg); - } - - private Token makeToken(string symbolName)() - { - auto tok = Token(symbol!symbolName, tokenStart); - tok.data = tokenData; - return tok; - } - - private @property string tokenData() - { - return source[ tokenStart.index .. location.index ]; - } - - /// Check the lookahead character - private bool lookahead(dchar ch) - { - return hasNextCh && nextCh == ch; - } - - private bool lookahead(bool function(dchar) condition) - { - return hasNextCh && condition(nextCh); - } - - private static bool isNewline(dchar ch) - { - return ch == '\n' || ch == '\r' || ch == lineSep || ch == paraSep; - } - - /// Returns the length of the newline sequence, or zero if the current - /// character is not a newline - /// - /// Note that there are only single character sequences and the two - /// character sequence `\r\n` as used on Windows. - private size_t isAtNewline() - { - if(ch == '\n' || ch == lineSep || ch == paraSep) return 1; - else if(ch == '\r') return lookahead('\n') ? 2 : 1; - else return 0; - } - - /// Is 'ch' a valid base 64 character? - private bool isBase64(dchar ch) - { - if(ch >= 'A' && ch <= 'Z') - return true; - - if(ch >= 'a' && ch <= 'z') - return true; - - if(ch >= '0' && ch <= '9') - return true; - - return ch == '+' || ch == '/' || ch == '='; - } - - /// Is the current character one that's allowed - /// immediately *after* an int/float literal? - private bool isEndOfNumber() - { - if(isEOF) - return true; - - return !isDigit(ch) && ch != ':' && ch != '_' && !isAlpha(ch); - } - - /// Is current character the last one in an ident? - private bool isEndOfIdentCached = false; - private bool _isEndOfIdent; - private bool isEndOfIdent() - { - if(!isEndOfIdentCached) - { - if(!hasNextCh) - _isEndOfIdent = true; - else - _isEndOfIdent = !isIdentChar(nextCh); - - isEndOfIdentCached = true; - } - - return _isEndOfIdent; - } - - /// Is 'ch' a character that's allowed *somewhere* in an identifier? - private bool isIdentChar(dchar ch) - { - if(isAlpha(ch)) - return true; - - else if(isNumber(ch)) - return true; - - else - return - ch == '-' || - ch == '_' || - ch == '.' || - ch == '$'; - } - - private bool isDigit(dchar ch) - { - return ch >= '0' && ch <= '9'; - } - - private enum KeywordResult - { - Accept, // Keyword is matched - Continue, // Keyword is not matched *yet* - Failed, // Keyword doesn't match - } - private KeywordResult checkKeyword(dstring keyword32) - { - // Still within length of keyword - if(tokenLength32 < keyword32.length) - { - if(ch == keyword32[tokenLength32]) - return KeywordResult.Continue; - else - return KeywordResult.Failed; - } - - // At position after keyword - else if(tokenLength32 == keyword32.length) - { - if(isEOF || !isIdentChar(ch)) - { - debug assert(tokenData == to!string(keyword32)); - return KeywordResult.Accept; - } - else - return KeywordResult.Failed; - } - - assert(0, "Fell off end of keyword to check"); - } - - enum ErrorOnEOF { No, Yes } - - /// Advance one code point. - private void advanceChar(ErrorOnEOF errorOnEOF) - { - if(auto cnt = isAtNewline()) - { - if (cnt == 1) - location.line++; - location.col = 0; - } - else - location.col++; - - location.index = nextPos; - - nextPos = posAfterLookahead; - ch = nextCh; - - if(!hasNextCh) - { - if(errorOnEOF == ErrorOnEOF.Yes) - error("Unexpected end of file"); - - return; - } - - tokenLength32++; - tokenLength = location.index - tokenStart.index; - - if(nextPos == source.length) - { - nextCh = dchar.init; - hasNextCh = false; - return; - } - - nextCh = source.decode(posAfterLookahead); - isEndOfIdentCached = false; - } - - /// Advances the specified amount of characters - private void advanceChar(size_t count, ErrorOnEOF errorOnEOF) - { - while(count-- > 0) - advanceChar(errorOnEOF); - } - - void popFront() - { - // -- Main Lexer ------------- - - eatWhite(); - - if(isEOF) - mixin(accept!"EOF"); - - tokenStart = location; - tokenLength = 0; - tokenLength32 = 0; - isEndOfIdentCached = false; - - if(lookaheadTokenInfo.exists) - { - tokenStart = lookaheadTokenInfo.tokenStart; - - auto prevLATokenInfo = lookaheadTokenInfo; - lookaheadTokenInfo = LookaheadTokenInfo.init; - lexNumeric(prevLATokenInfo); - return; - } - - if(ch == '=') - { - advanceChar(ErrorOnEOF.No); - mixin(accept!"="); - } - - else if(ch == '{') - { - advanceChar(ErrorOnEOF.No); - mixin(accept!"{"); - } - - else if(ch == '}') - { - advanceChar(ErrorOnEOF.No); - mixin(accept!"}"); - } - - else if(ch == ':') - { - advanceChar(ErrorOnEOF.No); - mixin(accept!":"); - } - - else if(ch == ';') - { - advanceChar(ErrorOnEOF.No); - mixin(accept!"EOL"); - } - - else if(auto cnt = isAtNewline()) - { - advanceChar(cnt, ErrorOnEOF.No); - mixin(accept!"EOL"); - } - - else if(isAlpha(ch) || ch == '_') - lexIdentKeyword(); - - else if(ch == '"') - lexRegularString(); - - else if(ch == '`') - lexRawString(); - - else if(ch == '\'') - lexCharacter(); - - else if(ch == '[') - lexBinary(); - - else if(ch == '-' || ch == '.' || isDigit(ch)) - lexNumeric(); - - else - { - if(ch == ',') - error("Unexpected comma: SDLang is not a comma-separated format."); - else if(std.ascii.isPrintable(ch)) - error(text("Unexpected: ", ch)); - else - error("Unexpected character code 0x%02X".format(ch)); - - advanceChar(ErrorOnEOF.No); - } - } - - /// Lex Ident or Keyword - private void lexIdentKeyword() - { - assert(isAlpha(ch) || ch == '_'); - - // Keyword - struct Key - { - dstring name; - Value value; - bool failed = false; - } - static Key[5] keywords; - static keywordsInited = false; - if(!keywordsInited) - { - // Value (as a std.variant-based type) can't be statically inited - keywords[0] = Key("true", Value(true )); - keywords[1] = Key("false", Value(false)); - keywords[2] = Key("on", Value(true )); - keywords[3] = Key("off", Value(false)); - keywords[4] = Key("null", Value(null )); - keywordsInited = true; - } - - foreach(ref key; keywords) - key.failed = false; - - auto numKeys = keywords.length; - - do - { - foreach(ref key; keywords) - if(!key.failed) - { - final switch(checkKeyword(key.name)) - { - case KeywordResult.Accept: - mixin(accept!("Value", "key.value")); - - case KeywordResult.Continue: - break; - - case KeywordResult.Failed: - key.failed = true; - numKeys--; - break; - } - } - - if(numKeys == 0) - { - lexIdent(); - return; - } - - advanceChar(ErrorOnEOF.No); - - } while(!isEOF); - - foreach(ref key; keywords) - if(!key.failed) - if(key.name.length == tokenLength32+1) - mixin(accept!("Value", "key.value")); - - mixin(accept!"Ident"); - } - - /// Lex Ident - private void lexIdent() - { - if(tokenLength == 0) - assert(isAlpha(ch) || ch == '_'); - - while(!isEOF && isIdentChar(ch)) - advanceChar(ErrorOnEOF.No); - - mixin(accept!"Ident"); - } - - /// Lex regular string - private void lexRegularString() - { - assert(ch == '"'); - - Appender!string buf; - size_t spanStart = nextPos; - - // Doesn't include current character - void updateBuf() - { - if(location.index == spanStart) - return; - - buf.put( source[spanStart..location.index] ); - } - - advanceChar(ErrorOnEOF.Yes); - while(ch != '"') - { - if(ch == '\\') - { - updateBuf(); - - bool wasEscSequence = true; - if(hasNextCh) - { - switch(nextCh) - { - case 'n': buf.put('\n'); break; - case 'r': buf.put('\r'); break; - case 't': buf.put('\t'); break; - case '"': buf.put('\"'); break; - case '\\': buf.put('\\'); break; - default: wasEscSequence = false; break; - } - } - - if(wasEscSequence) - { - advanceChar(ErrorOnEOF.Yes); - spanStart = nextPos; - } - else - { - eatWhite(false); - spanStart = location.index; - } - } - - else if(isNewline(ch)) - error("Unescaped newlines are only allowed in raw strings, not regular strings."); - - advanceChar(ErrorOnEOF.Yes); - } - - updateBuf(); - advanceChar(ErrorOnEOF.No); // Skip closing double-quote - mixin(accept!("Value", "buf.data")); - } - - /// Lex raw string - private void lexRawString() - { - assert(ch == '`'); - - do - advanceChar(ErrorOnEOF.Yes); - while(ch != '`'); - - advanceChar(ErrorOnEOF.No); // Skip closing back-tick - mixin(accept!("Value", "tokenData[1..$-1]")); - } - - /// Lex character literal - private void lexCharacter() - { - assert(ch == '\''); - advanceChar(ErrorOnEOF.Yes); // Skip opening single-quote - - dchar value; - if(ch == '\\') - { - advanceChar(ErrorOnEOF.Yes); // Skip escape backslash - switch(ch) - { - case 'n': value = '\n'; break; - case 'r': value = '\r'; break; - case 't': value = '\t'; break; - case '\'': value = '\''; break; - case '\\': value = '\\'; break; - default: error("Invalid escape sequence."); - } - } - else if(isNewline(ch)) - error("Newline not alowed in character literal."); - else - value = ch; - advanceChar(ErrorOnEOF.Yes); // Skip the character itself - - if(ch == '\'') - advanceChar(ErrorOnEOF.No); // Skip closing single-quote - else - error("Expected closing single-quote."); - - mixin(accept!("Value", "value")); - } - - /// Lex base64 binary literal - private void lexBinary() - { - assert(ch == '['); - advanceChar(ErrorOnEOF.Yes); - - void eatBase64Whitespace() - { - while(!isEOF && isWhite(ch)) - { - if(isNewline(ch)) - advanceChar(ErrorOnEOF.Yes); - - if(!isEOF && isWhite(ch)) - eatWhite(); - } - } - - eatBase64Whitespace(); - - // Iterates all valid base64 characters, ending at ']'. - // Skips all whitespace. Throws on invalid chars. - struct Base64InputRange - { - Lexer lexer; - private bool isInited = false; - private int numInputCharsMod4 = 0; - - @property bool empty() - { - if(lexer.ch == ']') - { - if(numInputCharsMod4 != 0) - lexer.error("Length of Base64 encoding must be a multiple of 4. ("~to!string(numInputCharsMod4)~")"); - - return true; - } - - return false; - } - - @property dchar front() - { - return lexer.ch; - } - - void popFront() - { - auto lex = lexer; - - if(!isInited) - { - if(lexer.isBase64(lexer.ch)) - { - numInputCharsMod4++; - numInputCharsMod4 %= 4; - } - - isInited = true; - } - - lex.advanceChar(lex.ErrorOnEOF.Yes); - - eatBase64Whitespace(); - - if(lex.isEOF) - lex.error("Unexpected end of file."); - - if(lex.ch != ']') - { - if(!lex.isBase64(lex.ch)) - lex.error("Invalid character in base64 binary literal."); - - numInputCharsMod4++; - numInputCharsMod4 %= 4; - } - } - } - - // This is a slow ugly hack. It's necessary because Base64.decode - // currently requires the source to have known length. - //TODO: Remove this when DMD issue #9543 is fixed. - dchar[] tmpBuf = array(Base64InputRange(this)); - - Appender!(ubyte[]) outputBuf; - // Ugly workaround for DMD issue #9102 - //TODO: Remove this when DMD #9102 is fixed - struct OutputBuf - { - void put(ubyte ch) - { - outputBuf.put(ch); - } - } - - try - //Base64.decode(Base64InputRange(this), OutputBuf()); - Base64.decode(tmpBuf, OutputBuf()); - - catch(Base64Exception e) - error("Invalid character in base64 binary literal."); - - advanceChar(ErrorOnEOF.No); // Skip ']' - mixin(accept!("Value", "outputBuf.data")); - } - - private BigInt toBigInt(bool isNegative, string absValue) - { - auto num = BigInt(absValue); - assert(num >= 0); - - if(isNegative) - num = -num; - - return num; - } - - /// Lex [0-9]+, but without emitting a token. - /// This is used by the other numeric parsing functions. - private string lexNumericFragment() - { - if(!isDigit(ch)) - error("Expected a digit 0-9."); - - auto spanStart = location.index; - - do - { - advanceChar(ErrorOnEOF.No); - } while(!isEOF && isDigit(ch)); - - return source[spanStart..location.index]; - } - - /// Lex anything that starts with 0-9 or '-'. Ints, floats, dates, etc. - private void lexNumeric(LookaheadTokenInfo laTokenInfo = LookaheadTokenInfo.init) - { - bool isNegative; - string firstFragment; - if(laTokenInfo.exists) - { - firstFragment = laTokenInfo.numericFragment; - isNegative = laTokenInfo.isNegative; - } - else - { - assert(ch == '-' || ch == '.' || isDigit(ch)); - - // Check for negative - isNegative = ch == '-'; - if(isNegative) - advanceChar(ErrorOnEOF.Yes); - - // Some floating point with omitted leading zero? - if(ch == '.') - { - lexFloatingPoint(""); - return; - } - - firstFragment = lexNumericFragment(); - } - - // Long integer (64-bit signed)? - if(ch == 'L' || ch == 'l') - { - advanceChar(ErrorOnEOF.No); - - // BigInt(long.min) is a workaround for DMD issue #9548 - auto num = toBigInt(isNegative, firstFragment); - if(num < BigInt(long.min) || num > long.max) - error(tokenStart, "Value doesn't fit in 64-bit signed long integer: "~to!string(num)); - - mixin(accept!("Value", "num.toLong()")); - } - - // Float (32-bit signed)? - else if(ch == 'F' || ch == 'f') - { - auto value = to!float(tokenData); - advanceChar(ErrorOnEOF.No); - mixin(accept!("Value", "value")); - } - - // Double float (64-bit signed) with suffix? - else if((ch == 'D' || ch == 'd') && !lookahead(':') - ) - { - auto value = to!double(tokenData); - advanceChar(ErrorOnEOF.No); - mixin(accept!("Value", "value")); - } - - // Decimal (128+ bits signed)? - else if( - (ch == 'B' || ch == 'b') && - (lookahead('D') || lookahead('d')) - ) - { - auto value = to!real(tokenData); - advanceChar(ErrorOnEOF.No); - advanceChar(ErrorOnEOF.No); - mixin(accept!("Value", "value")); - } - - // Some floating point? - else if(ch == '.') - lexFloatingPoint(firstFragment); - - // Some date? - else if(ch == '/' && hasNextCh && isDigit(nextCh)) - lexDate(isNegative, firstFragment); - - // Some time span? - else if(ch == ':' || ch == 'd') - lexTimeSpan(isNegative, firstFragment); - - // Integer (32-bit signed)? - else if(isEndOfNumber()) - { - auto num = toBigInt(isNegative, firstFragment); - if(num < int.min || num > int.max) - error(tokenStart, "Value doesn't fit in 32-bit signed integer: "~to!string(num)); - - mixin(accept!("Value", "num.toInt()")); - } - - // Invalid suffix - else - error("Invalid integer suffix."); - } - - /// Lex any floating-point literal (after the initial numeric fragment was lexed) - private void lexFloatingPoint(string firstPart) - { - assert(ch == '.'); - advanceChar(ErrorOnEOF.No); - - auto secondPart = lexNumericFragment(); - - try - { - // Double float (64-bit signed) with suffix? - if(ch == 'D' || ch == 'd') - { - auto value = to!double(tokenData); - advanceChar(ErrorOnEOF.No); - mixin(accept!("Value", "value")); - } - - // Float (32-bit signed)? - else if(ch == 'F' || ch == 'f') - { - auto value = to!float(tokenData); - advanceChar(ErrorOnEOF.No); - mixin(accept!("Value", "value")); - } - - // Decimal (128+ bits signed)? - else if(ch == 'B' || ch == 'b') - { - auto value = to!real(tokenData); - advanceChar(ErrorOnEOF.Yes); - - if(!isEOF && (ch == 'D' || ch == 'd')) - { - advanceChar(ErrorOnEOF.No); - if(isEndOfNumber()) - mixin(accept!("Value", "value")); - } - - error("Invalid floating point suffix."); - } - - // Double float (64-bit signed) without suffix? - else if(isEOF || !isIdentChar(ch)) - { - auto value = to!double(tokenData); - mixin(accept!("Value", "value")); - } - - // Invalid suffix - else - error("Invalid floating point suffix."); - } - catch(ConvException e) - error("Invalid floating point literal."); - } - - private Date makeDate(bool isNegative, string yearStr, string monthStr, string dayStr) - { - BigInt biTmp; - - biTmp = BigInt(yearStr); - if(isNegative) - biTmp = -biTmp; - if(biTmp < int.min || biTmp > int.max) - error(tokenStart, "Date's year is out of range. (Must fit within a 32-bit signed int.)"); - auto year = biTmp.toInt(); - - biTmp = BigInt(monthStr); - if(biTmp < 1 || biTmp > 12) - error(tokenStart, "Date's month is out of range."); - auto month = biTmp.toInt(); - - biTmp = BigInt(dayStr); - if(biTmp < 1 || biTmp > 31) - error(tokenStart, "Date's month is out of range."); - auto day = biTmp.toInt(); - - return Date(year, month, day); - } - - private DateTimeFrac makeDateTimeFrac( - bool isNegative, Date date, string hourStr, string minuteStr, - string secondStr, string millisecondStr - ) - { - BigInt biTmp; - - biTmp = BigInt(hourStr); - if(biTmp < int.min || biTmp > int.max) - error(tokenStart, "Datetime's hour is out of range."); - auto numHours = biTmp.toInt(); - - biTmp = BigInt(minuteStr); - if(biTmp < 0 || biTmp > int.max) - error(tokenStart, "Datetime's minute is out of range."); - auto numMinutes = biTmp.toInt(); - - int numSeconds = 0; - if(secondStr != "") - { - biTmp = BigInt(secondStr); - if(biTmp < 0 || biTmp > int.max) - error(tokenStart, "Datetime's second is out of range."); - numSeconds = biTmp.toInt(); - } - - int millisecond = 0; - if(millisecondStr != "") - { - biTmp = BigInt(millisecondStr); - if(biTmp < 0 || biTmp > int.max) - error(tokenStart, "Datetime's millisecond is out of range."); - millisecond = biTmp.toInt(); - - if(millisecondStr.length == 1) - millisecond *= 100; - else if(millisecondStr.length == 2) - millisecond *= 10; - } - - Duration fracSecs = millisecond.msecs; - - auto offset = hours(numHours) + minutes(numMinutes) + seconds(numSeconds); - - if(isNegative) - { - offset = -offset; - fracSecs = -fracSecs; - } - - return DateTimeFrac(DateTime(date) + offset, fracSecs); - } - - private Duration makeDuration( - bool isNegative, string dayStr, - string hourStr, string minuteStr, string secondStr, - string millisecondStr - ) - { - BigInt biTmp; - - long day = 0; - if(dayStr != "") - { - biTmp = BigInt(dayStr); - if(biTmp < long.min || biTmp > long.max) - error(tokenStart, "Time span's day is out of range."); - day = biTmp.toLong(); - } - - biTmp = BigInt(hourStr); - if(biTmp < long.min || biTmp > long.max) - error(tokenStart, "Time span's hour is out of range."); - auto hour = biTmp.toLong(); - - biTmp = BigInt(minuteStr); - if(biTmp < long.min || biTmp > long.max) - error(tokenStart, "Time span's minute is out of range."); - auto minute = biTmp.toLong(); - - biTmp = BigInt(secondStr); - if(biTmp < long.min || biTmp > long.max) - error(tokenStart, "Time span's second is out of range."); - auto second = biTmp.toLong(); - - long millisecond = 0; - if(millisecondStr != "") - { - biTmp = BigInt(millisecondStr); - if(biTmp < long.min || biTmp > long.max) - error(tokenStart, "Time span's millisecond is out of range."); - millisecond = biTmp.toLong(); - - if(millisecondStr.length == 1) - millisecond *= 100; - else if(millisecondStr.length == 2) - millisecond *= 10; - } - - auto duration = - dur!"days" (day) + - dur!"hours" (hour) + - dur!"minutes"(minute) + - dur!"seconds"(second) + - dur!"msecs" (millisecond); - - if(isNegative) - duration = -duration; - - return duration; - } - - // This has to reproduce some weird corner case behaviors from the - // original Java version of SDL. So some of this may seem weird. - private Nullable!Duration getTimeZoneOffset(string str) - { - if(str.length < 2) - return Nullable!Duration(); // Unknown timezone - - if(str[0] != '+' && str[0] != '-') - return Nullable!Duration(); // Unknown timezone - - auto isNegative = str[0] == '-'; - - string numHoursStr; - string numMinutesStr; - if(str[1] == ':') - { - numMinutesStr = str[1..$]; - numHoursStr = ""; - } - else - { - numMinutesStr = str.find(':'); - numHoursStr = str[1 .. $-numMinutesStr.length]; - } - - long numHours = 0; - long numMinutes = 0; - bool isUnknown = false; - try - { - switch(numHoursStr.length) - { - case 0: - if(numMinutesStr.length == 3) - { - numHours = 0; - numMinutes = to!long(numMinutesStr[1..$]); - } - else - isUnknown = true; - break; - - case 1: - case 2: - if(numMinutesStr.length == 0) - { - numHours = to!long(numHoursStr); - numMinutes = 0; - } - else if(numMinutesStr.length == 3) - { - numHours = to!long(numHoursStr); - numMinutes = to!long(numMinutesStr[1..$]); - } - else - isUnknown = true; - break; - - default: - if(numMinutesStr.length == 0) - { - // Yes, this is correct - numHours = 0; - numMinutes = to!long(numHoursStr[1..$]); - } - else - isUnknown = true; - break; - } - } - catch(ConvException e) - isUnknown = true; - - if(isUnknown) - return Nullable!Duration(); // Unknown timezone - - auto timeZoneOffset = hours(numHours) + minutes(numMinutes); - if(isNegative) - timeZoneOffset = -timeZoneOffset; - - // Timezone valid - return Nullable!Duration(timeZoneOffset); - } - - /// Lex date or datetime (after the initial numeric fragment was lexed) - private void lexDate(bool isDateNegative, string yearStr) - { - assert(ch == '/'); - - // Lex months - advanceChar(ErrorOnEOF.Yes); // Skip '/' - auto monthStr = lexNumericFragment(); - - // Lex days - if(ch != '/') - error("Invalid date format: Missing days."); - advanceChar(ErrorOnEOF.Yes); // Skip '/' - auto dayStr = lexNumericFragment(); - - auto date = makeDate(isDateNegative, yearStr, monthStr, dayStr); - - if(!isEndOfNumber() && ch != '/') - error("Dates cannot have suffixes."); - - // Date? - if(isEOF) - mixin(accept!("Value", "date")); - - auto endOfDate = location; - - while( - !isEOF && - ( ch == '\\' || ch == '/' || (isWhite(ch) && !isNewline(ch)) ) - ) - { - if(ch == '\\' && hasNextCh && isNewline(nextCh)) - { - advanceChar(ErrorOnEOF.Yes); - if(isAtNewline()) - advanceChar(ErrorOnEOF.Yes); - advanceChar(ErrorOnEOF.No); - } - - eatWhite(); - } - - // Date? - if(isEOF || (!isDigit(ch) && ch != '-')) - mixin(accept!("Value", "date", "", "endOfDate.index")); - - auto startOfTime = location; - - // Is time negative? - bool isTimeNegative = ch == '-'; - if(isTimeNegative) - advanceChar(ErrorOnEOF.Yes); - - // Lex hours - auto hourStr = ch == '.'? "" : lexNumericFragment(); - - // Lex minutes - if(ch != ':') - { - // No minutes found. Therefore we had a plain Date followed - // by a numeric literal, not a DateTime. - lookaheadTokenInfo.exists = true; - lookaheadTokenInfo.numericFragment = hourStr; - lookaheadTokenInfo.isNegative = isTimeNegative; - lookaheadTokenInfo.tokenStart = startOfTime; - mixin(accept!("Value", "date", "", "endOfDate.index")); - } - advanceChar(ErrorOnEOF.Yes); // Skip ':' - auto minuteStr = lexNumericFragment(); - - // Lex seconds, if exists - string secondStr; - if(ch == ':') - { - advanceChar(ErrorOnEOF.Yes); // Skip ':' - secondStr = lexNumericFragment(); - } - - // Lex milliseconds, if exists - string millisecondStr; - if(ch == '.') - { - advanceChar(ErrorOnEOF.Yes); // Skip '.' - millisecondStr = lexNumericFragment(); - } - - auto dateTimeFrac = makeDateTimeFrac(isTimeNegative, date, hourStr, minuteStr, secondStr, millisecondStr); - - // Lex zone, if exists - if(ch == '-') - { - advanceChar(ErrorOnEOF.Yes); // Skip '-' - auto timezoneStart = location; - - if(!isAlpha(ch)) - error("Invalid timezone format."); - - while(!isEOF && !isWhite(ch)) - advanceChar(ErrorOnEOF.No); - - auto timezoneStr = source[timezoneStart.index..location.index]; - if(timezoneStr.startsWith("GMT")) - { - auto isoPart = timezoneStr["GMT".length..$]; - auto offset = getTimeZoneOffset(isoPart); - - if(offset.isNull()) - { - // Unknown time zone - mixin(accept!("Value", "DateTimeFracUnknownZone(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezoneStr)")); - } - else - { - auto timezone = new immutable SimpleTimeZone(offset.get()); - mixin(accept!("Value", "SysTime(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezone)")); - } - } - - try - { - auto timezone = TimeZone.getTimeZone(timezoneStr); - if(timezone) - mixin(accept!("Value", "SysTime(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezone)")); - } - catch(TimeException e) - { - // Time zone not found. So just move along to "Unknown time zone" below. - } - - // Unknown time zone - mixin(accept!("Value", "DateTimeFracUnknownZone(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezoneStr)")); - } - - if(!isEndOfNumber()) - error("Date-Times cannot have suffixes."); - - mixin(accept!("Value", "dateTimeFrac")); - } - - /// Lex time span (after the initial numeric fragment was lexed) - private void lexTimeSpan(bool isNegative, string firstPart) - { - assert(ch == ':' || ch == 'd'); - - string dayStr = ""; - string hourStr; - - // Lexed days? - bool hasDays = ch == 'd'; - if(hasDays) - { - dayStr = firstPart; - advanceChar(ErrorOnEOF.Yes); // Skip 'd' - - // Lex hours - if(ch != ':') - error("Invalid time span format: Missing hours."); - advanceChar(ErrorOnEOF.Yes); // Skip ':' - hourStr = lexNumericFragment(); - } - else - hourStr = firstPart; - - // Lex minutes - if(ch != ':') - error("Invalid time span format: Missing minutes."); - advanceChar(ErrorOnEOF.Yes); // Skip ':' - auto minuteStr = lexNumericFragment(); - - // Lex seconds - if(ch != ':') - error("Invalid time span format: Missing seconds."); - advanceChar(ErrorOnEOF.Yes); // Skip ':' - auto secondStr = lexNumericFragment(); - - // Lex milliseconds, if exists - string millisecondStr = ""; - if(ch == '.') - { - advanceChar(ErrorOnEOF.Yes); // Skip '.' - millisecondStr = lexNumericFragment(); - } - - if(!isEndOfNumber()) - error("Time spans cannot have suffixes."); - - auto duration = makeDuration(isNegative, dayStr, hourStr, minuteStr, secondStr, millisecondStr); - mixin(accept!("Value", "duration")); - } - - /// Advances past whitespace and comments - private void eatWhite(bool allowComments=true) - { - // -- Comment/Whitepace Lexer ------------- - - enum State - { - normal, - lineComment, // Got "#" or "//" or "--", Eating everything until newline - blockComment, // Got "/*", Eating everything until "*/" - } - - if(isEOF) - return; - - Location commentStart; - State state = State.normal; - bool consumeNewlines = false; - bool hasConsumedNewline = false; - while(true) - { - final switch(state) - { - case State.normal: - - if(ch == '\\') - { - commentStart = location; - consumeNewlines = true; - hasConsumedNewline = false; - } - - else if(ch == '#') - { - if(!allowComments) - return; - - commentStart = location; - state = State.lineComment; - continue; - } - - else if(ch == '/' || ch == '-') - { - commentStart = location; - if(lookahead(ch)) - { - if(!allowComments) - return; - - advanceChar(ErrorOnEOF.No); - state = State.lineComment; - continue; - } - else if(ch == '/' && lookahead('*')) - { - if(!allowComments) - return; - - advanceChar(ErrorOnEOF.No); - state = State.blockComment; - continue; - } - else - return; // Done - } - else if(isAtNewline()) - { - if(consumeNewlines) - hasConsumedNewline = true; - else - return; // Done - } - else if(!isWhite(ch)) - { - if(consumeNewlines) - { - if(hasConsumedNewline) - return; // Done - else - error("Only whitespace can come between a line-continuation backslash and the following newline."); - } - else - return; // Done - } - - break; - - case State.lineComment: - if(lookahead(&isNewline)) - state = State.normal; - break; - - case State.blockComment: - if(ch == '*' && lookahead('/')) - { - advanceChar(ErrorOnEOF.No); - state = State.normal; - } - break; - } - - advanceChar(ErrorOnEOF.No); - if(isEOF) - { - // Reached EOF - - if(consumeNewlines && !hasConsumedNewline) - error("Missing newline after line-continuation backslash."); - - else if(state == State.blockComment) - error(commentStart, "Unterminated block comment."); - - else - return; // Done, reached EOF - } - } - } -} - -version(unittest) -{ - import std.stdio; - - version(Have_unit_threaded) import unit_threaded; - else { enum DontTest; } - - private auto loc = Location("filename", 0, 0, 0); - private auto loc2 = Location("a", 1, 1, 1); - - @("lexer: EOL") - unittest - { - assert([Token(symbol!"EOL",loc) ] == [Token(symbol!"EOL",loc) ] ); - assert([Token(symbol!"EOL",loc,Value(7),"A")] == [Token(symbol!"EOL",loc2,Value(7),"B")] ); - } - - private int numErrors = 0; - @DontTest - private void testLex(string source, Token[] expected, bool test_locations = false, string file=__FILE__, size_t line=__LINE__) - { - Token[] actual; - try - actual = lexSource(source, "filename"); - catch(ParseException e) - { - numErrors++; - stderr.writeln(file, "(", line, "): testLex failed on: ", source); - stderr.writeln(" Expected:"); - stderr.writeln(" ", expected); - stderr.writeln(" Actual: ParseException thrown:"); - stderr.writeln(" ", e.msg); - return; - } - - bool is_same = actual == expected; - if (is_same && test_locations) { - is_same = actual.map!(t => t.location).equal(expected.map!(t => t.location)); - } - - if(!is_same) - { - numErrors++; - stderr.writeln(file, "(", line, "): testLex failed on: ", source); - stderr.writeln(" Expected:"); - stderr.writeln(" ", expected); - stderr.writeln(" Actual:"); - stderr.writeln(" ", actual); - - if(expected.length > 1 || actual.length > 1) - { - stderr.writeln(" expected.length: ", expected.length); - stderr.writeln(" actual.length: ", actual.length); - - if(actual.length == expected.length) - foreach(i; 0..actual.length) - if(actual[i] != expected[i]) - { - stderr.writeln(" Unequal at index #", i, ":"); - stderr.writeln(" Expected:"); - stderr.writeln(" ", expected[i]); - stderr.writeln(" Actual:"); - stderr.writeln(" ", actual[i]); - } - } - } - } - - private void testLexThrows(string file=__FILE__, size_t line=__LINE__)(string source) - { - bool hadException = false; - Token[] actual; - try - actual = lexSource(source, "filename"); - catch(ParseException e) - hadException = true; - - if(!hadException) - { - numErrors++; - stderr.writeln(file, "(", line, "): testLex failed on: ", source); - stderr.writeln(" Expected ParseException"); - stderr.writeln(" Actual:"); - stderr.writeln(" ", actual); - } - } -} - -@("sdlang lexer") -unittest -{ - testLex("", []); - testLex(" ", []); - testLex("\\\n", []); - testLex("/*foo*/", []); - testLex("/* multiline \n comment */", []); - testLex("/* * */", []); - testLexThrows("/* "); - - testLex(":", [ Token(symbol!":", loc) ]); - testLex("=", [ Token(symbol!"=", loc) ]); - testLex("{", [ Token(symbol!"{", loc) ]); - testLex("}", [ Token(symbol!"}", loc) ]); - testLex(";", [ Token(symbol!"EOL",loc) ]); - testLex("\n", [ Token(symbol!"EOL",loc) ]); - - testLex("foo", [ Token(symbol!"Ident",loc,Value(null),"foo") ]); - testLex("_foo", [ Token(symbol!"Ident",loc,Value(null),"_foo") ]); - testLex("foo.bar", [ Token(symbol!"Ident",loc,Value(null),"foo.bar") ]); - testLex("foo-bar", [ Token(symbol!"Ident",loc,Value(null),"foo-bar") ]); - testLex("foo.", [ Token(symbol!"Ident",loc,Value(null),"foo.") ]); - testLex("foo-", [ Token(symbol!"Ident",loc,Value(null),"foo-") ]); - testLexThrows(".foo"); - - testLex("foo bar", [ - Token(symbol!"Ident",loc,Value(null),"foo"), - Token(symbol!"Ident",loc,Value(null),"bar"), - ]); - testLex("foo \\ \n \n bar", [ - Token(symbol!"Ident",loc,Value(null),"foo"), - Token(symbol!"Ident",loc,Value(null),"bar"), - ]); - testLex("foo \\ \n \\ \n bar", [ - Token(symbol!"Ident",loc,Value(null),"foo"), - Token(symbol!"Ident",loc,Value(null),"bar"), - ]); - testLexThrows("foo \\ "); - testLexThrows("foo \\ bar"); - testLexThrows("foo \\ \n \\ "); - testLexThrows("foo \\ \n \\ bar"); - - testLex("foo : = { } ; \n bar \n", [ - Token(symbol!"Ident",loc,Value(null),"foo"), - Token(symbol!":",loc), - Token(symbol!"=",loc), - Token(symbol!"{",loc), - Token(symbol!"}",loc), - Token(symbol!"EOL",loc), - Token(symbol!"EOL",loc), - Token(symbol!"Ident",loc,Value(null),"bar"), - Token(symbol!"EOL",loc), - ]); - - testLexThrows("<"); - testLexThrows("*"); - testLexThrows(`\`); - - // Integers - testLex( "7", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]); - testLex( "-7", [ Token(symbol!"Value",loc,Value(cast( int)-7)) ]); - testLex( "7L", [ Token(symbol!"Value",loc,Value(cast(long) 7)) ]); - testLex( "7l", [ Token(symbol!"Value",loc,Value(cast(long) 7)) ]); - testLex("-7L", [ Token(symbol!"Value",loc,Value(cast(long)-7)) ]); - testLex( "0", [ Token(symbol!"Value",loc,Value(cast( int) 0)) ]); - testLex( "-0", [ Token(symbol!"Value",loc,Value(cast( int) 0)) ]); - - testLex("7/**/", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]); - testLex("7#", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]); - - testLex("7 A", [ - Token(symbol!"Value",loc,Value(cast(int)7)), - Token(symbol!"Ident",loc,Value( null),"A"), - ]); - testLexThrows("7A"); - testLexThrows("-A"); - testLexThrows(`-""`); - - testLex("7;", [ - Token(symbol!"Value",loc,Value(cast(int)7)), - Token(symbol!"EOL",loc), - ]); - - // Floats - testLex("1.2F" , [ Token(symbol!"Value",loc,Value(cast( float)1.2)) ]); - testLex("1.2f" , [ Token(symbol!"Value",loc,Value(cast( float)1.2)) ]); - testLex("1.2" , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]); - testLex("1.2D" , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]); - testLex("1.2d" , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]); - testLex("1.2BD", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]); - testLex("1.2bd", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]); - testLex("1.2Bd", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]); - testLex("1.2bD", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]); - - testLex(".2F" , [ Token(symbol!"Value",loc,Value(cast( float)0.2)) ]); - testLex(".2" , [ Token(symbol!"Value",loc,Value(cast(double)0.2)) ]); - testLex(".2D" , [ Token(symbol!"Value",loc,Value(cast(double)0.2)) ]); - testLex(".2BD", [ Token(symbol!"Value",loc,Value(cast( real)0.2)) ]); - - testLex("-1.2F" , [ Token(symbol!"Value",loc,Value(cast( float)-1.2)) ]); - testLex("-1.2" , [ Token(symbol!"Value",loc,Value(cast(double)-1.2)) ]); - testLex("-1.2D" , [ Token(symbol!"Value",loc,Value(cast(double)-1.2)) ]); - testLex("-1.2BD", [ Token(symbol!"Value",loc,Value(cast( real)-1.2)) ]); - - testLex("-.2F" , [ Token(symbol!"Value",loc,Value(cast( float)-0.2)) ]); - testLex("-.2" , [ Token(symbol!"Value",loc,Value(cast(double)-0.2)) ]); - testLex("-.2D" , [ Token(symbol!"Value",loc,Value(cast(double)-0.2)) ]); - testLex("-.2BD", [ Token(symbol!"Value",loc,Value(cast( real)-0.2)) ]); - - testLex( "0.0" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); - testLex( "0.0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); - testLex( "0.0BD", [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]); - testLex("-0.0" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); - testLex("-0.0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); - testLex("-0.0BD", [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]); - testLex( "7F" , [ Token(symbol!"Value",loc,Value(cast( float)7.0)) ]); - testLex( "7D" , [ Token(symbol!"Value",loc,Value(cast(double)7.0)) ]); - testLex( "7BD" , [ Token(symbol!"Value",loc,Value(cast( real)7.0)) ]); - testLex( "0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); - testLex( "0D" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); - testLex( "0BD" , [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]); - testLex("-0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); - testLex("-0D" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); - testLex("-0BD" , [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]); - - testLex("1.2 F", [ - Token(symbol!"Value",loc,Value(cast(double)1.2)), - Token(symbol!"Ident",loc,Value( null),"F"), - ]); - testLexThrows("1.2A"); - testLexThrows("1.2B"); - testLexThrows("1.2BDF"); - - testLex("1.2;", [ - Token(symbol!"Value",loc,Value(cast(double)1.2)), - Token(symbol!"EOL",loc), - ]); - - testLex("1.2F;", [ - Token(symbol!"Value",loc,Value(cast(float)1.2)), - Token(symbol!"EOL",loc), - ]); - - testLex("1.2BD;", [ - Token(symbol!"Value",loc,Value(cast(real)1.2)), - Token(symbol!"EOL",loc), - ]); - - // Booleans and null - testLex("true", [ Token(symbol!"Value",loc,Value( true)) ]); - testLex("false", [ Token(symbol!"Value",loc,Value(false)) ]); - testLex("on", [ Token(symbol!"Value",loc,Value( true)) ]); - testLex("off", [ Token(symbol!"Value",loc,Value(false)) ]); - testLex("null", [ Token(symbol!"Value",loc,Value( null)) ]); - - testLex("TRUE", [ Token(symbol!"Ident",loc,Value(null),"TRUE") ]); - testLex("true ", [ Token(symbol!"Value",loc,Value(true)) ]); - testLex("true ", [ Token(symbol!"Value",loc,Value(true)) ]); - testLex("tru", [ Token(symbol!"Ident",loc,Value(null),"tru") ]); - testLex("truX", [ Token(symbol!"Ident",loc,Value(null),"truX") ]); - testLex("trueX", [ Token(symbol!"Ident",loc,Value(null),"trueX") ]); - - // Raw Backtick Strings - testLex("`hello world`", [ Token(symbol!"Value",loc,Value(`hello world` )) ]); - testLex("` hello world `", [ Token(symbol!"Value",loc,Value(` hello world ` )) ]); - testLex("`hello \\t world`", [ Token(symbol!"Value",loc,Value(`hello \t world`)) ]); - testLex("`hello \\n world`", [ Token(symbol!"Value",loc,Value(`hello \n world`)) ]); - testLex("`hello \n world`", [ Token(symbol!"Value",loc,Value("hello \n world")) ]); - testLex("`hello \r\n world`", [ Token(symbol!"Value",loc,Value("hello \r\n world")) ]); - testLex("`hello \"world\"`", [ Token(symbol!"Value",loc,Value(`hello "world"` )) ]); - - testLexThrows("`foo"); - testLexThrows("`"); - - // Double-Quote Strings - testLex(`"hello world"`, [ Token(symbol!"Value",loc,Value("hello world" )) ]); - testLex(`" hello world "`, [ Token(symbol!"Value",loc,Value(" hello world " )) ]); - testLex(`"hello \t world"`, [ Token(symbol!"Value",loc,Value("hello \t world")) ]); - testLex(`"hello \n world"`, [ Token(symbol!"Value",loc,Value("hello \n world")) ]); - testLex("\"hello \\\n world\"", [ Token(symbol!"Value",loc,Value("hello world" )) ]); - testLex("\"hello \\ \n world\"", [ Token(symbol!"Value",loc,Value("hello world" )) ]); - testLex("\"hello \\ \n\n world\"", [ Token(symbol!"Value",loc,Value("hello world" )) ]); - testLex(`"\"hello world\""`, [ Token(symbol!"Value",loc,Value(`"hello world"` )) ]); - testLex(`""`, [ Token(symbol!"Value",loc,Value("" )) ]); // issue #34 - - testLexThrows("\"hello \n world\""); - testLexThrows(`"foo`); - testLexThrows(`"`); - - // Characters - testLex("'a'", [ Token(symbol!"Value",loc,Value(cast(dchar) 'a')) ]); - testLex("'\\n'", [ Token(symbol!"Value",loc,Value(cast(dchar)'\n')) ]); - testLex("'\\t'", [ Token(symbol!"Value",loc,Value(cast(dchar)'\t')) ]); - testLex("'\t'", [ Token(symbol!"Value",loc,Value(cast(dchar)'\t')) ]); - testLex("'\\''", [ Token(symbol!"Value",loc,Value(cast(dchar)'\'')) ]); - testLex(`'\\'`, [ Token(symbol!"Value",loc,Value(cast(dchar)'\\')) ]); - - testLexThrows("'a"); - testLexThrows("'aa'"); - testLexThrows("''"); - testLexThrows("'\\\n'"); - testLexThrows("'\n'"); - testLexThrows(`'\`); - testLexThrows(`'\'`); - testLexThrows("'"); - - // Unicode - testLex("日本語", [ Token(symbol!"Ident",loc,Value(null), "日本語") ]); - testLex("`おはよう、日本。`", [ Token(symbol!"Value",loc,Value(`おはよう、日本。`)) ]); - testLex(`"おはよう、日本。"`, [ Token(symbol!"Value",loc,Value(`おはよう、日本。`)) ]); - testLex("'月'", [ Token(symbol!"Value",loc,Value("月"d.dup[0])) ]); - - // Base64 Binary - testLex("[aGVsbG8gd29ybGQ=]", [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]); - testLex("[ aGVsbG8gd29ybGQ= ]", [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]); - testLex("[\n aGVsbG8g \n \n d29ybGQ= \n]", [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]); - - testLexThrows("[aGVsbG8gd29ybGQ]"); // Ie: Not multiple of 4 - testLexThrows("[ aGVsbG8gd29ybGQ ]"); - - // Date - testLex( "1999/12/5", [ Token(symbol!"Value",loc,Value(Date( 1999, 12, 5))) ]); - testLex( "2013/2/22", [ Token(symbol!"Value",loc,Value(Date( 2013, 2, 22))) ]); - testLex("-2013/2/22", [ Token(symbol!"Value",loc,Value(Date(-2013, 2, 22))) ]); - - testLexThrows("7/"); - testLexThrows("2013/2/22a"); - testLexThrows("2013/2/22f"); - - testLex("1999/12/5\n", [ - Token(symbol!"Value",loc,Value(Date(1999, 12, 5))), - Token(symbol!"EOL",loc), - ]); - - // DateTime, no timezone - testLex( "2013/2/22 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22 \t 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22/*foo*/07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22 /*foo*/ \\\n /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22 /*foo*/ \\\n\n \n /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22 /*foo*/ \\\n\\\n \\\n /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22/*foo*/\\\n/*bar*/07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex("-2013/2/22 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime(-2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22 -07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53)))) ]); - testLex("-2013/2/22 -07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53)))) ]); - testLex( "2013/2/22 07:53:34", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34)))) ]); - testLex( "2013/2/22 07:53:34.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs))) ]); - testLex( "2013/2/22 07:53:34.12", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 120.msecs))) ]); - testLex( "2013/2/22 07:53:34.1", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 100.msecs))) ]); - testLex( "2013/2/22 07:53.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs))) ]); - - testLex( "2013/2/22 34:65", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds( 0)))) ]); - testLex( "2013/2/22 34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds(77), 123.msecs))) ]); - testLex( "2013/2/22 34:65.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds( 0), 123.msecs))) ]); - - testLex( "2013/2/22 -34:65", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0)))) ]); - testLex( "2013/2/22 -34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds(77), -123.msecs))) ]); - testLex( "2013/2/22 -34:65.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0), -123.msecs))) ]); - - testLexThrows("2013/2/22 07:53a"); - testLexThrows("2013/2/22 07:53f"); - testLexThrows("2013/2/22 07:53:34.123a"); - testLexThrows("2013/2/22 07:53:34.123f"); - testLexThrows("2013/2/22a 07:53"); - - testLex(`2013/2/22 "foo"`, [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value("foo")), - ]); - - testLex("2013/2/22 07", [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value(cast(int)7)), - ]); - - testLex("2013/2/22 1.2F", [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value(cast(float)1.2)), - ]); - - testLex("2013/2/22 .2F", [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value(cast(float)0.2)), - ]); - - testLex("2013/2/22 -1.2F", [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value(cast(float)-1.2)), - ]); - - testLex("2013/2/22 -.2F", [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value(cast(float)-0.2)), - ]); - - // DateTime, with known timezone - testLex( "2013/2/22 07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex("-2013/2/22 07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime(-2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 -07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex("-2013/2/22 -07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 07:53-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); - testLex( "2013/2/22 07:53-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - testLex( "2013/2/22 07:53:34-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 07:53:34-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); - testLex( "2013/2/22 07:53:34-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - testLex( "2013/2/22 07:53:34.123-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 07:53:34.123-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); - testLex( "2013/2/22 07:53:34.123-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - testLex( "2013/2/22 07:53.123-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 07:53.123-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); - testLex( "2013/2/22 07:53.123-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - - testLex( "2013/2/22 -34:65-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - - // DateTime, with Java SDLang's occasionally weird interpretation of some - // "not quite ISO" variations of the "GMT with offset" timezone strings. - Token testTokenSimpleTimeZone(Duration d) - { - auto dateTime = DateTime(2013, 2, 22, 7, 53, 0); - auto tz = new immutable SimpleTimeZone(d); - return Token( symbol!"Value", loc, Value(SysTime(dateTime,tz)) ); - } - Token testTokenUnknownTimeZone(string tzName) - { - auto dateTime = DateTime(2013, 2, 22, 7, 53, 0); - auto frac = 0.msecs; - return Token( symbol!"Value", loc, Value(DateTimeFracUnknownZone(dateTime,frac,tzName)) ); - } - testLex("2013/2/22 07:53-GMT+", [ testTokenUnknownTimeZone("GMT+") ]); - testLex("2013/2/22 07:53-GMT+:", [ testTokenUnknownTimeZone("GMT+:") ]); - testLex("2013/2/22 07:53-GMT+:3", [ testTokenUnknownTimeZone("GMT+:3") ]); - testLex("2013/2/22 07:53-GMT+:03", [ testTokenSimpleTimeZone(minutes(3)) ]); - testLex("2013/2/22 07:53-GMT+:003", [ testTokenUnknownTimeZone("GMT+:003") ]); - - testLex("2013/2/22 07:53-GMT+4", [ testTokenSimpleTimeZone(hours(4)) ]); - testLex("2013/2/22 07:53-GMT+4:", [ testTokenUnknownTimeZone("GMT+4:") ]); - testLex("2013/2/22 07:53-GMT+4:3", [ testTokenUnknownTimeZone("GMT+4:3") ]); - testLex("2013/2/22 07:53-GMT+4:03", [ testTokenSimpleTimeZone(hours(4)+minutes(3)) ]); - testLex("2013/2/22 07:53-GMT+4:003", [ testTokenUnknownTimeZone("GMT+4:003") ]); - - testLex("2013/2/22 07:53-GMT+04", [ testTokenSimpleTimeZone(hours(4)) ]); - testLex("2013/2/22 07:53-GMT+04:", [ testTokenUnknownTimeZone("GMT+04:") ]); - testLex("2013/2/22 07:53-GMT+04:3", [ testTokenUnknownTimeZone("GMT+04:3") ]); - testLex("2013/2/22 07:53-GMT+04:03", [ testTokenSimpleTimeZone(hours(4)+minutes(3)) ]); - testLex("2013/2/22 07:53-GMT+04:03abc", [ testTokenUnknownTimeZone("GMT+04:03abc") ]); - testLex("2013/2/22 07:53-GMT+04:003", [ testTokenUnknownTimeZone("GMT+04:003") ]); - - testLex("2013/2/22 07:53-GMT+004", [ testTokenSimpleTimeZone(minutes(4)) ]); - testLex("2013/2/22 07:53-GMT+004:", [ testTokenUnknownTimeZone("GMT+004:") ]); - testLex("2013/2/22 07:53-GMT+004:3", [ testTokenUnknownTimeZone("GMT+004:3") ]); - testLex("2013/2/22 07:53-GMT+004:03", [ testTokenUnknownTimeZone("GMT+004:03") ]); - testLex("2013/2/22 07:53-GMT+004:003", [ testTokenUnknownTimeZone("GMT+004:003") ]); - - testLex("2013/2/22 07:53-GMT+0004", [ testTokenSimpleTimeZone(minutes(4)) ]); - testLex("2013/2/22 07:53-GMT+0004:", [ testTokenUnknownTimeZone("GMT+0004:") ]); - testLex("2013/2/22 07:53-GMT+0004:3", [ testTokenUnknownTimeZone("GMT+0004:3") ]); - testLex("2013/2/22 07:53-GMT+0004:03", [ testTokenUnknownTimeZone("GMT+0004:03") ]); - testLex("2013/2/22 07:53-GMT+0004:003", [ testTokenUnknownTimeZone("GMT+0004:003") ]); - - testLex("2013/2/22 07:53-GMT+00004", [ testTokenSimpleTimeZone(minutes(4)) ]); - testLex("2013/2/22 07:53-GMT+00004:", [ testTokenUnknownTimeZone("GMT+00004:") ]); - testLex("2013/2/22 07:53-GMT+00004:3", [ testTokenUnknownTimeZone("GMT+00004:3") ]); - testLex("2013/2/22 07:53-GMT+00004:03", [ testTokenUnknownTimeZone("GMT+00004:03") ]); - testLex("2013/2/22 07:53-GMT+00004:003", [ testTokenUnknownTimeZone("GMT+00004:003") ]); - - // DateTime, with unknown timezone - testLex( "2013/2/22 07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 0), 0.msecs, "Bogus/Foo")), "2013/2/22 07:53-Bogus/Foo") ]); - testLex("-2013/2/22 07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 7, 53, 0), 0.msecs, "Bogus/Foo"))) ]); - testLex( "2013/2/22 -07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), 0.msecs, "Bogus/Foo"))) ]); - testLex("-2013/2/22 -07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), 0.msecs, "Bogus/Foo"))) ]); - testLex( "2013/2/22 07:53:34-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34), 0.msecs, "Bogus/Foo"))) ]); - testLex( "2013/2/22 07:53:34.123-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, "Bogus/Foo"))) ]); - testLex( "2013/2/22 07:53.123-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, "Bogus/Foo"))) ]); - - // Time Span - testLex( "12:14:42", [ Token(symbol!"Value",loc,Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs( 0))) ]); - testLex("-12:14:42", [ Token(symbol!"Value",loc,Value(-days( 0)-hours(12)-minutes(14)-seconds(42)-msecs( 0))) ]); - testLex( "00:09:12", [ Token(symbol!"Value",loc,Value( days( 0)+hours( 0)+minutes( 9)+seconds(12)+msecs( 0))) ]); - testLex( "00:00:01.023", [ Token(symbol!"Value",loc,Value( days( 0)+hours( 0)+minutes( 0)+seconds( 1)+msecs( 23))) ]); - testLex( "23d:05:21:23.532", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(532))) ]); - testLex( "23d:05:21:23.53", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(530))) ]); - testLex( "23d:05:21:23.5", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(500))) ]); - testLex("-23d:05:21:23.532", [ Token(symbol!"Value",loc,Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(532))) ]); - testLex("-23d:05:21:23.5", [ Token(symbol!"Value",loc,Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(500))) ]); - testLex( "23d:05:21:23", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs( 0))) ]); - - testLexThrows("12:14:42a"); - testLexThrows("23d:05:21:23.532a"); - testLexThrows("23d:05:21:23.532f"); - - // Combination - testLex("foo. 7", [ - Token(symbol!"Ident",loc,Value( null),"foo."), - Token(symbol!"Value",loc,Value(cast(int)7)) - ]); - - testLex(` - namespace:person "foo" "bar" 1 23L name.first="ひとみ" name.last="Smith" { - namespace:age 37; namespace:favorite_color "blue" // comment - somedate 2013/2/22 07:53 -- comment - - inventory /* comment */ { - socks - } - } - `, - [ - Token(symbol!"EOL",loc,Value(null),"\n"), - - Token(symbol!"Ident", loc, Value( null ), "namespace"), - Token(symbol!":", loc, Value( null ), ":"), - Token(symbol!"Ident", loc, Value( null ), "person"), - Token(symbol!"Value", loc, Value( "foo" ), `"foo"`), - Token(symbol!"Value", loc, Value( "bar" ), `"bar"`), - Token(symbol!"Value", loc, Value( cast( int) 1 ), "1"), - Token(symbol!"Value", loc, Value( cast(long)23 ), "23L"), - Token(symbol!"Ident", loc, Value( null ), "name.first"), - Token(symbol!"=", loc, Value( null ), "="), - Token(symbol!"Value", loc, Value( "ひとみ" ), `"ひとみ"`), - Token(symbol!"Ident", loc, Value( null ), "name.last"), - Token(symbol!"=", loc, Value( null ), "="), - Token(symbol!"Value", loc, Value( "Smith" ), `"Smith"`), - Token(symbol!"{", loc, Value( null ), "{"), - Token(symbol!"EOL", loc, Value( null ), "\n"), - - Token(symbol!"Ident", loc, Value( null ), "namespace"), - Token(symbol!":", loc, Value( null ), ":"), - Token(symbol!"Ident", loc, Value( null ), "age"), - Token(symbol!"Value", loc, Value( cast(int)37 ), "37"), - Token(symbol!"EOL", loc, Value( null ), ";"), - Token(symbol!"Ident", loc, Value( null ), "namespace"), - Token(symbol!":", loc, Value( null ), ":"), - Token(symbol!"Ident", loc, Value( null ), "favorite_color"), - Token(symbol!"Value", loc, Value( "blue" ), `"blue"`), - Token(symbol!"EOL", loc, Value( null ), "\n"), - - Token(symbol!"Ident", loc, Value( null ), "somedate"), - Token(symbol!"Value", loc, Value( DateTimeFrac(DateTime(2013, 2, 22, 7, 53, 0)) ), "2013/2/22 07:53"), - Token(symbol!"EOL", loc, Value( null ), "\n"), - Token(symbol!"EOL", loc, Value( null ), "\n"), - - Token(symbol!"Ident", loc, Value(null), "inventory"), - Token(symbol!"{", loc, Value(null), "{"), - Token(symbol!"EOL", loc, Value(null), "\n"), - - Token(symbol!"Ident", loc, Value(null), "socks"), - Token(symbol!"EOL", loc, Value(null), "\n"), - - Token(symbol!"}", loc, Value(null), "}"), - Token(symbol!"EOL", loc, Value(null), "\n"), - - Token(symbol!"}", loc, Value(null), "}"), - Token(symbol!"EOL", loc, Value(null), "\n"), - ]); - - if(numErrors > 0) - stderr.writeln(numErrors, " failed test(s)"); -} - -@("lexer: Regression test issue #8") -unittest -{ - testLex(`"\n \n"`, [ Token(symbol!"Value",loc,Value("\n \n"),`"\n \n"`) ]); - testLex(`"\t\t"`, [ Token(symbol!"Value",loc,Value("\t\t"),`"\t\t"`) ]); - testLex(`"\n\n"`, [ Token(symbol!"Value",loc,Value("\n\n"),`"\n\n"`) ]); -} - -@("lexer: Regression test issue #11") -unittest -{ - void test(string input) - { - testLex( - input, - [ - Token(symbol!"EOL", loc, Value(null), "\n"), - Token(symbol!"Ident",loc,Value(null), "a") - ] - ); - } - - test("//X\na"); - test("//\na"); - test("--\na"); - test("#\na"); -} - -@("ast: Regression test issue #28") -unittest -{ - enum offset = 1; // workaround for an of-by-one error for line numbers - testLex("test", [ - Token(symbol!"Ident", Location("filename", 0, 0, 0), Value(null), "test") - ], true); - testLex("\ntest", [ - Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\n"), - Token(symbol!"Ident", Location("filename", 1, 0, 1), Value(null), "test") - ], true); - testLex("\rtest", [ - Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r"), - Token(symbol!"Ident", Location("filename", 1, 0, 1), Value(null), "test") - ], true); - testLex("\r\ntest", [ - Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r\n"), - Token(symbol!"Ident", Location("filename", 1, 0, 2), Value(null), "test") - ], true); - testLex("\r\n\ntest", [ - Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r\n"), - Token(symbol!"EOL", Location("filename", 1, 0, 2), Value(null), "\n"), - Token(symbol!"Ident", Location("filename", 2, 0, 3), Value(null), "test") - ], true); - testLex("\r\r\ntest", [ - Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r"), - Token(symbol!"EOL", Location("filename", 1, 0, 1), Value(null), "\r\n"), - Token(symbol!"Ident", Location("filename", 2, 0, 3), Value(null), "test") - ], true); -} diff --git a/src/sdlang/libinputvisitor/dub.json b/src/sdlang/libinputvisitor/dub.json deleted file mode 100644 index 6e273c8..0000000 --- a/src/sdlang/libinputvisitor/dub.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "libinputvisitor", - "description": "Write D input range generators in a straightforward coroutine style", - "authors": ["Nick Sabalausky"], - "homepage": "https://github.com/abscissa/libInputVisitor", - "license": "WTFPL", - "sourcePaths": ["."], - "importPaths": ["."], - "excludedSourceFiles": ["libInputVisitorExample.d"] -} diff --git a/src/sdlang/libinputvisitor/libInputVisitor.d b/src/sdlang/libinputvisitor/libInputVisitor.d deleted file mode 100644 index f29dc4f..0000000 --- a/src/sdlang/libinputvisitor/libInputVisitor.d +++ /dev/null @@ -1,113 +0,0 @@ -/++ -Copyright (C) 2012 Nick Sabalausky <http://semitwist.com/contact> - -This program is free software. It comes without any warranty, to -the extent permitted by applicable law. You can redistribute it -and/or modify it under the terms of the Do What The Fuck You Want -To Public License, Version 2, as published by Sam Hocevar. See -http://www.wtfpl.net/ for more details. - - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - Version 2, December 2004 - -Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> - -Everyone is permitted to copy and distribute verbatim or modified -copies of this license document, and changing it is allowed as long -as the name is changed. - - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - -0. You just DO WHAT THE FUCK YOU WANT TO. -+/ - -/++ -Should work with DMD 2.059 and up - -For more info on this, see: -http://semitwist.com/articles/article/view/combine-coroutines-and-input-ranges-for-dead-simple-d-iteration -+/ - -import core.thread; - -class InputVisitor(Obj, Elem) : Fiber -{ - bool started = false; - Obj obj; - this(Obj obj) - { - this.obj = obj; - - version(Windows) // Issue #1 - { - import core.sys.windows.windows : SYSTEM_INFO, GetSystemInfo; - SYSTEM_INFO info; - GetSystemInfo(&info); - auto PAGESIZE = info.dwPageSize; - - super(&run, PAGESIZE * 16); - } - else - super(&run); - } - - this(Obj obj, size_t stackSize) - { - this.obj = obj; - super(&run, stackSize); - } - - private void run() - { - obj.visit(this); - } - - private void ensureStarted() - { - if(!started) - { - call(); - started = true; - } - } - - // Member 'front' must be a function due to DMD Issue #5403 - private Elem _front = Elem.init; // Default initing here avoids "Error: field _front must be initialized in constructor" - @property Elem front() - { - ensureStarted(); - return _front; - } - - void popFront() - { - ensureStarted(); - call(); - } - - @property bool empty() - { - ensureStarted(); - return state == Fiber.State.TERM; - } - - void yield(Elem elem) - { - _front = elem; - Fiber.yield(); - } -} - -template inputVisitor(Elem) -{ - @property InputVisitor!(Obj, Elem) inputVisitor(Obj)(Obj obj) - { - return new InputVisitor!(Obj, Elem)(obj); - } - - @property InputVisitor!(Obj, Elem) inputVisitor(Obj)(Obj obj, size_t stackSize) - { - return new InputVisitor!(Obj, Elem)(obj, stackSize); - } -} diff --git a/src/sdlang/package.d b/src/sdlang/package.d deleted file mode 100644 index dd8df1a..0000000 --- a/src/sdlang/package.d +++ /dev/null @@ -1,133 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -/++ -$(H2 SDLang-D v0.10.0) - -Library for parsing and generating SDL (Simple Declarative Language). - -Import this module to use SDLang-D as a library. - -For the list of officially supported compiler versions, see the -$(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/.travis.yml, .travis.yml) -file included with your version of SDLang-D. - -Links: -$(UL - $(LI $(LINK2 http://sdlang.org/, SDLang Language Homepage) ) - $(LI $(LINK2 https://github.com/Abscissa/SDLang-D, SDLang-D Homepage) ) - $(LI $(LINK2 http://semitwist.com/sdlang-d, SDLang-D API Reference (latest version) ) ) - $(LI $(LINK2 http://semitwist.com/sdlang-d-docs, SDLang-D API Reference (earlier versions) ) ) - $(LI $(LINK2 http://sdl.ikayzo.org/display/SDL/Language+Guide, Old Official SDL Site) [$(LINK2 http://semitwist.com/sdl-mirror/Language+Guide.html, mirror)] ) -) - -Authors: Nick Sabalausky ("Abscissa") http://semitwist.com/contact -Copyright: -Copyright (C) 2012-2016 Nick Sabalausky. - -License: $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/LICENSE.txt, zlib/libpng) -+/ - -module sdlang; - -import std.array; -import std.datetime; -import std.file; -import std.stdio; - -import sdlang.ast; -import sdlang.exception; -import sdlang.lexer; -import sdlang.parser; -import sdlang.symbol; -import sdlang.token; -import sdlang.util; - -// Expose main public API -public import sdlang.ast : Attribute, Tag; -public import sdlang.exception; -public import sdlang.parser : parseFile, parseSource; -public import sdlang.token : Value, Token, DateTimeFrac, DateTimeFracUnknownZone; -public import sdlang.util : sdlangVersion, Location; - -version(sdlangUsingBuiltinTestRunner) - void main() {} - -version(sdlangCliApp) -{ - int main(string[] args) - { - if( - args.length != 3 || - (args[1] != "lex" && args[1] != "parse" && args[1] != "to-sdl") - ) - { - stderr.writeln("SDLang-D v", sdlangVersion); - stderr.writeln("Usage: sdlang [lex|parse|to-sdl] filename.sdl"); - return 1; - } - - auto filename = args[2]; - - try - { - if(args[1] == "lex") - doLex(filename); - else if(args[1] == "parse") - doParse(filename); - else - doToSDL(filename); - } - catch(ParseException e) - { - stderr.writeln(e.msg); - return 1; - } - - return 0; - } - - void doLex(string filename) - { - auto source = cast(string)read(filename); - auto lexer = new Lexer(source, filename); - - foreach(tok; lexer) - { - // Value - string value; - if(tok.symbol == symbol!"Value") - value = tok.value.hasValue? toString(tok.value.type) : "{null}"; - - value = value==""? "\t" : "("~value~":"~tok.value.toString()~") "; - - // Data - auto data = tok.data.replace("\n", "").replace("\r", ""); - if(data != "") - data = "\t|"~tok.data~"|"; - - // Display - writeln( - tok.location.toString, ":\t", - tok.symbol.name, value, - data - ); - - if(tok.symbol.name == "Error") - break; - } - } - - void doParse(string filename) - { - auto root = parseFile(filename); - stdout.rawWrite(root.toDebugString()); - writeln(); - } - - void doToSDL(string filename) - { - auto root = parseFile(filename); - stdout.rawWrite(root.toSDLDocument()); - } -} diff --git a/src/sdlang/parser.d b/src/sdlang/parser.d deleted file mode 100644 index c9b8d4f..0000000 --- a/src/sdlang/parser.d +++ /dev/null @@ -1,628 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.parser; - -import std.file; - -import libInputVisitor; -import taggedalgebraic; - -import sdlang.ast; -import sdlang.exception; -import sdlang.lexer; -import sdlang.symbol; -import sdlang.token; -import sdlang.util; - -/// Returns root tag. -Tag parseFile(string filename) -{ - auto source = cast(string)read(filename); - return parseSource(source, filename); -} - -/// Returns root tag. The optional `filename` parameter can be included -/// so that the SDLang document's filename (if any) can be displayed with -/// any syntax error messages. -Tag parseSource(string source, string filename=null) -{ - auto lexer = new Lexer(source, filename); - auto parser = DOMParser(lexer); - return parser.parseRoot(); -} - -/++ -Parses an SDL document using StAX/Pull-style. Returns an InputRange with -element type ParserEvent. - -The pullParseFile version reads a file and parses it, while pullParseSource -parses a string passed in. The optional `filename` parameter in pullParseSource -can be included so that the SDLang document's filename (if any) can be displayed -with any syntax error messages. - -Note: The old FileStartEvent and FileEndEvent events -$(LINK2 https://github.com/Abscissa/SDLang-D/issues/17, were deemed unnessecary) -and removed as of SDLang-D v0.10.0. - -Note: Previously, in SDLang-D v0.9.x, ParserEvent was a -$(LINK2 http://dlang.org/phobos/std_variant.html#.Algebraic, std.variant.Algebraic). -As of SDLang-D v0.10.0, it is now a -$(LINK2 https://github.com/s-ludwig/taggedalgebraic, TaggedAlgebraic), -so usage has changed somewhat. - -Example: ------------------- -parent 12 attr="q" { - childA 34 - childB 56 -} -lastTag ------------------- - -The ParserEvent sequence emitted for that SDL document would be as -follows (indented for readability): ------------------- -TagStartEvent (parent) - ValueEvent (12) - AttributeEvent (attr, "q") - TagStartEvent (childA) - ValueEvent (34) - TagEndEvent - TagStartEvent (childB) - ValueEvent (56) - TagEndEvent -TagEndEvent -TagStartEvent (lastTag) -TagEndEvent ------------------- -+/ -auto pullParseFile(string filename) -{ - auto source = cast(string)read(filename); - return parseSource(source, filename); -} - -///ditto -auto pullParseSource(string source, string filename=null) -{ - auto lexer = new Lexer(source, filename); - auto parser = PullParser(lexer); - return inputVisitor!ParserEvent( parser ); -} - -/// -@("pullParseFile/pullParseSource example") -unittest -{ - // stuff.sdl - immutable stuffSdl = ` - name "sdlang-d" - description "An SDL (Simple Declarative Language) library for D." - homepage "http://github.com/Abscissa/SDLang-D" - - configuration "library" { - targetType "library" - } - `; - - import std.stdio; - - foreach(event; pullParseSource(stuffSdl)) - final switch(event.kind) - { - case ParserEvent.Kind.tagStart: - auto e = cast(TagStartEvent) event; - writeln("TagStartEvent: ", e.namespace, ":", e.name, " @ ", e.location); - break; - - case ParserEvent.Kind.tagEnd: - auto e = cast(TagEndEvent) event; - writeln("TagEndEvent"); - break; - - case ParserEvent.Kind.value: - auto e = cast(ValueEvent) event; - writeln("ValueEvent: ", e.value); - break; - - case ParserEvent.Kind.attribute: - auto e = cast(AttributeEvent) event; - writeln("AttributeEvent: ", e.namespace, ":", e.name, "=", e.value); - break; - } -} - -private union ParserEventUnion -{ - TagStartEvent tagStart; - TagEndEvent tagEnd; - ValueEvent value; - AttributeEvent attribute; -} - -/++ -The element of the InputRange returned by pullParseFile and pullParseSource. - -This is a tagged union, built from the following: -------- -alias ParserEvent = TaggedAlgebraic!ParserEventUnion; -private union ParserEventUnion -{ - TagStartEvent tagStart; - TagEndEvent tagEnd; - ValueEvent value; - AttributeEvent attribute; -} -------- - -Note: The old FileStartEvent and FileEndEvent events -$(LINK2 https://github.com/Abscissa/SDLang-D/issues/17, were deemed unnessecary) -and removed as of SDLang-D v0.10.0. - -Note: Previously, in SDLang-D v0.9.x, ParserEvent was a -$(LINK2 http://dlang.org/phobos/std_variant.html#.Algebraic, std.variant.Algebraic). -As of SDLang-D v0.10.0, it is now a -$(LINK2 https://github.com/s-ludwig/taggedalgebraic, TaggedAlgebraic), -so usage has changed somewhat. -+/ -alias ParserEvent = TaggedAlgebraic!ParserEventUnion; - -/// -@("ParserEvent example") -unittest -{ - // Create - ParserEvent event1 = TagStartEvent(); - ParserEvent event2 = TagEndEvent(); - ParserEvent event3 = ValueEvent(); - ParserEvent event4 = AttributeEvent(); - - // Check type - assert(event1.kind == ParserEvent.Kind.tagStart); - assert(event2.kind == ParserEvent.Kind.tagEnd); - assert(event3.kind == ParserEvent.Kind.value); - assert(event4.kind == ParserEvent.Kind.attribute); - - // Cast to base type - auto e1 = cast(TagStartEvent) event1; - auto e2 = cast(TagEndEvent) event2; - auto e3 = cast(ValueEvent) event3; - auto e4 = cast(AttributeEvent) event4; - //auto noGood = cast(AttributeEvent) event1; // AssertError: event1 is a TagStartEvent, not AttributeEvent. - - // Use as base type. - // In many cases, no casting is even needed. - event1.name = "foo"; - //auto noGood = event3.name; // AssertError: ValueEvent doesn't have a member 'name'. - - // Final switch is supported: - final switch(event1.kind) - { - case ParserEvent.Kind.tagStart: break; - case ParserEvent.Kind.tagEnd: break; - case ParserEvent.Kind.value: break; - case ParserEvent.Kind.attribute: break; - } -} - -/// Event: Start of tag -struct TagStartEvent -{ - Location location; - string namespace; - string name; -} - -/// Event: End of tag -struct TagEndEvent -{ - //Location location; -} - -/// Event: Found a Value in the current tag -struct ValueEvent -{ - Location location; - Value value; -} - -/// Event: Found an Attribute in the current tag -struct AttributeEvent -{ - Location location; - string namespace; - string name; - Value value; -} - -// The actual pull parser -private struct PullParser -{ - private Lexer lexer; - - private struct IDFull - { - string namespace; - string name; - } - - private void error(string msg) - { - error(lexer.front.location, msg); - } - - private void error(Location loc, string msg) - { - throw new ParseException(loc, "Error: "~msg); - } - - private InputVisitor!(PullParser, ParserEvent) v; - - void visit(InputVisitor!(PullParser, ParserEvent) v) - { - this.v = v; - parseRoot(); - } - - private void emit(Event)(Event event) - { - v.yield( ParserEvent(event) ); - } - - /// <Root> ::= <Tags> EOF (Lookaheads: Anything) - private void parseRoot() - { - //trace("Starting parse of file: ", lexer.filename); - //trace(__FUNCTION__, ": <Root> ::= <Tags> EOF (Lookaheads: Anything)"); - - auto startLocation = Location(lexer.filename, 0, 0, 0); - - parseTags(); - - auto token = lexer.front; - if(token.matches!":"()) - { - lexer.popFront(); - token = lexer.front; - if(token.matches!"Ident"()) - { - error("Missing namespace. If you don't wish to use a namespace, then say '"~token.data~"', not ':"~token.data~"'"); - assert(0); - } - else - { - error("Missing namespace. If you don't wish to use a namespace, then omit the ':'"); - assert(0); - } - } - else if(!token.matches!"EOF"()) - error("Expected a tag or end-of-file, not " ~ token.symbol.name); - } - - /// <Tags> ::= <Tag> <Tags> (Lookaheads: Ident Value) - /// | EOL <Tags> (Lookaheads: EOL) - /// | {empty} (Lookaheads: Anything else, except '{') - void parseTags() - { - //trace("Enter ", __FUNCTION__); - while(true) - { - auto token = lexer.front; - if(token.matches!"Ident"() || token.matches!"Value"()) - { - //trace(__FUNCTION__, ": <Tags> ::= <Tag> <Tags> (Lookaheads: Ident Value)"); - parseTag(); - continue; - } - else if(token.matches!"EOL"()) - { - //trace(__FUNCTION__, ": <Tags> ::= EOL <Tags> (Lookaheads: EOL)"); - lexer.popFront(); - continue; - } - else if(token.matches!"{"()) - { - error("Found start of child block, but no tag name. If you intended an anonymous "~ - "tag, you must have at least one value before any attributes or child tags."); - } - else - { - //trace(__FUNCTION__, ": <Tags> ::= {empty} (Lookaheads: Anything else, except '{')"); - break; - } - } - } - - /// <Tag> - /// ::= <IDFull> <Values> <Attributes> <OptChild> <TagTerminator> (Lookaheads: Ident) - /// | <Value> <Values> <Attributes> <OptChild> <TagTerminator> (Lookaheads: Value) - void parseTag() - { - auto token = lexer.front; - if(token.matches!"Ident"()) - { - //trace(__FUNCTION__, ": <Tag> ::= <IDFull> <Values> <Attributes> <OptChild> <TagTerminator> (Lookaheads: Ident)"); - //trace("Found tag named: ", tag.fullName); - auto id = parseIDFull(); - emit( TagStartEvent(token.location, id.namespace, id.name) ); - } - else if(token.matches!"Value"()) - { - //trace(__FUNCTION__, ": <Tag> ::= <Value> <Values> <Attributes> <OptChild> <TagTerminator> (Lookaheads: Value)"); - //trace("Found anonymous tag."); - emit( TagStartEvent(token.location, null, null) ); - } - else - error("Expected tag name or value, not " ~ token.symbol.name); - - if(lexer.front.matches!"="()) - error("Found attribute, but no tag name. If you intended an anonymous "~ - "tag, you must have at least one value before any attributes."); - - parseValues(); - parseAttributes(); - parseOptChild(); - parseTagTerminator(); - - emit( TagEndEvent() ); - } - - /// <IDFull> ::= Ident <IDSuffix> (Lookaheads: Ident) - IDFull parseIDFull() - { - auto token = lexer.front; - if(token.matches!"Ident"()) - { - //trace(__FUNCTION__, ": <IDFull> ::= Ident <IDSuffix> (Lookaheads: Ident)"); - lexer.popFront(); - return parseIDSuffix(token.data); - } - else - { - error("Expected namespace or identifier, not " ~ token.symbol.name); - assert(0); - } - } - - /// <IDSuffix> - /// ::= ':' Ident (Lookaheads: ':') - /// ::= {empty} (Lookaheads: Anything else) - IDFull parseIDSuffix(string firstIdent) - { - auto token = lexer.front; - if(token.matches!":"()) - { - //trace(__FUNCTION__, ": <IDSuffix> ::= ':' Ident (Lookaheads: ':')"); - lexer.popFront(); - token = lexer.front; - if(token.matches!"Ident"()) - { - lexer.popFront(); - return IDFull(firstIdent, token.data); - } - else - { - error("Expected name, not " ~ token.symbol.name); - assert(0); - } - } - else - { - //trace(__FUNCTION__, ": <IDSuffix> ::= {empty} (Lookaheads: Anything else)"); - return IDFull("", firstIdent); - } - } - - /// <Values> - /// ::= Value <Values> (Lookaheads: Value) - /// | {empty} (Lookaheads: Anything else) - void parseValues() - { - while(true) - { - auto token = lexer.front; - if(token.matches!"Value"()) - { - //trace(__FUNCTION__, ": <Values> ::= Value <Values> (Lookaheads: Value)"); - parseValue(); - continue; - } - else - { - //trace(__FUNCTION__, ": <Values> ::= {empty} (Lookaheads: Anything else)"); - break; - } - } - } - - /// Handle Value terminals that aren't part of an attribute - void parseValue() - { - auto token = lexer.front; - if(token.matches!"Value"()) - { - //trace(__FUNCTION__, ": (Handle Value terminals that aren't part of an attribute)"); - auto value = token.value; - //trace("In tag '", parent.fullName, "', found value: ", value); - emit( ValueEvent(token.location, value) ); - - lexer.popFront(); - } - else - error("Expected value, not "~token.symbol.name); - } - - /// <Attributes> - /// ::= <Attribute> <Attributes> (Lookaheads: Ident) - /// | {empty} (Lookaheads: Anything else) - void parseAttributes() - { - while(true) - { - auto token = lexer.front; - if(token.matches!"Ident"()) - { - //trace(__FUNCTION__, ": <Attributes> ::= <Attribute> <Attributes> (Lookaheads: Ident)"); - parseAttribute(); - continue; - } - else - { - //trace(__FUNCTION__, ": <Attributes> ::= {empty} (Lookaheads: Anything else)"); - break; - } - } - } - - /// <Attribute> ::= <IDFull> '=' Value (Lookaheads: Ident) - void parseAttribute() - { - //trace(__FUNCTION__, ": <Attribute> ::= <IDFull> '=' Value (Lookaheads: Ident)"); - auto token = lexer.front; - if(!token.matches!"Ident"()) - error("Expected attribute name, not "~token.symbol.name); - - auto id = parseIDFull(); - - token = lexer.front; - if(!token.matches!"="()) - error("Expected '=' after attribute name, not "~token.symbol.name); - - lexer.popFront(); - token = lexer.front; - if(!token.matches!"Value"()) - error("Expected attribute value, not "~token.symbol.name); - - //trace("In tag '", parent.fullName, "', found attribute '", attr.fullName, "'"); - emit( AttributeEvent(token.location, id.namespace, id.name, token.value) ); - - lexer.popFront(); - } - - /// <OptChild> - /// ::= '{' EOL <Tags> '}' (Lookaheads: '{') - /// | {empty} (Lookaheads: Anything else) - void parseOptChild() - { - auto token = lexer.front; - if(token.matches!"{") - { - //trace(__FUNCTION__, ": <OptChild> ::= '{' EOL <Tags> '}' (Lookaheads: '{')"); - lexer.popFront(); - token = lexer.front; - if(!token.matches!"EOL"()) - error("Expected newline or semicolon after '{', not "~token.symbol.name); - - lexer.popFront(); - parseTags(); - - token = lexer.front; - if(!token.matches!"}"()) - error("Expected '}' after child tags, not "~token.symbol.name); - lexer.popFront(); - } - else - { - //trace(__FUNCTION__, ": <OptChild> ::= {empty} (Lookaheads: Anything else)"); - // Do nothing, no error. - } - } - - /// <TagTerminator> - /// ::= EOL (Lookahead: EOL) - /// | {empty} (Lookahead: EOF) - void parseTagTerminator() - { - auto token = lexer.front; - if(token.matches!"EOL") - { - //trace(__FUNCTION__, ": <TagTerminator> ::= EOL (Lookahead: EOL)"); - lexer.popFront(); - } - else if(token.matches!"EOF") - { - //trace(__FUNCTION__, ": <TagTerminator> ::= {empty} (Lookahead: EOF)"); - // Do nothing - } - else - error("Expected end of tag (newline, semicolon or end-of-file), not " ~ token.symbol.name); - } -} - -private struct DOMParser -{ - Lexer lexer; - - Tag parseRoot() - { - auto currTag = new Tag(null, null, "root"); - currTag.location = Location(lexer.filename, 0, 0, 0); - - auto parser = PullParser(lexer); - auto eventRange = inputVisitor!ParserEvent( parser ); - - foreach(event; eventRange) - final switch(event.kind) - { - case ParserEvent.Kind.tagStart: - auto newTag = new Tag(currTag, event.namespace, event.name); - newTag.location = event.location; - - currTag = newTag; - break; - - case ParserEvent.Kind.tagEnd: - currTag = currTag.parent; - - if(!currTag) - parser.error("Internal Error: Received an extra TagEndEvent"); - break; - - case ParserEvent.Kind.value: - currTag.add((cast(ValueEvent)event).value); - break; - - case ParserEvent.Kind.attribute: - auto e = cast(AttributeEvent) event; - auto attr = new Attribute(e.namespace, e.name, e.value, e.location); - currTag.add(attr); - break; - } - - return currTag; - } -} - -// Other parser tests are part of the AST's tests over in the ast module. - -// Regression test, issue #13: https://github.com/Abscissa/SDLang-D/issues/13 -// "Incorrectly accepts ":tagname" (blank namespace, tagname prefixed with colon)" -@("parser: Regression test issue #13") -unittest -{ - import std.exception; - assertThrown!ParseException(parseSource(`:test`)); - assertThrown!ParseException(parseSource(`:4`)); -} - -// Regression test, issue #16: https://github.com/Abscissa/SDLang-D/issues/16 -@("parser: Regression test issue #16") -unittest -{ - // Shouldn't crash - foreach(event; pullParseSource(`tag "data"`)) - { - if(event.kind == ParserEvent.Kind.tagStart) - auto e = cast(TagStartEvent) event; - } -} - -// Regression test, issue #31: https://github.com/Abscissa/SDLang-D/issues/31 -// "Escape sequence results in range violation error" -@("parser: Regression test issue #31") -unittest -{ - // Shouldn't get a Range violation - parseSource(`test "\"foo\""`); -} diff --git a/src/sdlang/symbol.d b/src/sdlang/symbol.d deleted file mode 100644 index ebb2b93..0000000 --- a/src/sdlang/symbol.d +++ /dev/null @@ -1,61 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.symbol; - -import std.algorithm; - -static immutable validSymbolNames = [ - "Error", - "EOF", - "EOL", - - ":", - "=", - "{", - "}", - - "Ident", - "Value", -]; - -/// Use this to create a Symbol. Ex: symbol!"Value" or symbol!"=" -/// Invalid names (such as symbol!"FooBar") are rejected at compile-time. -template symbol(string name) -{ - static assert(validSymbolNames.find(name), "Invalid Symbol: '"~name~"'"); - immutable symbol = _symbol(name); -} - -private Symbol _symbol(string name) -{ - return Symbol(name); -} - -/// Symbol is essentially the "type" of a Token. -/// Token is like an instance of a Symbol. -/// -/// This only represents terminals. Nonterminal tokens aren't -/// constructed since the AST is built directly during parsing. -/// -/// You can't create a Symbol directly. Instead, use the `symbol` -/// template. -struct Symbol -{ - private string _name; - @property string name() - { - return _name; - } - - @disable this(); - private this(string name) - { - this._name = name; - } - - string toString() - { - return _name; - } -} diff --git a/src/sdlang/taggedalgebraic/taggedalgebraic.d b/src/sdlang/taggedalgebraic/taggedalgebraic.d deleted file mode 100644 index ffaac49..0000000 --- a/src/sdlang/taggedalgebraic/taggedalgebraic.d +++ /dev/null @@ -1,1085 +0,0 @@ -/** - * Algebraic data type implementation based on a tagged union. - * - * Copyright: Copyright 2015, Sönke Ludwig. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Sönke Ludwig -*/ -module taggedalgebraic; - -import std.typetuple; - -// TODO: -// - distinguish between @property and non@-property methods. -// - verify that static methods are handled properly - -/** Implements a generic algebraic type using an enum to identify the stored type. - - This struct takes a `union` or `struct` declaration as an input and builds - an algebraic data type from its fields, using an automatically generated - `Kind` enumeration to identify which field of the union is currently used. - Multiple fields with the same value are supported. - - All operators and methods are transparently forwarded to the contained - value. The caller has to make sure that the contained value supports the - requested operation. Failure to do so will result in an assertion failure. - - The return value of forwarded operations is determined as follows: - $(UL - $(LI If the type can be uniquely determined, it is used as the return - value) - $(LI If there are multiple possible return values and all of them match - the unique types defined in the `TaggedAlgebraic`, a - `TaggedAlgebraic` is returned.) - $(LI If there are multiple return values and none of them is a - `Variant`, an `Algebraic` of the set of possible return types is - returned.) - $(LI If any of the possible operations returns a `Variant`, this is used - as the return value.) - ) -*/ -struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct)) -{ - import std.algorithm : among; - import std.string : format; - import std.traits : FieldTypeTuple, FieldNameTuple, Largest, hasElaborateCopyConstructor, hasElaborateDestructor; - - private alias Union = U; - private alias FieldTypes = FieldTypeTuple!U; - private alias fieldNames = FieldNameTuple!U; - - static assert(FieldTypes.length > 0, "The TaggedAlgebraic's union type must have at least one field."); - static assert(FieldTypes.length == fieldNames.length); - - - private { - void[Largest!FieldTypes.sizeof] m_data = void; - Kind m_kind; - } - - /// A type enum that identifies the type of value currently stored. - alias Kind = TypeEnum!U; - - /// Compatibility alias - deprecated("Use 'Kind' instead.") alias Type = Kind; - - /// The type ID of the currently stored value. - @property Kind kind() const { return m_kind; } - - // Compatibility alias - deprecated("Use 'kind' instead.") - alias typeID = kind; - - // constructors - //pragma(msg, generateConstructors!U()); - mixin(generateConstructors!U); - - this(TaggedAlgebraic other) - { - import std.algorithm : swap; - swap(this, other); - } - - void opAssign(TaggedAlgebraic other) - { - import std.algorithm : swap; - swap(this, other); - } - - // postblit constructor - static if (anySatisfy!(hasElaborateCopyConstructor, FieldTypes)) - { - this(this) - { - switch (m_kind) { - default: break; - foreach (i, tname; fieldNames) { - alias T = typeof(__traits(getMember, U, tname)); - static if (hasElaborateCopyConstructor!T) - { - case __traits(getMember, Kind, tname): - typeid(T).postblit(cast(void*)&trustedGet!tname()); - return; - } - } - } - } - } - - // destructor - static if (anySatisfy!(hasElaborateDestructor, FieldTypes)) - { - ~this() - { - final switch (m_kind) { - foreach (i, tname; fieldNames) { - alias T = typeof(__traits(getMember, U, tname)); - case __traits(getMember, Kind, tname): - static if (hasElaborateDestructor!T) { - .destroy(trustedGet!tname); - } - return; - } - } - } - } - - /// Enables conversion or extraction of the stored value. - T opCast(T)() - { - import std.conv : to; - - final switch (m_kind) { - foreach (i, FT; FieldTypes) { - case __traits(getMember, Kind, fieldNames[i]): - static if (is(typeof(to!T(trustedGet!(fieldNames[i]))))) { - return to!T(trustedGet!(fieldNames[i])); - } else { - assert(false, "Cannot cast a "~(cast(Kind)m_kind).to!string~" value ("~FT.stringof~") to "~T.stringof); - } - } - } - assert(false); // never reached - } - /// ditto - T opCast(T)() const - { - // this method needs to be duplicated because inout doesn't work with to!() - import std.conv : to; - - final switch (m_kind) { - foreach (i, FT; FieldTypes) { - case __traits(getMember, Kind, fieldNames[i]): - static if (is(typeof(to!T(trustedGet!(fieldNames[i]))))) { - return to!T(trustedGet!(fieldNames[i])); - } else { - assert(false, "Cannot cast a "~(cast(Kind)m_kind).to!string~" value ("~FT.stringof~") to "~T.stringof); - } - } - } - assert(false); // never reached - } - - /// Uses `cast(string)`/`to!string` to return a string representation of the enclosed value. - string toString() const { return cast(string)this; } - - // NOTE: "this TA" is used here as the functional equivalent of inout, - // just that it generates one template instantiation per modifier - // combination, so that we can actually decide what to do for each - // case. - - /// Enables the invocation of methods of the stored value. - auto opDispatch(string name, this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.method, name)(this, args); } - /// Enables accessing properties/fields of the stored value. - @property auto opDispatch(string name, this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.field, name, ARGS) && !hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.field, name)(this, args); } - /// Enables equality comparison with the stored value. - auto opEquals(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "==", T)) { return implementOp!(OpKind.binary, "==")(this, other); } - /// Enables relational comparisons with the stored value. - auto opCmp(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "<", T)) { assert(false, "TODO!"); } - /// Enables the use of unary operators with the stored value. - auto opUnary(string op, this TA)() if (hasOp!(TA, OpKind.unary, op)) { return implementOp!(OpKind.unary, op)(this); } - /// Enables the use of binary operators with the stored value. - auto opBinary(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op, T)) { return implementOp!(OpKind.binary, op)(this, other); } - /// Enables the use of binary operators with the stored value. - auto opBinaryRight(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binaryRight, op, T)) { return implementOp!(OpKind.binaryRight, op)(this, other); } - /// Enables operator assignments on the stored value. - auto opOpAssign(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op~"=", T)) { return implementOp!(OpKind.binary, op~"=")(this, other); } - /// Enables indexing operations on the stored value. - auto opIndex(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.index, null, ARGS)) { return implementOp!(OpKind.index, null)(this, args); } - /// Enables index assignments on the stored value. - auto opIndexAssign(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.indexAssign, null, ARGS)) { return implementOp!(OpKind.indexAssign, null)(this, args); } - /// Enables call syntax operations on the stored value. - auto opCall(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.call, null, ARGS)) { return implementOp!(OpKind.call, null)(this, args); } - - private @trusted @property ref inout(typeof(__traits(getMember, U, f))) trustedGet(string f)() inout { return trustedGet!(inout(typeof(__traits(getMember, U, f)))); } - private @trusted @property ref inout(T) trustedGet(T)() inout { return *cast(inout(T)*)m_data.ptr; } -} - -/// -unittest -{ - import taggedalgebraic; - - struct Foo { - string name; - void bar() {} - } - - union Base { - int i; - string str; - Foo foo; - } - - alias Tagged = TaggedAlgebraic!Base; - - // Instantiate - Tagged taggedInt = 5; - Tagged taggedString = "Hello"; - Tagged taggedFoo = Foo(); - Tagged taggedAny = taggedInt; - taggedAny = taggedString; - taggedAny = taggedFoo; - - // Check type: Tagged.Kind is an enum - assert(taggedInt.kind == Tagged.Kind.i); - assert(taggedString.kind == Tagged.Kind.str); - assert(taggedFoo.kind == Tagged.Kind.foo); - assert(taggedAny.kind == Tagged.Kind.foo); - - // In most cases, can simply use as-is - auto num = 4 + taggedInt; - auto msg = taggedString ~ " World!"; - taggedFoo.bar(); - if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first! - taggedAny.bar(); - //taggedString.bar(); // AssertError: Not a Foo! - - // Convert back by casting - auto i = cast(int) taggedInt; - auto str = cast(string) taggedString; - auto foo = cast(Foo) taggedFoo; - if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first! - auto foo2 = cast(Foo) taggedAny; - //cast(Foo) taggedString; // AssertError! - - // Kind is an enum, so final switch is supported: - final switch (taggedAny.kind) { - case Tagged.Kind.i: - // It's "int i" - break; - - case Tagged.Kind.str: - // It's "string str" - break; - - case Tagged.Kind.foo: - // It's "Foo foo" - break; - } -} - -/** Operators and methods of the contained type can be used transparently. -*/ -@safe unittest { - static struct S { - int v; - int test() { return v / 2; } - } - - static union Test { - typeof(null) null_; - int integer; - string text; - string[string] dictionary; - S custom; - } - - alias TA = TaggedAlgebraic!Test; - - TA ta; - assert(ta.kind == TA.Kind.null_); - - ta = 12; - assert(ta.kind == TA.Kind.integer); - assert(ta == 12); - assert(cast(int)ta == 12); - assert(cast(long)ta == 12); - assert(cast(short)ta == 12); - - ta += 12; - assert(ta == 24); - assert(ta - 10 == 14); - - ta = ["foo" : "bar"]; - assert(ta.kind == TA.Kind.dictionary); - assert(ta["foo"] == "bar"); - - ta["foo"] = "baz"; - assert(ta["foo"] == "baz"); - - ta = S(8); - assert(ta.test() == 4); -} - -unittest { // std.conv integration - import std.conv : to; - - static struct S { - int v; - int test() { return v / 2; } - } - - static union Test { - typeof(null) null_; - int number; - string text; - } - - alias TA = TaggedAlgebraic!Test; - - TA ta; - assert(ta.kind == TA.Kind.null_); - ta = "34"; - assert(ta == "34"); - assert(to!int(ta) == 34, to!string(to!int(ta))); - assert(to!string(ta) == "34", to!string(ta)); -} - -/** Multiple fields are allowed to have the same type, in which case the type - ID enum is used to disambiguate. -*/ -@safe unittest { - static union Test { - typeof(null) null_; - int count; - int difference; - } - - alias TA = TaggedAlgebraic!Test; - - TA ta; - ta = TA(12, TA.Kind.count); - assert(ta.kind == TA.Kind.count); - assert(ta == 12); - - ta = null; - assert(ta.kind == TA.Kind.null_); -} - -unittest { - // test proper type modifier support - static struct S { - void test() {} - void testI() immutable {} - void testC() const {} - void testS() shared {} - void testSC() shared const {} - } - static union U { - S s; - } - - auto u = TaggedAlgebraic!U(S.init); - const uc = u; - immutable ui = cast(immutable)u; - //const shared usc = cast(shared)u; - //shared us = cast(shared)u; - - static assert( is(typeof(u.test()))); - static assert(!is(typeof(u.testI()))); - static assert( is(typeof(u.testC()))); - static assert(!is(typeof(u.testS()))); - static assert(!is(typeof(u.testSC()))); - - static assert(!is(typeof(uc.test()))); - static assert(!is(typeof(uc.testI()))); - static assert( is(typeof(uc.testC()))); - static assert(!is(typeof(uc.testS()))); - static assert(!is(typeof(uc.testSC()))); - - static assert(!is(typeof(ui.test()))); - static assert( is(typeof(ui.testI()))); - static assert( is(typeof(ui.testC()))); - static assert(!is(typeof(ui.testS()))); - static assert( is(typeof(ui.testSC()))); - - /*static assert(!is(typeof(us.test()))); - static assert(!is(typeof(us.testI()))); - static assert(!is(typeof(us.testC()))); - static assert( is(typeof(us.testS()))); - static assert( is(typeof(us.testSC()))); - - static assert(!is(typeof(usc.test()))); - static assert(!is(typeof(usc.testI()))); - static assert(!is(typeof(usc.testC()))); - static assert(!is(typeof(usc.testS()))); - static assert( is(typeof(usc.testSC())));*/ -} - -unittest { - // test attributes on contained values - import std.typecons : Rebindable, rebindable; - - class C { - void test() {} - void testC() const {} - void testI() immutable {} - } - union U { - Rebindable!(immutable(C)) c; - } - - auto ta = TaggedAlgebraic!U(rebindable(new immutable C)); - static assert(!is(typeof(ta.test()))); - static assert( is(typeof(ta.testC()))); - static assert( is(typeof(ta.testI()))); -} - -version (unittest) { - // test recursive definition using a wrapper dummy struct - // (needed to avoid "no size yet for forward reference" errors) - template ID(What) { alias ID = What; } - private struct _test_Wrapper { - TaggedAlgebraic!_test_U u; - alias u this; - this(ARGS...)(ARGS args) { u = TaggedAlgebraic!_test_U(args); } - } - private union _test_U { - _test_Wrapper[] children; - int value; - } - unittest { - alias TA = _test_Wrapper; - auto ta = TA(null); - ta ~= TA(0); - ta ~= TA(1); - ta ~= TA([TA(2)]); - assert(ta[0] == 0); - assert(ta[1] == 1); - assert(ta[2][0] == 2); - } -} - -unittest { // postblit/destructor test - static struct S { - static int i = 0; - bool initialized = false; - this(bool) { initialized = true; i++; } - this(this) { if (initialized) i++; } - ~this() { if (initialized) i--; } - } - - static struct U { - S s; - int t; - } - alias TA = TaggedAlgebraic!U; - { - assert(S.i == 0); - auto ta = TA(S(true)); - assert(S.i == 1); - { - auto tb = ta; - assert(S.i == 2); - ta = tb; - assert(S.i == 2); - ta = 1; - assert(S.i == 1); - ta = S(true); - assert(S.i == 2); - } - assert(S.i == 1); - } - assert(S.i == 0); - - static struct U2 { - S a; - S b; - } - alias TA2 = TaggedAlgebraic!U2; - { - auto ta2 = TA2(S(true), TA2.Kind.a); - assert(S.i == 1); - } - assert(S.i == 0); -} - -unittest { - static struct S { - union U { - int i; - string s; - U[] a; - } - alias TA = TaggedAlgebraic!U; - TA p; - alias p this; - } - S s = S(S.TA("hello")); - assert(cast(string)s == "hello"); -} - -unittest { // multiple operator choices - union U { - int i; - double d; - } - alias TA = TaggedAlgebraic!U; - TA ta = 12; - static assert(is(typeof(ta + 10) == TA)); // ambiguous, could be int or double - assert((ta + 10).kind == TA.Kind.i); - assert(ta + 10 == 22); - static assert(is(typeof(ta + 10.5) == double)); - assert(ta + 10.5 == 22.5); -} - -unittest { // Binary op between two TaggedAlgebraic values - union U { int i; } - alias TA = TaggedAlgebraic!U; - - TA a = 1, b = 2; - static assert(is(typeof(a + b) == int)); - assert(a + b == 3); -} - -unittest { // Ambiguous binary op between two TaggedAlgebraic values - union U { int i; double d; } - alias TA = TaggedAlgebraic!U; - - TA a = 1, b = 2; - static assert(is(typeof(a + b) == TA)); - assert((a + b).kind == TA.Kind.i); - assert(a + b == 3); -} - -unittest { - struct S { - union U { - @disableIndex string str; - S[] array; - S[string] object; - } - alias TA = TaggedAlgebraic!U; - TA payload; - alias payload this; - } - - S a = S(S.TA("hello")); - S b = S(S.TA(["foo": a])); - S c = S(S.TA([a])); - assert(b["foo"] == a); - assert(b["foo"] == "hello"); - assert(c[0] == a); - assert(c[0] == "hello"); -} - - -/** Tests if the algebraic type stores a value of a certain data type. -*/ -bool hasType(T, U)(in ref TaggedAlgebraic!U ta) -{ - alias Fields = Filter!(fieldMatchesType!(U, T), ta.fieldNames); - static assert(Fields.length > 0, "Type "~T.stringof~" cannot be stored in a "~(TaggedAlgebraic!U).stringof~"."); - - switch (ta.kind) { - default: return false; - foreach (i, fname; Fields) - case __traits(getMember, ta.Kind, fname): - return true; - } - assert(false); // never reached -} - -/// -unittest { - union Fields { - int number; - string text; - } - - TaggedAlgebraic!Fields ta = "test"; - - assert(ta.hasType!string); - assert(!ta.hasType!int); - - ta = 42; - assert(ta.hasType!int); - assert(!ta.hasType!string); -} - -unittest { // issue #1 - union U { - int a; - int b; - } - alias TA = TaggedAlgebraic!U; - - TA ta = TA(0, TA.Kind.b); - static assert(!is(typeof(ta.hasType!double))); - assert(ta.hasType!int); -} - -/** Gets the value stored in an algebraic type based on its data type. -*/ -ref inout(T) get(T, U)(ref inout(TaggedAlgebraic!U) ta) -{ - assert(hasType!(T, U)(ta)); - return ta.trustedGet!T; -} - -/// Convenience type that can be used for union fields that have no value (`void` is not allowed). -struct Void {} - -/// User-defined attibute to disable `opIndex` forwarding for a particular tagged union member. -@property auto disableIndex() { assert(__ctfe, "disableIndex must only be used as an attribute."); return DisableOpAttribute(OpKind.index, null); } - -private struct DisableOpAttribute { - OpKind kind; - string name; -} - - -private template hasOp(TA, OpKind kind, string name, ARGS...) -{ - import std.traits : CopyTypeQualifiers; - alias UQ = CopyTypeQualifiers!(TA, TA.Union); - enum hasOp = TypeTuple!(OpInfo!(UQ, kind, name, ARGS).fields).length > 0; -} - -unittest { - static struct S { - void m(int i) {} - bool opEquals(int i) { return true; } - bool opEquals(S s) { return true; } - } - - static union U { int i; string s; S st; } - alias TA = TaggedAlgebraic!U; - - static assert(hasOp!(TA, OpKind.binary, "+", int)); - static assert(hasOp!(TA, OpKind.binary, "~", string)); - static assert(hasOp!(TA, OpKind.binary, "==", int)); - static assert(hasOp!(TA, OpKind.binary, "==", string)); - static assert(hasOp!(TA, OpKind.binary, "==", int)); - static assert(hasOp!(TA, OpKind.binary, "==", S)); - static assert(hasOp!(TA, OpKind.method, "m", int)); - static assert(hasOp!(TA, OpKind.binary, "+=", int)); - static assert(!hasOp!(TA, OpKind.binary, "~", int)); - static assert(!hasOp!(TA, OpKind.binary, "~", int)); - static assert(!hasOp!(TA, OpKind.method, "m", string)); - static assert(!hasOp!(TA, OpKind.method, "m")); - static assert(!hasOp!(const(TA), OpKind.binary, "+=", int)); - static assert(!hasOp!(const(TA), OpKind.method, "m", int)); -} - -unittest { - struct S { - union U { - string s; - S[] arr; - S[string] obj; - } - alias TA = TaggedAlgebraic!(S.U); - TA payload; - alias payload this; - } - static assert(hasOp!(S.TA, OpKind.index, null, size_t)); - static assert(hasOp!(S.TA, OpKind.index, null, int)); - static assert(hasOp!(S.TA, OpKind.index, null, string)); - static assert(hasOp!(S.TA, OpKind.field, "length")); -} - -unittest { // "in" operator - union U { - string[string] dict; - } - alias TA = TaggedAlgebraic!U; - auto ta = TA(["foo": "bar"]); - assert("foo" in ta); - assert(*("foo" in ta) == "bar"); -} - -private static auto implementOp(OpKind kind, string name, T, ARGS...)(ref T self, auto ref ARGS args) -{ - import std.array : join; - import std.traits : CopyTypeQualifiers; - import std.variant : Algebraic, Variant; - alias UQ = CopyTypeQualifiers!(T, T.Union); - - alias info = OpInfo!(UQ, kind, name, ARGS); - - static assert(hasOp!(T, kind, name, ARGS)); - - static assert(info.fields.length > 0, "Implementing operator that has no valid implementation for any supported type."); - - //pragma(msg, "Fields for "~kind.stringof~" "~name~", "~T.stringof~": "~info.fields.stringof); - //pragma(msg, "Return types for "~kind.stringof~" "~name~", "~T.stringof~": "~info.ReturnTypes.stringof); - //pragma(msg, typeof(T.Union.tupleof)); - //import std.meta : staticMap; pragma(msg, staticMap!(isMatchingUniqueType!(T.Union), info.ReturnTypes)); - - switch (self.m_kind) { - default: assert(false, "Operator "~name~" ("~kind.stringof~") can only be used on values of the following types: "~[info.fields].join(", ")); - foreach (i, f; info.fields) { - alias FT = typeof(__traits(getMember, T.Union, f)); - case __traits(getMember, T.Kind, f): - static if (NoDuplicates!(info.ReturnTypes).length == 1) - return info.perform(self.trustedGet!FT, args); - else static if (allSatisfy!(isMatchingUniqueType!(T.Union), info.ReturnTypes)) - return TaggedAlgebraic!(T.Union)(info.perform(self.trustedGet!FT, args)); - else static if (allSatisfy!(isNoVariant, info.ReturnTypes)) { - alias Alg = Algebraic!(NoDuplicates!(info.ReturnTypes)); - info.ReturnTypes[i] ret = info.perform(self.trustedGet!FT, args); - import std.traits : isInstanceOf; - static if (isInstanceOf!(TaggedAlgebraic, typeof(ret))) return Alg(ret.payload); - else return Alg(ret); - } - else static if (is(FT == Variant)) - return info.perform(self.trustedGet!FT, args); - else - return Variant(info.perform(self.trustedGet!FT, args)); - } - } - - assert(false); // never reached -} - -unittest { // opIndex on recursive TA with closed return value set - static struct S { - union U { - char ch; - string str; - S[] arr; - } - alias TA = TaggedAlgebraic!U; - TA payload; - alias payload this; - - this(T)(T t) { this.payload = t; } - } - S a = S("foo"); - S s = S([a]); - - assert(implementOp!(OpKind.field, "length")(s.payload) == 1); - static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S.TA)); - assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo"); -} - -unittest { // opIndex on recursive TA with closed return value set using @disableIndex - static struct S { - union U { - @disableIndex string str; - S[] arr; - } - alias TA = TaggedAlgebraic!U; - TA payload; - alias payload this; - - this(T)(T t) { this.payload = t; } - } - S a = S("foo"); - S s = S([a]); - - assert(implementOp!(OpKind.field, "length")(s.payload) == 1); - static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S)); - assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo"); -} - - -private auto performOpRaw(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args) -{ - static if (kind == OpKind.binary) return mixin("value "~name~" args[0]"); - else static if (kind == OpKind.binaryRight) return mixin("args[0] "~name~" value"); - else static if (kind == OpKind.unary) return mixin("name "~value); - else static if (kind == OpKind.method) return __traits(getMember, value, name)(args); - else static if (kind == OpKind.field) return __traits(getMember, value, name); - else static if (kind == OpKind.index) return value[args]; - else static if (kind == OpKind.indexAssign) return value[args[1 .. $]] = args[0]; - else static if (kind == OpKind.call) return value(args); - else static assert(false, "Unsupported kind of operator: "~kind.stringof); -} - -unittest { - union U { int i; string s; } - - { int v = 1; assert(performOpRaw!(U, OpKind.binary, "+")(v, 3) == 4); } - { string v = "foo"; assert(performOpRaw!(U, OpKind.binary, "~")(v, "bar") == "foobar"); } -} - - -private auto performOp(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args) -{ - import std.traits : isInstanceOf; - static if (ARGS.length > 0 && isInstanceOf!(TaggedAlgebraic, ARGS[0])) { - static if (is(typeof(performOpRaw!(U, kind, name, T, ARGS)(value, args)))) { - return performOpRaw!(U, kind, name, T, ARGS)(value, args); - } else { - alias TA = ARGS[0]; - template MTypesImpl(size_t i) { - static if (i < TA.FieldTypes.length) { - alias FT = TA.FieldTypes[i]; - static if (is(typeof(&performOpRaw!(U, kind, name, T, FT, ARGS[1 .. $])))) - alias MTypesImpl = TypeTuple!(FT, MTypesImpl!(i+1)); - else alias MTypesImpl = TypeTuple!(MTypesImpl!(i+1)); - } else alias MTypesImpl = TypeTuple!(); - } - alias MTypes = NoDuplicates!(MTypesImpl!0); - static assert(MTypes.length > 0, "No type of the TaggedAlgebraic parameter matches any function declaration."); - static if (MTypes.length == 1) { - if (args[0].hasType!(MTypes[0])) - return performOpRaw!(U, kind, name)(value, args[0].get!(MTypes[0]), args[1 .. $]); - } else { - // TODO: allow all return types (fall back to Algebraic or Variant) - foreach (FT; MTypes) { - if (args[0].hasType!FT) - return ARGS[0](performOpRaw!(U, kind, name)(value, args[0].get!FT, args[1 .. $])); - } - } - throw new /*InvalidAgument*/Exception("Algebraic parameter type mismatch"); - } - } else return performOpRaw!(U, kind, name, T, ARGS)(value, args); -} - -unittest { - union U { int i; double d; string s; } - - { int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, 3) == 4); } - { string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, "bar") == "foobar"); } - { string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, TaggedAlgebraic!U("bar")) == "foobar"); } - { int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, TaggedAlgebraic!U(3)) == 4); } -} - - -private template OpInfo(U, OpKind kind, string name, ARGS...) -{ - import std.traits : CopyTypeQualifiers, FieldTypeTuple, FieldNameTuple, ReturnType; - - private alias FieldTypes = FieldTypeTuple!U; - private alias fieldNames = FieldNameTuple!U; - - private template isOpEnabled(string field) - { - alias attribs = TypeTuple!(__traits(getAttributes, __traits(getMember, U, field))); - template impl(size_t i) { - static if (i < attribs.length) { - static if (is(typeof(attribs[i]) == DisableOpAttribute)) { - static if (kind == attribs[i].kind && name == attribs[i].name) - enum impl = false; - else enum impl = impl!(i+1); - } else enum impl = impl!(i+1); - } else enum impl = true; - } - enum isOpEnabled = impl!0; - } - - template fieldsImpl(size_t i) - { - static if (i < FieldTypes.length) { - static if (isOpEnabled!(fieldNames[i]) && is(typeof(&performOp!(U, kind, name, FieldTypes[i], ARGS)))) { - alias fieldsImpl = TypeTuple!(fieldNames[i], fieldsImpl!(i+1)); - } else alias fieldsImpl = fieldsImpl!(i+1); - } else alias fieldsImpl = TypeTuple!(); - } - alias fields = fieldsImpl!0; - - template ReturnTypesImpl(size_t i) { - static if (i < fields.length) { - alias FT = CopyTypeQualifiers!(U, typeof(__traits(getMember, U, fields[i]))); - alias ReturnTypesImpl = TypeTuple!(ReturnType!(performOp!(U, kind, name, FT, ARGS)), ReturnTypesImpl!(i+1)); - } else alias ReturnTypesImpl = TypeTuple!(); - } - alias ReturnTypes = ReturnTypesImpl!0; - - static auto perform(T)(ref T value, auto ref ARGS args) { return performOp!(U, kind, name)(value, args); } -} - -private template ImplicitUnqual(T) { - import std.traits : Unqual, hasAliasing; - static if (is(T == void)) alias ImplicitUnqual = void; - else { - private static struct S { T t; } - static if (hasAliasing!S) alias ImplicitUnqual = T; - else alias ImplicitUnqual = Unqual!T; - } -} - -private enum OpKind { - binary, - binaryRight, - unary, - method, - field, - index, - indexAssign, - call -} - -private template TypeEnum(U) -{ - import std.array : join; - import std.traits : FieldNameTuple; - mixin("enum TypeEnum { " ~ [FieldNameTuple!U].join(", ") ~ " }"); -} - -private string generateConstructors(U)() -{ - import std.algorithm : map; - import std.array : join; - import std.string : format; - import std.traits : FieldTypeTuple; - - string ret; - - // disable default construction if first type is not a null/Void type - static if (!is(FieldTypeTuple!U[0] == typeof(null)) && !is(FieldTypeTuple!U[0] == Void)) - { - ret ~= q{ - @disable this(); - }; - } - - // normal type constructors - foreach (tname; UniqueTypeFields!U) - ret ~= q{ - this(typeof(U.%s) value) - { - m_data.rawEmplace(value); - m_kind = Kind.%s; - } - - void opAssign(typeof(U.%s) value) - { - if (m_kind != Kind.%s) { - // NOTE: destroy(this) doesn't work for some opDispatch-related reason - static if (is(typeof(&this.__xdtor))) - this.__xdtor(); - m_data.rawEmplace(value); - } else { - trustedGet!"%s" = value; - } - m_kind = Kind.%s; - } - }.format(tname, tname, tname, tname, tname, tname); - - // type constructors with explicit type tag - foreach (tname; AmbiguousTypeFields!U) - ret ~= q{ - this(typeof(U.%s) value, Kind type) - { - assert(type.among!(%s), format("Invalid type ID for type %%s: %%s", typeof(U.%s).stringof, type)); - m_data.rawEmplace(value); - m_kind = type; - } - }.format(tname, [SameTypeFields!(U, tname)].map!(f => "Kind."~f).join(", "), tname); - - return ret; -} - -private template UniqueTypeFields(U) { - import std.traits : FieldTypeTuple, FieldNameTuple; - - alias Types = FieldTypeTuple!U; - - template impl(size_t i) { - static if (i < Types.length) { - enum name = FieldNameTuple!U[i]; - alias T = Types[i]; - static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) < 0) - alias impl = TypeTuple!(name, impl!(i+1)); - else alias impl = TypeTuple!(impl!(i+1)); - } else alias impl = TypeTuple!(); - } - alias UniqueTypeFields = impl!0; -} - -private template AmbiguousTypeFields(U) { - import std.traits : FieldTypeTuple, FieldNameTuple; - - alias Types = FieldTypeTuple!U; - - template impl(size_t i) { - static if (i < Types.length) { - enum name = FieldNameTuple!U[i]; - alias T = Types[i]; - static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) >= 0) - alias impl = TypeTuple!(name, impl!(i+1)); - else alias impl = impl!(i+1); - } else alias impl = TypeTuple!(); - } - alias AmbiguousTypeFields = impl!0; -} - -unittest { - union U { - int a; - string b; - int c; - double d; - } - static assert([UniqueTypeFields!U] == ["b", "d"]); - static assert([AmbiguousTypeFields!U] == ["a"]); -} - -private template SameTypeFields(U, string field) { - import std.traits : FieldTypeTuple, FieldNameTuple; - - alias Types = FieldTypeTuple!U; - - alias T = typeof(__traits(getMember, U, field)); - template impl(size_t i) { - static if (i < Types.length) { - enum name = FieldNameTuple!U[i]; - static if (is(Types[i] == T)) - alias impl = TypeTuple!(name, impl!(i+1)); - else alias impl = TypeTuple!(impl!(i+1)); - } else alias impl = TypeTuple!(); - } - alias SameTypeFields = impl!0; -} - -private template MemberType(U) { - template MemberType(string name) { - alias MemberType = typeof(__traits(getMember, U, name)); - } -} - -private template isMatchingType(U) { - import std.traits : FieldTypeTuple; - enum isMatchingType(T) = staticIndexOf!(T, FieldTypeTuple!U) >= 0; -} - -private template isMatchingUniqueType(U) { - import std.traits : staticMap; - alias UniqueTypes = staticMap!(FieldTypeOf!U, UniqueTypeFields!U); - template isMatchingUniqueType(T) { - static if (is(T : TaggedAlgebraic!U)) enum isMatchingUniqueType = true; - else enum isMatchingUniqueType = staticIndexOfImplicit!(T, UniqueTypes) >= 0; - } -} - -private template fieldMatchesType(U, T) -{ - enum fieldMatchesType(string field) = is(typeof(__traits(getMember, U, field)) == T); -} - -private template FieldTypeOf(U) { - template FieldTypeOf(string name) { - alias FieldTypeOf = typeof(__traits(getMember, U, name)); - } -} - -private template staticIndexOfImplicit(T, Types...) { - template impl(size_t i) { - static if (i < Types.length) { - static if (is(T : Types[i])) enum impl = i; - else enum impl = impl!(i+1); - } else enum impl = -1; - } - enum staticIndexOfImplicit = impl!0; -} - -unittest { - static assert(staticIndexOfImplicit!(immutable(char), char) == 0); - static assert(staticIndexOfImplicit!(int, long) == 0); - static assert(staticIndexOfImplicit!(long, int) < 0); - static assert(staticIndexOfImplicit!(int, int, double) == 0); - static assert(staticIndexOfImplicit!(double, int, double) == 1); -} - - -private template isNoVariant(T) { - import std.variant : Variant; - enum isNoVariant = !is(T == Variant); -} - -private void rawEmplace(T)(void[] dst, ref T src) -{ - T* tdst = () @trusted { return cast(T*)dst.ptr; } (); - static if (is(T == class)) { - *tdst = src; - } else { - import std.conv : emplace; - emplace(tdst); - *tdst = src; - } -} diff --git a/src/sdlang/token.d b/src/sdlang/token.d deleted file mode 100644 index 0a5b2fd..0000000 --- a/src/sdlang/token.d +++ /dev/null @@ -1,550 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.token; - -import std.array; -import std.base64; -import std.conv; -import std.datetime; -import std.meta; -import std.range; -import std.string; -import std.traits; -import std.typetuple; -import std.variant; - -import sdlang.exception; -import sdlang.symbol; -import sdlang.util; - -/// DateTime doesn't support milliseconds, but SDLang's "Date Time" type does. -/// So this is needed for any SDL "Date Time" that doesn't include a time zone. -struct DateTimeFrac -{ - DateTime dateTime; - Duration fracSecs; - deprecated("Use fracSecs instead.") { - @property FracSec fracSec() const { return FracSec.from!"hnsecs"(fracSecs.total!"hnsecs"); } - @property void fracSec(FracSec v) { fracSecs = v.hnsecs.hnsecs; } - } -} - -/++ -If a "Date Time" literal in the SDL file has a time zone that's not found in -your system, you get one of these instead of a SysTime. (Because it's -impossible to indicate "unknown time zone" with `std.datetime.TimeZone`.) - -The difference between this and `DateTimeFrac` is that `DateTimeFrac` -indicates that no time zone was specified in the SDL at all, whereas -`DateTimeFracUnknownZone` indicates that a time zone was specified but -data for it could not be found on your system. -+/ -struct DateTimeFracUnknownZone -{ - DateTime dateTime; - Duration fracSecs; - deprecated("Use fracSecs instead.") { - @property FracSec fracSec() const { return FracSec.from!"hnsecs"(fracSecs.total!"hnsecs"); } - @property void fracSec(FracSec v) { fracSecs = v.hnsecs.hnsecs; } - } - string timeZone; - - bool opEquals(const DateTimeFracUnknownZone b) const - { - return opEquals(b); - } - bool opEquals(ref const DateTimeFracUnknownZone b) const - { - return - this.dateTime == b.dateTime && - this.fracSecs == b.fracSecs && - this.timeZone == b.timeZone; - } -} - -/++ -SDLang's datatypes map to D's datatypes as described below. -Most are straightforward, but take special note of the date/time-related types. - ---------------------------------------------------------------- -Boolean: bool -Null: typeof(null) -Unicode Character: dchar -Double-Quote Unicode String: string -Raw Backtick Unicode String: string -Integer (32 bits signed): int -Long Integer (64 bits signed): long -Float (32 bits signed): float -Double Float (64 bits signed): double -Decimal (128+ bits signed): real -Binary (standard Base64): ubyte[] -Time Span: Duration - -Date (with no time at all): Date -Date Time (no timezone): DateTimeFrac -Date Time (with a known timezone): SysTime -Date Time (with an unknown timezone): DateTimeFracUnknownZone ---------------------------------------------------------------- -+/ -alias ValueTypes = TypeTuple!( - bool, - string, dchar, - int, long, - float, double, real, - Date, DateTimeFrac, SysTime, DateTimeFracUnknownZone, Duration, - ubyte[], - typeof(null), -); - -alias Value = Algebraic!( ValueTypes ); ///ditto -enum isValueType(T) = staticIndexOf!(T, ValueTypes) != -1; - -enum isSink(T) = - isOutputRange!T && - is(ElementType!(T)[] == string); - -string toSDLString(T)(T value) if(is(T==Value) || isValueType!T) -{ - Appender!string sink; - toSDLString(value, sink); - return sink.data; -} - -/// Throws SDLangException if value is infinity, -infinity or NaN, because -/// those are not currently supported by the SDLang spec. -void toSDLString(Sink)(Value value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - foreach(T; ValueTypes) - { - if(value.type == typeid(T)) - { - toSDLString( value.get!T(), sink ); - return; - } - } - - throw new Exception("Internal SDLang-D error: Unhandled type of Value. Contains: "~value.toString()); -} - -@("toSDLString on infinity and NaN") -unittest -{ - import std.exception; - - auto floatInf = float.infinity; - auto floatNegInf = -float.infinity; - auto floatNaN = float.nan; - - auto doubleInf = double.infinity; - auto doubleNegInf = -double.infinity; - auto doubleNaN = double.nan; - - auto realInf = real.infinity; - auto realNegInf = -real.infinity; - auto realNaN = real.nan; - - assertNotThrown( toSDLString(0.0F) ); - assertNotThrown( toSDLString(0.0) ); - assertNotThrown( toSDLString(0.0L) ); - - assertThrown!ValidationException( toSDLString(floatInf) ); - assertThrown!ValidationException( toSDLString(floatNegInf) ); - assertThrown!ValidationException( toSDLString(floatNaN) ); - - assertThrown!ValidationException( toSDLString(doubleInf) ); - assertThrown!ValidationException( toSDLString(doubleNegInf) ); - assertThrown!ValidationException( toSDLString(doubleNaN) ); - - assertThrown!ValidationException( toSDLString(realInf) ); - assertThrown!ValidationException( toSDLString(realNegInf) ); - assertThrown!ValidationException( toSDLString(realNaN) ); - - assertThrown!ValidationException( toSDLString(Value(floatInf)) ); - assertThrown!ValidationException( toSDLString(Value(floatNegInf)) ); - assertThrown!ValidationException( toSDLString(Value(floatNaN)) ); - - assertThrown!ValidationException( toSDLString(Value(doubleInf)) ); - assertThrown!ValidationException( toSDLString(Value(doubleNegInf)) ); - assertThrown!ValidationException( toSDLString(Value(doubleNaN)) ); - - assertThrown!ValidationException( toSDLString(Value(realInf)) ); - assertThrown!ValidationException( toSDLString(Value(realNegInf)) ); - assertThrown!ValidationException( toSDLString(Value(realNaN)) ); -} - -void toSDLString(Sink)(typeof(null) value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put("null"); -} - -void toSDLString(Sink)(bool value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put(value? "true" : "false"); -} - -//TODO: Figure out how to properly handle strings/chars containing lineSep or paraSep -void toSDLString(Sink)(string value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put('"'); - - // This loop is UTF-safe - foreach(char ch; value) - { - if (ch == '\n') sink.put(`\n`); - else if(ch == '\r') sink.put(`\r`); - else if(ch == '\t') sink.put(`\t`); - else if(ch == '\"') sink.put(`\"`); - else if(ch == '\\') sink.put(`\\`); - else - sink.put(ch); - } - - sink.put('"'); -} - -void toSDLString(Sink)(dchar value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put('\''); - - if (value == '\n') sink.put(`\n`); - else if(value == '\r') sink.put(`\r`); - else if(value == '\t') sink.put(`\t`); - else if(value == '\'') sink.put(`\'`); - else if(value == '\\') sink.put(`\\`); - else - sink.put(value); - - sink.put('\''); -} - -void toSDLString(Sink)(int value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put( "%s".format(value) ); -} - -void toSDLString(Sink)(long value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put( "%sL".format(value) ); -} - -private void checkUnsupportedFloatingPoint(T)(T value) if(isFloatingPoint!T) -{ - import std.exception; - import std.math; - - enforce!ValidationException( - !isInfinity(value), - "SDLang does not currently support infinity for floating-point types" - ); - - enforce!ValidationException( - !isNaN(value), - "SDLang does not currently support NaN for floating-point types" - ); -} - -void toSDLString(Sink)(float value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - checkUnsupportedFloatingPoint(value); - sink.put( "%.10sF".format(value) ); -} - -void toSDLString(Sink)(double value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - checkUnsupportedFloatingPoint(value); - sink.put( "%.30sD".format(value) ); -} - -void toSDLString(Sink)(real value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - checkUnsupportedFloatingPoint(value); - sink.put( "%.30sBD".format(value) ); -} - -void toSDLString(Sink)(Date value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put(to!string(value.year)); - sink.put('/'); - sink.put(to!string(cast(int)value.month)); - sink.put('/'); - sink.put(to!string(value.day)); -} - -void toSDLString(Sink)(DateTimeFrac value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - toSDLString(value.dateTime.date, sink); - sink.put(' '); - sink.put("%.2s".format(value.dateTime.hour)); - sink.put(':'); - sink.put("%.2s".format(value.dateTime.minute)); - - if(value.dateTime.second != 0) - { - sink.put(':'); - sink.put("%.2s".format(value.dateTime.second)); - } - - if(value.fracSecs != 0.msecs) - { - sink.put('.'); - sink.put("%.3s".format(value.fracSecs.total!"msecs")); - } -} - -void toSDLString(Sink)(SysTime value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - auto dateTimeFrac = DateTimeFrac(cast(DateTime)value, value.fracSecs); - toSDLString(dateTimeFrac, sink); - - sink.put("-"); - - auto tzString = value.timezone.name; - - // If name didn't exist, try abbreviation. - // Note that according to std.datetime docs, on Windows the - // stdName/dstName may not be properly abbreviated. - version(Windows) {} else - if(tzString == "") - { - auto tz = value.timezone; - auto stdTime = value.stdTime; - - if(tz.hasDST()) - tzString = tz.dstInEffect(stdTime)? tz.dstName : tz.stdName; - else - tzString = tz.stdName; - } - - if(tzString == "") - { - auto offset = value.timezone.utcOffsetAt(value.stdTime); - sink.put("GMT"); - - if(offset < seconds(0)) - { - sink.put("-"); - offset = -offset; - } - else - sink.put("+"); - - sink.put("%.2s".format(offset.split.hours)); - sink.put(":"); - sink.put("%.2s".format(offset.split.minutes)); - } - else - sink.put(tzString); -} - -void toSDLString(Sink)(DateTimeFracUnknownZone value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - auto dateTimeFrac = DateTimeFrac(value.dateTime, value.fracSecs); - toSDLString(dateTimeFrac, sink); - - sink.put("-"); - sink.put(value.timeZone); -} - -void toSDLString(Sink)(Duration value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - if(value < seconds(0)) - { - sink.put("-"); - value = -value; - } - - auto days = value.total!"days"(); - if(days != 0) - { - sink.put("%s".format(days)); - sink.put("d:"); - } - - sink.put("%.2s".format(value.split.hours)); - sink.put(':'); - sink.put("%.2s".format(value.split.minutes)); - sink.put(':'); - sink.put("%.2s".format(value.split.seconds)); - - if(value.split.msecs != 0) - { - sink.put('.'); - sink.put("%.3s".format(value.split.msecs)); - } -} - -void toSDLString(Sink)(ubyte[] value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put('['); - sink.put( Base64.encode(value) ); - sink.put(']'); -} - -/// This only represents terminals. Nonterminals aren't -/// constructed since the AST is directly built during parsing. -struct Token -{ - Symbol symbol = sdlang.symbol.symbol!"Error"; /// The "type" of this token - Location location; - Value value; /// Only valid when `symbol` is `symbol!"Value"`, otherwise null - string data; /// Original text from source - - @disable this(); - this(Symbol symbol, Location location, Value value=Value(null), string data=null) - { - this.symbol = symbol; - this.location = location; - this.value = value; - this.data = data; - } - - /// Tokens with differing symbols are always unequal. - /// Tokens with differing values are always unequal. - /// Tokens with differing Value types are always unequal. - /// Member `location` is always ignored for comparison. - /// Member `data` is ignored for comparison *EXCEPT* when the symbol is Ident. - bool opEquals(Token b) - { - return opEquals(b); - } - bool opEquals(ref Token b) ///ditto - { - if( - this.symbol != b.symbol || - this.value.type != b.value.type || - this.value != b.value - ) - return false; - - if(this.symbol == .symbol!"Ident") - return this.data == b.data; - - return true; - } - - bool matches(string symbolName)() - { - return this.symbol == .symbol!symbolName; - } -} - -@("sdlang token") -unittest -{ - auto loc = Location("", 0, 0, 0); - auto loc2 = Location("a", 1, 1, 1); - - assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc )); - assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc2)); - assert(Token(symbol!":", loc) == Token(symbol!":", loc )); - assert(Token(symbol!"EOL",loc) != Token(symbol!":", loc )); - assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),"\n")); - - assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),";" )); - assert(Token(symbol!"EOL",loc,Value(null),"A" ) == Token(symbol!"EOL",loc,Value(null),"B" )); - assert(Token(symbol!":", loc,Value(null),"A" ) == Token(symbol!":", loc,Value(null),"BB")); - assert(Token(symbol!"EOL",loc,Value(null),"A" ) != Token(symbol!":", loc,Value(null),"A" )); - - assert(Token(symbol!"Ident",loc,Value(null),"foo") == Token(symbol!"Ident",loc,Value(null),"foo")); - assert(Token(symbol!"Ident",loc,Value(null),"foo") != Token(symbol!"Ident",loc,Value(null),"BAR")); - - assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"foo")); - assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc2,Value(null),"foo")); - assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"BAR")); - assert(Token(symbol!"Value",loc,Value( 7),"foo") == Token(symbol!"Value",loc, Value( 7),"BAR")); - assert(Token(symbol!"Value",loc,Value( 7),"foo") != Token(symbol!"Value",loc, Value( "A"),"foo")); - assert(Token(symbol!"Value",loc,Value( 7),"foo") != Token(symbol!"Value",loc, Value( 2),"foo")); - assert(Token(symbol!"Value",loc,Value(cast(int)7)) != Token(symbol!"Value",loc, Value(cast(long)7))); - assert(Token(symbol!"Value",loc,Value(cast(float)1.2)) != Token(symbol!"Value",loc, Value(cast(double)1.2))); -} - -@("sdlang Value.toSDLString()") -unittest -{ - // Bool and null - assert(Value(null ).toSDLString() == "null"); - assert(Value(true ).toSDLString() == "true"); - assert(Value(false).toSDLString() == "false"); - - // Base64 Binary - assert(Value(cast(ubyte[])"hello world".dup).toSDLString() == "[aGVsbG8gd29ybGQ=]"); - - // Integer - assert(Value(cast( int) 7).toSDLString() == "7"); - assert(Value(cast( int)-7).toSDLString() == "-7"); - assert(Value(cast( int) 0).toSDLString() == "0"); - - assert(Value(cast(long) 7).toSDLString() == "7L"); - assert(Value(cast(long)-7).toSDLString() == "-7L"); - assert(Value(cast(long) 0).toSDLString() == "0L"); - - // Floating point - assert(Value(cast(float) 1.5).toSDLString() == "1.5F"); - assert(Value(cast(float)-1.5).toSDLString() == "-1.5F"); - assert(Value(cast(float) 0).toSDLString() == "0F"); - - assert(Value(cast(double) 1.5).toSDLString() == "1.5D"); - assert(Value(cast(double)-1.5).toSDLString() == "-1.5D"); - assert(Value(cast(double) 0).toSDLString() == "0D"); - - assert(Value(cast(real) 1.5).toSDLString() == "1.5BD"); - assert(Value(cast(real)-1.5).toSDLString() == "-1.5BD"); - assert(Value(cast(real) 0).toSDLString() == "0BD"); - - // String - assert(Value("hello" ).toSDLString() == `"hello"`); - assert(Value(" hello ").toSDLString() == `" hello "`); - assert(Value("" ).toSDLString() == `""`); - assert(Value("hello \r\n\t\"\\ world").toSDLString() == `"hello \r\n\t\"\\ world"`); - assert(Value("日本語").toSDLString() == `"日本語"`); - - // Chars - assert(Value(cast(dchar) 'A').toSDLString() == `'A'`); - assert(Value(cast(dchar)'\r').toSDLString() == `'\r'`); - assert(Value(cast(dchar)'\n').toSDLString() == `'\n'`); - assert(Value(cast(dchar)'\t').toSDLString() == `'\t'`); - assert(Value(cast(dchar)'\'').toSDLString() == `'\''`); - assert(Value(cast(dchar)'\\').toSDLString() == `'\\'`); - assert(Value(cast(dchar) '月').toSDLString() == `'月'`); - - // Date - assert(Value(Date( 2004,10,31)).toSDLString() == "2004/10/31"); - assert(Value(Date(-2004,10,31)).toSDLString() == "-2004/10/31"); - - // DateTimeFrac w/o Frac - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15))).toSDLString() == "2004/10/31 14:30:15"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 1, 2, 3))).toSDLString() == "2004/10/31 01:02:03"); - assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15))).toSDLString() == "-2004/10/31 14:30:15"); - - // DateTimeFrac w/ Frac - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 123.msecs)).toSDLString() == "2004/10/31 14:30:15.123"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 120.msecs)).toSDLString() == "2004/10/31 14:30:15.120"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 100.msecs)).toSDLString() == "2004/10/31 14:30:15.100"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 12.msecs)).toSDLString() == "2004/10/31 14:30:15.012"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 1.msecs)).toSDLString() == "2004/10/31 14:30:15.001"); - assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15), 123.msecs)).toSDLString() == "-2004/10/31 14:30:15.123"); - - // DateTimeFracUnknownZone - assert(Value(DateTimeFracUnknownZone(DateTime(2004,10,31, 14,30,15), 123.msecs, "Foo/Bar")).toSDLString() == "2004/10/31 14:30:15.123-Foo/Bar"); - - // SysTime - assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 14:30:15-GMT+00:00"); - assert(Value(SysTime(DateTime(2004,10,31, 1, 2, 3), new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 01:02:03-GMT+00:00"); - assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes(10) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:10"); - assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone(-hours(5)-minutes(30) ))).toSDLString() == "2004/10/31 14:30:15-GMT-05:30"); - assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes( 3) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:03"); - assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), 123.msecs, new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 14:30:15.123-GMT+00:00"); - - // Duration - assert( "12:14:42" == Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs( 0)).toSDLString()); - assert("-12:14:42" == Value(-days( 0)-hours(12)-minutes(14)-seconds(42)-msecs( 0)).toSDLString()); - assert( "00:09:12" == Value( days( 0)+hours( 0)+minutes( 9)+seconds(12)+msecs( 0)).toSDLString()); - assert( "00:00:01.023" == Value( days( 0)+hours( 0)+minutes( 0)+seconds( 1)+msecs( 23)).toSDLString()); - assert( "23d:05:21:23.532" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(532)).toSDLString()); - assert( "23d:05:21:23.530" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(530)).toSDLString()); - assert( "23d:05:21:23.500" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(500)).toSDLString()); - assert("-23d:05:21:23.532" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(532)).toSDLString()); - assert("-23d:05:21:23.500" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(500)).toSDLString()); - assert( "23d:05:21:23" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs( 0)).toSDLString()); -} diff --git a/src/sdlang/util.d b/src/sdlang/util.d deleted file mode 100644 index d192ea2..0000000 --- a/src/sdlang/util.d +++ /dev/null @@ -1,200 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.util; - -import std.algorithm; -import std.array; -import std.conv; -import std.datetime; -import std.range; -import std.stdio; -import std.string; - -import sdlang.exception; -import sdlang.token; - -enum sdlangVersion = "0.9.1"; - -alias immutable(ubyte)[] ByteString; - -auto startsWith(T)(string haystack, T needle) - if( is(T:ByteString) || is(T:string) ) -{ - return std.algorithm.startsWith( cast(ByteString)haystack, cast(ByteString)needle ); -} - -struct Location -{ - string file; /// Filename (including path) - int line; /// Zero-indexed - int col; /// Zero-indexed, Tab counts as 1 - size_t index; /// Index into the source - - this(int line, int col, int index) - { - this.line = line; - this.col = col; - this.index = index; - } - - this(string file, int line, int col, int index) - { - this.file = file; - this.line = line; - this.col = col; - this.index = index; - } - - /// Convert to string. Optionally takes output range as a sink. - string toString() - { - Appender!string sink; - this.toString(sink); - return sink.data; - } - - ///ditto - void toString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char)) - { - sink.put(file); - sink.put("("); - sink.put(to!string(line+1)); - sink.put(":"); - sink.put(to!string(col+1)); - sink.put(")"); - } -} - -struct FullName -{ - string namespace; - string name; - - /// Convert to string. Optionally takes output range as a sink. - string toString() - { - if(namespace == "") - return name; - - Appender!string sink; - this.toString(sink); - return sink.data; - } - - ///ditto - void toString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char)) - { - if(namespace != "") - { - sink.put(namespace); - sink.put(":"); - } - - sink.put(name); - } - - /// - static string combine(string namespace, string name) - { - return FullName(namespace, name).toString(); - } - /// - @("FullName.combine example") - unittest - { - assert(FullName.combine("", "name") == "name"); - assert(FullName.combine("*", "name") == "*:name"); - assert(FullName.combine("namespace", "name") == "namespace:name"); - } - - /// - static FullName parse(string fullName) - { - FullName result; - - auto parts = fullName.findSplit(":"); - if(parts[1] == "") // No colon - { - result.namespace = ""; - result.name = parts[0]; - } - else - { - result.namespace = parts[0]; - result.name = parts[2]; - } - - return result; - } - /// - @("FullName.parse example") - unittest - { - assert(FullName.parse("name") == FullName("", "name")); - assert(FullName.parse("*:name") == FullName("*", "name")); - assert(FullName.parse("namespace:name") == FullName("namespace", "name")); - } - - /// Throws with appropriate message if this.name is "*". - /// Wildcards are only supported for namespaces, not names. - void ensureNoWildcardName(string extaMsg = null) - { - if(name == "*") - throw new ArgumentException(`Wildcards ("*") only allowed for namespaces, not names. `~extaMsg); - } -} -struct Foo { string foo; } - -void removeIndex(E)(ref E[] arr, ptrdiff_t index) -{ - arr = arr[0..index] ~ arr[index+1..$]; -} - -void trace(string file=__FILE__, size_t line=__LINE__, TArgs...)(TArgs args) -{ - version(sdlangTrace) - { - writeln(file, "(", line, "): ", args); - stdout.flush(); - } -} - -string toString(TypeInfo ti) -{ - if (ti == typeid( bool )) return "bool"; - else if(ti == typeid( string )) return "string"; - else if(ti == typeid( dchar )) return "dchar"; - else if(ti == typeid( int )) return "int"; - else if(ti == typeid( long )) return "long"; - else if(ti == typeid( float )) return "float"; - else if(ti == typeid( double )) return "double"; - else if(ti == typeid( real )) return "real"; - else if(ti == typeid( Date )) return "Date"; - else if(ti == typeid( DateTimeFrac )) return "DateTimeFrac"; - else if(ti == typeid( DateTimeFracUnknownZone )) return "DateTimeFracUnknownZone"; - else if(ti == typeid( SysTime )) return "SysTime"; - else if(ti == typeid( Duration )) return "Duration"; - else if(ti == typeid( ubyte[] )) return "ubyte[]"; - else if(ti == typeid( typeof(null) )) return "null"; - - return "{unknown}"; -} - -enum BOM { - UTF8, /// UTF-8 - UTF16LE, /// UTF-16 (little-endian) - UTF16BE, /// UTF-16 (big-endian) - UTF32LE, /// UTF-32 (little-endian) - UTF32BE, /// UTF-32 (big-endian) -} - -enum NBOM = __traits(allMembers, BOM).length; -immutable ubyte[][NBOM] ByteOrderMarks = -[ - [0xEF, 0xBB, 0xBF], //UTF8 - [0xFF, 0xFE], //UTF16LE - [0xFE, 0xFF], //UTF16BE - [0xFF, 0xFE, 0x00, 0x00], //UTF32LE - [0x00, 0x00, 0xFE, 0xFF] //UTF32BE -]; diff --git a/src/sdp/ao_abstract_doc_source.d b/src/sdp/ao_abstract_doc_source.d index db814ac..c7e5a13 100644 --- a/src/sdp/ao_abstract_doc_source.d +++ b/src/sdp/ao_abstract_doc_source.d @@ -1,5 +1,6 @@ -/+ - document abstraction +/++ + document abstraction: + abstraction of sisu markup for downstream processing ao_abstract_doc_source.d +/ template SiSUdocAbstraction() { @@ -14,10 +15,6 @@ template SiSUdocAbstraction() { /+ ↓ abstraction mixins +/ mixin ObjectSetter; mixin InternalMarkup; - // // mixin SiSUrgxInitFlags; - // // mixin AssertionsOnBlocks; - // mixin SiSUbiblio; // issue - // mixin SiSUheader; /+ ↓ abstraction struct init +/ /+ initialize +/ auto rgx = Rgx(); @@ -106,7 +103,6 @@ template SiSUdocAbstraction() { is_ ); } - // mixin SiSUdocAbstractionFunctions; /+ ↓ abstract marked up document +/ auto abstract_doc_source( char[][] markup_sourcefile_content, @@ -129,7 +125,6 @@ template SiSUdocAbstraction() { "para" : 0, ]; auto type = flags_type_init; - mixin ScreenTxtColors; void tell_lo(int obj_cite_number, in char[] line) { writefln( "* %s %s", @@ -201,10 +196,9 @@ template SiSUdocAbstraction() { } line = replaceAll(line, rgx.true_dollar, "$$$$"); // dollar represented as $$ needed to stop submatching on $ - // (substitutions using ${identifiers} must take into account (e.g. happen earlier)) + // (substitutions using ${identifiers} must take into account (i.e. happen earlier)) debug(source) { // source lines writeln(line); - // writeln(scr_txt_marker["green"], line); } debug(srclines) { if (!line.empty) { // source lines, not empty @@ -1870,16 +1864,13 @@ template SiSUdocAbstraction() { /+ abstraction functions ↑ +/ /+ ↓ abstraction function emitters +/ struct OCNemitter { - // class OCNemitter : AssertOCN { int obj_cite_number, obj_cite_number_; int obj_cite_number_emitter(int obj_cite_number_status_flag) in { assert(obj_cite_number_status_flag <= 2); } body { - if (obj_cite_number_status_flag == 0) { - obj_cite_number=++obj_cite_number_; - } else { - obj_cite_number=0; - } + obj_cite_number=(obj_cite_number_status_flag == 0) + ? ++obj_cite_number_ + : 0; assert(obj_cite_number >= 0); return obj_cite_number; } @@ -1887,7 +1878,6 @@ template SiSUdocAbstraction() { } } struct ObjAttributes { - // class ObjAttributes : AssertObjAttributes { string[string] obj_txt; string para_and_blocks(string obj_txt_in) in { } @@ -2417,14 +2407,11 @@ template SiSUdocAbstraction() { } } struct ObjAttrib { - // struct ObjAttrib : AssertObjAttrib { - // auto sink = appender!(char[])(); auto attrib = ObjAttributes(); string[string] obj_attrib; string obj_attributes(string obj_is_, string obj_raw, string node) in { } body { - // string s = "{ \"language\": \"D\", \"rating\": 3.14, \"code\": \"42\" }"; scope(exit) { // destroy(obj_is_); destroy(obj_raw); @@ -2488,9 +2475,7 @@ template SiSUdocAbstraction() { obj_attrib["json"] = oa_j.toString(); debug(structattrib) { if (oa_j["is"].str() == "heading") { - // writeln(__LINE__); writeln(obj_attrib["json"]); - // writeln(node); writeln( "is: ", oa_j["is"].str(), "; obj_cite_number: ", oa_j["obj_cite_number"].integer() @@ -2504,7 +2489,6 @@ template SiSUdocAbstraction() { } } struct BookIndexNuggetHash { - // class BookIndexNuggetHash : AssertBookIndexNuggetHash { string main_term, sub_term, sub_term_bits; int obj_cite_number_offset, obj_cite_number_endpoint; string[] obj_cite_numbers; @@ -2859,7 +2843,6 @@ template SiSUdocAbstraction() { ++mkn; foreach (endnote; endnotes_) { attrib=""; - attrib=""; // endnotes ~= // set_abstract_object.contents_para( // obj, @@ -2957,7 +2940,6 @@ template SiSUdocAbstraction() { } } struct NodeStructureMetadata { - // class NodeStructureMetadata : AssertNodeJSON { int lv, lv0, lv1, lv2, lv3, lv4, lv5, lv6, lv7; int obj_cite_number; int[string] p_; // p_ parent_ diff --git a/src/sdp/ao_ansi_colors.d b/src/sdp/ao_ansi_colors.d index e5a46f9..dea331d 100644 --- a/src/sdp/ao_ansi_colors.d +++ b/src/sdp/ao_ansi_colors.d @@ -1,6 +1,5 @@ -/+ - utils - ao_util.d +/++ + ansi colors, depreciate use +/ template ScreenTxtColors() { string[string] scr_txt_color = [ diff --git a/src/sdp/ao_conf_make_meta.d b/src/sdp/ao_conf_make_meta.d index 04a9d7a..5bc9694 100644 --- a/src/sdp/ao_conf_make_meta.d +++ b/src/sdp/ao_conf_make_meta.d @@ -1,5 +1,12 @@ -/+ - extract native/orig header return associative array +/++ + extract native/orig header return associative array<BR> + + the header is passed as text (lopped off top of a sisu markup file until the + required first heading ^A~), determine whether is a native header or sdlang one + with a regex check if whether it contains the "native header" required tag/field + @title: then process accordingly as a "native header" or "sdlang header" + converting the metadata and make instructions to a common json format used by + program internally. Moved to associative array. +/ template SiSUheaderExtractHub() { private import diff --git a/src/sdp/ao_conf_make_meta_native.d b/src/sdp/ao_conf_make_meta_native.d index 9f0ad63..f70a7bf 100644 --- a/src/sdp/ao_conf_make_meta_native.d +++ b/src/sdp/ao_conf_make_meta_native.d @@ -1,4 +1,5 @@ -/+ +/++ + native headers using<br>@title:<BR>:subtitle:<BR>type tags<BR> extract native/orig header return associative array +/ template SiSUheaderExtractNative() { diff --git a/src/sdp/ao_conf_make_meta_sdlang.d b/src/sdp/ao_conf_make_meta_sdlang.d index 1cc3498..61b4960 100644 --- a/src/sdp/ao_conf_make_meta_sdlang.d +++ b/src/sdp/ao_conf_make_meta_sdlang.d @@ -1,5 +1,6 @@ -/+ - extract sdl header return sdl +/++ + sdlang headers<BR> + extract sdlang header return sdlang +/ template SiSUheaderExtractSDLang() { private import diff --git a/src/sdp/ao_defaults.d b/src/sdp/ao_defaults.d index ea5caae..8db42e2 100644 --- a/src/sdp/ao_defaults.d +++ b/src/sdp/ao_defaults.d @@ -1,6 +1,5 @@ -/+ - defaults - ao_defaults.d +/++ + default settings +/ template SiSUregisters() { string[string][string] conf_aa() { diff --git a/src/sdp/ao_object_setter.d b/src/sdp/ao_object_setter.d index 745de4e..6cb359b 100644 --- a/src/sdp/ao_object_setter.d +++ b/src/sdp/ao_object_setter.d @@ -1,5 +1,6 @@ -/+ - object setter +/++ + object setter: + setting of sisu objects for downstream processing ao_object_setter.d +/ template ObjectSetter() { diff --git a/src/sdp/ao_output_debugs.d b/src/sdp/ao_output_debugs.d index b5f96fa..9111cd6 100644 --- a/src/sdp/ao_output_debugs.d +++ b/src/sdp/ao_output_debugs.d @@ -1,6 +1,5 @@ -/+ +/++ output debugs - ao_output_debugs.d +/ template SiSUoutputDebugs() { struct BookIndexReport { diff --git a/src/sdp/ao_read_config_files.d b/src/sdp/ao_read_config_files.d index 49efe7b..013acdd 100644 --- a/src/sdp/ao_read_config_files.d +++ b/src/sdp/ao_read_config_files.d @@ -1,6 +1,7 @@ -/+ +/++ + read configuration files<BR> + - read config files<BR> ao_config_files.d - - read config files +/ template SiSUconfigIn() { private import diff --git a/src/sdp/ao_read_source_files.d b/src/sdp/ao_read_source_files.d index eabc4dc..5aef05d 100644 --- a/src/sdp/ao_read_source_files.d +++ b/src/sdp/ao_read_source_files.d @@ -1,9 +1,8 @@ -/+ - ao_read_source_files.d - - open markup files +/++ + module ao_read_source_files;<BR> + - open markup files<BR> - if master file scan for addional files to import/insert +/ -// module ao_read_source_files; template SiSUmarkupRaw() { private import std.exception, diff --git a/src/sdp/ao_rgx.d b/src/sdp/ao_rgx.d index ccaf1bd..2a10d53 100644 --- a/src/sdp/ao_rgx.d +++ b/src/sdp/ao_rgx.d @@ -1,6 +1,5 @@ -/+ - regex - ao_rgx.d +/++ + regex: regular expressions used in sisu document parser +/ template RgxInit() { struct Rgx { diff --git a/src/sdp/compile_time_info.d b/src/sdp/compile_time_info.d index 783ac62..2b0151d 100644 --- a/src/sdp/compile_time_info.d +++ b/src/sdp/compile_time_info.d @@ -1,6 +1,5 @@ -/+ +/++ compile_time_info - compile_time_info.d +/ template CompileTimeInfo() { version(Windows) { diff --git a/src/sdp/output_hub.d b/src/sdp/output_hub.d index 0206bf5..e7c0c9e 100644 --- a/src/sdp/output_hub.d +++ b/src/sdp/output_hub.d @@ -1,6 +1,6 @@ -/+ - output_hub.d - output_html.d +/++ + output hub<BR> + check & generate output types requested +/ template SiSUoutputHub() { struct SDPoutput { diff --git a/src/undead/doformat.d b/src/undead/doformat.d deleted file mode 100644 index 4fc0daf..0000000 --- a/src/undead/doformat.d +++ /dev/null @@ -1,1620 +0,0 @@ -// Written in the D programming language. - -/** - Copyright: Copyright Digital Mars 2000-2013. - - License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). - - Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, - Andrei Alexandrescu), and Kenji Hara - - Source: $(PHOBOSSRC std/_format.d) - */ -module undead.doformat; - -//debug=format; // uncomment to turn on debugging printf's - -import core.vararg; -import std.exception; -import std.meta; -import std.range.primitives; -import std.traits; -import std.format; - -version(CRuntime_DigitalMars) -{ - version = DigitalMarsC; -} - -version (DigitalMarsC) -{ - // This is DMC's internal floating point formatting function - extern (C) - { - extern shared char* function(int c, int flags, int precision, - in real* pdval, - char* buf, size_t* psl, int width) __pfloatfmt; - } -} - -/********************************************************************** - * Signals a mismatch between a format and its corresponding argument. - */ -class FormatException : Exception -{ - @safe pure nothrow - this() - { - super("format error"); - } - - @safe pure nothrow - this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) - { - super(msg, fn, ln, next); - } -} - - -// Legacy implementation - -enum Mangle : char -{ - Tvoid = 'v', - Tbool = 'b', - Tbyte = 'g', - Tubyte = 'h', - Tshort = 's', - Tushort = 't', - Tint = 'i', - Tuint = 'k', - Tlong = 'l', - Tulong = 'm', - Tfloat = 'f', - Tdouble = 'd', - Treal = 'e', - - Tifloat = 'o', - Tidouble = 'p', - Tireal = 'j', - Tcfloat = 'q', - Tcdouble = 'r', - Tcreal = 'c', - - Tchar = 'a', - Twchar = 'u', - Tdchar = 'w', - - Tarray = 'A', - Tsarray = 'G', - Taarray = 'H', - Tpointer = 'P', - Tfunction = 'F', - Tident = 'I', - Tclass = 'C', - Tstruct = 'S', - Tenum = 'E', - Ttypedef = 'T', - Tdelegate = 'D', - - Tconst = 'x', - Timmutable = 'y', -} - -// return the TypeInfo for a primitive type and null otherwise. This -// is required since for arrays of ints we only have the mangled char -// to work from. If arrays always subclassed TypeInfo_Array this -// routine could go away. -private TypeInfo primitiveTypeInfo(Mangle m) -{ - // BUG: should fix this in static this() to avoid double checked locking bug - __gshared TypeInfo[Mangle] dic; - if (!dic.length) - { - dic = [ - Mangle.Tvoid : typeid(void), - Mangle.Tbool : typeid(bool), - Mangle.Tbyte : typeid(byte), - Mangle.Tubyte : typeid(ubyte), - Mangle.Tshort : typeid(short), - Mangle.Tushort : typeid(ushort), - Mangle.Tint : typeid(int), - Mangle.Tuint : typeid(uint), - Mangle.Tlong : typeid(long), - Mangle.Tulong : typeid(ulong), - Mangle.Tfloat : typeid(float), - Mangle.Tdouble : typeid(double), - Mangle.Treal : typeid(real), - Mangle.Tifloat : typeid(ifloat), - Mangle.Tidouble : typeid(idouble), - Mangle.Tireal : typeid(ireal), - Mangle.Tcfloat : typeid(cfloat), - Mangle.Tcdouble : typeid(cdouble), - Mangle.Tcreal : typeid(creal), - Mangle.Tchar : typeid(char), - Mangle.Twchar : typeid(wchar), - Mangle.Tdchar : typeid(dchar) - ]; - } - auto p = m in dic; - return p ? *p : null; -} - -// This stuff has been removed from the docs and is planned for deprecation. -/* - * Interprets variadic argument list pointed to by argptr whose types - * are given by arguments[], formats them according to embedded format - * strings in the variadic argument list, and sends the resulting - * characters to putc. - * - * The variadic arguments are consumed in order. Each is formatted - * into a sequence of chars, using the default format specification - * for its type, and the characters are sequentially passed to putc. - * If a $(D char[]), $(D wchar[]), or $(D dchar[]) argument is - * encountered, it is interpreted as a format string. As many - * arguments as specified in the format string are consumed and - * formatted according to the format specifications in that string and - * passed to putc. If there are too few remaining arguments, a - * $(D FormatException) is thrown. If there are more remaining arguments than - * needed by the format specification, the default processing of - * arguments resumes until they are all consumed. - * - * Params: - * putc = Output is sent do this delegate, character by character. - * arguments = Array of $(D TypeInfo)s, one for each argument to be formatted. - * argptr = Points to variadic argument list. - * - * Throws: - * Mismatched arguments and formats result in a $(D FormatException) being thrown. - * - * Format_String: - * <a name="format-string">$(I Format strings)</a> - * consist of characters interspersed with - * $(I format specifications). Characters are simply copied - * to the output (such as putc) after any necessary conversion - * to the corresponding UTF-8 sequence. - * - * A $(I format specification) starts with a '%' character, - * and has the following grammar: - -$(CONSOLE -$(I FormatSpecification): - $(B '%%') - $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar) - -$(I Flags): - $(I empty) - $(B '-') $(I Flags) - $(B '+') $(I Flags) - $(B '#') $(I Flags) - $(B '0') $(I Flags) - $(B ' ') $(I Flags) - -$(I Width): - $(I empty) - $(I Integer) - $(B '*') - -$(I Precision): - $(I empty) - $(B '.') - $(B '.') $(I Integer) - $(B '.*') - -$(I Integer): - $(I Digit) - $(I Digit) $(I Integer) - -$(I Digit): - $(B '0') - $(B '1') - $(B '2') - $(B '3') - $(B '4') - $(B '5') - $(B '6') - $(B '7') - $(B '8') - $(B '9') - -$(I FormatChar): - $(B 's') - $(B 'b') - $(B 'd') - $(B 'o') - $(B 'x') - $(B 'X') - $(B 'e') - $(B 'E') - $(B 'f') - $(B 'F') - $(B 'g') - $(B 'G') - $(B 'a') - $(B 'A') -) - $(DL - $(DT $(I Flags)) - $(DL - $(DT $(B '-')) - $(DD - Left justify the result in the field. - It overrides any $(B 0) flag.) - - $(DT $(B '+')) - $(DD Prefix positive numbers in a signed conversion with a $(B +). - It overrides any $(I space) flag.) - - $(DT $(B '#')) - $(DD Use alternative formatting: - $(DL - $(DT For $(B 'o'):) - $(DD Add to precision as necessary so that the first digit - of the octal formatting is a '0', even if both the argument - and the $(I Precision) are zero.) - $(DT For $(B 'x') ($(B 'X')):) - $(DD If non-zero, prefix result with $(B 0x) ($(B 0X)).) - $(DT For floating point formatting:) - $(DD Always insert the decimal point.) - $(DT For $(B 'g') ($(B 'G')):) - $(DD Do not elide trailing zeros.) - )) - - $(DT $(B '0')) - $(DD For integer and floating point formatting when not nan or - infinity, use leading zeros - to pad rather than spaces. - Ignore if there's a $(I Precision).) - - $(DT $(B ' ')) - $(DD Prefix positive numbers in a signed conversion with a space.) - ) - - $(DT $(I Width)) - $(DD - Specifies the minimum field width. - If the width is a $(B *), the next argument, which must be - of type $(B int), is taken as the width. - If the width is negative, it is as if the $(B -) was given - as a $(I Flags) character.) - - $(DT $(I Precision)) - $(DD Gives the precision for numeric conversions. - If the precision is a $(B *), the next argument, which must be - of type $(B int), is taken as the precision. If it is negative, - it is as if there was no $(I Precision).) - - $(DT $(I FormatChar)) - $(DD - $(DL - $(DT $(B 's')) - $(DD The corresponding argument is formatted in a manner consistent - with its type: - $(DL - $(DT $(B bool)) - $(DD The result is <tt>'true'</tt> or <tt>'false'</tt>.) - $(DT integral types) - $(DD The $(B %d) format is used.) - $(DT floating point types) - $(DD The $(B %g) format is used.) - $(DT string types) - $(DD The result is the string converted to UTF-8.) - A $(I Precision) specifies the maximum number of characters - to use in the result. - $(DT classes derived from $(B Object)) - $(DD The result is the string returned from the class instance's - $(B .toString()) method. - A $(I Precision) specifies the maximum number of characters - to use in the result.) - $(DT non-string static and dynamic arrays) - $(DD The result is [s<sub>0</sub>, s<sub>1</sub>, ...] - where s<sub>k</sub> is the kth element - formatted with the default format.) - )) - - $(DT $(B 'b','d','o','x','X')) - $(DD The corresponding argument must be an integral type - and is formatted as an integer. If the argument is a signed type - and the $(I FormatChar) is $(B d) it is converted to - a signed string of characters, otherwise it is treated as - unsigned. An argument of type $(B bool) is formatted as '1' - or '0'. The base used is binary for $(B b), octal for $(B o), - decimal - for $(B d), and hexadecimal for $(B x) or $(B X). - $(B x) formats using lower case letters, $(B X) uppercase. - If there are fewer resulting digits than the $(I Precision), - leading zeros are used as necessary. - If the $(I Precision) is 0 and the number is 0, no digits - result.) - - $(DT $(B 'e','E')) - $(DD A floating point number is formatted as one digit before - the decimal point, $(I Precision) digits after, the $(I FormatChar), - ±, followed by at least a two digit exponent: $(I d.dddddd)e$(I ±dd). - If there is no $(I Precision), six - digits are generated after the decimal point. - If the $(I Precision) is 0, no decimal point is generated.) - - $(DT $(B 'f','F')) - $(DD A floating point number is formatted in decimal notation. - The $(I Precision) specifies the number of digits generated - after the decimal point. It defaults to six. At least one digit - is generated before the decimal point. If the $(I Precision) - is zero, no decimal point is generated.) - - $(DT $(B 'g','G')) - $(DD A floating point number is formatted in either $(B e) or - $(B f) format for $(B g); $(B E) or $(B F) format for - $(B G). - The $(B f) format is used if the exponent for an $(B e) format - is greater than -5 and less than the $(I Precision). - The $(I Precision) specifies the number of significant - digits, and defaults to six. - Trailing zeros are elided after the decimal point, if the fractional - part is zero then no decimal point is generated.) - - $(DT $(B 'a','A')) - $(DD A floating point number is formatted in hexadecimal - exponential notation 0x$(I h.hhhhhh)p$(I ±d). - There is one hexadecimal digit before the decimal point, and as - many after as specified by the $(I Precision). - If the $(I Precision) is zero, no decimal point is generated. - If there is no $(I Precision), as many hexadecimal digits as - necessary to exactly represent the mantissa are generated. - The exponent is written in as few digits as possible, - but at least one, is in decimal, and represents a power of 2 as in - $(I h.hhhhhh)*2<sup>$(I ±d)</sup>. - The exponent for zero is zero. - The hexadecimal digits, x and p are in upper case if the - $(I FormatChar) is upper case.) - ) - - Floating point NaN's are formatted as $(B nan) if the - $(I FormatChar) is lower case, or $(B NAN) if upper. - Floating point infinities are formatted as $(B inf) or - $(B infinity) if the - $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper. - )) - -Example: - -------------------------- -import core.stdc.stdio; -import std.format; - -void myPrint(...) -{ - void putc(dchar c) - { - fputc(c, stdout); - } - - std.format.doFormat(&putc, _arguments, _argptr); -} - -void main() -{ - int x = 27; - - // prints 'The answer is 27:6' - myPrint("The answer is %s:", x, 6); -} ------------------------- - */ -void doFormat()(scope void delegate(dchar) putc, TypeInfo[] arguments, va_list ap) -{ - import std.utf : toUCSindex, isValidDchar, UTFException, toUTF8; - import core.stdc.string : strlen; - import core.stdc.stdlib : alloca, malloc, realloc, free; - import core.stdc.stdio : snprintf; - - size_t bufLength = 1024; - void* argBuffer = malloc(bufLength); - scope(exit) free(argBuffer); - - size_t bufUsed = 0; - foreach (ti; arguments) - { - // Ensure the required alignment - bufUsed += ti.talign - 1; - bufUsed -= (cast(size_t)argBuffer + bufUsed) & (ti.talign - 1); - auto pos = bufUsed; - // Align to next word boundary - bufUsed += ti.tsize + size_t.sizeof - 1; - bufUsed -= (cast(size_t)argBuffer + bufUsed) & (size_t.sizeof - 1); - // Resize buffer if necessary - while (bufUsed > bufLength) - { - bufLength *= 2; - argBuffer = realloc(argBuffer, bufLength); - } - // Copy argument into buffer - va_arg(ap, ti, argBuffer + pos); - } - - auto argptr = argBuffer; - void* skipArg(TypeInfo ti) - { - // Ensure the required alignment - argptr += ti.talign - 1; - argptr -= cast(size_t)argptr & (ti.talign - 1); - auto p = argptr; - // Align to next word boundary - argptr += ti.tsize + size_t.sizeof - 1; - argptr -= cast(size_t)argptr & (size_t.sizeof - 1); - return p; - } - auto getArg(T)() - { - return *cast(T*)skipArg(typeid(T)); - } - - TypeInfo ti; - Mangle m; - uint flags; - int field_width; - int precision; - - enum : uint - { - FLdash = 1, - FLplus = 2, - FLspace = 4, - FLhash = 8, - FLlngdbl = 0x20, - FL0pad = 0x40, - FLprecision = 0x80, - } - - static TypeInfo skipCI(TypeInfo valti) - { - for (;;) - { - if (typeid(valti).name.length == 18 && - typeid(valti).name[9..18] == "Invariant") - valti = (cast(TypeInfo_Invariant)valti).base; - else if (typeid(valti).name.length == 14 && - typeid(valti).name[9..14] == "Const") - valti = (cast(TypeInfo_Const)valti).base; - else - break; - } - - return valti; - } - - void formatArg(char fc) - { - bool vbit; - ulong vnumber; - char vchar; - dchar vdchar; - Object vobject; - real vreal; - creal vcreal; - Mangle m2; - int signed = 0; - uint base = 10; - int uc; - char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary - const(char)* prefix = ""; - string s; - - void putstr(const char[] s) - { - //printf("putstr: s = %.*s, flags = x%x\n", s.length, s.ptr, flags); - ptrdiff_t padding = field_width - - (strlen(prefix) + toUCSindex(s, s.length)); - ptrdiff_t prepad = 0; - ptrdiff_t postpad = 0; - if (padding > 0) - { - if (flags & FLdash) - postpad = padding; - else - prepad = padding; - } - - if (flags & FL0pad) - { - while (*prefix) - putc(*prefix++); - while (prepad--) - putc('0'); - } - else - { - while (prepad--) - putc(' '); - while (*prefix) - putc(*prefix++); - } - - foreach (dchar c; s) - putc(c); - - while (postpad--) - putc(' '); - } - - void putreal(real v) - { - //printf("putreal %Lg\n", vreal); - - switch (fc) - { - case 's': - fc = 'g'; - break; - - case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A': - break; - - default: - //printf("fc = '%c'\n", fc); - Lerror: - throw new FormatException("incompatible format character for floating point type"); - } - version (DigitalMarsC) - { - uint sl; - char[] fbuf = tmpbuf; - if (!(flags & FLprecision)) - precision = 6; - while (1) - { - sl = fbuf.length; - prefix = (*__pfloatfmt)(fc, flags | FLlngdbl, - precision, &v, cast(char*)fbuf, &sl, field_width); - if (sl != -1) - break; - sl = fbuf.length * 2; - fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; - } - putstr(fbuf[0 .. sl]); - } - else - { - ptrdiff_t sl; - char[] fbuf = tmpbuf; - char[12] format; - format[0] = '%'; - int i = 1; - if (flags & FLdash) - format[i++] = '-'; - if (flags & FLplus) - format[i++] = '+'; - if (flags & FLspace) - format[i++] = ' '; - if (flags & FLhash) - format[i++] = '#'; - if (flags & FL0pad) - format[i++] = '0'; - format[i + 0] = '*'; - format[i + 1] = '.'; - format[i + 2] = '*'; - format[i + 3] = 'L'; - format[i + 4] = fc; - format[i + 5] = 0; - if (!(flags & FLprecision)) - precision = -1; - while (1) - { - sl = fbuf.length; - int n; - version (CRuntime_Microsoft) - { - import std.math : isNaN, isInfinity; - if (isNaN(v)) // snprintf writes 1.#QNAN - n = snprintf(fbuf.ptr, sl, "nan"); - else if (isInfinity(v)) // snprintf writes 1.#INF - n = snprintf(fbuf.ptr, sl, v < 0 ? "-inf" : "inf"); - else - n = snprintf(fbuf.ptr, sl, format.ptr, field_width, - precision, cast(double)v); - } - else - n = snprintf(fbuf.ptr, sl, format.ptr, field_width, - precision, v); - //printf("format = '%s', n = %d\n", cast(char*)format, n); - if (n >= 0 && n < sl) - { sl = n; - break; - } - if (n < 0) - sl = sl * 2; - else - sl = n + 1; - fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; - } - putstr(fbuf[0 .. sl]); - } - return; - } - - static Mangle getMan(TypeInfo ti) - { - auto m = cast(Mangle)typeid(ti).name[9]; - if (typeid(ti).name.length == 20 && - typeid(ti).name[9..20] == "StaticArray") - m = cast(Mangle)'G'; - return m; - } - - /* p = pointer to the first element in the array - * len = number of elements in the array - * valti = type of the elements - */ - void putArray(void* p, size_t len, TypeInfo valti) - { - //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize); - putc('['); - valti = skipCI(valti); - size_t tsize = valti.tsize; - auto argptrSave = argptr; - auto tiSave = ti; - auto mSave = m; - ti = valti; - //printf("\n%.*s\n", typeid(valti).name.length, typeid(valti).name.ptr); - m = getMan(valti); - while (len--) - { - //doFormat(putc, (&valti)[0 .. 1], p); - argptr = p; - formatArg('s'); - p += tsize; - if (len > 0) putc(','); - } - m = mSave; - ti = tiSave; - argptr = argptrSave; - putc(']'); - } - - void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti) - { - putc('['); - bool comma=false; - auto argptrSave = argptr; - auto tiSave = ti; - auto mSave = m; - valti = skipCI(valti); - keyti = skipCI(keyti); - foreach (ref fakevalue; vaa) - { - if (comma) putc(','); - comma = true; - void *pkey = &fakevalue; - version (D_LP64) - pkey -= (long.sizeof + 15) & ~(15); - else - pkey -= (long.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1); - - // the key comes before the value - auto keysize = keyti.tsize; - version (D_LP64) - auto keysizet = (keysize + 15) & ~(15); - else - auto keysizet = (keysize + size_t.sizeof - 1) & ~(size_t.sizeof - 1); - - void* pvalue = pkey + keysizet; - - //doFormat(putc, (&keyti)[0..1], pkey); - m = getMan(keyti); - argptr = pkey; - - ti = keyti; - formatArg('s'); - - putc(':'); - //doFormat(putc, (&valti)[0..1], pvalue); - m = getMan(valti); - argptr = pvalue; - - ti = valti; - formatArg('s'); - } - m = mSave; - ti = tiSave; - argptr = argptrSave; - putc(']'); - } - - //printf("formatArg(fc = '%c', m = '%c')\n", fc, m); - int mi; - switch (m) - { - case Mangle.Tbool: - vbit = getArg!(bool)(); - if (fc != 's') - { vnumber = vbit; - goto Lnumber; - } - putstr(vbit ? "true" : "false"); - return; - - case Mangle.Tchar: - vchar = getArg!(char)(); - if (fc != 's') - { vnumber = vchar; - goto Lnumber; - } - L2: - putstr((&vchar)[0 .. 1]); - return; - - case Mangle.Twchar: - vdchar = getArg!(wchar)(); - goto L1; - - case Mangle.Tdchar: - vdchar = getArg!(dchar)(); - L1: - if (fc != 's') - { vnumber = vdchar; - goto Lnumber; - } - if (vdchar <= 0x7F) - { vchar = cast(char)vdchar; - goto L2; - } - else - { if (!isValidDchar(vdchar)) - throw new UTFException("invalid dchar in format"); - char[4] vbuf; - putstr(toUTF8(vbuf, vdchar)); - } - return; - - case Mangle.Tbyte: - signed = 1; - vnumber = getArg!(byte)(); - goto Lnumber; - - case Mangle.Tubyte: - vnumber = getArg!(ubyte)(); - goto Lnumber; - - case Mangle.Tshort: - signed = 1; - vnumber = getArg!(short)(); - goto Lnumber; - - case Mangle.Tushort: - vnumber = getArg!(ushort)(); - goto Lnumber; - - case Mangle.Tint: - signed = 1; - vnumber = getArg!(int)(); - goto Lnumber; - - case Mangle.Tuint: - Luint: - vnumber = getArg!(uint)(); - goto Lnumber; - - case Mangle.Tlong: - signed = 1; - vnumber = cast(ulong)getArg!(long)(); - goto Lnumber; - - case Mangle.Tulong: - Lulong: - vnumber = getArg!(ulong)(); - goto Lnumber; - - case Mangle.Tclass: - vobject = getArg!(Object)(); - if (vobject is null) - s = "null"; - else - s = vobject.toString(); - goto Lputstr; - - case Mangle.Tpointer: - vnumber = cast(ulong)getArg!(void*)(); - if (fc != 'x') uc = 1; - flags |= FL0pad; - if (!(flags & FLprecision)) - { flags |= FLprecision; - precision = (void*).sizeof; - } - base = 16; - goto Lnumber; - - case Mangle.Tfloat: - case Mangle.Tifloat: - if (fc == 'x' || fc == 'X') - goto Luint; - vreal = getArg!(float)(); - goto Lreal; - - case Mangle.Tdouble: - case Mangle.Tidouble: - if (fc == 'x' || fc == 'X') - goto Lulong; - vreal = getArg!(double)(); - goto Lreal; - - case Mangle.Treal: - case Mangle.Tireal: - vreal = getArg!(real)(); - goto Lreal; - - case Mangle.Tcfloat: - vcreal = getArg!(cfloat)(); - goto Lcomplex; - - case Mangle.Tcdouble: - vcreal = getArg!(cdouble)(); - goto Lcomplex; - - case Mangle.Tcreal: - vcreal = getArg!(creal)(); - goto Lcomplex; - - case Mangle.Tsarray: - putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next); - return; - - case Mangle.Tarray: - mi = 10; - if (typeid(ti).name.length == 14 && - typeid(ti).name[9..14] == "Array") - { // array of non-primitive types - TypeInfo tn = (cast(TypeInfo_Array)ti).next; - tn = skipCI(tn); - switch (cast(Mangle)typeid(tn).name[9]) - { - case Mangle.Tchar: goto LarrayChar; - case Mangle.Twchar: goto LarrayWchar; - case Mangle.Tdchar: goto LarrayDchar; - default: - break; - } - void[] va = getArg!(void[])(); - putArray(va.ptr, va.length, tn); - return; - } - if (typeid(ti).name.length == 25 && - typeid(ti).name[9..25] == "AssociativeArray") - { // associative array - ubyte[long] vaa = getArg!(ubyte[long])(); - putAArray(vaa, - (cast(TypeInfo_AssociativeArray)ti).next, - (cast(TypeInfo_AssociativeArray)ti).key); - return; - } - - while (1) - { - m2 = cast(Mangle)typeid(ti).name[mi]; - switch (m2) - { - case Mangle.Tchar: - LarrayChar: - s = getArg!(string)(); - goto Lputstr; - - case Mangle.Twchar: - LarrayWchar: - wchar[] sw = getArg!(wchar[])(); - s = toUTF8(sw); - goto Lputstr; - - case Mangle.Tdchar: - LarrayDchar: - s = toUTF8(getArg!(dstring)()); - Lputstr: - if (fc != 's') - throw new FormatException("string"); - if (flags & FLprecision && precision < s.length) - s = s[0 .. precision]; - putstr(s); - break; - - case Mangle.Tconst: - case Mangle.Timmutable: - mi++; - continue; - - default: - TypeInfo ti2 = primitiveTypeInfo(m2); - if (!ti2) - goto Lerror; - void[] va = getArg!(void[])(); - putArray(va.ptr, va.length, ti2); - } - return; - } - assert(0); - - case Mangle.Ttypedef: - ti = (cast(TypeInfo_Typedef)ti).base; - m = cast(Mangle)typeid(ti).name[9]; - formatArg(fc); - return; - - case Mangle.Tenum: - ti = (cast(TypeInfo_Enum)ti).base; - m = cast(Mangle)typeid(ti).name[9]; - formatArg(fc); - return; - - case Mangle.Tstruct: - { TypeInfo_Struct tis = cast(TypeInfo_Struct)ti; - if (tis.xtoString is null) - throw new FormatException("Can't convert " ~ tis.toString() - ~ " to string: \"string toString()\" not defined"); - s = tis.xtoString(skipArg(tis)); - goto Lputstr; - } - - default: - goto Lerror; - } - - Lnumber: - switch (fc) - { - case 's': - case 'd': - if (signed) - { if (cast(long)vnumber < 0) - { prefix = "-"; - vnumber = -vnumber; - } - else if (flags & FLplus) - prefix = "+"; - else if (flags & FLspace) - prefix = " "; - } - break; - - case 'b': - signed = 0; - base = 2; - break; - - case 'o': - signed = 0; - base = 8; - break; - - case 'X': - uc = 1; - if (flags & FLhash && vnumber) - prefix = "0X"; - signed = 0; - base = 16; - break; - - case 'x': - if (flags & FLhash && vnumber) - prefix = "0x"; - signed = 0; - base = 16; - break; - - default: - goto Lerror; - } - - if (!signed) - { - switch (m) - { - case Mangle.Tbyte: - vnumber &= 0xFF; - break; - - case Mangle.Tshort: - vnumber &= 0xFFFF; - break; - - case Mangle.Tint: - vnumber &= 0xFFFFFFFF; - break; - - default: - break; - } - } - - if (flags & FLprecision && fc != 'p') - flags &= ~FL0pad; - - if (vnumber < base) - { - if (vnumber == 0 && precision == 0 && flags & FLprecision && - !(fc == 'o' && flags & FLhash)) - { - putstr(null); - return; - } - if (precision == 0 || !(flags & FLprecision)) - { vchar = cast(char)('0' + vnumber); - if (vnumber < 10) - vchar = cast(char)('0' + vnumber); - else - vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber); - goto L2; - } - } - - { - ptrdiff_t n = tmpbuf.length; - char c; - int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1)); - - while (vnumber) - { - c = cast(char)((vnumber % base) + '0'); - if (c > '9') - c += hexoffset; - vnumber /= base; - tmpbuf[--n] = c; - } - if (tmpbuf.length - n < precision && precision < tmpbuf.length) - { - ptrdiff_t m = tmpbuf.length - precision; - tmpbuf[m .. n] = '0'; - n = m; - } - else if (flags & FLhash && fc == 'o') - prefix = "0"; - putstr(tmpbuf[n .. tmpbuf.length]); - return; - } - - Lreal: - putreal(vreal); - return; - - Lcomplex: - putreal(vcreal.re); - if (vcreal.im >= 0) - { - putc('+'); - } - putreal(vcreal.im); - putc('i'); - return; - - Lerror: - throw new FormatException("formatArg"); - } - - for (int j = 0; j < arguments.length; ) - { - ti = arguments[j++]; - //printf("arg[%d]: '%.*s' %d\n", j, typeid(ti).name.length, typeid(ti).name.ptr, typeid(ti).name.length); - //ti.print(); - - flags = 0; - precision = 0; - field_width = 0; - - ti = skipCI(ti); - int mi = 9; - do - { - if (typeid(ti).name.length <= mi) - goto Lerror; - m = cast(Mangle)typeid(ti).name[mi++]; - } while (m == Mangle.Tconst || m == Mangle.Timmutable); - - if (m == Mangle.Tarray) - { - if (typeid(ti).name.length == 14 && - typeid(ti).name[9..14] == "Array") - { - TypeInfo tn = (cast(TypeInfo_Array)ti).next; - tn = skipCI(tn); - switch (cast(Mangle)typeid(tn).name[9]) - { - case Mangle.Tchar: - case Mangle.Twchar: - case Mangle.Tdchar: - ti = tn; - mi = 9; - break; - default: - break; - } - } - L1: - Mangle m2 = cast(Mangle)typeid(ti).name[mi]; - string fmt; // format string - wstring wfmt; - dstring dfmt; - - /* For performance reasons, this code takes advantage of the - * fact that most format strings will be ASCII, and that the - * format specifiers are always ASCII. This means we only need - * to deal with UTF in a couple of isolated spots. - */ - - switch (m2) - { - case Mangle.Tchar: - fmt = getArg!(string)(); - break; - - case Mangle.Twchar: - wfmt = getArg!(wstring)(); - fmt = toUTF8(wfmt); - break; - - case Mangle.Tdchar: - dfmt = getArg!(dstring)(); - fmt = toUTF8(dfmt); - break; - - case Mangle.Tconst: - case Mangle.Timmutable: - mi++; - goto L1; - - default: - formatArg('s'); - continue; - } - - for (size_t i = 0; i < fmt.length; ) - { dchar c = fmt[i++]; - - dchar getFmtChar() - { // Valid format specifier characters will never be UTF - if (i == fmt.length) - throw new FormatException("invalid specifier"); - return fmt[i++]; - } - - int getFmtInt() - { int n; - - while (1) - { - n = n * 10 + (c - '0'); - if (n < 0) // overflow - throw new FormatException("int overflow"); - c = getFmtChar(); - if (c < '0' || c > '9') - break; - } - return n; - } - - int getFmtStar() - { Mangle m; - TypeInfo ti; - - if (j == arguments.length) - throw new FormatException("too few arguments"); - ti = arguments[j++]; - m = cast(Mangle)typeid(ti).name[9]; - if (m != Mangle.Tint) - throw new FormatException("int argument expected"); - return getArg!(int)(); - } - - if (c != '%') - { - if (c > 0x7F) // if UTF sequence - { - i--; // back up and decode UTF sequence - import std.utf : decode; - c = decode(fmt, i); - } - Lputc: - putc(c); - continue; - } - - // Get flags {-+ #} - flags = 0; - while (1) - { - c = getFmtChar(); - switch (c) - { - case '-': flags |= FLdash; continue; - case '+': flags |= FLplus; continue; - case ' ': flags |= FLspace; continue; - case '#': flags |= FLhash; continue; - case '0': flags |= FL0pad; continue; - - case '%': if (flags == 0) - goto Lputc; - break; - - default: break; - } - break; - } - - // Get field width - field_width = 0; - if (c == '*') - { - field_width = getFmtStar(); - if (field_width < 0) - { flags |= FLdash; - field_width = -field_width; - } - - c = getFmtChar(); - } - else if (c >= '0' && c <= '9') - field_width = getFmtInt(); - - if (flags & FLplus) - flags &= ~FLspace; - if (flags & FLdash) - flags &= ~FL0pad; - - // Get precision - precision = 0; - if (c == '.') - { flags |= FLprecision; - //flags &= ~FL0pad; - - c = getFmtChar(); - if (c == '*') - { - precision = getFmtStar(); - if (precision < 0) - { precision = 0; - flags &= ~FLprecision; - } - - c = getFmtChar(); - } - else if (c >= '0' && c <= '9') - precision = getFmtInt(); - } - - if (j == arguments.length) - goto Lerror; - ti = arguments[j++]; - ti = skipCI(ti); - mi = 9; - do - { - m = cast(Mangle)typeid(ti).name[mi++]; - } while (m == Mangle.Tconst || m == Mangle.Timmutable); - - if (c > 0x7F) // if UTF sequence - goto Lerror; // format specifiers can't be UTF - formatArg(cast(char)c); - } - } - else - { - formatArg('s'); - } - } - return; - - Lerror: - throw new FormatException(); -} - - -private bool needToSwapEndianess(Char)(ref FormatSpec!Char f) -{ - import std.system : endian, Endian; - - return endian == Endian.littleEndian && f.flPlus - || endian == Endian.bigEndian && f.flDash; -} - -/* ======================== Unit Tests ====================================== */ - -unittest -{ - import std.conv : octal; - - int i; - string s; - - debug(format) printf("std.format.format.unittest\n"); - - s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo"); - assert(s == "hello world! true 57 1000000000x foo"); - - s = format("%s %A %s", 1.67, -1.28, float.nan); - /* The host C library is used to format floats. - * C99 doesn't specify what the hex digit before the decimal point - * is for %A. - */ - //version (linux) - // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan"); - //else version (OSX) - // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s); - //else - version (MinGW) - assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s); - else version (CRuntime_Microsoft) - assert(s == "1.67 -0X1.47AE14P+0 nan" - || s == "1.67 -0X1.47AE147AE147BP+0 nan", s); // MSVCRT 14+ (VS 2015) - else - assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s); - - s = format("%x %X", 0x1234AF, 0xAFAFAFAF); - assert(s == "1234af AFAFAFAF"); - - s = format("%b %o", 0x1234AF, 0xAFAFAFAF); - assert(s == "100100011010010101111 25753727657"); - - s = format("%d %s", 0x1234AF, 0xAFAFAFAF); - assert(s == "1193135 2947526575"); - - //version(X86_64) - //{ - // pragma(msg, "several format tests disabled on x86_64 due to bug 5625"); - //} - //else - //{ - s = format("%s", 1.2 + 3.4i); - assert(s == "1.2+3.4i", s); - - //s = format("%x %X", 1.32, 6.78f); - //assert(s == "3ff51eb851eb851f 40D8F5C3"); - - //} - - s = format("%#06.*f",2,12.345); - assert(s == "012.35"); - - s = format("%#0*.*f",6,2,12.345); - assert(s == "012.35"); - - s = format("%7.4g:", 12.678); - assert(s == " 12.68:"); - - s = format("%7.4g:", 12.678L); - assert(s == " 12.68:"); - - s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1); - assert(s == "-4.000000|-0010|0x001| 0x1"); - - i = -10; - s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); - assert(s == "-10|-10|-10|-10|-10.0000"); - - i = -5; - s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); - assert(s == "-5| -5|-05|-5|-5.0000"); - - i = 0; - s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); - assert(s == "0| 0|000|0|0.0000"); - - i = 5; - s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); - assert(s == "5| 5|005|5|5.0000"); - - i = 10; - s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); - assert(s == "10| 10|010|10|10.0000"); - - s = format("%.0d", 0); - assert(s == ""); - - s = format("%.g", .34); - assert(s == "0.3"); - - s = format("%.0g", .34); - assert(s == "0.3"); - - s = format("%.2g", .34); - assert(s == "0.34"); - - s = format("%0.0008f", 1e-08); - assert(s == "0.00000001"); - - s = format("%0.0008f", 1e-05); - assert(s == "0.00001000"); - - s = "helloworld"; - string r; - r = format("%.2s", s[0..5]); - assert(r == "he"); - r = format("%.20s", s[0..5]); - assert(r == "hello"); - r = format("%8s", s[0..5]); - assert(r == " hello"); - - byte[] arrbyte = new byte[4]; - arrbyte[0] = 100; - arrbyte[1] = -99; - arrbyte[3] = 0; - r = format("%s", arrbyte); - assert(r == "[100, -99, 0, 0]"); - - ubyte[] arrubyte = new ubyte[4]; - arrubyte[0] = 100; - arrubyte[1] = 200; - arrubyte[3] = 0; - r = format("%s", arrubyte); - assert(r == "[100, 200, 0, 0]"); - - short[] arrshort = new short[4]; - arrshort[0] = 100; - arrshort[1] = -999; - arrshort[3] = 0; - r = format("%s", arrshort); - assert(r == "[100, -999, 0, 0]"); - - ushort[] arrushort = new ushort[4]; - arrushort[0] = 100; - arrushort[1] = 20_000; - arrushort[3] = 0; - r = format("%s", arrushort); - assert(r == "[100, 20000, 0, 0]"); - - int[] arrint = new int[4]; - arrint[0] = 100; - arrint[1] = -999; - arrint[3] = 0; - r = format("%s", arrint); - assert(r == "[100, -999, 0, 0]"); - - long[] arrlong = new long[4]; - arrlong[0] = 100; - arrlong[1] = -999; - arrlong[3] = 0; - r = format("%s", arrlong); - assert(r == "[100, -999, 0, 0]"); - - ulong[] arrulong = new ulong[4]; - arrulong[0] = 100; - arrulong[1] = 999; - arrulong[3] = 0; - r = format("%s", arrulong); - assert(r == "[100, 999, 0, 0]"); - - string[] arr2 = new string[4]; - arr2[0] = "hello"; - arr2[1] = "world"; - arr2[3] = "foo"; - r = format("%s", arr2); - assert(r == `["hello", "world", "", "foo"]`); - - r = format("%.8d", 7); - assert(r == "00000007"); - r = format("%.8x", 10); - assert(r == "0000000a"); - - r = format("%-3d", 7); - assert(r == "7 "); - - r = format("%*d", -3, 7); - assert(r == "7 "); - - r = format("%.*d", -3, 7); - assert(r == "7"); - - r = format("abc"c); - assert(r == "abc"); - - //format() returns the same type as inputted. - wstring wr; - wr = format("def"w); - assert(wr == "def"w); - - dstring dr; - dr = format("ghi"d); - assert(dr == "ghi"d); - - void* p = cast(void*)0xDEADBEEF; - r = format("%s", p); - assert(r == "DEADBEEF"); - - r = format("%#x", 0xabcd); - assert(r == "0xabcd"); - r = format("%#X", 0xABCD); - assert(r == "0XABCD"); - - r = format("%#o", octal!12345); - assert(r == "012345"); - r = format("%o", 9); - assert(r == "11"); - r = format("%#o", 0); // issue 15663 - assert(r == "0"); - - r = format("%+d", 123); - assert(r == "+123"); - r = format("%+d", -123); - assert(r == "-123"); - r = format("% d", 123); - assert(r == " 123"); - r = format("% d", -123); - assert(r == "-123"); - - r = format("%%"); - assert(r == "%"); - - r = format("%d", true); - assert(r == "1"); - r = format("%d", false); - assert(r == "0"); - - r = format("%d", 'a'); - assert(r == "97"); - wchar wc = 'a'; - r = format("%d", wc); - assert(r == "97"); - dchar dc = 'a'; - r = format("%d", dc); - assert(r == "97"); - - byte b = byte.max; - r = format("%x", b); - assert(r == "7f"); - r = format("%x", ++b); - assert(r == "80"); - r = format("%x", ++b); - assert(r == "81"); - - short sh = short.max; - r = format("%x", sh); - assert(r == "7fff"); - r = format("%x", ++sh); - assert(r == "8000"); - r = format("%x", ++sh); - assert(r == "8001"); - - i = int.max; - r = format("%x", i); - assert(r == "7fffffff"); - r = format("%x", ++i); - assert(r == "80000000"); - r = format("%x", ++i); - assert(r == "80000001"); - - r = format("%x", 10); - assert(r == "a"); - r = format("%X", 10); - assert(r == "A"); - r = format("%x", 15); - assert(r == "f"); - r = format("%X", 15); - assert(r == "F"); - - Object c = null; - r = format("%s", c); - assert(r == "null"); - - enum TestEnum - { - Value1, Value2 - } - r = format("%s", TestEnum.Value2); - assert(r == "Value2"); - - immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); - r = format("%s", aa.values); - assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`); - r = format("%s", aa); - assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`); - - static const dchar[] ds = ['a','b']; - for (int j = 0; j < ds.length; ++j) - { - r = format(" %d", ds[j]); - if (j == 0) - assert(r == " 97"); - else - assert(r == " 98"); - } - - r = format(">%14d<, %s", 15, [1,2,3]); - assert(r == "> 15<, [1, 2, 3]"); - - assert(format("%8s", "bar") == " bar"); - assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4"); -} diff --git a/src/undead/internal/file.d b/src/undead/internal/file.d deleted file mode 100644 index f756674..0000000 --- a/src/undead/internal/file.d +++ /dev/null @@ -1,25 +0,0 @@ -// Written in the D programming language - -module undead.internal.file; - -// Copied from std.file. undead doesn't have access to it, but some modules -// in undead used std.file.deleteme when they were in Phobos, so this gives -// them access to a version of it. -public @property string deleteme() @safe -{ - import std.conv : to; - import std.file : tempDir; - import std.path : buildPath; - import std.process : thisProcessID; - static _deleteme = "deleteme.dmd.unittest.pid"; - static _first = true; - - if(_first) - { - _deleteme = buildPath(tempDir(), _deleteme) ~ to!string(thisProcessID); - _first = false; - } - - return _deleteme; -} - diff --git a/src/undead/stream.d b/src/undead/stream.d deleted file mode 100644 index dc81b7f..0000000 --- a/src/undead/stream.d +++ /dev/null @@ -1,3076 +0,0 @@ -// Written in the D programming language - -/** - * $(RED Deprecated: This module is considered out-dated and not up to Phobos' - * current standards.) - * - * Source: $(PHOBOSSRC std/_stream.d) - * Macros: - * WIKI = Phobos/StdStream - */ - -/* - * Copyright (c) 2001-2005 - * Pavel "EvilOne" Minayev - * with buffering and endian support added by Ben Hinkle - * with buffered readLine performance improvements by Dave Fladebo - * with opApply inspired by (and mostly copied from) Regan Heath - * with bug fixes and MemoryStream/SliceStream enhancements by Derick Eddington - * - * Permission to use, copy, modify, distribute and sell this software - * and its documentation for any purpose is hereby granted without fee, - * provided that the above copyright notice appear in all copies and - * that both that copyright notice and this permission notice appear - * in supporting documentation. Author makes no representations about - * the suitability of this software for any purpose. It is provided - * "as is" without express or implied warranty. - */ -module undead.stream; - -import std.internal.cstring; - -/* Class structure: - * InputStream interface for reading - * OutputStream interface for writing - * Stream abstract base of stream implementations - * File an OS file stream - * FilterStream a base-class for wrappers around another stream - * BufferedStream a buffered stream wrapping another stream - * BufferedFile a buffered File - * EndianStream a wrapper stream for swapping byte order and BOMs - * SliceStream a portion of another stream - * MemoryStream a stream entirely stored in main memory - * TArrayStream a stream wrapping an array-like buffer - */ - -/// A base class for stream exceptions. -class StreamException: Exception { - /// Construct a StreamException with given error message. - this(string msg) { super(msg); } -} - -/// Thrown when unable to read data from Stream. -class ReadException: StreamException { - /// Construct a ReadException with given error message. - this(string msg) { super(msg); } -} - -/// Thrown when unable to write data to Stream. -class WriteException: StreamException { - /// Construct a WriteException with given error message. - this(string msg) { super(msg); } -} - -/// Thrown when unable to move Stream pointer. -class SeekException: StreamException { - /// Construct a SeekException with given error message. - this(string msg) { super(msg); } -} - -// seek whence... -enum SeekPos { - Set, - Current, - End -} - -private { - import std.conv; - import std.algorithm; - import std.ascii; - //import std.format; - import std.system; // for Endian enumeration - import std.utf; - import core.bitop; // for bswap - import core.vararg; - import std.file; - import undead.internal.file; - import undead.doformat; -} - -/// InputStream is the interface for readable streams. - -interface InputStream { - - /*** - * Read exactly size bytes into the buffer. - * - * Throws a ReadException if it is not correct. - */ - void readExact(void* buffer, size_t size); - - /*** - * Read a block of data big enough to fill the given array buffer. - * - * Returns: the actual number of bytes read. Unfilled bytes are not modified. - */ - size_t read(ubyte[] buffer); - - /*** - * Read a basic type or counted string. - * - * Throw a ReadException if it could not be read. - * Outside of byte, ubyte, and char, the format is - * implementation-specific and should not be used except as opposite actions - * to write. - */ - void read(out byte x); - void read(out ubyte x); /// ditto - void read(out short x); /// ditto - void read(out ushort x); /// ditto - void read(out int x); /// ditto - void read(out uint x); /// ditto - void read(out long x); /// ditto - void read(out ulong x); /// ditto - void read(out float x); /// ditto - void read(out double x); /// ditto - void read(out real x); /// ditto - void read(out ifloat x); /// ditto - void read(out idouble x); /// ditto - void read(out ireal x); /// ditto - void read(out cfloat x); /// ditto - void read(out cdouble x); /// ditto - void read(out creal x); /// ditto - void read(out char x); /// ditto - void read(out wchar x); /// ditto - void read(out dchar x); /// ditto - - // reads a string, written earlier by write() - void read(out char[] s); /// ditto - - // reads a Unicode string, written earlier by write() - void read(out wchar[] s); /// ditto - - /*** - * Read a line that is terminated with some combination of carriage return and - * line feed or end-of-file. - * - * The terminators are not included. The wchar version - * is identical. The optional buffer parameter is filled (reallocating - * it if necessary) and a slice of the result is returned. - */ - char[] readLine(); - char[] readLine(char[] result); /// ditto - wchar[] readLineW(); /// ditto - wchar[] readLineW(wchar[] result); /// ditto - - /*** - * Overload foreach statements to read the stream line by line and call the - * supplied delegate with each line or with each line with line number. - * - * The string passed in line may be reused between calls to the delegate. - * Line numbering starts at 1. - * Breaking out of the foreach will leave the stream - * position at the beginning of the next line to be read. - * For example, to echo a file line-by-line with line numbers run: - * ------------------------------------ - * Stream file = new BufferedFile("sample.txt"); - * foreach(ulong n, char[] line; file) - * { - * writefln("line %d: %s", n, line); - * } - * file.close(); - * ------------------------------------ - */ - - // iterate through the stream line-by-line - int opApply(scope int delegate(ref char[] line) dg); - int opApply(scope int delegate(ref ulong n, ref char[] line) dg); /// ditto - int opApply(scope int delegate(ref wchar[] line) dg); /// ditto - int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg); /// ditto - - /// Read a string of the given length, - /// throwing ReadException if there was a problem. - char[] readString(size_t length); - - /*** - * Read a string of the given length, throwing ReadException if there was a - * problem. - * - * The file format is implementation-specific and should not be used - * except as opposite actions to <b>write</b>. - */ - - wchar[] readStringW(size_t length); - - - /*** - * Read and return the next character in the stream. - * - * This is the only method that will handle ungetc properly. - * getcw's format is implementation-specific. - * If EOF is reached then getc returns char.init and getcw returns wchar.init. - */ - - char getc(); - wchar getcw(); /// ditto - - /*** - * Push a character back onto the stream. - * - * They will be returned in first-in last-out order from getc/getcw. - * Only has effect on further calls to getc() and getcw(). - */ - char ungetc(char c); - wchar ungetcw(wchar c); /// ditto - - /*** - * Scan a string from the input using a similar form to C's scanf - * and <a href="std_format.html">std.format</a>. - * - * An argument of type string is interpreted as a format string. - * All other arguments must be pointer types. - * If a format string is not present a default will be supplied computed from - * the base type of the pointer type. An argument of type string* is filled - * (possibly with appending characters) and a slice of the result is assigned - * back into the argument. For example the following readf statements - * are equivalent: - * -------------------------- - * int x; - * double y; - * string s; - * file.readf(&x, " hello ", &y, &s); - * file.readf("%d hello %f %s", &x, &y, &s); - * file.readf("%d hello %f", &x, &y, "%s", &s); - * -------------------------- - */ - int vreadf(TypeInfo[] arguments, va_list args); - int readf(...); /// ditto - - /// Retrieve the number of bytes available for immediate reading. - @property size_t available(); - - /*** - * Return whether the current file position is the same as the end of the - * file. - * - * This does not require actually reading past the end, as with stdio. For - * non-seekable streams this might only return true after attempting to read - * past the end. - */ - - @property bool eof(); - - @property bool isOpen(); /// Return true if the stream is currently open. -} - -/// Interface for writable streams. -interface OutputStream { - - /*** - * Write exactly size bytes from buffer, or throw a WriteException if that - * could not be done. - */ - void writeExact(const void* buffer, size_t size); - - /*** - * Write as much of the buffer as possible, - * returning the number of bytes written. - */ - size_t write(const(ubyte)[] buffer); - - /*** - * Write a basic type. - * - * Outside of byte, ubyte, and char, the format is implementation-specific - * and should only be used in conjunction with read. - * Throw WriteException on error. - */ - void write(byte x); - void write(ubyte x); /// ditto - void write(short x); /// ditto - void write(ushort x); /// ditto - void write(int x); /// ditto - void write(uint x); /// ditto - void write(long x); /// ditto - void write(ulong x); /// ditto - void write(float x); /// ditto - void write(double x); /// ditto - void write(real x); /// ditto - void write(ifloat x); /// ditto - void write(idouble x); /// ditto - void write(ireal x); /// ditto - void write(cfloat x); /// ditto - void write(cdouble x); /// ditto - void write(creal x); /// ditto - void write(char x); /// ditto - void write(wchar x); /// ditto - void write(dchar x); /// ditto - - /*** - * Writes a string, together with its length. - * - * The format is implementation-specific - * and should only be used in conjunction with read. - * Throw WriteException on error. - */ - void write(const(char)[] s); - void write(const(wchar)[] s); /// ditto - - /*** - * Write a line of text, - * appending the line with an operating-system-specific line ending. - * - * Throws WriteException on error. - */ - void writeLine(const(char)[] s); - - /*** - * Write a line of text, - * appending the line with an operating-system-specific line ending. - * - * The format is implementation-specific. - * Throws WriteException on error. - */ - void writeLineW(const(wchar)[] s); - - /*** - * Write a string of text. - * - * Throws WriteException if it could not be fully written. - */ - void writeString(const(char)[] s); - - /*** - * Write a string of text. - * - * The format is implementation-specific. - * Throws WriteException if it could not be fully written. - */ - void writeStringW(const(wchar)[] s); - - /*** - * Print a formatted string into the stream using printf-style syntax, - * returning the number of bytes written. - */ - size_t vprintf(const(char)[] format, va_list args); - size_t printf(const(char)[] format, ...); /// ditto - - /*** - * Print a formatted string into the stream using writef-style syntax. - * References: <a href="std_format.html">std.format</a>. - * Returns: self to chain with other stream commands like flush. - */ - OutputStream writef(...); - OutputStream writefln(...); /// ditto - OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline = false); /// ditto - - void flush(); /// Flush pending output if appropriate. - void close(); /// Close the stream, flushing output if appropriate. - @property bool isOpen(); /// Return true if the stream is currently open. -} - - -/*** - * Stream is the base abstract class from which the other stream classes derive. - * - * Stream's byte order is the format native to the computer. - * - * Reading: - * These methods require that the readable flag be set. - * Problems with reading result in a ReadException being thrown. - * Stream implements the InputStream interface in addition to the - * readBlock method. - * - * Writing: - * These methods require that the writeable flag be set. Problems with writing - * result in a WriteException being thrown. Stream implements the OutputStream - * interface in addition to the following methods: - * writeBlock - * copyFrom - * copyFrom - * - * Seeking: - * These methods require that the seekable flag be set. - * Problems with seeking result in a SeekException being thrown. - * seek, seekSet, seekCur, seekEnd, position, size, toString, toHash - */ - -// not really abstract, but its instances will do nothing useful -class Stream : InputStream, OutputStream { - private import std.string, std.digest.crc, core.stdc.stdlib, core.stdc.stdio; - - // stream abilities - bool readable = false; /// Indicates whether this stream can be read from. - bool writeable = false; /// Indicates whether this stream can be written to. - bool seekable = false; /// Indicates whether this stream can be seeked within. - protected bool isopen = true; /// Indicates whether this stream is open. - - protected bool readEOF = false; /** Indicates whether this stream is at eof - * after the last read attempt. - */ - - protected bool prevCr = false; /** For a non-seekable stream indicates that - * the last readLine or readLineW ended on a - * '\r' character. - */ - - this() {} - - /*** - * Read up to size bytes into the buffer and return the number of bytes - * actually read. A return value of 0 indicates end-of-file. - */ - abstract size_t readBlock(void* buffer, size_t size); - - // reads block of data of specified size, - // throws ReadException on error - void readExact(void* buffer, size_t size) { - for(;;) { - if (!size) return; - size_t readsize = readBlock(buffer, size); // return 0 on eof - if (readsize == 0) break; - buffer += readsize; - size -= readsize; - } - if (size != 0) - throw new ReadException("not enough data in stream"); - } - - // reads block of data big enough to fill the given - // array, returns actual number of bytes read - size_t read(ubyte[] buffer) { - return readBlock(buffer.ptr, buffer.length); - } - - // read a single value of desired type, - // throw ReadException on error - void read(out byte x) { readExact(&x, x.sizeof); } - void read(out ubyte x) { readExact(&x, x.sizeof); } - void read(out short x) { readExact(&x, x.sizeof); } - void read(out ushort x) { readExact(&x, x.sizeof); } - void read(out int x) { readExact(&x, x.sizeof); } - void read(out uint x) { readExact(&x, x.sizeof); } - void read(out long x) { readExact(&x, x.sizeof); } - void read(out ulong x) { readExact(&x, x.sizeof); } - void read(out float x) { readExact(&x, x.sizeof); } - void read(out double x) { readExact(&x, x.sizeof); } - void read(out real x) { readExact(&x, x.sizeof); } - void read(out ifloat x) { readExact(&x, x.sizeof); } - void read(out idouble x) { readExact(&x, x.sizeof); } - void read(out ireal x) { readExact(&x, x.sizeof); } - void read(out cfloat x) { readExact(&x, x.sizeof); } - void read(out cdouble x) { readExact(&x, x.sizeof); } - void read(out creal x) { readExact(&x, x.sizeof); } - void read(out char x) { readExact(&x, x.sizeof); } - void read(out wchar x) { readExact(&x, x.sizeof); } - void read(out dchar x) { readExact(&x, x.sizeof); } - - // reads a string, written earlier by write() - void read(out char[] s) { - size_t len; - read(len); - s = readString(len); - } - - // reads a Unicode string, written earlier by write() - void read(out wchar[] s) { - size_t len; - read(len); - s = readStringW(len); - } - - // reads a line, terminated by either CR, LF, CR/LF, or EOF - char[] readLine() { - return readLine(null); - } - - // reads a line, terminated by either CR, LF, CR/LF, or EOF - // reusing the memory in buffer if result will fit and otherwise - // allocates a new string - char[] readLine(char[] result) { - size_t strlen = 0; - char ch = getc(); - while (readable) { - switch (ch) { - case '\r': - if (seekable) { - ch = getc(); - if (ch != '\n') - ungetc(ch); - } else { - prevCr = true; - } - goto case; - case '\n': - case char.init: - result.length = strlen; - return result; - - default: - if (strlen < result.length) { - result[strlen] = ch; - } else { - result ~= ch; - } - strlen++; - } - ch = getc(); - } - result.length = strlen; - return result; - } - - // reads a Unicode line, terminated by either CR, LF, CR/LF, - // or EOF; pretty much the same as the above, working with - // wchars rather than chars - wchar[] readLineW() { - return readLineW(null); - } - - // reads a Unicode line, terminated by either CR, LF, CR/LF, - // or EOF; - // fills supplied buffer if line fits and otherwise allocates a new string. - wchar[] readLineW(wchar[] result) { - size_t strlen = 0; - wchar c = getcw(); - while (readable) { - switch (c) { - case '\r': - if (seekable) { - c = getcw(); - if (c != '\n') - ungetcw(c); - } else { - prevCr = true; - } - goto case; - case '\n': - case wchar.init: - result.length = strlen; - return result; - - default: - if (strlen < result.length) { - result[strlen] = c; - } else { - result ~= c; - } - strlen++; - } - c = getcw(); - } - result.length = strlen; - return result; - } - - // iterate through the stream line-by-line - due to Regan Heath - int opApply(scope int delegate(ref char[] line) dg) { - int res = 0; - char[128] buf; - while (!eof) { - char[] line = readLine(buf); - res = dg(line); - if (res) break; - } - return res; - } - - // iterate through the stream line-by-line with line count and string - int opApply(scope int delegate(ref ulong n, ref char[] line) dg) { - int res = 0; - ulong n = 1; - char[128] buf; - while (!eof) { - auto line = readLine(buf); - res = dg(n,line); - if (res) break; - n++; - } - return res; - } - - // iterate through the stream line-by-line with wchar[] - int opApply(scope int delegate(ref wchar[] line) dg) { - int res = 0; - wchar[128] buf; - while (!eof) { - auto line = readLineW(buf); - res = dg(line); - if (res) break; - } - return res; - } - - // iterate through the stream line-by-line with line count and wchar[] - int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg) { - int res = 0; - ulong n = 1; - wchar[128] buf; - while (!eof) { - auto line = readLineW(buf); - res = dg(n,line); - if (res) break; - n++; - } - return res; - } - - // reads a string of given length, throws - // ReadException on error - char[] readString(size_t length) { - char[] result = new char[length]; - readExact(result.ptr, length); - return result; - } - - // reads a Unicode string of given length, throws - // ReadException on error - wchar[] readStringW(size_t length) { - auto result = new wchar[length]; - readExact(result.ptr, result.length * wchar.sizeof); - return result; - } - - // unget buffer - private wchar[] unget; - final bool ungetAvailable() { return unget.length > 1; } - - // reads and returns next character from the stream, - // handles characters pushed back by ungetc() - // returns char.init on eof. - char getc() { - char c; - if (prevCr) { - prevCr = false; - c = getc(); - if (c != '\n') - return c; - } - if (unget.length > 1) { - c = cast(char)unget[unget.length - 1]; - unget.length = unget.length - 1; - } else { - readBlock(&c,1); - } - return c; - } - - // reads and returns next Unicode character from the - // stream, handles characters pushed back by ungetc() - // returns wchar.init on eof. - wchar getcw() { - wchar c; - if (prevCr) { - prevCr = false; - c = getcw(); - if (c != '\n') - return c; - } - if (unget.length > 1) { - c = unget[unget.length - 1]; - unget.length = unget.length - 1; - } else { - void* buf = &c; - size_t n = readBlock(buf,2); - if (n == 1 && readBlock(buf+1,1) == 0) - throw new ReadException("not enough data in stream"); - } - return c; - } - - // pushes back character c into the stream; only has - // effect on further calls to getc() and getcw() - char ungetc(char c) { - if (c == c.init) return c; - // first byte is a dummy so that we never set length to 0 - if (unget.length == 0) - unget.length = 1; - unget ~= c; - return c; - } - - // pushes back Unicode character c into the stream; only - // has effect on further calls to getc() and getcw() - wchar ungetcw(wchar c) { - if (c == c.init) return c; - // first byte is a dummy so that we never set length to 0 - if (unget.length == 0) - unget.length = 1; - unget ~= c; - return c; - } - - int vreadf(TypeInfo[] arguments, va_list args) { - string fmt; - int j = 0; - int count = 0, i = 0; - char c; - bool firstCharacter = true; - while ((j < arguments.length || i < fmt.length) && !eof) { - if(firstCharacter) { - c = getc(); - firstCharacter = false; - } - if (fmt.length == 0 || i == fmt.length) { - i = 0; - if (arguments[j] is typeid(string) || arguments[j] is typeid(char[]) - || arguments[j] is typeid(const(char)[])) { - fmt = va_arg!(string)(args); - j++; - continue; - } else if (arguments[j] is typeid(int*) || - arguments[j] is typeid(byte*) || - arguments[j] is typeid(short*) || - arguments[j] is typeid(long*)) { - fmt = "%d"; - } else if (arguments[j] is typeid(uint*) || - arguments[j] is typeid(ubyte*) || - arguments[j] is typeid(ushort*) || - arguments[j] is typeid(ulong*)) { - fmt = "%d"; - } else if (arguments[j] is typeid(float*) || - arguments[j] is typeid(double*) || - arguments[j] is typeid(real*)) { - fmt = "%f"; - } else if (arguments[j] is typeid(char[]*) || - arguments[j] is typeid(wchar[]*) || - arguments[j] is typeid(dchar[]*)) { - fmt = "%s"; - } else if (arguments[j] is typeid(char*)) { - fmt = "%c"; - } - } - if (fmt[i] == '%') { // a field - i++; - bool suppress = false; - if (fmt[i] == '*') { // suppress assignment - suppress = true; - i++; - } - // read field width - int width = 0; - while (isDigit(fmt[i])) { - width = width * 10 + (fmt[i] - '0'); - i++; - } - if (width == 0) - width = -1; - // skip any modifier if present - if (fmt[i] == 'h' || fmt[i] == 'l' || fmt[i] == 'L') - i++; - // check the typechar and act accordingly - switch (fmt[i]) { - case 'd': // decimal/hexadecimal/octal integer - case 'D': - case 'u': - case 'U': - case 'o': - case 'O': - case 'x': - case 'X': - case 'i': - case 'I': - { - while (isWhite(c)) { - c = getc(); - count++; - } - bool neg = false; - if (c == '-') { - neg = true; - c = getc(); - count++; - } else if (c == '+') { - c = getc(); - count++; - } - char ifmt = cast(char)(fmt[i] | 0x20); - if (ifmt == 'i') { // undetermined base - if (c == '0') { // octal or hex - c = getc(); - count++; - if (c == 'x' || c == 'X') { // hex - ifmt = 'x'; - c = getc(); - count++; - } else { // octal - ifmt = 'o'; - } - } - else // decimal - ifmt = 'd'; - } - long n = 0; - switch (ifmt) - { - case 'd': // decimal - case 'u': { - while (isDigit(c) && width) { - n = n * 10 + (c - '0'); - width--; - c = getc(); - count++; - } - } break; - - case 'o': { // octal - while (isOctalDigit(c) && width) { - n = n * 8 + (c - '0'); - width--; - c = getc(); - count++; - } - } break; - - case 'x': { // hexadecimal - while (isHexDigit(c) && width) { - n *= 0x10; - if (isDigit(c)) - n += c - '0'; - else - n += 0xA + (c | 0x20) - 'a'; - width--; - c = getc(); - count++; - } - } break; - - default: - assert(0); - } - if (neg) - n = -n; - if (arguments[j] is typeid(int*)) { - int* p = va_arg!(int*)(args); - *p = cast(int)n; - } else if (arguments[j] is typeid(short*)) { - short* p = va_arg!(short*)(args); - *p = cast(short)n; - } else if (arguments[j] is typeid(byte*)) { - byte* p = va_arg!(byte*)(args); - *p = cast(byte)n; - } else if (arguments[j] is typeid(long*)) { - long* p = va_arg!(long*)(args); - *p = n; - } else if (arguments[j] is typeid(uint*)) { - uint* p = va_arg!(uint*)(args); - *p = cast(uint)n; - } else if (arguments[j] is typeid(ushort*)) { - ushort* p = va_arg!(ushort*)(args); - *p = cast(ushort)n; - } else if (arguments[j] is typeid(ubyte*)) { - ubyte* p = va_arg!(ubyte*)(args); - *p = cast(ubyte)n; - } else if (arguments[j] is typeid(ulong*)) { - ulong* p = va_arg!(ulong*)(args); - *p = cast(ulong)n; - } - j++; - i++; - } break; - - case 'f': // float - case 'F': - case 'e': - case 'E': - case 'g': - case 'G': - { - while (isWhite(c)) { - c = getc(); - count++; - } - bool neg = false; - if (c == '-') { - neg = true; - c = getc(); - count++; - } else if (c == '+') { - c = getc(); - count++; - } - real r = 0; - while (isDigit(c) && width) { - r = r * 10 + (c - '0'); - width--; - c = getc(); - count++; - } - if (width && c == '.') { - width--; - c = getc(); - count++; - double frac = 1; - while (isDigit(c) && width) { - r = r * 10 + (c - '0'); - frac *= 10; - width--; - c = getc(); - count++; - } - r /= frac; - } - if (width && (c == 'e' || c == 'E')) { - width--; - c = getc(); - count++; - if (width) { - bool expneg = false; - if (c == '-') { - expneg = true; - width--; - c = getc(); - count++; - } else if (c == '+') { - width--; - c = getc(); - count++; - } - real exp = 0; - while (isDigit(c) && width) { - exp = exp * 10 + (c - '0'); - width--; - c = getc(); - count++; - } - if (expneg) { - while (exp--) - r /= 10; - } else { - while (exp--) - r *= 10; - } - } - } - if(width && (c == 'n' || c == 'N')) { - width--; - c = getc(); - count++; - if(width && (c == 'a' || c == 'A')) { - width--; - c = getc(); - count++; - if(width && (c == 'n' || c == 'N')) { - width--; - c = getc(); - count++; - r = real.nan; - } - } - } - if(width && (c == 'i' || c == 'I')) { - width--; - c = getc(); - count++; - if(width && (c == 'n' || c == 'N')) { - width--; - c = getc(); - count++; - if(width && (c == 'f' || c == 'F')) { - width--; - c = getc(); - count++; - r = real.infinity; - } - } - } - if (neg) - r = -r; - if (arguments[j] is typeid(float*)) { - float* p = va_arg!(float*)(args); - *p = r; - } else if (arguments[j] is typeid(double*)) { - double* p = va_arg!(double*)(args); - *p = r; - } else if (arguments[j] is typeid(real*)) { - real* p = va_arg!(real*)(args); - *p = r; - } - j++; - i++; - } break; - - case 's': { // string - while (isWhite(c)) { - c = getc(); - count++; - } - char[] s; - char[]* p; - size_t strlen; - if (arguments[j] is typeid(char[]*)) { - p = va_arg!(char[]*)(args); - s = *p; - } - while (!isWhite(c) && c != char.init) { - if (strlen < s.length) { - s[strlen] = c; - } else { - s ~= c; - } - strlen++; - c = getc(); - count++; - } - s = s[0 .. strlen]; - if (arguments[j] is typeid(char[]*)) { - *p = s; - } else if (arguments[j] is typeid(char*)) { - s ~= 0; - auto q = va_arg!(char*)(args); - q[0 .. s.length] = s[]; - } else if (arguments[j] is typeid(wchar[]*)) { - auto q = va_arg!(const(wchar)[]*)(args); - *q = toUTF16(s); - } else if (arguments[j] is typeid(dchar[]*)) { - auto q = va_arg!(const(dchar)[]*)(args); - *q = toUTF32(s); - } - j++; - i++; - } break; - - case 'c': { // character(s) - char* s = va_arg!(char*)(args); - if (width < 0) - width = 1; - else - while (isWhite(c)) { - c = getc(); - count++; - } - while (width-- && !eof) { - *(s++) = c; - c = getc(); - count++; - } - j++; - i++; - } break; - - case 'n': { // number of chars read so far - int* p = va_arg!(int*)(args); - *p = count; - j++; - i++; - } break; - - default: // read character as is - goto nws; - } - } else if (isWhite(fmt[i])) { // skip whitespace - while (isWhite(c)) - c = getc(); - i++; - } else { // read character as is - nws: - if (fmt[i] != c) - break; - c = getc(); - i++; - } - } - ungetc(c); - return count; - } - - int readf(...) { - return vreadf(_arguments, _argptr); - } - - // returns estimated number of bytes available for immediate reading - @property size_t available() { return 0; } - - /*** - * Write up to size bytes from buffer in the stream, returning the actual - * number of bytes that were written. - */ - abstract size_t writeBlock(const void* buffer, size_t size); - - // writes block of data of specified size, - // throws WriteException on error - void writeExact(const void* buffer, size_t size) { - const(void)* p = buffer; - for(;;) { - if (!size) return; - size_t writesize = writeBlock(p, size); - if (writesize == 0) break; - p += writesize; - size -= writesize; - } - if (size != 0) - throw new WriteException("unable to write to stream"); - } - - // writes the given array of bytes, returns - // actual number of bytes written - size_t write(const(ubyte)[] buffer) { - return writeBlock(buffer.ptr, buffer.length); - } - - // write a single value of desired type, - // throw WriteException on error - void write(byte x) { writeExact(&x, x.sizeof); } - void write(ubyte x) { writeExact(&x, x.sizeof); } - void write(short x) { writeExact(&x, x.sizeof); } - void write(ushort x) { writeExact(&x, x.sizeof); } - void write(int x) { writeExact(&x, x.sizeof); } - void write(uint x) { writeExact(&x, x.sizeof); } - void write(long x) { writeExact(&x, x.sizeof); } - void write(ulong x) { writeExact(&x, x.sizeof); } - void write(float x) { writeExact(&x, x.sizeof); } - void write(double x) { writeExact(&x, x.sizeof); } - void write(real x) { writeExact(&x, x.sizeof); } - void write(ifloat x) { writeExact(&x, x.sizeof); } - void write(idouble x) { writeExact(&x, x.sizeof); } - void write(ireal x) { writeExact(&x, x.sizeof); } - void write(cfloat x) { writeExact(&x, x.sizeof); } - void write(cdouble x) { writeExact(&x, x.sizeof); } - void write(creal x) { writeExact(&x, x.sizeof); } - void write(char x) { writeExact(&x, x.sizeof); } - void write(wchar x) { writeExact(&x, x.sizeof); } - void write(dchar x) { writeExact(&x, x.sizeof); } - - // writes a string, together with its length - void write(const(char)[] s) { - write(s.length); - writeString(s); - } - - // writes a Unicode string, together with its length - void write(const(wchar)[] s) { - write(s.length); - writeStringW(s); - } - - // writes a line, throws WriteException on error - void writeLine(const(char)[] s) { - writeString(s); - version (Windows) - writeString("\r\n"); - else version (Mac) - writeString("\r"); - else - writeString("\n"); - } - - // writes a Unicode line, throws WriteException on error - void writeLineW(const(wchar)[] s) { - writeStringW(s); - version (Windows) - writeStringW("\r\n"); - else version (Mac) - writeStringW("\r"); - else - writeStringW("\n"); - } - - // writes a string, throws WriteException on error - void writeString(const(char)[] s) { - writeExact(s.ptr, s.length); - } - - // writes a Unicode string, throws WriteException on error - void writeStringW(const(wchar)[] s) { - writeExact(s.ptr, s.length * wchar.sizeof); - } - - // writes data to stream using vprintf() syntax, - // returns number of bytes written - size_t vprintf(const(char)[] format, va_list args) { - // shamelessly stolen from OutBuffer, - // by Walter's permission - char[1024] buffer; - char* p = buffer.ptr; - // Can't use `tempCString()` here as it will result in compilation error: - // "cannot mix core.std.stdlib.alloca() and exception handling". - auto f = toStringz(format); - size_t psize = buffer.length; - size_t count; - while (true) { - version (Windows) { - count = vsnprintf(p, psize, f, args); - if (count != -1) - break; - psize *= 2; - p = cast(char*) alloca(psize); - } else version (Posix) { - count = vsnprintf(p, psize, f, args); - if (count == -1) - psize *= 2; - else if (count >= psize) - psize = count + 1; - else - break; - p = cast(char*) alloca(psize); - } else - throw new Exception("unsupported platform"); - } - writeString(p[0 .. count]); - return count; - } - - // writes data to stream using printf() syntax, - // returns number of bytes written - size_t printf(const(char)[] format, ...) { - va_list ap; - va_start(ap, format); - auto result = vprintf(format, ap); - va_end(ap); - return result; - } - - private void doFormatCallback(dchar c) { - char[4] buf; - auto b = std.utf.toUTF8(buf, c); - writeString(b); - } - - // writes data to stream using writef() syntax, - OutputStream writef(...) { - return writefx(_arguments,_argptr,0); - } - - // writes data with trailing newline - OutputStream writefln(...) { - return writefx(_arguments,_argptr,1); - } - - // writes data with optional trailing newline - OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline=false) { - doFormat(&doFormatCallback,arguments,argptr); - if (newline) - writeLine(""); - return this; - } - - /*** - * Copies all data from s into this stream. - * This may throw ReadException or WriteException on failure. - * This restores the file position of s so that it is unchanged. - */ - void copyFrom(Stream s) { - if (seekable) { - ulong pos = s.position; - s.position = 0; - copyFrom(s, s.size); - s.position = pos; - } else { - ubyte[128] buf; - while (!s.eof) { - size_t m = s.readBlock(buf.ptr, buf.length); - writeExact(buf.ptr, m); - } - } - } - - /*** - * Copy a specified number of bytes from the given stream into this one. - * This may throw ReadException or WriteException on failure. - * Unlike the previous form, this doesn't restore the file position of s. - */ - void copyFrom(Stream s, ulong count) { - ubyte[128] buf; - while (count > 0) { - size_t n = cast(size_t)(count<buf.length ? count : buf.length); - s.readExact(buf.ptr, n); - writeExact(buf.ptr, n); - count -= n; - } - } - - /*** - * Change the current position of the stream. whence is either SeekPos.Set, in - which case the offset is an absolute index from the beginning of the stream, - SeekPos.Current, in which case the offset is a delta from the current - position, or SeekPos.End, in which case the offset is a delta from the end of - the stream (negative or zero offsets only make sense in that case). This - returns the new file position. - */ - abstract ulong seek(long offset, SeekPos whence); - - /*** - * Aliases for their normal seek counterparts. - */ - ulong seekSet(long offset) { return seek (offset, SeekPos.Set); } - ulong seekCur(long offset) { return seek (offset, SeekPos.Current); } /// ditto - ulong seekEnd(long offset) { return seek (offset, SeekPos.End); } /// ditto - - /*** - * Sets file position. Equivalent to calling seek(pos, SeekPos.Set). - */ - @property void position(ulong pos) { seek(cast(long)pos, SeekPos.Set); } - - /*** - * Returns current file position. Equivalent to seek(0, SeekPos.Current). - */ - @property ulong position() { return seek(0, SeekPos.Current); } - - /*** - * Retrieve the size of the stream in bytes. - * The stream must be seekable or a SeekException is thrown. - */ - @property ulong size() { - assertSeekable(); - ulong pos = position, result = seek(0, SeekPos.End); - position = pos; - return result; - } - - // returns true if end of stream is reached, false otherwise - @property bool eof() { - // for unseekable streams we only know the end when we read it - if (readEOF && !ungetAvailable()) - return true; - else if (seekable) - return position == size; - else - return false; - } - - // returns true if the stream is open - @property bool isOpen() { return isopen; } - - // flush the buffer if writeable - void flush() { - if (unget.length > 1) - unget.length = 1; // keep at least 1 so that data ptr stays - } - - // close the stream somehow; the default just flushes the buffer - void close() { - if (isopen) - flush(); - readEOF = prevCr = isopen = readable = writeable = seekable = false; - } - - /*** - * Read the entire stream and return it as a string. - * If the stream is not seekable the contents from the current position to eof - * is read and returned. - */ - override string toString() { - if (!readable) - return super.toString(); - try - { - size_t pos; - size_t rdlen; - size_t blockSize; - char[] result; - if (seekable) { - ulong orig_pos = position; - scope(exit) position = orig_pos; - position = 0; - blockSize = cast(size_t)size; - result = new char[blockSize]; - while (blockSize > 0) { - rdlen = readBlock(&result[pos], blockSize); - pos += rdlen; - blockSize -= rdlen; - } - } else { - blockSize = 4096; - result = new char[blockSize]; - while ((rdlen = readBlock(&result[pos], blockSize)) > 0) { - pos += rdlen; - blockSize += rdlen; - result.length = result.length + blockSize; - } - } - return cast(string) result[0 .. pos]; - } - catch (Throwable) - { - return super.toString(); - } - } - - /*** - * Get a hash of the stream by reading each byte and using it in a CRC-32 - * checksum. - */ - override size_t toHash() @trusted { - if (!readable || !seekable) - return super.toHash(); - try - { - ulong pos = position; - scope(exit) position = pos; - CRC32 crc; - crc.start(); - position = 0; - ulong len = size; - for (ulong i = 0; i < len; i++) - { - ubyte c; - read(c); - crc.put(c); - } - - union resUnion - { - size_t hash; - ubyte[4] crcVal; - } - resUnion res; - res.crcVal = crc.finish(); - return res.hash; - } - catch (Throwable) - { - return super.toHash(); - } - } - - // helper for checking that the stream is readable - final protected void assertReadable() { - if (!readable) - throw new ReadException("Stream is not readable"); - } - // helper for checking that the stream is writeable - final protected void assertWriteable() { - if (!writeable) - throw new WriteException("Stream is not writeable"); - } - // helper for checking that the stream is seekable - final protected void assertSeekable() { - if (!seekable) - throw new SeekException("Stream is not seekable"); - } - /+ - unittest { // unit test for Issue 3363 - import std.stdio; - immutable fileName = undead.internal.file.deleteme ~ "-issue3363.txt"; - auto w = File(fileName, "w"); - scope (exit) remove(fileName.ptr); - w.write("one two three"); - w.close(); - auto r = File(fileName, "r"); - const(char)[] constChar; - string str; - char[] chars; - r.readf("%s %s %s", &constChar, &str, &chars); - assert (constChar == "one", constChar); - assert (str == "two", str); - assert (chars == "three", chars); - } - - unittest { //unit tests for Issue 1668 - void tryFloatRoundtrip(float x, string fmt = "", string pad = "") { - auto s = new MemoryStream(); - s.writef(fmt, x, pad); - s.position = 0; - - float f; - assert(s.readf(&f)); - assert(x == f || (x != x && f != f)); //either equal or both NaN - } - - tryFloatRoundtrip(1.0); - tryFloatRoundtrip(1.0, "%f"); - tryFloatRoundtrip(1.0, "", " "); - tryFloatRoundtrip(1.0, "%f", " "); - - tryFloatRoundtrip(3.14); - tryFloatRoundtrip(3.14, "%f"); - tryFloatRoundtrip(3.14, "", " "); - tryFloatRoundtrip(3.14, "%f", " "); - - float nan = float.nan; - tryFloatRoundtrip(nan); - tryFloatRoundtrip(nan, "%f"); - tryFloatRoundtrip(nan, "", " "); - tryFloatRoundtrip(nan, "%f", " "); - - float inf = 1.0/0.0; - tryFloatRoundtrip(inf); - tryFloatRoundtrip(inf, "%f"); - tryFloatRoundtrip(inf, "", " "); - tryFloatRoundtrip(inf, "%f", " "); - - tryFloatRoundtrip(-inf); - tryFloatRoundtrip(-inf,"%f"); - tryFloatRoundtrip(-inf, "", " "); - tryFloatRoundtrip(-inf, "%f", " "); - } - +/ -} - -/*** - * A base class for streams that wrap a source stream with additional - * functionality. - * - * The method implementations forward read/write/seek calls to the - * source stream. A FilterStream can change the position of the source stream - * arbitrarily and may not keep the source stream state in sync with the - * FilterStream, even upon flushing and closing the FilterStream. It is - * recommended to not make any assumptions about the state of the source position - * and read/write state after a FilterStream has acted upon it. Specifc subclasses - * of FilterStream should document how they modify the source stream and if any - * invariants hold true between the source and filter. - */ -class FilterStream : Stream { - private Stream s; // source stream - - /// Property indicating when this stream closes to close the source stream as - /// well. - /// Defaults to true. - bool nestClose = true; - - /// Construct a FilterStream for the given source. - this(Stream source) { - s = source; - resetSource(); - } - - // source getter/setter - - /*** - * Get the current source stream. - */ - final Stream source(){return s;} - - /*** - * Set the current source stream. - * - * Setting the source stream closes this stream before attaching the new - * source. Attaching an open stream reopens this stream and resets the stream - * state. - */ - void source(Stream s) { - close(); - this.s = s; - resetSource(); - } - - /*** - * Indicates the source stream changed state and that this stream should reset - * any readable, writeable, seekable, isopen and buffering flags. - */ - void resetSource() { - if (s !is null) { - readable = s.readable; - writeable = s.writeable; - seekable = s.seekable; - isopen = s.isOpen; - } else { - readable = writeable = seekable = false; - isopen = false; - } - readEOF = prevCr = false; - } - - // read from source - override size_t readBlock(void* buffer, size_t size) { - size_t res = s.readBlock(buffer,size); - readEOF = res == 0; - return res; - } - - // write to source - override size_t writeBlock(const void* buffer, size_t size) { - return s.writeBlock(buffer,size); - } - - // close stream - override void close() { - if (isopen) { - super.close(); - if (nestClose) - s.close(); - } - } - - // seek on source - override ulong seek(long offset, SeekPos whence) { - readEOF = false; - return s.seek(offset,whence); - } - - override @property size_t available() { return s.available; } - override void flush() { super.flush(); s.flush(); } -} - -/*** - * This subclass is for buffering a source stream. - * - * A buffered stream must be - * closed explicitly to ensure the final buffer content is written to the source - * stream. The source stream position is changed according to the block size so - * reading or writing to the BufferedStream may not change the source stream - * position by the same amount. - */ -class BufferedStream : FilterStream { - ubyte[] buffer; // buffer, if any - size_t bufferCurPos; // current position in buffer - size_t bufferLen; // amount of data in buffer - bool bufferDirty = false; - size_t bufferSourcePos; // position in buffer of source stream position - ulong streamPos; // absolute position in source stream - - /* Example of relationship between fields: - * - * s ...01234567890123456789012EOF - * buffer |-- --| - * bufferCurPos | - * bufferLen |-- --| - * bufferSourcePos | - * - */ - - invariant() { - assert(bufferSourcePos <= bufferLen); - assert(bufferCurPos <= bufferLen); - assert(bufferLen <= buffer.length); - } - - enum size_t DefaultBufferSize = 8192; - - /*** - * Create a buffered stream for the stream source with the buffer size - * bufferSize. - */ - this(Stream source, size_t bufferSize = DefaultBufferSize) { - super(source); - if (bufferSize) - buffer = new ubyte[bufferSize]; - } - - override protected void resetSource() { - super.resetSource(); - streamPos = 0; - bufferLen = bufferSourcePos = bufferCurPos = 0; - bufferDirty = false; - } - - // reads block of data of specified size using any buffered data - // returns actual number of bytes read - override size_t readBlock(void* result, size_t len) { - if (len == 0) return 0; - - assertReadable(); - - ubyte* outbuf = cast(ubyte*)result; - size_t readsize = 0; - - if (bufferCurPos + len < bufferLen) { - // buffer has all the data so copy it - outbuf[0 .. len] = buffer[bufferCurPos .. bufferCurPos+len]; - bufferCurPos += len; - readsize = len; - goto ExitRead; - } - - readsize = bufferLen - bufferCurPos; - if (readsize > 0) { - // buffer has some data so copy what is left - outbuf[0 .. readsize] = buffer[bufferCurPos .. bufferLen]; - outbuf += readsize; - bufferCurPos += readsize; - len -= readsize; - } - - flush(); - - if (len >= buffer.length) { - // buffer can't hold the data so fill output buffer directly - size_t siz = super.readBlock(outbuf, len); - readsize += siz; - streamPos += siz; - } else { - // read a new block into buffer - bufferLen = super.readBlock(buffer.ptr, buffer.length); - if (bufferLen < len) len = bufferLen; - outbuf[0 .. len] = buffer[0 .. len]; - bufferSourcePos = bufferLen; - streamPos += bufferLen; - bufferCurPos = len; - readsize += len; - } - - ExitRead: - return readsize; - } - - // write block of data of specified size - // returns actual number of bytes written - override size_t writeBlock(const void* result, size_t len) { - assertWriteable(); - - ubyte* buf = cast(ubyte*)result; - size_t writesize = 0; - - if (bufferLen == 0) { - // buffer is empty so fill it if possible - if ((len < buffer.length) && (readable)) { - // read in data if the buffer is currently empty - bufferLen = s.readBlock(buffer.ptr, buffer.length); - bufferSourcePos = bufferLen; - streamPos += bufferLen; - - } else if (len >= buffer.length) { - // buffer can't hold the data so write it directly and exit - writesize = s.writeBlock(buf,len); - streamPos += writesize; - goto ExitWrite; - } - } - - if (bufferCurPos + len <= buffer.length) { - // buffer has space for all the data so copy it and exit - buffer[bufferCurPos .. bufferCurPos+len] = buf[0 .. len]; - bufferCurPos += len; - bufferLen = bufferCurPos > bufferLen ? bufferCurPos : bufferLen; - writesize = len; - bufferDirty = true; - goto ExitWrite; - } - - writesize = buffer.length - bufferCurPos; - if (writesize > 0) { - // buffer can take some data - buffer[bufferCurPos .. buffer.length] = buf[0 .. writesize]; - bufferCurPos = bufferLen = buffer.length; - buf += writesize; - len -= writesize; - bufferDirty = true; - } - - assert(bufferCurPos == buffer.length); - assert(bufferLen == buffer.length); - - flush(); - - writesize += writeBlock(buf,len); - - ExitWrite: - return writesize; - } - - override ulong seek(long offset, SeekPos whence) { - assertSeekable(); - - if ((whence != SeekPos.Current) || - (offset + bufferCurPos < 0) || - (offset + bufferCurPos >= bufferLen)) { - flush(); - streamPos = s.seek(offset,whence); - } else { - bufferCurPos += offset; - } - readEOF = false; - return streamPos-bufferSourcePos+bufferCurPos; - } - - // Buffered readLine - Dave Fladebo - // reads a line, terminated by either CR, LF, CR/LF, or EOF - // reusing the memory in buffer if result will fit, otherwise - // will reallocate (using concatenation) - template TreadLine(T) { - T[] readLine(T[] inBuffer) - { - size_t lineSize = 0; - bool haveCR = false; - T c = '\0'; - size_t idx = 0; - ubyte* pc = cast(ubyte*)&c; - - L0: - for(;;) { - size_t start = bufferCurPos; - L1: - foreach(ubyte b; buffer[start .. bufferLen]) { - bufferCurPos++; - pc[idx] = b; - if(idx < T.sizeof - 1) { - idx++; - continue L1; - } else { - idx = 0; - } - if(c == '\n' || haveCR) { - if(haveCR && c != '\n') bufferCurPos--; - break L0; - } else { - if(c == '\r') { - haveCR = true; - } else { - if(lineSize < inBuffer.length) { - inBuffer[lineSize] = c; - } else { - inBuffer ~= c; - } - lineSize++; - } - } - } - flush(); - size_t res = super.readBlock(buffer.ptr, buffer.length); - if(!res) break L0; // EOF - bufferSourcePos = bufferLen = res; - streamPos += res; - } - return inBuffer[0 .. lineSize]; - } - } // template TreadLine(T) - - override char[] readLine(char[] inBuffer) { - if (ungetAvailable()) - return super.readLine(inBuffer); - else - return TreadLine!(char).readLine(inBuffer); - } - alias readLine = Stream.readLine; - - override wchar[] readLineW(wchar[] inBuffer) { - if (ungetAvailable()) - return super.readLineW(inBuffer); - else - return TreadLine!(wchar).readLine(inBuffer); - } - alias readLineW = Stream.readLineW; - - override void flush() - out { - assert(bufferCurPos == 0); - assert(bufferSourcePos == 0); - assert(bufferLen == 0); - } - body { - if (writeable && bufferDirty) { - if (bufferSourcePos != 0 && seekable) { - // move actual file pointer to front of buffer - streamPos = s.seek(-bufferSourcePos, SeekPos.Current); - } - // write buffer out - bufferSourcePos = s.writeBlock(buffer.ptr, bufferLen); - if (bufferSourcePos != bufferLen) { - throw new WriteException("Unable to write to stream"); - } - } - super.flush(); - long diff = cast(long)bufferCurPos-bufferSourcePos; - if (diff != 0 && seekable) { - // move actual file pointer to current position - streamPos = s.seek(diff, SeekPos.Current); - } - // reset buffer data to be empty - bufferSourcePos = bufferCurPos = bufferLen = 0; - bufferDirty = false; - } - - // returns true if end of stream is reached, false otherwise - override @property bool eof() { - if ((buffer.length == 0) || !readable) { - return super.eof; - } - // some simple tests to avoid flushing - if (ungetAvailable() || bufferCurPos != bufferLen) - return false; - if (bufferLen == buffer.length) - flush(); - size_t res = super.readBlock(&buffer[bufferLen],buffer.length-bufferLen); - bufferSourcePos += res; - bufferLen += res; - streamPos += res; - return readEOF; - } - - // returns size of stream - override @property ulong size() { - if (bufferDirty) flush(); - return s.size; - } - - // returns estimated number of bytes available for immediate reading - override @property size_t available() { - return bufferLen - bufferCurPos; - } -} - -/// An exception for File errors. -class StreamFileException: StreamException { - /// Construct a StreamFileException with given error message. - this(string msg) { super(msg); } -} - -/// An exception for errors during File.open. -class OpenException: StreamFileException { - /// Construct an OpenFileException with given error message. - this(string msg) { super(msg); } -} - -/// Specifies the $(LREF File) access mode used when opening the file. -enum FileMode { - In = 1, /// Opens the file for reading. - Out = 2, /// Opens the file for writing. - OutNew = 6, /// Opens the file for writing, creates a new file if it doesn't exist. - Append = 10 /// Opens the file for writing, appending new data to the end of the file. -} - -version (Windows) { - private import core.sys.windows.windows; - extern (Windows) { - void FlushFileBuffers(HANDLE hFile); - DWORD GetFileType(HANDLE hFile); - } -} -version (Posix) { - private import core.sys.posix.fcntl; - private import core.sys.posix.unistd; - alias HANDLE = int; -} - -/// This subclass is for unbuffered file system streams. -class File: Stream { - - version (Windows) { - private HANDLE hFile; - } - version (Posix) { - private HANDLE hFile = -1; - } - - this() { - super(); - version (Windows) { - hFile = null; - } - version (Posix) { - hFile = -1; - } - isopen = false; - } - - // opens existing handle; use with care! - this(HANDLE hFile, FileMode mode) { - super(); - this.hFile = hFile; - readable = cast(bool)(mode & FileMode.In); - writeable = cast(bool)(mode & FileMode.Out); - version(Windows) { - seekable = GetFileType(hFile) == 1; // FILE_TYPE_DISK - } else { - auto result = lseek(hFile, 0, 0); - seekable = (result != ~0); - } - } - - /*** - * Create the stream with no open file, an open file in read mode, or an open - * file with explicit file mode. - * mode, if given, is a combination of FileMode.In - * (indicating a file that can be read) and FileMode.Out (indicating a file - * that can be written). - * Opening a file for reading that doesn't exist will error. - * Opening a file for writing that doesn't exist will create the file. - * The FileMode.OutNew mode will open the file for writing and reset the - * length to zero. - * The FileMode.Append mode will open the file for writing and move the - * file position to the end of the file. - */ - this(string filename, FileMode mode = FileMode.In) - { - this(); - open(filename, mode); - } - - - /*** - * Open a file for the stream, in an identical manner to the constructors. - * If an error occurs an OpenException is thrown. - */ - void open(string filename, FileMode mode = FileMode.In) { - close(); - int access, share, createMode; - parseMode(mode, access, share, createMode); - seekable = true; - readable = cast(bool)(mode & FileMode.In); - writeable = cast(bool)(mode & FileMode.Out); - version (Windows) { - hFile = CreateFileW(filename.tempCStringW(), access, share, - null, createMode, 0, null); - isopen = hFile != INVALID_HANDLE_VALUE; - } - version (Posix) { - hFile = core.sys.posix.fcntl.open(filename.tempCString(), access | createMode, share); - isopen = hFile != -1; - } - if (!isopen) - throw new OpenException(cast(string) ("Cannot open or create file '" - ~ filename ~ "'")); - else if ((mode & FileMode.Append) == FileMode.Append) - seekEnd(0); - } - - private void parseMode(int mode, - out int access, - out int share, - out int createMode) { - version (Windows) { - share |= FILE_SHARE_READ | FILE_SHARE_WRITE; - if (mode & FileMode.In) { - access |= GENERIC_READ; - createMode = OPEN_EXISTING; - } - if (mode & FileMode.Out) { - access |= GENERIC_WRITE; - createMode = OPEN_ALWAYS; // will create if not present - } - if ((mode & FileMode.OutNew) == FileMode.OutNew) { - createMode = CREATE_ALWAYS; // resets file - } - } - version (Posix) { - share = octal!666; - if (mode & FileMode.In) { - access = O_RDONLY; - } - if (mode & FileMode.Out) { - createMode = O_CREAT; // will create if not present - access = O_WRONLY; - } - if (access == (O_WRONLY | O_RDONLY)) { - access = O_RDWR; - } - if ((mode & FileMode.OutNew) == FileMode.OutNew) { - access |= O_TRUNC; // resets file - } - } - } - - /// Create a file for writing. - void create(string filename) { - create(filename, FileMode.OutNew); - } - - /// ditto - void create(string filename, FileMode mode) { - close(); - open(filename, mode | FileMode.OutNew); - } - - /// Close the current file if it is open; otherwise it does nothing. - override void close() { - if (isopen) { - super.close(); - if (hFile) { - version (Windows) { - CloseHandle(hFile); - hFile = null; - } else version (Posix) { - core.sys.posix.unistd.close(hFile); - hFile = -1; - } - } - } - } - - // destructor, closes file if still opened - ~this() { close(); } - - version (Windows) { - // returns size of stream - override @property ulong size() { - assertSeekable(); - uint sizehi; - uint sizelow = GetFileSize(hFile,&sizehi); - return (cast(ulong)sizehi << 32) + sizelow; - } - } - - override size_t readBlock(void* buffer, size_t size) { - assertReadable(); - version (Windows) { - auto dwSize = to!DWORD(size); - ReadFile(hFile, buffer, dwSize, &dwSize, null); - size = dwSize; - } else version (Posix) { - size = core.sys.posix.unistd.read(hFile, buffer, size); - if (size == -1) - size = 0; - } - readEOF = (size == 0); - return size; - } - - override size_t writeBlock(const void* buffer, size_t size) { - assertWriteable(); - version (Windows) { - auto dwSize = to!DWORD(size); - WriteFile(hFile, buffer, dwSize, &dwSize, null); - size = dwSize; - } else version (Posix) { - size = core.sys.posix.unistd.write(hFile, buffer, size); - if (size == -1) - size = 0; - } - return size; - } - - override ulong seek(long offset, SeekPos rel) { - assertSeekable(); - version (Windows) { - int hi = cast(int)(offset>>32); - uint low = SetFilePointer(hFile, cast(int)offset, &hi, rel); - if ((low == INVALID_SET_FILE_POINTER) && (GetLastError() != 0)) - throw new SeekException("unable to move file pointer"); - ulong result = (cast(ulong)hi << 32) + low; - } else version (Posix) { - auto result = lseek(hFile, cast(off_t)offset, rel); - if (result == cast(typeof(result))-1) - throw new SeekException("unable to move file pointer"); - } - readEOF = false; - return cast(ulong)result; - } - - /*** - * For a seekable file returns the difference of the size and position and - * otherwise returns 0. - */ - - override @property size_t available() { - if (seekable) { - ulong lavail = size - position; - if (lavail > size_t.max) lavail = size_t.max; - return cast(size_t)lavail; - } - return 0; - } - - // OS-specific property, just in case somebody wants - // to mess with underlying API - HANDLE handle() { return hFile; } - - // run a few tests - /+ - unittest { - import std.internal.cstring : tempCString; - - File file = new File; - int i = 666; - auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$"; - file.create(stream_file); - // should be ok to write - assert(file.writeable); - file.writeLine("Testing stream.d:"); - file.writeString("Hello, world!"); - file.write(i); - // string#1 + string#2 + int should give exacly that - version (Windows) - assert(file.position == 19 + 13 + 4); - version (Posix) - assert(file.position == 18 + 13 + 4); - // we must be at the end of file - assert(file.eof); - file.close(); - // no operations are allowed when file is closed - assert(!file.readable && !file.writeable && !file.seekable); - file.open(stream_file); - // should be ok to read - assert(file.readable); - assert(file.available == file.size); - char[] line = file.readLine(); - char[] exp = "Testing stream.d:".dup; - assert(line[0] == 'T'); - assert(line.length == exp.length); - assert(!std.algorithm.cmp(line, "Testing stream.d:")); - // jump over "Hello, " - file.seek(7, SeekPos.Current); - version (Windows) - assert(file.position == 19 + 7); - version (Posix) - assert(file.position == 18 + 7); - assert(!std.algorithm.cmp(file.readString(6), "world!")); - i = 0; file.read(i); - assert(i == 666); - // string#1 + string#2 + int should give exacly that - version (Windows) - assert(file.position == 19 + 13 + 4); - version (Posix) - assert(file.position == 18 + 13 + 4); - // we must be at the end of file - assert(file.eof); - file.close(); - file.open(stream_file,FileMode.OutNew | FileMode.In); - file.writeLine("Testing stream.d:"); - file.writeLine("Another line"); - file.writeLine(""); - file.writeLine("That was blank"); - file.position = 0; - char[][] lines; - foreach(char[] line; file) { - lines ~= line.dup; - } - assert( lines.length == 4 ); - assert( lines[0] == "Testing stream.d:"); - assert( lines[1] == "Another line"); - assert( lines[2] == ""); - assert( lines[3] == "That was blank"); - file.position = 0; - lines = new char[][4]; - foreach(ulong n, char[] line; file) { - lines[cast(size_t)(n-1)] = line.dup; - } - assert( lines[0] == "Testing stream.d:"); - assert( lines[1] == "Another line"); - assert( lines[2] == ""); - assert( lines[3] == "That was blank"); - file.close(); - remove(stream_file.tempCString()); - } - +/ -} - -/*** - * This subclass is for buffered file system streams. - * - * It is a convenience class for wrapping a File in a BufferedStream. - * A buffered stream must be closed explicitly to ensure the final buffer - * content is written to the file. - */ -class BufferedFile: BufferedStream { - - /// opens file for reading - this() { super(new File()); } - - /// opens file in requested mode and buffer size - this(string filename, FileMode mode = FileMode.In, - size_t bufferSize = DefaultBufferSize) { - super(new File(filename,mode),bufferSize); - } - - /// opens file for reading with requested buffer size - this(File file, size_t bufferSize = DefaultBufferSize) { - super(file,bufferSize); - } - - /// opens existing handle; use with care! - this(HANDLE hFile, FileMode mode, size_t buffersize = DefaultBufferSize) { - super(new File(hFile,mode),buffersize); - } - - /// opens file in requested mode - void open(string filename, FileMode mode = FileMode.In) { - File sf = cast(File)s; - sf.open(filename,mode); - resetSource(); - } - - /// creates file in requested mode - void create(string filename, FileMode mode = FileMode.OutNew) { - File sf = cast(File)s; - sf.create(filename,mode); - resetSource(); - } - - // run a few tests same as File - /+ - unittest { - import std.internal.cstring : tempCString; - - BufferedFile file = new BufferedFile; - int i = 666; - auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$"; - file.create(stream_file); - // should be ok to write - assert(file.writeable); - file.writeLine("Testing stream.d:"); - file.writeString("Hello, world!"); - file.write(i); - // string#1 + string#2 + int should give exacly that - version (Windows) - assert(file.position == 19 + 13 + 4); - version (Posix) - assert(file.position == 18 + 13 + 4); - // we must be at the end of file - assert(file.eof); - long oldsize = cast(long)file.size; - file.close(); - // no operations are allowed when file is closed - assert(!file.readable && !file.writeable && !file.seekable); - file.open(stream_file); - // should be ok to read - assert(file.readable); - // test getc/ungetc and size - char c1 = file.getc(); - file.ungetc(c1); - assert( file.size == oldsize ); - assert(!std.algorithm.cmp(file.readLine(), "Testing stream.d:")); - // jump over "Hello, " - file.seek(7, SeekPos.Current); - version (Windows) - assert(file.position == 19 + 7); - version (Posix) - assert(file.position == 18 + 7); - assert(!std.algorithm.cmp(file.readString(6), "world!")); - i = 0; file.read(i); - assert(i == 666); - // string#1 + string#2 + int should give exacly that - version (Windows) - assert(file.position == 19 + 13 + 4); - version (Posix) - assert(file.position == 18 + 13 + 4); - // we must be at the end of file - assert(file.eof); - file.close(); - remove(stream_file.tempCString()); - } - +/ - -} - -/// UTF byte-order-mark signatures -enum BOM { - UTF8, /// UTF-8 - UTF16LE, /// UTF-16 Little Endian - UTF16BE, /// UTF-16 Big Endian - UTF32LE, /// UTF-32 Little Endian - UTF32BE, /// UTF-32 Big Endian -} - -private enum int NBOMS = 5; -immutable Endian[NBOMS] BOMEndian = -[ std.system.endian, - Endian.littleEndian, Endian.bigEndian, - Endian.littleEndian, Endian.bigEndian - ]; - -immutable ubyte[][NBOMS] ByteOrderMarks = -[ [0xEF, 0xBB, 0xBF], - [0xFF, 0xFE], - [0xFE, 0xFF], - [0xFF, 0xFE, 0x00, 0x00], - [0x00, 0x00, 0xFE, 0xFF] - ]; - - -/*** - * This subclass wraps a stream with big-endian or little-endian byte order - * swapping. - * - * UTF Byte-Order-Mark (BOM) signatures can be read and deduced or - * written. - * Note that an EndianStream should not be used as the source of another - * FilterStream since a FilterStream call the source with byte-oriented - * read/write requests and the EndianStream will not perform any byte swapping. - * The EndianStream reads and writes binary data (non-getc functions) in a - * one-to-one - * manner with the source stream so the source stream's position and state will be - * kept in sync with the EndianStream if only non-getc functions are called. - */ -class EndianStream : FilterStream { - - Endian endian; /// Endianness property of the source stream. - - /*** - * Create the endian stream for the source stream source with endianness end. - * The default endianness is the native byte order. - * The Endian type is defined - * in the std.system module. - */ - this(Stream source, Endian end = std.system.endian) { - super(source); - endian = end; - } - - /*** - * Return -1 if no BOM and otherwise read the BOM and return it. - * - * If there is no BOM or if bytes beyond the BOM are read then the bytes read - * are pushed back onto the ungetc buffer or ungetcw buffer. - * Pass ungetCharSize == 2 to use - * ungetcw instead of ungetc when no BOM is present. - */ - int readBOM(int ungetCharSize = 1) { - ubyte[4] BOM_buffer; - int n = 0; // the number of read bytes - int result = -1; // the last match or -1 - for (int i=0; i < NBOMS; ++i) { - int j; - immutable ubyte[] bom = ByteOrderMarks[i]; - for (j=0; j < bom.length; ++j) { - if (n <= j) { // have to read more - if (eof) - break; - readExact(&BOM_buffer[n++],1); - } - if (BOM_buffer[j] != bom[j]) - break; - } - if (j == bom.length) // found a match - result = i; - } - ptrdiff_t m = 0; - if (result != -1) { - endian = BOMEndian[result]; // set stream endianness - m = ByteOrderMarks[result].length; - } - if ((ungetCharSize == 1 && result == -1) || (result == BOM.UTF8)) { - while (n-- > m) - ungetc(BOM_buffer[n]); - } else { // should eventually support unget for dchar as well - if (n & 1) // make sure we have an even number of bytes - readExact(&BOM_buffer[n++],1); - while (n > m) { - n -= 2; - wchar cw = *(cast(wchar*)&BOM_buffer[n]); - fixBO(&cw,2); - ungetcw(cw); - } - } - return result; - } - - /*** - * Correct the byte order of buffer to match native endianness. - * size must be even. - */ - final void fixBO(const(void)* buffer, size_t size) { - if (endian != std.system.endian) { - ubyte* startb = cast(ubyte*)buffer; - uint* start = cast(uint*)buffer; - switch (size) { - case 0: break; - case 2: { - ubyte x = *startb; - *startb = *(startb+1); - *(startb+1) = x; - break; - } - case 4: { - *start = bswap(*start); - break; - } - default: { - uint* end = cast(uint*)(buffer + size - uint.sizeof); - while (start < end) { - uint x = bswap(*start); - *start = bswap(*end); - *end = x; - ++start; - --end; - } - startb = cast(ubyte*)start; - ubyte* endb = cast(ubyte*)end; - auto len = uint.sizeof - (startb - endb); - if (len > 0) - fixBO(startb,len); - } - } - } - } - - /*** - * Correct the byte order of the given buffer in blocks of the given size and - * repeated the given number of times. - * size must be even. - */ - final void fixBlockBO(void* buffer, uint size, size_t repeat) { - while (repeat--) { - fixBO(buffer,size); - buffer += size; - } - } - - override void read(out byte x) { readExact(&x, x.sizeof); } - override void read(out ubyte x) { readExact(&x, x.sizeof); } - override void read(out short x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out ushort x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out int x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out uint x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out long x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out ulong x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out float x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out double x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out real x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out ifloat x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out idouble x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out ireal x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out cfloat x) { readExact(&x, x.sizeof); fixBlockBO(&x,float.sizeof,2); } - override void read(out cdouble x) { readExact(&x, x.sizeof); fixBlockBO(&x,double.sizeof,2); } - override void read(out creal x) { readExact(&x, x.sizeof); fixBlockBO(&x,real.sizeof,2); } - override void read(out char x) { readExact(&x, x.sizeof); } - override void read(out wchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out dchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - - override wchar getcw() { - wchar c; - if (prevCr) { - prevCr = false; - c = getcw(); - if (c != '\n') - return c; - } - if (unget.length > 1) { - c = unget[unget.length - 1]; - unget.length = unget.length - 1; - } else { - void* buf = &c; - size_t n = readBlock(buf,2); - if (n == 1 && readBlock(buf+1,1) == 0) - throw new ReadException("not enough data in stream"); - fixBO(&c,c.sizeof); - } - return c; - } - - override wchar[] readStringW(size_t length) { - wchar[] result = new wchar[length]; - readExact(result.ptr, length * wchar.sizeof); - fixBlockBO(result.ptr, wchar.sizeof, length); - return result; - } - - /// Write the specified BOM b to the source stream. - void writeBOM(BOM b) { - immutable ubyte[] bom = ByteOrderMarks[b]; - writeBlock(bom.ptr, bom.length); - } - - override void write(byte x) { writeExact(&x, x.sizeof); } - override void write(ubyte x) { writeExact(&x, x.sizeof); } - override void write(short x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(ushort x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(int x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(uint x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(long x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(ulong x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(float x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(double x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(real x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(ifloat x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(idouble x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(ireal x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(cfloat x) { fixBlockBO(&x,float.sizeof,2); writeExact(&x, x.sizeof); } - override void write(cdouble x) { fixBlockBO(&x,double.sizeof,2); writeExact(&x, x.sizeof); } - override void write(creal x) { fixBlockBO(&x,real.sizeof,2); writeExact(&x, x.sizeof); } - override void write(char x) { writeExact(&x, x.sizeof); } - override void write(wchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(dchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - - override void writeStringW(const(wchar)[] str) { - foreach(wchar cw;str) { - fixBO(&cw,2); - s.writeExact(&cw, 2); - } - } - - override @property bool eof() { return s.eof && !ungetAvailable(); } - override @property ulong size() { return s.size; } - - unittest { - MemoryStream m; - m = new MemoryStream (); - EndianStream em = new EndianStream(m,Endian.bigEndian); - uint x = 0x11223344; - em.write(x); - assert( m.data[0] == 0x11 ); - assert( m.data[1] == 0x22 ); - assert( m.data[2] == 0x33 ); - assert( m.data[3] == 0x44 ); - em.position = 0; - ushort x2 = 0x5566; - em.write(x2); - assert( m.data[0] == 0x55 ); - assert( m.data[1] == 0x66 ); - em.position = 0; - static ubyte[12] x3 = [1,2,3,4,5,6,7,8,9,10,11,12]; - em.fixBO(x3.ptr,12); - if (std.system.endian == Endian.littleEndian) { - assert( x3[0] == 12 ); - assert( x3[1] == 11 ); - assert( x3[2] == 10 ); - assert( x3[4] == 8 ); - assert( x3[5] == 7 ); - assert( x3[6] == 6 ); - assert( x3[8] == 4 ); - assert( x3[9] == 3 ); - assert( x3[10] == 2 ); - assert( x3[11] == 1 ); - } - em.endian = Endian.littleEndian; - em.write(x); - assert( m.data[0] == 0x44 ); - assert( m.data[1] == 0x33 ); - assert( m.data[2] == 0x22 ); - assert( m.data[3] == 0x11 ); - em.position = 0; - em.write(x2); - assert( m.data[0] == 0x66 ); - assert( m.data[1] == 0x55 ); - em.position = 0; - em.fixBO(x3.ptr,12); - if (std.system.endian == Endian.bigEndian) { - assert( x3[0] == 12 ); - assert( x3[1] == 11 ); - assert( x3[2] == 10 ); - assert( x3[4] == 8 ); - assert( x3[5] == 7 ); - assert( x3[6] == 6 ); - assert( x3[8] == 4 ); - assert( x3[9] == 3 ); - assert( x3[10] == 2 ); - assert( x3[11] == 1 ); - } - em.writeBOM(BOM.UTF8); - assert( m.position == 3 ); - assert( m.data[0] == 0xEF ); - assert( m.data[1] == 0xBB ); - assert( m.data[2] == 0xBF ); - em.writeString ("Hello, world"); - em.position = 0; - assert( m.position == 0 ); - assert( em.readBOM() == BOM.UTF8 ); - assert( m.position == 3 ); - assert( em.getc() == 'H' ); - em.position = 0; - em.writeBOM(BOM.UTF16BE); - assert( m.data[0] == 0xFE ); - assert( m.data[1] == 0xFF ); - em.position = 0; - em.writeBOM(BOM.UTF16LE); - assert( m.data[0] == 0xFF ); - assert( m.data[1] == 0xFE ); - em.position = 0; - em.writeString ("Hello, world"); - em.position = 0; - assert( em.readBOM() == -1 ); - assert( em.getc() == 'H' ); - assert( em.getc() == 'e' ); - assert( em.getc() == 'l' ); - assert( em.getc() == 'l' ); - em.position = 0; - } -} - -/*** - * Parameterized subclass that wraps an array-like buffer with a stream - * interface. - * - * The type Buffer must support the length property, opIndex and opSlice. - * Compile in release mode when directly instantiating a TArrayStream to avoid - * link errors. - */ -class TArrayStream(Buffer): Stream { - Buffer buf; // current data - ulong len; // current data length - ulong cur; // current file position - - /// Create the stream for the the buffer buf. Non-copying. - this(Buffer buf) { - super (); - this.buf = buf; - this.len = buf.length; - readable = writeable = seekable = true; - } - - // ensure subclasses don't violate this - invariant() { - assert(len <= buf.length); - assert(cur <= len); - } - - override size_t readBlock(void* buffer, size_t size) { - assertReadable(); - ubyte* cbuf = cast(ubyte*) buffer; - if (len - cur < size) - size = cast(size_t)(len - cur); - ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)]; - cbuf[0 .. size] = ubuf[]; - cur += size; - return size; - } - - override size_t writeBlock(const void* buffer, size_t size) { - assertWriteable(); - ubyte* cbuf = cast(ubyte*) buffer; - ulong blen = buf.length; - if (cur + size > blen) - size = cast(size_t)(blen - cur); - ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)]; - ubuf[] = cbuf[0 .. size]; - cur += size; - if (cur > len) - len = cur; - return size; - } - - override ulong seek(long offset, SeekPos rel) { - assertSeekable(); - long scur; // signed to saturate to 0 properly - - switch (rel) { - case SeekPos.Set: scur = offset; break; - case SeekPos.Current: scur = cast(long)(cur + offset); break; - case SeekPos.End: scur = cast(long)(len + offset); break; - default: - assert(0); - } - - if (scur < 0) - cur = 0; - else if (scur > len) - cur = len; - else - cur = cast(ulong)scur; - - return cur; - } - - override @property size_t available () { return cast(size_t)(len - cur); } - - /// Get the current memory data in total. - @property ubyte[] data() { - if (len > size_t.max) - throw new StreamException("Stream too big"); - const(void)[] res = buf[0 .. cast(size_t)len]; - return cast(ubyte[])res; - } - - override string toString() { - // assume data is UTF8 - return to!(string)(cast(char[])data); - } -} - -/* Test the TArrayStream */ -unittest { - char[100] buf; - TArrayStream!(char[]) m; - - m = new TArrayStream!(char[]) (buf); - assert (m.isOpen); - m.writeString ("Hello, world"); - assert (m.position == 12); - assert (m.available == 88); - assert (m.seekSet (0) == 0); - assert (m.available == 100); - assert (m.seekCur (4) == 4); - assert (m.available == 96); - assert (m.seekEnd (-8) == 92); - assert (m.available == 8); - assert (m.size == 100); - assert (m.seekSet (4) == 4); - assert (m.readString (4) == "o, w"); - m.writeString ("ie"); - assert (buf[0..12] == "Hello, wield"); - assert (m.position == 10); - assert (m.available == 90); - assert (m.size == 100); - m.seekSet (0); - assert (m.printf ("Answer is %d", 42) == 12); - assert (buf[0..12] == "Answer is 42"); -} - -/// This subclass reads and constructs an array of bytes in memory. -class MemoryStream: TArrayStream!(ubyte[]) { - - /// Create the output buffer and setup for reading, writing, and seeking. - // clear to an empty buffer. - this() { this(cast(ubyte[]) null); } - - /*** - * Create the output buffer and setup for reading, writing, and seeking. - * Load it with specific input data. - */ - this(ubyte[] buf) { super (buf); } - this(byte[] buf) { this(cast(ubyte[]) buf); } /// ditto - this(char[] buf) { this(cast(ubyte[]) buf); } /// ditto - - /// Ensure the stream can write count extra bytes from cursor position without an allocation. - void reserve(size_t count) { - if (cur + count > buf.length) - buf.length = cast(uint)((cur + count) * 2); - } - - override size_t writeBlock(const void* buffer, size_t size) { - reserve(size); - return super.writeBlock(buffer,size); - } - - unittest { - MemoryStream m; - - m = new MemoryStream (); - assert (m.isOpen); - m.writeString ("Hello, world"); - assert (m.position == 12); - assert (m.seekSet (0) == 0); - assert (m.available == 12); - assert (m.seekCur (4) == 4); - assert (m.available == 8); - assert (m.seekEnd (-8) == 4); - assert (m.available == 8); - assert (m.size == 12); - assert (m.readString (4) == "o, w"); - m.writeString ("ie"); - assert (cast(char[]) m.data == "Hello, wield"); - m.seekEnd (0); - m.writeString ("Foo"); - assert (m.position == 15); - assert (m.available == 0); - m.writeString ("Foo foo foo foo foo foo foo"); - assert (m.position == 42); - m.position = 0; - assert (m.available == 42); - m.writef("%d %d %s",100,345,"hello"); - auto str = m.toString(); - assert (str[0..13] == "100 345 hello", str[0 .. 13]); - assert (m.available == 29); - assert (m.position == 13); - - MemoryStream m2; - m.position = 3; - m2 = new MemoryStream (); - m2.writeString("before"); - m2.copyFrom(m,10); - str = m2.toString(); - assert (str[0..16] == "before 345 hello"); - m2.position = 3; - m2.copyFrom(m); - auto str2 = m.toString(); - str = m2.toString(); - assert (str == ("bef" ~ str2)); - } -} - -import std.mmfile; - -/*** - * This subclass wraps a memory-mapped file with the stream API. - * See std.mmfile module. - */ -class MmFileStream : TArrayStream!(MmFile) { - - /// Create stream wrapper for file. - this(MmFile file) { - super (file); - MmFile.Mode mode = file.mode(); - writeable = mode > MmFile.Mode.read; - } - - override void flush() { - if (isopen) { - super.flush(); - buf.flush(); - } - } - - override void close() { - if (isopen) { - super.close(); - delete buf; - buf = null; - } - } -} - -unittest { - auto test_file = undead.internal.file.deleteme ~ "-testing.txt"; - MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew,100,null); - MmFileStream m; - m = new MmFileStream (mf); - m.writeString ("Hello, world"); - assert (m.position == 12); - assert (m.seekSet (0) == 0); - assert (m.seekCur (4) == 4); - assert (m.seekEnd (-8) == 92); - assert (m.size == 100); - assert (m.seekSet (4)); - assert (m.readString (4) == "o, w"); - m.writeString ("ie"); - ubyte[] dd = m.data; - assert ((cast(char[]) dd)[0 .. 12] == "Hello, wield"); - m.position = 12; - m.writeString ("Foo"); - assert (m.position == 15); - m.writeString ("Foo foo foo foo foo foo foo"); - assert (m.position == 42); - m.close(); - mf = new MmFile(test_file); - m = new MmFileStream (mf); - assert (!m.writeable); - char[] str = m.readString(12); - assert (str == "Hello, wield"); - m.close(); - std.file.remove(test_file); -} - - -/*** - * This subclass slices off a portion of another stream, making seeking relative - * to the boundaries of the slice. - * - * It could be used to section a large file into a - * set of smaller files, such as with tar archives. Reading and writing a - * SliceStream does not modify the position of the source stream if it is - * seekable. - */ -class SliceStream : FilterStream { - private { - ulong pos; // our position relative to low - ulong low; // low stream offset. - ulong high; // high stream offset. - bool bounded; // upper-bounded by high. - } - - /*** - * Indicate both the source stream to use for reading from and the low part of - * the slice. - * - * The high part of the slice is dependent upon the end of the source - * stream, so that if you write beyond the end it resizes the stream normally. - */ - this (Stream s, ulong low) - in { - assert (low <= s.size); - } - body { - super(s); - this.low = low; - this.high = 0; - this.bounded = false; - } - - /*** - * Indicate the high index as well. - * - * Attempting to read or write past the high - * index results in the end being clipped off. - */ - this (Stream s, ulong low, ulong high) - in { - assert (low <= high); - assert (high <= s.size); - } - body { - super(s); - this.low = low; - this.high = high; - this.bounded = true; - } - - invariant() { - if (bounded) - assert (pos <= high - low); - else - // size() does not appear to be const, though it should be - assert (pos <= (cast()s).size - low); - } - - override size_t readBlock (void *buffer, size_t size) { - assertReadable(); - if (bounded && size > high - low - pos) - size = cast(size_t)(high - low - pos); - ulong bp = s.position; - if (seekable) - s.position = low + pos; - size_t ret = super.readBlock(buffer, size); - if (seekable) { - pos = s.position - low; - s.position = bp; - } - return ret; - } - - override size_t writeBlock (const void *buffer, size_t size) { - assertWriteable(); - if (bounded && size > high - low - pos) - size = cast(size_t)(high - low - pos); - ulong bp = s.position; - if (seekable) - s.position = low + pos; - size_t ret = s.writeBlock(buffer, size); - if (seekable) { - pos = s.position - low; - s.position = bp; - } - return ret; - } - - override ulong seek(long offset, SeekPos rel) { - assertSeekable(); - long spos; - - switch (rel) { - case SeekPos.Set: - spos = offset; - break; - case SeekPos.Current: - spos = cast(long)(pos + offset); - break; - case SeekPos.End: - if (bounded) - spos = cast(long)(high - low + offset); - else - spos = cast(long)(s.size - low + offset); - break; - default: - assert(0); - } - - if (spos < 0) - pos = 0; - else if (bounded && spos > high - low) - pos = high - low; - else if (!bounded && spos > s.size - low) - pos = s.size - low; - else - pos = cast(ulong)spos; - - readEOF = false; - return pos; - } - - override @property size_t available() { - size_t res = s.available; - ulong bp = s.position; - if (bp <= pos+low && pos+low <= bp+res) { - if (!bounded || bp+res <= high) - return cast(size_t)(bp + res - pos - low); - else if (high <= bp+res) - return cast(size_t)(high - pos - low); - } - return 0; - } - - unittest { - MemoryStream m; - SliceStream s; - - m = new MemoryStream ((cast(char[])"Hello, world").dup); - s = new SliceStream (m, 4, 8); - assert (s.size == 4); - assert (m.position == 0); - assert (s.position == 0); - assert (m.available == 12); - assert (s.available == 4); - - assert (s.writeBlock (cast(char *) "Vroom", 5) == 4); - assert (m.position == 0); - assert (s.position == 4); - assert (m.available == 12); - assert (s.available == 0); - assert (s.seekEnd (-2) == 2); - assert (s.available == 2); - assert (s.seekEnd (2) == 4); - assert (s.available == 0); - assert (m.position == 0); - assert (m.available == 12); - - m.seekEnd(0); - m.writeString("\nBlaho"); - assert (m.position == 18); - assert (m.available == 0); - assert (s.position == 4); - assert (s.available == 0); - - s = new SliceStream (m, 4); - assert (s.size == 14); - assert (s.toString () == "Vrooorld\nBlaho"); - s.seekEnd (0); - assert (s.available == 0); - - s.writeString (", etcetera."); - assert (s.position == 25); - assert (s.seekSet (0) == 0); - assert (s.size == 25); - assert (m.position == 18); - assert (m.size == 29); - assert (m.toString() == "HellVrooorld\nBlaho, etcetera."); - } -} |