aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorRalph Amissah <ralph@amissah.com>2016-10-01 14:12:13 -0400
committerRalph Amissah <ralph@amissah.com>2019-04-10 15:14:13 -0400
commitba1712e77b31704fd9ba16d14e15518e7a7dd104 (patch)
tree1a0d3233fb611b68dbf43e098a41a0d9378e9ace /src
parentupdate 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')
-rw-r--r--src/sdlang/ast.d2945
-rw-r--r--src/sdlang/dub.json38
-rw-r--r--src/sdlang/exception.d190
-rw-r--r--src/sdlang/lexer.d2068
-rw-r--r--src/sdlang/libinputvisitor/dub.json10
-rw-r--r--src/sdlang/libinputvisitor/libInputVisitor.d113
-rw-r--r--src/sdlang/package.d133
-rw-r--r--src/sdlang/parser.d628
-rw-r--r--src/sdlang/symbol.d61
-rw-r--r--src/sdlang/taggedalgebraic/taggedalgebraic.d1085
-rw-r--r--src/sdlang/token.d550
-rw-r--r--src/sdlang/util.d200
-rw-r--r--src/sdp/ao_abstract_doc_source.d32
-rw-r--r--src/sdp/ao_ansi_colors.d5
-rw-r--r--src/sdp/ao_conf_make_meta.d11
-rw-r--r--src/sdp/ao_conf_make_meta_native.d3
-rw-r--r--src/sdp/ao_conf_make_meta_sdlang.d5
-rw-r--r--src/sdp/ao_defaults.d5
-rw-r--r--src/sdp/ao_object_setter.d5
-rw-r--r--src/sdp/ao_output_debugs.d3
-rw-r--r--src/sdp/ao_read_config_files.d5
-rw-r--r--src/sdp/ao_read_source_files.d7
-rw-r--r--src/sdp/ao_rgx.d5
-rw-r--r--src/sdp/compile_time_info.d3
-rw-r--r--src/sdp/output_hub.d6
-rw-r--r--src/undead/doformat.d1620
-rw-r--r--src/undead/internal/file.d25
-rw-r--r--src/undead/stream.d3076
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),
- &plusmn;, followed by at least a two digit exponent: $(I d.dddddd)e$(I &plusmn;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 &plusmn;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 &plusmn;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.");
- }
-}