diff options
| author | Ralph Amissah <ralph@amissah.com> | 2016-10-01 14:12:13 -0400 | 
|---|---|---|
| committer | Ralph Amissah <ralph@amissah.com> | 2019-04-10 15:14:13 -0400 | 
| commit | ba1712e77b31704fd9ba16d14e15518e7a7dd104 (patch) | |
| tree | 1a0d3233fb611b68dbf43e098a41a0d9378e9ace /src | |
| parent | update sdlang, start looking to using dub remote dependencies (diff) | |
0.7.0 using dub remote dependencies (local src related to sdlang removed)
Diffstat (limited to 'src')
28 files changed, 41 insertions, 12796 deletions
diff --git a/src/sdlang/ast.d b/src/sdlang/ast.d deleted file mode 100644 index 87dd0bd..0000000 --- a/src/sdlang/ast.d +++ /dev/null @@ -1,2945 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.ast; - -import std.algorithm; -import std.array; -import std.conv; -import std.range; -import std.string; - -import sdlang.exception; -import sdlang.token; -import sdlang.util; - -class Attribute -{ -	Value    value; -	Location location; -	 -	private Tag _parent; -	/// Get parent tag. To set a parent, attach this Attribute to its intended -	/// parent tag by calling `Tag.add(...)`, or by passing it to -	/// the parent tag's constructor. -	@property Tag parent() -	{ -		return _parent; -	} - -	private string _namespace; -	/++ -	This tag's namespace. Empty string if no namespace. -	 -	Note that setting this value is O(n) because internal lookup structures  -	need to be updated. -	 -	Note also, that setting this may change where this tag is ordered among -	its parent's list of tags. -	+/ -	@property string namespace() -	{ -		return _namespace; -	} -	///ditto -	@property void namespace(string value) -	{ -		if(_parent && _namespace != value) -		{ -			// Remove -			auto saveParent = _parent; -			if(_parent) -				this.remove(); - -			// Change namespace -			_namespace = value; - -			// Re-add -			if(saveParent) -				saveParent.add(this); -		} -		else -			_namespace = value; -	} -	 -	private string _name; -	/++ -	This attribute's name, not including namespace. -	 -	Use `getFullName().toString` if you want the namespace included. -	 -	Note that setting this value is O(n) because internal lookup structures  -	need to be updated. - -	Note also, that setting this may change where this attribute is ordered -	among its parent's list of tags. -	+/ -	@property string name() -	{ -		return _name; -	} -	///ditto -	@property void name(string value) -	{ -		if(_parent && _name != value) -		{ -			_parent.updateId++; -			 -			void removeFromGroupedLookup(string ns) -			{ -				// Remove from _parent._attributes[ns] -				auto sameNameAttrs = _parent._attributes[ns][_name]; -				auto targetIndex = sameNameAttrs.countUntil(this); -				_parent._attributes[ns][_name].removeIndex(targetIndex); -			} -			 -			// Remove from _parent._tags -			removeFromGroupedLookup(_namespace); -			removeFromGroupedLookup("*"); - -			// Change name -			_name = value; -			 -			// Add to new locations in _parent._attributes -			_parent._attributes[_namespace][_name] ~= this; -			_parent._attributes["*"][_name] ~= this; -		} -		else -			_name = value; -	} - -	/// This tag's name, including namespace if one exists. -	deprecated("Use 'getFullName().toString()'") -	@property string fullName() -	{ -		return getFullName().toString(); -	} -	 -	/// This tag's name, including namespace if one exists. -	FullName getFullName() -	{ -		return FullName(_namespace, _name); -	} - -	this(string namespace, string name, Value value, Location location = Location(0, 0, 0)) -	{ -		this._namespace = namespace; -		this._name      = name; -		this.location   = location; -		this.value      = value; -	} -	 -	this(string name, Value value, Location location = Location(0, 0, 0)) -	{ -		this._namespace = ""; -		this._name      = name; -		this.location   = location; -		this.value      = value; -	} -	 -	/// Copy this Attribute. -	/// The clone does $(B $(I not)) have a parent, even if the original does. -	Attribute clone() -	{ -		return new Attribute(_namespace, _name, value, location); -	} -	 -	/// Removes `this` from its parent, if any. Returns `this` for chaining. -	/// Inefficient ATM, but it works. -	Attribute remove() -	{ -		if(!_parent) -			return this; -		 -		void removeFromGroupedLookup(string ns) -		{ -			// Remove from _parent._attributes[ns] -			auto sameNameAttrs = _parent._attributes[ns][_name]; -			auto targetIndex = sameNameAttrs.countUntil(this); -			_parent._attributes[ns][_name].removeIndex(targetIndex); -		} -		 -		// Remove from _parent._attributes -		removeFromGroupedLookup(_namespace); -		removeFromGroupedLookup("*"); - -		// Remove from _parent.allAttributes -		auto allAttrsIndex = _parent.allAttributes.countUntil(this); -		_parent.allAttributes.removeIndex(allAttrsIndex); - -		// Remove from _parent.attributeIndicies -		auto sameNamespaceAttrs = _parent.attributeIndicies[_namespace]; -		auto attrIndiciesIndex = sameNamespaceAttrs.countUntil(allAttrsIndex); -		_parent.attributeIndicies[_namespace].removeIndex(attrIndiciesIndex); -		 -		// Fixup other indicies -		foreach(ns, ref nsAttrIndicies; _parent.attributeIndicies) -		foreach(k, ref v; nsAttrIndicies) -		if(v > allAttrsIndex) -			v--; -		 -		_parent.removeNamespaceIfEmpty(_namespace); -		_parent.updateId++; -		_parent = null; -		return this; -	} - -	override bool opEquals(Object o) -	{ -		auto a = cast(Attribute)o; -		if(!a) -			return false; - -		return -			_namespace == a._namespace && -			_name      == a._name      && -			value      == a.value; -	} -	 -	string toSDLString()() -	{ -		Appender!string sink; -		this.toSDLString(sink); -		return sink.data; -	} - -	void toSDLString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char)) -	{ -		if(_namespace != "") -		{ -			sink.put(_namespace); -			sink.put(':'); -		} - -		sink.put(_name); -		sink.put('='); -		value.toSDLString(sink); -	} -} - -/// Deep-copy an array of Tag or Attribute. -/// The top-level clones are $(B $(I not)) attached to any parent, even if the originals are. -T[] clone(T)(T[] arr) if(is(T==Tag) || is(T==Attribute)) -{ -	T[] newArr; -	newArr.length = arr.length; -	 -	foreach(i; 0..arr.length) -		newArr[i] = arr[i].clone(); -	 -	return newArr; -} - -class Tag -{ -	/// File/Line/Column/Index information for where this tag was located in -	/// its original SDLang file. -	Location location; -	 -	/// Access all this tag's values, as an array of type `sdlang.token.Value`. -	Value[]  values; - -	private Tag _parent; -	/// Get parent tag. To set a parent, attach this Tag to its intended -	/// parent tag by calling `Tag.add(...)`, or by passing it to -	/// the parent tag's constructor. -	@property Tag parent() -	{ -		return _parent; -	} - -	private string _namespace; -	/++ -	This tag's namespace. Empty string if no namespace. -	 -	Note that setting this value is O(n) because internal lookup structures  -	need to be updated. -	 -	Note also, that setting this may change where this tag is ordered among -	its parent's list of tags. -	+/ -	@property string namespace() -	{ -		return _namespace; -	} -	///ditto -	@property void namespace(string value) -	{ -		//TODO: Can we do this in-place, without removing/adding and thus -		//      modyfying the internal order? -		if(_parent && _namespace != value) -		{ -			// Remove -			auto saveParent = _parent; -			if(_parent) -				this.remove(); - -			// Change namespace -			_namespace = value; - -			// Re-add -			if(saveParent) -				saveParent.add(this); -		} -		else -			_namespace = value; -	} -	 -	private string _name; -	/++ -	This tag's name, not including namespace. -	 -	Use `getFullName().toString` if you want the namespace included. -	 -	Note that setting this value is O(n) because internal lookup structures  -	need to be updated. - -	Note also, that setting this may change where this tag is ordered among -	its parent's list of tags. -	+/ -	@property string name() -	{ -		return _name; -	} -	///ditto -	@property void name(string value) -	{ -		//TODO: Seriously? Can't we at least do the "*" modification *in-place*? -		 -		if(_parent && _name != value) -		{ -			_parent.updateId++; -			 -			// Not the most efficient, but it works. -			void removeFromGroupedLookup(string ns) -			{ -				// Remove from _parent._tags[ns] -				auto sameNameTags = _parent._tags[ns][_name]; -				auto targetIndex = sameNameTags.countUntil(this); -				_parent._tags[ns][_name].removeIndex(targetIndex); -			} -			 -			// Remove from _parent._tags -			removeFromGroupedLookup(_namespace); -			removeFromGroupedLookup("*"); -			 -			// Change name -			_name = value; -			 -			// Add to new locations in _parent._tags -			//TODO: Can we re-insert while preserving the original order? -			_parent._tags[_namespace][_name] ~= this; -			_parent._tags["*"][_name] ~= this; -		} -		else -			_name = value; -	} -	 -	/// This tag's name, including namespace if one exists. -	deprecated("Use 'getFullName().toString()'") -	@property string fullName() -	{ -		return getFullName().toString(); -	} -	 -	/// This tag's name, including namespace if one exists. -	FullName getFullName() -	{ -		return FullName(_namespace, _name); -	} -	 -	// Tracks dirtiness. This is incremented every time a change is made which -	// could invalidate existing ranges. This way, the ranges can detect when -	// they've been invalidated. -	private size_t updateId=0; -	 -	this(Tag parent = null) -	{ -		if(parent) -			parent.add(this); -	} - -	this( -		string namespace, string name, -		Value[] values=null, Attribute[] attributes=null, Tag[] children=null -	) -	{ -		this(null, namespace, name, values, attributes, children); -	} - -	this( -		Tag parent, string namespace, string name, -		Value[] values=null, Attribute[] attributes=null, Tag[] children=null -	) -	{ -		this._namespace = namespace; -		this._name      = name; - -		if(parent) -			parent.add(this); -		 -		this.values = values; -		this.add(attributes); -		this.add(children); -	} - -	/// Deep-copy this Tag. -	/// The clone does $(B $(I not)) have a parent, even if the original does. -	Tag clone() -	{ -		auto newTag = new Tag(_namespace, _name, values.dup, allAttributes.clone(), allTags.clone()); -		newTag.location = location; -		return newTag; -	} -	 -	private Attribute[] allAttributes; // In same order as specified in SDL file. -	private Tag[]       allTags;       // In same order as specified in SDL file. -	private string[]    allNamespaces; // In same order as specified in SDL file. - -	private size_t[][string] attributeIndicies; // allAttributes[ attributes[namespace][i] ] -	private size_t[][string] tagIndicies;       // allTags[ tags[namespace][i] ] - -	private Attribute[][string][string] _attributes; // attributes[namespace or "*"][name][i] -	private Tag[][string][string]       _tags;       // tags[namespace or "*"][name][i] -	 -	/// Adds a Value, Attribute, Tag (or array of such) as a member/child of this Tag. -	/// Returns `this` for chaining. -	/// Throws `ValidationException` if trying to add an Attribute or Tag -	/// that already has a parent. -	Tag add(Value val) -	{ -		values ~= val; -		updateId++; -		return this; -	} -	 -	///ditto -	Tag add(Value[] vals) -	{ -		foreach(val; vals) -			add(val); - -		return this; -	} -	 -	///ditto -	Tag add(Attribute attr) -	{ -		if(attr._parent) -		{ -			throw new ValidationException( -				"Attribute is already attached to a parent tag. "~ -				"Use Attribute.remove() before adding it to another tag." -			); -		} -		 -		if(!allNamespaces.canFind(attr._namespace)) -			allNamespaces ~= attr._namespace; - -		attr._parent = this; -		 -		allAttributes ~= attr; -		attributeIndicies[attr._namespace] ~= allAttributes.length-1; -		_attributes[attr._namespace][attr._name] ~= attr; -		_attributes["*"]            [attr._name] ~= attr; - -		updateId++; -		return this; -	} -	 -	///ditto -	Tag add(Attribute[] attrs) -	{ -		foreach(attr; attrs) -			add(attr); - -		return this; -	} -	 -	///ditto -	Tag add(Tag tag) -	{ -		if(tag._parent) -		{ -			throw new ValidationException( -				"Tag is already attached to a parent tag. "~ -				"Use Tag.remove() before adding it to another tag." -			); -		} - -		if(!allNamespaces.canFind(tag._namespace)) -			allNamespaces ~= tag._namespace; -		 -		tag._parent = this; - -		allTags ~= tag; -		tagIndicies[tag._namespace] ~= allTags.length-1; -		_tags[tag._namespace][tag._name] ~= tag; -		_tags["*"]           [tag._name] ~= tag; -		 -		updateId++; -		return this; -	} -	 -	///ditto -	Tag add(Tag[] tags) -	{ -		foreach(tag; tags) -			add(tag); - -		return this; -	} -	 -	/// Removes `this` from its parent, if any. Returns `this` for chaining. -	/// Inefficient ATM, but it works. -	Tag remove() -	{ -		if(!_parent) -			return this; -		 -		void removeFromGroupedLookup(string ns) -		{ -			// Remove from _parent._tags[ns] -			auto sameNameTags = _parent._tags[ns][_name]; -			auto targetIndex = sameNameTags.countUntil(this); -			_parent._tags[ns][_name].removeIndex(targetIndex); -		} -		 -		// Remove from _parent._tags -		removeFromGroupedLookup(_namespace); -		removeFromGroupedLookup("*"); - -		// Remove from _parent.allTags -		auto allTagsIndex = _parent.allTags.countUntil(this); -		_parent.allTags.removeIndex(allTagsIndex); - -		// Remove from _parent.tagIndicies -		auto sameNamespaceTags = _parent.tagIndicies[_namespace]; -		auto tagIndiciesIndex = sameNamespaceTags.countUntil(allTagsIndex); -		_parent.tagIndicies[_namespace].removeIndex(tagIndiciesIndex); -		 -		// Fixup other indicies -		foreach(ns, ref nsTagIndicies; _parent.tagIndicies) -		foreach(k, ref v; nsTagIndicies) -		if(v > allTagsIndex) -			v--; -		 -		_parent.removeNamespaceIfEmpty(_namespace); -		_parent.updateId++; -		_parent = null; -		return this; -	} -	 -	private void removeNamespaceIfEmpty(string namespace) -	{ -		// If namespace has no attributes, remove it from attributeIndicies/_attributes -		if(namespace in attributeIndicies && attributeIndicies[namespace].length == 0) -		{ -			attributeIndicies.remove(namespace); -			_attributes.remove(namespace); -		} - -		// If namespace has no tags, remove it from tagIndicies/_tags -		if(namespace in tagIndicies && tagIndicies[namespace].length == 0) -		{ -			tagIndicies.remove(namespace); -			_tags.remove(namespace); -		} -		 -		// If namespace is now empty, remove it from allNamespaces -		if( -			namespace !in tagIndicies && -			namespace !in attributeIndicies -		) -		{ -			auto allNamespacesIndex = allNamespaces.length - allNamespaces.find(namespace).length; -			allNamespaces = allNamespaces[0..allNamespacesIndex] ~ allNamespaces[allNamespacesIndex+1..$]; -		} -	} -	 -	struct NamedMemberRange(T, string membersGrouped) -	{ -		private Tag tag; -		private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name) -		private string name; -		private size_t updateId;  // Tag's updateId when this range was created. - -		this(Tag tag, string namespace, string name, size_t updateId) -		{ -			this.tag       = tag; -			this.namespace = namespace; -			this.name      = name; -			this.updateId  = updateId; -			frontIndex = 0; - -			if( -				tag !is null && -				namespace in mixin("tag."~membersGrouped) && -				name in mixin("tag."~membersGrouped~"[namespace]") -			) -				endIndex = mixin("tag."~membersGrouped~"[namespace][name].length"); -			else -				endIndex = 0; -		} -		 -		invariant() -		{ -			assert( -				this.updateId == tag.updateId, -				"This range has been invalidated by a change to the tag." -			); -		} - -		@property bool empty() -		{ -			return tag is null || frontIndex == endIndex; -		} -		 -		private size_t frontIndex; -		@property T front() -		{ -			return this[0]; -		} -		void popFront() -		{ -			if(empty) -				throw new DOMRangeException(tag, "Range is empty"); - -			frontIndex++; -		} - -		private size_t endIndex; // One past the last element -		@property T back() -		{ -			return this[$-1]; -		} -		void popBack() -		{ -			if(empty) -				throw new DOMRangeException(tag, "Range is empty"); - -			endIndex--; -		} -		 -		alias length opDollar; -		@property size_t length() -		{ -			return endIndex - frontIndex; -		} -		 -		@property typeof(this) save() -		{ -			auto r = typeof(this)(this.tag, this.namespace, this.name, this.updateId); -			r.frontIndex = this.frontIndex; -			r.endIndex   = this.endIndex; -			return r; -		} -		 -		typeof(this) opSlice() -		{ -			return save(); -		} -		 -		typeof(this) opSlice(size_t start, size_t end) -		{ -			auto r = save(); -			r.frontIndex = this.frontIndex + start; -			r.endIndex   = this.frontIndex + end; -			 -			if( -				r.frontIndex > this.endIndex || -				r.endIndex > this.endIndex || -				r.frontIndex > r.endIndex -			) -				throw new DOMRangeException(tag, "Slice out of range"); -			 -			return r; -		} - -		T opIndex(size_t index) -		{ -			if(empty) -				throw new DOMRangeException(tag, "Range is empty"); - -			return mixin("tag."~membersGrouped~"[namespace][name][frontIndex+index]"); -		} -	} - -	struct MemberRange(T, string allMembers, string memberIndicies, string membersGrouped) -	{ -		private Tag tag; -		private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name) -		private bool isMaybe; -		private size_t updateId;  // Tag's updateId when this range was created. -		private size_t initialEndIndex; - -		this(Tag tag, string namespace, bool isMaybe) -		{ -			this.tag       = tag; -			this.namespace = namespace; -			this.updateId  = tag.updateId; -			this.isMaybe   = isMaybe; -			frontIndex = 0; - -			if(tag is null) -				endIndex = 0; -			else -			{ - -				if(namespace == "*") -					initialEndIndex = mixin("tag."~allMembers~".length"); -				else if(namespace in mixin("tag."~memberIndicies)) -					initialEndIndex = mixin("tag."~memberIndicies~"[namespace].length"); -				else -					initialEndIndex = 0; -			 -				endIndex = initialEndIndex; -			} -		} -		 -		invariant() -		{ -			assert( -				this.updateId == tag.updateId, -				"This range has been invalidated by a change to the tag." -			); -		} - -		@property bool empty() -		{ -			return tag is null || frontIndex == endIndex; -		} -		 -		private size_t frontIndex; -		@property T front() -		{ -			return this[0]; -		} -		void popFront() -		{ -			if(empty) -				throw new DOMRangeException(tag, "Range is empty"); - -			frontIndex++; -		} - -		private size_t endIndex; // One past the last element -		@property T back() -		{ -			return this[$-1]; -		} -		void popBack() -		{ -			if(empty) -				throw new DOMRangeException(tag, "Range is empty"); - -			endIndex--; -		} -		 -		alias length opDollar; -		@property size_t length() -		{ -			return endIndex - frontIndex; -		} -		 -		@property typeof(this) save() -		{ -			auto r = typeof(this)(this.tag, this.namespace, this.isMaybe); -			r.frontIndex      = this.frontIndex; -			r.endIndex        = this.endIndex; -			r.initialEndIndex = this.initialEndIndex; -			r.updateId        = this.updateId; -			return r; -		} -		 -		typeof(this) opSlice() -		{ -			return save(); -		} -		 -		typeof(this) opSlice(size_t start, size_t end) -		{ -			auto r = save(); -			r.frontIndex = this.frontIndex + start; -			r.endIndex   = this.frontIndex + end; -			 -			if( -				r.frontIndex > this.endIndex || -				r.endIndex > this.endIndex || -				r.frontIndex > r.endIndex -			) -				throw new DOMRangeException(tag, "Slice out of range"); -			 -			return r; -		} -		 -		T opIndex(size_t index) -		{ -			if(empty) -				throw new DOMRangeException(tag, "Range is empty"); - -			if(namespace == "*") -				return mixin("tag."~allMembers~"[ frontIndex+index ]"); -			else -				return mixin("tag."~allMembers~"[ tag."~memberIndicies~"[namespace][frontIndex+index] ]"); -		} -		 -		alias NamedMemberRange!(T,membersGrouped) ThisNamedMemberRange; -		ThisNamedMemberRange opIndex(string name) -		{ -			if(frontIndex != 0 || endIndex != initialEndIndex) -			{ -				throw new DOMRangeException(tag, -					"Cannot lookup tags/attributes by name on a subset of a range, "~ -					"only across the entire tag. "~ -					"Please make sure you haven't called popFront or popBack on this "~ -					"range and that you aren't using a slice of the range." -				); -			} -			 -			if(!isMaybe && empty) -				throw new DOMRangeException(tag, "Range is empty"); -			 -			if(!isMaybe && name !in this) -				throw new DOMRangeException(tag, `No such `~T.stringof~` named: "`~name~`"`); - -			return ThisNamedMemberRange(tag, namespace, name, updateId); -		} - -		bool opBinaryRight(string op)(string name) if(op=="in") -		{ -			if(frontIndex != 0 || endIndex != initialEndIndex) -			{ -				throw new DOMRangeException(tag, -					"Cannot lookup tags/attributes by name on a subset of a range, "~ -					"only across the entire tag. "~ -					"Please make sure you haven't called popFront or popBack on this "~ -					"range and that you aren't using a slice of the range." -				); -			} -			 -			if(tag is null) -				return false; -			 -			return -				namespace in mixin("tag."~membersGrouped) && -				name in mixin("tag."~membersGrouped~"[namespace]") &&  -				mixin("tag."~membersGrouped~"[namespace][name].length") > 0; -		} -	} - -	struct NamespaceRange -	{ -		private Tag tag; -		private bool isMaybe; -		private size_t updateId;  // Tag's updateId when this range was created. - -		this(Tag tag, bool isMaybe) -		{ -			this.tag      = tag; -			this.isMaybe  = isMaybe; -			this.updateId = tag.updateId; -			frontIndex = 0; -			endIndex = tag.allNamespaces.length; -		} - -		invariant() -		{ -			assert( -				this.updateId == tag.updateId, -				"This range has been invalidated by a change to the tag." -			); -		} -		 -		@property bool empty() -		{ -			return frontIndex == endIndex; -		} -		 -		private size_t frontIndex; -		@property NamespaceAccess front() -		{ -			return this[0]; -		} -		void popFront() -		{ -			if(empty) -				throw new DOMRangeException(tag, "Range is empty"); -			 -			frontIndex++; -		} - -		private size_t endIndex; // One past the last element -		@property NamespaceAccess back() -		{ -			return this[$-1]; -		} -		void popBack() -		{ -			if(empty) -				throw new DOMRangeException(tag, "Range is empty"); -			 -			endIndex--; -		} -		 -		alias length opDollar; -		@property size_t length() -		{ -			return endIndex - frontIndex; -		} -		 -		@property NamespaceRange save() -		{ -			auto r = NamespaceRange(this.tag, this.isMaybe); -			r.frontIndex = this.frontIndex; -			r.endIndex   = this.endIndex; -			r.updateId   = this.updateId; -			return r; -		} -		 -		typeof(this) opSlice() -		{ -			return save(); -		} -		 -		typeof(this) opSlice(size_t start, size_t end) -		{ -			auto r = save(); -			r.frontIndex = this.frontIndex + start; -			r.endIndex   = this.frontIndex + end; -			 -			if( -				r.frontIndex > this.endIndex || -				r.endIndex > this.endIndex || -				r.frontIndex > r.endIndex -			) -				throw new DOMRangeException(tag, "Slice out of range"); -			 -			return r; -		} -		 -		NamespaceAccess opIndex(size_t index) -		{ -			if(empty) -				throw new DOMRangeException(tag, "Range is empty"); - -			auto namespace = tag.allNamespaces[frontIndex+index]; -			return NamespaceAccess( -				namespace, -				AttributeRange(tag, namespace, isMaybe), -				TagRange(tag, namespace, isMaybe) -			); -		} -		 -		NamespaceAccess opIndex(string namespace) -		{ -			if(!isMaybe && empty) -				throw new DOMRangeException(tag, "Range is empty"); -			 -			if(!isMaybe && namespace !in this) -				throw new DOMRangeException(tag, `No such namespace: "`~namespace~`"`); -			 -			return NamespaceAccess( -				namespace, -				AttributeRange(tag, namespace, isMaybe), -				TagRange(tag, namespace, isMaybe) -			); -		} -		 -		/// Inefficient when range is a slice or has used popFront/popBack, but it works. -		bool opBinaryRight(string op)(string namespace) if(op=="in") -		{ -			if(frontIndex == 0 && endIndex == tag.allNamespaces.length) -			{ -				return -					namespace in tag.attributeIndicies || -					namespace in tag.tagIndicies; -			} -			else -				// Slower fallback method -				return tag.allNamespaces[frontIndex..endIndex].canFind(namespace); -		} -	} - -	static struct NamespaceAccess -	{ -		string name; -		AttributeRange attributes; -		TagRange tags; -	} - -	alias MemberRange!(Attribute, "allAttributes", "attributeIndicies", "_attributes") AttributeRange; -	alias MemberRange!(Tag,       "allTags",       "tagIndicies",       "_tags"      ) TagRange; -	static assert(isRandomAccessRange!AttributeRange); -	static assert(isRandomAccessRange!TagRange); -	static assert(isRandomAccessRange!NamespaceRange); - -	/++ -	Access all attributes that don't have a namespace - -	Returns a random access range of `Attribute` objects that supports -	numeric-indexing, string-indexing, slicing and length. -	 -	Since SDLang allows multiple attributes with the same name, -	string-indexing returns a random access range of all attributes -	with the given name. -	 -	The string-indexing does $(B $(I not)) support namespace prefixes. -	Use `namespace[string]`.`attributes` or `all`.`attributes` for that. -	 -	See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) -	for a high-level overview (and examples) of how to use this. -	+/ -	@property AttributeRange attributes() -	{ -		return AttributeRange(this, "", false); -	} - -	/++ -	Access all direct-child tags that don't have a namespace. -	 -	Returns a random access range of `Tag` objects that supports -	numeric-indexing, string-indexing, slicing and length. -	 -	Since SDLang allows multiple tags with the same name, string-indexing -	returns a random access range of all immediate child tags with the -	given name. -	 -	The string-indexing does $(B $(I not)) support namespace prefixes. -	Use `namespace[string]`.`attributes` or `all`.`attributes` for that. -	 -	See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) -	for a high-level overview (and examples) of how to use this. -	+/ -	@property TagRange tags() -	{ -		return TagRange(this, "", false); -	} -	 -	/++ -	Access all namespaces in this tag, and the attributes/tags within them. -	 -	Returns a random access range of `NamespaceAccess` elements that supports -	numeric-indexing, string-indexing, slicing and length. -	 -	See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) -	for a high-level overview (and examples) of how to use this. -	+/ -	@property NamespaceRange namespaces() -	{ -		return NamespaceRange(this, false); -	} - -	/// Access all attributes and tags regardless of namespace. -	/// -	/// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) -	/// for a better understanding (and examples) of how to use this. -	@property NamespaceAccess all() -	{ -		// "*" isn't a valid namespace name, so we can use it to indicate "all namespaces" -		return NamespaceAccess( -			"*", -			AttributeRange(this, "*", false), -			TagRange(this, "*", false) -		); -	} - -	struct MaybeAccess -	{ -		Tag tag; - -		/// Access all attributes that don't have a namespace -		@property AttributeRange attributes() -		{ -			return AttributeRange(tag, "", true); -		} - -		/// Access all direct-child tags that don't have a namespace -		@property TagRange tags() -		{ -			return TagRange(tag, "", true); -		} -		 -		/// Access all namespaces in this tag, and the attributes/tags within them. -		@property NamespaceRange namespaces() -		{ -			return NamespaceRange(tag, true); -		} - -		/// Access all attributes and tags regardless of namespace. -		@property NamespaceAccess all() -		{ -			// "*" isn't a valid namespace name, so we can use it to indicate "all namespaces" -			return NamespaceAccess( -				"*", -				AttributeRange(tag, "*", true), -				TagRange(tag, "*", true) -			); -		} -	} -	 -	/// Access `attributes`, `tags`, `namespaces` and `all` like normal, -	/// except that looking up a non-existant name/namespace with -	/// opIndex(string) results in an empty array instead of -	/// a thrown `sdlang.exception.DOMRangeException`. -	/// -	/// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) -	/// for a more information (and examples) of how to use this. -	@property MaybeAccess maybe() -	{ -		return MaybeAccess(this); -	} -	 -	// Internal implementations for the get/expect functions further below: -	 -	private Tag getTagImpl(FullName tagFullName, Tag defaultValue=null, bool useDefaultValue=true) -	{ -		auto tagNS   = tagFullName.namespace; -		auto tagName = tagFullName.name; -		 -		// Can find namespace? -		if(tagNS !in _tags) -		{ -			if(useDefaultValue) -				return defaultValue; -			else -				throw new TagNotFoundException(this, tagFullName, "No tags found in namespace '"~namespace~"'"); -		} - -		// Can find tag in namespace? -		if(tagName !in _tags[tagNS] || _tags[tagNS][tagName].length == 0) -		{ -			if(useDefaultValue) -				return defaultValue; -			else -				throw new TagNotFoundException(this, tagFullName, "Can't find tag '"~tagFullName.toString()~"'"); -		} - -		// Return last matching tag found -		return _tags[tagNS][tagName][$-1]; -	} - -	private T getValueImpl(T)(T defaultValue, bool useDefaultValue=true) -	if(isValueType!T) -	{ -		// Find value -		foreach(value; this.values) -		{ -			if(value.type == typeid(T)) -				return value.get!T(); -		} -		 -		// No value of type T found -		if(useDefaultValue) -			return defaultValue; -		else -		{ -			throw new ValueNotFoundException( -				this, -				FullName(this.namespace, this.name), -				typeid(T), -				"No value of type "~T.stringof~" found." -			); -		} -	} - -	private T getAttributeImpl(T)(FullName attrFullName, T defaultValue, bool useDefaultValue=true) -	if(isValueType!T) -	{ -		auto attrNS   = attrFullName.namespace; -		auto attrName = attrFullName.name; -		 -		// Can find namespace and attribute name? -		if(attrNS !in this._attributes || attrName !in this._attributes[attrNS]) -		{ -			if(useDefaultValue) -				return defaultValue; -			else -			{ -				throw new AttributeNotFoundException( -					this, this.getFullName(), attrFullName, typeid(T), -					"Can't find attribute '"~FullName.combine(attrNS, attrName)~"'" -				); -			} -		} - -		// Find value with chosen type -		foreach(attr; this._attributes[attrNS][attrName]) -		{ -			if(attr.value.type == typeid(T)) -				return attr.value.get!T(); -		} -		 -		// Chosen type not found -		if(useDefaultValue) -			return defaultValue; -		else -		{ -			throw new AttributeNotFoundException( -				this, this.getFullName(), attrFullName, typeid(T), -				"Can't find attribute '"~FullName.combine(attrNS, attrName)~"' of type "~T.stringof -			); -		} -	} - -	// High-level interfaces for get/expect funtions: -	 -	/++ -	Lookup a child tag by name. Returns null if not found. -	 -	Useful if you only expect one, and only one, child tag of a given name. -	Only looks for immediate child tags of `this`, doesn't search recursively. -	 -	If you expect multiple tags by the same name and want to get them all, -	use `maybe`.`tags[string]` instead. -	 -	The name can optionally include a namespace, as in `"namespace:name"`. -	Or, you can search all namespaces using `"*:name"`. Use an empty string -	to search for anonymous tags, or `"namespace:"` for anonymous tags inside -	a namespace. Wildcard searching is only supported for namespaces, not names. -	Use `maybe`.`tags[0]` if you don't care about the name. -	 -	If there are multiple tags by the chosen name, the $(B $(I last tag)) will -	always be chosen. That is, this function considers later tags with the -	same name to override previous ones. -	 -	If the tag cannot be found, and you provides a default value, the default -	value is returned. Otherwise null is returned. If you'd prefer an -	exception thrown, use `expectTag` instead. -	+/ -	Tag getTag(string fullTagName, Tag defaultValue=null) -	{ -		auto parsedName = FullName.parse(fullTagName); -		parsedName.ensureNoWildcardName( -			"Instead, use 'Tag.maybe.tags[0]', 'Tag.maybe.all.tags[0]' or 'Tag.maybe.namespace[ns].tags[0]'." -		); -		return getTagImpl(parsedName, defaultValue); -	} -	 -	/// -	@("Tag.getTag") -	unittest -	{ -		import std.exception; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo 1 -			foo 2  // getTag considers this to override the first foo - -			ns1:foo 3 -			ns1:foo 4   // getTag considers this to override the first ns1:foo -			ns2:foo 33 -			ns2:foo 44  // getTag considers this to override the first ns2:foo -		`); -		assert( root.getTag("foo"    ).values[0].get!int() == 2  ); -		assert( root.getTag("ns1:foo").values[0].get!int() == 4  ); -		assert( root.getTag("*:foo"  ).values[0].get!int() == 44 ); // Search all namespaces -		 -		// Not found -		// If you'd prefer an exception, use `expectTag` instead. -		assert( root.getTag("doesnt-exist") is null ); - -		// Default value -		auto foo = root.getTag("foo"); -		assert( root.getTag("doesnt-exist", foo) is foo ); -	} -	 -	/++ -	Lookup a child tag by name. Throws if not found. -	 -	Useful if you only expect one, and only one, child tag of a given name. -	Only looks for immediate child tags of `this`, doesn't search recursively. -	 -	If you expect multiple tags by the same name and want to get them all, -	use `tags[string]` instead. - -	The name can optionally include a namespace, as in `"namespace:name"`. -	Or, you can search all namespaces using `"*:name"`. Use an empty string -	to search for anonymous tags, or `"namespace:"` for anonymous tags inside -	a namespace. Wildcard searching is only supported for namespaces, not names. -	Use `tags[0]` if you don't care about the name. -	 -	If there are multiple tags by the chosen name, the $(B $(I last tag)) will -	always be chosen. That is, this function considers later tags with the -	same name to override previous ones. -	 -	If no such tag is found, an `sdlang.exception.TagNotFoundException` will -	be thrown. If you'd rather receive a default value, use `getTag` instead. -	+/ -	Tag expectTag(string fullTagName) -	{ -		auto parsedName = FullName.parse(fullTagName); -		parsedName.ensureNoWildcardName( -			"Instead, use 'Tag.tags[0]', 'Tag.all.tags[0]' or 'Tag.namespace[ns].tags[0]'." -		); -		return getTagImpl(parsedName, null, false); -	} -	 -	/// -	@("Tag.expectTag") -	unittest -	{ -		import std.exception; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo 1 -			foo 2  // expectTag considers this to override the first foo - -			ns1:foo 3 -			ns1:foo 4   // expectTag considers this to override the first ns1:foo -			ns2:foo 33 -			ns2:foo 44  // expectTag considers this to override the first ns2:foo -		`); -		assert( root.expectTag("foo"    ).values[0].get!int() == 2  ); -		assert( root.expectTag("ns1:foo").values[0].get!int() == 4  ); -		assert( root.expectTag("*:foo"  ).values[0].get!int() == 44 ); // Search all namespaces -		 -		// Not found -		// If you'd rather receive a default value than an exception, use `getTag` instead. -		assertThrown!TagNotFoundException( root.expectTag("doesnt-exist") ); -	} -	 -	/++ -	Retrieve a value of type T from `this` tag. Returns a default value if not found. -	 -	Useful if you only expect one value of type T from this tag. Only looks for -	values of `this` tag, it does not search child tags. If you wish to search -	for a value in a child tag (for example, if this current tag is a root tag), -	try `getTagValue`. - -	If you want to get more than one value from this tag, use `values` instead. - -	If this tag has multiple values, the $(B $(I first)) value matching the -	requested type will be returned. Ie, Extra values in the tag are ignored. -	 -	You may provide a default value to be returned in case no value of -	the requested type can be found. If you don't provide a default value, -	`T.init` will be used. -	 -	If you'd rather an exception be thrown when a value cannot be found, -	use `expectValue` instead. -	+/ -	T getValue(T)(T defaultValue = T.init) if(isValueType!T) -	{ -		return getValueImpl!T(defaultValue, true); -	} - -	/// -	@("Tag.getValue") -	unittest -	{ -		import std.exception; -		import std.math; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo 1 true 2 false -		`); -		auto foo = root.getTag("foo"); -		assert( foo.getValue!int() == 1 ); -		assert( foo.getValue!bool() == true ); - -		// Value found, default value ignored. -		assert( foo.getValue!int(999) == 1 ); - -		// No strings found -		// If you'd prefer an exception, use `expectValue` instead. -		assert( foo.getValue!string("Default") == "Default" ); -		assert( foo.getValue!string() is null ); - -		// No floats found -		assert( foo.getValue!float(99.9).approxEqual(99.9) ); -		assert( foo.getValue!float().isNaN() ); -	} - -	/++ -	Retrieve a value of type T from `this` tag. Throws if not found. -	 -	Useful if you only expect one value of type T from this tag. Only looks -	for values of `this` tag, it does not search child tags. If you wish to -	search for a value in a child tag (for example, if this current tag is a -	root tag), try `expectTagValue`. - -	If you want to get more than one value from this tag, use `values` instead. - -	If this tag has multiple values, the $(B $(I first)) value matching the -	requested type will be returned. Ie, Extra values in the tag are ignored. -	 -	An `sdlang.exception.ValueNotFoundException` will be thrown if no value of -	the requested type can be found. If you'd rather receive a default value, -	use `getValue` instead. -	+/ -	T expectValue(T)() if(isValueType!T) -	{ -		return getValueImpl!T(T.init, false); -	} - -	/// -	@("Tag.expectValue") -	unittest -	{ -		import std.exception; -		import std.math; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo 1 true 2 false -		`); -		auto foo = root.getTag("foo"); -		assert( foo.expectValue!int() == 1 ); -		assert( foo.expectValue!bool() == true ); - -		// No strings or floats found -		// If you'd rather receive a default value than an exception, use `getValue` instead. -		assertThrown!ValueNotFoundException( foo.expectValue!string() ); -		assertThrown!ValueNotFoundException( foo.expectValue!float() ); -	} - -	/++ -	Lookup a child tag by name, and retrieve a value of type T from it. -	Returns a default value if not found. -	 -	Useful if you only expect one value of type T from a given tag. Only looks -	for immediate child tags of `this`, doesn't search recursively. - -	This is a shortcut for `getTag().getValue()`, except if the tag isn't found, -	then instead of a null reference error, it will return the requested -	`defaultValue` (or T.init by default). -	+/ -	T getTagValue(T)(string fullTagName, T defaultValue = T.init) if(isValueType!T) -	{ -		auto tag = getTag(fullTagName); -		if(!tag) -			return defaultValue; -		 -		return tag.getValue!T(defaultValue); -	} - -	/// -	@("Tag.getTagValue") -	unittest -	{ -		import std.exception; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo 1 "a" 2 "b" -			foo 3 "c" 4 "d"  // getTagValue considers this to override the first foo -			 -			bar "hi" -			bar 379  // getTagValue considers this to override the first bar -		`); -		assert( root.getTagValue!int("foo") == 3 ); -		assert( root.getTagValue!string("foo") == "c" ); - -		// Value found, default value ignored. -		assert( root.getTagValue!int("foo", 999) == 3 ); - -		// Tag not found -		// If you'd prefer an exception, use `expectTagValue` instead. -		assert( root.getTagValue!int("doesnt-exist", 999) == 999 ); -		assert( root.getTagValue!int("doesnt-exist") == 0 ); -		 -		// The last "bar" tag doesn't have an int (only the first "bar" tag does) -		assert( root.getTagValue!string("bar", "Default") == "Default" ); -		assert( root.getTagValue!string("bar") is null ); - -		// Using namespaces: -		root = parseSource(` -			ns1:foo 1 "a" 2 "b" -			ns1:foo 3 "c" 4 "d" -			ns2:foo 11 "aa" 22 "bb" -			ns2:foo 33 "cc" 44 "dd" -			 -			ns1:bar "hi" -			ns1:bar 379  // getTagValue considers this to override the first bar -		`); -		assert( root.getTagValue!int("ns1:foo") == 3  ); -		assert( root.getTagValue!int("*:foo"  ) == 33 ); // Search all namespaces - -		assert( root.getTagValue!string("ns1:foo") == "c"  ); -		assert( root.getTagValue!string("*:foo"  ) == "cc" ); // Search all namespaces -		 -		// The last "bar" tag doesn't have a string (only the first "bar" tag does) -		assert( root.getTagValue!string("*:bar", "Default") == "Default" ); -		assert( root.getTagValue!string("*:bar") is null ); -	} - -	/++ -	Lookup a child tag by name, and retrieve a value of type T from it. -	Throws if not found, -	 -	Useful if you only expect one value of type T from a given tag. Only -	looks for immediate child tags of `this`, doesn't search recursively. -	 -	This is a shortcut for `expectTag().expectValue()`. -	+/ -	T expectTagValue(T)(string fullTagName) if(isValueType!T) -	{ -		return expectTag(fullTagName).expectValue!T(); -	} - -	/// -	@("Tag.expectTagValue") -	unittest -	{ -		import std.exception; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo 1 "a" 2 "b" -			foo 3 "c" 4 "d"  // expectTagValue considers this to override the first foo -			 -			bar "hi" -			bar 379  // expectTagValue considers this to override the first bar -		`); -		assert( root.expectTagValue!int("foo") == 3 ); -		assert( root.expectTagValue!string("foo") == "c" ); -		 -		// The last "bar" tag doesn't have a string (only the first "bar" tag does) -		// If you'd rather receive a default value than an exception, use `getTagValue` instead. -		assertThrown!ValueNotFoundException( root.expectTagValue!string("bar") ); - -		// Tag not found -		assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist") ); - -		// Using namespaces: -		root = parseSource(` -			ns1:foo 1 "a" 2 "b" -			ns1:foo 3 "c" 4 "d" -			ns2:foo 11 "aa" 22 "bb" -			ns2:foo 33 "cc" 44 "dd" -			 -			ns1:bar "hi" -			ns1:bar 379  // expectTagValue considers this to override the first bar -		`); -		assert( root.expectTagValue!int("ns1:foo") == 3  ); -		assert( root.expectTagValue!int("*:foo"  ) == 33 ); // Search all namespaces - -		assert( root.expectTagValue!string("ns1:foo") == "c"  ); -		assert( root.expectTagValue!string("*:foo"  ) == "cc" ); // Search all namespaces -		 -		// The last "bar" tag doesn't have a string (only the first "bar" tag does) -		assertThrown!ValueNotFoundException( root.expectTagValue!string("*:bar") ); -		 -		// Namespace not found -		assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist:bar") ); -	} - -	/++ -	Lookup an attribute of `this` tag by name, and retrieve a value of type T -	from it. Returns a default value if not found. -	 -	Useful if you only expect one attribute of the given name and type. -	 -	Only looks for attributes of `this` tag, it does not search child tags. -	If you wish to search for a value in a child tag (for example, if this -	current tag is a root tag), try `getTagAttribute`. -	 -	If you expect multiple attributes by the same name and want to get them all, -	use `maybe`.`attributes[string]` instead. - -	The attribute name can optionally include a namespace, as in -	`"namespace:name"`. Or, you can search all namespaces using `"*:name"`. -	(Note that unlike tags. attributes can't be anonymous - that's what -	values are.) Wildcard searching is only supported for namespaces, not names. -	Use `maybe`.`attributes[0]` if you don't care about the name. - -	If this tag has multiple attributes, the $(B $(I first)) attribute -	matching the requested name and type will be returned. Ie, Extra -	attributes in the tag are ignored. -	 -	You may provide a default value to be returned in case no attribute of -	the requested name and type can be found. If you don't provide a default -	value, `T.init` will be used. -	 -	If you'd rather an exception be thrown when an attribute cannot be found, -	use `expectAttribute` instead. -	+/ -	T getAttribute(T)(string fullAttributeName, T defaultValue = T.init) if(isValueType!T) -	{ -		auto parsedName = FullName.parse(fullAttributeName); -		parsedName.ensureNoWildcardName( -			"Instead, use 'Attribute.maybe.tags[0]', 'Attribute.maybe.all.tags[0]' or 'Attribute.maybe.namespace[ns].tags[0]'." -		); -		return getAttributeImpl!T(parsedName, defaultValue); -	} -	 -	/// -	@("Tag.getAttribute") -	unittest -	{ -		import std.exception; -		import std.math; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo z=0 X=1 X=true X=2 X=false -		`); -		auto foo = root.getTag("foo"); -		assert( foo.getAttribute!int("X") == 1 ); -		assert( foo.getAttribute!bool("X") == true ); - -		// Value found, default value ignored. -		assert( foo.getAttribute!int("X", 999) == 1 ); - -		// Attribute name not found -		// If you'd prefer an exception, use `expectValue` instead. -		assert( foo.getAttribute!int("doesnt-exist", 999) == 999 ); -		assert( foo.getAttribute!int("doesnt-exist") == 0 ); - -		// No strings found -		assert( foo.getAttribute!string("X", "Default") == "Default" ); -		assert( foo.getAttribute!string("X") is null ); - -		// No floats found -		assert( foo.getAttribute!float("X", 99.9).approxEqual(99.9) ); -		assert( foo.getAttribute!float("X").isNaN() ); - -		 -		// Using namespaces: -		root = parseSource(` -			foo  ns1:z=0  ns1:X=1  ns1:X=2  ns2:X=3  ns2:X=4 -		`); -		foo = root.getTag("foo"); -		assert( foo.getAttribute!int("ns2:X") == 3 ); -		assert( foo.getAttribute!int("*:X") == 1 ); // Search all namespaces -		 -		// Namespace not found -		assert( foo.getAttribute!int("doesnt-exist:X", 999) == 999 ); -		 -		// No attribute X is in the default namespace -		assert( foo.getAttribute!int("X", 999) == 999 ); -		 -		// Attribute name not found -		assert( foo.getAttribute!int("ns1:doesnt-exist", 999) == 999 ); -	} -	 -	/++ -	Lookup an attribute of `this` tag by name, and retrieve a value of type T -	from it. Throws if not found. -	 -	Useful if you only expect one attribute of the given name and type. -	 -	Only looks for attributes of `this` tag, it does not search child tags. -	If you wish to search for a value in a child tag (for example, if this -	current tag is a root tag), try `expectTagAttribute`. - -	If you expect multiple attributes by the same name and want to get them all, -	use `attributes[string]` instead. - -	The attribute name can optionally include a namespace, as in -	`"namespace:name"`. Or, you can search all namespaces using `"*:name"`. -	(Note that unlike tags. attributes can't be anonymous - that's what -	values are.) Wildcard searching is only supported for namespaces, not names. -	Use `attributes[0]` if you don't care about the name. - -	If this tag has multiple attributes, the $(B $(I first)) attribute -	matching the requested name and type will be returned. Ie, Extra -	attributes in the tag are ignored. -	 -	An `sdlang.exception.AttributeNotFoundException` will be thrown if no -	value of the requested type can be found. If you'd rather receive a -	default value, use `getAttribute` instead. -	+/ -	T expectAttribute(T)(string fullAttributeName) if(isValueType!T) -	{ -		auto parsedName = FullName.parse(fullAttributeName); -		parsedName.ensureNoWildcardName( -			"Instead, use 'Attribute.tags[0]', 'Attribute.all.tags[0]' or 'Attribute.namespace[ns].tags[0]'." -		); -		return getAttributeImpl!T(parsedName, T.init, false); -	} -	 -	/// -	@("Tag.expectAttribute") -	unittest -	{ -		import std.exception; -		import std.math; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo z=0 X=1 X=true X=2 X=false -		`); -		auto foo = root.getTag("foo"); -		assert( foo.expectAttribute!int("X") == 1 ); -		assert( foo.expectAttribute!bool("X") == true ); - -		// Attribute name not found -		// If you'd rather receive a default value than an exception, use `getAttribute` instead. -		assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist") ); - -		// No strings found -		assertThrown!AttributeNotFoundException( foo.expectAttribute!string("X") ); - -		// No floats found -		assertThrown!AttributeNotFoundException( foo.expectAttribute!float("X") ); - -		 -		// Using namespaces: -		root = parseSource(` -			foo  ns1:z=0  ns1:X=1  ns1:X=2  ns2:X=3  ns2:X=4 -		`); -		foo = root.getTag("foo"); -		assert( foo.expectAttribute!int("ns2:X") == 3 ); -		assert( foo.expectAttribute!int("*:X") == 1 ); // Search all namespaces -		 -		// Namespace not found -		assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist:X") ); -		 -		// No attribute X is in the default namespace -		assertThrown!AttributeNotFoundException( foo.expectAttribute!int("X") ); -		 -		// Attribute name not found -		assertThrown!AttributeNotFoundException( foo.expectAttribute!int("ns1:doesnt-exist") ); -	} - -	/++ -	Lookup a child tag and attribute by name, and retrieve a value of type T -	from it. Returns a default value if not found. -	 -	Useful if you only expect one attribute of type T from given -	the tag and attribute names. Only looks for immediate child tags of -	`this`, doesn't search recursively. - -	This is a shortcut for `getTag().getAttribute()`, except if the tag isn't -	found, then instead of a null reference error, it will return the requested -	`defaultValue` (or T.init by default). -	+/ -	T getTagAttribute(T)(string fullTagName, string fullAttributeName, T defaultValue = T.init) if(isValueType!T) -	{ -		auto tag = getTag(fullTagName); -		if(!tag) -			return defaultValue; -		 -		return tag.getAttribute!T(fullAttributeName, defaultValue); -	} -	 -	/// -	@("Tag.getTagAttribute") -	unittest -	{ -		import std.exception; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo X=1 X="a" X=2 X="b" -			foo X=3 X="c" X=4 X="d"  // getTagAttribute considers this to override the first foo -			 -			bar X="hi" -			bar X=379  // getTagAttribute considers this to override the first bar -		`); -		assert( root.getTagAttribute!int("foo", "X") == 3 ); -		assert( root.getTagAttribute!string("foo", "X") == "c" ); - -		// Value found, default value ignored. -		assert( root.getTagAttribute!int("foo", "X", 999) == 3 ); - -		// Tag not found -		// If you'd prefer an exception, use `expectTagAttribute` instead of `getTagAttribute` -		assert( root.getTagAttribute!int("doesnt-exist", "X", 999)   == 999 ); -		assert( root.getTagAttribute!int("doesnt-exist", "X")        == 0   ); -		assert( root.getTagAttribute!int("foo", "doesnt-exist", 999) == 999 ); -		assert( root.getTagAttribute!int("foo", "doesnt-exist")      == 0   ); -		 -		// The last "bar" tag doesn't have a string (only the first "bar" tag does) -		assert( root.getTagAttribute!string("bar", "X", "Default") == "Default" ); -		assert( root.getTagAttribute!string("bar", "X") is null ); -		 - -		// Using namespaces: -		root = parseSource(` -			ns1:foo X=1 X="a" X=2 X="b" -			ns1:foo X=3 X="c" X=4 X="d" -			ns2:foo X=11 X="aa" X=22 X="bb" -			ns2:foo X=33 X="cc" X=44 X="dd" -			 -			ns1:bar attrNS:X="hi" -			ns1:bar attrNS:X=379  // getTagAttribute considers this to override the first bar -		`); -		assert( root.getTagAttribute!int("ns1:foo", "X") == 3  ); -		assert( root.getTagAttribute!int("*:foo",   "X") == 33 ); // Search all namespaces - -		assert( root.getTagAttribute!string("ns1:foo", "X") == "c"  ); -		assert( root.getTagAttribute!string("*:foo",   "X") == "cc" ); // Search all namespaces -		 -		// bar's attribute X is't in the default namespace -		assert( root.getTagAttribute!int("*:bar", "X", 999) == 999 ); -		assert( root.getTagAttribute!int("*:bar", "X") == 0 ); - -		// The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does) -		assert( root.getTagAttribute!string("*:bar", "attrNS:X", "Default") == "Default" ); -		assert( root.getTagAttribute!string("*:bar", "attrNS:X") is null); -	} - -	/++ -	Lookup a child tag and attribute by name, and retrieve a value of type T -	from it. Throws if not found. -	 -	Useful if you only expect one attribute of type T from given -	the tag and attribute names. Only looks for immediate child tags of -	`this`, doesn't search recursively. - -	This is a shortcut for `expectTag().expectAttribute()`. -	+/ -	T expectTagAttribute(T)(string fullTagName, string fullAttributeName) if(isValueType!T) -	{ -		return expectTag(fullTagName).expectAttribute!T(fullAttributeName); -	} -	 -	/// -	@("Tag.expectTagAttribute") -	unittest -	{ -		import std.exception; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo X=1 X="a" X=2 X="b" -			foo X=3 X="c" X=4 X="d"  // expectTagAttribute considers this to override the first foo -			 -			bar X="hi" -			bar X=379  // expectTagAttribute considers this to override the first bar -		`); -		assert( root.expectTagAttribute!int("foo", "X") == 3 ); -		assert( root.expectTagAttribute!string("foo", "X") == "c" ); -		 -		// The last "bar" tag doesn't have an int attribute named "X" (only the first "bar" tag does) -		// If you'd rather receive a default value than an exception, use `getAttribute` instead. -		assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("bar", "X") ); -		 -		// Tag not found -		assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist", "X") ); - -		// Using namespaces: -		root = parseSource(` -			ns1:foo X=1 X="a" X=2 X="b" -			ns1:foo X=3 X="c" X=4 X="d" -			ns2:foo X=11 X="aa" X=22 X="bb" -			ns2:foo X=33 X="cc" X=44 X="dd" -			 -			ns1:bar attrNS:X="hi" -			ns1:bar attrNS:X=379  // expectTagAttribute considers this to override the first bar -		`); -		assert( root.expectTagAttribute!int("ns1:foo", "X") == 3  ); -		assert( root.expectTagAttribute!int("*:foo",   "X") == 33 ); // Search all namespaces -		 -		assert( root.expectTagAttribute!string("ns1:foo", "X") == "c"  ); -		assert( root.expectTagAttribute!string("*:foo",   "X") == "cc" ); // Search all namespaces -		 -		// bar's attribute X is't in the default namespace -		assertThrown!AttributeNotFoundException( root.expectTagAttribute!int("*:bar", "X") ); - -		// The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does) -		assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("*:bar", "attrNS:X") ); - -		// Tag's namespace not found -		assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist:bar", "attrNS:X") ); -	} - -	/++ -	Lookup a child tag by name, and retrieve all values from it. - -	This just like using `getTag()`.`values`, except if the tag isn't found, -	it safely returns null (or an optional array of default values) instead of -	a dereferencing null error. -	 -	Note that, unlike `getValue`, this doesn't discriminate by the value's -	type. It simply returns all values of a single tag as a `Value[]`. - -	If you'd prefer an exception thrown when the tag isn't found, use -	`expectTag`.`values` instead. -	+/ -	Value[] getTagValues(string fullTagName, Value[] defaultValues = null) -	{ -		auto tag = getTag(fullTagName); -		if(tag) -			return tag.values; -		else -			return defaultValues; -	} -	 -	/// -	@("getTagValues") -	unittest -	{ -		import std.exception; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo 1 "a" 2 "b" -			foo 3 "c" 4 "d"  // getTagValues considers this to override the first foo -		`); -		assert( root.getTagValues("foo") == [Value(3), Value("c"), Value(4), Value("d")] ); - -		// Tag not found -		// If you'd prefer an exception, use `expectTag.values` instead. -		assert( root.getTagValues("doesnt-exist") is null ); -		assert( root.getTagValues("doesnt-exist", [ Value(999), Value("Not found") ]) == -			[ Value(999), Value("Not found") ] ); -	} -	 -	/++ -	Lookup a child tag by name, and retrieve all attributes in a chosen -	(or default) namespace from it. - -	This just like using `getTag()`.`attributes` (or -	`getTag()`.`namespace[...]`.`attributes`, or `getTag()`.`all`.`attributes`), -	except if the tag isn't found, it safely returns an empty range instead -	of a dereferencing null error. -	 -	If provided, the `attributeNamespace` parameter can be either the name of -	a namespace, or an empty string for the default namespace (the default), -	or `"*"` to retreive attributes from all namespaces. -	 -	Note that, unlike `getAttributes`, this doesn't discriminate by the -	value's type. It simply returns the usual `attributes` range. - -	If you'd prefer an exception thrown when the tag isn't found, use -	`expectTag`.`attributes` instead. -	+/ -	auto getTagAttributes(string fullTagName, string attributeNamespace = null) -	{ -		auto tag = getTag(fullTagName); -		if(tag) -		{ -			if(attributeNamespace && attributeNamespace in tag.namespaces) -				return tag.namespaces[attributeNamespace].attributes; -			else if(attributeNamespace == "*") -				return tag.all.attributes; -			else -				return tag.attributes; -		} - -		return AttributeRange(null, null, false); -	} -	 -	/// -	@("getTagAttributes") -	unittest -	{ -		import std.exception; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo X=1 X=2 -			 -			// getTagAttributes considers this to override the first foo -			foo X1=3 X2="c" namespace:bar=7 X3=4 X4="d" -		`); - -		auto fooAttrs = root.getTagAttributes("foo"); -		assert( !fooAttrs.empty ); -		assert( fooAttrs.length == 4 ); -		assert( fooAttrs[0].name == "X1" && fooAttrs[0].value == Value(3)   ); -		assert( fooAttrs[1].name == "X2" && fooAttrs[1].value == Value("c") ); -		assert( fooAttrs[2].name == "X3" && fooAttrs[2].value == Value(4)   ); -		assert( fooAttrs[3].name == "X4" && fooAttrs[3].value == Value("d") ); - -		fooAttrs = root.getTagAttributes("foo", "namespace"); -		assert( !fooAttrs.empty ); -		assert( fooAttrs.length == 1 ); -		assert( fooAttrs[0].name == "bar" && fooAttrs[0].value == Value(7) ); - -		fooAttrs = root.getTagAttributes("foo", "*"); -		assert( !fooAttrs.empty ); -		assert( fooAttrs.length == 5 ); -		assert( fooAttrs[0].name == "X1"  && fooAttrs[0].value == Value(3)   ); -		assert( fooAttrs[1].name == "X2"  && fooAttrs[1].value == Value("c") ); -		assert( fooAttrs[2].name == "bar" && fooAttrs[2].value == Value(7)   ); -		assert( fooAttrs[3].name == "X3"  && fooAttrs[3].value == Value(4)   ); -		assert( fooAttrs[4].name == "X4"  && fooAttrs[4].value == Value("d") ); - -		// Tag not found -		// If you'd prefer an exception, use `expectTag.attributes` instead. -		assert( root.getTagValues("doesnt-exist").empty ); -	} - -	@("*: Disallow wildcards for names") -	unittest -	{ -		import std.exception; -		import std.math; -		import sdlang.parser; -		 -		auto root = parseSource(` -			foo 1 X=2 -			ns:foo 3 ns:X=4 -		`); -		auto foo = root.getTag("foo"); -		auto nsfoo = root.getTag("ns:foo"); - -		// Sanity check -		assert( foo !is null ); -		assert( foo.name == "foo" ); -		assert( foo.namespace == "" ); - -		assert( nsfoo !is null ); -		assert( nsfoo.name == "foo" ); -		assert( nsfoo.namespace == "ns" ); - -		assert( foo.getValue     !int() == 1 ); -		assert( foo.expectValue  !int() == 1 ); -		assert( nsfoo.getValue   !int() == 3 ); -		assert( nsfoo.expectValue!int() == 3 ); - -		assert( root.getTagValue   !int("foo")    == 1 ); -		assert( root.expectTagValue!int("foo")    == 1 ); -		assert( root.getTagValue   !int("ns:foo") == 3 ); -		assert( root.expectTagValue!int("ns:foo") == 3 ); - -		assert( foo.getAttribute     !int("X")    == 2 ); -		assert( foo.expectAttribute  !int("X")    == 2 ); -		assert( nsfoo.getAttribute   !int("ns:X") == 4 ); -		assert( nsfoo.expectAttribute!int("ns:X") == 4 ); - -		assert( root.getTagAttribute   !int("foo", "X")       == 2 ); -		assert( root.expectTagAttribute!int("foo", "X")       == 2 ); -		assert( root.getTagAttribute   !int("ns:foo", "ns:X") == 4 ); -		assert( root.expectTagAttribute!int("ns:foo", "ns:X") == 4 ); -		 -		// No namespace -		assertThrown!ArgumentException( root.getTag   ("*") ); -		assertThrown!ArgumentException( root.expectTag("*") ); -		 -		assertThrown!ArgumentException( root.getTagValue   !int("*") ); -		assertThrown!ArgumentException( root.expectTagValue!int("*") ); - -		assertThrown!ArgumentException( foo.getAttribute       !int("*")        ); -		assertThrown!ArgumentException( foo.expectAttribute    !int("*")        ); -		assertThrown!ArgumentException( root.getTagAttribute   !int("*", "X")   ); -		assertThrown!ArgumentException( root.expectTagAttribute!int("*", "X")   ); -		assertThrown!ArgumentException( root.getTagAttribute   !int("foo", "*") ); -		assertThrown!ArgumentException( root.expectTagAttribute!int("foo", "*") ); - -		// With namespace -		assertThrown!ArgumentException( root.getTag   ("ns:*") ); -		assertThrown!ArgumentException( root.expectTag("ns:*") ); -		 -		assertThrown!ArgumentException( root.getTagValue   !int("ns:*") ); -		assertThrown!ArgumentException( root.expectTagValue!int("ns:*") ); - -		assertThrown!ArgumentException( nsfoo.getAttribute     !int("ns:*")           ); -		assertThrown!ArgumentException( nsfoo.expectAttribute  !int("ns:*")           ); -		assertThrown!ArgumentException( root.getTagAttribute   !int("ns:*",   "ns:X") ); -		assertThrown!ArgumentException( root.expectTagAttribute!int("ns:*",   "ns:X") ); -		assertThrown!ArgumentException( root.getTagAttribute   !int("ns:foo", "ns:*") ); -		assertThrown!ArgumentException( root.expectTagAttribute!int("ns:foo", "ns:*") ); - -		// With wildcard namespace -		assertThrown!ArgumentException( root.getTag   ("*:*") ); -		assertThrown!ArgumentException( root.expectTag("*:*") ); -		 -		assertThrown!ArgumentException( root.getTagValue   !int("*:*") ); -		assertThrown!ArgumentException( root.expectTagValue!int("*:*") ); - -		assertThrown!ArgumentException( nsfoo.getAttribute     !int("*:*")          ); -		assertThrown!ArgumentException( nsfoo.expectAttribute  !int("*:*")          ); -		assertThrown!ArgumentException( root.getTagAttribute   !int("*:*",   "*:X") ); -		assertThrown!ArgumentException( root.expectTagAttribute!int("*:*",   "*:X") ); -		assertThrown!ArgumentException( root.getTagAttribute   !int("*:foo", "*:*") ); -		assertThrown!ArgumentException( root.expectTagAttribute!int("*:foo", "*:*") ); -	} -	 -	override bool opEquals(Object o) -	{ -		auto t = cast(Tag)o; -		if(!t) -			return false; -		 -		if(_namespace != t._namespace || _name != t._name) -			return false; - -		if( -			values        .length != t.values       .length || -			allAttributes .length != t.allAttributes.length || -			allNamespaces .length != t.allNamespaces.length || -			allTags       .length != t.allTags      .length -		) -			return false; -		 -		if(values != t.values) -			return false; - -		if(allNamespaces != t.allNamespaces) -			return false; - -		if(allAttributes != t.allAttributes) -			return false; -		 -		// Ok because cycles are not allowed -		//TODO: Actually check for or prevent cycles. -		return allTags == t.allTags; -	} -	 -	/// Treats `this` as the root tag. Note that root tags cannot have -	/// values or attributes, and cannot be part of a namespace. -	/// If this isn't a valid root tag, `sdlang.exception.ValidationException` -	/// will be thrown. -	string toSDLDocument()(string indent="\t", int indentLevel=0) -	{ -		Appender!string sink; -		toSDLDocument(sink, indent, indentLevel); -		return sink.data; -	} -	 -	///ditto -	void toSDLDocument(Sink)(ref Sink sink, string indent="\t", int indentLevel=0) -		if(isOutputRange!(Sink,char)) -	{ -		if(values.length > 0) -			throw new ValidationException("Root tags cannot have any values, only child tags."); - -		if(allAttributes.length > 0) -			throw new ValidationException("Root tags cannot have any attributes, only child tags."); - -		if(_namespace != "") -			throw new ValidationException("Root tags cannot have a namespace."); -		 -		foreach(tag; allTags) -			tag.toSDLString(sink, indent, indentLevel); -	} -	 -	/// Output this entire tag in SDL format. Does $(B $(I not)) treat `this` as -	/// a root tag. If you intend this to be the root of a standard SDL -	/// document, use `toSDLDocument` instead. -	string toSDLString()(string indent="\t", int indentLevel=0) -	{ -		Appender!string sink; -		toSDLString(sink, indent, indentLevel); -		return sink.data; -	} -	 -	///ditto -	void toSDLString(Sink)(ref Sink sink, string indent="\t", int indentLevel=0) -		if(isOutputRange!(Sink,char)) -	{ -		if(_name == "" && values.length == 0) -			throw new ValidationException("Anonymous tags must have at least one value."); -		 -		if(_name == "" && _namespace != "") -			throw new ValidationException("Anonymous tags cannot have a namespace."); -	 -		// Indent -		foreach(i; 0..indentLevel) -			sink.put(indent); -		 -		// Name -		if(_namespace != "") -		{ -			sink.put(_namespace); -			sink.put(':'); -		} -		sink.put(_name); -		 -		// Values -		foreach(i, v; values) -		{ -			// Omit the first space for anonymous tags -			if(_name != "" || i > 0) -				sink.put(' '); -			 -			v.toSDLString(sink); -		} -		 -		// Attributes -		foreach(attr; allAttributes) -		{ -			sink.put(' '); -			attr.toSDLString(sink); -		} -		 -		// Child tags -		bool foundChild=false; -		foreach(tag; allTags) -		{ -			if(!foundChild) -			{ -				sink.put(" {\n"); -				foundChild = true; -			} - -			tag.toSDLString(sink, indent, indentLevel+1); -		} -		if(foundChild) -		{ -			foreach(i; 0..indentLevel) -				sink.put(indent); - -			sink.put("}\n"); -		} -		else -			sink.put("\n"); -	} - -	/// Outputs full information on the tag. -	string toDebugString() -	{ -		import std.algorithm : sort; - -		Appender!string buf; -		 -		buf.put("\n"); -		buf.put("Tag "); -		if(_namespace != "") -		{ -			buf.put("["); -			buf.put(_namespace); -			buf.put("]"); -		} -		buf.put("'%s':\n".format(_name)); - -		// Values -		foreach(val; values) -			buf.put("    (%s): %s\n".format(.toString(val.type), val)); - -		// Attributes -		foreach(attrNamespace; _attributes.keys.sort()) -		if(attrNamespace != "*") -		foreach(attrName; _attributes[attrNamespace].keys.sort()) -		foreach(attr; _attributes[attrNamespace][attrName]) -		{ -			string namespaceStr; -			if(attr._namespace != "") -				namespaceStr = "["~attr._namespace~"]"; -			 -			buf.put( -				"    %s%s(%s): %s\n".format( -					namespaceStr, attr._name, .toString(attr.value.type), attr.value -				) -			); -		} -		 -		// Children -		foreach(tagNamespace; _tags.keys.sort()) -		if(tagNamespace != "*") -		foreach(tagName; _tags[tagNamespace].keys.sort()) -		foreach(tag; _tags[tagNamespace][tagName]) -			buf.put( tag.toDebugString().replace("\n", "\n    ") ); -		 -		return buf.data; -	} -} - -version(unittest) -{ -	private void testRandomAccessRange(R, E)(R range, E[] expected, bool function(E, E) equals=null) -	{ -		static assert(isRandomAccessRange!R); -		static assert(is(ElementType!R == E)); -		static assert(hasLength!R); -		static assert(!isInfinite!R); - -		assert(range.length == expected.length); -		if(range.length == 0) -		{ -			assert(range.empty); -			return; -		} -		 -		static bool defaultEquals(E e1, E e2) -		{ -			return e1 == e2; -		} -		if(equals is null) -			equals = &defaultEquals; -		 -		assert(equals(range.front, expected[0])); -		assert(equals(range.front, expected[0]));  // Ensure consistent result from '.front' -		assert(equals(range.front, expected[0]));  // Ensure consistent result from '.front' - -		assert(equals(range.back, expected[$-1])); -		assert(equals(range.back, expected[$-1]));  // Ensure consistent result from '.back' -		assert(equals(range.back, expected[$-1]));  // Ensure consistent result from '.back' -		 -		// Forward iteration -		auto original = range.save; -		auto r2 = range.save; -		foreach(i; 0..expected.length) -		{ -			//trace("Forward iteration: ", i); -			 -			// Test length/empty -			assert(range.length == expected.length - i); -			assert(range.length == r2.length); -			assert(!range.empty); -			assert(!r2.empty); -			 -			// Test front -			assert(equals(range.front, expected[i])); -			assert(equals(range.front, r2.front)); - -			// Test back -			assert(equals(range.back, expected[$-1])); -			assert(equals(range.back, r2.back)); - -			// Test opIndex(0) -			assert(equals(range[0], expected[i])); -			assert(equals(range[0], r2[0])); - -			// Test opIndex($-1) -			assert(equals(range[$-1], expected[$-1])); -			assert(equals(range[$-1], r2[$-1])); - -			// Test popFront -			range.popFront(); -			assert(range.length == r2.length - 1); -			r2.popFront(); -			assert(range.length == r2.length); -		} -		assert(range.empty); -		assert(r2.empty); -		assert(original.length == expected.length); -		 -		// Backwards iteration -		range = original.save; -		r2    = original.save; -		foreach(i; iota(0, expected.length).retro()) -		{ -			//trace("Backwards iteration: ", i); - -			// Test length/empty -			assert(range.length == i+1); -			assert(range.length == r2.length); -			assert(!range.empty); -			assert(!r2.empty); -			 -			// Test front -			assert(equals(range.front, expected[0])); -			assert(equals(range.front, r2.front)); - -			// Test back -			assert(equals(range.back, expected[i])); -			assert(equals(range.back, r2.back)); - -			// Test opIndex(0) -			assert(equals(range[0], expected[0])); -			assert(equals(range[0], r2[0])); - -			// Test opIndex($-1) -			assert(equals(range[$-1], expected[i])); -			assert(equals(range[$-1], r2[$-1])); - -			// Test popBack -			range.popBack(); -			assert(range.length == r2.length - 1); -			r2.popBack(); -			assert(range.length == r2.length); -		} -		assert(range.empty); -		assert(r2.empty); -		assert(original.length == expected.length); -		 -		// Random access -		range = original.save; -		r2    = original.save; -		foreach(i; 0..expected.length) -		{ -			//trace("Random access: ", i); - -			// Test length/empty -			assert(range.length == expected.length); -			assert(range.length == r2.length); -			assert(!range.empty); -			assert(!r2.empty); -			 -			// Test front -			assert(equals(range.front, expected[0])); -			assert(equals(range.front, r2.front)); - -			// Test back -			assert(equals(range.back, expected[$-1])); -			assert(equals(range.back, r2.back)); - -			// Test opIndex(i) -			assert(equals(range[i], expected[i])); -			assert(equals(range[i], r2[i])); -		} -		assert(!range.empty); -		assert(!r2.empty); -		assert(original.length == expected.length); -	} -} - -@("*: Test sdlang ast") -unittest -{ -	import std.exception; -	import sdlang.parser; -	 -	Tag root; -	root = parseSource(""); -	testRandomAccessRange(root.attributes, cast(          Attribute[])[]); -	testRandomAccessRange(root.tags,       cast(                Tag[])[]); -	testRandomAccessRange(root.namespaces, cast(Tag.NamespaceAccess[])[]); -	 -	root = parseSource(` -		blue 3 "Lee" isThree=true -		blue 5 "Chan" 12345 isThree=false -		stuff:orange 1 2 3 2 1 -		stuff:square points=4 dimensions=2 points="Still four" -		stuff:triangle data:points=3 data:dimensions=2 -		nothing -		namespaces small:A=1 med:A=2 big:A=3 small:B=10 big:B=30 -		 -		people visitor:a=1 b=2 { -			chiyo "Small" "Flies?" nemesis="Car" score=100 -			yukari -			visitor:sana -			tomo -			visitor:hayama -		} -	`); - -	auto blue3 = new Tag( -		null, "", "blue", -		[ Value(3), Value("Lee") ], -		[ new Attribute("isThree", Value(true)) ], -		null -	); -	auto blue5 = new Tag( -		null, "", "blue", -		[ Value(5), Value("Chan"), Value(12345) ], -		[ new Attribute("isThree", Value(false)) ], -		null -	); -	auto orange = new Tag( -		null, "stuff", "orange", -		[ Value(1), Value(2), Value(3), Value(2), Value(1) ], -		null, -		null -	); -	auto square = new Tag( -		null, "stuff", "square", -		null, -		[ -			new Attribute("points", Value(4)), -			new Attribute("dimensions", Value(2)), -			new Attribute("points", Value("Still four")), -		], -		null -	); -	auto triangle = new Tag( -		null, "stuff", "triangle", -		null, -		[ -			new Attribute("data", "points", Value(3)), -			new Attribute("data", "dimensions", Value(2)), -		], -		null -	); -	auto nothing = new Tag( -		null, "", "nothing", -		null, null, null -	); -	auto namespaces = new Tag( -		null, "", "namespaces", -		null, -		[ -			new Attribute("small", "A", Value(1)), -			new Attribute("med",   "A", Value(2)), -			new Attribute("big",   "A", Value(3)), -			new Attribute("small", "B", Value(10)), -			new Attribute("big",   "B", Value(30)), -		], -		null -	); -	auto chiyo = new Tag( -		null, "", "chiyo", -		[ Value("Small"), Value("Flies?") ], -		[ -			new Attribute("nemesis", Value("Car")), -			new Attribute("score", Value(100)), -		], -		null -	); -	auto chiyo_ = new Tag( -		null, "", "chiyo_", -		[ Value("Small"), Value("Flies?") ], -		[ -			new Attribute("nemesis", Value("Car")), -			new Attribute("score", Value(100)), -		], -		null -	); -	auto yukari = new Tag( -		null, "", "yukari", -		null, null, null -	); -	auto sana = new Tag( -		null, "visitor", "sana", -		null, null, null -	); -	auto sana_ = new Tag( -		null, "visitor", "sana_", -		null, null, null -	); -	auto sanaVisitor_ = new Tag( -		null, "visitor_", "sana_", -		null, null, null -	); -	auto tomo = new Tag( -		null, "", "tomo", -		null, null, null -	); -	auto hayama = new Tag( -		null, "visitor", "hayama", -		null, null, null -	); -	auto people = new Tag( -		null, "", "people", -		null, -		[ -			new Attribute("visitor", "a", Value(1)), -			new Attribute("b", Value(2)), -		], -		[chiyo, yukari, sana, tomo, hayama] -	); -	 -	assert(blue3      .opEquals( blue3      )); -	assert(blue5      .opEquals( blue5      )); -	assert(orange     .opEquals( orange     )); -	assert(square     .opEquals( square     )); -	assert(triangle   .opEquals( triangle   )); -	assert(nothing    .opEquals( nothing    )); -	assert(namespaces .opEquals( namespaces )); -	assert(people     .opEquals( people     )); -	assert(chiyo      .opEquals( chiyo      )); -	assert(yukari     .opEquals( yukari     )); -	assert(sana       .opEquals( sana       )); -	assert(tomo       .opEquals( tomo       )); -	assert(hayama     .opEquals( hayama     )); -	 -	assert(!blue3.opEquals(orange)); -	assert(!blue3.opEquals(people)); -	assert(!blue3.opEquals(sana)); -	assert(!blue3.opEquals(blue5)); -	assert(!blue5.opEquals(blue3)); -	 -	alias Tag.NamespaceAccess NSA; -	static bool namespaceEquals(NSA n1, NSA n2) -	{ -		return n1.name == n2.name; -	} -	 -	testRandomAccessRange(root.attributes, cast(Attribute[])[]); -	testRandomAccessRange(root.tags,       [blue3, blue5, nothing, namespaces, people]); -	testRandomAccessRange(root.namespaces, [NSA(""), NSA("stuff")], &namespaceEquals); -	testRandomAccessRange(root.namespaces[0].tags, [blue3, blue5, nothing, namespaces, people]); -	testRandomAccessRange(root.namespaces[1].tags, [orange, square, triangle]); -	assert(""        in root.namespaces); -	assert("stuff"   in root.namespaces); -	assert("foobar" !in root.namespaces); -	testRandomAccessRange(root.namespaces[     ""].tags, [blue3, blue5, nothing, namespaces, people]); -	testRandomAccessRange(root.namespaces["stuff"].tags, [orange, square, triangle]); -	testRandomAccessRange(root.all.attributes, cast(Attribute[])[]); -	testRandomAccessRange(root.all.tags,       [blue3, blue5, orange, square, triangle, nothing, namespaces, people]); -	testRandomAccessRange(root.all.tags[],     [blue3, blue5, orange, square, triangle, nothing, namespaces, people]); -	testRandomAccessRange(root.all.tags[3..6], [square, triangle, nothing]); -	assert("blue"    in root.tags); -	assert("nothing" in root.tags); -	assert("people"  in root.tags); -	assert("orange" !in root.tags); -	assert("square" !in root.tags); -	assert("foobar" !in root.tags); -	assert("blue"    in root.all.tags); -	assert("nothing" in root.all.tags); -	assert("people"  in root.all.tags); -	assert("orange"  in root.all.tags); -	assert("square"  in root.all.tags); -	assert("foobar" !in root.all.tags); -	assert("orange"  in root.namespaces["stuff"].tags); -	assert("square"  in root.namespaces["stuff"].tags); -	assert("square"  in root.namespaces["stuff"].tags); -	assert("foobar" !in root.attributes); -	assert("foobar" !in root.all.attributes); -	assert("foobar" !in root.namespaces["stuff"].attributes); -	assert("blue"   !in root.attributes); -	assert("blue"   !in root.all.attributes); -	assert("blue"   !in root.namespaces["stuff"].attributes); -	testRandomAccessRange(root.tags["nothing"],                    [nothing]); -	testRandomAccessRange(root.tags["blue"],                       [blue3, blue5]); -	testRandomAccessRange(root.namespaces["stuff"].tags["orange"], [orange]); -	testRandomAccessRange(root.all.tags["nothing"],                [nothing]); -	testRandomAccessRange(root.all.tags["blue"],                   [blue3, blue5]); -	testRandomAccessRange(root.all.tags["orange"],                 [orange]); - -	assertThrown!DOMRangeException(root.tags["foobar"]); -	assertThrown!DOMRangeException(root.all.tags["foobar"]); -	assertThrown!DOMRangeException(root.attributes["foobar"]); -	assertThrown!DOMRangeException(root.all.attributes["foobar"]); -	 -	// DMD Issue #12585 causes a segfault in these two tests when using 2.064 or 2.065, -	// so work around it. -	//assertThrown!DOMRangeException(root.namespaces["foobar"].tags["foobar"]); -	//assertThrown!DOMRangeException(root.namespaces["foobar"].attributes["foobar"]); -	bool didCatch = false; -	try -		auto x = root.namespaces["foobar"].tags["foobar"]; -	catch(DOMRangeException e) -		didCatch = true; -	assert(didCatch); -	 -	didCatch = false; -	try -		auto x = root.namespaces["foobar"].attributes["foobar"]; -	catch(DOMRangeException e) -		didCatch = true; -	assert(didCatch); - -	testRandomAccessRange(root.maybe.tags["nothing"],                    [nothing]); -	testRandomAccessRange(root.maybe.tags["blue"],                       [blue3, blue5]); -	testRandomAccessRange(root.maybe.namespaces["stuff"].tags["orange"], [orange]); -	testRandomAccessRange(root.maybe.all.tags["nothing"],                [nothing]); -	testRandomAccessRange(root.maybe.all.tags["blue"],                   [blue3, blue5]); -	testRandomAccessRange(root.maybe.all.tags["blue"][],                 [blue3, blue5]); -	testRandomAccessRange(root.maybe.all.tags["blue"][0..1],             [blue3]); -	testRandomAccessRange(root.maybe.all.tags["blue"][1..2],             [blue5]); -	testRandomAccessRange(root.maybe.all.tags["orange"],                 [orange]); -	testRandomAccessRange(root.maybe.tags["foobar"],                      cast(Tag[])[]); -	testRandomAccessRange(root.maybe.all.tags["foobar"],                  cast(Tag[])[]); -	testRandomAccessRange(root.maybe.namespaces["foobar"].tags["foobar"], cast(Tag[])[]); -	testRandomAccessRange(root.maybe.attributes["foobar"],                      cast(Attribute[])[]); -	testRandomAccessRange(root.maybe.all.attributes["foobar"],                  cast(Attribute[])[]); -	testRandomAccessRange(root.maybe.namespaces["foobar"].attributes["foobar"], cast(Attribute[])[]); - -	testRandomAccessRange(blue3.attributes,     [ new Attribute("isThree", Value(true)) ]); -	testRandomAccessRange(blue3.tags,           cast(Tag[])[]); -	testRandomAccessRange(blue3.namespaces,     [NSA("")], &namespaceEquals); -	testRandomAccessRange(blue3.all.attributes, [ new Attribute("isThree", Value(true)) ]); -	testRandomAccessRange(blue3.all.tags,       cast(Tag[])[]); -	 -	testRandomAccessRange(blue5.attributes,     [ new Attribute("isThree", Value(false)) ]); -	testRandomAccessRange(blue5.tags,           cast(Tag[])[]); -	testRandomAccessRange(blue5.namespaces,     [NSA("")], &namespaceEquals); -	testRandomAccessRange(blue5.all.attributes, [ new Attribute("isThree", Value(false)) ]); -	testRandomAccessRange(blue5.all.tags,       cast(Tag[])[]); -	 -	testRandomAccessRange(orange.attributes,     cast(Attribute[])[]); -	testRandomAccessRange(orange.tags,           cast(Tag[])[]); -	testRandomAccessRange(orange.namespaces,     cast(NSA[])[], &namespaceEquals); -	testRandomAccessRange(orange.all.attributes, cast(Attribute[])[]); -	testRandomAccessRange(orange.all.tags,       cast(Tag[])[]); -	 -	testRandomAccessRange(square.attributes, [ -		new Attribute("points", Value(4)), -		new Attribute("dimensions", Value(2)), -		new Attribute("points", Value("Still four")), -	]); -	testRandomAccessRange(square.tags,       cast(Tag[])[]); -	testRandomAccessRange(square.namespaces, [NSA("")], &namespaceEquals); -	testRandomAccessRange(square.all.attributes, [ -		new Attribute("points", Value(4)), -		new Attribute("dimensions", Value(2)), -		new Attribute("points", Value("Still four")), -	]); -	testRandomAccessRange(square.all.tags, cast(Tag[])[]); -	 -	testRandomAccessRange(triangle.attributes, cast(Attribute[])[]); -	testRandomAccessRange(triangle.tags,       cast(Tag[])[]); -	testRandomAccessRange(triangle.namespaces, [NSA("data")], &namespaceEquals); -	testRandomAccessRange(triangle.namespaces[0].attributes, [ -		new Attribute("data", "points", Value(3)), -		new Attribute("data", "dimensions", Value(2)), -	]); -	assert("data"    in triangle.namespaces); -	assert("foobar" !in triangle.namespaces); -	testRandomAccessRange(triangle.namespaces["data"].attributes, [ -		new Attribute("data", "points", Value(3)), -		new Attribute("data", "dimensions", Value(2)), -	]); -	testRandomAccessRange(triangle.all.attributes, [ -		new Attribute("data", "points", Value(3)), -		new Attribute("data", "dimensions", Value(2)), -	]); -	testRandomAccessRange(triangle.all.tags, cast(Tag[])[]); -	 -	testRandomAccessRange(nothing.attributes,     cast(Attribute[])[]); -	testRandomAccessRange(nothing.tags,           cast(Tag[])[]); -	testRandomAccessRange(nothing.namespaces,     cast(NSA[])[], &namespaceEquals); -	testRandomAccessRange(nothing.all.attributes, cast(Attribute[])[]); -	testRandomAccessRange(nothing.all.tags,       cast(Tag[])[]); -	 -	testRandomAccessRange(namespaces.attributes,   cast(Attribute[])[]); -	testRandomAccessRange(namespaces.tags,         cast(Tag[])[]); -	testRandomAccessRange(namespaces.namespaces,   [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals); -	testRandomAccessRange(namespaces.namespaces[], [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals); -	testRandomAccessRange(namespaces.namespaces[1..2], [NSA("med")], &namespaceEquals); -	testRandomAccessRange(namespaces.namespaces[0].attributes, [ -		new Attribute("small", "A", Value(1)), -		new Attribute("small", "B", Value(10)), -	]); -	testRandomAccessRange(namespaces.namespaces[1].attributes, [ -		new Attribute("med", "A", Value(2)), -	]); -	testRandomAccessRange(namespaces.namespaces[2].attributes, [ -		new Attribute("big", "A", Value(3)), -		new Attribute("big", "B", Value(30)), -	]); -	testRandomAccessRange(namespaces.namespaces[1..2][0].attributes, [ -		new Attribute("med", "A", Value(2)), -	]); -	assert("small"   in namespaces.namespaces); -	assert("med"     in namespaces.namespaces); -	assert("big"     in namespaces.namespaces); -	assert("foobar" !in namespaces.namespaces); -	assert("small"  !in namespaces.namespaces[1..2]); -	assert("med"     in namespaces.namespaces[1..2]); -	assert("big"    !in namespaces.namespaces[1..2]); -	assert("foobar" !in namespaces.namespaces[1..2]); -	testRandomAccessRange(namespaces.namespaces["small"].attributes, [ -		new Attribute("small", "A", Value(1)), -		new Attribute("small", "B", Value(10)), -	]); -	testRandomAccessRange(namespaces.namespaces["med"].attributes, [ -		new Attribute("med", "A", Value(2)), -	]); -	testRandomAccessRange(namespaces.namespaces["big"].attributes, [ -		new Attribute("big", "A", Value(3)), -		new Attribute("big", "B", Value(30)), -	]); -	testRandomAccessRange(namespaces.all.attributes, [ -		new Attribute("small", "A", Value(1)), -		new Attribute("med",   "A", Value(2)), -		new Attribute("big",   "A", Value(3)), -		new Attribute("small", "B", Value(10)), -		new Attribute("big",   "B", Value(30)), -	]); -	testRandomAccessRange(namespaces.all.attributes[], [ -		new Attribute("small", "A", Value(1)), -		new Attribute("med",   "A", Value(2)), -		new Attribute("big",   "A", Value(3)), -		new Attribute("small", "B", Value(10)), -		new Attribute("big",   "B", Value(30)), -	]); -	testRandomAccessRange(namespaces.all.attributes[2..4], [ -		new Attribute("big",   "A", Value(3)), -		new Attribute("small", "B", Value(10)), -	]); -	testRandomAccessRange(namespaces.all.tags, cast(Tag[])[]); -	assert("A"      !in namespaces.attributes); -	assert("B"      !in namespaces.attributes); -	assert("foobar" !in namespaces.attributes); -	assert("A"       in namespaces.all.attributes); -	assert("B"       in namespaces.all.attributes); -	assert("foobar" !in namespaces.all.attributes); -	assert("A"       in namespaces.namespaces["small"].attributes); -	assert("B"       in namespaces.namespaces["small"].attributes); -	assert("foobar" !in namespaces.namespaces["small"].attributes); -	assert("A"       in namespaces.namespaces["med"].attributes); -	assert("B"      !in namespaces.namespaces["med"].attributes); -	assert("foobar" !in namespaces.namespaces["med"].attributes); -	assert("A"       in namespaces.namespaces["big"].attributes); -	assert("B"       in namespaces.namespaces["big"].attributes); -	assert("foobar" !in namespaces.namespaces["big"].attributes); -	assert("foobar" !in namespaces.tags); -	assert("foobar" !in namespaces.all.tags); -	assert("foobar" !in namespaces.namespaces["small"].tags); -	assert("A"      !in namespaces.tags); -	assert("A"      !in namespaces.all.tags); -	assert("A"      !in namespaces.namespaces["small"].tags); -	testRandomAccessRange(namespaces.namespaces["small"].attributes["A"], [ -		new Attribute("small", "A", Value(1)), -	]); -	testRandomAccessRange(namespaces.namespaces["med"].attributes["A"], [ -		new Attribute("med", "A", Value(2)), -	]); -	testRandomAccessRange(namespaces.namespaces["big"].attributes["A"], [ -		new Attribute("big", "A", Value(3)), -	]); -	testRandomAccessRange(namespaces.all.attributes["A"], [ -		new Attribute("small", "A", Value(1)), -		new Attribute("med",   "A", Value(2)), -		new Attribute("big",   "A", Value(3)), -	]); -	testRandomAccessRange(namespaces.all.attributes["B"], [ -		new Attribute("small", "B", Value(10)), -		new Attribute("big",   "B", Value(30)), -	]); - -	testRandomAccessRange(chiyo.attributes, [ -		new Attribute("nemesis", Value("Car")), -		new Attribute("score", Value(100)), -	]); -	testRandomAccessRange(chiyo.tags,       cast(Tag[])[]); -	testRandomAccessRange(chiyo.namespaces, [NSA("")], &namespaceEquals); -	testRandomAccessRange(chiyo.all.attributes, [ -		new Attribute("nemesis", Value("Car")), -		new Attribute("score", Value(100)), -	]); -	testRandomAccessRange(chiyo.all.tags, cast(Tag[])[]); -	 -	testRandomAccessRange(yukari.attributes,     cast(Attribute[])[]); -	testRandomAccessRange(yukari.tags,           cast(Tag[])[]); -	testRandomAccessRange(yukari.namespaces,     cast(NSA[])[], &namespaceEquals); -	testRandomAccessRange(yukari.all.attributes, cast(Attribute[])[]); -	testRandomAccessRange(yukari.all.tags,       cast(Tag[])[]); -	 -	testRandomAccessRange(sana.attributes,     cast(Attribute[])[]); -	testRandomAccessRange(sana.tags,           cast(Tag[])[]); -	testRandomAccessRange(sana.namespaces,     cast(NSA[])[], &namespaceEquals); -	testRandomAccessRange(sana.all.attributes, cast(Attribute[])[]); -	testRandomAccessRange(sana.all.tags,       cast(Tag[])[]); -	 -	testRandomAccessRange(people.attributes,         [new Attribute("b", Value(2))]); -	testRandomAccessRange(people.tags,               [chiyo, yukari, tomo]); -	testRandomAccessRange(people.namespaces,         [NSA("visitor"), NSA("")], &namespaceEquals); -	testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("visitor", "a", Value(1))]); -	testRandomAccessRange(people.namespaces[1].attributes, [new Attribute("b", Value(2))]); -	testRandomAccessRange(people.namespaces[0].tags,       [sana, hayama]); -	testRandomAccessRange(people.namespaces[1].tags,       [chiyo, yukari, tomo]); -	assert("visitor" in people.namespaces); -	assert(""        in people.namespaces); -	assert("foobar" !in people.namespaces); -	testRandomAccessRange(people.namespaces["visitor"].attributes, [new Attribute("visitor", "a", Value(1))]); -	testRandomAccessRange(people.namespaces[       ""].attributes, [new Attribute("b", Value(2))]); -	testRandomAccessRange(people.namespaces["visitor"].tags,       [sana, hayama]); -	testRandomAccessRange(people.namespaces[       ""].tags,       [chiyo, yukari, tomo]); -	testRandomAccessRange(people.all.attributes, [ -		new Attribute("visitor", "a", Value(1)), -		new Attribute("b", Value(2)), -	]); -	testRandomAccessRange(people.all.tags, [chiyo, yukari, sana, tomo, hayama]); -	 -	people.attributes["b"][0].name = "b_"; -	people.namespaces["visitor"].attributes["a"][0].name = "a_"; -	people.tags["chiyo"][0].name = "chiyo_"; -	people.namespaces["visitor"].tags["sana"][0].name = "sana_"; - -	assert("b_"     in people.attributes); -	assert("a_"     in people.namespaces["visitor"].attributes); -	assert("chiyo_" in people.tags); -	assert("sana_"  in people.namespaces["visitor"].tags); - -	assert(people.attributes["b_"][0]                       == new Attribute("b_", Value(2))); -	assert(people.namespaces["visitor"].attributes["a_"][0] == new Attribute("visitor", "a_", Value(1))); -	assert(people.tags["chiyo_"][0]                         == chiyo_); -	assert(people.namespaces["visitor"].tags["sana_"][0]    == sana_); - -	assert("b"     !in people.attributes); -	assert("a"     !in people.namespaces["visitor"].attributes); -	assert("chiyo" !in people.tags); -	assert("sana"  !in people.namespaces["visitor"].tags); - -	assert(people.maybe.attributes["b"].length                       == 0); -	assert(people.maybe.namespaces["visitor"].attributes["a"].length == 0); -	assert(people.maybe.tags["chiyo"].length                         == 0); -	assert(people.maybe.namespaces["visitor"].tags["sana"].length    == 0); - -	people.tags["tomo"][0].remove(); -	people.namespaces["visitor"].tags["hayama"][0].remove(); -	people.tags["chiyo_"][0].remove(); -	testRandomAccessRange(people.tags,               [yukari]); -	testRandomAccessRange(people.namespaces,         [NSA("visitor"), NSA("")], &namespaceEquals); -	testRandomAccessRange(people.namespaces[0].tags, [sana_]); -	testRandomAccessRange(people.namespaces[1].tags, [yukari]); -	assert("visitor" in people.namespaces); -	assert(""        in people.namespaces); -	assert("foobar" !in people.namespaces); -	testRandomAccessRange(people.namespaces["visitor"].tags, [sana_]); -	testRandomAccessRange(people.namespaces[       ""].tags, [yukari]); -	testRandomAccessRange(people.all.tags, [yukari, sana_]); -	 -	people.attributes["b_"][0].namespace = "_"; -	people.namespaces["visitor"].attributes["a_"][0].namespace = "visitor_"; -	assert("_"         in people.namespaces); -	assert("visitor_"  in people.namespaces); -	assert(""          in people.namespaces); -	assert("visitor"   in people.namespaces); -	people.namespaces["visitor"].tags["sana_"][0].namespace = "visitor_"; -	assert("_"         in people.namespaces); -	assert("visitor_"  in people.namespaces); -	assert(""          in people.namespaces); -	assert("visitor"  !in people.namespaces); - -	assert(people.namespaces["_"       ].attributes["b_"][0] == new Attribute("_", "b_", Value(2))); -	assert(people.namespaces["visitor_"].attributes["a_"][0] == new Attribute("visitor_", "a_", Value(1))); -	assert(people.namespaces["visitor_"].tags["sana_"][0]    == sanaVisitor_); -	 -	people.tags["yukari"][0].remove(); -	people.namespaces["visitor_"].tags["sana_"][0].remove(); -	people.namespaces["visitor_"].attributes["a_"][0].namespace = "visitor"; -	people.namespaces["_"].attributes["b_"][0].namespace = ""; -	testRandomAccessRange(people.tags,               cast(Tag[])[]); -	testRandomAccessRange(people.namespaces,         [NSA("visitor"), NSA("")], &namespaceEquals); -	testRandomAccessRange(people.namespaces[0].tags, cast(Tag[])[]); -	testRandomAccessRange(people.namespaces[1].tags, cast(Tag[])[]); -	assert("visitor" in people.namespaces); -	assert(""        in people.namespaces); -	assert("foobar" !in people.namespaces); -	testRandomAccessRange(people.namespaces["visitor"].tags, cast(Tag[])[]); -	testRandomAccessRange(people.namespaces[       ""].tags, cast(Tag[])[]); -	testRandomAccessRange(people.all.tags, cast(Tag[])[]); -	 -	people.namespaces["visitor"].attributes["a_"][0].remove(); -	testRandomAccessRange(people.attributes,               [new Attribute("b_", Value(2))]); -	testRandomAccessRange(people.namespaces,               [NSA("")], &namespaceEquals); -	testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("b_", Value(2))]); -	assert("visitor" !in people.namespaces); -	assert(""         in people.namespaces); -	assert("foobar"  !in people.namespaces); -	testRandomAccessRange(people.namespaces[""].attributes, [new Attribute("b_", Value(2))]); -	testRandomAccessRange(people.all.attributes, [ -		new Attribute("b_", Value(2)), -	]); -	 -	people.attributes["b_"][0].remove(); -	testRandomAccessRange(people.attributes, cast(Attribute[])[]); -	testRandomAccessRange(people.namespaces, cast(NSA[])[], &namespaceEquals); -	assert("visitor" !in people.namespaces); -	assert(""        !in people.namespaces); -	assert("foobar"  !in people.namespaces); -	testRandomAccessRange(people.all.attributes, cast(Attribute[])[]); -	 -	// Test clone() -	auto rootClone = root.clone(); -	assert(rootClone !is root); -	assert(rootClone.parent is null); -	assert(rootClone.name      == root.name); -	assert(rootClone.namespace == root.namespace); -	assert(rootClone.location  == root.location); -	assert(rootClone.values    == root.values); -	assert(rootClone.toSDLDocument() == root.toSDLDocument()); - -	auto peopleClone = people.clone(); -	assert(peopleClone !is people); -	assert(peopleClone.parent is null); -	assert(peopleClone.name      == people.name); -	assert(peopleClone.namespace == people.namespace); -	assert(peopleClone.location  == people.location); -	assert(peopleClone.values    == people.values); -	assert(peopleClone.toSDLString() == people.toSDLString()); -} - -// Regression test, issue #11: https://github.com/Abscissa/SDLang-D/issues/11 -@("*: Regression test issue #11") -unittest -{ -	import sdlang.parser; - -	auto root = parseSource( -`// -a`); - -	assert("a" in root.tags); - -	root = parseSource( -`// -parent { -	child -} -`); - -	auto child = new Tag( -		null, "", "child", -		null, null, null -	); -	 -	assert("parent" in root.tags); -	assert("child" !in root.tags); -	testRandomAccessRange(root.tags["parent"][0].tags, [child]); -	assert("child" in root.tags["parent"][0].tags); -} diff --git a/src/sdlang/dub.json b/src/sdlang/dub.json deleted file mode 100644 index d5a0493..0000000 --- a/src/sdlang/dub.json +++ /dev/null @@ -1,38 +0,0 @@ -{ -	"name":        "sdlang-d", -	"description": "An SDL (Simple Declarative Language) library for D.", -	"homepage":    "http://github.com/Abscissa/SDLang-D", -	"authors":     ["Nick Sabalausky"], -	"license":     "zlib/libpng", -	"copyright":   "©2012-2015 Nick Sabalausky", -	"sourcePaths": ["."], -	"importPaths": ["."], -	"buildRequirements": ["allowWarnings"], -	"dependencies": { -		"libinputvisitor": "~>1.2.0" -	}, -	"subPackages": [ -		"./libinputvisitor" -	], -	"configurations": [ -		{ -			"name": "test", -			"targetType": "executable", -			"versions": ["SDLang_TestApp"], -			"targetPath": "../../bin/", -			"targetName": "sdlang" -		}, -		{ -			"name": "library", -			"targetType": "library" -		}, -		{ -			"name": "unittest", -			"targetType": "executable", -			"targetPath": "../../bin/", -			"targetName": "sdlang-unittest", - -			"versions": ["sdlangUnittest", "sdlangTrace"] -		} -	] -} diff --git a/src/sdlang/exception.d b/src/sdlang/exception.d deleted file mode 100644 index 188991e..0000000 --- a/src/sdlang/exception.d +++ /dev/null @@ -1,190 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.exception; - -import std.array; -import std.exception; -import std.range; -import std.stdio; -import std.string; - -import sdlang.ast; -import sdlang.util; - -/// Abstract parent class of all SDLang-D defined exceptions. -abstract class SDLangException : Exception -{ -	this(string msg, string file = __FILE__, size_t line = __LINE__) -	{ -		super(msg, file, line); -	} -} - -/// Thrown when a syntax error is encounterd while parsing. -class ParseException : SDLangException -{ -	Location location; -	bool hasLocation; - -	this(string msg, string file = __FILE__, size_t line = __LINE__) -	{ -		hasLocation = false; -		super(msg, file, line); -	} - -	this(Location location, string msg, string file = __FILE__, size_t line = __LINE__) -	{ -		hasLocation = true; -		super("%s: %s".format(location.toString(), msg), file, line); -	} -} - -/// Compatibility alias -deprecated("The new name is ParseException") -alias SDLangParseException = ParseException; - -/++ -Thrown when attempting to do something in the DOM that's unsupported, such as: - -$(UL -$(LI Adding the same instance of a tag or attribute to more than one parent.) -$(LI Writing SDLang where: -	$(UL -	$(LI The root tag has values, attributes or a namespace. ) -	$(LI An anonymous tag has a namespace. ) -	$(LI An anonymous tag has no values. ) -	$(LI A floating point value is infinity or NaN. ) -	) -)) -+/ -class ValidationException : SDLangException -{ -	this(string msg, string file = __FILE__, size_t line = __LINE__) -	{ -		super(msg, file, line); -	} -} - -/// Compatibility alias -deprecated("The new name is ValidationException") -alias SDLangValidationException = ValidationException; - -/// Thrown when someting is wrong with the provided arguments to a function. -class ArgumentException : SDLangException -{ -	this(string msg, string file = __FILE__, size_t line = __LINE__) -	{ -		super(msg, file, line); -	} -} - -/// Thrown by the DOM on empty range and out-of-range conditions. -abstract class DOMException : SDLangException -{ -	Tag base; /// The tag searched from - -	this(Tag base, string msg, string file = __FILE__, size_t line = __LINE__) -	{ -		this.base = base; -		super(msg, file, line); -	} - -	/// Prefixes a message with file/line information from the tag (if tag exists). -	/// Optionally takes output range as a sink. -	string customMsg(string msg) -	{ -		if(!base) -			return msg; - -		Appender!string sink; -		this.customMsg(sink, msg); -		return sink.data; -	} - -	///ditto -	void customMsg(Sink)(ref Sink sink, string msg) if(isOutputRange!(Sink,char)) -	{ -		if(base) -		{ -			sink.put(base.location.toString()); -			sink.put(": "); -			sink.put(msg); -		} -		else -			sink.put(msg); -	} - -	/// Outputs a message to stderr, prefixed with file/line information -	void writeCustomMsg(string msg) -	{ -		stderr.writeln( customMsg(msg) ); -	} -} - -/// Thrown by the DOM on empty range and out-of-range conditions. -class DOMRangeException : DOMException -{ -	this(Tag base, string msg, string file = __FILE__, size_t line = __LINE__) -	{ -		super(base, msg, file, line); -	} -} - -/// Compatibility alias -deprecated("The new name is DOMRangeException") -alias SDLangRangeException = DOMRangeException; - -/// Abstract parent class of `TagNotFoundException`, `ValueNotFoundException` -/// and `AttributeNotFoundException`. -/// -/// Thrown by the DOM's `sdlang.ast.Tag.expectTag`, etc. functions if a matching element isn't found. -abstract class DOMNotFoundException : DOMException -{ -	FullName tagName; /// The tag searched for - -	this(Tag base, FullName tagName, string msg, string file = __FILE__, size_t line = __LINE__) -	{ -		this.tagName = tagName; -		super(base, msg, file, line); -	} -} - -/// Thrown by the DOM's `sdlang.ast.Tag.expectTag`, etc. functions if a Tag isn't found. -class TagNotFoundException : DOMNotFoundException -{ -	this(Tag base, FullName tagName, string msg, string file = __FILE__, size_t line = __LINE__) -	{ -		super(base, tagName, msg, file, line); -	} -} - -/// Thrown by the DOM's `sdlang.ast.Tag.expectValue`, etc. functions if a Value isn't found. -class ValueNotFoundException : DOMNotFoundException -{ -	/// Expected type for the not-found value. -	TypeInfo valueType; - -	this(Tag base, FullName tagName, TypeInfo valueType, string msg, string file = __FILE__, size_t line = __LINE__) -	{ -		this.valueType = valueType; -		super(base, tagName, msg, file, line); -	} -} - -/// Thrown by the DOM's `sdlang.ast.Tag.expectAttribute`, etc. functions if an Attribute isn't found. -class AttributeNotFoundException : DOMNotFoundException -{ -	FullName attributeName; /// The attribute searched for - -	/// Expected type for the not-found attribute's value. -	TypeInfo valueType; - -	this(Tag base, FullName tagName, FullName attributeName, TypeInfo valueType, string msg, -		string file = __FILE__, size_t line = __LINE__) -	{ -		this.valueType = valueType; -		this.attributeName = attributeName; -		super(base, tagName, msg, file, line); -	} -} diff --git a/src/sdlang/lexer.d b/src/sdlang/lexer.d deleted file mode 100644 index 3788188..0000000 --- a/src/sdlang/lexer.d +++ /dev/null @@ -1,2068 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.lexer; - -import std.algorithm; -import std.array; -static import std.ascii; -import std.base64; -import std.bigint; -import std.conv; -import std.datetime; -import std.file; -import std.format; -import std.traits; -import std.typecons; -import std.uni; -import std.utf; -import std.variant; - -import sdlang.exception; -import sdlang.symbol; -import sdlang.token; -import sdlang.util; - -alias sdlang.util.startsWith startsWith; - -Token[] lexFile(string filename) -{ -	auto source = cast(string)read(filename); -	return lexSource(source, filename); -} - -Token[] lexSource(string source, string filename=null) -{ -	auto lexer = scoped!Lexer(source, filename); -	 -	// Can't use 'std.array.array(Range)' because 'lexer' is scoped -	// and therefore cannot have its reference copied. -	Appender!(Token[]) tokens; -	foreach(tok; lexer) -		tokens.put(tok); - -	return tokens.data; -} - -// Kind of a poor-man's yield, but fast. -// Only to be used inside Lexer.popFront (and Lexer.this). -private template accept(string symbolName) -{ -	static assert(symbolName != "Value", "Value symbols must also take a value."); -	enum accept = acceptImpl!(symbolName, "null"); -} -private template accept(string symbolName, string value) -{ -	static assert(symbolName == "Value", "Only a Value symbol can take a value."); -	enum accept = acceptImpl!(symbolName, value); -} -private template accept(string symbolName, string value, string startLocation, string endLocation) -{ -	static assert(symbolName == "Value", "Only a Value symbol can take a value."); -	enum accept = (" -		{ -			_front = makeToken!"~symbolName.stringof~"; -			_front.value = "~value~"; -			_front.location = "~(startLocation==""? "tokenStart" : startLocation)~"; -			_front.data = source[ -				"~(startLocation==""? "tokenStart.index" : startLocation)~" -				.. -				"~(endLocation==""? "location.index" : endLocation)~" -			]; -			return; -		} -	").replace("\n", ""); -} -private template acceptImpl(string symbolName, string value) -{ -	enum acceptImpl = (" -		{ -			_front = makeToken!"~symbolName.stringof~"; -			_front.value = "~value~"; -			return; -		} -	").replace("\n", ""); -} - -class Lexer -{ -	string source; -	string filename; -	Location location; /// Location of current character in source - -	private dchar  ch;         // Current character -	private dchar  nextCh;     // Lookahead character -	private size_t nextPos;    // Position of lookahead character (an index into source) -	private bool   hasNextCh;  // If false, then there's no more lookahead, just EOF -	private size_t posAfterLookahead; // Position after lookahead character (an index into source) - -	private Location tokenStart;    // The starting location of the token being lexed -	 -	// Length so far of the token being lexed, not including current char -	private size_t tokenLength;   // Length in UTF-8 code units -	private size_t tokenLength32; // Length in UTF-32 code units -	 -	// Slight kludge: -	// If a numeric fragment is found after a Date (separated by arbitrary -	// whitespace), it could be the "hours" part of a DateTime, or it could -	// be a separate numeric literal that simply follows a plain Date. If the -	// latter, then the Date must be emitted, but numeric fragment that was -	// found after it needs to be saved for the the lexer's next iteration. -	//  -	// It's a slight kludge, and could instead be implemented as a slightly -	// kludgey parser hack, but it's the only situation where SDLang's lexing -	// needs to lookahead more than one character, so this is good enough. -	private struct LookaheadTokenInfo -	{ -		bool     exists          = false; -		string   numericFragment = ""; -		bool     isNegative      = false; -		Location tokenStart; -	} -	private LookaheadTokenInfo lookaheadTokenInfo; -	 -	this(string source=null, string filename=null) -	{ -		this.filename = filename; -		this.source = source; -		 -		_front = Token(symbol!"Error", Location()); -		lookaheadTokenInfo = LookaheadTokenInfo.init; - -		if( source.startsWith( ByteOrderMarks[BOM.UTF8] ) ) -		{ -			source = source[ ByteOrderMarks[BOM.UTF8].length .. $ ]; -			this.source = source; -		} -		 -		foreach(bom; ByteOrderMarks) -		if( source.startsWith(bom) ) -			error(Location(filename,0,0,0), "SDL spec only supports UTF-8, not UTF-16 or UTF-32"); -		 -		if(source == "") -			mixin(accept!"EOF"); -		 -		// Prime everything -		hasNextCh = true; -		nextCh = source.decode(posAfterLookahead); -		advanceChar(ErrorOnEOF.Yes); -		location = Location(filename, 0, 0, 0); -		popFront(); -	} -	 -	@property bool empty() -	{ -		return _front.symbol == symbol!"EOF"; -	} -	 -	Token _front; -	@property Token front() -	{ -		return _front; -	} - -	@property bool isEOF() -	{ -		return location.index == source.length && !lookaheadTokenInfo.exists; -	} - -	private void error(string msg) -	{ -		error(location, msg); -	} - -	//TODO: Take varargs and use output range sink. -	private void error(Location loc, string msg) -	{ -		throw new ParseException(loc, "Error: "~msg); -	} - -	private Token makeToken(string symbolName)() -	{ -		auto tok = Token(symbol!symbolName, tokenStart); -		tok.data = tokenData; -		return tok; -	} -	 -	private @property string tokenData() -	{ -		return source[ tokenStart.index .. location.index ]; -	} -	 -	/// Check the lookahead character -	private bool lookahead(dchar ch) -	{ -		return hasNextCh && nextCh == ch; -	} - -	private bool lookahead(bool function(dchar) condition) -	{ -		return hasNextCh && condition(nextCh); -	} - -	private static bool isNewline(dchar ch) -	{ -		return ch == '\n' || ch == '\r' || ch == lineSep || ch == paraSep; -	} - -	/// Returns the length of the newline sequence, or zero if the current -	/// character is not a newline -	/// -	/// Note that there are only single character sequences and the two -	/// character sequence `\r\n` as used on Windows. -	private size_t isAtNewline() -	{ -		if(ch == '\n' || ch == lineSep || ch == paraSep) return 1; -		else if(ch == '\r') return lookahead('\n') ? 2 : 1; -		else return 0; -	} - -	/// Is 'ch' a valid base 64 character? -	private bool isBase64(dchar ch) -	{ -		if(ch >= 'A' && ch <= 'Z') -			return true; - -		if(ch >= 'a' && ch <= 'z') -			return true; - -		if(ch >= '0' && ch <= '9') -			return true; -		 -		return ch == '+' || ch == '/' || ch == '='; -	} -	 -	/// Is the current character one that's allowed -	/// immediately *after* an int/float literal? -	private bool isEndOfNumber() -	{ -		if(isEOF) -			return true; -		 -		return !isDigit(ch) && ch != ':' && ch != '_' && !isAlpha(ch); -	} -	 -	/// Is current character the last one in an ident? -	private bool isEndOfIdentCached = false; -	private bool _isEndOfIdent; -	private bool isEndOfIdent() -	{ -		if(!isEndOfIdentCached) -		{ -			if(!hasNextCh) -				_isEndOfIdent = true; -			else -				_isEndOfIdent = !isIdentChar(nextCh); -			 -			isEndOfIdentCached = true; -		} -		 -		return _isEndOfIdent; -	} - -	/// Is 'ch' a character that's allowed *somewhere* in an identifier? -	private bool isIdentChar(dchar ch) -	{ -		if(isAlpha(ch)) -			return true; -		 -		else if(isNumber(ch)) -			return true; -		 -		else -			return  -				ch == '-' || -				ch == '_' || -				ch == '.' || -				ch == '$'; -	} - -	private bool isDigit(dchar ch) -	{ -		return ch >= '0' && ch <= '9'; -	} -	 -	private enum KeywordResult -	{ -		Accept,   // Keyword is matched -		Continue, // Keyword is not matched *yet* -		Failed,   // Keyword doesn't match -	} -	private KeywordResult checkKeyword(dstring keyword32) -	{ -		// Still within length of keyword -		if(tokenLength32 < keyword32.length) -		{ -			if(ch == keyword32[tokenLength32]) -				return KeywordResult.Continue; -			else -				return KeywordResult.Failed; -		} - -		// At position after keyword -		else if(tokenLength32 == keyword32.length) -		{ -			if(isEOF || !isIdentChar(ch)) -			{ -				debug assert(tokenData == to!string(keyword32)); -				return KeywordResult.Accept; -			} -			else -				return KeywordResult.Failed; -		} - -		assert(0, "Fell off end of keyword to check"); -	} - -	enum ErrorOnEOF { No, Yes } - -	/// Advance one code point. -	private void advanceChar(ErrorOnEOF errorOnEOF) -	{ -		if(auto cnt = isAtNewline()) -		{ -			if (cnt == 1) -				location.line++; -			location.col = 0; -		} -		else -			location.col++; - -		location.index = nextPos; - -		nextPos = posAfterLookahead; -		ch      = nextCh; - -		if(!hasNextCh) -		{ -			if(errorOnEOF == ErrorOnEOF.Yes) -				error("Unexpected end of file"); - -			return; -		} - -		tokenLength32++; -		tokenLength = location.index - tokenStart.index; - -		if(nextPos == source.length) -		{ -			nextCh = dchar.init; -			hasNextCh = false; -			return; -		} -		 -		nextCh = source.decode(posAfterLookahead); -		isEndOfIdentCached = false; -	} - -	/// Advances the specified amount of characters -	private void advanceChar(size_t count, ErrorOnEOF errorOnEOF) -	{ -		while(count-- > 0) -			advanceChar(errorOnEOF); -	} - -	void popFront() -	{ -		// -- Main Lexer ------------- - -		eatWhite(); - -		if(isEOF) -			mixin(accept!"EOF"); -		 -		tokenStart    = location; -		tokenLength   = 0; -		tokenLength32 = 0; -		isEndOfIdentCached = false; -		 -		if(lookaheadTokenInfo.exists) -		{ -			tokenStart = lookaheadTokenInfo.tokenStart; - -			auto prevLATokenInfo = lookaheadTokenInfo; -			lookaheadTokenInfo = LookaheadTokenInfo.init; -			lexNumeric(prevLATokenInfo); -			return; -		} -		 -		if(ch == '=') -		{ -			advanceChar(ErrorOnEOF.No); -			mixin(accept!"="); -		} -		 -		else if(ch == '{') -		{ -			advanceChar(ErrorOnEOF.No); -			mixin(accept!"{"); -		} -		 -		else if(ch == '}') -		{ -			advanceChar(ErrorOnEOF.No); -			mixin(accept!"}"); -		} -		 -		else if(ch == ':') -		{ -			advanceChar(ErrorOnEOF.No); -			mixin(accept!":"); -		} -		 -		else if(ch == ';') -		{ -			advanceChar(ErrorOnEOF.No); -			mixin(accept!"EOL"); -		} - -		else if(auto cnt = isAtNewline()) -		{ -			advanceChar(cnt, ErrorOnEOF.No); -			mixin(accept!"EOL"); -		} -		 -		else if(isAlpha(ch) || ch == '_') -			lexIdentKeyword(); - -		else if(ch == '"') -			lexRegularString(); - -		else if(ch == '`') -			lexRawString(); -		 -		else if(ch == '\'') -			lexCharacter(); - -		else if(ch == '[') -			lexBinary(); - -		else if(ch == '-' || ch == '.' || isDigit(ch)) -			lexNumeric(); - -		else -		{ -			if(ch == ',') -				error("Unexpected comma: SDLang is not a comma-separated format."); -			else if(std.ascii.isPrintable(ch)) -				error(text("Unexpected: ", ch)); -			else -				error("Unexpected character code 0x%02X".format(ch)); - -			advanceChar(ErrorOnEOF.No); -		} -	} - -	/// Lex Ident or Keyword -	private void lexIdentKeyword() -	{ -		assert(isAlpha(ch) || ch == '_'); -		 -		// Keyword -		struct Key -		{ -			dstring name; -			Value value; -			bool failed = false; -		} -		static Key[5] keywords; -		static keywordsInited = false; -		if(!keywordsInited) -		{ -			// Value (as a std.variant-based type) can't be statically inited -			keywords[0] = Key("true",  Value(true )); -			keywords[1] = Key("false", Value(false)); -			keywords[2] = Key("on",    Value(true )); -			keywords[3] = Key("off",   Value(false)); -			keywords[4] = Key("null",  Value(null )); -			keywordsInited = true; -		} -		 -		foreach(ref key; keywords) -			key.failed = false; -		 -		auto numKeys = keywords.length; - -		do -		{ -			foreach(ref key; keywords) -			if(!key.failed) -			{ -				final switch(checkKeyword(key.name)) -				{ -				case KeywordResult.Accept: -					mixin(accept!("Value", "key.value")); -				 -				case KeywordResult.Continue: -					break; -				 -				case KeywordResult.Failed: -					key.failed = true; -					numKeys--; -					break; -				} -			} - -			if(numKeys == 0) -			{ -				lexIdent(); -				return; -			} - -			advanceChar(ErrorOnEOF.No); - -		} while(!isEOF); - -		foreach(ref key; keywords) -		if(!key.failed) -		if(key.name.length == tokenLength32+1) -			mixin(accept!("Value", "key.value")); - -		mixin(accept!"Ident"); -	} - -	/// Lex Ident -	private void lexIdent() -	{ -		if(tokenLength == 0) -			assert(isAlpha(ch) || ch == '_'); -		 -		while(!isEOF && isIdentChar(ch)) -			advanceChar(ErrorOnEOF.No); - -		mixin(accept!"Ident"); -	} -	 -	/// Lex regular string -	private void lexRegularString() -	{ -		assert(ch == '"'); - -		Appender!string buf; -		size_t spanStart = nextPos; -		 -		// Doesn't include current character -		void updateBuf() -		{ -			if(location.index == spanStart) -				return; - -			buf.put( source[spanStart..location.index] ); -		} -		 -		advanceChar(ErrorOnEOF.Yes); -		while(ch != '"') -		{ -			if(ch == '\\') -			{ -				updateBuf(); - -				bool wasEscSequence = true; -				if(hasNextCh) -				{ -					switch(nextCh) -					{ -					case 'n':  buf.put('\n'); break; -					case 'r':  buf.put('\r'); break; -					case 't':  buf.put('\t'); break; -					case '"':  buf.put('\"'); break; -					case '\\': buf.put('\\'); break; -					default: wasEscSequence = false; break; -					} -				} -				 -				if(wasEscSequence) -				{ -					advanceChar(ErrorOnEOF.Yes); -					spanStart = nextPos; -				} -				else -				{ -					eatWhite(false); -					spanStart = location.index; -				} -			} - -			else if(isNewline(ch)) -				error("Unescaped newlines are only allowed in raw strings, not regular strings."); - -			advanceChar(ErrorOnEOF.Yes); -		} -		 -		updateBuf(); -		advanceChar(ErrorOnEOF.No); // Skip closing double-quote -		mixin(accept!("Value", "buf.data")); -	} - -	/// Lex raw string -	private void lexRawString() -	{ -		assert(ch == '`'); -		 -		do -			advanceChar(ErrorOnEOF.Yes); -		while(ch != '`'); -		 -		advanceChar(ErrorOnEOF.No); // Skip closing back-tick -		mixin(accept!("Value", "tokenData[1..$-1]")); -	} -	 -	/// Lex character literal -	private void lexCharacter() -	{ -		assert(ch == '\''); -		advanceChar(ErrorOnEOF.Yes); // Skip opening single-quote -		 -		dchar value; -		if(ch == '\\') -		{ -			advanceChar(ErrorOnEOF.Yes); // Skip escape backslash -			switch(ch) -			{ -			case 'n':  value = '\n'; break; -			case 'r':  value = '\r'; break; -			case 't':  value = '\t'; break; -			case '\'': value = '\''; break; -			case '\\': value = '\\'; break; -			default: error("Invalid escape sequence."); -			} -		} -		else if(isNewline(ch)) -			error("Newline not alowed in character literal."); -		else -			value = ch; -		advanceChar(ErrorOnEOF.Yes); // Skip the character itself - -		if(ch == '\'') -			advanceChar(ErrorOnEOF.No); // Skip closing single-quote -		else -			error("Expected closing single-quote."); - -		mixin(accept!("Value", "value")); -	} -	 -	/// Lex base64 binary literal -	private void lexBinary() -	{ -		assert(ch == '['); -		advanceChar(ErrorOnEOF.Yes); -		 -		void eatBase64Whitespace() -		{ -			while(!isEOF && isWhite(ch)) -			{ -				if(isNewline(ch)) -					advanceChar(ErrorOnEOF.Yes); -				 -				if(!isEOF && isWhite(ch)) -					eatWhite(); -			} -		} -		 -		eatBase64Whitespace(); - -		// Iterates all valid base64 characters, ending at ']'. -		// Skips all whitespace. Throws on invalid chars. -		struct Base64InputRange -		{ -			Lexer lexer; -			private bool isInited = false; -			private int numInputCharsMod4 = 0; -			 -			@property bool empty() -			{ -				if(lexer.ch == ']') -				{ -					if(numInputCharsMod4 != 0) -						lexer.error("Length of Base64 encoding must be a multiple of 4. ("~to!string(numInputCharsMod4)~")"); -					 -					return true; -				} -				 -				return false; -			} - -			@property dchar front() -			{ -				return lexer.ch; -			} -			 -			void popFront() -			{ -				auto lex = lexer; - -				if(!isInited) -				{ -					if(lexer.isBase64(lexer.ch)) -					{ -						numInputCharsMod4++; -						numInputCharsMod4 %= 4; -					} -					 -					isInited = true; -				} -				 -				lex.advanceChar(lex.ErrorOnEOF.Yes); - -				eatBase64Whitespace(); -				 -				if(lex.isEOF) -					lex.error("Unexpected end of file."); - -				if(lex.ch != ']') -				{ -					if(!lex.isBase64(lex.ch)) -						lex.error("Invalid character in base64 binary literal."); -					 -					numInputCharsMod4++; -					numInputCharsMod4 %= 4; -				} -			} -		} -		 -		// This is a slow ugly hack. It's necessary because Base64.decode -		// currently requires the source to have known length. -		//TODO: Remove this when DMD issue #9543 is fixed. -		dchar[] tmpBuf = array(Base64InputRange(this)); - -		Appender!(ubyte[]) outputBuf; -		// Ugly workaround for DMD issue #9102 -		//TODO: Remove this when DMD #9102 is fixed -		struct OutputBuf -		{ -			void put(ubyte ch) -			{ -				outputBuf.put(ch); -			} -		} -		 -		try -			//Base64.decode(Base64InputRange(this), OutputBuf()); -			Base64.decode(tmpBuf, OutputBuf()); - -		catch(Base64Exception e) -			error("Invalid character in base64 binary literal."); -		 -		advanceChar(ErrorOnEOF.No); // Skip ']' -		mixin(accept!("Value", "outputBuf.data")); -	} -	 -	private BigInt toBigInt(bool isNegative, string absValue) -	{ -		auto num = BigInt(absValue); -		assert(num >= 0); - -		if(isNegative) -			num = -num; - -		return num; -	} - -	/// Lex [0-9]+, but without emitting a token. -	/// This is used by the other numeric parsing functions. -	private string lexNumericFragment() -	{ -		if(!isDigit(ch)) -			error("Expected a digit 0-9."); -		 -		auto spanStart = location.index; -		 -		do -		{ -			advanceChar(ErrorOnEOF.No); -		} while(!isEOF && isDigit(ch)); -		 -		return source[spanStart..location.index]; -	} - -	/// Lex anything that starts with 0-9 or '-'. Ints, floats, dates, etc. -	private void lexNumeric(LookaheadTokenInfo laTokenInfo = LookaheadTokenInfo.init) -	{ -		bool isNegative; -		string firstFragment; -		if(laTokenInfo.exists) -		{ -			firstFragment = laTokenInfo.numericFragment; -			isNegative    = laTokenInfo.isNegative; -		} -		else -		{ -			assert(ch == '-' || ch == '.' || isDigit(ch)); - -			// Check for negative -			isNegative = ch == '-'; -			if(isNegative) -				advanceChar(ErrorOnEOF.Yes); - -			// Some floating point with omitted leading zero? -			if(ch == '.') -			{ -				lexFloatingPoint(""); -				return; -			} - -			firstFragment = lexNumericFragment(); -		} - -		// Long integer (64-bit signed)? -		if(ch == 'L' || ch == 'l') -		{ -			advanceChar(ErrorOnEOF.No); - -			// BigInt(long.min) is a workaround for DMD issue #9548 -			auto num = toBigInt(isNegative, firstFragment); -			if(num < BigInt(long.min) || num > long.max) -				error(tokenStart, "Value doesn't fit in 64-bit signed long integer: "~to!string(num)); - -			mixin(accept!("Value", "num.toLong()")); -		} -		 -		// Float (32-bit signed)? -		else if(ch == 'F' || ch == 'f') -		{ -			auto value = to!float(tokenData); -			advanceChar(ErrorOnEOF.No); -			mixin(accept!("Value", "value")); -		} -		 -		// Double float (64-bit signed) with suffix? -		else if((ch == 'D' || ch == 'd') && !lookahead(':') -		) -		{ -			auto value = to!double(tokenData); -			advanceChar(ErrorOnEOF.No); -			mixin(accept!("Value", "value")); -		} -		 -		// Decimal (128+ bits signed)? -		else if( -			(ch == 'B' || ch == 'b') && -			(lookahead('D') || lookahead('d')) -		) -		{ -			auto value = to!real(tokenData); -			advanceChar(ErrorOnEOF.No); -			advanceChar(ErrorOnEOF.No); -			mixin(accept!("Value", "value")); -		} -		 -		// Some floating point? -		else if(ch == '.') -			lexFloatingPoint(firstFragment); -		 -		// Some date? -		else if(ch == '/' && hasNextCh && isDigit(nextCh)) -			lexDate(isNegative, firstFragment); -		 -		// Some time span? -		else if(ch == ':' || ch == 'd') -			lexTimeSpan(isNegative, firstFragment); - -		// Integer (32-bit signed)? -		else if(isEndOfNumber()) -		{ -			auto num = toBigInt(isNegative, firstFragment); -			if(num < int.min || num > int.max) -				error(tokenStart, "Value doesn't fit in 32-bit signed integer: "~to!string(num)); - -			mixin(accept!("Value", "num.toInt()")); -		} - -		// Invalid suffix -		else -			error("Invalid integer suffix."); -	} -	 -	/// Lex any floating-point literal (after the initial numeric fragment was lexed) -	private void lexFloatingPoint(string firstPart) -	{ -		assert(ch == '.'); -		advanceChar(ErrorOnEOF.No); -		 -		auto secondPart = lexNumericFragment(); -		 -		try -		{ -			// Double float (64-bit signed) with suffix? -			if(ch == 'D' || ch == 'd') -			{ -				auto value = to!double(tokenData); -				advanceChar(ErrorOnEOF.No); -				mixin(accept!("Value", "value")); -			} - -			// Float (32-bit signed)? -			else if(ch == 'F' || ch == 'f') -			{ -				auto value = to!float(tokenData); -				advanceChar(ErrorOnEOF.No); -				mixin(accept!("Value", "value")); -			} - -			// Decimal (128+ bits signed)? -			else if(ch == 'B' || ch == 'b') -			{ -				auto value = to!real(tokenData); -				advanceChar(ErrorOnEOF.Yes); - -				if(!isEOF && (ch == 'D' || ch == 'd')) -				{ -					advanceChar(ErrorOnEOF.No); -					if(isEndOfNumber()) -						mixin(accept!("Value", "value")); -				} - -				error("Invalid floating point suffix."); -			} - -			// Double float (64-bit signed) without suffix? -			else if(isEOF || !isIdentChar(ch)) -			{ -				auto value = to!double(tokenData); -				mixin(accept!("Value", "value")); -			} - -			// Invalid suffix -			else -				error("Invalid floating point suffix."); -		} -		catch(ConvException e) -			error("Invalid floating point literal."); -	} - -	private Date makeDate(bool isNegative, string yearStr, string monthStr, string dayStr) -	{ -		BigInt biTmp; -		 -		biTmp = BigInt(yearStr); -		if(isNegative) -			biTmp = -biTmp; -		if(biTmp < int.min || biTmp > int.max) -			error(tokenStart, "Date's year is out of range. (Must fit within a 32-bit signed int.)"); -		auto year = biTmp.toInt(); - -		biTmp = BigInt(monthStr); -		if(biTmp < 1 || biTmp > 12) -			error(tokenStart, "Date's month is out of range."); -		auto month = biTmp.toInt(); -		 -		biTmp = BigInt(dayStr); -		if(biTmp < 1 || biTmp > 31) -			error(tokenStart, "Date's month is out of range."); -		auto day = biTmp.toInt(); -		 -		return Date(year, month, day); -	} -	 -	private DateTimeFrac makeDateTimeFrac( -		bool isNegative, Date date, string hourStr, string minuteStr, -		string secondStr, string millisecondStr -	) -	{ -		BigInt biTmp; - -		biTmp = BigInt(hourStr); -		if(biTmp < int.min || biTmp > int.max) -			error(tokenStart, "Datetime's hour is out of range."); -		auto numHours = biTmp.toInt(); -		 -		biTmp = BigInt(minuteStr); -		if(biTmp < 0 || biTmp > int.max) -			error(tokenStart, "Datetime's minute is out of range."); -		auto numMinutes = biTmp.toInt(); -		 -		int numSeconds = 0; -		if(secondStr != "") -		{ -			biTmp = BigInt(secondStr); -			if(biTmp < 0 || biTmp > int.max) -				error(tokenStart, "Datetime's second is out of range."); -			numSeconds = biTmp.toInt(); -		} -		 -		int millisecond = 0; -		if(millisecondStr != "") -		{ -			biTmp = BigInt(millisecondStr); -			if(biTmp < 0 || biTmp > int.max) -				error(tokenStart, "Datetime's millisecond is out of range."); -			millisecond = biTmp.toInt(); - -			if(millisecondStr.length == 1) -				millisecond *= 100; -			else if(millisecondStr.length == 2) -				millisecond *= 10; -		} - -		Duration fracSecs = millisecond.msecs; -		 -		auto offset = hours(numHours) + minutes(numMinutes) + seconds(numSeconds); - -		if(isNegative) -		{ -			offset   = -offset; -			fracSecs = -fracSecs; -		} -		 -		return DateTimeFrac(DateTime(date) + offset, fracSecs); -	} - -	private Duration makeDuration( -		bool isNegative, string dayStr, -		string hourStr, string minuteStr, string secondStr, -		string millisecondStr -	) -	{ -		BigInt biTmp; - -		long day = 0; -		if(dayStr != "") -		{ -			biTmp = BigInt(dayStr); -			if(biTmp < long.min || biTmp > long.max) -				error(tokenStart, "Time span's day is out of range."); -			day = biTmp.toLong(); -		} - -		biTmp = BigInt(hourStr); -		if(biTmp < long.min || biTmp > long.max) -			error(tokenStart, "Time span's hour is out of range."); -		auto hour = biTmp.toLong(); - -		biTmp = BigInt(minuteStr); -		if(biTmp < long.min || biTmp > long.max) -			error(tokenStart, "Time span's minute is out of range."); -		auto minute = biTmp.toLong(); - -		biTmp = BigInt(secondStr); -		if(biTmp < long.min || biTmp > long.max) -			error(tokenStart, "Time span's second is out of range."); -		auto second = biTmp.toLong(); - -		long millisecond = 0; -		if(millisecondStr != "") -		{ -			biTmp = BigInt(millisecondStr); -			if(biTmp < long.min || biTmp > long.max) -				error(tokenStart, "Time span's millisecond is out of range."); -			millisecond = biTmp.toLong(); - -			if(millisecondStr.length == 1) -				millisecond *= 100; -			else if(millisecondStr.length == 2) -				millisecond *= 10; -		} -		 -		auto duration = -			dur!"days"   (day)    + -			dur!"hours"  (hour)   + -			dur!"minutes"(minute) + -			dur!"seconds"(second) + -			dur!"msecs"  (millisecond); - -		if(isNegative) -			duration = -duration; -		 -		return duration; -	} - -	// This has to reproduce some weird corner case behaviors from the -	// original Java version of SDL. So some of this may seem weird. -	private Nullable!Duration getTimeZoneOffset(string str) -	{ -		if(str.length < 2) -			return Nullable!Duration(); // Unknown timezone -		 -		if(str[0] != '+' && str[0] != '-') -			return Nullable!Duration(); // Unknown timezone - -		auto isNegative = str[0] == '-'; - -		string numHoursStr; -		string numMinutesStr; -		if(str[1] == ':') -		{ -			numMinutesStr = str[1..$]; -			numHoursStr = ""; -		} -		else -		{ -			numMinutesStr = str.find(':'); -			numHoursStr = str[1 .. $-numMinutesStr.length]; -		} -		 -		long numHours = 0; -		long numMinutes = 0; -		bool isUnknown = false; -		try -		{ -			switch(numHoursStr.length) -			{ -			case 0: -				if(numMinutesStr.length == 3) -				{ -					numHours   = 0; -					numMinutes = to!long(numMinutesStr[1..$]); -				} -				else -					isUnknown = true; -				break; - -			case 1: -			case 2: -				if(numMinutesStr.length == 0) -				{ -					numHours   = to!long(numHoursStr); -					numMinutes = 0; -				} -				else if(numMinutesStr.length == 3) -				{ -					numHours   = to!long(numHoursStr); -					numMinutes = to!long(numMinutesStr[1..$]); -				} -				else -					isUnknown = true; -				break; - -			default: -				if(numMinutesStr.length == 0) -				{ -					// Yes, this is correct -					numHours   = 0; -					numMinutes = to!long(numHoursStr[1..$]); -				} -				else -					isUnknown = true; -				break; -			} -		} -		catch(ConvException e) -			isUnknown = true; -		 -		if(isUnknown) -			return Nullable!Duration(); // Unknown timezone - -		auto timeZoneOffset = hours(numHours) + minutes(numMinutes); -		if(isNegative) -			timeZoneOffset = -timeZoneOffset; - -		// Timezone valid -		return Nullable!Duration(timeZoneOffset); -	} -	 -	/// Lex date or datetime (after the initial numeric fragment was lexed) -	private void lexDate(bool isDateNegative, string yearStr) -	{ -		assert(ch == '/'); -		 -		// Lex months -		advanceChar(ErrorOnEOF.Yes); // Skip '/' -		auto monthStr = lexNumericFragment(); - -		// Lex days -		if(ch != '/') -			error("Invalid date format: Missing days."); -		advanceChar(ErrorOnEOF.Yes); // Skip '/' -		auto dayStr = lexNumericFragment(); -		 -		auto date = makeDate(isDateNegative, yearStr, monthStr, dayStr); - -		if(!isEndOfNumber() && ch != '/') -			error("Dates cannot have suffixes."); -		 -		// Date? -		if(isEOF) -			mixin(accept!("Value", "date")); -		 -		auto endOfDate = location; -		 -		while( -			!isEOF && -			( ch == '\\' || ch == '/' || (isWhite(ch) && !isNewline(ch)) ) -		) -		{ -			if(ch == '\\' && hasNextCh && isNewline(nextCh)) -			{ -				advanceChar(ErrorOnEOF.Yes); -				if(isAtNewline()) -					advanceChar(ErrorOnEOF.Yes); -				advanceChar(ErrorOnEOF.No); -			} - -			eatWhite(); -		} - -		// Date? -		if(isEOF || (!isDigit(ch) && ch != '-')) -			mixin(accept!("Value", "date", "", "endOfDate.index")); -		 -		auto startOfTime = location; - -		// Is time negative? -		bool isTimeNegative = ch == '-'; -		if(isTimeNegative) -			advanceChar(ErrorOnEOF.Yes); - -		// Lex hours -		auto hourStr = ch == '.'? "" : lexNumericFragment(); -		 -		// Lex minutes -		if(ch != ':') -		{ -			// No minutes found. Therefore we had a plain Date followed -			// by a numeric literal, not a DateTime. -			lookaheadTokenInfo.exists          = true; -			lookaheadTokenInfo.numericFragment = hourStr; -			lookaheadTokenInfo.isNegative      = isTimeNegative; -			lookaheadTokenInfo.tokenStart      = startOfTime; -			mixin(accept!("Value", "date", "", "endOfDate.index")); -		} -		advanceChar(ErrorOnEOF.Yes); // Skip ':' -		auto minuteStr = lexNumericFragment(); -		 -		// Lex seconds, if exists -		string secondStr; -		if(ch == ':') -		{ -			advanceChar(ErrorOnEOF.Yes); // Skip ':' -			secondStr = lexNumericFragment(); -		} -		 -		// Lex milliseconds, if exists -		string millisecondStr; -		if(ch == '.') -		{ -			advanceChar(ErrorOnEOF.Yes); // Skip '.' -			millisecondStr = lexNumericFragment(); -		} - -		auto dateTimeFrac = makeDateTimeFrac(isTimeNegative, date, hourStr, minuteStr, secondStr, millisecondStr); -		 -		// Lex zone, if exists -		if(ch == '-') -		{ -			advanceChar(ErrorOnEOF.Yes); // Skip '-' -			auto timezoneStart = location; -			 -			if(!isAlpha(ch)) -				error("Invalid timezone format."); -			 -			while(!isEOF && !isWhite(ch)) -				advanceChar(ErrorOnEOF.No); -			 -			auto timezoneStr = source[timezoneStart.index..location.index]; -			if(timezoneStr.startsWith("GMT")) -			{ -				auto isoPart = timezoneStr["GMT".length..$]; -				auto offset = getTimeZoneOffset(isoPart); -				 -				if(offset.isNull()) -				{ -					// Unknown time zone -					mixin(accept!("Value", "DateTimeFracUnknownZone(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezoneStr)")); -				} -				else -				{ -					auto timezone = new immutable SimpleTimeZone(offset.get()); -					mixin(accept!("Value", "SysTime(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezone)")); -				} -			} -			 -			try -			{ -				auto timezone = TimeZone.getTimeZone(timezoneStr); -				if(timezone) -					mixin(accept!("Value", "SysTime(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezone)")); -			} -			catch(TimeException e) -			{ -				// Time zone not found. So just move along to "Unknown time zone" below. -			} - -			// Unknown time zone -			mixin(accept!("Value", "DateTimeFracUnknownZone(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezoneStr)")); -		} - -		if(!isEndOfNumber()) -			error("Date-Times cannot have suffixes."); - -		mixin(accept!("Value", "dateTimeFrac")); -	} - -	/// Lex time span (after the initial numeric fragment was lexed) -	private void lexTimeSpan(bool isNegative, string firstPart) -	{ -		assert(ch == ':' || ch == 'd'); -		 -		string dayStr = ""; -		string hourStr; - -		// Lexed days? -		bool hasDays = ch == 'd'; -		if(hasDays) -		{ -			dayStr = firstPart; -			advanceChar(ErrorOnEOF.Yes); // Skip 'd' - -			// Lex hours -			if(ch != ':') -				error("Invalid time span format: Missing hours."); -			advanceChar(ErrorOnEOF.Yes); // Skip ':' -			hourStr = lexNumericFragment(); -		} -		else -			hourStr = firstPart; - -		// Lex minutes -		if(ch != ':') -			error("Invalid time span format: Missing minutes."); -		advanceChar(ErrorOnEOF.Yes); // Skip ':' -		auto minuteStr = lexNumericFragment(); - -		// Lex seconds -		if(ch != ':') -			error("Invalid time span format: Missing seconds."); -		advanceChar(ErrorOnEOF.Yes); // Skip ':' -		auto secondStr = lexNumericFragment(); -		 -		// Lex milliseconds, if exists -		string millisecondStr = ""; -		if(ch == '.') -		{ -			advanceChar(ErrorOnEOF.Yes); // Skip '.' -			millisecondStr = lexNumericFragment(); -		} - -		if(!isEndOfNumber()) -			error("Time spans cannot have suffixes."); -		 -		auto duration = makeDuration(isNegative, dayStr, hourStr, minuteStr, secondStr, millisecondStr); -		mixin(accept!("Value", "duration")); -	} - -	/// Advances past whitespace and comments -	private void eatWhite(bool allowComments=true) -	{ -		// -- Comment/Whitepace Lexer ------------- - -		enum State -		{ -			normal, -			lineComment,  // Got "#" or "//" or "--", Eating everything until newline -			blockComment, // Got "/*", Eating everything until "*/" -		} - -		if(isEOF) -			return; -		 -		Location commentStart; -		State state = State.normal; -		bool consumeNewlines = false; -		bool hasConsumedNewline = false; -		while(true) -		{ -			final switch(state) -			{ -			case State.normal: - -				if(ch == '\\') -				{ -					commentStart = location; -					consumeNewlines = true; -					hasConsumedNewline = false; -				} - -				else if(ch == '#') -				{ -					if(!allowComments) -						return; - -					commentStart = location; -					state = State.lineComment; -					continue; -				} - -				else if(ch == '/' || ch == '-') -				{ -					commentStart = location; -					if(lookahead(ch)) -					{ -						if(!allowComments) -							return; - -						advanceChar(ErrorOnEOF.No); -						state = State.lineComment; -						continue; -					} -					else if(ch == '/' && lookahead('*')) -					{ -						if(!allowComments) -							return; - -						advanceChar(ErrorOnEOF.No); -						state = State.blockComment; -						continue; -					} -					else -						return; // Done -				} -				else if(isAtNewline()) -				{ -					if(consumeNewlines) -						hasConsumedNewline = true; -					else -						return; // Done -				} -				else if(!isWhite(ch)) -				{ -					if(consumeNewlines) -					{ -						if(hasConsumedNewline) -							return; // Done -						else -							error("Only whitespace can come between a line-continuation backslash and the following newline."); -					} -					else -						return; // Done -				} - -				break; -			 -			case State.lineComment: -				if(lookahead(&isNewline)) -					state = State.normal; -				break; -			 -			case State.blockComment: -				if(ch == '*' && lookahead('/')) -				{ -					advanceChar(ErrorOnEOF.No); -					state = State.normal; -				} -				break; -			} -			 -			advanceChar(ErrorOnEOF.No); -			if(isEOF) -			{ -				// Reached EOF - -				if(consumeNewlines && !hasConsumedNewline) -					error("Missing newline after line-continuation backslash."); - -				else if(state == State.blockComment) -					error(commentStart, "Unterminated block comment."); - -				else -					return; // Done, reached EOF -			} -		} -	} -} - -version(unittest) -{ -	import std.stdio; - -	version(Have_unit_threaded) import unit_threaded; -	else                        { enum DontTest; } - -	private auto loc  = Location("filename", 0, 0, 0); -	private auto loc2 = Location("a", 1, 1, 1); - -	@("lexer: EOL") -	unittest -	{ -		assert([Token(symbol!"EOL",loc)             ] == [Token(symbol!"EOL",loc)              ] ); -		assert([Token(symbol!"EOL",loc,Value(7),"A")] == [Token(symbol!"EOL",loc2,Value(7),"B")] ); -	} - -	private int numErrors = 0; -	@DontTest -	private void testLex(string source, Token[] expected, bool test_locations = false, string file=__FILE__, size_t line=__LINE__) -	{ -		Token[] actual; -		try -			actual = lexSource(source, "filename"); -		catch(ParseException e) -		{ -			numErrors++; -			stderr.writeln(file, "(", line, "): testLex failed on: ", source); -			stderr.writeln("    Expected:"); -			stderr.writeln("        ", expected); -			stderr.writeln("    Actual: ParseException thrown:"); -			stderr.writeln("        ", e.msg); -			return; -		} - -		bool is_same = actual == expected; -		if (is_same && test_locations) { -			is_same = actual.map!(t => t.location).equal(expected.map!(t => t.location)); -		} -		 -		if(!is_same) -		{ -			numErrors++; -			stderr.writeln(file, "(", line, "): testLex failed on: ", source); -			stderr.writeln("    Expected:"); -			stderr.writeln("        ", expected); -			stderr.writeln("    Actual:"); -			stderr.writeln("        ", actual); - -			if(expected.length > 1 || actual.length > 1) -			{ -				stderr.writeln("    expected.length: ", expected.length); -				stderr.writeln("    actual.length:   ", actual.length); - -				if(actual.length == expected.length) -				foreach(i; 0..actual.length) -				if(actual[i] != expected[i]) -				{ -					stderr.writeln("    Unequal at index #", i, ":"); -					stderr.writeln("        Expected:"); -					stderr.writeln("            ", expected[i]); -					stderr.writeln("        Actual:"); -					stderr.writeln("            ", actual[i]); -				} -			} -		} -	} - -	private void testLexThrows(string file=__FILE__, size_t line=__LINE__)(string source) -	{ -		bool hadException = false; -		Token[] actual; -		try -			actual = lexSource(source, "filename"); -		catch(ParseException e) -			hadException = true; - -		if(!hadException) -		{ -			numErrors++; -			stderr.writeln(file, "(", line, "): testLex failed on: ", source); -			stderr.writeln("    Expected ParseException"); -			stderr.writeln("    Actual:"); -			stderr.writeln("        ", actual); -		} -	} -} - -@("sdlang lexer") -unittest -{ -	testLex("",        []); -	testLex(" ",       []); -	testLex("\\\n",    []); -	testLex("/*foo*/", []); -	testLex("/* multiline \n comment */", []); -	testLex("/* * */", []); -	testLexThrows("/* "); - -	testLex(":",  [ Token(symbol!":",  loc) ]); -	testLex("=",  [ Token(symbol!"=",  loc) ]); -	testLex("{",  [ Token(symbol!"{",  loc) ]); -	testLex("}",  [ Token(symbol!"}",  loc) ]); -	testLex(";",  [ Token(symbol!"EOL",loc) ]); -	testLex("\n", [ Token(symbol!"EOL",loc) ]); - -	testLex("foo",     [ Token(symbol!"Ident",loc,Value(null),"foo")     ]); -	testLex("_foo",    [ Token(symbol!"Ident",loc,Value(null),"_foo")    ]); -	testLex("foo.bar", [ Token(symbol!"Ident",loc,Value(null),"foo.bar") ]); -	testLex("foo-bar", [ Token(symbol!"Ident",loc,Value(null),"foo-bar") ]); -	testLex("foo.",    [ Token(symbol!"Ident",loc,Value(null),"foo.")    ]); -	testLex("foo-",    [ Token(symbol!"Ident",loc,Value(null),"foo-")    ]); -	testLexThrows(".foo"); - -	testLex("foo bar", [ -		Token(symbol!"Ident",loc,Value(null),"foo"), -		Token(symbol!"Ident",loc,Value(null),"bar"), -	]); -	testLex("foo \\  \n  \n  bar", [ -		Token(symbol!"Ident",loc,Value(null),"foo"), -		Token(symbol!"Ident",loc,Value(null),"bar"), -	]); -	testLex("foo \\  \n \\ \n  bar", [ -		Token(symbol!"Ident",loc,Value(null),"foo"), -		Token(symbol!"Ident",loc,Value(null),"bar"), -	]); -	testLexThrows("foo \\ "); -	testLexThrows("foo \\ bar"); -	testLexThrows("foo \\  \n  \\ "); -	testLexThrows("foo \\  \n  \\ bar"); - -	testLex("foo : = { } ; \n bar \n", [ -		Token(symbol!"Ident",loc,Value(null),"foo"), -		Token(symbol!":",loc), -		Token(symbol!"=",loc), -		Token(symbol!"{",loc), -		Token(symbol!"}",loc), -		Token(symbol!"EOL",loc), -		Token(symbol!"EOL",loc), -		Token(symbol!"Ident",loc,Value(null),"bar"), -		Token(symbol!"EOL",loc), -	]); - -	testLexThrows("<"); -	testLexThrows("*"); -	testLexThrows(`\`); -	 -	// Integers -	testLex(  "7", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]); -	testLex( "-7", [ Token(symbol!"Value",loc,Value(cast( int)-7)) ]); -	testLex( "7L", [ Token(symbol!"Value",loc,Value(cast(long) 7)) ]); -	testLex( "7l", [ Token(symbol!"Value",loc,Value(cast(long) 7)) ]); -	testLex("-7L", [ Token(symbol!"Value",loc,Value(cast(long)-7)) ]); -	testLex(  "0", [ Token(symbol!"Value",loc,Value(cast( int) 0)) ]); -	testLex( "-0", [ Token(symbol!"Value",loc,Value(cast( int) 0)) ]); - -	testLex("7/**/", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]); -	testLex("7#",    [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]); - -	testLex("7 A", [ -		Token(symbol!"Value",loc,Value(cast(int)7)), -		Token(symbol!"Ident",loc,Value(      null),"A"), -	]); -	testLexThrows("7A"); -	testLexThrows("-A"); -	testLexThrows(`-""`); -	 -	testLex("7;", [ -		Token(symbol!"Value",loc,Value(cast(int)7)), -		Token(symbol!"EOL",loc), -	]); -	 -	// Floats -	testLex("1.2F" , [ Token(symbol!"Value",loc,Value(cast( float)1.2)) ]); -	testLex("1.2f" , [ Token(symbol!"Value",loc,Value(cast( float)1.2)) ]); -	testLex("1.2"  , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]); -	testLex("1.2D" , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]); -	testLex("1.2d" , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]); -	testLex("1.2BD", [ Token(symbol!"Value",loc,Value(cast(  real)1.2)) ]); -	testLex("1.2bd", [ Token(symbol!"Value",loc,Value(cast(  real)1.2)) ]); -	testLex("1.2Bd", [ Token(symbol!"Value",loc,Value(cast(  real)1.2)) ]); -	testLex("1.2bD", [ Token(symbol!"Value",loc,Value(cast(  real)1.2)) ]); - -	testLex(".2F" , [ Token(symbol!"Value",loc,Value(cast( float)0.2)) ]); -	testLex(".2"  , [ Token(symbol!"Value",loc,Value(cast(double)0.2)) ]); -	testLex(".2D" , [ Token(symbol!"Value",loc,Value(cast(double)0.2)) ]); -	testLex(".2BD", [ Token(symbol!"Value",loc,Value(cast(  real)0.2)) ]); - -	testLex("-1.2F" , [ Token(symbol!"Value",loc,Value(cast( float)-1.2)) ]); -	testLex("-1.2"  , [ Token(symbol!"Value",loc,Value(cast(double)-1.2)) ]); -	testLex("-1.2D" , [ Token(symbol!"Value",loc,Value(cast(double)-1.2)) ]); -	testLex("-1.2BD", [ Token(symbol!"Value",loc,Value(cast(  real)-1.2)) ]); - -	testLex("-.2F" , [ Token(symbol!"Value",loc,Value(cast( float)-0.2)) ]); -	testLex("-.2"  , [ Token(symbol!"Value",loc,Value(cast(double)-0.2)) ]); -	testLex("-.2D" , [ Token(symbol!"Value",loc,Value(cast(double)-0.2)) ]); -	testLex("-.2BD", [ Token(symbol!"Value",loc,Value(cast(  real)-0.2)) ]); - -	testLex( "0.0"  , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); -	testLex( "0.0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); -	testLex( "0.0BD", [ Token(symbol!"Value",loc,Value(cast(  real)0.0)) ]); -	testLex("-0.0"  , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); -	testLex("-0.0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); -	testLex("-0.0BD", [ Token(symbol!"Value",loc,Value(cast(  real)0.0)) ]); -	testLex( "7F"   , [ Token(symbol!"Value",loc,Value(cast( float)7.0)) ]); -	testLex( "7D"   , [ Token(symbol!"Value",loc,Value(cast(double)7.0)) ]); -	testLex( "7BD"  , [ Token(symbol!"Value",loc,Value(cast(  real)7.0)) ]); -	testLex( "0F"   , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); -	testLex( "0D"   , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); -	testLex( "0BD"  , [ Token(symbol!"Value",loc,Value(cast(  real)0.0)) ]); -	testLex("-0F"   , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); -	testLex("-0D"   , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); -	testLex("-0BD"  , [ Token(symbol!"Value",loc,Value(cast(  real)0.0)) ]); - -	testLex("1.2 F", [ -		Token(symbol!"Value",loc,Value(cast(double)1.2)), -		Token(symbol!"Ident",loc,Value(           null),"F"), -	]); -	testLexThrows("1.2A"); -	testLexThrows("1.2B"); -	testLexThrows("1.2BDF"); - -	testLex("1.2;", [ -		Token(symbol!"Value",loc,Value(cast(double)1.2)), -		Token(symbol!"EOL",loc), -	]); - -	testLex("1.2F;", [ -		Token(symbol!"Value",loc,Value(cast(float)1.2)), -		Token(symbol!"EOL",loc), -	]); - -	testLex("1.2BD;", [ -		Token(symbol!"Value",loc,Value(cast(real)1.2)), -		Token(symbol!"EOL",loc), -	]); - -	// Booleans and null -	testLex("true",   [ Token(symbol!"Value",loc,Value( true)) ]); -	testLex("false",  [ Token(symbol!"Value",loc,Value(false)) ]); -	testLex("on",     [ Token(symbol!"Value",loc,Value( true)) ]); -	testLex("off",    [ Token(symbol!"Value",loc,Value(false)) ]); -	testLex("null",   [ Token(symbol!"Value",loc,Value( null)) ]); - -	testLex("TRUE",   [ Token(symbol!"Ident",loc,Value(null),"TRUE")  ]); -	testLex("true ",  [ Token(symbol!"Value",loc,Value(true)) ]); -	testLex("true  ", [ Token(symbol!"Value",loc,Value(true)) ]); -	testLex("tru",    [ Token(symbol!"Ident",loc,Value(null),"tru")   ]); -	testLex("truX",   [ Token(symbol!"Ident",loc,Value(null),"truX")  ]); -	testLex("trueX",  [ Token(symbol!"Ident",loc,Value(null),"trueX") ]); - -	// Raw Backtick Strings -	testLex("`hello world`",      [ Token(symbol!"Value",loc,Value(`hello world`   )) ]); -	testLex("` hello world `",    [ Token(symbol!"Value",loc,Value(` hello world ` )) ]); -	testLex("`hello \\t world`",  [ Token(symbol!"Value",loc,Value(`hello \t world`)) ]); -	testLex("`hello \\n world`",  [ Token(symbol!"Value",loc,Value(`hello \n world`)) ]); -	testLex("`hello \n world`",   [ Token(symbol!"Value",loc,Value("hello \n world")) ]); -	testLex("`hello \r\n world`", [ Token(symbol!"Value",loc,Value("hello \r\n world")) ]); -	testLex("`hello \"world\"`",  [ Token(symbol!"Value",loc,Value(`hello "world"` )) ]); - -	testLexThrows("`foo"); -	testLexThrows("`"); - -	// Double-Quote Strings -	testLex(`"hello world"`,            [ Token(symbol!"Value",loc,Value("hello world"   )) ]); -	testLex(`" hello world "`,          [ Token(symbol!"Value",loc,Value(" hello world " )) ]); -	testLex(`"hello \t world"`,         [ Token(symbol!"Value",loc,Value("hello \t world")) ]); -	testLex(`"hello \n world"`,         [ Token(symbol!"Value",loc,Value("hello \n world")) ]); -	testLex("\"hello \\\n world\"",     [ Token(symbol!"Value",loc,Value("hello world"   )) ]); -	testLex("\"hello \\  \n world\"",   [ Token(symbol!"Value",loc,Value("hello world"   )) ]); -	testLex("\"hello \\  \n\n world\"", [ Token(symbol!"Value",loc,Value("hello world"   )) ]); -	testLex(`"\"hello world\""`,        [ Token(symbol!"Value",loc,Value(`"hello world"` )) ]); -	testLex(`""`,                       [ Token(symbol!"Value",loc,Value(""              )) ]); // issue #34 - -	testLexThrows("\"hello \n world\""); -	testLexThrows(`"foo`); -	testLexThrows(`"`); - -	// Characters -	testLex("'a'",   [ Token(symbol!"Value",loc,Value(cast(dchar) 'a')) ]); -	testLex("'\\n'", [ Token(symbol!"Value",loc,Value(cast(dchar)'\n')) ]); -	testLex("'\\t'", [ Token(symbol!"Value",loc,Value(cast(dchar)'\t')) ]); -	testLex("'\t'",  [ Token(symbol!"Value",loc,Value(cast(dchar)'\t')) ]); -	testLex("'\\''", [ Token(symbol!"Value",loc,Value(cast(dchar)'\'')) ]); -	testLex(`'\\'`,  [ Token(symbol!"Value",loc,Value(cast(dchar)'\\')) ]); - -	testLexThrows("'a"); -	testLexThrows("'aa'"); -	testLexThrows("''"); -	testLexThrows("'\\\n'"); -	testLexThrows("'\n'"); -	testLexThrows(`'\`); -	testLexThrows(`'\'`); -	testLexThrows("'"); -	 -	// Unicode -	testLex("日本語",         [ Token(symbol!"Ident",loc,Value(null), "日本語") ]); -	testLex("`おはよう、日本。`", [ Token(symbol!"Value",loc,Value(`おはよう、日本。`)) ]); -	testLex(`"おはよう、日本。"`, [ Token(symbol!"Value",loc,Value(`おはよう、日本。`)) ]); -	testLex("'月'",           [ Token(symbol!"Value",loc,Value("月"d.dup[0]))   ]); - -	// Base64 Binary -	testLex("[aGVsbG8gd29ybGQ=]",              [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]); -	testLex("[ aGVsbG8gd29ybGQ= ]",            [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]); -	testLex("[\n aGVsbG8g \n \n d29ybGQ= \n]", [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]); - -	testLexThrows("[aGVsbG8gd29ybGQ]"); // Ie: Not multiple of 4 -	testLexThrows("[ aGVsbG8gd29ybGQ ]"); - -	// Date -	testLex( "1999/12/5", [ Token(symbol!"Value",loc,Value(Date( 1999, 12, 5))) ]); -	testLex( "2013/2/22", [ Token(symbol!"Value",loc,Value(Date( 2013, 2, 22))) ]); -	testLex("-2013/2/22", [ Token(symbol!"Value",loc,Value(Date(-2013, 2, 22))) ]); - -	testLexThrows("7/"); -	testLexThrows("2013/2/22a"); -	testLexThrows("2013/2/22f"); - -	testLex("1999/12/5\n", [ -		Token(symbol!"Value",loc,Value(Date(1999, 12, 5))), -		Token(symbol!"EOL",loc), -	]); - -	// DateTime, no timezone -	testLex( "2013/2/22 07:53",        [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53,  0)))) ]); -	testLex( "2013/2/22 \t 07:53",     [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53,  0)))) ]); -	testLex( "2013/2/22/*foo*/07:53",  [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53,  0)))) ]); -	testLex( "2013/2/22 /*foo*/ \\\n  /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53,  0)))) ]); -	testLex( "2013/2/22 /*foo*/ \\\n\n  \n  /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53,  0)))) ]); -	testLex( "2013/2/22 /*foo*/ \\\n\\\n  \\\n  /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53,  0)))) ]); -	testLex( "2013/2/22/*foo*/\\\n/*bar*/07:53",      [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53,  0)))) ]); -	testLex("-2013/2/22 07:53",        [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime(-2013, 2, 22, 7, 53,  0)))) ]); -	testLex( "2013/2/22 -07:53",       [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0,  0,  0) - hours(7) - minutes(53)))) ]); -	testLex("-2013/2/22 -07:53",       [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime(-2013, 2, 22, 0,  0,  0) - hours(7) - minutes(53)))) ]); -	testLex( "2013/2/22 07:53:34",     [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34)))) ]); -	testLex( "2013/2/22 07:53:34.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs))) ]); -	testLex( "2013/2/22 07:53:34.12",  [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 120.msecs))) ]); -	testLex( "2013/2/22 07:53:34.1",   [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 100.msecs))) ]); -	testLex( "2013/2/22 07:53.123",    [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53,  0), 123.msecs))) ]); - -	testLex( "2013/2/22 34:65",        [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds( 0)))) ]); -	testLex( "2013/2/22 34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds(77), 123.msecs))) ]); -	testLex( "2013/2/22 34:65.123",    [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds( 0), 123.msecs))) ]); - -	testLex( "2013/2/22 -34:65",        [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0)))) ]); -	testLex( "2013/2/22 -34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds(77), -123.msecs))) ]); -	testLex( "2013/2/22 -34:65.123",    [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0), -123.msecs))) ]); - -	testLexThrows("2013/2/22 07:53a"); -	testLexThrows("2013/2/22 07:53f"); -	testLexThrows("2013/2/22 07:53:34.123a"); -	testLexThrows("2013/2/22 07:53:34.123f"); -	testLexThrows("2013/2/22a 07:53"); - -	testLex(`2013/2/22 "foo"`, [ -		Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), -		Token(symbol!"Value",loc,Value("foo")), -	]); - -	testLex("2013/2/22 07", [ -		Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), -		Token(symbol!"Value",loc,Value(cast(int)7)), -	]); - -	testLex("2013/2/22 1.2F", [ -		Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), -		Token(symbol!"Value",loc,Value(cast(float)1.2)), -	]); - -	testLex("2013/2/22 .2F", [ -		Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), -		Token(symbol!"Value",loc,Value(cast(float)0.2)), -	]); - -	testLex("2013/2/22 -1.2F", [ -		Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), -		Token(symbol!"Value",loc,Value(cast(float)-1.2)), -	]); - -	testLex("2013/2/22 -.2F", [ -		Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), -		Token(symbol!"Value",loc,Value(cast(float)-0.2)), -	]); - -	// DateTime, with known timezone -	testLex( "2013/2/22 07:53-GMT+00:00",        [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53,  0), new immutable SimpleTimeZone( hours(0)            )))) ]); -	testLex("-2013/2/22 07:53-GMT+00:00",        [ Token(symbol!"Value",loc,Value(SysTime(DateTime(-2013, 2, 22, 7, 53,  0), new immutable SimpleTimeZone( hours(0)            )))) ]); -	testLex( "2013/2/22 -07:53-GMT+00:00",       [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 0,  0,  0) - hours(7) - minutes(53), new immutable SimpleTimeZone( hours(0)            )))) ]); -	testLex("-2013/2/22 -07:53-GMT+00:00",       [ Token(symbol!"Value",loc,Value(SysTime(DateTime(-2013, 2, 22, 0,  0,  0) - hours(7) - minutes(53), new immutable SimpleTimeZone( hours(0)            )))) ]); -	testLex( "2013/2/22 07:53-GMT+02:10",        [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53,  0), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); -	testLex( "2013/2/22 07:53-GMT-05:30",        [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53,  0), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); -	testLex( "2013/2/22 07:53:34-GMT+00:00",     [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone( hours(0)            )))) ]); -	testLex( "2013/2/22 07:53:34-GMT+02:10",     [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); -	testLex( "2013/2/22 07:53:34-GMT-05:30",     [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); -	testLex( "2013/2/22 07:53:34.123-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone( hours(0)            )))) ]); -	testLex( "2013/2/22 07:53:34.123-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); -	testLex( "2013/2/22 07:53:34.123-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); -	testLex( "2013/2/22 07:53.123-GMT+00:00",    [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53,  0), 123.msecs, new immutable SimpleTimeZone( hours(0)            )))) ]); -	testLex( "2013/2/22 07:53.123-GMT+02:10",    [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53,  0), 123.msecs, new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); -	testLex( "2013/2/22 07:53.123-GMT-05:30",    [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53,  0), 123.msecs, new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - -	testLex( "2013/2/22 -34:65-GMT-05:30",       [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 0,  0,  0) - hours(34) - minutes(65) - seconds( 0), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - -	// DateTime, with Java SDLang's occasionally weird interpretation of some -	// "not quite ISO" variations of the "GMT with offset" timezone strings. -	Token testTokenSimpleTimeZone(Duration d) -	{ -		auto dateTime = DateTime(2013, 2, 22, 7, 53, 0); -		auto tz = new immutable SimpleTimeZone(d); -		return Token( symbol!"Value", loc, Value(SysTime(dateTime,tz)) ); -	} -	Token testTokenUnknownTimeZone(string tzName) -	{ -		auto dateTime = DateTime(2013, 2, 22, 7, 53, 0); -		auto frac = 0.msecs; -		return Token( symbol!"Value", loc, Value(DateTimeFracUnknownZone(dateTime,frac,tzName)) ); -	} -	testLex("2013/2/22 07:53-GMT+",          [ testTokenUnknownTimeZone("GMT+")     ]); -	testLex("2013/2/22 07:53-GMT+:",         [ testTokenUnknownTimeZone("GMT+:")    ]); -	testLex("2013/2/22 07:53-GMT+:3",        [ testTokenUnknownTimeZone("GMT+:3")   ]); -	testLex("2013/2/22 07:53-GMT+:03",       [ testTokenSimpleTimeZone(minutes(3))  ]); -	testLex("2013/2/22 07:53-GMT+:003",      [ testTokenUnknownTimeZone("GMT+:003") ]); - -	testLex("2013/2/22 07:53-GMT+4",         [ testTokenSimpleTimeZone(hours(4))            ]); -	testLex("2013/2/22 07:53-GMT+4:",        [ testTokenUnknownTimeZone("GMT+4:")           ]); -	testLex("2013/2/22 07:53-GMT+4:3",       [ testTokenUnknownTimeZone("GMT+4:3")          ]); -	testLex("2013/2/22 07:53-GMT+4:03",      [ testTokenSimpleTimeZone(hours(4)+minutes(3)) ]); -	testLex("2013/2/22 07:53-GMT+4:003",     [ testTokenUnknownTimeZone("GMT+4:003")        ]); - -	testLex("2013/2/22 07:53-GMT+04",        [ testTokenSimpleTimeZone(hours(4))            ]); -	testLex("2013/2/22 07:53-GMT+04:",       [ testTokenUnknownTimeZone("GMT+04:")          ]); -	testLex("2013/2/22 07:53-GMT+04:3",      [ testTokenUnknownTimeZone("GMT+04:3")         ]); -	testLex("2013/2/22 07:53-GMT+04:03",     [ testTokenSimpleTimeZone(hours(4)+minutes(3)) ]); -	testLex("2013/2/22 07:53-GMT+04:03abc",  [ testTokenUnknownTimeZone("GMT+04:03abc")     ]); -	testLex("2013/2/22 07:53-GMT+04:003",    [ testTokenUnknownTimeZone("GMT+04:003")       ]); - -	testLex("2013/2/22 07:53-GMT+004",       [ testTokenSimpleTimeZone(minutes(4))     ]); -	testLex("2013/2/22 07:53-GMT+004:",      [ testTokenUnknownTimeZone("GMT+004:")    ]); -	testLex("2013/2/22 07:53-GMT+004:3",     [ testTokenUnknownTimeZone("GMT+004:3")   ]); -	testLex("2013/2/22 07:53-GMT+004:03",    [ testTokenUnknownTimeZone("GMT+004:03")  ]); -	testLex("2013/2/22 07:53-GMT+004:003",   [ testTokenUnknownTimeZone("GMT+004:003") ]); - -	testLex("2013/2/22 07:53-GMT+0004",      [ testTokenSimpleTimeZone(minutes(4))      ]); -	testLex("2013/2/22 07:53-GMT+0004:",     [ testTokenUnknownTimeZone("GMT+0004:")    ]); -	testLex("2013/2/22 07:53-GMT+0004:3",    [ testTokenUnknownTimeZone("GMT+0004:3")   ]); -	testLex("2013/2/22 07:53-GMT+0004:03",   [ testTokenUnknownTimeZone("GMT+0004:03")  ]); -	testLex("2013/2/22 07:53-GMT+0004:003",  [ testTokenUnknownTimeZone("GMT+0004:003") ]); - -	testLex("2013/2/22 07:53-GMT+00004",     [ testTokenSimpleTimeZone(minutes(4))       ]); -	testLex("2013/2/22 07:53-GMT+00004:",    [ testTokenUnknownTimeZone("GMT+00004:")    ]); -	testLex("2013/2/22 07:53-GMT+00004:3",   [ testTokenUnknownTimeZone("GMT+00004:3")   ]); -	testLex("2013/2/22 07:53-GMT+00004:03",  [ testTokenUnknownTimeZone("GMT+00004:03")  ]); -	testLex("2013/2/22 07:53-GMT+00004:003", [ testTokenUnknownTimeZone("GMT+00004:003") ]); - -	// DateTime, with unknown timezone -	testLex( "2013/2/22 07:53-Bogus/Foo",        [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53,  0),   0.msecs, "Bogus/Foo")), "2013/2/22 07:53-Bogus/Foo") ]); -	testLex("-2013/2/22 07:53-Bogus/Foo",        [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 7, 53,  0),   0.msecs, "Bogus/Foo"))) ]); -	testLex( "2013/2/22 -07:53-Bogus/Foo",       [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 0,  0,  0) - hours(7) - minutes(53), 0.msecs, "Bogus/Foo"))) ]); -	testLex("-2013/2/22 -07:53-Bogus/Foo",       [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 0,  0,  0) - hours(7) - minutes(53), 0.msecs, "Bogus/Foo"))) ]); -	testLex( "2013/2/22 07:53:34-Bogus/Foo",     [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34),   0.msecs, "Bogus/Foo"))) ]); -	testLex( "2013/2/22 07:53:34.123-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, "Bogus/Foo"))) ]); -	testLex( "2013/2/22 07:53.123-Bogus/Foo",    [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53,  0), 123.msecs, "Bogus/Foo"))) ]); - -	// Time Span -	testLex( "12:14:42",         [ Token(symbol!"Value",loc,Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs(  0))) ]); -	testLex("-12:14:42",         [ Token(symbol!"Value",loc,Value(-days( 0)-hours(12)-minutes(14)-seconds(42)-msecs(  0))) ]); -	testLex( "00:09:12",         [ Token(symbol!"Value",loc,Value( days( 0)+hours( 0)+minutes( 9)+seconds(12)+msecs(  0))) ]); -	testLex( "00:00:01.023",     [ Token(symbol!"Value",loc,Value( days( 0)+hours( 0)+minutes( 0)+seconds( 1)+msecs( 23))) ]); -	testLex( "23d:05:21:23.532", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(532))) ]); -	testLex( "23d:05:21:23.53",  [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(530))) ]); -	testLex( "23d:05:21:23.5",   [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(500))) ]); -	testLex("-23d:05:21:23.532", [ Token(symbol!"Value",loc,Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(532))) ]); -	testLex("-23d:05:21:23.5",   [ Token(symbol!"Value",loc,Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(500))) ]); -	testLex( "23d:05:21:23",     [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(  0))) ]); - -	testLexThrows("12:14:42a"); -	testLexThrows("23d:05:21:23.532a"); -	testLexThrows("23d:05:21:23.532f"); - -	// Combination -	testLex("foo. 7", [ -		Token(symbol!"Ident",loc,Value(      null),"foo."), -		Token(symbol!"Value",loc,Value(cast(int)7)) -	]); -	 -	testLex(` -		namespace:person "foo" "bar" 1 23L name.first="ひとみ" name.last="Smith" { -			namespace:age 37; namespace:favorite_color "blue" // comment -			somedate 2013/2/22  07:53 -- comment -			 -			inventory /* comment */ { -				socks -			} -		} -	`, -	[ -		Token(symbol!"EOL",loc,Value(null),"\n"), - -		Token(symbol!"Ident", loc, Value(         null ), "namespace"), -		Token(symbol!":",     loc, Value(         null ), ":"), -		Token(symbol!"Ident", loc, Value(         null ), "person"), -		Token(symbol!"Value", loc, Value(        "foo" ), `"foo"`), -		Token(symbol!"Value", loc, Value(        "bar" ), `"bar"`), -		Token(symbol!"Value", loc, Value( cast( int) 1 ), "1"), -		Token(symbol!"Value", loc, Value( cast(long)23 ), "23L"), -		Token(symbol!"Ident", loc, Value(         null ), "name.first"), -		Token(symbol!"=",     loc, Value(         null ), "="), -		Token(symbol!"Value", loc, Value(       "ひとみ" ), `"ひとみ"`), -		Token(symbol!"Ident", loc, Value(         null ), "name.last"), -		Token(symbol!"=",     loc, Value(         null ), "="), -		Token(symbol!"Value", loc, Value(      "Smith" ), `"Smith"`), -		Token(symbol!"{",     loc, Value(         null ), "{"), -		Token(symbol!"EOL",   loc, Value(         null ), "\n"), - -		Token(symbol!"Ident", loc, Value(        null ), "namespace"), -		Token(symbol!":",     loc, Value(        null ), ":"), -		Token(symbol!"Ident", loc, Value(        null ), "age"), -		Token(symbol!"Value", loc, Value( cast(int)37 ), "37"), -		Token(symbol!"EOL",   loc, Value(        null ), ";"), -		Token(symbol!"Ident", loc, Value(        null ), "namespace"), -		Token(symbol!":",     loc, Value(        null ), ":"), -		Token(symbol!"Ident", loc, Value(        null ), "favorite_color"), -		Token(symbol!"Value", loc, Value(      "blue" ), `"blue"`), -		Token(symbol!"EOL",   loc, Value(        null ), "\n"), - -		Token(symbol!"Ident", loc, Value( null ), "somedate"), -		Token(symbol!"Value", loc, Value( DateTimeFrac(DateTime(2013, 2, 22, 7, 53, 0)) ), "2013/2/22  07:53"), -		Token(symbol!"EOL",   loc, Value( null ), "\n"), -		Token(symbol!"EOL",   loc, Value( null ), "\n"), - -		Token(symbol!"Ident", loc, Value(null), "inventory"), -		Token(symbol!"{",     loc, Value(null), "{"), -		Token(symbol!"EOL",   loc, Value(null), "\n"), - -		Token(symbol!"Ident", loc, Value(null), "socks"), -		Token(symbol!"EOL",   loc, Value(null), "\n"), - -		Token(symbol!"}",     loc, Value(null), "}"), -		Token(symbol!"EOL",   loc, Value(null), "\n"), - -		Token(symbol!"}",     loc, Value(null), "}"), -		Token(symbol!"EOL",   loc, Value(null), "\n"), -	]); -	 -	if(numErrors > 0) -		stderr.writeln(numErrors, " failed test(s)"); -} - -@("lexer: Regression test issue #8") -unittest -{ -	testLex(`"\n \n"`, [ Token(symbol!"Value",loc,Value("\n \n"),`"\n \n"`) ]); -	testLex(`"\t\t"`, [ Token(symbol!"Value",loc,Value("\t\t"),`"\t\t"`) ]); -	testLex(`"\n\n"`, [ Token(symbol!"Value",loc,Value("\n\n"),`"\n\n"`) ]); -} - -@("lexer: Regression test issue #11") -unittest -{ -	void test(string input) -	{ -		testLex( -			input, -			[ -				Token(symbol!"EOL", loc, Value(null), "\n"), -				Token(symbol!"Ident",loc,Value(null), "a") -			] -		); -	} - -	test("//X\na"); -	test("//\na"); -	test("--\na"); -	test("#\na"); -} - -@("ast: Regression test issue #28") -unittest -{ -	enum offset = 1; // workaround for an of-by-one error for line numbers -	testLex("test", [ -		Token(symbol!"Ident", Location("filename", 0, 0, 0), Value(null), "test") -	], true); -	testLex("\ntest", [ -		Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\n"), -		Token(symbol!"Ident", Location("filename", 1, 0, 1), Value(null), "test") -	], true); -	testLex("\rtest", [ -		Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r"), -		Token(symbol!"Ident", Location("filename", 1, 0, 1), Value(null), "test") -	], true); -	testLex("\r\ntest", [ -		Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r\n"), -		Token(symbol!"Ident", Location("filename", 1, 0, 2), Value(null), "test") -	], true); -	testLex("\r\n\ntest", [ -		Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r\n"), -		Token(symbol!"EOL", Location("filename", 1, 0, 2), Value(null), "\n"), -		Token(symbol!"Ident", Location("filename", 2, 0, 3), Value(null), "test") -	], true); -	testLex("\r\r\ntest", [ -		Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r"), -		Token(symbol!"EOL", Location("filename", 1, 0, 1), Value(null), "\r\n"), -		Token(symbol!"Ident", Location("filename", 2, 0, 3), Value(null), "test") -	], true); -} diff --git a/src/sdlang/libinputvisitor/dub.json b/src/sdlang/libinputvisitor/dub.json deleted file mode 100644 index 6e273c8..0000000 --- a/src/sdlang/libinputvisitor/dub.json +++ /dev/null @@ -1,10 +0,0 @@ -{ -	"name": "libinputvisitor", -	"description": "Write D input range generators in a straightforward coroutine style", -	"authors": ["Nick Sabalausky"], -	"homepage": "https://github.com/abscissa/libInputVisitor", -	"license": "WTFPL", -	"sourcePaths": ["."], -	"importPaths": ["."], -	"excludedSourceFiles": ["libInputVisitorExample.d"] -} diff --git a/src/sdlang/libinputvisitor/libInputVisitor.d b/src/sdlang/libinputvisitor/libInputVisitor.d deleted file mode 100644 index f29dc4f..0000000 --- a/src/sdlang/libinputvisitor/libInputVisitor.d +++ /dev/null @@ -1,113 +0,0 @@ -/++ -Copyright (C) 2012 Nick Sabalausky <http://semitwist.com/contact> - -This program is free software. It comes without any warranty, to -the extent permitted by applicable law. You can redistribute it -and/or modify it under the terms of the Do What The Fuck You Want -To Public License, Version 2, as published by Sam Hocevar. See -http://www.wtfpl.net/ for more details. - -	DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE  -				Version 2, December 2004  - -Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>  - -Everyone is permitted to copy and distribute verbatim or modified  -copies of this license document, and changing it is allowed as long  -as the name is changed.  - -		DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE  -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION  - -0. You just DO WHAT THE FUCK YOU WANT TO. -+/ - -/++ -Should work with DMD 2.059 and up - -For more info on this, see: -http://semitwist.com/articles/article/view/combine-coroutines-and-input-ranges-for-dead-simple-d-iteration -+/ - -import core.thread; - -class InputVisitor(Obj, Elem) : Fiber -{ -	bool started = false; -	Obj obj; -	this(Obj obj) -	{ -		this.obj = obj; - -		version(Windows) // Issue #1 -		{ -			import core.sys.windows.windows : SYSTEM_INFO, GetSystemInfo; -			SYSTEM_INFO info; -			GetSystemInfo(&info); -			auto PAGESIZE = info.dwPageSize; - -			super(&run, PAGESIZE * 16); -		} -		else -			super(&run); -	} - -	this(Obj obj, size_t stackSize) -	{ -		this.obj = obj; -		super(&run, stackSize); -	} - -	private void run() -	{ -		obj.visit(this); -	} -	 -	private void ensureStarted() -	{ -		if(!started) -		{ -			call(); -			started = true; -		} -	} -	 -	// Member 'front' must be a function due to DMD Issue #5403 -	private Elem _front = Elem.init; // Default initing here avoids "Error: field _front must be initialized in constructor" -	@property Elem front() -	{ -		ensureStarted(); -		return _front; -	} -	 -	void popFront() -	{ -		ensureStarted(); -		call(); -	} -	 -	@property bool empty() -	{ -		ensureStarted(); -		return state == Fiber.State.TERM; -	} -	 -	void yield(Elem elem) -	{ -		_front = elem; -		Fiber.yield(); -	} -} - -template inputVisitor(Elem) -{ -	@property InputVisitor!(Obj, Elem) inputVisitor(Obj)(Obj obj) -	{ -		return new InputVisitor!(Obj, Elem)(obj); -	} - -	@property InputVisitor!(Obj, Elem) inputVisitor(Obj)(Obj obj, size_t stackSize) -	{ -		return new InputVisitor!(Obj, Elem)(obj, stackSize); -	} -} diff --git a/src/sdlang/package.d b/src/sdlang/package.d deleted file mode 100644 index dd8df1a..0000000 --- a/src/sdlang/package.d +++ /dev/null @@ -1,133 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -/++ -$(H2 SDLang-D v0.10.0) - -Library for parsing and generating SDL (Simple Declarative Language). - -Import this module to use SDLang-D as a library. - -For the list of officially supported compiler versions, see the -$(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/.travis.yml, .travis.yml) -file included with your version of SDLang-D. - -Links: -$(UL -	$(LI $(LINK2 http://sdlang.org/, SDLang Language Homepage) ) -	$(LI $(LINK2 https://github.com/Abscissa/SDLang-D, SDLang-D Homepage) ) -	$(LI $(LINK2 http://semitwist.com/sdlang-d, SDLang-D API Reference (latest version) ) ) -	$(LI $(LINK2 http://semitwist.com/sdlang-d-docs, SDLang-D API Reference (earlier versions) ) ) -	$(LI $(LINK2 http://sdl.ikayzo.org/display/SDL/Language+Guide, Old Official SDL Site) [$(LINK2 http://semitwist.com/sdl-mirror/Language+Guide.html, mirror)] ) -) - -Authors: Nick Sabalausky ("Abscissa") http://semitwist.com/contact -Copyright: -Copyright (C) 2012-2016 Nick Sabalausky. - -License: $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/LICENSE.txt, zlib/libpng) -+/ - -module sdlang; - -import std.array; -import std.datetime; -import std.file; -import std.stdio; - -import sdlang.ast; -import sdlang.exception; -import sdlang.lexer; -import sdlang.parser; -import sdlang.symbol; -import sdlang.token; -import sdlang.util; - -// Expose main public API -public import sdlang.ast       : Attribute, Tag; -public import sdlang.exception; -public import sdlang.parser    : parseFile, parseSource; -public import sdlang.token     : Value, Token, DateTimeFrac, DateTimeFracUnknownZone; -public import sdlang.util      : sdlangVersion, Location; - -version(sdlangUsingBuiltinTestRunner) -	void main() {} - -version(sdlangCliApp) -{ -	int main(string[] args) -	{ -		if( -			args.length != 3 || -			(args[1] != "lex" && args[1] != "parse" && args[1] != "to-sdl") -		) -		{ -			stderr.writeln("SDLang-D v", sdlangVersion); -			stderr.writeln("Usage: sdlang [lex|parse|to-sdl] filename.sdl"); -			return 1; -		} -		 -		auto filename = args[2]; - -		try -		{ -			if(args[1] == "lex") -				doLex(filename); -			else if(args[1] == "parse") -				doParse(filename); -			else -				doToSDL(filename); -		} -		catch(ParseException e) -		{ -			stderr.writeln(e.msg); -			return 1; -		} -		 -		return 0; -	} - -	void doLex(string filename) -	{ -		auto source = cast(string)read(filename); -		auto lexer = new Lexer(source, filename); -		 -		foreach(tok; lexer) -		{ -			// Value -			string value; -			if(tok.symbol == symbol!"Value") -				value = tok.value.hasValue? toString(tok.value.type) : "{null}"; -			 -			value = value==""? "\t" : "("~value~":"~tok.value.toString()~") "; - -			// Data -			auto data = tok.data.replace("\n", "").replace("\r", ""); -			if(data != "") -				data = "\t|"~tok.data~"|"; -			 -			// Display -			writeln( -				tok.location.toString, ":\t", -				tok.symbol.name, value, -				data -			); -			 -			if(tok.symbol.name == "Error") -				break; -		} -	} - -	void doParse(string filename) -	{ -		auto root = parseFile(filename); -		stdout.rawWrite(root.toDebugString()); -		writeln(); -	} - -	void doToSDL(string filename) -	{ -		auto root = parseFile(filename); -		stdout.rawWrite(root.toSDLDocument()); -	} -} diff --git a/src/sdlang/parser.d b/src/sdlang/parser.d deleted file mode 100644 index c9b8d4f..0000000 --- a/src/sdlang/parser.d +++ /dev/null @@ -1,628 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.parser; - -import std.file; - -import libInputVisitor; -import taggedalgebraic; - -import sdlang.ast; -import sdlang.exception; -import sdlang.lexer; -import sdlang.symbol; -import sdlang.token; -import sdlang.util; - -/// Returns root tag. -Tag parseFile(string filename) -{ -	auto source = cast(string)read(filename); -	return parseSource(source, filename); -} - -/// Returns root tag. The optional `filename` parameter can be included -/// so that the SDLang document's filename (if any) can be displayed with -/// any syntax error messages. -Tag parseSource(string source, string filename=null) -{ -	auto lexer = new Lexer(source, filename); -	auto parser = DOMParser(lexer); -	return parser.parseRoot(); -} - -/++ -Parses an SDL document using StAX/Pull-style. Returns an InputRange with -element type ParserEvent. - -The pullParseFile version reads a file and parses it, while pullParseSource -parses a string passed in. The optional `filename` parameter in pullParseSource -can be included so that the SDLang document's filename (if any) can be displayed -with any syntax error messages. - -Note: The old FileStartEvent and FileEndEvent events -$(LINK2 https://github.com/Abscissa/SDLang-D/issues/17, were deemed unnessecary) -and removed as of SDLang-D v0.10.0. - -Note: Previously, in SDLang-D v0.9.x, ParserEvent was a -$(LINK2 http://dlang.org/phobos/std_variant.html#.Algebraic, std.variant.Algebraic). -As of SDLang-D v0.10.0, it is now a -$(LINK2 https://github.com/s-ludwig/taggedalgebraic, TaggedAlgebraic), -so usage has changed somewhat. - -Example: ------------------- -parent 12 attr="q" { -	childA 34 -	childB 56 -} -lastTag ------------------- - -The ParserEvent sequence emitted for that SDL document would be as -follows (indented for readability): ------------------- -TagStartEvent (parent) -	ValueEvent (12) -	AttributeEvent (attr, "q") -	TagStartEvent (childA) -		ValueEvent (34) -	TagEndEvent -	TagStartEvent (childB) -		ValueEvent (56) -	TagEndEvent -TagEndEvent -TagStartEvent (lastTag) -TagEndEvent ------------------- -+/ -auto pullParseFile(string filename) -{ -	auto source = cast(string)read(filename); -	return parseSource(source, filename); -} - -///ditto -auto pullParseSource(string source, string filename=null) -{ -	auto lexer = new Lexer(source, filename); -	auto parser = PullParser(lexer); -	return inputVisitor!ParserEvent( parser ); -} - -/// -@("pullParseFile/pullParseSource example") -unittest -{ -	// stuff.sdl -	immutable stuffSdl = ` -		name "sdlang-d" -		description "An SDL (Simple Declarative Language) library for D." -		homepage "http://github.com/Abscissa/SDLang-D" -		 -		configuration "library" { -			targetType "library" -		} -	`; -	 -	import std.stdio; - -	foreach(event; pullParseSource(stuffSdl)) -	final switch(event.kind) -	{ -	case ParserEvent.Kind.tagStart: -		auto e = cast(TagStartEvent) event; -		writeln("TagStartEvent: ", e.namespace, ":", e.name, " @ ", e.location); -		break; - -	case ParserEvent.Kind.tagEnd: -		auto e = cast(TagEndEvent) event; -		writeln("TagEndEvent"); -		break; - -	case ParserEvent.Kind.value: -		auto e = cast(ValueEvent) event; -		writeln("ValueEvent: ", e.value); -		break; - -	case ParserEvent.Kind.attribute: -		auto e = cast(AttributeEvent) event; -		writeln("AttributeEvent: ", e.namespace, ":", e.name, "=", e.value); -		break; -	} -} - -private union ParserEventUnion -{ -	TagStartEvent  tagStart; -	TagEndEvent    tagEnd; -	ValueEvent     value; -	AttributeEvent attribute; -} - -/++ -The element of the InputRange returned by pullParseFile and pullParseSource. - -This is a tagged union, built from the following: -------- -alias ParserEvent = TaggedAlgebraic!ParserEventUnion; -private union ParserEventUnion -{ -	TagStartEvent  tagStart; -	TagEndEvent    tagEnd; -	ValueEvent     value; -	AttributeEvent attribute; -} -------- - -Note: The old FileStartEvent and FileEndEvent events -$(LINK2 https://github.com/Abscissa/SDLang-D/issues/17, were deemed unnessecary) -and removed as of SDLang-D v0.10.0. - -Note: Previously, in SDLang-D v0.9.x, ParserEvent was a -$(LINK2 http://dlang.org/phobos/std_variant.html#.Algebraic, std.variant.Algebraic). -As of SDLang-D v0.10.0, it is now a -$(LINK2 https://github.com/s-ludwig/taggedalgebraic, TaggedAlgebraic), -so usage has changed somewhat. -+/ -alias ParserEvent = TaggedAlgebraic!ParserEventUnion; - -/// -@("ParserEvent example") -unittest -{ -	// Create -	ParserEvent event1 = TagStartEvent(); -	ParserEvent event2 = TagEndEvent(); -	ParserEvent event3 = ValueEvent(); -	ParserEvent event4 = AttributeEvent(); - -	// Check type -	assert(event1.kind == ParserEvent.Kind.tagStart); -	assert(event2.kind == ParserEvent.Kind.tagEnd); -	assert(event3.kind == ParserEvent.Kind.value); -	assert(event4.kind == ParserEvent.Kind.attribute); - -	// Cast to base type -	auto e1 = cast(TagStartEvent) event1; -	auto e2 = cast(TagEndEvent) event2; -	auto e3 = cast(ValueEvent) event3; -	auto e4 = cast(AttributeEvent) event4; -	//auto noGood = cast(AttributeEvent) event1; // AssertError: event1 is a TagStartEvent, not AttributeEvent. - -	// Use as base type. -	// In many cases, no casting is even needed. -	event1.name = "foo";   -	//auto noGood = event3.name; // AssertError: ValueEvent doesn't have a member 'name'. - -	// Final switch is supported: -	final switch(event1.kind) -	{ -		case ParserEvent.Kind.tagStart:  break; -		case ParserEvent.Kind.tagEnd:    break; -		case ParserEvent.Kind.value:     break; -		case ParserEvent.Kind.attribute: break; -	} -} - -/// Event: Start of tag -struct TagStartEvent -{ -	Location location; -	string namespace; -	string name; -} - -/// Event: End of tag -struct TagEndEvent -{ -	//Location location; -} - -/// Event: Found a Value in the current tag -struct ValueEvent -{ -	Location location; -	Value value; -} - -/// Event: Found an Attribute in the current tag -struct AttributeEvent -{ -	Location location; -	string namespace; -	string name; -	Value value; -} - -// The actual pull parser -private struct PullParser -{ -	private Lexer lexer; -	 -	private struct IDFull -	{ -		string namespace; -		string name; -	} -	 -	private void error(string msg) -	{ -		error(lexer.front.location, msg); -	} - -	private void error(Location loc, string msg) -	{ -		throw new ParseException(loc, "Error: "~msg); -	} -	 -	private InputVisitor!(PullParser, ParserEvent) v; -	 -	void visit(InputVisitor!(PullParser, ParserEvent) v) -	{ -		this.v = v; -		parseRoot(); -	} -	 -	private void emit(Event)(Event event) -	{ -		v.yield( ParserEvent(event) ); -	} -	 -	/// <Root> ::= <Tags> EOF  (Lookaheads: Anything) -	private void parseRoot() -	{ -		//trace("Starting parse of file: ", lexer.filename); -		//trace(__FUNCTION__, ": <Root> ::= <Tags> EOF  (Lookaheads: Anything)"); - -		auto startLocation = Location(lexer.filename, 0, 0, 0); - -		parseTags(); -		 -		auto token = lexer.front; -		if(token.matches!":"()) -		{ -			lexer.popFront(); -			token = lexer.front; -			if(token.matches!"Ident"()) -			{ -				error("Missing namespace. If you don't wish to use a namespace, then say '"~token.data~"', not ':"~token.data~"'"); -				assert(0); -			} -			else -			{ -				error("Missing namespace. If you don't wish to use a namespace, then omit the ':'"); -				assert(0); -			} -		} -		else if(!token.matches!"EOF"()) -			error("Expected a tag or end-of-file, not " ~ token.symbol.name); -	} - -	/// <Tags> ::= <Tag> <Tags>  (Lookaheads: Ident Value) -	///        |   EOL   <Tags>  (Lookaheads: EOL) -	///        |   {empty}       (Lookaheads: Anything else, except '{') -	void parseTags() -	{ -		//trace("Enter ", __FUNCTION__); -		while(true) -		{ -			auto token = lexer.front; -			if(token.matches!"Ident"() || token.matches!"Value"()) -			{ -				//trace(__FUNCTION__, ": <Tags> ::= <Tag> <Tags>  (Lookaheads: Ident Value)"); -				parseTag(); -				continue; -			} -			else if(token.matches!"EOL"()) -			{ -				//trace(__FUNCTION__, ": <Tags> ::= EOL <Tags>  (Lookaheads: EOL)"); -				lexer.popFront(); -				continue; -			} -			else if(token.matches!"{"()) -			{ -				error("Found start of child block, but no tag name. If you intended an anonymous "~ -				"tag, you must have at least one value before any attributes or child tags."); -			} -			else -			{ -				//trace(__FUNCTION__, ": <Tags> ::= {empty}  (Lookaheads: Anything else, except '{')"); -				break; -			} -		} -	} - -	/// <Tag> -	///     ::= <IDFull> <Values> <Attributes> <OptChild> <TagTerminator>  (Lookaheads: Ident) -	///     |   <Value>  <Values> <Attributes> <OptChild> <TagTerminator>  (Lookaheads: Value) -	void parseTag() -	{ -		auto token = lexer.front; -		if(token.matches!"Ident"()) -		{ -			//trace(__FUNCTION__, ": <Tag> ::= <IDFull> <Values> <Attributes> <OptChild> <TagTerminator>  (Lookaheads: Ident)"); -			//trace("Found tag named: ", tag.fullName); -			auto id = parseIDFull(); -			emit( TagStartEvent(token.location, id.namespace, id.name) ); -		} -		else if(token.matches!"Value"()) -		{ -			//trace(__FUNCTION__, ": <Tag> ::= <Value>  <Values> <Attributes> <OptChild> <TagTerminator>  (Lookaheads: Value)"); -			//trace("Found anonymous tag."); -			emit( TagStartEvent(token.location, null, null) ); -		} -		else -			error("Expected tag name or value, not " ~ token.symbol.name); - -		if(lexer.front.matches!"="()) -			error("Found attribute, but no tag name. If you intended an anonymous "~ -			"tag, you must have at least one value before any attributes."); - -		parseValues(); -		parseAttributes(); -		parseOptChild(); -		parseTagTerminator(); -		 -		emit( TagEndEvent() ); -	} - -	/// <IDFull> ::= Ident <IDSuffix>  (Lookaheads: Ident) -	IDFull parseIDFull() -	{ -		auto token = lexer.front; -		if(token.matches!"Ident"()) -		{ -			//trace(__FUNCTION__, ": <IDFull> ::= Ident <IDSuffix>  (Lookaheads: Ident)"); -			lexer.popFront(); -			return parseIDSuffix(token.data); -		} -		else -		{ -			error("Expected namespace or identifier, not " ~ token.symbol.name); -			assert(0); -		} -	} - -	/// <IDSuffix> -	///     ::= ':' Ident  (Lookaheads: ':') -	///     ::= {empty}    (Lookaheads: Anything else) -	IDFull parseIDSuffix(string firstIdent) -	{ -		auto token = lexer.front; -		if(token.matches!":"()) -		{ -			//trace(__FUNCTION__, ": <IDSuffix> ::= ':' Ident  (Lookaheads: ':')"); -			lexer.popFront(); -			token = lexer.front; -			if(token.matches!"Ident"()) -			{ -				lexer.popFront(); -				return IDFull(firstIdent, token.data); -			} -			else -			{ -				error("Expected name, not " ~ token.symbol.name); -				assert(0); -			} -		} -		else -		{ -			//trace(__FUNCTION__, ": <IDSuffix> ::= {empty}  (Lookaheads: Anything else)"); -			return IDFull("", firstIdent); -		} -	} - -	/// <Values> -	///     ::= Value <Values>  (Lookaheads: Value) -	///     |   {empty}         (Lookaheads: Anything else) -	void parseValues() -	{ -		while(true) -		{ -			auto token = lexer.front; -			if(token.matches!"Value"()) -			{ -				//trace(__FUNCTION__, ": <Values> ::= Value <Values>  (Lookaheads: Value)"); -				parseValue(); -				continue; -			} -			else -			{ -				//trace(__FUNCTION__, ": <Values> ::= {empty}  (Lookaheads: Anything else)"); -				break; -			} -		} -	} - -	/// Handle Value terminals that aren't part of an attribute -	void parseValue() -	{ -		auto token = lexer.front; -		if(token.matches!"Value"()) -		{ -			//trace(__FUNCTION__, ": (Handle Value terminals that aren't part of an attribute)"); -			auto value = token.value; -			//trace("In tag '", parent.fullName, "', found value: ", value); -			emit( ValueEvent(token.location, value) ); -			 -			lexer.popFront(); -		} -		else -			error("Expected value, not "~token.symbol.name); -	} - -	/// <Attributes> -	///     ::= <Attribute> <Attributes>  (Lookaheads: Ident) -	///     |   {empty}                   (Lookaheads: Anything else) -	void parseAttributes() -	{ -		while(true) -		{ -			auto token = lexer.front; -			if(token.matches!"Ident"()) -			{ -				//trace(__FUNCTION__, ": <Attributes> ::= <Attribute> <Attributes>  (Lookaheads: Ident)"); -				parseAttribute(); -				continue; -			} -			else -			{ -				//trace(__FUNCTION__, ": <Attributes> ::= {empty}  (Lookaheads: Anything else)"); -				break; -			} -		} -	} - -	/// <Attribute> ::= <IDFull> '=' Value  (Lookaheads: Ident) -	void parseAttribute() -	{ -		//trace(__FUNCTION__, ": <Attribute> ::= <IDFull> '=' Value  (Lookaheads: Ident)"); -		auto token = lexer.front; -		if(!token.matches!"Ident"()) -			error("Expected attribute name, not "~token.symbol.name); -		 -		auto id = parseIDFull(); -		 -		token = lexer.front; -		if(!token.matches!"="()) -			error("Expected '=' after attribute name, not "~token.symbol.name); -		 -		lexer.popFront(); -		token = lexer.front; -		if(!token.matches!"Value"()) -			error("Expected attribute value, not "~token.symbol.name); -		 -		//trace("In tag '", parent.fullName, "', found attribute '", attr.fullName, "'"); -		emit( AttributeEvent(token.location, id.namespace, id.name, token.value) ); -		 -		lexer.popFront(); -	} - -	/// <OptChild> -	///      ::= '{' EOL <Tags> '}'  (Lookaheads: '{') -	///      |   {empty}             (Lookaheads: Anything else) -	void parseOptChild() -	{ -		auto token = lexer.front; -		if(token.matches!"{") -		{ -			//trace(__FUNCTION__, ": <OptChild> ::= '{' EOL <Tags> '}'  (Lookaheads: '{')"); -			lexer.popFront(); -			token = lexer.front; -			if(!token.matches!"EOL"()) -				error("Expected newline or semicolon after '{', not "~token.symbol.name); -			 -			lexer.popFront(); -			parseTags(); -			 -			token = lexer.front; -			if(!token.matches!"}"()) -				error("Expected '}' after child tags, not "~token.symbol.name); -			lexer.popFront(); -		} -		else -		{ -			//trace(__FUNCTION__, ": <OptChild> ::= {empty}  (Lookaheads: Anything else)"); -			// Do nothing, no error. -		} -	} -	 -	/// <TagTerminator> -	///     ::= EOL      (Lookahead: EOL) -	///     |   {empty}  (Lookahead: EOF) -	void parseTagTerminator() -	{ -		auto token = lexer.front; -		if(token.matches!"EOL") -		{ -			//trace(__FUNCTION__, ": <TagTerminator> ::= EOL  (Lookahead: EOL)"); -			lexer.popFront(); -		} -		else if(token.matches!"EOF") -		{ -			//trace(__FUNCTION__, ": <TagTerminator> ::= {empty}  (Lookahead: EOF)"); -			// Do nothing -		} -		else -			error("Expected end of tag (newline, semicolon or end-of-file), not " ~ token.symbol.name); -	} -} - -private struct DOMParser -{ -	Lexer lexer; -	 -	Tag parseRoot() -	{ -		auto currTag = new Tag(null, null, "root"); -		currTag.location = Location(lexer.filename, 0, 0, 0); -		 -		auto parser = PullParser(lexer); -		auto eventRange = inputVisitor!ParserEvent( parser ); -		 -		foreach(event; eventRange) -		final switch(event.kind) -		{ -		case ParserEvent.Kind.tagStart: -			auto newTag = new Tag(currTag, event.namespace, event.name); -			newTag.location = event.location; -			 -			currTag = newTag; -			break; - -		case ParserEvent.Kind.tagEnd: -			currTag = currTag.parent; - -			if(!currTag) -				parser.error("Internal Error: Received an extra TagEndEvent"); -			break; - -		case ParserEvent.Kind.value: -			currTag.add((cast(ValueEvent)event).value); -			break; - -		case ParserEvent.Kind.attribute: -			auto e = cast(AttributeEvent) event; -			auto attr = new Attribute(e.namespace, e.name, e.value, e.location); -			currTag.add(attr); -			break; -		} -		 -		return currTag; -	} -} - -// Other parser tests are part of the AST's tests over in the ast module. - -// Regression test, issue #13: https://github.com/Abscissa/SDLang-D/issues/13 -// "Incorrectly accepts ":tagname" (blank namespace, tagname prefixed with colon)" -@("parser: Regression test issue #13") -unittest -{ -	import std.exception; -	assertThrown!ParseException(parseSource(`:test`)); -	assertThrown!ParseException(parseSource(`:4`)); -} - -// Regression test, issue #16: https://github.com/Abscissa/SDLang-D/issues/16 -@("parser: Regression test issue #16") -unittest -{ -	// Shouldn't crash -	foreach(event; pullParseSource(`tag "data"`)) -	{ -		if(event.kind == ParserEvent.Kind.tagStart) -			auto e = cast(TagStartEvent) event; -	} -} - -// Regression test, issue #31: https://github.com/Abscissa/SDLang-D/issues/31 -// "Escape sequence results in range violation error" -@("parser: Regression test issue #31") -unittest -{ -	// Shouldn't get a Range violation -	parseSource(`test "\"foo\""`); -} diff --git a/src/sdlang/symbol.d b/src/sdlang/symbol.d deleted file mode 100644 index ebb2b93..0000000 --- a/src/sdlang/symbol.d +++ /dev/null @@ -1,61 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.symbol; - -import std.algorithm; - -static immutable validSymbolNames = [ -	"Error", -	"EOF", -	"EOL", - -	":", -	"=", -	"{", -	"}", - -	"Ident", -	"Value", -]; - -/// Use this to create a Symbol. Ex: symbol!"Value" or symbol!"=" -/// Invalid names (such as symbol!"FooBar") are rejected at compile-time. -template symbol(string name) -{ -	static assert(validSymbolNames.find(name), "Invalid Symbol: '"~name~"'"); -	immutable symbol = _symbol(name); -} - -private Symbol _symbol(string name) -{ -	return Symbol(name); -} - -/// Symbol is essentially the "type" of a Token. -/// Token is like an instance of a Symbol. -/// -/// This only represents terminals. Nonterminal tokens aren't -/// constructed since the AST is built directly during parsing. -/// -/// You can't create a Symbol directly. Instead, use the `symbol` -/// template. -struct Symbol -{ -	private string _name; -	@property string name() -	{ -		return _name; -	} -	 -	@disable this(); -	private this(string name) -	{ -		this._name = name; -	} - -	string toString() -	{ -		return _name; -	} -} diff --git a/src/sdlang/taggedalgebraic/taggedalgebraic.d b/src/sdlang/taggedalgebraic/taggedalgebraic.d deleted file mode 100644 index ffaac49..0000000 --- a/src/sdlang/taggedalgebraic/taggedalgebraic.d +++ /dev/null @@ -1,1085 +0,0 @@ -/** - * Algebraic data type implementation based on a tagged union. - *  - * Copyright: Copyright 2015, Sönke Ludwig. - * License:   $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors:   Sönke Ludwig -*/ -module taggedalgebraic; - -import std.typetuple; - -// TODO: -//  - distinguish between @property and non@-property methods. -//  - verify that static methods are handled properly - -/** Implements a generic algebraic type using an enum to identify the stored type. - -	This struct takes a `union` or `struct` declaration as an input and builds -	an algebraic data type from its fields, using an automatically generated -	`Kind` enumeration to identify which field of the union is currently used. -	Multiple fields with the same value are supported. - -	All operators and methods are transparently forwarded to the contained -	value. The caller has to make sure that the contained value supports the -	requested operation. Failure to do so will result in an assertion failure. - -	The return value of forwarded operations is determined as follows: -	$(UL -		$(LI If the type can be uniquely determined, it is used as the return -			value) -		$(LI If there are multiple possible return values and all of them match -			the unique types defined in the `TaggedAlgebraic`, a -			`TaggedAlgebraic` is returned.) -		$(LI If there are multiple return values and none of them is a -			`Variant`, an `Algebraic` of the set of possible return types is -			returned.) -		$(LI If any of the possible operations returns a `Variant`, this is used -			as the return value.) -	) -*/ -struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct)) -{ -	import std.algorithm : among; -	import std.string : format; -	import std.traits : FieldTypeTuple, FieldNameTuple, Largest, hasElaborateCopyConstructor, hasElaborateDestructor; - -	private alias Union = U; -	private alias FieldTypes = FieldTypeTuple!U; -	private alias fieldNames = FieldNameTuple!U; - -	static assert(FieldTypes.length > 0, "The TaggedAlgebraic's union type must have at least one field."); -	static assert(FieldTypes.length == fieldNames.length); - - -	private { -		void[Largest!FieldTypes.sizeof] m_data = void; -		Kind m_kind; -	} - -	/// A type enum that identifies the type of value currently stored. -	alias Kind = TypeEnum!U; - -	/// Compatibility alias -	deprecated("Use 'Kind' instead.") alias Type = Kind; - -	/// The type ID of the currently stored value. -	@property Kind kind() const { return m_kind; } - -	// Compatibility alias -	deprecated("Use 'kind' instead.") -	alias typeID = kind; - -	// constructors -	//pragma(msg, generateConstructors!U()); -	mixin(generateConstructors!U); - -	this(TaggedAlgebraic other) -	{ -		import std.algorithm : swap; -		swap(this, other); -	} - -	void opAssign(TaggedAlgebraic other) -	{ -		import std.algorithm : swap; -		swap(this, other); -	} - -	// postblit constructor -	static if (anySatisfy!(hasElaborateCopyConstructor, FieldTypes)) -	{ -		this(this) -		{ -			switch (m_kind) { -				default: break; -				foreach (i, tname; fieldNames) { -					alias T = typeof(__traits(getMember, U, tname)); -					static if (hasElaborateCopyConstructor!T) -					{ -						case __traits(getMember, Kind, tname): -							typeid(T).postblit(cast(void*)&trustedGet!tname()); -							return; -					} -				} -			} -		} -	} - -	// destructor -	static if (anySatisfy!(hasElaborateDestructor, FieldTypes)) -	{ -		~this() -		{ -			final switch (m_kind) { -				foreach (i, tname; fieldNames) { -					alias T = typeof(__traits(getMember, U, tname)); -					case __traits(getMember, Kind, tname): -						static if (hasElaborateDestructor!T) { -							.destroy(trustedGet!tname); -						} -						return; -				} -			} -		} -	} - -	/// Enables conversion or extraction of the stored value. -	T opCast(T)() -	{ -		import std.conv : to; - -		final switch (m_kind) { -			foreach (i, FT; FieldTypes) { -				case __traits(getMember, Kind, fieldNames[i]): -					static if (is(typeof(to!T(trustedGet!(fieldNames[i]))))) { -						return to!T(trustedGet!(fieldNames[i])); -					} else { -						assert(false, "Cannot cast a "~(cast(Kind)m_kind).to!string~" value ("~FT.stringof~") to "~T.stringof); -					} -			} -		} -		assert(false); // never reached -	} -	/// ditto -	T opCast(T)() const -	{ -		// this method needs to be duplicated because inout doesn't work with to!() -		import std.conv : to; - -		final switch (m_kind) { -			foreach (i, FT; FieldTypes) { -				case __traits(getMember, Kind, fieldNames[i]): -					static if (is(typeof(to!T(trustedGet!(fieldNames[i]))))) { -						return to!T(trustedGet!(fieldNames[i])); -					} else { -						assert(false, "Cannot cast a "~(cast(Kind)m_kind).to!string~" value ("~FT.stringof~") to "~T.stringof); -					} -			} -		} -		assert(false); // never reached -	} - -	/// Uses `cast(string)`/`to!string` to return a string representation of the enclosed value. -	string toString() const { return cast(string)this; } - -	// NOTE: "this TA" is used here as the functional equivalent of inout, -	//       just that it generates one template instantiation per modifier -	//       combination, so that we can actually decide what to do for each -	//       case. - -	/// Enables the invocation of methods of the stored value. -	auto opDispatch(string name, this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.method, name)(this, args); } -	/// Enables accessing properties/fields of the stored value. -	@property auto opDispatch(string name, this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.field, name, ARGS) && !hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.field, name)(this, args); } -	/// Enables equality comparison with the stored value. -	auto opEquals(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "==", T)) { return implementOp!(OpKind.binary, "==")(this, other); } -	/// Enables relational comparisons with the stored value. -	auto opCmp(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "<", T)) { assert(false, "TODO!"); } -	/// Enables the use of unary operators with the stored value. -	auto opUnary(string op, this TA)() if (hasOp!(TA, OpKind.unary, op)) { return implementOp!(OpKind.unary, op)(this); } -	/// Enables the use of binary operators with the stored value. -	auto opBinary(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op, T)) { return implementOp!(OpKind.binary, op)(this, other); } -	/// Enables the use of binary operators with the stored value. -	auto opBinaryRight(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binaryRight, op, T)) { return implementOp!(OpKind.binaryRight, op)(this, other); } -	/// Enables operator assignments on the stored value. -	auto opOpAssign(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op~"=", T)) { return implementOp!(OpKind.binary, op~"=")(this, other); } -	/// Enables indexing operations on the stored value. -	auto opIndex(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.index, null, ARGS)) { return implementOp!(OpKind.index, null)(this, args); } -	/// Enables index assignments on the stored value. -	auto opIndexAssign(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.indexAssign, null, ARGS)) { return implementOp!(OpKind.indexAssign, null)(this, args); } -	/// Enables call syntax operations on the stored value. -	auto opCall(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.call, null, ARGS)) { return implementOp!(OpKind.call, null)(this, args); } - -	private @trusted @property ref inout(typeof(__traits(getMember, U, f))) trustedGet(string f)() inout { return trustedGet!(inout(typeof(__traits(getMember, U, f)))); } -	private @trusted @property ref inout(T) trustedGet(T)() inout { return *cast(inout(T)*)m_data.ptr; } -} - -/// -unittest -{ -	import taggedalgebraic; - -	struct Foo { -		string name; -		void bar() {} -	} - -	union Base { -		int i; -		string str; -		Foo foo; -	} - -	alias Tagged = TaggedAlgebraic!Base; - -	// Instantiate -	Tagged taggedInt = 5; -	Tagged taggedString = "Hello"; -	Tagged taggedFoo = Foo(); -	Tagged taggedAny = taggedInt; -	taggedAny = taggedString; -	taggedAny = taggedFoo; -	 -	// Check type: Tagged.Kind is an enum -	assert(taggedInt.kind == Tagged.Kind.i); -	assert(taggedString.kind == Tagged.Kind.str); -	assert(taggedFoo.kind == Tagged.Kind.foo); -	assert(taggedAny.kind == Tagged.Kind.foo); - -	// In most cases, can simply use as-is -	auto num = 4 + taggedInt; -	auto msg = taggedString ~ " World!"; -	taggedFoo.bar(); -	if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first! -		taggedAny.bar(); -	//taggedString.bar(); // AssertError: Not a Foo! - -	// Convert back by casting -	auto i   = cast(int)    taggedInt; -	auto str = cast(string) taggedString; -	auto foo = cast(Foo)    taggedFoo; -	if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first! -		auto foo2 = cast(Foo) taggedAny; -	//cast(Foo) taggedString; // AssertError! - -	// Kind is an enum, so final switch is supported: -	final switch (taggedAny.kind) { -		case Tagged.Kind.i: -			// It's "int i" -			break; - -		case Tagged.Kind.str: -			// It's "string str" -			break; - -		case Tagged.Kind.foo: -			// It's "Foo foo" -			break; -	} -} - -/** Operators and methods of the contained type can be used transparently. -*/ -@safe unittest { -	static struct S { -		int v; -		int test() { return v / 2; } -	} - -	static union Test { -		typeof(null) null_; -		int integer; -		string text; -		string[string] dictionary; -		S custom; -	} - -	alias TA = TaggedAlgebraic!Test; - -	TA ta; -	assert(ta.kind == TA.Kind.null_); - -	ta = 12; -	assert(ta.kind == TA.Kind.integer); -	assert(ta == 12); -	assert(cast(int)ta == 12); -	assert(cast(long)ta == 12); -	assert(cast(short)ta == 12); - -	ta += 12; -	assert(ta == 24); -	assert(ta - 10 == 14); - -	ta = ["foo" : "bar"]; -	assert(ta.kind == TA.Kind.dictionary); -	assert(ta["foo"] == "bar"); - -	ta["foo"] = "baz"; -	assert(ta["foo"] == "baz"); - -	ta = S(8); -	assert(ta.test() == 4); -} - -unittest { // std.conv integration -	import std.conv : to; - -	static struct S { -		int v; -		int test() { return v / 2; } -	} - -	static union Test { -		typeof(null) null_; -		int number; -		string text; -	} - -	alias TA = TaggedAlgebraic!Test; - -	TA ta; -	assert(ta.kind == TA.Kind.null_); -	ta = "34"; -	assert(ta == "34"); -	assert(to!int(ta) == 34, to!string(to!int(ta))); -	assert(to!string(ta) == "34", to!string(ta)); -} - -/** Multiple fields are allowed to have the same type, in which case the type -	ID enum is used to disambiguate. -*/ -@safe unittest { -	static union Test { -		typeof(null) null_; -		int count; -		int difference; -	} - -	alias TA = TaggedAlgebraic!Test; - -	TA ta; -	ta = TA(12, TA.Kind.count); -	assert(ta.kind == TA.Kind.count); -	assert(ta == 12); - -	ta = null; -	assert(ta.kind == TA.Kind.null_); -} - -unittest { -	// test proper type modifier support -	static struct  S { -		void test() {} -		void testI() immutable {} -		void testC() const {} -		void testS() shared {} -		void testSC() shared const {} -	} -	static union U { -		S s; -	} -	 -	auto u = TaggedAlgebraic!U(S.init); -	const uc = u; -	immutable ui = cast(immutable)u; -	//const shared usc = cast(shared)u; -	//shared us = cast(shared)u; - -	static assert( is(typeof(u.test()))); -	static assert(!is(typeof(u.testI()))); -	static assert( is(typeof(u.testC()))); -	static assert(!is(typeof(u.testS()))); -	static assert(!is(typeof(u.testSC()))); - -	static assert(!is(typeof(uc.test()))); -	static assert(!is(typeof(uc.testI()))); -	static assert( is(typeof(uc.testC()))); -	static assert(!is(typeof(uc.testS()))); -	static assert(!is(typeof(uc.testSC()))); - -	static assert(!is(typeof(ui.test()))); -	static assert( is(typeof(ui.testI()))); -	static assert( is(typeof(ui.testC()))); -	static assert(!is(typeof(ui.testS()))); -	static assert( is(typeof(ui.testSC()))); - -	/*static assert(!is(typeof(us.test()))); -	static assert(!is(typeof(us.testI()))); -	static assert(!is(typeof(us.testC()))); -	static assert( is(typeof(us.testS()))); -	static assert( is(typeof(us.testSC()))); - -	static assert(!is(typeof(usc.test()))); -	static assert(!is(typeof(usc.testI()))); -	static assert(!is(typeof(usc.testC()))); -	static assert(!is(typeof(usc.testS()))); -	static assert( is(typeof(usc.testSC())));*/ -} - -unittest { -	// test attributes on contained values -	import std.typecons : Rebindable, rebindable; - -	class C { -		void test() {} -		void testC() const {} -		void testI() immutable {} -	} -	union U { -		Rebindable!(immutable(C)) c; -	} - -	auto ta = TaggedAlgebraic!U(rebindable(new immutable C)); -	static assert(!is(typeof(ta.test()))); -	static assert( is(typeof(ta.testC()))); -	static assert( is(typeof(ta.testI()))); -} - -version (unittest) { -	// test recursive definition using a wrapper dummy struct -	// (needed to avoid "no size yet for forward reference" errors) -	template ID(What) { alias ID = What; } -	private struct _test_Wrapper { -		TaggedAlgebraic!_test_U u; -		alias u this; -		this(ARGS...)(ARGS args) { u = TaggedAlgebraic!_test_U(args); } -	} -	private union _test_U { -		_test_Wrapper[] children; -		int value; -	} -	unittest { -		alias TA = _test_Wrapper; -		auto ta = TA(null); -		ta ~= TA(0); -		ta ~= TA(1); -		ta ~= TA([TA(2)]); -		assert(ta[0] == 0); -		assert(ta[1] == 1); -		assert(ta[2][0] == 2); -	} -} - -unittest { // postblit/destructor test -	static struct S { -		static int i = 0; -		bool initialized = false; -		this(bool) { initialized = true; i++; } -		this(this) { if (initialized) i++; } -		~this() { if (initialized) i--; } -	} - -	static struct U { -		S s; -		int t; -	} -	alias TA = TaggedAlgebraic!U; -	{ -		assert(S.i == 0); -		auto ta = TA(S(true)); -		assert(S.i == 1); -		{ -			auto tb = ta; -			assert(S.i == 2); -			ta = tb; -			assert(S.i == 2); -			ta = 1; -			assert(S.i == 1); -			ta = S(true); -			assert(S.i == 2); -		} -		assert(S.i == 1); -	} -	assert(S.i == 0); - -	static struct U2 { -		S a; -		S b; -	} -	alias TA2 = TaggedAlgebraic!U2; -	{ -		auto ta2 = TA2(S(true), TA2.Kind.a); -		assert(S.i == 1); -	} -	assert(S.i == 0); -} - -unittest { -	static struct S { -		union U { -			int i; -			string s; -			U[] a; -		} -		alias TA = TaggedAlgebraic!U; -		TA p; -		alias p this; -	} -	S s = S(S.TA("hello")); -	assert(cast(string)s == "hello"); -} - -unittest { // multiple operator choices -	union U { -		int i; -		double d; -	} -	alias TA = TaggedAlgebraic!U; -	TA ta = 12; -	static assert(is(typeof(ta + 10) == TA)); // ambiguous, could be int or double -	assert((ta + 10).kind == TA.Kind.i); -	assert(ta + 10 == 22); -	static assert(is(typeof(ta + 10.5) == double)); -	assert(ta + 10.5 == 22.5); -} - -unittest { // Binary op between two TaggedAlgebraic values -	union U { int i; } -	alias TA = TaggedAlgebraic!U; - -	TA a = 1, b = 2; -	static assert(is(typeof(a + b) == int)); -	assert(a + b == 3); -} - -unittest { // Ambiguous binary op between two TaggedAlgebraic values -	union U { int i; double d; } -	alias TA = TaggedAlgebraic!U; - -	TA a = 1, b = 2; -	static assert(is(typeof(a + b) == TA)); -	assert((a + b).kind == TA.Kind.i); -	assert(a + b == 3); -} - -unittest { -	struct S { -		union U { -			@disableIndex string str; -			S[] array; -			S[string] object; -		} -		alias TA = TaggedAlgebraic!U; -		TA payload; -		alias payload this; -	} - -	S a = S(S.TA("hello")); -	S b = S(S.TA(["foo": a])); -	S c = S(S.TA([a])); -	assert(b["foo"] == a); -	assert(b["foo"] == "hello"); -	assert(c[0] == a); -	assert(c[0] == "hello"); -} - - -/** Tests if the algebraic type stores a value of a certain data type. -*/ -bool hasType(T, U)(in ref TaggedAlgebraic!U ta) -{ -	alias Fields = Filter!(fieldMatchesType!(U, T), ta.fieldNames); -	static assert(Fields.length > 0, "Type "~T.stringof~" cannot be stored in a "~(TaggedAlgebraic!U).stringof~"."); - -	switch (ta.kind) { -		default: return false; -		foreach (i, fname; Fields) -			case __traits(getMember, ta.Kind, fname): -				return true; -	} -	assert(false); // never reached -} - -/// -unittest { -	union Fields { -		int number; -		string text; -	} - -	TaggedAlgebraic!Fields ta = "test"; - -	assert(ta.hasType!string); -	assert(!ta.hasType!int); - -	ta = 42; -	assert(ta.hasType!int); -	assert(!ta.hasType!string); -} - -unittest { // issue #1 -	union U { -		int a; -		int b; -	} -	alias TA = TaggedAlgebraic!U; - -	TA ta = TA(0, TA.Kind.b); -	static assert(!is(typeof(ta.hasType!double))); -	assert(ta.hasType!int); -} - -/** Gets the value stored in an algebraic type based on its data type. -*/ -ref inout(T) get(T, U)(ref inout(TaggedAlgebraic!U) ta) -{ -	assert(hasType!(T, U)(ta)); -	return ta.trustedGet!T; -} - -/// Convenience type that can be used for union fields that have no value (`void` is not allowed). -struct Void {} - -/// User-defined attibute to disable `opIndex` forwarding for a particular tagged union member. -@property auto disableIndex() { assert(__ctfe, "disableIndex must only be used as an attribute."); return DisableOpAttribute(OpKind.index, null); } - -private struct DisableOpAttribute { -	OpKind kind; -	string name; -} - - -private template hasOp(TA, OpKind kind, string name, ARGS...) -{ -	import std.traits : CopyTypeQualifiers; -	alias UQ = CopyTypeQualifiers!(TA, TA.Union); -	enum hasOp = TypeTuple!(OpInfo!(UQ, kind, name, ARGS).fields).length > 0; -} - -unittest { -	static struct S { -		void m(int i) {} -		bool opEquals(int i) { return true; } -		bool opEquals(S s) { return true; } -	} - -	static union U { int i; string s; S st; } -	alias TA = TaggedAlgebraic!U; - -	static assert(hasOp!(TA, OpKind.binary, "+", int)); -	static assert(hasOp!(TA, OpKind.binary, "~", string)); -	static assert(hasOp!(TA, OpKind.binary, "==", int)); -	static assert(hasOp!(TA, OpKind.binary, "==", string)); -	static assert(hasOp!(TA, OpKind.binary, "==", int)); -	static assert(hasOp!(TA, OpKind.binary, "==", S)); -	static assert(hasOp!(TA, OpKind.method, "m", int)); -	static assert(hasOp!(TA, OpKind.binary, "+=", int)); -	static assert(!hasOp!(TA, OpKind.binary, "~", int)); -	static assert(!hasOp!(TA, OpKind.binary, "~", int)); -	static assert(!hasOp!(TA, OpKind.method, "m", string)); -	static assert(!hasOp!(TA, OpKind.method, "m")); -	static assert(!hasOp!(const(TA), OpKind.binary, "+=", int)); -	static assert(!hasOp!(const(TA), OpKind.method, "m", int)); -} - -unittest { -	struct S { -		union U { -			string s; -			S[] arr; -			S[string] obj; -		} -		alias TA = TaggedAlgebraic!(S.U); -		TA payload; -		alias payload this; -	} -	static assert(hasOp!(S.TA, OpKind.index, null, size_t)); -	static assert(hasOp!(S.TA, OpKind.index, null, int)); -	static assert(hasOp!(S.TA, OpKind.index, null, string)); -	static assert(hasOp!(S.TA, OpKind.field, "length")); -} - -unittest { // "in" operator -	union U { -		string[string] dict; -	} -	alias TA = TaggedAlgebraic!U; -	auto ta = TA(["foo": "bar"]); -	assert("foo" in ta); -	assert(*("foo" in ta) == "bar"); -} - -private static auto implementOp(OpKind kind, string name, T, ARGS...)(ref T self, auto ref ARGS args) -{ -	import std.array : join; -	import std.traits : CopyTypeQualifiers; -	import std.variant : Algebraic, Variant; -	alias UQ = CopyTypeQualifiers!(T, T.Union); - -	alias info = OpInfo!(UQ, kind, name, ARGS); - -	static assert(hasOp!(T, kind, name, ARGS)); - -	static assert(info.fields.length > 0, "Implementing operator that has no valid implementation for any supported type."); - -	//pragma(msg, "Fields for "~kind.stringof~" "~name~", "~T.stringof~": "~info.fields.stringof); -	//pragma(msg, "Return types for "~kind.stringof~" "~name~", "~T.stringof~": "~info.ReturnTypes.stringof); -	//pragma(msg, typeof(T.Union.tupleof)); -	//import std.meta : staticMap; pragma(msg, staticMap!(isMatchingUniqueType!(T.Union), info.ReturnTypes)); - -	switch (self.m_kind) { -		default: assert(false, "Operator "~name~" ("~kind.stringof~") can only be used on values of the following types: "~[info.fields].join(", ")); -		foreach (i, f; info.fields) { -			alias FT = typeof(__traits(getMember, T.Union, f)); -			case __traits(getMember, T.Kind, f): -				static if (NoDuplicates!(info.ReturnTypes).length == 1) -					return info.perform(self.trustedGet!FT, args); -				else static if (allSatisfy!(isMatchingUniqueType!(T.Union), info.ReturnTypes)) -					return TaggedAlgebraic!(T.Union)(info.perform(self.trustedGet!FT, args)); -				else static if (allSatisfy!(isNoVariant, info.ReturnTypes)) { -					alias Alg = Algebraic!(NoDuplicates!(info.ReturnTypes)); -					info.ReturnTypes[i] ret = info.perform(self.trustedGet!FT, args); -					import std.traits : isInstanceOf; -					static if (isInstanceOf!(TaggedAlgebraic, typeof(ret))) return Alg(ret.payload); -					else return Alg(ret); -				} -				else static if (is(FT == Variant)) -					return info.perform(self.trustedGet!FT, args); -				else -					return Variant(info.perform(self.trustedGet!FT, args)); -		} -	} - -	assert(false); // never reached -} - -unittest { // opIndex on recursive TA with closed return value set -	static struct S { -		union U { -			char ch; -			string str; -			S[] arr; -		} -		alias TA = TaggedAlgebraic!U; -		TA payload; -		alias payload this; - -		this(T)(T t) { this.payload = t; } -	} -	S a = S("foo"); -	S s = S([a]); - -	assert(implementOp!(OpKind.field, "length")(s.payload) == 1); -	static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S.TA)); -	assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo"); -} - -unittest { // opIndex on recursive TA with closed return value set using @disableIndex -	static struct S { -		union U { -			@disableIndex string str; -			S[] arr; -		} -		alias TA = TaggedAlgebraic!U; -		TA payload; -		alias payload this; - -		this(T)(T t) { this.payload = t; } -	} -	S a = S("foo"); -	S s = S([a]); - -	assert(implementOp!(OpKind.field, "length")(s.payload) == 1); -	static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S)); -	assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo"); -} - - -private auto performOpRaw(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args) -{ -	static if (kind == OpKind.binary) return mixin("value "~name~" args[0]"); -	else static if (kind == OpKind.binaryRight) return mixin("args[0] "~name~" value"); -	else static if (kind == OpKind.unary) return mixin("name "~value); -	else static if (kind == OpKind.method) return __traits(getMember, value, name)(args); -	else static if (kind == OpKind.field) return __traits(getMember, value, name); -	else static if (kind == OpKind.index) return value[args]; -	else static if (kind == OpKind.indexAssign) return value[args[1 .. $]] = args[0]; -	else static if (kind == OpKind.call) return value(args); -	else static assert(false, "Unsupported kind of operator: "~kind.stringof); -} - -unittest { -	union U { int i; string s; } - -	{ int v = 1; assert(performOpRaw!(U, OpKind.binary, "+")(v, 3) == 4); } -	{ string v = "foo"; assert(performOpRaw!(U, OpKind.binary, "~")(v, "bar") == "foobar"); } -} - - -private auto performOp(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args) -{ -	import std.traits : isInstanceOf; -	static if (ARGS.length > 0 && isInstanceOf!(TaggedAlgebraic, ARGS[0])) { -		static if (is(typeof(performOpRaw!(U, kind, name, T, ARGS)(value, args)))) { -			return performOpRaw!(U, kind, name, T, ARGS)(value, args); -		} else { -			alias TA = ARGS[0]; -			template MTypesImpl(size_t i) { -				static if (i < TA.FieldTypes.length) { -					alias FT = TA.FieldTypes[i]; -					static if (is(typeof(&performOpRaw!(U, kind, name, T, FT, ARGS[1 .. $])))) -						alias MTypesImpl = TypeTuple!(FT, MTypesImpl!(i+1)); -					else alias MTypesImpl = TypeTuple!(MTypesImpl!(i+1)); -				} else alias MTypesImpl = TypeTuple!(); -			} -			alias MTypes = NoDuplicates!(MTypesImpl!0); -			static assert(MTypes.length > 0, "No type of the TaggedAlgebraic parameter matches any function declaration."); -			static if (MTypes.length == 1) { -				if (args[0].hasType!(MTypes[0])) -					return performOpRaw!(U, kind, name)(value, args[0].get!(MTypes[0]), args[1 .. $]); -			} else { -				// TODO: allow all return types (fall back to Algebraic or Variant) -				foreach (FT; MTypes) { -					if (args[0].hasType!FT) -						return ARGS[0](performOpRaw!(U, kind, name)(value, args[0].get!FT, args[1 .. $])); -				} -			} -			throw new /*InvalidAgument*/Exception("Algebraic parameter type mismatch"); -		} -	} else return performOpRaw!(U, kind, name, T, ARGS)(value, args); -} - -unittest { -	union U { int i; double d; string s; } - -	{ int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, 3) == 4); } -	{ string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, "bar") == "foobar"); } -	{ string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, TaggedAlgebraic!U("bar")) == "foobar"); } -	{ int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, TaggedAlgebraic!U(3)) == 4); } -} - - -private template OpInfo(U, OpKind kind, string name, ARGS...) -{ -	import std.traits : CopyTypeQualifiers, FieldTypeTuple, FieldNameTuple, ReturnType; - -	private alias FieldTypes = FieldTypeTuple!U; -	private alias fieldNames = FieldNameTuple!U; - -	private template isOpEnabled(string field) -	{ -		alias attribs = TypeTuple!(__traits(getAttributes, __traits(getMember, U, field))); -		template impl(size_t i) { -			static if (i < attribs.length) { -				static if (is(typeof(attribs[i]) == DisableOpAttribute)) { -					static if (kind == attribs[i].kind && name == attribs[i].name) -						enum impl = false; -					else enum impl = impl!(i+1); -				} else enum impl = impl!(i+1); -			} else enum impl = true; -		} -		enum isOpEnabled = impl!0; -	} - -	template fieldsImpl(size_t i) -	{ -		static if (i < FieldTypes.length) { -			static if (isOpEnabled!(fieldNames[i]) && is(typeof(&performOp!(U, kind, name, FieldTypes[i], ARGS)))) { -				alias fieldsImpl = TypeTuple!(fieldNames[i], fieldsImpl!(i+1)); -			} else alias fieldsImpl = fieldsImpl!(i+1); -		} else alias fieldsImpl = TypeTuple!(); -	} -	alias fields = fieldsImpl!0; - -	template ReturnTypesImpl(size_t i) { -		static if (i < fields.length) { -			alias FT = CopyTypeQualifiers!(U, typeof(__traits(getMember, U, fields[i]))); -			alias ReturnTypesImpl = TypeTuple!(ReturnType!(performOp!(U, kind, name, FT, ARGS)), ReturnTypesImpl!(i+1)); -		} else alias ReturnTypesImpl = TypeTuple!(); -	} -	alias ReturnTypes = ReturnTypesImpl!0; - -	static auto perform(T)(ref T value, auto ref ARGS args) { return performOp!(U, kind, name)(value, args); } -} - -private template ImplicitUnqual(T) { -	import std.traits : Unqual, hasAliasing; -	static if (is(T == void)) alias ImplicitUnqual = void; -	else { -		private static struct S { T t; } -		static if (hasAliasing!S) alias ImplicitUnqual = T; -		else alias ImplicitUnqual = Unqual!T; -	} -} - -private enum OpKind { -	binary, -	binaryRight, -	unary, -	method, -	field, -	index, -	indexAssign, -	call -} - -private template TypeEnum(U) -{ -	import std.array : join; -	import std.traits : FieldNameTuple; -	mixin("enum TypeEnum { " ~ [FieldNameTuple!U].join(", ") ~ " }"); -} - -private string generateConstructors(U)() -{ -	import std.algorithm : map; -	import std.array : join; -	import std.string : format; -	import std.traits : FieldTypeTuple; - -	string ret; - -	// disable default construction if first type is not a null/Void type -	static if (!is(FieldTypeTuple!U[0] == typeof(null)) && !is(FieldTypeTuple!U[0] == Void)) -	{ -		ret ~= q{ -			@disable this(); -		}; -	} - -	// normal type constructors -	foreach (tname; UniqueTypeFields!U) -		ret ~= q{ -			this(typeof(U.%s) value) -			{ -				m_data.rawEmplace(value); -				m_kind = Kind.%s; -			} - -			void opAssign(typeof(U.%s) value) -			{ -				if (m_kind != Kind.%s) { -					// NOTE: destroy(this) doesn't work for some opDispatch-related reason -					static if (is(typeof(&this.__xdtor))) -						this.__xdtor(); -					m_data.rawEmplace(value); -				} else { -					trustedGet!"%s" = value; -				} -				m_kind = Kind.%s; -			} -		}.format(tname, tname, tname, tname, tname, tname); - -	// type constructors with explicit type tag -	foreach (tname; AmbiguousTypeFields!U) -		ret ~= q{ -			this(typeof(U.%s) value, Kind type) -			{ -				assert(type.among!(%s), format("Invalid type ID for type %%s: %%s", typeof(U.%s).stringof, type)); -				m_data.rawEmplace(value); -				m_kind = type; -			} -		}.format(tname, [SameTypeFields!(U, tname)].map!(f => "Kind."~f).join(", "), tname); - -	return ret; -} - -private template UniqueTypeFields(U) { -	import std.traits : FieldTypeTuple, FieldNameTuple; - -	alias Types = FieldTypeTuple!U; - -	template impl(size_t i) { -		static if (i < Types.length) { -			enum name = FieldNameTuple!U[i]; -			alias T = Types[i]; -			static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) < 0) -				alias impl = TypeTuple!(name, impl!(i+1)); -			else alias impl = TypeTuple!(impl!(i+1)); -		} else alias impl = TypeTuple!(); -	} -	alias UniqueTypeFields = impl!0; -} - -private template AmbiguousTypeFields(U) { -	import std.traits : FieldTypeTuple, FieldNameTuple; - -	alias Types = FieldTypeTuple!U; - -	template impl(size_t i) { -		static if (i < Types.length) { -			enum name = FieldNameTuple!U[i]; -			alias T = Types[i]; -			static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) >= 0) -				alias impl = TypeTuple!(name, impl!(i+1)); -			else alias impl = impl!(i+1); -		} else alias impl = TypeTuple!(); -	} -	alias AmbiguousTypeFields = impl!0; -} - -unittest { -	union U { -		int a; -		string b; -		int c; -		double d; -	} -	static assert([UniqueTypeFields!U] == ["b", "d"]); -	static assert([AmbiguousTypeFields!U] == ["a"]); -} - -private template SameTypeFields(U, string field) { -	import std.traits : FieldTypeTuple, FieldNameTuple; - -	alias Types = FieldTypeTuple!U; - -	alias T = typeof(__traits(getMember, U, field)); -	template impl(size_t i) { -		static if (i < Types.length) { -			enum name = FieldNameTuple!U[i]; -			static if (is(Types[i] == T)) -				alias impl = TypeTuple!(name, impl!(i+1)); -			else alias impl = TypeTuple!(impl!(i+1)); -		} else alias impl = TypeTuple!(); -	} -	alias SameTypeFields = impl!0; -} - -private template MemberType(U) { -	template MemberType(string name) { -		alias MemberType = typeof(__traits(getMember, U, name)); -	} -} - -private template isMatchingType(U) { -	import std.traits : FieldTypeTuple; -	enum isMatchingType(T) = staticIndexOf!(T, FieldTypeTuple!U) >= 0; -} - -private template isMatchingUniqueType(U) { -	import std.traits : staticMap; -	alias UniqueTypes = staticMap!(FieldTypeOf!U, UniqueTypeFields!U); -	template isMatchingUniqueType(T) { -		static if (is(T : TaggedAlgebraic!U)) enum isMatchingUniqueType = true; -		else enum isMatchingUniqueType = staticIndexOfImplicit!(T, UniqueTypes) >= 0; -	} -} - -private template fieldMatchesType(U, T) -{ -	enum fieldMatchesType(string field) = is(typeof(__traits(getMember, U, field)) == T); -} - -private template FieldTypeOf(U) { -	template FieldTypeOf(string name) { -		alias FieldTypeOf = typeof(__traits(getMember, U, name)); -	} -} - -private template staticIndexOfImplicit(T, Types...) { -	template impl(size_t i) { -		static if (i < Types.length) { -			static if (is(T : Types[i])) enum impl = i; -			else enum impl = impl!(i+1); -		} else enum impl = -1; -	} -	enum staticIndexOfImplicit = impl!0; -} - -unittest { -	static assert(staticIndexOfImplicit!(immutable(char), char) == 0); -	static assert(staticIndexOfImplicit!(int, long) == 0); -	static assert(staticIndexOfImplicit!(long, int) < 0); -	static assert(staticIndexOfImplicit!(int, int, double) == 0); -	static assert(staticIndexOfImplicit!(double, int, double) == 1); -} - - -private template isNoVariant(T) { -	import std.variant : Variant; -	enum isNoVariant = !is(T == Variant); -} - -private void rawEmplace(T)(void[] dst, ref T src) -{ -	T* tdst = () @trusted { return cast(T*)dst.ptr; } (); -	static if (is(T == class)) { -		*tdst = src; -	} else { -		import std.conv : emplace; -		emplace(tdst); -		*tdst = src; -	} -} diff --git a/src/sdlang/token.d b/src/sdlang/token.d deleted file mode 100644 index 0a5b2fd..0000000 --- a/src/sdlang/token.d +++ /dev/null @@ -1,550 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.token; - -import std.array; -import std.base64; -import std.conv; -import std.datetime; -import std.meta; -import std.range; -import std.string; -import std.traits; -import std.typetuple; -import std.variant; - -import sdlang.exception; -import sdlang.symbol; -import sdlang.util; - -/// DateTime doesn't support milliseconds, but SDLang's "Date Time" type does. -/// So this is needed for any SDL "Date Time" that doesn't include a time zone. -struct DateTimeFrac -{ -	DateTime dateTime; -	Duration fracSecs; -	deprecated("Use fracSecs instead.") { -		@property FracSec fracSec() const { return FracSec.from!"hnsecs"(fracSecs.total!"hnsecs"); } -		@property void fracSec(FracSec v) { fracSecs = v.hnsecs.hnsecs; } -	} -} - -/++ -If a "Date Time" literal in the SDL file has a time zone that's not found in -your system, you get one of these instead of a SysTime. (Because it's -impossible to indicate "unknown time zone" with `std.datetime.TimeZone`.) - -The difference between this and `DateTimeFrac` is that `DateTimeFrac` -indicates that no time zone was specified in the SDL at all, whereas -`DateTimeFracUnknownZone` indicates that a time zone was specified but -data for it could not be found on your system. -+/ -struct DateTimeFracUnknownZone -{ -	DateTime dateTime; -	Duration fracSecs; -	deprecated("Use fracSecs instead.") { -		@property FracSec fracSec() const { return FracSec.from!"hnsecs"(fracSecs.total!"hnsecs"); } -		@property void fracSec(FracSec v) { fracSecs = v.hnsecs.hnsecs; } -	} -	string timeZone; - -	bool opEquals(const DateTimeFracUnknownZone b) const -	{ -		return opEquals(b); -	} -	bool opEquals(ref const DateTimeFracUnknownZone b) const -	{ -		return -			this.dateTime == b.dateTime && -			this.fracSecs  == b.fracSecs  && -			this.timeZone == b.timeZone; -	} -} - -/++ -SDLang's datatypes map to D's datatypes as described below. -Most are straightforward, but take special note of the date/time-related types. - ---------------------------------------------------------------- -Boolean:                       bool -Null:                          typeof(null) -Unicode Character:             dchar -Double-Quote Unicode String:   string -Raw Backtick Unicode String:   string -Integer (32 bits signed):      int -Long Integer (64 bits signed): long -Float (32 bits signed):        float -Double Float (64 bits signed): double -Decimal (128+ bits signed):    real -Binary (standard Base64):      ubyte[] -Time Span:                     Duration - -Date (with no time at all):           Date -Date Time (no timezone):              DateTimeFrac -Date Time (with a known timezone):    SysTime -Date Time (with an unknown timezone): DateTimeFracUnknownZone ---------------------------------------------------------------- -+/ -alias ValueTypes = TypeTuple!( -	bool, -	string, dchar, -	int, long, -	float, double, real, -	Date, DateTimeFrac, SysTime, DateTimeFracUnknownZone, Duration, -	ubyte[], -	typeof(null), -); - -alias Value = Algebraic!( ValueTypes ); ///ditto -enum isValueType(T) = staticIndexOf!(T, ValueTypes) != -1; - -enum isSink(T) = -	isOutputRange!T && -	is(ElementType!(T)[] == string); - -string toSDLString(T)(T value) if(is(T==Value) || isValueType!T) -{ -	Appender!string sink; -	toSDLString(value, sink); -	return sink.data; -} - -/// Throws SDLangException if value is infinity, -infinity or NaN, because -/// those are not currently supported by the SDLang spec. -void toSDLString(Sink)(Value value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	foreach(T; ValueTypes) -	{ -		if(value.type == typeid(T)) -		{ -			toSDLString( value.get!T(), sink ); -			return; -		} -	} -	 -	throw new Exception("Internal SDLang-D error: Unhandled type of Value. Contains: "~value.toString()); -} - -@("toSDLString on infinity and NaN") -unittest -{ -	import std.exception; -	 -	auto floatInf    = float.infinity; -	auto floatNegInf = -float.infinity; -	auto floatNaN    = float.nan; - -	auto doubleInf    = double.infinity; -	auto doubleNegInf = -double.infinity; -	auto doubleNaN    = double.nan; - -	auto realInf    = real.infinity; -	auto realNegInf = -real.infinity; -	auto realNaN    = real.nan; - -	assertNotThrown( toSDLString(0.0F) ); -	assertNotThrown( toSDLString(0.0)  ); -	assertNotThrown( toSDLString(0.0L) ); -	 -	assertThrown!ValidationException( toSDLString(floatInf) ); -	assertThrown!ValidationException( toSDLString(floatNegInf) ); -	assertThrown!ValidationException( toSDLString(floatNaN) ); - -	assertThrown!ValidationException( toSDLString(doubleInf) ); -	assertThrown!ValidationException( toSDLString(doubleNegInf) ); -	assertThrown!ValidationException( toSDLString(doubleNaN) ); - -	assertThrown!ValidationException( toSDLString(realInf) ); -	assertThrown!ValidationException( toSDLString(realNegInf) ); -	assertThrown!ValidationException( toSDLString(realNaN) ); -	 -	assertThrown!ValidationException( toSDLString(Value(floatInf)) ); -	assertThrown!ValidationException( toSDLString(Value(floatNegInf)) ); -	assertThrown!ValidationException( toSDLString(Value(floatNaN)) ); - -	assertThrown!ValidationException( toSDLString(Value(doubleInf)) ); -	assertThrown!ValidationException( toSDLString(Value(doubleNegInf)) ); -	assertThrown!ValidationException( toSDLString(Value(doubleNaN)) ); - -	assertThrown!ValidationException( toSDLString(Value(realInf)) ); -	assertThrown!ValidationException( toSDLString(Value(realNegInf)) ); -	assertThrown!ValidationException( toSDLString(Value(realNaN)) ); -} - -void toSDLString(Sink)(typeof(null) value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	sink.put("null"); -} - -void toSDLString(Sink)(bool value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	sink.put(value? "true" : "false"); -} - -//TODO: Figure out how to properly handle strings/chars containing lineSep or paraSep -void toSDLString(Sink)(string value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	sink.put('"'); -	 -	// This loop is UTF-safe -	foreach(char ch; value) -	{ -		if     (ch == '\n') sink.put(`\n`); -		else if(ch == '\r') sink.put(`\r`); -		else if(ch == '\t') sink.put(`\t`); -		else if(ch == '\"') sink.put(`\"`); -		else if(ch == '\\') sink.put(`\\`); -		else -			sink.put(ch); -	} - -	sink.put('"'); -} - -void toSDLString(Sink)(dchar value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	sink.put('\''); -	 -	if     (value == '\n') sink.put(`\n`); -	else if(value == '\r') sink.put(`\r`); -	else if(value == '\t') sink.put(`\t`); -	else if(value == '\'') sink.put(`\'`); -	else if(value == '\\') sink.put(`\\`); -	else -		sink.put(value); - -	sink.put('\''); -} - -void toSDLString(Sink)(int value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	sink.put( "%s".format(value) ); -} - -void toSDLString(Sink)(long value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	sink.put( "%sL".format(value) ); -} - -private void checkUnsupportedFloatingPoint(T)(T value) if(isFloatingPoint!T) -{ -	import std.exception; -	import std.math; -	 -	enforce!ValidationException( -		!isInfinity(value), -		"SDLang does not currently support infinity for floating-point types" -	); - -	enforce!ValidationException( -		!isNaN(value), -		"SDLang does not currently support NaN for floating-point types" -	); -} - -void toSDLString(Sink)(float value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	checkUnsupportedFloatingPoint(value); -	sink.put( "%.10sF".format(value) ); -} - -void toSDLString(Sink)(double value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	checkUnsupportedFloatingPoint(value); -	sink.put( "%.30sD".format(value) ); -} - -void toSDLString(Sink)(real value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	checkUnsupportedFloatingPoint(value); -	sink.put( "%.30sBD".format(value) ); -} - -void toSDLString(Sink)(Date value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	sink.put(to!string(value.year)); -	sink.put('/'); -	sink.put(to!string(cast(int)value.month)); -	sink.put('/'); -	sink.put(to!string(value.day)); -} - -void toSDLString(Sink)(DateTimeFrac value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	toSDLString(value.dateTime.date, sink); -	sink.put(' '); -	sink.put("%.2s".format(value.dateTime.hour)); -	sink.put(':'); -	sink.put("%.2s".format(value.dateTime.minute)); -	 -	if(value.dateTime.second != 0) -	{ -		sink.put(':'); -		sink.put("%.2s".format(value.dateTime.second)); -	} - -	if(value.fracSecs != 0.msecs) -	{ -		sink.put('.'); -		sink.put("%.3s".format(value.fracSecs.total!"msecs")); -	} -} - -void toSDLString(Sink)(SysTime value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	auto dateTimeFrac = DateTimeFrac(cast(DateTime)value, value.fracSecs); -	toSDLString(dateTimeFrac, sink); -	 -	sink.put("-"); -	 -	auto tzString = value.timezone.name; -	 -	// If name didn't exist, try abbreviation. -	// Note that according to std.datetime docs, on Windows the -	// stdName/dstName may not be properly abbreviated. -	version(Windows) {} else -	if(tzString == "") -	{ -		auto tz = value.timezone; -		auto stdTime = value.stdTime; -		 -		if(tz.hasDST()) -			tzString = tz.dstInEffect(stdTime)? tz.dstName : tz.stdName; -		else -			tzString = tz.stdName; -	} -	 -	if(tzString == "") -	{ -		auto offset = value.timezone.utcOffsetAt(value.stdTime); -		sink.put("GMT"); - -		if(offset < seconds(0)) -		{ -			sink.put("-"); -			offset = -offset; -		} -		else -			sink.put("+"); -		 -		sink.put("%.2s".format(offset.split.hours)); -		sink.put(":"); -		sink.put("%.2s".format(offset.split.minutes)); -	} -	else -		sink.put(tzString); -} - -void toSDLString(Sink)(DateTimeFracUnknownZone value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	auto dateTimeFrac = DateTimeFrac(value.dateTime, value.fracSecs); -	toSDLString(dateTimeFrac, sink); -	 -	sink.put("-"); -	sink.put(value.timeZone); -} - -void toSDLString(Sink)(Duration value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	if(value < seconds(0)) -	{ -		sink.put("-"); -		value = -value; -	} -	 -	auto days = value.total!"days"(); -	if(days != 0) -	{ -		sink.put("%s".format(days)); -		sink.put("d:"); -	} - -	sink.put("%.2s".format(value.split.hours)); -	sink.put(':'); -	sink.put("%.2s".format(value.split.minutes)); -	sink.put(':'); -	sink.put("%.2s".format(value.split.seconds)); - -	if(value.split.msecs != 0) -	{ -		sink.put('.'); -		sink.put("%.3s".format(value.split.msecs)); -	} -} - -void toSDLString(Sink)(ubyte[] value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ -	sink.put('['); -	sink.put( Base64.encode(value) ); -	sink.put(']'); -} - -/// This only represents terminals. Nonterminals aren't -/// constructed since the AST is directly built during parsing. -struct Token -{ -	Symbol symbol = sdlang.symbol.symbol!"Error"; /// The "type" of this token -	Location location; -	Value value; /// Only valid when `symbol` is `symbol!"Value"`, otherwise null -	string data; /// Original text from source - -	@disable this(); -	this(Symbol symbol, Location location, Value value=Value(null), string data=null) -	{ -		this.symbol   = symbol; -		this.location = location; -		this.value    = value; -		this.data     = data; -	} -	 -	/// Tokens with differing symbols are always unequal. -	/// Tokens with differing values are always unequal. -	/// Tokens with differing Value types are always unequal. -	/// Member `location` is always ignored for comparison. -	/// Member `data` is ignored for comparison *EXCEPT* when the symbol is Ident. -	bool opEquals(Token b) -	{ -		return opEquals(b); -	} -	bool opEquals(ref Token b) ///ditto -	{ -		if( -			this.symbol     != b.symbol     || -			this.value.type != b.value.type || -			this.value      != b.value -		) -			return false; -		 -		if(this.symbol == .symbol!"Ident") -			return this.data == b.data; -		 -		return true; -	} -	 -	bool matches(string symbolName)() -	{ -		return this.symbol == .symbol!symbolName; -	} -} - -@("sdlang token") -unittest -{ -	auto loc  = Location("", 0, 0, 0); -	auto loc2 = Location("a", 1, 1, 1); - -	assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc )); -	assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc2)); -	assert(Token(symbol!":",  loc) == Token(symbol!":",  loc )); -	assert(Token(symbol!"EOL",loc) != Token(symbol!":",  loc )); -	assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),"\n")); - -	assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),";" )); -	assert(Token(symbol!"EOL",loc,Value(null),"A" ) == Token(symbol!"EOL",loc,Value(null),"B" )); -	assert(Token(symbol!":",  loc,Value(null),"A" ) == Token(symbol!":",  loc,Value(null),"BB")); -	assert(Token(symbol!"EOL",loc,Value(null),"A" ) != Token(symbol!":",  loc,Value(null),"A" )); - -	assert(Token(symbol!"Ident",loc,Value(null),"foo") == Token(symbol!"Ident",loc,Value(null),"foo")); -	assert(Token(symbol!"Ident",loc,Value(null),"foo") != Token(symbol!"Ident",loc,Value(null),"BAR")); - -	assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"foo")); -	assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc2,Value(null),"foo")); -	assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"BAR")); -	assert(Token(symbol!"Value",loc,Value(   7),"foo") == Token(symbol!"Value",loc, Value(   7),"BAR")); -	assert(Token(symbol!"Value",loc,Value(   7),"foo") != Token(symbol!"Value",loc, Value( "A"),"foo")); -	assert(Token(symbol!"Value",loc,Value(   7),"foo") != Token(symbol!"Value",loc, Value(   2),"foo")); -	assert(Token(symbol!"Value",loc,Value(cast(int)7)) != Token(symbol!"Value",loc, Value(cast(long)7))); -	assert(Token(symbol!"Value",loc,Value(cast(float)1.2)) != Token(symbol!"Value",loc, Value(cast(double)1.2))); -} - -@("sdlang Value.toSDLString()") -unittest -{ -	// Bool and null -	assert(Value(null ).toSDLString() == "null"); -	assert(Value(true ).toSDLString() == "true"); -	assert(Value(false).toSDLString() == "false"); -	 -	// Base64 Binary -	assert(Value(cast(ubyte[])"hello world".dup).toSDLString() == "[aGVsbG8gd29ybGQ=]"); - -	// Integer -	assert(Value(cast( int) 7).toSDLString() ==  "7"); -	assert(Value(cast( int)-7).toSDLString() == "-7"); -	assert(Value(cast( int) 0).toSDLString() ==  "0"); - -	assert(Value(cast(long) 7).toSDLString() ==  "7L"); -	assert(Value(cast(long)-7).toSDLString() == "-7L"); -	assert(Value(cast(long) 0).toSDLString() ==  "0L"); - -	// Floating point -	assert(Value(cast(float) 1.5).toSDLString() ==  "1.5F"); -	assert(Value(cast(float)-1.5).toSDLString() == "-1.5F"); -	assert(Value(cast(float)   0).toSDLString() ==    "0F"); - -	assert(Value(cast(double) 1.5).toSDLString() ==  "1.5D"); -	assert(Value(cast(double)-1.5).toSDLString() == "-1.5D"); -	assert(Value(cast(double)   0).toSDLString() ==    "0D"); - -	assert(Value(cast(real) 1.5).toSDLString() ==  "1.5BD"); -	assert(Value(cast(real)-1.5).toSDLString() == "-1.5BD"); -	assert(Value(cast(real)   0).toSDLString() ==    "0BD"); - -	// String -	assert(Value("hello"  ).toSDLString() == `"hello"`); -	assert(Value(" hello ").toSDLString() == `" hello "`); -	assert(Value(""       ).toSDLString() == `""`); -	assert(Value("hello \r\n\t\"\\ world").toSDLString() == `"hello \r\n\t\"\\ world"`); -	assert(Value("日本語").toSDLString() == `"日本語"`); - -	// Chars -	assert(Value(cast(dchar) 'A').toSDLString() ==  `'A'`); -	assert(Value(cast(dchar)'\r').toSDLString() == `'\r'`); -	assert(Value(cast(dchar)'\n').toSDLString() == `'\n'`); -	assert(Value(cast(dchar)'\t').toSDLString() == `'\t'`); -	assert(Value(cast(dchar)'\'').toSDLString() == `'\''`); -	assert(Value(cast(dchar)'\\').toSDLString() == `'\\'`); -	assert(Value(cast(dchar) '月').toSDLString() ==  `'月'`); - -	// Date -	assert(Value(Date( 2004,10,31)).toSDLString() == "2004/10/31"); -	assert(Value(Date(-2004,10,31)).toSDLString() == "-2004/10/31"); - -	// DateTimeFrac w/o Frac -	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15))).toSDLString() == "2004/10/31 14:30:15"); -	assert(Value(DateTimeFrac(DateTime(2004,10,31,   1, 2, 3))).toSDLString() == "2004/10/31 01:02:03"); -	assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15))).toSDLString() == "-2004/10/31 14:30:15"); - -	// DateTimeFrac w/ Frac -	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15), 123.msecs)).toSDLString() == "2004/10/31 14:30:15.123"); -	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15), 120.msecs)).toSDLString() == "2004/10/31 14:30:15.120"); -	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15), 100.msecs)).toSDLString() == "2004/10/31 14:30:15.100"); -	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15),  12.msecs)).toSDLString() == "2004/10/31 14:30:15.012"); -	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15),   1.msecs)).toSDLString() == "2004/10/31 14:30:15.001"); -	assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15), 123.msecs)).toSDLString() == "-2004/10/31 14:30:15.123"); - -	// DateTimeFracUnknownZone -	assert(Value(DateTimeFracUnknownZone(DateTime(2004,10,31, 14,30,15), 123.msecs, "Foo/Bar")).toSDLString() == "2004/10/31 14:30:15.123-Foo/Bar"); - -	// SysTime -	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(0)             ))).toSDLString() == "2004/10/31 14:30:15-GMT+00:00"); -	assert(Value(SysTime(DateTime(2004,10,31,  1, 2, 3), new immutable SimpleTimeZone( hours(0)             ))).toSDLString() == "2004/10/31 01:02:03-GMT+00:00"); -	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes(10) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:10"); -	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone(-hours(5)-minutes(30) ))).toSDLString() == "2004/10/31 14:30:15-GMT-05:30"); -	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes( 3) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:03"); -	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), 123.msecs, new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 14:30:15.123-GMT+00:00"); - -	// Duration -	assert( "12:14:42"         == Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs(  0)).toSDLString()); -	assert("-12:14:42"         == Value(-days( 0)-hours(12)-minutes(14)-seconds(42)-msecs(  0)).toSDLString()); -	assert( "00:09:12"         == Value( days( 0)+hours( 0)+minutes( 9)+seconds(12)+msecs(  0)).toSDLString()); -	assert( "00:00:01.023"     == Value( days( 0)+hours( 0)+minutes( 0)+seconds( 1)+msecs( 23)).toSDLString()); -	assert( "23d:05:21:23.532" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(532)).toSDLString()); -	assert( "23d:05:21:23.530" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(530)).toSDLString()); -	assert( "23d:05:21:23.500" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(500)).toSDLString()); -	assert("-23d:05:21:23.532" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(532)).toSDLString()); -	assert("-23d:05:21:23.500" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(500)).toSDLString()); -	assert( "23d:05:21:23"     == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(  0)).toSDLString()); -} diff --git a/src/sdlang/util.d b/src/sdlang/util.d deleted file mode 100644 index d192ea2..0000000 --- a/src/sdlang/util.d +++ /dev/null @@ -1,200 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.util; - -import std.algorithm; -import std.array; -import std.conv; -import std.datetime; -import std.range; -import std.stdio; -import std.string; - -import sdlang.exception; -import sdlang.token; - -enum sdlangVersion = "0.9.1"; - -alias immutable(ubyte)[] ByteString; - -auto startsWith(T)(string haystack, T needle) -	if( is(T:ByteString) || is(T:string) ) -{ -	return std.algorithm.startsWith( cast(ByteString)haystack, cast(ByteString)needle ); -} - -struct Location -{ -	string file; /// Filename (including path) -	int line; /// Zero-indexed -	int col;  /// Zero-indexed, Tab counts as 1 -	size_t index; /// Index into the source - -	this(int line, int col, int index) -	{ -		this.line  = line; -		this.col   = col; -		this.index = index; -	} - -	this(string file, int line, int col, int index) -	{ -		this.file  = file; -		this.line  = line; -		this.col   = col; -		this.index = index; -	} - -	/// Convert to string. Optionally takes output range as a sink. -	string toString() -	{ -		Appender!string sink; -		this.toString(sink); -		return sink.data; -	} - -	///ditto -	void toString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char)) -	{ -		sink.put(file); -		sink.put("("); -		sink.put(to!string(line+1)); -		sink.put(":"); -		sink.put(to!string(col+1)); -		sink.put(")"); -	} -} - -struct FullName -{ -	string namespace; -	string name; - -	/// Convert to string. Optionally takes output range as a sink. -	string toString() -	{ -		if(namespace == "") -			return name; - -		Appender!string sink; -		this.toString(sink); -		return sink.data; -	} - -	///ditto -	void toString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char)) -	{ -		if(namespace != "") -		{ -			sink.put(namespace); -			sink.put(":"); -		} - -		sink.put(name); -	} - -	/// -	static string combine(string namespace, string name) -	{ -		return FullName(namespace, name).toString(); -	} -	/// -	@("FullName.combine example") -	unittest -	{ -		assert(FullName.combine("", "name") == "name"); -		assert(FullName.combine("*", "name") == "*:name"); -		assert(FullName.combine("namespace", "name") == "namespace:name"); -	} - -	/// -	static FullName parse(string fullName) -	{ -		FullName result; -		 -		auto parts = fullName.findSplit(":"); -		if(parts[1] == "") // No colon -		{ -			result.namespace = ""; -			result.name      = parts[0]; -		} -		else -		{ -			result.namespace = parts[0]; -			result.name      = parts[2]; -		} - -		return result; -	} -	/// -	@("FullName.parse example") -	unittest -	{ -		assert(FullName.parse("name") == FullName("", "name")); -		assert(FullName.parse("*:name") == FullName("*", "name")); -		assert(FullName.parse("namespace:name") == FullName("namespace", "name")); -	} - -	/// Throws with appropriate message if this.name is "*". -	/// Wildcards are only supported for namespaces, not names. -	void ensureNoWildcardName(string extaMsg = null) -	{ -		if(name == "*") -			throw new ArgumentException(`Wildcards ("*") only allowed for namespaces, not names. `~extaMsg); -	} -} -struct Foo { string foo; } - -void removeIndex(E)(ref E[] arr, ptrdiff_t index) -{ -	arr = arr[0..index] ~ arr[index+1..$]; -} - -void trace(string file=__FILE__, size_t line=__LINE__, TArgs...)(TArgs args) -{ -	version(sdlangTrace) -	{ -		writeln(file, "(", line, "): ", args); -		stdout.flush(); -	} -} - -string toString(TypeInfo ti) -{ -	if     (ti == typeid( bool         )) return "bool"; -	else if(ti == typeid( string       )) return "string"; -	else if(ti == typeid( dchar        )) return "dchar"; -	else if(ti == typeid( int          )) return "int"; -	else if(ti == typeid( long         )) return "long"; -	else if(ti == typeid( float        )) return "float"; -	else if(ti == typeid( double       )) return "double"; -	else if(ti == typeid( real         )) return "real"; -	else if(ti == typeid( Date         )) return "Date"; -	else if(ti == typeid( DateTimeFrac )) return "DateTimeFrac"; -	else if(ti == typeid( DateTimeFracUnknownZone )) return "DateTimeFracUnknownZone"; -	else if(ti == typeid( SysTime      )) return "SysTime"; -	else if(ti == typeid( Duration     )) return "Duration"; -	else if(ti == typeid( ubyte[]      )) return "ubyte[]"; -	else if(ti == typeid( typeof(null) )) return "null"; - -	return "{unknown}"; -} - -enum BOM { -	UTF8,           /// UTF-8 -	UTF16LE,        /// UTF-16 (little-endian) -	UTF16BE,        /// UTF-16 (big-endian) -	UTF32LE,        /// UTF-32 (little-endian) -	UTF32BE,        /// UTF-32 (big-endian) -} - -enum NBOM = __traits(allMembers, BOM).length; -immutable ubyte[][NBOM] ByteOrderMarks = -[ -	[0xEF, 0xBB, 0xBF],         //UTF8 -	[0xFF, 0xFE],               //UTF16LE -	[0xFE, 0xFF],               //UTF16BE -	[0xFF, 0xFE, 0x00, 0x00],   //UTF32LE -	[0x00, 0x00, 0xFE, 0xFF]    //UTF32BE -]; diff --git a/src/sdp/ao_abstract_doc_source.d b/src/sdp/ao_abstract_doc_source.d index db814ac..c7e5a13 100644 --- a/src/sdp/ao_abstract_doc_source.d +++ b/src/sdp/ao_abstract_doc_source.d @@ -1,5 +1,6 @@ -/+ -  document abstraction +/++ +  document abstraction: +  abstraction of sisu markup for downstream processing    ao_abstract_doc_source.d  +/  template SiSUdocAbstraction() { @@ -14,10 +15,6 @@ template SiSUdocAbstraction() {      /+ ↓ abstraction mixins +/      mixin ObjectSetter;      mixin InternalMarkup; -    // // mixin SiSUrgxInitFlags; -    // // mixin AssertionsOnBlocks; -    // mixin SiSUbiblio; // issue -    // mixin SiSUheader;      /+ ↓ abstraction struct init +/      /+ initialize +/      auto rgx = Rgx(); @@ -106,7 +103,6 @@ template SiSUdocAbstraction() {          is_        );      } -    // mixin SiSUdocAbstractionFunctions;      /+ ↓ abstract marked up document +/      auto abstract_doc_source(        char[][] markup_sourcefile_content, @@ -129,7 +125,6 @@ template SiSUdocAbstraction() {          "para"    : 0,        ];        auto type = flags_type_init; -      mixin ScreenTxtColors;        void tell_lo(int obj_cite_number, in char[] line) {          writefln(            "* %s %s", @@ -201,10 +196,9 @@ template SiSUdocAbstraction() {          }          line = replaceAll(line, rgx.true_dollar, "$$$$");            // dollar represented as $$ needed to stop submatching on $ -          // (substitutions using ${identifiers} must take into account (e.g. happen earlier)) +          // (substitutions using ${identifiers} must take into account (i.e. happen earlier))          debug(source) {                                  // source lines            writeln(line); -          // writeln(scr_txt_marker["green"], line);          }          debug(srclines) {            if (!line.empty) {                             // source lines, not empty @@ -1870,16 +1864,13 @@ template SiSUdocAbstraction() {      /+ abstraction functions ↑ +/      /+ ↓ abstraction function emitters +/      struct OCNemitter { -    // class OCNemitter : AssertOCN {        int obj_cite_number, obj_cite_number_;        int obj_cite_number_emitter(int obj_cite_number_status_flag)        in { assert(obj_cite_number_status_flag <= 2); }        body { -        if (obj_cite_number_status_flag == 0) { -          obj_cite_number=++obj_cite_number_; -        } else { -          obj_cite_number=0; -        } +        obj_cite_number=(obj_cite_number_status_flag == 0) +        ? ++obj_cite_number_ +        : 0;          assert(obj_cite_number >= 0);          return obj_cite_number;        } @@ -1887,7 +1878,6 @@ template SiSUdocAbstraction() {        }      }      struct ObjAttributes { -    // class ObjAttributes : AssertObjAttributes {        string[string] obj_txt;        string para_and_blocks(string obj_txt_in)        in { } @@ -2417,14 +2407,11 @@ template SiSUdocAbstraction() {        }      }      struct ObjAttrib { -    // struct ObjAttrib : AssertObjAttrib { -    // auto sink = appender!(char[])();        auto attrib = ObjAttributes();        string[string] obj_attrib;        string obj_attributes(string obj_is_, string obj_raw, string node)        in { }        body { -        // string s = "{ \"language\": \"D\", \"rating\": 3.14, \"code\": \"42\" }";          scope(exit) {            // destroy(obj_is_);            destroy(obj_raw); @@ -2488,9 +2475,7 @@ template SiSUdocAbstraction() {          obj_attrib["json"] = oa_j.toString();          debug(structattrib) {            if (oa_j["is"].str() == "heading") { -            // writeln(__LINE__);              writeln(obj_attrib["json"]); -            // writeln(node);              writeln(                "is: ", oa_j["is"].str(),                "; obj_cite_number: ", oa_j["obj_cite_number"].integer() @@ -2504,7 +2489,6 @@ template SiSUdocAbstraction() {        }      }      struct BookIndexNuggetHash { -    // class BookIndexNuggetHash : AssertBookIndexNuggetHash {        string main_term, sub_term, sub_term_bits;        int obj_cite_number_offset, obj_cite_number_endpoint;        string[] obj_cite_numbers; @@ -2859,7 +2843,6 @@ template SiSUdocAbstraction() {          ++mkn;          foreach (endnote; endnotes_) {            attrib=""; -          attrib="";            // endnotes ~=            //   set_abstract_object.contents_para(            //     obj, @@ -2957,7 +2940,6 @@ template SiSUdocAbstraction() {        }      }      struct NodeStructureMetadata { -    // class NodeStructureMetadata : AssertNodeJSON {        int lv, lv0, lv1, lv2, lv3, lv4, lv5, lv6, lv7;        int obj_cite_number;        int[string] p_; // p_ parent_ diff --git a/src/sdp/ao_ansi_colors.d b/src/sdp/ao_ansi_colors.d index e5a46f9..dea331d 100644 --- a/src/sdp/ao_ansi_colors.d +++ b/src/sdp/ao_ansi_colors.d @@ -1,6 +1,5 @@ -/+ -  utils -  ao_util.d +/++ +  ansi colors, depreciate use  +/  template ScreenTxtColors() {    string[string] scr_txt_color = [ diff --git a/src/sdp/ao_conf_make_meta.d b/src/sdp/ao_conf_make_meta.d index 04a9d7a..5bc9694 100644 --- a/src/sdp/ao_conf_make_meta.d +++ b/src/sdp/ao_conf_make_meta.d @@ -1,5 +1,12 @@ -/+ -  extract native/orig header return associative array +/++ +  extract native/orig header return associative array<BR> + +  the header is passed as text (lopped off top of a sisu markup file until the +  required first heading ^A~), determine whether is a native header or sdlang one +  with a regex check if whether it contains the "native header" required tag/field +  @title: then process accordingly as a "native header" or "sdlang header" +  converting the metadata and make instructions to a common json format used by +  program internally. Moved to associative array.  +/  template SiSUheaderExtractHub() {    private import diff --git a/src/sdp/ao_conf_make_meta_native.d b/src/sdp/ao_conf_make_meta_native.d index 9f0ad63..f70a7bf 100644 --- a/src/sdp/ao_conf_make_meta_native.d +++ b/src/sdp/ao_conf_make_meta_native.d @@ -1,4 +1,5 @@ -/+ +/++ +  native headers using<br>@title:<BR>:subtitle:<BR>type tags<BR>    extract native/orig header return associative array  +/  template SiSUheaderExtractNative() { diff --git a/src/sdp/ao_conf_make_meta_sdlang.d b/src/sdp/ao_conf_make_meta_sdlang.d index 1cc3498..61b4960 100644 --- a/src/sdp/ao_conf_make_meta_sdlang.d +++ b/src/sdp/ao_conf_make_meta_sdlang.d @@ -1,5 +1,6 @@ -/+ -  extract sdl header return sdl +/++ +  sdlang headers<BR> +  extract sdlang header return sdlang  +/  template SiSUheaderExtractSDLang() {    private import diff --git a/src/sdp/ao_defaults.d b/src/sdp/ao_defaults.d index ea5caae..8db42e2 100644 --- a/src/sdp/ao_defaults.d +++ b/src/sdp/ao_defaults.d @@ -1,6 +1,5 @@ -/+ -  defaults -  ao_defaults.d +/++ +  default settings  +/  template SiSUregisters() {    string[string][string] conf_aa() { diff --git a/src/sdp/ao_object_setter.d b/src/sdp/ao_object_setter.d index 745de4e..6cb359b 100644 --- a/src/sdp/ao_object_setter.d +++ b/src/sdp/ao_object_setter.d @@ -1,5 +1,6 @@ -/+ -  object setter +/++ +  object setter: +  setting of sisu objects for downstream processing    ao_object_setter.d  +/  template ObjectSetter() { diff --git a/src/sdp/ao_output_debugs.d b/src/sdp/ao_output_debugs.d index b5f96fa..9111cd6 100644 --- a/src/sdp/ao_output_debugs.d +++ b/src/sdp/ao_output_debugs.d @@ -1,6 +1,5 @@ -/+ +/++    output debugs -  ao_output_debugs.d  +/  template SiSUoutputDebugs() {    struct BookIndexReport { diff --git a/src/sdp/ao_read_config_files.d b/src/sdp/ao_read_config_files.d index 49efe7b..013acdd 100644 --- a/src/sdp/ao_read_config_files.d +++ b/src/sdp/ao_read_config_files.d @@ -1,6 +1,7 @@ -/+ +/++ +  read configuration files<BR> +  - read config files<BR>    ao_config_files.d -  - read config files  +/  template SiSUconfigIn() {    private import diff --git a/src/sdp/ao_read_source_files.d b/src/sdp/ao_read_source_files.d index eabc4dc..5aef05d 100644 --- a/src/sdp/ao_read_source_files.d +++ b/src/sdp/ao_read_source_files.d @@ -1,9 +1,8 @@ -/+ -  ao_read_source_files.d -  - open markup files +/++ +  module ao_read_source_files;<BR> +  - open markup files<BR>    - if master file scan for addional files to import/insert  +/ -// module ao_read_source_files;  template SiSUmarkupRaw() {    private import      std.exception, diff --git a/src/sdp/ao_rgx.d b/src/sdp/ao_rgx.d index ccaf1bd..2a10d53 100644 --- a/src/sdp/ao_rgx.d +++ b/src/sdp/ao_rgx.d @@ -1,6 +1,5 @@ -/+ -  regex -  ao_rgx.d +/++ +  regex: regular expressions used in sisu document parser  +/  template RgxInit() {    struct Rgx { diff --git a/src/sdp/compile_time_info.d b/src/sdp/compile_time_info.d index 783ac62..2b0151d 100644 --- a/src/sdp/compile_time_info.d +++ b/src/sdp/compile_time_info.d @@ -1,6 +1,5 @@ -/+ +/++    compile_time_info -  compile_time_info.d  +/  template CompileTimeInfo() {    version(Windows) { diff --git a/src/sdp/output_hub.d b/src/sdp/output_hub.d index 0206bf5..e7c0c9e 100644 --- a/src/sdp/output_hub.d +++ b/src/sdp/output_hub.d @@ -1,6 +1,6 @@ -/+ -  output_hub.d -  output_html.d +/++ +  output hub<BR> +  check & generate output types requested  +/  template SiSUoutputHub() {    struct SDPoutput { diff --git a/src/undead/doformat.d b/src/undead/doformat.d deleted file mode 100644 index 4fc0daf..0000000 --- a/src/undead/doformat.d +++ /dev/null @@ -1,1620 +0,0 @@ -// Written in the D programming language. - -/** -   Copyright: Copyright Digital Mars 2000-2013. - -   License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). - -   Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, -   Andrei Alexandrescu), and Kenji Hara - -   Source: $(PHOBOSSRC std/_format.d) - */ -module undead.doformat; - -//debug=format;                // uncomment to turn on debugging printf's - -import core.vararg; -import std.exception; -import std.meta; -import std.range.primitives; -import std.traits; -import std.format; - -version(CRuntime_DigitalMars) -{ -    version = DigitalMarsC; -} - -version (DigitalMarsC) -{ -    // This is DMC's internal floating point formatting function -    extern (C) -    { -        extern shared char* function(int c, int flags, int precision, -                in real* pdval, -                char* buf, size_t* psl, int width) __pfloatfmt; -    } -} - -/********************************************************************** - * Signals a mismatch between a format and its corresponding argument. - */ -class FormatException : Exception -{ -    @safe pure nothrow -    this() -    { -        super("format error"); -    } - -    @safe pure nothrow -    this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) -    { -        super(msg, fn, ln, next); -    } -} - - -// Legacy implementation - -enum Mangle : char -{ -    Tvoid     = 'v', -    Tbool     = 'b', -    Tbyte     = 'g', -    Tubyte    = 'h', -    Tshort    = 's', -    Tushort   = 't', -    Tint      = 'i', -    Tuint     = 'k', -    Tlong     = 'l', -    Tulong    = 'm', -    Tfloat    = 'f', -    Tdouble   = 'd', -    Treal     = 'e', - -    Tifloat   = 'o', -    Tidouble  = 'p', -    Tireal    = 'j', -    Tcfloat   = 'q', -    Tcdouble  = 'r', -    Tcreal    = 'c', - -    Tchar     = 'a', -    Twchar    = 'u', -    Tdchar    = 'w', - -    Tarray    = 'A', -    Tsarray   = 'G', -    Taarray   = 'H', -    Tpointer  = 'P', -    Tfunction = 'F', -    Tident    = 'I', -    Tclass    = 'C', -    Tstruct   = 'S', -    Tenum     = 'E', -    Ttypedef  = 'T', -    Tdelegate = 'D', - -    Tconst    = 'x', -    Timmutable = 'y', -} - -// return the TypeInfo for a primitive type and null otherwise.  This -// is required since for arrays of ints we only have the mangled char -// to work from. If arrays always subclassed TypeInfo_Array this -// routine could go away. -private TypeInfo primitiveTypeInfo(Mangle m) -{ -    // BUG: should fix this in static this() to avoid double checked locking bug -    __gshared TypeInfo[Mangle] dic; -    if (!dic.length) -    { -        dic = [ -            Mangle.Tvoid : typeid(void), -            Mangle.Tbool : typeid(bool), -            Mangle.Tbyte : typeid(byte), -            Mangle.Tubyte : typeid(ubyte), -            Mangle.Tshort : typeid(short), -            Mangle.Tushort : typeid(ushort), -            Mangle.Tint : typeid(int), -            Mangle.Tuint : typeid(uint), -            Mangle.Tlong : typeid(long), -            Mangle.Tulong : typeid(ulong), -            Mangle.Tfloat : typeid(float), -            Mangle.Tdouble : typeid(double), -            Mangle.Treal : typeid(real), -            Mangle.Tifloat : typeid(ifloat), -            Mangle.Tidouble : typeid(idouble), -            Mangle.Tireal : typeid(ireal), -            Mangle.Tcfloat : typeid(cfloat), -            Mangle.Tcdouble : typeid(cdouble), -            Mangle.Tcreal : typeid(creal), -            Mangle.Tchar : typeid(char), -            Mangle.Twchar : typeid(wchar), -            Mangle.Tdchar : typeid(dchar) -            ]; -    } -    auto p = m in dic; -    return p ? *p : null; -} - -// This stuff has been removed from the docs and is planned for deprecation. -/* - * Interprets variadic argument list pointed to by argptr whose types - * are given by arguments[], formats them according to embedded format - * strings in the variadic argument list, and sends the resulting - * characters to putc. - * - * The variadic arguments are consumed in order.  Each is formatted - * into a sequence of chars, using the default format specification - * for its type, and the characters are sequentially passed to putc. - * If a $(D char[]), $(D wchar[]), or $(D dchar[]) argument is - * encountered, it is interpreted as a format string. As many - * arguments as specified in the format string are consumed and - * formatted according to the format specifications in that string and - * passed to putc. If there are too few remaining arguments, a - * $(D FormatException) is thrown. If there are more remaining arguments than - * needed by the format specification, the default processing of - * arguments resumes until they are all consumed. - * - * Params: - *        putc =        Output is sent do this delegate, character by character. - *        arguments = Array of $(D TypeInfo)s, one for each argument to be formatted. - *        argptr = Points to variadic argument list. - * - * Throws: - *        Mismatched arguments and formats result in a $(D FormatException) being thrown. - * - * Format_String: - *        <a name="format-string">$(I Format strings)</a> - *        consist of characters interspersed with - *        $(I format specifications). Characters are simply copied - *        to the output (such as putc) after any necessary conversion - *        to the corresponding UTF-8 sequence. - * - *        A $(I format specification) starts with a '%' character, - *        and has the following grammar: - -$(CONSOLE -$(I FormatSpecification): -    $(B '%%') -    $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar) - -$(I Flags): -    $(I empty) -    $(B '-') $(I Flags) -    $(B '+') $(I Flags) -    $(B '#') $(I Flags) -    $(B '0') $(I Flags) -    $(B ' ') $(I Flags) - -$(I Width): -    $(I empty) -    $(I Integer) -    $(B '*') - -$(I Precision): -    $(I empty) -    $(B '.') -    $(B '.') $(I Integer) -    $(B '.*') - -$(I Integer): -    $(I Digit) -    $(I Digit) $(I Integer) - -$(I Digit): -    $(B '0') -    $(B '1') -    $(B '2') -    $(B '3') -    $(B '4') -    $(B '5') -    $(B '6') -    $(B '7') -    $(B '8') -    $(B '9') - -$(I FormatChar): -    $(B 's') -    $(B 'b') -    $(B 'd') -    $(B 'o') -    $(B 'x') -    $(B 'X') -    $(B 'e') -    $(B 'E') -    $(B 'f') -    $(B 'F') -    $(B 'g') -    $(B 'G') -    $(B 'a') -    $(B 'A') -) -    $(DL -    $(DT $(I Flags)) -    $(DL -        $(DT $(B '-')) -        $(DD -        Left justify the result in the field. -        It overrides any $(B 0) flag.) - -        $(DT $(B '+')) -        $(DD Prefix positive numbers in a signed conversion with a $(B +). -        It overrides any $(I space) flag.) - -        $(DT $(B '#')) -        $(DD Use alternative formatting: -        $(DL -            $(DT For $(B 'o'):) -            $(DD Add to precision as necessary so that the first digit -            of the octal formatting is a '0', even if both the argument -            and the $(I Precision) are zero.) -            $(DT For $(B 'x') ($(B 'X')):) -            $(DD If non-zero, prefix result with $(B 0x) ($(B 0X)).) -            $(DT For floating point formatting:) -            $(DD Always insert the decimal point.) -            $(DT For $(B 'g') ($(B 'G')):) -            $(DD Do not elide trailing zeros.) -        )) - -        $(DT $(B '0')) -        $(DD For integer and floating point formatting when not nan or -        infinity, use leading zeros -        to pad rather than spaces. -        Ignore if there's a $(I Precision).) - -        $(DT $(B ' ')) -        $(DD Prefix positive numbers in a signed conversion with a space.) -    ) - -    $(DT $(I Width)) -    $(DD -    Specifies the minimum field width. -    If the width is a $(B *), the next argument, which must be -    of type $(B int), is taken as the width. -    If the width is negative, it is as if the $(B -) was given -    as a $(I Flags) character.) - -    $(DT $(I Precision)) -    $(DD Gives the precision for numeric conversions. -    If the precision is a $(B *), the next argument, which must be -    of type $(B int), is taken as the precision. If it is negative, -    it is as if there was no $(I Precision).) - -    $(DT $(I FormatChar)) -    $(DD -    $(DL -        $(DT $(B 's')) -        $(DD The corresponding argument is formatted in a manner consistent -        with its type: -        $(DL -            $(DT $(B bool)) -            $(DD The result is <tt>'true'</tt> or <tt>'false'</tt>.) -            $(DT integral types) -            $(DD The $(B %d) format is used.) -            $(DT floating point types) -            $(DD The $(B %g) format is used.) -            $(DT string types) -            $(DD The result is the string converted to UTF-8.) -            A $(I Precision) specifies the maximum number of characters -            to use in the result. -            $(DT classes derived from $(B Object)) -            $(DD The result is the string returned from the class instance's -            $(B .toString()) method. -            A $(I Precision) specifies the maximum number of characters -            to use in the result.) -            $(DT non-string static and dynamic arrays) -            $(DD The result is [s<sub>0</sub>, s<sub>1</sub>, ...] -            where s<sub>k</sub> is the kth element -            formatted with the default format.) -        )) - -        $(DT $(B 'b','d','o','x','X')) -        $(DD The corresponding argument must be an integral type -        and is formatted as an integer. If the argument is a signed type -        and the $(I FormatChar) is $(B d) it is converted to -        a signed string of characters, otherwise it is treated as -        unsigned. An argument of type $(B bool) is formatted as '1' -        or '0'. The base used is binary for $(B b), octal for $(B o), -        decimal -        for $(B d), and hexadecimal for $(B x) or $(B X). -        $(B x) formats using lower case letters, $(B X) uppercase. -        If there are fewer resulting digits than the $(I Precision), -        leading zeros are used as necessary. -        If the $(I Precision) is 0 and the number is 0, no digits -        result.) - -        $(DT $(B 'e','E')) -        $(DD A floating point number is formatted as one digit before -        the decimal point, $(I Precision) digits after, the $(I FormatChar), -        ±, followed by at least a two digit exponent: $(I d.dddddd)e$(I ±dd). -        If there is no $(I Precision), six -        digits are generated after the decimal point. -        If the $(I Precision) is 0, no decimal point is generated.) - -        $(DT $(B 'f','F')) -        $(DD A floating point number is formatted in decimal notation. -        The $(I Precision) specifies the number of digits generated -        after the decimal point. It defaults to six. At least one digit -        is generated before the decimal point. If the $(I Precision) -        is zero, no decimal point is generated.) - -        $(DT $(B 'g','G')) -        $(DD A floating point number is formatted in either $(B e) or -        $(B f) format for $(B g); $(B E) or $(B F) format for -        $(B G). -        The $(B f) format is used if the exponent for an $(B e) format -        is greater than -5 and less than the $(I Precision). -        The $(I Precision) specifies the number of significant -        digits, and defaults to six. -        Trailing zeros are elided after the decimal point, if the fractional -        part is zero then no decimal point is generated.) - -        $(DT $(B 'a','A')) -        $(DD A floating point number is formatted in hexadecimal -        exponential notation 0x$(I h.hhhhhh)p$(I ±d). -        There is one hexadecimal digit before the decimal point, and as -        many after as specified by the $(I Precision). -        If the $(I Precision) is zero, no decimal point is generated. -        If there is no $(I Precision), as many hexadecimal digits as -        necessary to exactly represent the mantissa are generated. -        The exponent is written in as few digits as possible, -        but at least one, is in decimal, and represents a power of 2 as in -        $(I h.hhhhhh)*2<sup>$(I ±d)</sup>. -        The exponent for zero is zero. -        The hexadecimal digits, x and p are in upper case if the -        $(I FormatChar) is upper case.) -    ) - -    Floating point NaN's are formatted as $(B nan) if the -    $(I FormatChar) is lower case, or $(B NAN) if upper. -    Floating point infinities are formatted as $(B inf) or -    $(B infinity) if the -    $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper. -    )) - -Example: - -------------------------- -import core.stdc.stdio; -import std.format; - -void myPrint(...) -{ -    void putc(dchar c) -    { -        fputc(c, stdout); -    } - -    std.format.doFormat(&putc, _arguments, _argptr); -} - -void main() -{ -    int x = 27; - -    // prints 'The answer is 27:6' -    myPrint("The answer is %s:", x, 6); -} ------------------------- - */ -void doFormat()(scope void delegate(dchar) putc, TypeInfo[] arguments, va_list ap) -{ -    import std.utf : toUCSindex, isValidDchar, UTFException, toUTF8; -    import core.stdc.string : strlen; -    import core.stdc.stdlib : alloca, malloc, realloc, free; -    import core.stdc.stdio : snprintf; - -    size_t bufLength = 1024; -    void* argBuffer = malloc(bufLength); -    scope(exit) free(argBuffer); - -    size_t bufUsed = 0; -    foreach (ti; arguments) -    { -        // Ensure the required alignment -        bufUsed += ti.talign - 1; -        bufUsed -= (cast(size_t)argBuffer + bufUsed) & (ti.talign - 1); -        auto pos = bufUsed; -        // Align to next word boundary -        bufUsed += ti.tsize + size_t.sizeof - 1; -        bufUsed -= (cast(size_t)argBuffer + bufUsed) & (size_t.sizeof - 1); -        // Resize buffer if necessary -        while (bufUsed > bufLength) -        { -            bufLength *= 2; -            argBuffer = realloc(argBuffer, bufLength); -        } -        // Copy argument into buffer -        va_arg(ap, ti, argBuffer + pos); -    } - -    auto argptr = argBuffer; -    void* skipArg(TypeInfo ti) -    { -        // Ensure the required alignment -        argptr += ti.talign - 1; -        argptr -= cast(size_t)argptr & (ti.talign - 1); -        auto p = argptr; -        // Align to next word boundary -        argptr += ti.tsize + size_t.sizeof - 1; -        argptr -= cast(size_t)argptr & (size_t.sizeof - 1); -        return p; -    } -    auto getArg(T)() -    { -        return *cast(T*)skipArg(typeid(T)); -    } - -    TypeInfo ti; -    Mangle m; -    uint flags; -    int field_width; -    int precision; - -    enum : uint -    { -        FLdash = 1, -        FLplus = 2, -        FLspace = 4, -        FLhash = 8, -        FLlngdbl = 0x20, -        FL0pad = 0x40, -        FLprecision = 0x80, -    } - -    static TypeInfo skipCI(TypeInfo valti) -    { -        for (;;) -        { -            if (typeid(valti).name.length == 18 && -                    typeid(valti).name[9..18] == "Invariant") -                valti = (cast(TypeInfo_Invariant)valti).base; -            else if (typeid(valti).name.length == 14 && -                    typeid(valti).name[9..14] == "Const") -                valti = (cast(TypeInfo_Const)valti).base; -            else -                break; -        } - -        return valti; -    } - -    void formatArg(char fc) -    { -        bool vbit; -        ulong vnumber; -        char vchar; -        dchar vdchar; -        Object vobject; -        real vreal; -        creal vcreal; -        Mangle m2; -        int signed = 0; -        uint base = 10; -        int uc; -        char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary -        const(char)* prefix = ""; -        string s; - -        void putstr(const char[] s) -        { -            //printf("putstr: s = %.*s, flags = x%x\n", s.length, s.ptr, flags); -            ptrdiff_t padding = field_width - -                (strlen(prefix) + toUCSindex(s, s.length)); -            ptrdiff_t prepad = 0; -            ptrdiff_t postpad = 0; -            if (padding > 0) -            { -                if (flags & FLdash) -                    postpad = padding; -                else -                    prepad = padding; -            } - -            if (flags & FL0pad) -            { -                while (*prefix) -                    putc(*prefix++); -                while (prepad--) -                    putc('0'); -            } -            else -            { -                while (prepad--) -                    putc(' '); -                while (*prefix) -                    putc(*prefix++); -            } - -            foreach (dchar c; s) -                putc(c); - -            while (postpad--) -                putc(' '); -        } - -        void putreal(real v) -        { -            //printf("putreal %Lg\n", vreal); - -            switch (fc) -            { -                case 's': -                    fc = 'g'; -                    break; - -                case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A': -                    break; - -                default: -                    //printf("fc = '%c'\n", fc); -                Lerror: -                    throw new FormatException("incompatible format character for floating point type"); -            } -            version (DigitalMarsC) -            { -                uint sl; -                char[] fbuf = tmpbuf; -                if (!(flags & FLprecision)) -                    precision = 6; -                while (1) -                { -                    sl = fbuf.length; -                    prefix = (*__pfloatfmt)(fc, flags | FLlngdbl, -                            precision, &v, cast(char*)fbuf, &sl, field_width); -                    if (sl != -1) -                        break; -                    sl = fbuf.length * 2; -                    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; -                } -                putstr(fbuf[0 .. sl]); -            } -            else -            { -                ptrdiff_t sl; -                char[] fbuf = tmpbuf; -                char[12] format; -                format[0] = '%'; -                int i = 1; -                if (flags & FLdash) -                    format[i++] = '-'; -                if (flags & FLplus) -                    format[i++] = '+'; -                if (flags & FLspace) -                    format[i++] = ' '; -                if (flags & FLhash) -                    format[i++] = '#'; -                if (flags & FL0pad) -                    format[i++] = '0'; -                format[i + 0] = '*'; -                format[i + 1] = '.'; -                format[i + 2] = '*'; -                format[i + 3] = 'L'; -                format[i + 4] = fc; -                format[i + 5] = 0; -                if (!(flags & FLprecision)) -                    precision = -1; -                while (1) -                { -                    sl = fbuf.length; -                    int n; -                    version (CRuntime_Microsoft) -                    { -                        import std.math : isNaN, isInfinity; -                        if (isNaN(v)) // snprintf writes 1.#QNAN -                            n = snprintf(fbuf.ptr, sl, "nan"); -                        else if (isInfinity(v)) // snprintf writes 1.#INF -                            n = snprintf(fbuf.ptr, sl, v < 0 ? "-inf" : "inf"); -                        else -                            n = snprintf(fbuf.ptr, sl, format.ptr, field_width, -                                         precision, cast(double)v); -                    } -                    else -                        n = snprintf(fbuf.ptr, sl, format.ptr, field_width, -                                precision, v); -                    //printf("format = '%s', n = %d\n", cast(char*)format, n); -                    if (n >= 0 && n < sl) -                    {        sl = n; -                        break; -                    } -                    if (n < 0) -                        sl = sl * 2; -                    else -                        sl = n + 1; -                    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; -                } -                putstr(fbuf[0 .. sl]); -            } -            return; -        } - -        static Mangle getMan(TypeInfo ti) -        { -          auto m = cast(Mangle)typeid(ti).name[9]; -          if (typeid(ti).name.length == 20 && -              typeid(ti).name[9..20] == "StaticArray") -                m = cast(Mangle)'G'; -          return m; -        } - -        /* p = pointer to the first element in the array -         * len = number of elements in the array -         * valti = type of the elements -         */ -        void putArray(void* p, size_t len, TypeInfo valti) -        { -          //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize); -          putc('['); -          valti = skipCI(valti); -          size_t tsize = valti.tsize; -          auto argptrSave = argptr; -          auto tiSave = ti; -          auto mSave = m; -          ti = valti; -          //printf("\n%.*s\n", typeid(valti).name.length, typeid(valti).name.ptr); -          m = getMan(valti); -          while (len--) -          { -            //doFormat(putc, (&valti)[0 .. 1], p); -            argptr = p; -            formatArg('s'); -            p += tsize; -            if (len > 0) putc(','); -          } -          m = mSave; -          ti = tiSave; -          argptr = argptrSave; -          putc(']'); -        } - -        void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti) -        { -            putc('['); -            bool comma=false; -            auto argptrSave = argptr; -            auto tiSave = ti; -            auto mSave = m; -            valti = skipCI(valti); -            keyti = skipCI(keyti); -            foreach (ref fakevalue; vaa) -            { -                if (comma) putc(','); -                comma = true; -                void *pkey = &fakevalue; -                version (D_LP64) -                    pkey -= (long.sizeof + 15) & ~(15); -                else -                    pkey -= (long.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1); - -                // the key comes before the value -                auto keysize = keyti.tsize; -                version (D_LP64) -                    auto keysizet = (keysize + 15) & ~(15); -                else -                    auto keysizet = (keysize + size_t.sizeof - 1) & ~(size_t.sizeof - 1); - -                void* pvalue = pkey + keysizet; - -                //doFormat(putc, (&keyti)[0..1], pkey); -                m = getMan(keyti); -                argptr = pkey; - -                ti = keyti; -                formatArg('s'); - -                putc(':'); -                //doFormat(putc, (&valti)[0..1], pvalue); -                m = getMan(valti); -                argptr = pvalue; - -                ti = valti; -                formatArg('s'); -            } -            m = mSave; -            ti = tiSave; -            argptr = argptrSave; -            putc(']'); -        } - -        //printf("formatArg(fc = '%c', m = '%c')\n", fc, m); -        int mi; -        switch (m) -        { -            case Mangle.Tbool: -                vbit = getArg!(bool)(); -                if (fc != 's') -                {   vnumber = vbit; -                    goto Lnumber; -                } -                putstr(vbit ? "true" : "false"); -                return; - -            case Mangle.Tchar: -                vchar = getArg!(char)(); -                if (fc != 's') -                {   vnumber = vchar; -                    goto Lnumber; -                } -            L2: -                putstr((&vchar)[0 .. 1]); -                return; - -            case Mangle.Twchar: -                vdchar = getArg!(wchar)(); -                goto L1; - -            case Mangle.Tdchar: -                vdchar = getArg!(dchar)(); -            L1: -                if (fc != 's') -                {   vnumber = vdchar; -                    goto Lnumber; -                } -                if (vdchar <= 0x7F) -                {   vchar = cast(char)vdchar; -                    goto L2; -                } -                else -                {   if (!isValidDchar(vdchar)) -                        throw new UTFException("invalid dchar in format"); -                    char[4] vbuf; -                    putstr(toUTF8(vbuf, vdchar)); -                } -                return; - -            case Mangle.Tbyte: -                signed = 1; -                vnumber = getArg!(byte)(); -                goto Lnumber; - -            case Mangle.Tubyte: -                vnumber = getArg!(ubyte)(); -                goto Lnumber; - -            case Mangle.Tshort: -                signed = 1; -                vnumber = getArg!(short)(); -                goto Lnumber; - -            case Mangle.Tushort: -                vnumber = getArg!(ushort)(); -                goto Lnumber; - -            case Mangle.Tint: -                signed = 1; -                vnumber = getArg!(int)(); -                goto Lnumber; - -            case Mangle.Tuint: -            Luint: -                vnumber = getArg!(uint)(); -                goto Lnumber; - -            case Mangle.Tlong: -                signed = 1; -                vnumber = cast(ulong)getArg!(long)(); -                goto Lnumber; - -            case Mangle.Tulong: -            Lulong: -                vnumber = getArg!(ulong)(); -                goto Lnumber; - -            case Mangle.Tclass: -                vobject = getArg!(Object)(); -                if (vobject is null) -                    s = "null"; -                else -                    s = vobject.toString(); -                goto Lputstr; - -            case Mangle.Tpointer: -                vnumber = cast(ulong)getArg!(void*)(); -                if (fc != 'x')  uc = 1; -                flags |= FL0pad; -                if (!(flags & FLprecision)) -                {   flags |= FLprecision; -                    precision = (void*).sizeof; -                } -                base = 16; -                goto Lnumber; - -            case Mangle.Tfloat: -            case Mangle.Tifloat: -                if (fc == 'x' || fc == 'X') -                    goto Luint; -                vreal = getArg!(float)(); -                goto Lreal; - -            case Mangle.Tdouble: -            case Mangle.Tidouble: -                if (fc == 'x' || fc == 'X') -                    goto Lulong; -                vreal = getArg!(double)(); -                goto Lreal; - -            case Mangle.Treal: -            case Mangle.Tireal: -                vreal = getArg!(real)(); -                goto Lreal; - -            case Mangle.Tcfloat: -                vcreal = getArg!(cfloat)(); -                goto Lcomplex; - -            case Mangle.Tcdouble: -                vcreal = getArg!(cdouble)(); -                goto Lcomplex; - -            case Mangle.Tcreal: -                vcreal = getArg!(creal)(); -                goto Lcomplex; - -            case Mangle.Tsarray: -                putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next); -                return; - -            case Mangle.Tarray: -                mi = 10; -                if (typeid(ti).name.length == 14 && -                    typeid(ti).name[9..14] == "Array") -                { // array of non-primitive types -                  TypeInfo tn = (cast(TypeInfo_Array)ti).next; -                  tn = skipCI(tn); -                  switch (cast(Mangle)typeid(tn).name[9]) -                  { -                    case Mangle.Tchar:  goto LarrayChar; -                    case Mangle.Twchar: goto LarrayWchar; -                    case Mangle.Tdchar: goto LarrayDchar; -                    default: -                        break; -                  } -                  void[] va = getArg!(void[])(); -                  putArray(va.ptr, va.length, tn); -                  return; -                } -                if (typeid(ti).name.length == 25 && -                    typeid(ti).name[9..25] == "AssociativeArray") -                { // associative array -                  ubyte[long] vaa = getArg!(ubyte[long])(); -                  putAArray(vaa, -                        (cast(TypeInfo_AssociativeArray)ti).next, -                        (cast(TypeInfo_AssociativeArray)ti).key); -                  return; -                } - -                while (1) -                { -                    m2 = cast(Mangle)typeid(ti).name[mi]; -                    switch (m2) -                    { -                        case Mangle.Tchar: -                        LarrayChar: -                            s = getArg!(string)(); -                            goto Lputstr; - -                        case Mangle.Twchar: -                        LarrayWchar: -                            wchar[] sw = getArg!(wchar[])(); -                            s = toUTF8(sw); -                            goto Lputstr; - -                        case Mangle.Tdchar: -                        LarrayDchar: -                            s = toUTF8(getArg!(dstring)()); -                        Lputstr: -                            if (fc != 's') -                                throw new FormatException("string"); -                            if (flags & FLprecision && precision < s.length) -                                s = s[0 .. precision]; -                            putstr(s); -                            break; - -                        case Mangle.Tconst: -                        case Mangle.Timmutable: -                            mi++; -                            continue; - -                        default: -                            TypeInfo ti2 = primitiveTypeInfo(m2); -                            if (!ti2) -                              goto Lerror; -                            void[] va = getArg!(void[])(); -                            putArray(va.ptr, va.length, ti2); -                    } -                    return; -                } -                assert(0); - -            case Mangle.Ttypedef: -                ti = (cast(TypeInfo_Typedef)ti).base; -                m = cast(Mangle)typeid(ti).name[9]; -                formatArg(fc); -                return; - -            case Mangle.Tenum: -                ti = (cast(TypeInfo_Enum)ti).base; -                m = cast(Mangle)typeid(ti).name[9]; -                formatArg(fc); -                return; - -            case Mangle.Tstruct: -            {        TypeInfo_Struct tis = cast(TypeInfo_Struct)ti; -                if (tis.xtoString is null) -                    throw new FormatException("Can't convert " ~ tis.toString() -                            ~ " to string: \"string toString()\" not defined"); -                s = tis.xtoString(skipArg(tis)); -                goto Lputstr; -            } - -            default: -                goto Lerror; -        } - -    Lnumber: -        switch (fc) -        { -            case 's': -            case 'd': -                if (signed) -                {   if (cast(long)vnumber < 0) -                    {        prefix = "-"; -                        vnumber = -vnumber; -                    } -                    else if (flags & FLplus) -                        prefix = "+"; -                    else if (flags & FLspace) -                        prefix = " "; -                } -                break; - -            case 'b': -                signed = 0; -                base = 2; -                break; - -            case 'o': -                signed = 0; -                base = 8; -                break; - -            case 'X': -                uc = 1; -                if (flags & FLhash && vnumber) -                    prefix = "0X"; -                signed = 0; -                base = 16; -                break; - -            case 'x': -                if (flags & FLhash && vnumber) -                    prefix = "0x"; -                signed = 0; -                base = 16; -                break; - -            default: -                goto Lerror; -        } - -        if (!signed) -        { -            switch (m) -            { -                case Mangle.Tbyte: -                    vnumber &= 0xFF; -                    break; - -                case Mangle.Tshort: -                    vnumber &= 0xFFFF; -                    break; - -                case Mangle.Tint: -                    vnumber &= 0xFFFFFFFF; -                    break; - -                default: -                    break; -            } -        } - -        if (flags & FLprecision && fc != 'p') -            flags &= ~FL0pad; - -        if (vnumber < base) -        { -            if (vnumber == 0 && precision == 0 && flags & FLprecision && -                !(fc == 'o' && flags & FLhash)) -            { -                putstr(null); -                return; -            } -            if (precision == 0 || !(flags & FLprecision)) -            {        vchar = cast(char)('0' + vnumber); -                if (vnumber < 10) -                    vchar = cast(char)('0' + vnumber); -                else -                    vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber); -                goto L2; -            } -        } - -        { -            ptrdiff_t n = tmpbuf.length; -            char c; -            int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1)); - -            while (vnumber) -            { -                c = cast(char)((vnumber % base) + '0'); -                if (c > '9') -                    c += hexoffset; -                vnumber /= base; -                tmpbuf[--n] = c; -            } -            if (tmpbuf.length - n < precision && precision < tmpbuf.length) -            { -                ptrdiff_t m = tmpbuf.length - precision; -                tmpbuf[m .. n] = '0'; -                n = m; -            } -            else if (flags & FLhash && fc == 'o') -                prefix = "0"; -            putstr(tmpbuf[n .. tmpbuf.length]); -            return; -        } - -    Lreal: -        putreal(vreal); -        return; - -    Lcomplex: -        putreal(vcreal.re); -        if (vcreal.im >= 0) -        { -            putc('+'); -        } -        putreal(vcreal.im); -        putc('i'); -        return; - -    Lerror: -        throw new FormatException("formatArg"); -    } - -    for (int j = 0; j < arguments.length; ) -    { -        ti = arguments[j++]; -        //printf("arg[%d]: '%.*s' %d\n", j, typeid(ti).name.length, typeid(ti).name.ptr, typeid(ti).name.length); -        //ti.print(); - -        flags = 0; -        precision = 0; -        field_width = 0; - -        ti = skipCI(ti); -        int mi = 9; -        do -        { -            if (typeid(ti).name.length <= mi) -                goto Lerror; -            m = cast(Mangle)typeid(ti).name[mi++]; -        } while (m == Mangle.Tconst || m == Mangle.Timmutable); - -        if (m == Mangle.Tarray) -        { -            if (typeid(ti).name.length == 14 && -                    typeid(ti).name[9..14] == "Array") -            { -                TypeInfo tn = (cast(TypeInfo_Array)ti).next; -                tn = skipCI(tn); -                switch (cast(Mangle)typeid(tn).name[9]) -                { -                case Mangle.Tchar: -                case Mangle.Twchar: -                case Mangle.Tdchar: -                    ti = tn; -                    mi = 9; -                    break; -                default: -                    break; -                } -            } -          L1: -            Mangle m2 = cast(Mangle)typeid(ti).name[mi]; -            string  fmt;                        // format string -            wstring wfmt; -            dstring dfmt; - -            /* For performance reasons, this code takes advantage of the -             * fact that most format strings will be ASCII, and that the -             * format specifiers are always ASCII. This means we only need -             * to deal with UTF in a couple of isolated spots. -             */ - -            switch (m2) -            { -            case Mangle.Tchar: -                fmt = getArg!(string)(); -                break; - -            case Mangle.Twchar: -                wfmt = getArg!(wstring)(); -                fmt = toUTF8(wfmt); -                break; - -            case Mangle.Tdchar: -                dfmt = getArg!(dstring)(); -                fmt = toUTF8(dfmt); -                break; - -            case Mangle.Tconst: -            case Mangle.Timmutable: -                mi++; -                goto L1; - -            default: -                formatArg('s'); -                continue; -            } - -            for (size_t i = 0; i < fmt.length; ) -            {        dchar c = fmt[i++]; - -                dchar getFmtChar() -                {   // Valid format specifier characters will never be UTF -                    if (i == fmt.length) -                        throw new FormatException("invalid specifier"); -                    return fmt[i++]; -                } - -                int getFmtInt() -                {   int n; - -                    while (1) -                    { -                        n = n * 10 + (c - '0'); -                        if (n < 0)        // overflow -                            throw new FormatException("int overflow"); -                        c = getFmtChar(); -                        if (c < '0' || c > '9') -                            break; -                    } -                    return n; -                } - -                int getFmtStar() -                {   Mangle m; -                    TypeInfo ti; - -                    if (j == arguments.length) -                        throw new FormatException("too few arguments"); -                    ti = arguments[j++]; -                    m = cast(Mangle)typeid(ti).name[9]; -                    if (m != Mangle.Tint) -                        throw new FormatException("int argument expected"); -                    return getArg!(int)(); -                } - -                if (c != '%') -                { -                    if (c > 0x7F)        // if UTF sequence -                    { -                        i--;                // back up and decode UTF sequence -                        import std.utf : decode; -                        c = decode(fmt, i); -                    } -                  Lputc: -                    putc(c); -                    continue; -                } - -                // Get flags {-+ #} -                flags = 0; -                while (1) -                { -                    c = getFmtChar(); -                    switch (c) -                    { -                    case '-':        flags |= FLdash;        continue; -                    case '+':        flags |= FLplus;        continue; -                    case ' ':        flags |= FLspace;        continue; -                    case '#':        flags |= FLhash;        continue; -                    case '0':        flags |= FL0pad;        continue; - -                    case '%':        if (flags == 0) -                                          goto Lputc; -                                     break; - -                    default:         break; -                    } -                    break; -                } - -                // Get field width -                field_width = 0; -                if (c == '*') -                { -                    field_width = getFmtStar(); -                    if (field_width < 0) -                    {   flags |= FLdash; -                        field_width = -field_width; -                    } - -                    c = getFmtChar(); -                } -                else if (c >= '0' && c <= '9') -                    field_width = getFmtInt(); - -                if (flags & FLplus) -                    flags &= ~FLspace; -                if (flags & FLdash) -                    flags &= ~FL0pad; - -                // Get precision -                precision = 0; -                if (c == '.') -                {   flags |= FLprecision; -                    //flags &= ~FL0pad; - -                    c = getFmtChar(); -                    if (c == '*') -                    { -                        precision = getFmtStar(); -                        if (precision < 0) -                        {   precision = 0; -                            flags &= ~FLprecision; -                        } - -                        c = getFmtChar(); -                    } -                    else if (c >= '0' && c <= '9') -                        precision = getFmtInt(); -                } - -                if (j == arguments.length) -                    goto Lerror; -                ti = arguments[j++]; -                ti = skipCI(ti); -                mi = 9; -                do -                { -                    m = cast(Mangle)typeid(ti).name[mi++]; -                } while (m == Mangle.Tconst || m == Mangle.Timmutable); - -                if (c > 0x7F)                // if UTF sequence -                    goto Lerror;        // format specifiers can't be UTF -                formatArg(cast(char)c); -            } -        } -        else -        { -            formatArg('s'); -        } -    } -    return; - -  Lerror: -    throw new FormatException(); -} - - -private bool needToSwapEndianess(Char)(ref FormatSpec!Char f) -{ -    import std.system : endian, Endian; - -    return endian == Endian.littleEndian && f.flPlus -        || endian == Endian.bigEndian && f.flDash; -} - -/* ======================== Unit Tests ====================================== */ - -unittest -{ -    import std.conv : octal; - -    int i; -    string s; - -    debug(format) printf("std.format.format.unittest\n"); - -    s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo"); -    assert(s == "hello world! true 57 1000000000x foo"); - -    s = format("%s %A %s", 1.67, -1.28, float.nan); -    /* The host C library is used to format floats. -     * C99 doesn't specify what the hex digit before the decimal point -     * is for %A. -     */ -    //version (linux) -    //    assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan"); -    //else version (OSX) -    //    assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s); -    //else -    version (MinGW) -        assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s); -    else version (CRuntime_Microsoft) -        assert(s == "1.67 -0X1.47AE14P+0 nan" -            || s == "1.67 -0X1.47AE147AE147BP+0 nan", s); // MSVCRT 14+ (VS 2015) -    else -        assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s); - -    s = format("%x %X", 0x1234AF, 0xAFAFAFAF); -    assert(s == "1234af AFAFAFAF"); - -    s = format("%b %o", 0x1234AF, 0xAFAFAFAF); -    assert(s == "100100011010010101111 25753727657"); - -    s = format("%d %s", 0x1234AF, 0xAFAFAFAF); -    assert(s == "1193135 2947526575"); - -    //version(X86_64) -    //{ -    //    pragma(msg, "several format tests disabled on x86_64 due to bug 5625"); -    //} -    //else -    //{ -        s = format("%s", 1.2 + 3.4i); -        assert(s == "1.2+3.4i", s); - -        //s = format("%x %X", 1.32, 6.78f); -        //assert(s == "3ff51eb851eb851f 40D8F5C3"); - -    //} - -    s = format("%#06.*f",2,12.345); -    assert(s == "012.35"); - -    s = format("%#0*.*f",6,2,12.345); -    assert(s == "012.35"); - -    s = format("%7.4g:", 12.678); -    assert(s == "  12.68:"); - -    s = format("%7.4g:", 12.678L); -    assert(s == "  12.68:"); - -    s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1); -    assert(s == "-4.000000|-0010|0x001|  0x1"); - -    i = -10; -    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); -    assert(s == "-10|-10|-10|-10|-10.0000"); - -    i = -5; -    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); -    assert(s == "-5| -5|-05|-5|-5.0000"); - -    i = 0; -    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); -    assert(s == "0|  0|000|0|0.0000"); - -    i = 5; -    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); -    assert(s == "5|  5|005|5|5.0000"); - -    i = 10; -    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); -    assert(s == "10| 10|010|10|10.0000"); - -    s = format("%.0d", 0); -    assert(s == ""); - -    s = format("%.g", .34); -    assert(s == "0.3"); - -    s = format("%.0g", .34); -    assert(s == "0.3"); - -    s = format("%.2g", .34); -    assert(s == "0.34"); - -    s = format("%0.0008f", 1e-08); -    assert(s == "0.00000001"); - -    s = format("%0.0008f", 1e-05); -    assert(s == "0.00001000"); - -    s = "helloworld"; -    string r; -    r = format("%.2s", s[0..5]); -    assert(r == "he"); -    r = format("%.20s", s[0..5]); -    assert(r == "hello"); -    r = format("%8s", s[0..5]); -    assert(r == "   hello"); - -    byte[] arrbyte = new byte[4]; -    arrbyte[0] = 100; -    arrbyte[1] = -99; -    arrbyte[3] = 0; -    r = format("%s", arrbyte); -    assert(r == "[100, -99, 0, 0]"); - -    ubyte[] arrubyte = new ubyte[4]; -    arrubyte[0] = 100; -    arrubyte[1] = 200; -    arrubyte[3] = 0; -    r = format("%s", arrubyte); -    assert(r == "[100, 200, 0, 0]"); - -    short[] arrshort = new short[4]; -    arrshort[0] = 100; -    arrshort[1] = -999; -    arrshort[3] = 0; -    r = format("%s", arrshort); -    assert(r == "[100, -999, 0, 0]"); - -    ushort[] arrushort = new ushort[4]; -    arrushort[0] = 100; -    arrushort[1] = 20_000; -    arrushort[3] = 0; -    r = format("%s", arrushort); -    assert(r == "[100, 20000, 0, 0]"); - -    int[] arrint = new int[4]; -    arrint[0] = 100; -    arrint[1] = -999; -    arrint[3] = 0; -    r = format("%s", arrint); -    assert(r == "[100, -999, 0, 0]"); - -    long[] arrlong = new long[4]; -    arrlong[0] = 100; -    arrlong[1] = -999; -    arrlong[3] = 0; -    r = format("%s", arrlong); -    assert(r == "[100, -999, 0, 0]"); - -    ulong[] arrulong = new ulong[4]; -    arrulong[0] = 100; -    arrulong[1] = 999; -    arrulong[3] = 0; -    r = format("%s", arrulong); -    assert(r == "[100, 999, 0, 0]"); - -    string[] arr2 = new string[4]; -    arr2[0] = "hello"; -    arr2[1] = "world"; -    arr2[3] = "foo"; -    r = format("%s", arr2); -    assert(r == `["hello", "world", "", "foo"]`); - -    r = format("%.8d", 7); -    assert(r == "00000007"); -    r = format("%.8x", 10); -    assert(r == "0000000a"); - -    r = format("%-3d", 7); -    assert(r == "7  "); - -    r = format("%*d", -3, 7); -    assert(r == "7  "); - -    r = format("%.*d", -3, 7); -    assert(r == "7"); - -    r = format("abc"c); -    assert(r == "abc"); - -    //format() returns the same type as inputted. -    wstring wr; -    wr = format("def"w); -    assert(wr == "def"w); - -    dstring dr; -    dr = format("ghi"d); -    assert(dr == "ghi"d); - -    void* p = cast(void*)0xDEADBEEF; -    r = format("%s", p); -    assert(r == "DEADBEEF"); - -    r = format("%#x", 0xabcd); -    assert(r == "0xabcd"); -    r = format("%#X", 0xABCD); -    assert(r == "0XABCD"); - -    r = format("%#o", octal!12345); -    assert(r == "012345"); -    r = format("%o", 9); -    assert(r == "11"); -    r = format("%#o", 0);   // issue 15663 -    assert(r == "0"); - -    r = format("%+d", 123); -    assert(r == "+123"); -    r = format("%+d", -123); -    assert(r == "-123"); -    r = format("% d", 123); -    assert(r == " 123"); -    r = format("% d", -123); -    assert(r == "-123"); - -    r = format("%%"); -    assert(r == "%"); - -    r = format("%d", true); -    assert(r == "1"); -    r = format("%d", false); -    assert(r == "0"); - -    r = format("%d", 'a'); -    assert(r == "97"); -    wchar wc = 'a'; -    r = format("%d", wc); -    assert(r == "97"); -    dchar dc = 'a'; -    r = format("%d", dc); -    assert(r == "97"); - -    byte b = byte.max; -    r = format("%x", b); -    assert(r == "7f"); -    r = format("%x", ++b); -    assert(r == "80"); -    r = format("%x", ++b); -    assert(r == "81"); - -    short sh = short.max; -    r = format("%x", sh); -    assert(r == "7fff"); -    r = format("%x", ++sh); -    assert(r == "8000"); -    r = format("%x", ++sh); -    assert(r == "8001"); - -    i = int.max; -    r = format("%x", i); -    assert(r == "7fffffff"); -    r = format("%x", ++i); -    assert(r == "80000000"); -    r = format("%x", ++i); -    assert(r == "80000001"); - -    r = format("%x", 10); -    assert(r == "a"); -    r = format("%X", 10); -    assert(r == "A"); -    r = format("%x", 15); -    assert(r == "f"); -    r = format("%X", 15); -    assert(r == "F"); - -    Object c = null; -    r = format("%s", c); -    assert(r == "null"); - -    enum TestEnum -    { -        Value1, Value2 -    } -    r = format("%s", TestEnum.Value2); -    assert(r == "Value2"); - -    immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); -    r = format("%s", aa.values); -    assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`); -    r = format("%s", aa); -    assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`); - -    static const dchar[] ds = ['a','b']; -    for (int j = 0; j < ds.length; ++j) -    { -        r = format(" %d", ds[j]); -        if (j == 0) -            assert(r == " 97"); -        else -            assert(r == " 98"); -    } - -    r = format(">%14d<, %s", 15, [1,2,3]); -    assert(r == ">            15<, [1, 2, 3]"); - -    assert(format("%8s", "bar") == "     bar"); -    assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4"); -} diff --git a/src/undead/internal/file.d b/src/undead/internal/file.d deleted file mode 100644 index f756674..0000000 --- a/src/undead/internal/file.d +++ /dev/null @@ -1,25 +0,0 @@ -// Written in the D programming language - -module undead.internal.file; - -// Copied from std.file. undead doesn't have access to it, but some modules -// in undead used std.file.deleteme when they were in Phobos, so this gives -// them access to a version of it. -public @property string deleteme() @safe -{ -    import std.conv : to; -    import std.file : tempDir; -    import std.path : buildPath; -    import std.process : thisProcessID; -    static _deleteme = "deleteme.dmd.unittest.pid"; -    static _first = true; - -    if(_first) -    { -        _deleteme = buildPath(tempDir(), _deleteme) ~ to!string(thisProcessID); -        _first = false; -    } - -    return _deleteme; -} - diff --git a/src/undead/stream.d b/src/undead/stream.d deleted file mode 100644 index dc81b7f..0000000 --- a/src/undead/stream.d +++ /dev/null @@ -1,3076 +0,0 @@ -// Written in the D programming language - -/** - * $(RED Deprecated: This module is considered out-dated and not up to Phobos' - *       current standards.) - * - * Source:    $(PHOBOSSRC std/_stream.d) - * Macros: - *      WIKI = Phobos/StdStream - */ - -/* - * Copyright (c) 2001-2005 - * Pavel "EvilOne" Minayev - *  with buffering and endian support added by Ben Hinkle - *  with buffered readLine performance improvements by Dave Fladebo - *  with opApply inspired by (and mostly copied from) Regan Heath - *  with bug fixes and MemoryStream/SliceStream enhancements by Derick Eddington - * - * Permission to use, copy, modify, distribute and sell this software - * and its documentation for any purpose is hereby granted without fee, - * provided that the above copyright notice appear in all copies and - * that both that copyright notice and this permission notice appear - * in supporting documentation.  Author makes no representations about - * the suitability of this software for any purpose. It is provided - * "as is" without express or implied warranty. - */ -module undead.stream; - -import std.internal.cstring; - -/* Class structure: - *  InputStream       interface for reading - *  OutputStream      interface for writing - *  Stream            abstract base of stream implementations - *    File            an OS file stream - *    FilterStream    a base-class for wrappers around another stream - *      BufferedStream  a buffered stream wrapping another stream - *        BufferedFile  a buffered File - *      EndianStream    a wrapper stream for swapping byte order and BOMs - *      SliceStream     a portion of another stream - *    MemoryStream    a stream entirely stored in main memory - *    TArrayStream    a stream wrapping an array-like buffer - */ - -/// A base class for stream exceptions. -class StreamException: Exception { -  /// Construct a StreamException with given error message. -  this(string msg) { super(msg); } -} - -/// Thrown when unable to read data from Stream. -class ReadException: StreamException { -  /// Construct a ReadException with given error message. -  this(string msg) { super(msg); } -} - -/// Thrown when unable to write data to Stream. -class WriteException: StreamException { -  /// Construct a WriteException with given error message. -  this(string msg) { super(msg); } -} - -/// Thrown when unable to move Stream pointer. -class SeekException: StreamException { -  /// Construct a SeekException with given error message. -  this(string msg) { super(msg); } -} - -// seek whence... -enum SeekPos { -  Set, -  Current, -  End -} - -private { -  import std.conv; -  import std.algorithm; -  import std.ascii; -  //import std.format; -  import std.system;    // for Endian enumeration -  import std.utf; -  import core.bitop; // for bswap -  import core.vararg; -  import std.file; -  import undead.internal.file; -  import undead.doformat; -} - -/// InputStream is the interface for readable streams. - -interface InputStream { - -  /*** -   * Read exactly size bytes into the buffer. -   * -   * Throws a ReadException if it is not correct. -   */ -  void readExact(void* buffer, size_t size); - -  /*** -   * Read a block of data big enough to fill the given array buffer. -   * -   * Returns: the actual number of bytes read. Unfilled bytes are not modified. -   */ -  size_t read(ubyte[] buffer); - -  /*** -   * Read a basic type or counted string. -   * -   * Throw a ReadException if it could not be read. -   * Outside of byte, ubyte, and char, the format is -   * implementation-specific and should not be used except as opposite actions -   * to write. -   */ -  void read(out byte x); -  void read(out ubyte x);       /// ditto -  void read(out short x);       /// ditto -  void read(out ushort x);      /// ditto -  void read(out int x);         /// ditto -  void read(out uint x);        /// ditto -  void read(out long x);        /// ditto -  void read(out ulong x);       /// ditto -  void read(out float x);       /// ditto -  void read(out double x);      /// ditto -  void read(out real x);        /// ditto -  void read(out ifloat x);      /// ditto -  void read(out idouble x);     /// ditto -  void read(out ireal x);       /// ditto -  void read(out cfloat x);      /// ditto -  void read(out cdouble x);     /// ditto -  void read(out creal x);       /// ditto -  void read(out char x);        /// ditto -  void read(out wchar x);       /// ditto -  void read(out dchar x);       /// ditto - -  // reads a string, written earlier by write() -  void read(out char[] s);      /// ditto - -  // reads a Unicode string, written earlier by write() -  void read(out wchar[] s);     /// ditto - -  /*** -   * Read a line that is terminated with some combination of carriage return and -   * line feed or end-of-file. -   * -   * The terminators are not included. The wchar version -   * is identical. The optional buffer parameter is filled (reallocating -   * it if necessary) and a slice of the result is returned. -   */ -  char[] readLine(); -  char[] readLine(char[] result);       /// ditto -  wchar[] readLineW();                  /// ditto -  wchar[] readLineW(wchar[] result);    /// ditto - -  /*** -   * Overload foreach statements to read the stream line by line and call the -   * supplied delegate with each line or with each line with line number. -   * -   * The string passed in line may be reused between calls to the delegate. -   * Line numbering starts at 1. -   * Breaking out of the foreach will leave the stream -   * position at the beginning of the next line to be read. -   * For example, to echo a file line-by-line with line numbers run: -   * ------------------------------------ -   * Stream file = new BufferedFile("sample.txt"); -   * foreach(ulong n, char[] line; file) -   * { -   *     writefln("line %d: %s", n, line); -   * } -   * file.close(); -   * ------------------------------------ -   */ - -  // iterate through the stream line-by-line -  int opApply(scope int delegate(ref char[] line) dg); -  int opApply(scope int delegate(ref ulong n, ref char[] line) dg);  /// ditto -  int opApply(scope int delegate(ref wchar[] line) dg);            /// ditto -  int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg); /// ditto - -  /// Read a string of the given length, -  /// throwing ReadException if there was a problem. -  char[] readString(size_t length); - -  /*** -   * Read a string of the given length, throwing ReadException if there was a -   * problem. -   * -   * The file format is implementation-specific and should not be used -   * except as opposite actions to <b>write</b>. -   */ - -  wchar[] readStringW(size_t length); - - -  /*** -   * Read and return the next character in the stream. -   * -   * This is the only method that will handle ungetc properly. -   * getcw's format is implementation-specific. -   * If EOF is reached then getc returns char.init and getcw returns wchar.init. -   */ - -  char getc(); -  wchar getcw(); /// ditto - -  /*** -   * Push a character back onto the stream. -   * -   * They will be returned in first-in last-out order from getc/getcw. -   * Only has effect on further calls to getc() and getcw(). -   */ -  char ungetc(char c); -  wchar ungetcw(wchar c); /// ditto - -  /*** -   * Scan a string from the input using a similar form to C's scanf -   * and <a href="std_format.html">std.format</a>. -   * -   * An argument of type string is interpreted as a format string. -   * All other arguments must be pointer types. -   * If a format string is not present a default will be supplied computed from -   * the base type of the pointer type. An argument of type string* is filled -   * (possibly with appending characters) and a slice of the result is assigned -   * back into the argument. For example the following readf statements -   * are equivalent: -   * -------------------------- -   * int x; -   * double y; -   * string s; -   * file.readf(&x, " hello ", &y, &s); -   * file.readf("%d hello %f %s", &x, &y, &s); -   * file.readf("%d hello %f", &x, &y, "%s", &s); -   * -------------------------- -   */ -  int vreadf(TypeInfo[] arguments, va_list args); -  int readf(...); /// ditto - -  /// Retrieve the number of bytes available for immediate reading. -  @property size_t available(); - -  /*** -   * Return whether the current file position is the same as the end of the -   * file. -   * -   * This does not require actually reading past the end, as with stdio. For -   * non-seekable streams this might only return true after attempting to read -   * past the end. -   */ - -  @property bool eof(); - -  @property bool isOpen();        /// Return true if the stream is currently open. -} - -/// Interface for writable streams. -interface OutputStream { - -  /*** -   * Write exactly size bytes from buffer, or throw a WriteException if that -   * could not be done. -   */ -  void writeExact(const void* buffer, size_t size); - -  /*** -   * Write as much of the buffer as possible, -   * returning the number of bytes written. -   */ -  size_t write(const(ubyte)[] buffer); - -  /*** -   * Write a basic type. -   * -   * Outside of byte, ubyte, and char, the format is implementation-specific -   * and should only be used in conjunction with read. -   * Throw WriteException on error. -   */ -  void write(byte x); -  void write(ubyte x);          /// ditto -  void write(short x);          /// ditto -  void write(ushort x);         /// ditto -  void write(int x);            /// ditto -  void write(uint x);           /// ditto -  void write(long x);           /// ditto -  void write(ulong x);          /// ditto -  void write(float x);          /// ditto -  void write(double x);         /// ditto -  void write(real x);           /// ditto -  void write(ifloat x);         /// ditto -  void write(idouble x);        /// ditto -  void write(ireal x);          /// ditto -  void write(cfloat x);         /// ditto -  void write(cdouble x);        /// ditto -  void write(creal x);          /// ditto -  void write(char x);           /// ditto -  void write(wchar x);          /// ditto -  void write(dchar x);          /// ditto - -  /*** -   * Writes a string, together with its length. -   * -   * The format is implementation-specific -   * and should only be used in conjunction with read. -   * Throw WriteException on error. -   */ -    void write(const(char)[] s); -    void write(const(wchar)[] s); /// ditto - -  /*** -   * Write a line of text, -   * appending the line with an operating-system-specific line ending. -   * -   * Throws WriteException on error. -   */ -  void writeLine(const(char)[] s); - -  /*** -   * Write a line of text, -   * appending the line with an operating-system-specific line ending. -   * -   * The format is implementation-specific. -   * Throws WriteException on error. -   */ -    void writeLineW(const(wchar)[] s); - -  /*** -   * Write a string of text. -   * -   * Throws WriteException if it could not be fully written. -   */ -    void writeString(const(char)[] s); - -  /*** -   * Write a string of text. -   * -   * The format is implementation-specific. -   * Throws WriteException if it could not be fully written. -   */ -  void writeStringW(const(wchar)[] s); - -  /*** -   * Print a formatted string into the stream using printf-style syntax, -   * returning the number of bytes written. -   */ -  size_t vprintf(const(char)[] format, va_list args); -  size_t printf(const(char)[] format, ...);    /// ditto - -  /*** -   * Print a formatted string into the stream using writef-style syntax. -   * References: <a href="std_format.html">std.format</a>. -   * Returns: self to chain with other stream commands like flush. -   */ -  OutputStream writef(...); -  OutputStream writefln(...); /// ditto -  OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline = false);  /// ditto - -  void flush(); /// Flush pending output if appropriate. -  void close(); /// Close the stream, flushing output if appropriate. -  @property bool isOpen(); /// Return true if the stream is currently open. -} - - -/*** - * Stream is the base abstract class from which the other stream classes derive. - * - * Stream's byte order is the format native to the computer. - * - * Reading: - * These methods require that the readable flag be set. - * Problems with reading result in a ReadException being thrown. - * Stream implements the InputStream interface in addition to the - * readBlock method. - * - * Writing: - * These methods require that the writeable flag be set. Problems with writing - * result in a WriteException being thrown. Stream implements the OutputStream - * interface in addition to the following methods: - * writeBlock - * copyFrom - * copyFrom - * - * Seeking: - * These methods require that the seekable flag be set. - * Problems with seeking result in a SeekException being thrown. - * seek, seekSet, seekCur, seekEnd, position, size, toString, toHash - */ - -// not really abstract, but its instances will do nothing useful -class Stream : InputStream, OutputStream { -  private import std.string, std.digest.crc, core.stdc.stdlib, core.stdc.stdio; - -  // stream abilities -  bool readable = false;        /// Indicates whether this stream can be read from. -  bool writeable = false;       /// Indicates whether this stream can be written to. -  bool seekable = false;        /// Indicates whether this stream can be seeked within. -  protected bool isopen = true; /// Indicates whether this stream is open. - -  protected bool readEOF = false; /** Indicates whether this stream is at eof -                                   * after the last read attempt. -                                   */ - -  protected bool prevCr = false; /** For a non-seekable stream indicates that -                                  * the last readLine or readLineW ended on a -                                  * '\r' character. -                                  */ - -  this() {} - -  /*** -   * Read up to size bytes into the buffer and return the number of bytes -   * actually read. A return value of 0 indicates end-of-file. -   */ -  abstract size_t readBlock(void* buffer, size_t size); - -  // reads block of data of specified size, -  // throws ReadException on error -  void readExact(void* buffer, size_t size) { -    for(;;) { -      if (!size) return; -      size_t readsize = readBlock(buffer, size); // return 0 on eof -      if (readsize == 0) break; -      buffer += readsize; -      size -= readsize; -    } -    if (size != 0) -      throw new ReadException("not enough data in stream"); -  } - -  // reads block of data big enough to fill the given -  // array, returns actual number of bytes read -  size_t read(ubyte[] buffer) { -    return readBlock(buffer.ptr, buffer.length); -  } - -  // read a single value of desired type, -  // throw ReadException on error -  void read(out byte x) { readExact(&x, x.sizeof); } -  void read(out ubyte x) { readExact(&x, x.sizeof); } -  void read(out short x) { readExact(&x, x.sizeof); } -  void read(out ushort x) { readExact(&x, x.sizeof); } -  void read(out int x) { readExact(&x, x.sizeof); } -  void read(out uint x) { readExact(&x, x.sizeof); } -  void read(out long x) { readExact(&x, x.sizeof); } -  void read(out ulong x) { readExact(&x, x.sizeof); } -  void read(out float x) { readExact(&x, x.sizeof); } -  void read(out double x) { readExact(&x, x.sizeof); } -  void read(out real x) { readExact(&x, x.sizeof); } -  void read(out ifloat x) { readExact(&x, x.sizeof); } -  void read(out idouble x) { readExact(&x, x.sizeof); } -  void read(out ireal x) { readExact(&x, x.sizeof); } -  void read(out cfloat x) { readExact(&x, x.sizeof); } -  void read(out cdouble x) { readExact(&x, x.sizeof); } -  void read(out creal x) { readExact(&x, x.sizeof); } -  void read(out char x) { readExact(&x, x.sizeof); } -  void read(out wchar x) { readExact(&x, x.sizeof); } -  void read(out dchar x) { readExact(&x, x.sizeof); } - -  // reads a string, written earlier by write() -  void read(out char[] s) { -    size_t len; -    read(len); -    s = readString(len); -  } - -  // reads a Unicode string, written earlier by write() -  void read(out wchar[] s) { -    size_t len; -    read(len); -    s = readStringW(len); -  } - -  // reads a line, terminated by either CR, LF, CR/LF, or EOF -  char[] readLine() { -    return readLine(null); -  } - -  // reads a line, terminated by either CR, LF, CR/LF, or EOF -  // reusing the memory in buffer if result will fit and otherwise -  // allocates a new string -  char[] readLine(char[] result) { -    size_t strlen = 0; -    char ch = getc(); -    while (readable) { -      switch (ch) { -      case '\r': -        if (seekable) { -          ch = getc(); -          if (ch != '\n') -            ungetc(ch); -        } else { -          prevCr = true; -        } -        goto case; -      case '\n': -      case char.init: -        result.length = strlen; -        return result; - -      default: -        if (strlen < result.length) { -          result[strlen] = ch; -        } else { -          result ~= ch; -        } -        strlen++; -      } -      ch = getc(); -    } -    result.length = strlen; -    return result; -  } - -  // reads a Unicode line, terminated by either CR, LF, CR/LF, -  // or EOF; pretty much the same as the above, working with -  // wchars rather than chars -  wchar[] readLineW() { -    return readLineW(null); -  } - -  // reads a Unicode line, terminated by either CR, LF, CR/LF, -  // or EOF; -  // fills supplied buffer if line fits and otherwise allocates a new string. -  wchar[] readLineW(wchar[] result) { -    size_t strlen = 0; -    wchar c = getcw(); -    while (readable) { -      switch (c) { -      case '\r': -        if (seekable) { -          c = getcw(); -          if (c != '\n') -            ungetcw(c); -        } else { -          prevCr = true; -        } -        goto case; -      case '\n': -      case wchar.init: -        result.length = strlen; -        return result; - -      default: -        if (strlen < result.length) { -          result[strlen] = c; -        } else { -          result ~= c; -        } -        strlen++; -      } -      c = getcw(); -    } -    result.length = strlen; -    return result; -  } - -  // iterate through the stream line-by-line - due to Regan Heath -  int opApply(scope int delegate(ref char[] line) dg) { -    int res = 0; -    char[128] buf; -    while (!eof) { -      char[] line = readLine(buf); -      res = dg(line); -      if (res) break; -    } -    return res; -  } - -  // iterate through the stream line-by-line with line count and string -  int opApply(scope int delegate(ref ulong n, ref char[] line) dg) { -    int res = 0; -    ulong n = 1; -    char[128] buf; -    while (!eof) { -      auto line = readLine(buf); -      res = dg(n,line); -      if (res) break; -      n++; -    } -    return res; -  } - -  // iterate through the stream line-by-line with wchar[] -  int opApply(scope int delegate(ref wchar[] line) dg) { -    int res = 0; -    wchar[128] buf; -    while (!eof) { -      auto line = readLineW(buf); -      res = dg(line); -      if (res) break; -    } -    return res; -  } - -  // iterate through the stream line-by-line with line count and wchar[] -  int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg) { -    int res = 0; -    ulong n = 1; -    wchar[128] buf; -    while (!eof) { -      auto line = readLineW(buf); -      res = dg(n,line); -      if (res) break; -      n++; -    } -    return res; -  } - -  // reads a string of given length, throws -  // ReadException on error -  char[] readString(size_t length) { -    char[] result = new char[length]; -    readExact(result.ptr, length); -    return result; -  } - -  // reads a Unicode string of given length, throws -  // ReadException on error -  wchar[] readStringW(size_t length) { -    auto result = new wchar[length]; -    readExact(result.ptr, result.length * wchar.sizeof); -    return result; -  } - -  // unget buffer -  private wchar[] unget; -  final bool ungetAvailable() { return unget.length > 1; } - -  // reads and returns next character from the stream, -  // handles characters pushed back by ungetc() -  // returns char.init on eof. -  char getc() { -    char c; -    if (prevCr) { -      prevCr = false; -      c = getc(); -      if (c != '\n') -        return c; -    } -    if (unget.length > 1) { -      c = cast(char)unget[unget.length - 1]; -      unget.length = unget.length - 1; -    } else { -      readBlock(&c,1); -    } -    return c; -  } - -  // reads and returns next Unicode character from the -  // stream, handles characters pushed back by ungetc() -  // returns wchar.init on eof. -  wchar getcw() { -    wchar c; -    if (prevCr) { -      prevCr = false; -      c = getcw(); -      if (c != '\n') -        return c; -    } -    if (unget.length > 1) { -      c = unget[unget.length - 1]; -      unget.length = unget.length - 1; -    } else { -      void* buf = &c; -      size_t n = readBlock(buf,2); -      if (n == 1 && readBlock(buf+1,1) == 0) -          throw new ReadException("not enough data in stream"); -    } -    return c; -  } - -  // pushes back character c into the stream; only has -  // effect on further calls to getc() and getcw() -  char ungetc(char c) { -    if (c == c.init) return c; -    // first byte is a dummy so that we never set length to 0 -    if (unget.length == 0) -      unget.length = 1; -    unget ~= c; -    return c; -  } - -  // pushes back Unicode character c into the stream; only -  // has effect on further calls to getc() and getcw() -  wchar ungetcw(wchar c) { -    if (c == c.init) return c; -    // first byte is a dummy so that we never set length to 0 -    if (unget.length == 0) -      unget.length = 1; -    unget ~= c; -    return c; -  } - -  int vreadf(TypeInfo[] arguments, va_list args) { -    string fmt; -    int j = 0; -    int count = 0, i = 0; -    char c; -    bool firstCharacter = true; -    while ((j < arguments.length || i < fmt.length) && !eof) { -      if(firstCharacter) { -        c = getc(); -        firstCharacter = false; -      } -      if (fmt.length == 0 || i == fmt.length) { -        i = 0; -        if (arguments[j] is typeid(string) || arguments[j] is typeid(char[]) -            || arguments[j] is typeid(const(char)[])) { -          fmt = va_arg!(string)(args); -          j++; -          continue; -        } else if (arguments[j] is typeid(int*) || -                   arguments[j] is typeid(byte*) || -                   arguments[j] is typeid(short*) || -                   arguments[j] is typeid(long*)) { -          fmt = "%d"; -        } else if (arguments[j] is typeid(uint*) || -                   arguments[j] is typeid(ubyte*) || -                   arguments[j] is typeid(ushort*) || -                   arguments[j] is typeid(ulong*)) { -          fmt = "%d"; -        } else if (arguments[j] is typeid(float*) || -                   arguments[j] is typeid(double*) || -                   arguments[j] is typeid(real*)) { -          fmt = "%f"; -        } else if (arguments[j] is typeid(char[]*) || -                   arguments[j] is typeid(wchar[]*) || -                   arguments[j] is typeid(dchar[]*)) { -          fmt = "%s"; -        } else if (arguments[j] is typeid(char*)) { -          fmt = "%c"; -        } -      } -      if (fmt[i] == '%') {      // a field -        i++; -        bool suppress = false; -        if (fmt[i] == '*') {    // suppress assignment -          suppress = true; -          i++; -        } -        // read field width -        int width = 0; -        while (isDigit(fmt[i])) { -          width = width * 10 + (fmt[i] - '0'); -          i++; -        } -        if (width == 0) -          width = -1; -        // skip any modifier if present -        if (fmt[i] == 'h' || fmt[i] == 'l' || fmt[i] == 'L') -          i++; -        // check the typechar and act accordingly -        switch (fmt[i]) { -        case 'd':       // decimal/hexadecimal/octal integer -        case 'D': -        case 'u': -        case 'U': -        case 'o': -        case 'O': -        case 'x': -        case 'X': -        case 'i': -        case 'I': -          { -            while (isWhite(c)) { -              c = getc(); -              count++; -            } -            bool neg = false; -            if (c == '-') { -              neg = true; -              c = getc(); -              count++; -            } else if (c == '+') { -              c = getc(); -              count++; -            } -            char ifmt = cast(char)(fmt[i] | 0x20); -            if (ifmt == 'i')    { // undetermined base -              if (c == '0')     { // octal or hex -                c = getc(); -                count++; -                if (c == 'x' || c == 'X')       { // hex -                  ifmt = 'x'; -                  c = getc(); -                  count++; -                } else {        // octal -                  ifmt = 'o'; -                } -              } -              else      // decimal -                ifmt = 'd'; -            } -            long n = 0; -            switch (ifmt) -            { -                case 'd':       // decimal -                case 'u': { -                  while (isDigit(c) && width) { -                    n = n * 10 + (c - '0'); -                    width--; -                    c = getc(); -                    count++; -                  } -                } break; - -                case 'o': {     // octal -                  while (isOctalDigit(c) && width) { -                    n = n * 8 + (c - '0'); -                    width--; -                    c = getc(); -                    count++; -                  } -                } break; - -                case 'x': {     // hexadecimal -                  while (isHexDigit(c) && width) { -                    n *= 0x10; -                    if (isDigit(c)) -                      n += c - '0'; -                    else -                      n += 0xA + (c | 0x20) - 'a'; -                    width--; -                    c = getc(); -                    count++; -                  } -                } break; - -                default: -                    assert(0); -            } -            if (neg) -              n = -n; -            if (arguments[j] is typeid(int*)) { -              int* p = va_arg!(int*)(args); -              *p = cast(int)n; -            } else if (arguments[j] is typeid(short*)) { -              short* p = va_arg!(short*)(args); -              *p = cast(short)n; -            } else if (arguments[j] is typeid(byte*)) { -              byte* p = va_arg!(byte*)(args); -              *p = cast(byte)n; -            } else if (arguments[j] is typeid(long*)) { -              long* p = va_arg!(long*)(args); -              *p = n; -            } else if (arguments[j] is typeid(uint*)) { -              uint* p = va_arg!(uint*)(args); -              *p = cast(uint)n; -            } else if (arguments[j] is typeid(ushort*)) { -              ushort* p = va_arg!(ushort*)(args); -              *p = cast(ushort)n; -            } else if (arguments[j] is typeid(ubyte*)) { -              ubyte* p = va_arg!(ubyte*)(args); -              *p = cast(ubyte)n; -            } else if (arguments[j] is typeid(ulong*)) { -              ulong* p = va_arg!(ulong*)(args); -              *p = cast(ulong)n; -            } -            j++; -            i++; -          } break; - -        case 'f':       // float -        case 'F': -        case 'e': -        case 'E': -        case 'g': -        case 'G': -          { -            while (isWhite(c)) { -              c = getc(); -              count++; -            } -            bool neg = false; -            if (c == '-') { -              neg = true; -              c = getc(); -              count++; -            } else if (c == '+') { -              c = getc(); -              count++; -            } -            real r = 0; -            while (isDigit(c) && width) { -              r = r * 10 + (c - '0'); -              width--; -              c = getc(); -              count++; -            } -            if (width && c == '.') { -              width--; -              c = getc(); -              count++; -              double frac = 1; -              while (isDigit(c) && width) { -                r = r * 10 + (c - '0'); -                frac *= 10; -                width--; -                c = getc(); -                count++; -              } -              r /= frac; -            } -            if (width && (c == 'e' || c == 'E')) { -              width--; -              c = getc(); -              count++; -              if (width) { -                bool expneg = false; -                if (c == '-') { -                  expneg = true; -                  width--; -                  c = getc(); -                  count++; -                } else if (c == '+') { -                  width--; -                  c = getc(); -                  count++; -                } -                real exp = 0; -                while (isDigit(c) && width) { -                  exp = exp * 10 + (c - '0'); -                  width--; -                  c = getc(); -                  count++; -                } -                if (expneg) { -                  while (exp--) -                    r /= 10; -                } else { -                  while (exp--) -                    r *= 10; -                } -              } -            } -            if(width && (c == 'n' || c == 'N')) { -              width--; -              c = getc(); -              count++; -              if(width && (c == 'a' || c == 'A')) { -                width--; -                c = getc(); -                count++; -                if(width && (c == 'n' || c == 'N')) { -                  width--; -                  c = getc(); -                  count++; -                  r = real.nan; -                } -              } -            } -            if(width && (c == 'i' || c == 'I')) { -              width--; -              c = getc(); -              count++; -              if(width && (c == 'n' || c == 'N')) { -                width--; -                c = getc(); -                count++; -                if(width && (c == 'f' || c == 'F')) { -                  width--; -                  c = getc(); -                  count++; -                  r = real.infinity; -                } -              } -            } -            if (neg) -              r = -r; -            if (arguments[j] is typeid(float*)) { -              float* p = va_arg!(float*)(args); -              *p = r; -            } else if (arguments[j] is typeid(double*)) { -              double* p = va_arg!(double*)(args); -              *p = r; -            } else if (arguments[j] is typeid(real*)) { -              real* p = va_arg!(real*)(args); -              *p = r; -            } -            j++; -            i++; -          } break; - -        case 's': {     // string -          while (isWhite(c)) { -            c = getc(); -            count++; -          } -          char[] s; -          char[]* p; -          size_t strlen; -          if (arguments[j] is typeid(char[]*)) { -            p = va_arg!(char[]*)(args); -            s = *p; -          } -          while (!isWhite(c) && c != char.init) { -            if (strlen < s.length) { -              s[strlen] = c; -            } else { -              s ~= c; -            } -            strlen++; -            c = getc(); -            count++; -          } -          s = s[0 .. strlen]; -          if (arguments[j] is typeid(char[]*)) { -            *p = s; -          } else if (arguments[j] is typeid(char*)) { -            s ~= 0; -            auto q = va_arg!(char*)(args); -            q[0 .. s.length] = s[]; -          } else if (arguments[j] is typeid(wchar[]*)) { -            auto q = va_arg!(const(wchar)[]*)(args); -            *q = toUTF16(s); -          } else if (arguments[j] is typeid(dchar[]*)) { -            auto q = va_arg!(const(dchar)[]*)(args); -            *q = toUTF32(s); -          } -          j++; -          i++; -        } break; - -        case 'c': {     // character(s) -          char* s = va_arg!(char*)(args); -          if (width < 0) -            width = 1; -          else -            while (isWhite(c)) { -            c = getc(); -            count++; -          } -          while (width-- && !eof) { -            *(s++) = c; -            c = getc(); -            count++; -          } -          j++; -          i++; -        } break; - -        case 'n': {     // number of chars read so far -          int* p = va_arg!(int*)(args); -          *p = count; -          j++; -          i++; -        } break; - -        default:        // read character as is -          goto nws; -        } -      } else if (isWhite(fmt[i])) {     // skip whitespace -        while (isWhite(c)) -          c = getc(); -        i++; -      } else {  // read character as is -      nws: -        if (fmt[i] != c) -          break; -        c = getc(); -        i++; -      } -    } -    ungetc(c); -    return count; -  } - -  int readf(...) { -    return vreadf(_arguments, _argptr); -  } - -  // returns estimated number of bytes available for immediate reading -  @property size_t available() { return 0; } - -  /*** -   * Write up to size bytes from buffer in the stream, returning the actual -   * number of bytes that were written. -   */ -  abstract size_t writeBlock(const void* buffer, size_t size); - -  // writes block of data of specified size, -  // throws WriteException on error -  void writeExact(const void* buffer, size_t size) { -    const(void)* p = buffer; -    for(;;) { -      if (!size) return; -      size_t writesize = writeBlock(p, size); -      if (writesize == 0) break; -      p += writesize; -      size -= writesize; -    } -    if (size != 0) -      throw new WriteException("unable to write to stream"); -  } - -  // writes the given array of bytes, returns -  // actual number of bytes written -  size_t write(const(ubyte)[] buffer) { -    return writeBlock(buffer.ptr, buffer.length); -  } - -  // write a single value of desired type, -  // throw WriteException on error -  void write(byte x) { writeExact(&x, x.sizeof); } -  void write(ubyte x) { writeExact(&x, x.sizeof); } -  void write(short x) { writeExact(&x, x.sizeof); } -  void write(ushort x) { writeExact(&x, x.sizeof); } -  void write(int x) { writeExact(&x, x.sizeof); } -  void write(uint x) { writeExact(&x, x.sizeof); } -  void write(long x) { writeExact(&x, x.sizeof); } -  void write(ulong x) { writeExact(&x, x.sizeof); } -  void write(float x) { writeExact(&x, x.sizeof); } -  void write(double x) { writeExact(&x, x.sizeof); } -  void write(real x) { writeExact(&x, x.sizeof); } -  void write(ifloat x) { writeExact(&x, x.sizeof); } -  void write(idouble x) { writeExact(&x, x.sizeof); } -  void write(ireal x) { writeExact(&x, x.sizeof); } -  void write(cfloat x) { writeExact(&x, x.sizeof); } -  void write(cdouble x) { writeExact(&x, x.sizeof); } -  void write(creal x) { writeExact(&x, x.sizeof); } -  void write(char x) { writeExact(&x, x.sizeof); } -  void write(wchar x) { writeExact(&x, x.sizeof); } -  void write(dchar x) { writeExact(&x, x.sizeof); } - -  // writes a string, together with its length -  void write(const(char)[] s) { -    write(s.length); -    writeString(s); -  } - -  // writes a Unicode string, together with its length -  void write(const(wchar)[] s) { -    write(s.length); -    writeStringW(s); -  } - -  // writes a line, throws WriteException on error -  void writeLine(const(char)[] s) { -    writeString(s); -    version (Windows) -      writeString("\r\n"); -    else version (Mac) -      writeString("\r"); -    else -      writeString("\n"); -  } - -  // writes a Unicode line, throws WriteException on error -  void writeLineW(const(wchar)[] s) { -    writeStringW(s); -    version (Windows) -      writeStringW("\r\n"); -    else version (Mac) -      writeStringW("\r"); -    else -      writeStringW("\n"); -  } - -  // writes a string, throws WriteException on error -  void writeString(const(char)[] s) { -    writeExact(s.ptr, s.length); -  } - -  // writes a Unicode string, throws WriteException on error -  void writeStringW(const(wchar)[] s) { -    writeExact(s.ptr, s.length * wchar.sizeof); -  } - -  // writes data to stream using vprintf() syntax, -  // returns number of bytes written -  size_t vprintf(const(char)[] format, va_list args) { -    // shamelessly stolen from OutBuffer, -    // by Walter's permission -    char[1024] buffer; -    char* p = buffer.ptr; -    // Can't use `tempCString()` here as it will result in compilation error: -    // "cannot mix core.std.stdlib.alloca() and exception handling". -    auto f = toStringz(format); -    size_t psize = buffer.length; -    size_t count; -    while (true) { -      version (Windows) { -        count = vsnprintf(p, psize, f, args); -        if (count != -1) -          break; -        psize *= 2; -        p = cast(char*) alloca(psize); -      } else version (Posix) { -        count = vsnprintf(p, psize, f, args); -        if (count == -1) -          psize *= 2; -        else if (count >= psize) -          psize = count + 1; -        else -          break; -        p = cast(char*) alloca(psize); -      } else -          throw new Exception("unsupported platform"); -    } -    writeString(p[0 .. count]); -    return count; -  } - -  // writes data to stream using printf() syntax, -  // returns number of bytes written -  size_t printf(const(char)[] format, ...) { -    va_list ap; -    va_start(ap, format); -    auto result = vprintf(format, ap); -    va_end(ap); -    return result; -  } - -  private void doFormatCallback(dchar c) { -    char[4] buf; -    auto b = std.utf.toUTF8(buf, c); -    writeString(b); -  } - -  // writes data to stream using writef() syntax, -  OutputStream writef(...) { -    return writefx(_arguments,_argptr,0); -  } - -  // writes data with trailing newline -  OutputStream writefln(...) { -    return writefx(_arguments,_argptr,1); -  } - -  // writes data with optional trailing newline -  OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline=false) { -    doFormat(&doFormatCallback,arguments,argptr); -    if (newline) -      writeLine(""); -    return this; -  } - -  /*** -   * Copies all data from s into this stream. -   * This may throw ReadException or WriteException on failure. -   * This restores the file position of s so that it is unchanged. -   */ -  void copyFrom(Stream s) { -    if (seekable) { -      ulong pos = s.position; -      s.position = 0; -      copyFrom(s, s.size); -      s.position = pos; -    } else { -      ubyte[128] buf; -      while (!s.eof) { -        size_t m = s.readBlock(buf.ptr, buf.length); -        writeExact(buf.ptr, m); -      } -    } -  } - -  /*** -   * Copy a specified number of bytes from the given stream into this one. -   * This may throw ReadException or WriteException on failure. -   * Unlike the previous form, this doesn't restore the file position of s. -   */ -  void copyFrom(Stream s, ulong count) { -    ubyte[128] buf; -    while (count > 0) { -      size_t n = cast(size_t)(count<buf.length ? count : buf.length); -      s.readExact(buf.ptr, n); -      writeExact(buf.ptr, n); -      count -= n; -    } -  } - -  /*** -   * Change the current position of the stream. whence is either SeekPos.Set, in -   which case the offset is an absolute index from the beginning of the stream, -   SeekPos.Current, in which case the offset is a delta from the current -   position, or SeekPos.End, in which case the offset is a delta from the end of -   the stream (negative or zero offsets only make sense in that case). This -   returns the new file position. -   */ -  abstract ulong seek(long offset, SeekPos whence); - -  /*** -   * Aliases for their normal seek counterparts. -   */ -  ulong seekSet(long offset) { return seek (offset, SeekPos.Set); } -  ulong seekCur(long offset) { return seek (offset, SeekPos.Current); } /// ditto -  ulong seekEnd(long offset) { return seek (offset, SeekPos.End); }     /// ditto - -  /*** -   * Sets file position. Equivalent to calling seek(pos, SeekPos.Set). -   */ -  @property void position(ulong pos) { seek(cast(long)pos, SeekPos.Set); } - -  /*** -   * Returns current file position. Equivalent to seek(0, SeekPos.Current). -   */ -  @property ulong position() { return seek(0, SeekPos.Current); } - -  /*** -   * Retrieve the size of the stream in bytes. -   * The stream must be seekable or a SeekException is thrown. -   */ -  @property ulong size() { -    assertSeekable(); -    ulong pos = position, result = seek(0, SeekPos.End); -    position = pos; -    return result; -  } - -  // returns true if end of stream is reached, false otherwise -  @property bool eof() { -    // for unseekable streams we only know the end when we read it -    if (readEOF && !ungetAvailable()) -      return true; -    else if (seekable) -      return position == size; -    else -      return false; -  } - -  // returns true if the stream is open -  @property bool isOpen() { return isopen; } - -  // flush the buffer if writeable -  void flush() { -    if (unget.length > 1) -      unget.length = 1; // keep at least 1 so that data ptr stays -  } - -  // close the stream somehow; the default just flushes the buffer -  void close() { -    if (isopen) -      flush(); -    readEOF = prevCr = isopen = readable = writeable = seekable = false; -  } - -  /*** -   * Read the entire stream and return it as a string. -   * If the stream is not seekable the contents from the current position to eof -   * is read and returned. -   */ -  override string toString() { -    if (!readable) -      return super.toString(); -    try -    { -        size_t pos; -        size_t rdlen; -        size_t blockSize; -        char[] result; -        if (seekable) { -          ulong orig_pos = position; -          scope(exit) position = orig_pos; -          position = 0; -          blockSize = cast(size_t)size; -          result = new char[blockSize]; -          while (blockSize > 0) { -            rdlen = readBlock(&result[pos], blockSize); -            pos += rdlen; -            blockSize -= rdlen; -          } -        } else { -          blockSize = 4096; -          result = new char[blockSize]; -          while ((rdlen = readBlock(&result[pos], blockSize)) > 0) { -            pos += rdlen; -            blockSize += rdlen; -            result.length = result.length + blockSize; -          } -        } -        return cast(string) result[0 .. pos]; -    } -    catch (Throwable) -    { -        return super.toString(); -    } -  } - -  /*** -   * Get a hash of the stream by reading each byte and using it in a CRC-32 -   * checksum. -   */ -  override size_t toHash() @trusted { -    if (!readable || !seekable) -      return super.toHash(); -    try -    { -        ulong pos = position; -        scope(exit) position = pos; -        CRC32 crc; -        crc.start(); -        position = 0; -        ulong len = size; -        for (ulong i = 0; i < len; i++) -        { -          ubyte c; -          read(c); -          crc.put(c); -        } - -        union resUnion -        { -            size_t hash; -            ubyte[4] crcVal; -        } -        resUnion res; -        res.crcVal = crc.finish(); -        return res.hash; -    } -    catch (Throwable) -    { -        return super.toHash(); -    } -  } - -  // helper for checking that the stream is readable -  final protected void assertReadable() { -    if (!readable) -      throw new ReadException("Stream is not readable"); -  } -  // helper for checking that the stream is writeable -  final protected void assertWriteable() { -    if (!writeable) -      throw new WriteException("Stream is not writeable"); -  } -  // helper for checking that the stream is seekable -  final protected void assertSeekable() { -    if (!seekable) -      throw new SeekException("Stream is not seekable"); -  } -  /+ -  unittest { // unit test for Issue 3363 -    import std.stdio; -    immutable fileName = undead.internal.file.deleteme ~ "-issue3363.txt"; -    auto w = File(fileName, "w"); -    scope (exit) remove(fileName.ptr); -    w.write("one two three"); -    w.close(); -    auto r = File(fileName, "r"); -    const(char)[] constChar; -    string str; -    char[] chars; -    r.readf("%s %s %s", &constChar, &str, &chars); -    assert (constChar == "one", constChar); -    assert (str == "two", str); -    assert (chars == "three", chars); -  } - -  unittest { //unit tests for Issue 1668 -    void tryFloatRoundtrip(float x, string fmt = "", string pad = "") { -      auto s = new MemoryStream(); -      s.writef(fmt, x, pad); -      s.position = 0; - -      float f; -      assert(s.readf(&f)); -      assert(x == f || (x != x && f != f)); //either equal or both NaN -    } - -    tryFloatRoundtrip(1.0); -    tryFloatRoundtrip(1.0, "%f"); -    tryFloatRoundtrip(1.0, "", " "); -    tryFloatRoundtrip(1.0, "%f", " "); - -    tryFloatRoundtrip(3.14); -    tryFloatRoundtrip(3.14, "%f"); -    tryFloatRoundtrip(3.14, "", " "); -    tryFloatRoundtrip(3.14, "%f", " "); - -    float nan = float.nan; -    tryFloatRoundtrip(nan); -    tryFloatRoundtrip(nan, "%f"); -    tryFloatRoundtrip(nan, "", " "); -    tryFloatRoundtrip(nan, "%f", " "); - -    float inf = 1.0/0.0; -    tryFloatRoundtrip(inf); -    tryFloatRoundtrip(inf, "%f"); -    tryFloatRoundtrip(inf, "", " "); -    tryFloatRoundtrip(inf, "%f", " "); - -    tryFloatRoundtrip(-inf); -    tryFloatRoundtrip(-inf,"%f"); -    tryFloatRoundtrip(-inf, "", " "); -    tryFloatRoundtrip(-inf, "%f", " "); -  } -  +/ -} - -/*** - * A base class for streams that wrap a source stream with additional - * functionality. - * - * The method implementations forward read/write/seek calls to the - * source stream. A FilterStream can change the position of the source stream - * arbitrarily and may not keep the source stream state in sync with the - * FilterStream, even upon flushing and closing the FilterStream. It is - * recommended to not make any assumptions about the state of the source position - * and read/write state after a FilterStream has acted upon it. Specifc subclasses - * of FilterStream should document how they modify the source stream and if any - * invariants hold true between the source and filter. - */ -class FilterStream : Stream { -  private Stream s;              // source stream - -  /// Property indicating when this stream closes to close the source stream as -  /// well. -  /// Defaults to true. -  bool nestClose = true; - -  /// Construct a FilterStream for the given source. -  this(Stream source) { -    s = source; -    resetSource(); -  } - -  // source getter/setter - -  /*** -   * Get the current source stream. -   */ -  final Stream source(){return s;} - -  /*** -   * Set the current source stream. -   * -   * Setting the source stream closes this stream before attaching the new -   * source. Attaching an open stream reopens this stream and resets the stream -   * state. -   */ -  void source(Stream s) { -    close(); -    this.s = s; -    resetSource(); -  } - -  /*** -   * Indicates the source stream changed state and that this stream should reset -   * any readable, writeable, seekable, isopen and buffering flags. -   */ -  void resetSource() { -    if (s !is null) { -      readable = s.readable; -      writeable = s.writeable; -      seekable = s.seekable; -      isopen = s.isOpen; -    } else { -      readable = writeable = seekable = false; -      isopen = false; -    } -    readEOF = prevCr = false; -  } - -  // read from source -  override size_t readBlock(void* buffer, size_t size) { -    size_t res = s.readBlock(buffer,size); -    readEOF = res == 0; -    return res; -  } - -  // write to source -  override size_t writeBlock(const void* buffer, size_t size) { -    return s.writeBlock(buffer,size); -  } - -  // close stream -  override void close() { -    if (isopen) { -      super.close(); -      if (nestClose) -        s.close(); -    } -  } - -  // seek on source -  override ulong seek(long offset, SeekPos whence) { -    readEOF = false; -    return s.seek(offset,whence); -  } - -  override @property size_t available() { return s.available; } -  override void flush() { super.flush(); s.flush(); } -} - -/*** - * This subclass is for buffering a source stream. - * - * A buffered stream must be - * closed explicitly to ensure the final buffer content is written to the source - * stream. The source stream position is changed according to the block size so - * reading or writing to the BufferedStream may not change the source stream - * position by the same amount. - */ -class BufferedStream : FilterStream { -  ubyte[] buffer;       // buffer, if any -  size_t bufferCurPos;    // current position in buffer -  size_t bufferLen;       // amount of data in buffer -  bool bufferDirty = false; -  size_t bufferSourcePos; // position in buffer of source stream position -  ulong streamPos;      // absolute position in source stream - -  /* Example of relationship between fields: -   * -   *  s             ...01234567890123456789012EOF -   *  buffer                |--                     --| -   *  bufferCurPos                       | -   *  bufferLen             |--            --| -   *  bufferSourcePos                        | -   * -   */ - -  invariant() { -    assert(bufferSourcePos <= bufferLen); -    assert(bufferCurPos <= bufferLen); -    assert(bufferLen <= buffer.length); -  } - -  enum size_t DefaultBufferSize = 8192; - -  /*** -   * Create a buffered stream for the stream source with the buffer size -   * bufferSize. -   */ -  this(Stream source, size_t bufferSize = DefaultBufferSize) { -    super(source); -    if (bufferSize) -      buffer = new ubyte[bufferSize]; -  } - -  override protected void resetSource() { -    super.resetSource(); -    streamPos = 0; -    bufferLen = bufferSourcePos = bufferCurPos = 0; -    bufferDirty = false; -  } - -  // reads block of data of specified size using any buffered data -  // returns actual number of bytes read -  override size_t readBlock(void* result, size_t len) { -    if (len == 0) return 0; - -    assertReadable(); - -    ubyte* outbuf = cast(ubyte*)result; -    size_t readsize = 0; - -    if (bufferCurPos + len < bufferLen) { -      // buffer has all the data so copy it -      outbuf[0 .. len] = buffer[bufferCurPos .. bufferCurPos+len]; -      bufferCurPos += len; -      readsize = len; -      goto ExitRead; -    } - -    readsize = bufferLen - bufferCurPos; -    if (readsize > 0) { -      // buffer has some data so copy what is left -      outbuf[0 .. readsize] = buffer[bufferCurPos .. bufferLen]; -      outbuf += readsize; -      bufferCurPos += readsize; -      len -= readsize; -    } - -    flush(); - -    if (len >= buffer.length) { -      // buffer can't hold the data so fill output buffer directly -      size_t siz = super.readBlock(outbuf, len); -      readsize += siz; -      streamPos += siz; -    } else { -      // read a new block into buffer -        bufferLen = super.readBlock(buffer.ptr, buffer.length); -        if (bufferLen < len) len = bufferLen; -        outbuf[0 .. len] = buffer[0 .. len]; -        bufferSourcePos = bufferLen; -        streamPos += bufferLen; -        bufferCurPos = len; -        readsize += len; -    } - -  ExitRead: -    return readsize; -  } - -  // write block of data of specified size -  // returns actual number of bytes written -  override size_t writeBlock(const void* result, size_t len) { -    assertWriteable(); - -    ubyte* buf = cast(ubyte*)result; -    size_t writesize = 0; - -    if (bufferLen == 0) { -      // buffer is empty so fill it if possible -      if ((len < buffer.length) && (readable)) { -        // read in data if the buffer is currently empty -        bufferLen = s.readBlock(buffer.ptr, buffer.length); -        bufferSourcePos = bufferLen; -        streamPos += bufferLen; - -      } else if (len >= buffer.length) { -        // buffer can't hold the data so write it directly and exit -        writesize = s.writeBlock(buf,len); -        streamPos += writesize; -        goto ExitWrite; -      } -    } - -    if (bufferCurPos + len <= buffer.length) { -      // buffer has space for all the data so copy it and exit -      buffer[bufferCurPos .. bufferCurPos+len] = buf[0 .. len]; -      bufferCurPos += len; -      bufferLen = bufferCurPos > bufferLen ? bufferCurPos : bufferLen; -      writesize = len; -      bufferDirty = true; -      goto ExitWrite; -    } - -    writesize = buffer.length - bufferCurPos; -    if (writesize > 0) { -      // buffer can take some data -      buffer[bufferCurPos .. buffer.length] = buf[0 .. writesize]; -      bufferCurPos = bufferLen = buffer.length; -      buf += writesize; -      len -= writesize; -      bufferDirty = true; -    } - -    assert(bufferCurPos == buffer.length); -    assert(bufferLen == buffer.length); - -    flush(); - -    writesize += writeBlock(buf,len); - -  ExitWrite: -    return writesize; -  } - -  override ulong seek(long offset, SeekPos whence) { -    assertSeekable(); - -    if ((whence != SeekPos.Current) || -        (offset + bufferCurPos < 0) || -        (offset + bufferCurPos >= bufferLen)) { -      flush(); -      streamPos = s.seek(offset,whence); -    } else { -      bufferCurPos += offset; -    } -    readEOF = false; -    return streamPos-bufferSourcePos+bufferCurPos; -  } - -  // Buffered readLine - Dave Fladebo -  // reads a line, terminated by either CR, LF, CR/LF, or EOF -  // reusing the memory in buffer if result will fit, otherwise -  // will reallocate (using concatenation) -  template TreadLine(T) { -      T[] readLine(T[] inBuffer) -      { -          size_t    lineSize = 0; -          bool    haveCR = false; -          T       c = '\0'; -          size_t    idx = 0; -          ubyte*  pc = cast(ubyte*)&c; - -        L0: -          for(;;) { -              size_t start = bufferCurPos; -            L1: -              foreach(ubyte b; buffer[start .. bufferLen]) { -                  bufferCurPos++; -                  pc[idx] = b; -                  if(idx < T.sizeof - 1) { -                      idx++; -                      continue L1; -                  } else { -                      idx = 0; -                  } -                  if(c == '\n' || haveCR) { -                      if(haveCR && c != '\n') bufferCurPos--; -                      break L0; -                  } else { -                      if(c == '\r') { -                          haveCR = true; -                      } else { -                          if(lineSize < inBuffer.length) { -                              inBuffer[lineSize] = c; -                          } else { -                              inBuffer ~= c; -                          } -                          lineSize++; -                      } -                  } -              } -              flush(); -              size_t res = super.readBlock(buffer.ptr, buffer.length); -              if(!res) break L0; // EOF -              bufferSourcePos = bufferLen = res; -              streamPos += res; -          } -          return inBuffer[0 .. lineSize]; -      } -  } // template TreadLine(T) - -  override char[] readLine(char[] inBuffer) { -    if (ungetAvailable()) -      return super.readLine(inBuffer); -    else -      return TreadLine!(char).readLine(inBuffer); -  } -  alias readLine = Stream.readLine; - -  override wchar[] readLineW(wchar[] inBuffer) { -    if (ungetAvailable()) -      return super.readLineW(inBuffer); -    else -      return TreadLine!(wchar).readLine(inBuffer); -  } -  alias readLineW = Stream.readLineW; - -  override void flush() -  out { -    assert(bufferCurPos == 0); -    assert(bufferSourcePos == 0); -    assert(bufferLen == 0); -  } -  body { -    if (writeable && bufferDirty) { -      if (bufferSourcePos != 0 && seekable) { -        // move actual file pointer to front of buffer -        streamPos = s.seek(-bufferSourcePos, SeekPos.Current); -      } -      // write buffer out -      bufferSourcePos = s.writeBlock(buffer.ptr, bufferLen); -      if (bufferSourcePos != bufferLen) { -        throw new WriteException("Unable to write to stream"); -      } -    } -    super.flush(); -    long diff = cast(long)bufferCurPos-bufferSourcePos; -    if (diff != 0 && seekable) { -      // move actual file pointer to current position -      streamPos = s.seek(diff, SeekPos.Current); -    } -    // reset buffer data to be empty -    bufferSourcePos = bufferCurPos = bufferLen = 0; -    bufferDirty = false; -  } - -  // returns true if end of stream is reached, false otherwise -  override @property bool eof() { -    if ((buffer.length == 0) || !readable) { -      return super.eof; -    } -    // some simple tests to avoid flushing -    if (ungetAvailable() || bufferCurPos != bufferLen) -      return false; -    if (bufferLen == buffer.length) -      flush(); -    size_t res = super.readBlock(&buffer[bufferLen],buffer.length-bufferLen); -    bufferSourcePos +=  res; -    bufferLen += res; -    streamPos += res; -    return readEOF; -  } - -  // returns size of stream -  override @property ulong size() { -    if (bufferDirty) flush(); -    return s.size; -  } - -  // returns estimated number of bytes available for immediate reading -  override @property size_t available() { -    return bufferLen - bufferCurPos; -  } -} - -/// An exception for File errors. -class StreamFileException: StreamException { -  /// Construct a StreamFileException with given error message. -  this(string msg) { super(msg); } -} - -/// An exception for errors during File.open. -class OpenException: StreamFileException { -  /// Construct an OpenFileException with given error message. -  this(string msg) { super(msg); } -} - -/// Specifies the $(LREF File) access mode used when opening the file. -enum FileMode { -  In = 1,     /// Opens the file for reading. -  Out = 2,    /// Opens the file for writing. -  OutNew = 6, /// Opens the file for writing, creates a new file if it doesn't exist. -  Append = 10 /// Opens the file for writing, appending new data to the end of the file. -} - -version (Windows) { -  private import core.sys.windows.windows; -  extern (Windows) { -    void FlushFileBuffers(HANDLE hFile); -    DWORD  GetFileType(HANDLE hFile); -  } -} -version (Posix) { -  private import core.sys.posix.fcntl; -  private import core.sys.posix.unistd; -  alias HANDLE = int; -} - -/// This subclass is for unbuffered file system streams. -class File: Stream { - -  version (Windows) { -    private HANDLE hFile; -  } -  version (Posix) { -    private HANDLE hFile = -1; -  } - -  this() { -    super(); -    version (Windows) { -      hFile = null; -    } -    version (Posix) { -      hFile = -1; -    } -    isopen = false; -  } - -  // opens existing handle; use with care! -  this(HANDLE hFile, FileMode mode) { -    super(); -    this.hFile = hFile; -    readable = cast(bool)(mode & FileMode.In); -    writeable = cast(bool)(mode & FileMode.Out); -    version(Windows) { -      seekable = GetFileType(hFile) == 1; // FILE_TYPE_DISK -    } else { -      auto result = lseek(hFile, 0, 0); -      seekable = (result != ~0); -    } -  } - -  /*** -   * Create the stream with no open file, an open file in read mode, or an open -   * file with explicit file mode. -   * mode, if given, is a combination of FileMode.In -   * (indicating a file that can be read) and FileMode.Out (indicating a file -   * that can be written). -   * Opening a file for reading that doesn't exist will error. -   * Opening a file for writing that doesn't exist will create the file. -   * The FileMode.OutNew mode will open the file for writing and reset the -   * length to zero. -   * The FileMode.Append mode will open the file for writing and move the -   * file position to the end of the file. -   */ -  this(string filename, FileMode mode = FileMode.In) -  { -      this(); -      open(filename, mode); -  } - - -  /*** -   * Open a file for the stream, in an identical manner to the constructors. -   * If an error occurs an OpenException is thrown. -   */ -  void open(string filename, FileMode mode = FileMode.In) { -    close(); -    int access, share, createMode; -    parseMode(mode, access, share, createMode); -    seekable = true; -    readable = cast(bool)(mode & FileMode.In); -    writeable = cast(bool)(mode & FileMode.Out); -    version (Windows) { -      hFile = CreateFileW(filename.tempCStringW(), access, share, -                          null, createMode, 0, null); -      isopen = hFile != INVALID_HANDLE_VALUE; -    } -    version (Posix) { -      hFile = core.sys.posix.fcntl.open(filename.tempCString(), access | createMode, share); -      isopen = hFile != -1; -    } -    if (!isopen) -      throw new OpenException(cast(string) ("Cannot open or create file '" -                                            ~ filename ~ "'")); -    else if ((mode & FileMode.Append) == FileMode.Append) -      seekEnd(0); -  } - -  private void parseMode(int mode, -                         out int access, -                         out int share, -                         out int createMode) { -    version (Windows) { -      share |= FILE_SHARE_READ | FILE_SHARE_WRITE; -      if (mode & FileMode.In) { -        access |= GENERIC_READ; -        createMode = OPEN_EXISTING; -      } -      if (mode & FileMode.Out) { -        access |= GENERIC_WRITE; -        createMode = OPEN_ALWAYS; // will create if not present -      } -      if ((mode & FileMode.OutNew) == FileMode.OutNew) { -        createMode = CREATE_ALWAYS; // resets file -      } -    } -    version (Posix) { -      share = octal!666; -      if (mode & FileMode.In) { -        access = O_RDONLY; -      } -      if (mode & FileMode.Out) { -        createMode = O_CREAT; // will create if not present -        access = O_WRONLY; -      } -      if (access == (O_WRONLY | O_RDONLY)) { -        access = O_RDWR; -      } -      if ((mode & FileMode.OutNew) == FileMode.OutNew) { -        access |= O_TRUNC; // resets file -      } -    } -  } - -  /// Create a file for writing. -  void create(string filename) { -    create(filename, FileMode.OutNew); -  } - -  /// ditto -  void create(string filename, FileMode mode) { -    close(); -    open(filename, mode | FileMode.OutNew); -  } - -  /// Close the current file if it is open; otherwise it does nothing. -  override void close() { -    if (isopen) { -      super.close(); -      if (hFile) { -        version (Windows) { -          CloseHandle(hFile); -          hFile = null; -        } else version (Posix) { -          core.sys.posix.unistd.close(hFile); -          hFile = -1; -        } -      } -    } -  } - -  // destructor, closes file if still opened -  ~this() { close(); } - -  version (Windows) { -    // returns size of stream -    override @property ulong size() { -      assertSeekable(); -      uint sizehi; -      uint sizelow = GetFileSize(hFile,&sizehi); -      return (cast(ulong)sizehi << 32) + sizelow; -    } -  } - -  override size_t readBlock(void* buffer, size_t size) { -    assertReadable(); -    version (Windows) { -      auto dwSize = to!DWORD(size); -      ReadFile(hFile, buffer, dwSize, &dwSize, null); -      size = dwSize; -    } else version (Posix) { -      size = core.sys.posix.unistd.read(hFile, buffer, size); -      if (size == -1) -        size = 0; -    } -    readEOF = (size == 0); -    return size; -  } - -  override size_t writeBlock(const void* buffer, size_t size) { -    assertWriteable(); -    version (Windows) { -      auto dwSize = to!DWORD(size); -      WriteFile(hFile, buffer, dwSize, &dwSize, null); -      size = dwSize; -    } else version (Posix) { -      size = core.sys.posix.unistd.write(hFile, buffer, size); -      if (size == -1) -        size = 0; -    } -    return size; -  } - -  override ulong seek(long offset, SeekPos rel) { -    assertSeekable(); -    version (Windows) { -      int hi = cast(int)(offset>>32); -      uint low = SetFilePointer(hFile, cast(int)offset, &hi, rel); -      if ((low == INVALID_SET_FILE_POINTER) && (GetLastError() != 0)) -        throw new SeekException("unable to move file pointer"); -      ulong result = (cast(ulong)hi << 32) + low; -    } else version (Posix) { -      auto result = lseek(hFile, cast(off_t)offset, rel); -      if (result == cast(typeof(result))-1) -        throw new SeekException("unable to move file pointer"); -    } -    readEOF = false; -    return cast(ulong)result; -  } - -  /*** -   * For a seekable file returns the difference of the size and position and -   * otherwise returns 0. -   */ - -  override @property size_t available() { -    if (seekable) { -      ulong lavail = size - position; -      if (lavail > size_t.max) lavail = size_t.max; -      return cast(size_t)lavail; -    } -    return 0; -  } - -  // OS-specific property, just in case somebody wants -  // to mess with underlying API -  HANDLE handle() { return hFile; } - -  // run a few tests -  /+ -  unittest { -    import std.internal.cstring : tempCString; - -    File file = new File; -    int i = 666; -    auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$"; -    file.create(stream_file); -    // should be ok to write -    assert(file.writeable); -    file.writeLine("Testing stream.d:"); -    file.writeString("Hello, world!"); -    file.write(i); -    // string#1 + string#2 + int should give exacly that -    version (Windows) -      assert(file.position == 19 + 13 + 4); -    version (Posix) -      assert(file.position == 18 + 13 + 4); -    // we must be at the end of file -    assert(file.eof); -    file.close(); -    // no operations are allowed when file is closed -    assert(!file.readable && !file.writeable && !file.seekable); -    file.open(stream_file); -    // should be ok to read -    assert(file.readable); -    assert(file.available == file.size); -    char[] line = file.readLine(); -    char[] exp = "Testing stream.d:".dup; -    assert(line[0] == 'T'); -    assert(line.length == exp.length); -    assert(!std.algorithm.cmp(line, "Testing stream.d:")); -    // jump over "Hello, " -    file.seek(7, SeekPos.Current); -    version (Windows) -      assert(file.position == 19 + 7); -    version (Posix) -      assert(file.position == 18 + 7); -    assert(!std.algorithm.cmp(file.readString(6), "world!")); -    i = 0; file.read(i); -    assert(i == 666); -    // string#1 + string#2 + int should give exacly that -    version (Windows) -      assert(file.position == 19 + 13 + 4); -    version (Posix) -      assert(file.position == 18 + 13 + 4); -    // we must be at the end of file -    assert(file.eof); -    file.close(); -    file.open(stream_file,FileMode.OutNew | FileMode.In); -    file.writeLine("Testing stream.d:"); -    file.writeLine("Another line"); -    file.writeLine(""); -    file.writeLine("That was blank"); -    file.position = 0; -    char[][] lines; -    foreach(char[] line; file) { -      lines ~= line.dup; -    } -    assert( lines.length == 4 ); -    assert( lines[0] == "Testing stream.d:"); -    assert( lines[1] == "Another line"); -    assert( lines[2] == ""); -    assert( lines[3] == "That was blank"); -    file.position = 0; -    lines = new char[][4]; -    foreach(ulong n, char[] line; file) { -      lines[cast(size_t)(n-1)] = line.dup; -    } -    assert( lines[0] == "Testing stream.d:"); -    assert( lines[1] == "Another line"); -    assert( lines[2] == ""); -    assert( lines[3] == "That was blank"); -    file.close(); -    remove(stream_file.tempCString()); -  } -  +/ -} - -/*** - * This subclass is for buffered file system streams. - * - * It is a convenience class for wrapping a File in a BufferedStream. - * A buffered stream must be closed explicitly to ensure the final buffer - * content is written to the file. - */ -class BufferedFile: BufferedStream { - -  /// opens file for reading -  this() { super(new File()); } - -  /// opens file in requested mode and buffer size -  this(string filename, FileMode mode = FileMode.In, -       size_t bufferSize = DefaultBufferSize) { -    super(new File(filename,mode),bufferSize); -  } - -  /// opens file for reading with requested buffer size -  this(File file, size_t bufferSize = DefaultBufferSize) { -    super(file,bufferSize); -  } - -  /// opens existing handle; use with care! -  this(HANDLE hFile, FileMode mode, size_t buffersize = DefaultBufferSize) { -    super(new File(hFile,mode),buffersize); -  } - -  /// opens file in requested mode -  void open(string filename, FileMode mode = FileMode.In) { -    File sf = cast(File)s; -    sf.open(filename,mode); -    resetSource(); -  } - -  /// creates file in requested mode -  void create(string filename, FileMode mode = FileMode.OutNew) { -    File sf = cast(File)s; -    sf.create(filename,mode); -    resetSource(); -  } - -  // run a few tests same as File -  /+ -  unittest { -    import std.internal.cstring : tempCString; - -    BufferedFile file = new BufferedFile; -    int i = 666; -    auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$"; -    file.create(stream_file); -    // should be ok to write -    assert(file.writeable); -    file.writeLine("Testing stream.d:"); -    file.writeString("Hello, world!"); -    file.write(i); -    // string#1 + string#2 + int should give exacly that -    version (Windows) -      assert(file.position == 19 + 13 + 4); -    version (Posix) -      assert(file.position == 18 + 13 + 4); -    // we must be at the end of file -    assert(file.eof); -    long oldsize = cast(long)file.size; -    file.close(); -    // no operations are allowed when file is closed -    assert(!file.readable && !file.writeable && !file.seekable); -    file.open(stream_file); -    // should be ok to read -    assert(file.readable); -    // test getc/ungetc and size -    char c1 = file.getc(); -    file.ungetc(c1); -    assert( file.size == oldsize ); -    assert(!std.algorithm.cmp(file.readLine(), "Testing stream.d:")); -    // jump over "Hello, " -    file.seek(7, SeekPos.Current); -    version (Windows) -      assert(file.position == 19 + 7); -    version (Posix) -      assert(file.position == 18 + 7); -    assert(!std.algorithm.cmp(file.readString(6), "world!")); -    i = 0; file.read(i); -    assert(i == 666); -    // string#1 + string#2 + int should give exacly that -    version (Windows) -      assert(file.position == 19 + 13 + 4); -    version (Posix) -      assert(file.position == 18 + 13 + 4); -    // we must be at the end of file -    assert(file.eof); -    file.close(); -    remove(stream_file.tempCString()); -  } -  +/ - -} - -/// UTF byte-order-mark signatures -enum BOM { -        UTF8,           /// UTF-8 -        UTF16LE,        /// UTF-16 Little Endian -        UTF16BE,        /// UTF-16 Big Endian -        UTF32LE,        /// UTF-32 Little Endian -        UTF32BE,        /// UTF-32 Big Endian -} - -private enum int NBOMS = 5; -immutable Endian[NBOMS] BOMEndian = -[ std.system.endian, -  Endian.littleEndian, Endian.bigEndian, -  Endian.littleEndian, Endian.bigEndian -  ]; - -immutable ubyte[][NBOMS] ByteOrderMarks = -[ [0xEF, 0xBB, 0xBF], -  [0xFF, 0xFE], -  [0xFE, 0xFF], -  [0xFF, 0xFE, 0x00, 0x00], -  [0x00, 0x00, 0xFE, 0xFF] -  ]; - - -/*** - * This subclass wraps a stream with big-endian or little-endian byte order - * swapping. - * - * UTF Byte-Order-Mark (BOM) signatures can be read and deduced or - * written. - * Note that an EndianStream should not be used as the source of another - * FilterStream since a FilterStream call the source with byte-oriented - * read/write requests and the EndianStream will not perform any byte swapping. - * The EndianStream reads and writes binary data (non-getc functions) in a - * one-to-one - * manner with the source stream so the source stream's position and state will be - * kept in sync with the EndianStream if only non-getc functions are called. - */ -class EndianStream : FilterStream { - -  Endian endian;        /// Endianness property of the source stream. - -  /*** -   * Create the endian stream for the source stream source with endianness end. -   * The default endianness is the native byte order. -   * The Endian type is defined -   * in the std.system module. -   */ -  this(Stream source, Endian end = std.system.endian) { -    super(source); -    endian = end; -  } - -  /*** -   * Return -1 if no BOM and otherwise read the BOM and return it. -   * -   * If there is no BOM or if bytes beyond the BOM are read then the bytes read -   * are pushed back onto the ungetc buffer or ungetcw buffer. -   * Pass ungetCharSize == 2 to use -   * ungetcw instead of ungetc when no BOM is present. -   */ -  int readBOM(int ungetCharSize = 1) { -    ubyte[4] BOM_buffer; -    int n = 0;       // the number of read bytes -    int result = -1; // the last match or -1 -    for (int i=0; i < NBOMS; ++i) { -      int j; -      immutable ubyte[] bom = ByteOrderMarks[i]; -      for (j=0; j < bom.length; ++j) { -        if (n <= j) { // have to read more -          if (eof) -            break; -          readExact(&BOM_buffer[n++],1); -        } -        if (BOM_buffer[j] != bom[j]) -          break; -      } -      if (j == bom.length) // found a match -        result = i; -    } -    ptrdiff_t m = 0; -    if (result != -1) { -      endian = BOMEndian[result]; // set stream endianness -      m = ByteOrderMarks[result].length; -    } -    if ((ungetCharSize == 1 && result == -1) || (result == BOM.UTF8)) { -      while (n-- > m) -        ungetc(BOM_buffer[n]); -    } else { // should eventually support unget for dchar as well -      if (n & 1) // make sure we have an even number of bytes -        readExact(&BOM_buffer[n++],1); -      while (n > m) { -        n -= 2; -        wchar cw = *(cast(wchar*)&BOM_buffer[n]); -        fixBO(&cw,2); -        ungetcw(cw); -      } -    } -    return result; -  } - -  /*** -   * Correct the byte order of buffer to match native endianness. -   * size must be even. -   */ -  final void fixBO(const(void)* buffer, size_t size) { -    if (endian != std.system.endian) { -      ubyte* startb = cast(ubyte*)buffer; -      uint* start = cast(uint*)buffer; -      switch (size) { -      case 0: break; -      case 2: { -        ubyte x = *startb; -        *startb = *(startb+1); -        *(startb+1) = x; -        break; -      } -      case 4: { -        *start = bswap(*start); -        break; -      } -      default: { -        uint* end = cast(uint*)(buffer + size - uint.sizeof); -        while (start < end) { -          uint x = bswap(*start); -          *start = bswap(*end); -          *end = x; -          ++start; -          --end; -        } -        startb = cast(ubyte*)start; -        ubyte* endb = cast(ubyte*)end; -        auto len = uint.sizeof - (startb - endb); -        if (len > 0) -          fixBO(startb,len); -      } -      } -    } -  } - -  /*** -   * Correct the byte order of the given buffer in blocks of the given size and -   * repeated the given number of times. -   * size must be even. -   */ -  final void fixBlockBO(void* buffer, uint size, size_t repeat) { -    while (repeat--) { -      fixBO(buffer,size); -      buffer += size; -    } -  } - -  override void read(out byte x) { readExact(&x, x.sizeof); } -  override void read(out ubyte x) { readExact(&x, x.sizeof); } -  override void read(out short x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out ushort x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out int x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out uint x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out long x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out ulong x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out float x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out double x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out real x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out ifloat x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out idouble x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out ireal x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out cfloat x) { readExact(&x, x.sizeof); fixBlockBO(&x,float.sizeof,2); } -  override void read(out cdouble x) { readExact(&x, x.sizeof); fixBlockBO(&x,double.sizeof,2); } -  override void read(out creal x) { readExact(&x, x.sizeof); fixBlockBO(&x,real.sizeof,2); } -  override void read(out char x) { readExact(&x, x.sizeof); } -  override void read(out wchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } -  override void read(out dchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - -  override wchar getcw() { -    wchar c; -    if (prevCr) { -      prevCr = false; -      c = getcw(); -      if (c != '\n') -        return c; -    } -    if (unget.length > 1) { -      c = unget[unget.length - 1]; -      unget.length = unget.length - 1; -    } else { -      void* buf = &c; -      size_t n = readBlock(buf,2); -      if (n == 1 && readBlock(buf+1,1) == 0) -          throw new ReadException("not enough data in stream"); -      fixBO(&c,c.sizeof); -    } -    return c; -  } - -  override wchar[] readStringW(size_t length) { -    wchar[] result = new wchar[length]; -    readExact(result.ptr, length * wchar.sizeof); -    fixBlockBO(result.ptr, wchar.sizeof, length); -    return result; -  } - -  /// Write the specified BOM b to the source stream. -  void writeBOM(BOM b) { -    immutable ubyte[] bom = ByteOrderMarks[b]; -    writeBlock(bom.ptr, bom.length); -  } - -  override void write(byte x) { writeExact(&x, x.sizeof); } -  override void write(ubyte x) { writeExact(&x, x.sizeof); } -  override void write(short x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(ushort x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(int x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(uint x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(long x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(ulong x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(float x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(double x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(real x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(ifloat x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(idouble x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(ireal x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(cfloat x) { fixBlockBO(&x,float.sizeof,2); writeExact(&x, x.sizeof); } -  override void write(cdouble x) { fixBlockBO(&x,double.sizeof,2); writeExact(&x, x.sizeof); } -  override void write(creal x) { fixBlockBO(&x,real.sizeof,2); writeExact(&x, x.sizeof);  } -  override void write(char x) { writeExact(&x, x.sizeof); } -  override void write(wchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } -  override void write(dchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - -  override void writeStringW(const(wchar)[] str) { -    foreach(wchar cw;str) { -      fixBO(&cw,2); -      s.writeExact(&cw, 2); -    } -  } - -  override @property bool eof() { return s.eof && !ungetAvailable();  } -  override @property ulong size() { return s.size;  } - -  unittest { -    MemoryStream m; -    m = new MemoryStream (); -    EndianStream em = new EndianStream(m,Endian.bigEndian); -    uint x = 0x11223344; -    em.write(x); -    assert( m.data[0] == 0x11 ); -    assert( m.data[1] == 0x22 ); -    assert( m.data[2] == 0x33 ); -    assert( m.data[3] == 0x44 ); -    em.position = 0; -    ushort x2 = 0x5566; -    em.write(x2); -    assert( m.data[0] == 0x55 ); -    assert( m.data[1] == 0x66 ); -    em.position = 0; -    static ubyte[12] x3 = [1,2,3,4,5,6,7,8,9,10,11,12]; -    em.fixBO(x3.ptr,12); -    if (std.system.endian == Endian.littleEndian) { -      assert( x3[0] == 12 ); -      assert( x3[1] == 11 ); -      assert( x3[2] == 10 ); -      assert( x3[4] == 8 ); -      assert( x3[5] == 7 ); -      assert( x3[6] == 6 ); -      assert( x3[8] == 4 ); -      assert( x3[9] == 3 ); -      assert( x3[10] == 2 ); -      assert( x3[11] == 1 ); -    } -    em.endian = Endian.littleEndian; -    em.write(x); -    assert( m.data[0] == 0x44 ); -    assert( m.data[1] == 0x33 ); -    assert( m.data[2] == 0x22 ); -    assert( m.data[3] == 0x11 ); -    em.position = 0; -    em.write(x2); -    assert( m.data[0] == 0x66 ); -    assert( m.data[1] == 0x55 ); -    em.position = 0; -    em.fixBO(x3.ptr,12); -    if (std.system.endian == Endian.bigEndian) { -      assert( x3[0] == 12 ); -      assert( x3[1] == 11 ); -      assert( x3[2] == 10 ); -      assert( x3[4] == 8 ); -      assert( x3[5] == 7 ); -      assert( x3[6] == 6 ); -      assert( x3[8] == 4 ); -      assert( x3[9] == 3 ); -      assert( x3[10] == 2 ); -      assert( x3[11] == 1 ); -    } -    em.writeBOM(BOM.UTF8); -    assert( m.position == 3 ); -    assert( m.data[0] == 0xEF ); -    assert( m.data[1] == 0xBB ); -    assert( m.data[2] == 0xBF ); -    em.writeString ("Hello, world"); -    em.position = 0; -    assert( m.position == 0 ); -    assert( em.readBOM() == BOM.UTF8 ); -    assert( m.position == 3 ); -    assert( em.getc() == 'H' ); -    em.position = 0; -    em.writeBOM(BOM.UTF16BE); -    assert( m.data[0] == 0xFE ); -    assert( m.data[1] == 0xFF ); -    em.position = 0; -    em.writeBOM(BOM.UTF16LE); -    assert( m.data[0] == 0xFF ); -    assert( m.data[1] == 0xFE ); -    em.position = 0; -    em.writeString ("Hello, world"); -    em.position = 0; -    assert( em.readBOM() == -1 ); -    assert( em.getc() == 'H' ); -    assert( em.getc() == 'e' ); -    assert( em.getc() == 'l' ); -    assert( em.getc() == 'l' ); -    em.position = 0; -  } -} - -/*** - * Parameterized subclass that wraps an array-like buffer with a stream - * interface. - * - * The type Buffer must support the length property, opIndex and opSlice. - * Compile in release mode when directly instantiating a TArrayStream to avoid - * link errors. - */ -class TArrayStream(Buffer): Stream { -  Buffer buf; // current data -  ulong len;  // current data length -  ulong cur;  // current file position - -  /// Create the stream for the the buffer buf. Non-copying. -  this(Buffer buf) { -    super (); -    this.buf = buf; -    this.len = buf.length; -    readable = writeable = seekable = true; -  } - -  // ensure subclasses don't violate this -  invariant() { -    assert(len <= buf.length); -    assert(cur <= len); -  } - -  override size_t readBlock(void* buffer, size_t size) { -    assertReadable(); -    ubyte* cbuf = cast(ubyte*) buffer; -    if (len - cur < size) -      size = cast(size_t)(len - cur); -    ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)]; -    cbuf[0 .. size] = ubuf[]; -    cur += size; -    return size; -  } - -  override size_t writeBlock(const void* buffer, size_t size) { -    assertWriteable(); -    ubyte* cbuf = cast(ubyte*) buffer; -    ulong blen = buf.length; -    if (cur + size > blen) -      size = cast(size_t)(blen - cur); -    ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)]; -    ubuf[] = cbuf[0 .. size]; -    cur += size; -    if (cur > len) -      len = cur; -    return size; -  } - -  override ulong seek(long offset, SeekPos rel) { -    assertSeekable(); -    long scur; // signed to saturate to 0 properly - -    switch (rel) { -    case SeekPos.Set: scur = offset; break; -    case SeekPos.Current: scur = cast(long)(cur + offset); break; -    case SeekPos.End: scur = cast(long)(len + offset); break; -    default: -        assert(0); -    } - -    if (scur < 0) -      cur = 0; -    else if (scur > len) -      cur = len; -    else -      cur = cast(ulong)scur; - -    return cur; -  } - -  override @property size_t available () { return cast(size_t)(len - cur); } - -  /// Get the current memory data in total. -  @property ubyte[] data() { -    if (len > size_t.max) -      throw new StreamException("Stream too big"); -    const(void)[] res = buf[0 .. cast(size_t)len]; -    return cast(ubyte[])res; -  } - -  override string toString() { -      // assume data is UTF8 -      return to!(string)(cast(char[])data); -  } -} - -/* Test the TArrayStream */ -unittest { -  char[100] buf; -  TArrayStream!(char[]) m; - -  m = new TArrayStream!(char[]) (buf); -  assert (m.isOpen); -  m.writeString ("Hello, world"); -  assert (m.position == 12); -  assert (m.available == 88); -  assert (m.seekSet (0) == 0); -  assert (m.available == 100); -  assert (m.seekCur (4) == 4); -  assert (m.available == 96); -  assert (m.seekEnd (-8) == 92); -  assert (m.available == 8); -  assert (m.size == 100); -  assert (m.seekSet (4) == 4); -  assert (m.readString (4) == "o, w"); -  m.writeString ("ie"); -  assert (buf[0..12] == "Hello, wield"); -  assert (m.position == 10); -  assert (m.available == 90); -  assert (m.size == 100); -  m.seekSet (0); -  assert (m.printf ("Answer is %d", 42) == 12); -  assert (buf[0..12] == "Answer is 42"); -} - -/// This subclass reads and constructs an array of bytes in memory. -class MemoryStream: TArrayStream!(ubyte[]) { - -  /// Create the output buffer and setup for reading, writing, and seeking. -  // clear to an empty buffer. -  this() { this(cast(ubyte[]) null); } - -  /*** -   * Create the output buffer and setup for reading, writing, and seeking. -   * Load it with specific input data. -   */ -  this(ubyte[] buf) { super (buf); } -  this(byte[] buf) { this(cast(ubyte[]) buf); } /// ditto -  this(char[] buf) { this(cast(ubyte[]) buf); } /// ditto - -  /// Ensure the stream can write count extra bytes from cursor position without an allocation. -  void reserve(size_t count) { -    if (cur + count > buf.length) -      buf.length = cast(uint)((cur + count) * 2); -  } - -  override size_t writeBlock(const void* buffer, size_t size) { -    reserve(size); -    return super.writeBlock(buffer,size); -  } - -  unittest { -    MemoryStream m; - -    m = new MemoryStream (); -    assert (m.isOpen); -    m.writeString ("Hello, world"); -    assert (m.position == 12); -    assert (m.seekSet (0) == 0); -    assert (m.available == 12); -    assert (m.seekCur (4) == 4); -    assert (m.available == 8); -    assert (m.seekEnd (-8) == 4); -    assert (m.available == 8); -    assert (m.size == 12); -    assert (m.readString (4) == "o, w"); -    m.writeString ("ie"); -    assert (cast(char[]) m.data == "Hello, wield"); -    m.seekEnd (0); -    m.writeString ("Foo"); -    assert (m.position == 15); -    assert (m.available == 0); -    m.writeString ("Foo foo foo foo foo foo foo"); -    assert (m.position == 42); -    m.position = 0; -    assert (m.available == 42); -    m.writef("%d %d %s",100,345,"hello"); -    auto str = m.toString(); -    assert (str[0..13] == "100 345 hello", str[0 .. 13]); -    assert (m.available == 29); -    assert (m.position == 13); - -    MemoryStream m2; -    m.position = 3; -    m2 = new MemoryStream (); -    m2.writeString("before"); -    m2.copyFrom(m,10); -    str = m2.toString(); -    assert (str[0..16] == "before 345 hello"); -    m2.position = 3; -    m2.copyFrom(m); -    auto str2 = m.toString(); -    str = m2.toString(); -    assert (str == ("bef" ~ str2)); -  } -} - -import std.mmfile; - -/*** - * This subclass wraps a memory-mapped file with the stream API. - * See std.mmfile module. - */ -class MmFileStream : TArrayStream!(MmFile) { - -  /// Create stream wrapper for file. -  this(MmFile file) { -    super (file); -    MmFile.Mode mode = file.mode(); -    writeable = mode > MmFile.Mode.read; -  } - -  override void flush() { -    if (isopen) { -      super.flush(); -      buf.flush(); -    } -  } - -  override void close() { -    if (isopen) { -      super.close(); -      delete buf; -      buf = null; -    } -  } -} - -unittest { -  auto test_file = undead.internal.file.deleteme ~ "-testing.txt"; -  MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew,100,null); -  MmFileStream m; -  m = new MmFileStream (mf); -  m.writeString ("Hello, world"); -  assert (m.position == 12); -  assert (m.seekSet (0) == 0); -  assert (m.seekCur (4) == 4); -  assert (m.seekEnd (-8) == 92); -  assert (m.size == 100); -  assert (m.seekSet (4)); -  assert (m.readString (4) == "o, w"); -  m.writeString ("ie"); -  ubyte[] dd = m.data; -  assert ((cast(char[]) dd)[0 .. 12] == "Hello, wield"); -  m.position = 12; -  m.writeString ("Foo"); -  assert (m.position == 15); -  m.writeString ("Foo foo foo foo foo foo foo"); -  assert (m.position == 42); -  m.close(); -  mf = new MmFile(test_file); -  m = new MmFileStream (mf); -  assert (!m.writeable); -  char[] str = m.readString(12); -  assert (str == "Hello, wield"); -  m.close(); -  std.file.remove(test_file); -} - - -/*** - * This subclass slices off a portion of another stream, making seeking relative - * to the boundaries of the slice. - * - * It could be used to section a large file into a - * set of smaller files, such as with tar archives. Reading and writing a - * SliceStream does not modify the position of the source stream if it is - * seekable. - */ -class SliceStream : FilterStream { -  private { -    ulong pos;  // our position relative to low -    ulong low; // low stream offset. -    ulong high; // high stream offset. -    bool bounded; // upper-bounded by high. -  } - -  /*** -   * Indicate both the source stream to use for reading from and the low part of -   * the slice. -   * -   * The high part of the slice is dependent upon the end of the source -   * stream, so that if you write beyond the end it resizes the stream normally. -   */ -  this (Stream s, ulong low) -  in { -    assert (low <= s.size); -  } -  body { -    super(s); -    this.low = low; -    this.high = 0; -    this.bounded = false; -  } - -  /*** -   * Indicate the high index as well. -   * -   * Attempting to read or write past the high -   * index results in the end being clipped off. -   */ -  this (Stream s, ulong low, ulong high) -  in { -    assert (low <= high); -    assert (high <= s.size); -  } -  body { -    super(s); -    this.low = low; -    this.high = high; -    this.bounded = true; -  } - -  invariant() { -    if (bounded) -      assert (pos <= high - low); -    else -      // size() does not appear to be const, though it should be -      assert (pos <= (cast()s).size - low); -  } - -  override size_t readBlock (void *buffer, size_t size) { -    assertReadable(); -    if (bounded && size > high - low - pos) -        size = cast(size_t)(high - low - pos); -    ulong bp = s.position; -    if (seekable) -      s.position = low + pos; -    size_t ret = super.readBlock(buffer, size); -    if (seekable) { -      pos = s.position - low; -      s.position = bp; -    } -    return ret; -  } - -  override size_t writeBlock (const void *buffer, size_t size) { -    assertWriteable(); -    if (bounded && size > high - low - pos) -        size = cast(size_t)(high - low - pos); -    ulong bp = s.position; -    if (seekable) -      s.position = low + pos; -    size_t ret = s.writeBlock(buffer, size); -    if (seekable) { -      pos = s.position - low; -      s.position = bp; -    } -    return ret; -  } - -  override ulong seek(long offset, SeekPos rel) { -    assertSeekable(); -    long spos; - -    switch (rel) { -      case SeekPos.Set: -        spos = offset; -        break; -      case SeekPos.Current: -        spos = cast(long)(pos + offset); -        break; -      case SeekPos.End: -        if (bounded) -          spos = cast(long)(high - low + offset); -        else -          spos = cast(long)(s.size - low + offset); -        break; -      default: -        assert(0); -    } - -    if (spos < 0) -      pos = 0; -    else if (bounded && spos > high - low) -      pos = high - low; -    else if (!bounded && spos > s.size - low) -      pos = s.size - low; -    else -      pos = cast(ulong)spos; - -    readEOF = false; -    return pos; -  } - -  override @property size_t available() { -    size_t res = s.available; -    ulong bp = s.position; -    if (bp <= pos+low && pos+low <= bp+res) { -      if (!bounded || bp+res <= high) -        return cast(size_t)(bp + res - pos - low); -      else if (high <= bp+res) -        return cast(size_t)(high - pos - low); -    } -    return 0; -  } - -  unittest { -    MemoryStream m; -    SliceStream s; - -    m = new MemoryStream ((cast(char[])"Hello, world").dup); -    s = new SliceStream (m, 4, 8); -    assert (s.size == 4); -    assert (m.position == 0); -    assert (s.position == 0); -    assert (m.available == 12); -    assert (s.available == 4); - -    assert (s.writeBlock (cast(char *) "Vroom", 5) == 4); -    assert (m.position == 0); -    assert (s.position == 4); -    assert (m.available == 12); -    assert (s.available == 0); -    assert (s.seekEnd (-2) == 2); -    assert (s.available == 2); -    assert (s.seekEnd (2) == 4); -    assert (s.available == 0); -    assert (m.position == 0); -    assert (m.available == 12); - -    m.seekEnd(0); -    m.writeString("\nBlaho"); -    assert (m.position == 18); -    assert (m.available == 0); -    assert (s.position == 4); -    assert (s.available == 0); - -    s = new SliceStream (m, 4); -    assert (s.size == 14); -    assert (s.toString () == "Vrooorld\nBlaho"); -    s.seekEnd (0); -    assert (s.available == 0); - -    s.writeString (", etcetera."); -    assert (s.position == 25); -    assert (s.seekSet (0) == 0); -    assert (s.size == 25); -    assert (m.position == 18); -    assert (m.size == 29); -    assert (m.toString() == "HellVrooorld\nBlaho, etcetera."); -  } -}  | 
