From ba1712e77b31704fd9ba16d14e15518e7a7dd104 Mon Sep 17 00:00:00 2001
From: Ralph Amissah <ralph@amissah.com>
Date: Sat, 1 Oct 2016 14:12:13 -0400
Subject: 0.7.0 using dub remote dependencies (local src related to sdlang
 removed)

---
 src/sdlang/ast.d                             | 2945 ------------------------
 src/sdlang/dub.json                          |   38 -
 src/sdlang/exception.d                       |  190 --
 src/sdlang/lexer.d                           | 2068 -----------------
 src/sdlang/libinputvisitor/dub.json          |   10 -
 src/sdlang/libinputvisitor/libInputVisitor.d |  113 -
 src/sdlang/package.d                         |  133 --
 src/sdlang/parser.d                          |  628 ------
 src/sdlang/symbol.d                          |   61 -
 src/sdlang/taggedalgebraic/taggedalgebraic.d | 1085 ---------
 src/sdlang/token.d                           |  550 -----
 src/sdlang/util.d                            |  200 --
 src/sdp/ao_abstract_doc_source.d             |   32 +-
 src/sdp/ao_ansi_colors.d                     |    5 +-
 src/sdp/ao_conf_make_meta.d                  |   11 +-
 src/sdp/ao_conf_make_meta_native.d           |    3 +-
 src/sdp/ao_conf_make_meta_sdlang.d           |    5 +-
 src/sdp/ao_defaults.d                        |    5 +-
 src/sdp/ao_object_setter.d                   |    5 +-
 src/sdp/ao_output_debugs.d                   |    3 +-
 src/sdp/ao_read_config_files.d               |    5 +-
 src/sdp/ao_read_source_files.d               |    7 +-
 src/sdp/ao_rgx.d                             |    5 +-
 src/sdp/compile_time_info.d                  |    3 +-
 src/sdp/output_hub.d                         |    6 +-
 src/undead/doformat.d                        | 1620 --------------
 src/undead/internal/file.d                   |   25 -
 src/undead/stream.d                          | 3076 --------------------------
 28 files changed, 41 insertions(+), 12796 deletions(-)
 delete mode 100644 src/sdlang/ast.d
 delete mode 100644 src/sdlang/dub.json
 delete mode 100644 src/sdlang/exception.d
 delete mode 100644 src/sdlang/lexer.d
 delete mode 100644 src/sdlang/libinputvisitor/dub.json
 delete mode 100644 src/sdlang/libinputvisitor/libInputVisitor.d
 delete mode 100644 src/sdlang/package.d
 delete mode 100644 src/sdlang/parser.d
 delete mode 100644 src/sdlang/symbol.d
 delete mode 100644 src/sdlang/taggedalgebraic/taggedalgebraic.d
 delete mode 100644 src/sdlang/token.d
 delete mode 100644 src/sdlang/util.d
 delete mode 100644 src/undead/doformat.d
 delete mode 100644 src/undead/internal/file.d
 delete mode 100644 src/undead/stream.d

(limited to 'src')

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;
@@ -2858,7 +2842,6 @@ template SiSUdocAbstraction() {
         ++obj_cite_number;
         ++mkn;
         foreach (endnote; endnotes_) {
-          attrib="";
           attrib="";
           // endnotes ~=
           //   set_abstract_object.contents_para(
@@ -2957,7 +2940,6 @@ template SiSUdocAbstraction() {
       }
     }
     struct NodeStructureMetadata {
-    // class NodeStructureMetadata : AssertNodeJSON {
       int lv, lv0, lv1, lv2, lv3, lv4, lv5, lv6, lv7;
       int obj_cite_number;
       int[string] p_; // p_ parent_
diff --git a/src/sdp/ao_ansi_colors.d b/src/sdp/ao_ansi_colors.d
index e5a46f9..dea331d 100644
--- a/src/sdp/ao_ansi_colors.d
+++ b/src/sdp/ao_ansi_colors.d
@@ -1,6 +1,5 @@
-/+
-  utils
-  ao_util.d
+/++
+  ansi colors, depreciate use
 +/
 template ScreenTxtColors() {
   string[string] scr_txt_color = [
diff --git a/src/sdp/ao_conf_make_meta.d b/src/sdp/ao_conf_make_meta.d
index 04a9d7a..5bc9694 100644
--- a/src/sdp/ao_conf_make_meta.d
+++ b/src/sdp/ao_conf_make_meta.d
@@ -1,5 +1,12 @@
-/+
-  extract native/orig header return associative array
+/++
+  extract native/orig header return associative array<BR>
+
+  the header is passed as text (lopped off top of a sisu markup file until the
+  required first heading ^A~), determine whether is a native header or sdlang one
+  with a regex check if whether it contains the "native header" required tag/field
+  @title: then process accordingly as a "native header" or "sdlang header"
+  converting the metadata and make instructions to a common json format used by
+  program internally. Moved to associative array.
 +/
 template SiSUheaderExtractHub() {
   private import
diff --git a/src/sdp/ao_conf_make_meta_native.d b/src/sdp/ao_conf_make_meta_native.d
index 9f0ad63..f70a7bf 100644
--- a/src/sdp/ao_conf_make_meta_native.d
+++ b/src/sdp/ao_conf_make_meta_native.d
@@ -1,4 +1,5 @@
-/+
+/++
+  native headers using<br>@title:<BR>:subtitle:<BR>type tags<BR>
   extract native/orig header return associative array
 +/
 template SiSUheaderExtractNative() {
diff --git a/src/sdp/ao_conf_make_meta_sdlang.d b/src/sdp/ao_conf_make_meta_sdlang.d
index 1cc3498..61b4960 100644
--- a/src/sdp/ao_conf_make_meta_sdlang.d
+++ b/src/sdp/ao_conf_make_meta_sdlang.d
@@ -1,5 +1,6 @@
-/+
-  extract sdl header return sdl
+/++
+  sdlang headers<BR>
+  extract sdlang header return sdlang
 +/
 template SiSUheaderExtractSDLang() {
   private import
diff --git a/src/sdp/ao_defaults.d b/src/sdp/ao_defaults.d
index ea5caae..8db42e2 100644
--- a/src/sdp/ao_defaults.d
+++ b/src/sdp/ao_defaults.d
@@ -1,6 +1,5 @@
-/+
-  defaults
-  ao_defaults.d
+/++
+  default settings
 +/
 template SiSUregisters() {
   string[string][string] conf_aa() {
diff --git a/src/sdp/ao_object_setter.d b/src/sdp/ao_object_setter.d
index 745de4e..6cb359b 100644
--- a/src/sdp/ao_object_setter.d
+++ b/src/sdp/ao_object_setter.d
@@ -1,5 +1,6 @@
-/+
-  object setter
+/++
+  object setter:
+  setting of sisu objects for downstream processing
   ao_object_setter.d
 +/
 template ObjectSetter() {
diff --git a/src/sdp/ao_output_debugs.d b/src/sdp/ao_output_debugs.d
index b5f96fa..9111cd6 100644
--- a/src/sdp/ao_output_debugs.d
+++ b/src/sdp/ao_output_debugs.d
@@ -1,6 +1,5 @@
-/+
+/++
   output debugs
-  ao_output_debugs.d
 +/
 template SiSUoutputDebugs() {
   struct BookIndexReport {
diff --git a/src/sdp/ao_read_config_files.d b/src/sdp/ao_read_config_files.d
index 49efe7b..013acdd 100644
--- a/src/sdp/ao_read_config_files.d
+++ b/src/sdp/ao_read_config_files.d
@@ -1,6 +1,7 @@
-/+
+/++
+  read configuration files<BR>
+  - read config files<BR>
   ao_config_files.d
-  - read config files
 +/
 template SiSUconfigIn() {
   private import
diff --git a/src/sdp/ao_read_source_files.d b/src/sdp/ao_read_source_files.d
index eabc4dc..5aef05d 100644
--- a/src/sdp/ao_read_source_files.d
+++ b/src/sdp/ao_read_source_files.d
@@ -1,9 +1,8 @@
-/+
-  ao_read_source_files.d
-  - open markup files
+/++
+  module ao_read_source_files;<BR>
+  - open markup files<BR>
   - if master file scan for addional files to import/insert
 +/
-// module ao_read_source_files;
 template SiSUmarkupRaw() {
   private import
     std.exception,
diff --git a/src/sdp/ao_rgx.d b/src/sdp/ao_rgx.d
index ccaf1bd..2a10d53 100644
--- a/src/sdp/ao_rgx.d
+++ b/src/sdp/ao_rgx.d
@@ -1,6 +1,5 @@
-/+
-  regex
-  ao_rgx.d
+/++
+  regex: regular expressions used in sisu document parser
 +/
 template RgxInit() {
   struct Rgx {
diff --git a/src/sdp/compile_time_info.d b/src/sdp/compile_time_info.d
index 783ac62..2b0151d 100644
--- a/src/sdp/compile_time_info.d
+++ b/src/sdp/compile_time_info.d
@@ -1,6 +1,5 @@
-/+
+/++
   compile_time_info
-  compile_time_info.d
 +/
 template CompileTimeInfo() {
   version(Windows) {
diff --git a/src/sdp/output_hub.d b/src/sdp/output_hub.d
index 0206bf5..e7c0c9e 100644
--- a/src/sdp/output_hub.d
+++ b/src/sdp/output_hub.d
@@ -1,6 +1,6 @@
-/+
-  output_hub.d
-  output_html.d
+/++
+  output hub<BR>
+  check & generate output types requested
 +/
 template SiSUoutputHub() {
   struct SDPoutput {
diff --git a/src/undead/doformat.d b/src/undead/doformat.d
deleted file mode 100644
index 4fc0daf..0000000
--- a/src/undead/doformat.d
+++ /dev/null
@@ -1,1620 +0,0 @@
-// Written in the D programming language.
-
-/**
-   Copyright: Copyright Digital Mars 2000-2013.
-
-   License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
-
-   Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
-   Andrei Alexandrescu), and Kenji Hara
-
-   Source: $(PHOBOSSRC std/_format.d)
- */
-module undead.doformat;
-
-//debug=format;                // uncomment to turn on debugging printf's
-
-import core.vararg;
-import std.exception;
-import std.meta;
-import std.range.primitives;
-import std.traits;
-import std.format;
-
-version(CRuntime_DigitalMars)
-{
-    version = DigitalMarsC;
-}
-
-version (DigitalMarsC)
-{
-    // This is DMC's internal floating point formatting function
-    extern (C)
-    {
-        extern shared char* function(int c, int flags, int precision,
-                in real* pdval,
-                char* buf, size_t* psl, int width) __pfloatfmt;
-    }
-}
-
-/**********************************************************************
- * Signals a mismatch between a format and its corresponding argument.
- */
-class FormatException : Exception
-{
-    @safe pure nothrow
-    this()
-    {
-        super("format error");
-    }
-
-    @safe pure nothrow
-    this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null)
-    {
-        super(msg, fn, ln, next);
-    }
-}
-
-
-// Legacy implementation
-
-enum Mangle : char
-{
-    Tvoid     = 'v',
-    Tbool     = 'b',
-    Tbyte     = 'g',
-    Tubyte    = 'h',
-    Tshort    = 's',
-    Tushort   = 't',
-    Tint      = 'i',
-    Tuint     = 'k',
-    Tlong     = 'l',
-    Tulong    = 'm',
-    Tfloat    = 'f',
-    Tdouble   = 'd',
-    Treal     = 'e',
-
-    Tifloat   = 'o',
-    Tidouble  = 'p',
-    Tireal    = 'j',
-    Tcfloat   = 'q',
-    Tcdouble  = 'r',
-    Tcreal    = 'c',
-
-    Tchar     = 'a',
-    Twchar    = 'u',
-    Tdchar    = 'w',
-
-    Tarray    = 'A',
-    Tsarray   = 'G',
-    Taarray   = 'H',
-    Tpointer  = 'P',
-    Tfunction = 'F',
-    Tident    = 'I',
-    Tclass    = 'C',
-    Tstruct   = 'S',
-    Tenum     = 'E',
-    Ttypedef  = 'T',
-    Tdelegate = 'D',
-
-    Tconst    = 'x',
-    Timmutable = 'y',
-}
-
-// return the TypeInfo for a primitive type and null otherwise.  This
-// is required since for arrays of ints we only have the mangled char
-// to work from. If arrays always subclassed TypeInfo_Array this
-// routine could go away.
-private TypeInfo primitiveTypeInfo(Mangle m)
-{
-    // BUG: should fix this in static this() to avoid double checked locking bug
-    __gshared TypeInfo[Mangle] dic;
-    if (!dic.length)
-    {
-        dic = [
-            Mangle.Tvoid : typeid(void),
-            Mangle.Tbool : typeid(bool),
-            Mangle.Tbyte : typeid(byte),
-            Mangle.Tubyte : typeid(ubyte),
-            Mangle.Tshort : typeid(short),
-            Mangle.Tushort : typeid(ushort),
-            Mangle.Tint : typeid(int),
-            Mangle.Tuint : typeid(uint),
-            Mangle.Tlong : typeid(long),
-            Mangle.Tulong : typeid(ulong),
-            Mangle.Tfloat : typeid(float),
-            Mangle.Tdouble : typeid(double),
-            Mangle.Treal : typeid(real),
-            Mangle.Tifloat : typeid(ifloat),
-            Mangle.Tidouble : typeid(idouble),
-            Mangle.Tireal : typeid(ireal),
-            Mangle.Tcfloat : typeid(cfloat),
-            Mangle.Tcdouble : typeid(cdouble),
-            Mangle.Tcreal : typeid(creal),
-            Mangle.Tchar : typeid(char),
-            Mangle.Twchar : typeid(wchar),
-            Mangle.Tdchar : typeid(dchar)
-            ];
-    }
-    auto p = m in dic;
-    return p ? *p : null;
-}
-
-// This stuff has been removed from the docs and is planned for deprecation.
-/*
- * Interprets variadic argument list pointed to by argptr whose types
- * are given by arguments[], formats them according to embedded format
- * strings in the variadic argument list, and sends the resulting
- * characters to putc.
- *
- * The variadic arguments are consumed in order.  Each is formatted
- * into a sequence of chars, using the default format specification
- * for its type, and the characters are sequentially passed to putc.
- * If a $(D char[]), $(D wchar[]), or $(D dchar[]) argument is
- * encountered, it is interpreted as a format string. As many
- * arguments as specified in the format string are consumed and
- * formatted according to the format specifications in that string and
- * passed to putc. If there are too few remaining arguments, a
- * $(D FormatException) is thrown. If there are more remaining arguments than
- * needed by the format specification, the default processing of
- * arguments resumes until they are all consumed.
- *
- * Params:
- *        putc =        Output is sent do this delegate, character by character.
- *        arguments = Array of $(D TypeInfo)s, one for each argument to be formatted.
- *        argptr = Points to variadic argument list.
- *
- * Throws:
- *        Mismatched arguments and formats result in a $(D FormatException) being thrown.
- *
- * Format_String:
- *        <a name="format-string">$(I Format strings)</a>
- *        consist of characters interspersed with
- *        $(I format specifications). Characters are simply copied
- *        to the output (such as putc) after any necessary conversion
- *        to the corresponding UTF-8 sequence.
- *
- *        A $(I format specification) starts with a '%' character,
- *        and has the following grammar:
-
-$(CONSOLE
-$(I FormatSpecification):
-    $(B '%%')
-    $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar)
-
-$(I Flags):
-    $(I empty)
-    $(B '-') $(I Flags)
-    $(B '+') $(I Flags)
-    $(B '#') $(I Flags)
-    $(B '0') $(I Flags)
-    $(B ' ') $(I Flags)
-
-$(I Width):
-    $(I empty)
-    $(I Integer)
-    $(B '*')
-
-$(I Precision):
-    $(I empty)
-    $(B '.')
-    $(B '.') $(I Integer)
-    $(B '.*')
-
-$(I Integer):
-    $(I Digit)
-    $(I Digit) $(I Integer)
-
-$(I Digit):
-    $(B '0')
-    $(B '1')
-    $(B '2')
-    $(B '3')
-    $(B '4')
-    $(B '5')
-    $(B '6')
-    $(B '7')
-    $(B '8')
-    $(B '9')
-
-$(I FormatChar):
-    $(B 's')
-    $(B 'b')
-    $(B 'd')
-    $(B 'o')
-    $(B 'x')
-    $(B 'X')
-    $(B 'e')
-    $(B 'E')
-    $(B 'f')
-    $(B 'F')
-    $(B 'g')
-    $(B 'G')
-    $(B 'a')
-    $(B 'A')
-)
-    $(DL
-    $(DT $(I Flags))
-    $(DL
-        $(DT $(B '-'))
-        $(DD
-        Left justify the result in the field.
-        It overrides any $(B 0) flag.)
-
-        $(DT $(B '+'))
-        $(DD Prefix positive numbers in a signed conversion with a $(B +).
-        It overrides any $(I space) flag.)
-
-        $(DT $(B '#'))
-        $(DD Use alternative formatting:
-        $(DL
-            $(DT For $(B 'o'):)
-            $(DD Add to precision as necessary so that the first digit
-            of the octal formatting is a '0', even if both the argument
-            and the $(I Precision) are zero.)
-            $(DT For $(B 'x') ($(B 'X')):)
-            $(DD If non-zero, prefix result with $(B 0x) ($(B 0X)).)
-            $(DT For floating point formatting:)
-            $(DD Always insert the decimal point.)
-            $(DT For $(B 'g') ($(B 'G')):)
-            $(DD Do not elide trailing zeros.)
-        ))
-
-        $(DT $(B '0'))
-        $(DD For integer and floating point formatting when not nan or
-        infinity, use leading zeros
-        to pad rather than spaces.
-        Ignore if there's a $(I Precision).)
-
-        $(DT $(B ' '))
-        $(DD Prefix positive numbers in a signed conversion with a space.)
-    )
-
-    $(DT $(I Width))
-    $(DD
-    Specifies the minimum field width.
-    If the width is a $(B *), the next argument, which must be
-    of type $(B int), is taken as the width.
-    If the width is negative, it is as if the $(B -) was given
-    as a $(I Flags) character.)
-
-    $(DT $(I Precision))
-    $(DD Gives the precision for numeric conversions.
-    If the precision is a $(B *), the next argument, which must be
-    of type $(B int), is taken as the precision. If it is negative,
-    it is as if there was no $(I Precision).)
-
-    $(DT $(I FormatChar))
-    $(DD
-    $(DL
-        $(DT $(B 's'))
-        $(DD The corresponding argument is formatted in a manner consistent
-        with its type:
-        $(DL
-            $(DT $(B bool))
-            $(DD The result is <tt>'true'</tt> or <tt>'false'</tt>.)
-            $(DT integral types)
-            $(DD The $(B %d) format is used.)
-            $(DT floating point types)
-            $(DD The $(B %g) format is used.)
-            $(DT string types)
-            $(DD The result is the string converted to UTF-8.)
-            A $(I Precision) specifies the maximum number of characters
-            to use in the result.
-            $(DT classes derived from $(B Object))
-            $(DD The result is the string returned from the class instance's
-            $(B .toString()) method.
-            A $(I Precision) specifies the maximum number of characters
-            to use in the result.)
-            $(DT non-string static and dynamic arrays)
-            $(DD The result is [s<sub>0</sub>, s<sub>1</sub>, ...]
-            where s<sub>k</sub> is the kth element
-            formatted with the default format.)
-        ))
-
-        $(DT $(B 'b','d','o','x','X'))
-        $(DD The corresponding argument must be an integral type
-        and is formatted as an integer. If the argument is a signed type
-        and the $(I FormatChar) is $(B d) it is converted to
-        a signed string of characters, otherwise it is treated as
-        unsigned. An argument of type $(B bool) is formatted as '1'
-        or '0'. The base used is binary for $(B b), octal for $(B o),
-        decimal
-        for $(B d), and hexadecimal for $(B x) or $(B X).
-        $(B x) formats using lower case letters, $(B X) uppercase.
-        If there are fewer resulting digits than the $(I Precision),
-        leading zeros are used as necessary.
-        If the $(I Precision) is 0 and the number is 0, no digits
-        result.)
-
-        $(DT $(B 'e','E'))
-        $(DD A floating point number is formatted as one digit before
-        the decimal point, $(I Precision) digits after, the $(I FormatChar),
-        &plusmn;, followed by at least a two digit exponent: $(I d.dddddd)e$(I &plusmn;dd).
-        If there is no $(I Precision), six
-        digits are generated after the decimal point.
-        If the $(I Precision) is 0, no decimal point is generated.)
-
-        $(DT $(B 'f','F'))
-        $(DD A floating point number is formatted in decimal notation.
-        The $(I Precision) specifies the number of digits generated
-        after the decimal point. It defaults to six. At least one digit
-        is generated before the decimal point. If the $(I Precision)
-        is zero, no decimal point is generated.)
-
-        $(DT $(B 'g','G'))
-        $(DD A floating point number is formatted in either $(B e) or
-        $(B f) format for $(B g); $(B E) or $(B F) format for
-        $(B G).
-        The $(B f) format is used if the exponent for an $(B e) format
-        is greater than -5 and less than the $(I Precision).
-        The $(I Precision) specifies the number of significant
-        digits, and defaults to six.
-        Trailing zeros are elided after the decimal point, if the fractional
-        part is zero then no decimal point is generated.)
-
-        $(DT $(B 'a','A'))
-        $(DD A floating point number is formatted in hexadecimal
-        exponential notation 0x$(I h.hhhhhh)p$(I &plusmn;d).
-        There is one hexadecimal digit before the decimal point, and as
-        many after as specified by the $(I Precision).
-        If the $(I Precision) is zero, no decimal point is generated.
-        If there is no $(I Precision), as many hexadecimal digits as
-        necessary to exactly represent the mantissa are generated.
-        The exponent is written in as few digits as possible,
-        but at least one, is in decimal, and represents a power of 2 as in
-        $(I h.hhhhhh)*2<sup>$(I &plusmn;d)</sup>.
-        The exponent for zero is zero.
-        The hexadecimal digits, x and p are in upper case if the
-        $(I FormatChar) is upper case.)
-    )
-
-    Floating point NaN's are formatted as $(B nan) if the
-    $(I FormatChar) is lower case, or $(B NAN) if upper.
-    Floating point infinities are formatted as $(B inf) or
-    $(B infinity) if the
-    $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper.
-    ))
-
-Example:
-
--------------------------
-import core.stdc.stdio;
-import std.format;
-
-void myPrint(...)
-{
-    void putc(dchar c)
-    {
-        fputc(c, stdout);
-    }
-
-    std.format.doFormat(&putc, _arguments, _argptr);
-}
-
-void main()
-{
-    int x = 27;
-
-    // prints 'The answer is 27:6'
-    myPrint("The answer is %s:", x, 6);
-}
-------------------------
- */
-void doFormat()(scope void delegate(dchar) putc, TypeInfo[] arguments, va_list ap)
-{
-    import std.utf : toUCSindex, isValidDchar, UTFException, toUTF8;
-    import core.stdc.string : strlen;
-    import core.stdc.stdlib : alloca, malloc, realloc, free;
-    import core.stdc.stdio : snprintf;
-
-    size_t bufLength = 1024;
-    void* argBuffer = malloc(bufLength);
-    scope(exit) free(argBuffer);
-
-    size_t bufUsed = 0;
-    foreach (ti; arguments)
-    {
-        // Ensure the required alignment
-        bufUsed += ti.talign - 1;
-        bufUsed -= (cast(size_t)argBuffer + bufUsed) & (ti.talign - 1);
-        auto pos = bufUsed;
-        // Align to next word boundary
-        bufUsed += ti.tsize + size_t.sizeof - 1;
-        bufUsed -= (cast(size_t)argBuffer + bufUsed) & (size_t.sizeof - 1);
-        // Resize buffer if necessary
-        while (bufUsed > bufLength)
-        {
-            bufLength *= 2;
-            argBuffer = realloc(argBuffer, bufLength);
-        }
-        // Copy argument into buffer
-        va_arg(ap, ti, argBuffer + pos);
-    }
-
-    auto argptr = argBuffer;
-    void* skipArg(TypeInfo ti)
-    {
-        // Ensure the required alignment
-        argptr += ti.talign - 1;
-        argptr -= cast(size_t)argptr & (ti.talign - 1);
-        auto p = argptr;
-        // Align to next word boundary
-        argptr += ti.tsize + size_t.sizeof - 1;
-        argptr -= cast(size_t)argptr & (size_t.sizeof - 1);
-        return p;
-    }
-    auto getArg(T)()
-    {
-        return *cast(T*)skipArg(typeid(T));
-    }
-
-    TypeInfo ti;
-    Mangle m;
-    uint flags;
-    int field_width;
-    int precision;
-
-    enum : uint
-    {
-        FLdash = 1,
-        FLplus = 2,
-        FLspace = 4,
-        FLhash = 8,
-        FLlngdbl = 0x20,
-        FL0pad = 0x40,
-        FLprecision = 0x80,
-    }
-
-    static TypeInfo skipCI(TypeInfo valti)
-    {
-        for (;;)
-        {
-            if (typeid(valti).name.length == 18 &&
-                    typeid(valti).name[9..18] == "Invariant")
-                valti = (cast(TypeInfo_Invariant)valti).base;
-            else if (typeid(valti).name.length == 14 &&
-                    typeid(valti).name[9..14] == "Const")
-                valti = (cast(TypeInfo_Const)valti).base;
-            else
-                break;
-        }
-
-        return valti;
-    }
-
-    void formatArg(char fc)
-    {
-        bool vbit;
-        ulong vnumber;
-        char vchar;
-        dchar vdchar;
-        Object vobject;
-        real vreal;
-        creal vcreal;
-        Mangle m2;
-        int signed = 0;
-        uint base = 10;
-        int uc;
-        char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary
-        const(char)* prefix = "";
-        string s;
-
-        void putstr(const char[] s)
-        {
-            //printf("putstr: s = %.*s, flags = x%x\n", s.length, s.ptr, flags);
-            ptrdiff_t padding = field_width -
-                (strlen(prefix) + toUCSindex(s, s.length));
-            ptrdiff_t prepad = 0;
-            ptrdiff_t postpad = 0;
-            if (padding > 0)
-            {
-                if (flags & FLdash)
-                    postpad = padding;
-                else
-                    prepad = padding;
-            }
-
-            if (flags & FL0pad)
-            {
-                while (*prefix)
-                    putc(*prefix++);
-                while (prepad--)
-                    putc('0');
-            }
-            else
-            {
-                while (prepad--)
-                    putc(' ');
-                while (*prefix)
-                    putc(*prefix++);
-            }
-
-            foreach (dchar c; s)
-                putc(c);
-
-            while (postpad--)
-                putc(' ');
-        }
-
-        void putreal(real v)
-        {
-            //printf("putreal %Lg\n", vreal);
-
-            switch (fc)
-            {
-                case 's':
-                    fc = 'g';
-                    break;
-
-                case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A':
-                    break;
-
-                default:
-                    //printf("fc = '%c'\n", fc);
-                Lerror:
-                    throw new FormatException("incompatible format character for floating point type");
-            }
-            version (DigitalMarsC)
-            {
-                uint sl;
-                char[] fbuf = tmpbuf;
-                if (!(flags & FLprecision))
-                    precision = 6;
-                while (1)
-                {
-                    sl = fbuf.length;
-                    prefix = (*__pfloatfmt)(fc, flags | FLlngdbl,
-                            precision, &v, cast(char*)fbuf, &sl, field_width);
-                    if (sl != -1)
-                        break;
-                    sl = fbuf.length * 2;
-                    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
-                }
-                putstr(fbuf[0 .. sl]);
-            }
-            else
-            {
-                ptrdiff_t sl;
-                char[] fbuf = tmpbuf;
-                char[12] format;
-                format[0] = '%';
-                int i = 1;
-                if (flags & FLdash)
-                    format[i++] = '-';
-                if (flags & FLplus)
-                    format[i++] = '+';
-                if (flags & FLspace)
-                    format[i++] = ' ';
-                if (flags & FLhash)
-                    format[i++] = '#';
-                if (flags & FL0pad)
-                    format[i++] = '0';
-                format[i + 0] = '*';
-                format[i + 1] = '.';
-                format[i + 2] = '*';
-                format[i + 3] = 'L';
-                format[i + 4] = fc;
-                format[i + 5] = 0;
-                if (!(flags & FLprecision))
-                    precision = -1;
-                while (1)
-                {
-                    sl = fbuf.length;
-                    int n;
-                    version (CRuntime_Microsoft)
-                    {
-                        import std.math : isNaN, isInfinity;
-                        if (isNaN(v)) // snprintf writes 1.#QNAN
-                            n = snprintf(fbuf.ptr, sl, "nan");
-                        else if (isInfinity(v)) // snprintf writes 1.#INF
-                            n = snprintf(fbuf.ptr, sl, v < 0 ? "-inf" : "inf");
-                        else
-                            n = snprintf(fbuf.ptr, sl, format.ptr, field_width,
-                                         precision, cast(double)v);
-                    }
-                    else
-                        n = snprintf(fbuf.ptr, sl, format.ptr, field_width,
-                                precision, v);
-                    //printf("format = '%s', n = %d\n", cast(char*)format, n);
-                    if (n >= 0 && n < sl)
-                    {        sl = n;
-                        break;
-                    }
-                    if (n < 0)
-                        sl = sl * 2;
-                    else
-                        sl = n + 1;
-                    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
-                }
-                putstr(fbuf[0 .. sl]);
-            }
-            return;
-        }
-
-        static Mangle getMan(TypeInfo ti)
-        {
-          auto m = cast(Mangle)typeid(ti).name[9];
-          if (typeid(ti).name.length == 20 &&
-              typeid(ti).name[9..20] == "StaticArray")
-                m = cast(Mangle)'G';
-          return m;
-        }
-
-        /* p = pointer to the first element in the array
-         * len = number of elements in the array
-         * valti = type of the elements
-         */
-        void putArray(void* p, size_t len, TypeInfo valti)
-        {
-          //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize);
-          putc('[');
-          valti = skipCI(valti);
-          size_t tsize = valti.tsize;
-          auto argptrSave = argptr;
-          auto tiSave = ti;
-          auto mSave = m;
-          ti = valti;
-          //printf("\n%.*s\n", typeid(valti).name.length, typeid(valti).name.ptr);
-          m = getMan(valti);
-          while (len--)
-          {
-            //doFormat(putc, (&valti)[0 .. 1], p);
-            argptr = p;
-            formatArg('s');
-            p += tsize;
-            if (len > 0) putc(',');
-          }
-          m = mSave;
-          ti = tiSave;
-          argptr = argptrSave;
-          putc(']');
-        }
-
-        void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti)
-        {
-            putc('[');
-            bool comma=false;
-            auto argptrSave = argptr;
-            auto tiSave = ti;
-            auto mSave = m;
-            valti = skipCI(valti);
-            keyti = skipCI(keyti);
-            foreach (ref fakevalue; vaa)
-            {
-                if (comma) putc(',');
-                comma = true;
-                void *pkey = &fakevalue;
-                version (D_LP64)
-                    pkey -= (long.sizeof + 15) & ~(15);
-                else
-                    pkey -= (long.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
-
-                // the key comes before the value
-                auto keysize = keyti.tsize;
-                version (D_LP64)
-                    auto keysizet = (keysize + 15) & ~(15);
-                else
-                    auto keysizet = (keysize + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
-
-                void* pvalue = pkey + keysizet;
-
-                //doFormat(putc, (&keyti)[0..1], pkey);
-                m = getMan(keyti);
-                argptr = pkey;
-
-                ti = keyti;
-                formatArg('s');
-
-                putc(':');
-                //doFormat(putc, (&valti)[0..1], pvalue);
-                m = getMan(valti);
-                argptr = pvalue;
-
-                ti = valti;
-                formatArg('s');
-            }
-            m = mSave;
-            ti = tiSave;
-            argptr = argptrSave;
-            putc(']');
-        }
-
-        //printf("formatArg(fc = '%c', m = '%c')\n", fc, m);
-        int mi;
-        switch (m)
-        {
-            case Mangle.Tbool:
-                vbit = getArg!(bool)();
-                if (fc != 's')
-                {   vnumber = vbit;
-                    goto Lnumber;
-                }
-                putstr(vbit ? "true" : "false");
-                return;
-
-            case Mangle.Tchar:
-                vchar = getArg!(char)();
-                if (fc != 's')
-                {   vnumber = vchar;
-                    goto Lnumber;
-                }
-            L2:
-                putstr((&vchar)[0 .. 1]);
-                return;
-
-            case Mangle.Twchar:
-                vdchar = getArg!(wchar)();
-                goto L1;
-
-            case Mangle.Tdchar:
-                vdchar = getArg!(dchar)();
-            L1:
-                if (fc != 's')
-                {   vnumber = vdchar;
-                    goto Lnumber;
-                }
-                if (vdchar <= 0x7F)
-                {   vchar = cast(char)vdchar;
-                    goto L2;
-                }
-                else
-                {   if (!isValidDchar(vdchar))
-                        throw new UTFException("invalid dchar in format");
-                    char[4] vbuf;
-                    putstr(toUTF8(vbuf, vdchar));
-                }
-                return;
-
-            case Mangle.Tbyte:
-                signed = 1;
-                vnumber = getArg!(byte)();
-                goto Lnumber;
-
-            case Mangle.Tubyte:
-                vnumber = getArg!(ubyte)();
-                goto Lnumber;
-
-            case Mangle.Tshort:
-                signed = 1;
-                vnumber = getArg!(short)();
-                goto Lnumber;
-
-            case Mangle.Tushort:
-                vnumber = getArg!(ushort)();
-                goto Lnumber;
-
-            case Mangle.Tint:
-                signed = 1;
-                vnumber = getArg!(int)();
-                goto Lnumber;
-
-            case Mangle.Tuint:
-            Luint:
-                vnumber = getArg!(uint)();
-                goto Lnumber;
-
-            case Mangle.Tlong:
-                signed = 1;
-                vnumber = cast(ulong)getArg!(long)();
-                goto Lnumber;
-
-            case Mangle.Tulong:
-            Lulong:
-                vnumber = getArg!(ulong)();
-                goto Lnumber;
-
-            case Mangle.Tclass:
-                vobject = getArg!(Object)();
-                if (vobject is null)
-                    s = "null";
-                else
-                    s = vobject.toString();
-                goto Lputstr;
-
-            case Mangle.Tpointer:
-                vnumber = cast(ulong)getArg!(void*)();
-                if (fc != 'x')  uc = 1;
-                flags |= FL0pad;
-                if (!(flags & FLprecision))
-                {   flags |= FLprecision;
-                    precision = (void*).sizeof;
-                }
-                base = 16;
-                goto Lnumber;
-
-            case Mangle.Tfloat:
-            case Mangle.Tifloat:
-                if (fc == 'x' || fc == 'X')
-                    goto Luint;
-                vreal = getArg!(float)();
-                goto Lreal;
-
-            case Mangle.Tdouble:
-            case Mangle.Tidouble:
-                if (fc == 'x' || fc == 'X')
-                    goto Lulong;
-                vreal = getArg!(double)();
-                goto Lreal;
-
-            case Mangle.Treal:
-            case Mangle.Tireal:
-                vreal = getArg!(real)();
-                goto Lreal;
-
-            case Mangle.Tcfloat:
-                vcreal = getArg!(cfloat)();
-                goto Lcomplex;
-
-            case Mangle.Tcdouble:
-                vcreal = getArg!(cdouble)();
-                goto Lcomplex;
-
-            case Mangle.Tcreal:
-                vcreal = getArg!(creal)();
-                goto Lcomplex;
-
-            case Mangle.Tsarray:
-                putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next);
-                return;
-
-            case Mangle.Tarray:
-                mi = 10;
-                if (typeid(ti).name.length == 14 &&
-                    typeid(ti).name[9..14] == "Array")
-                { // array of non-primitive types
-                  TypeInfo tn = (cast(TypeInfo_Array)ti).next;
-                  tn = skipCI(tn);
-                  switch (cast(Mangle)typeid(tn).name[9])
-                  {
-                    case Mangle.Tchar:  goto LarrayChar;
-                    case Mangle.Twchar: goto LarrayWchar;
-                    case Mangle.Tdchar: goto LarrayDchar;
-                    default:
-                        break;
-                  }
-                  void[] va = getArg!(void[])();
-                  putArray(va.ptr, va.length, tn);
-                  return;
-                }
-                if (typeid(ti).name.length == 25 &&
-                    typeid(ti).name[9..25] == "AssociativeArray")
-                { // associative array
-                  ubyte[long] vaa = getArg!(ubyte[long])();
-                  putAArray(vaa,
-                        (cast(TypeInfo_AssociativeArray)ti).next,
-                        (cast(TypeInfo_AssociativeArray)ti).key);
-                  return;
-                }
-
-                while (1)
-                {
-                    m2 = cast(Mangle)typeid(ti).name[mi];
-                    switch (m2)
-                    {
-                        case Mangle.Tchar:
-                        LarrayChar:
-                            s = getArg!(string)();
-                            goto Lputstr;
-
-                        case Mangle.Twchar:
-                        LarrayWchar:
-                            wchar[] sw = getArg!(wchar[])();
-                            s = toUTF8(sw);
-                            goto Lputstr;
-
-                        case Mangle.Tdchar:
-                        LarrayDchar:
-                            s = toUTF8(getArg!(dstring)());
-                        Lputstr:
-                            if (fc != 's')
-                                throw new FormatException("string");
-                            if (flags & FLprecision && precision < s.length)
-                                s = s[0 .. precision];
-                            putstr(s);
-                            break;
-
-                        case Mangle.Tconst:
-                        case Mangle.Timmutable:
-                            mi++;
-                            continue;
-
-                        default:
-                            TypeInfo ti2 = primitiveTypeInfo(m2);
-                            if (!ti2)
-                              goto Lerror;
-                            void[] va = getArg!(void[])();
-                            putArray(va.ptr, va.length, ti2);
-                    }
-                    return;
-                }
-                assert(0);
-
-            case Mangle.Ttypedef:
-                ti = (cast(TypeInfo_Typedef)ti).base;
-                m = cast(Mangle)typeid(ti).name[9];
-                formatArg(fc);
-                return;
-
-            case Mangle.Tenum:
-                ti = (cast(TypeInfo_Enum)ti).base;
-                m = cast(Mangle)typeid(ti).name[9];
-                formatArg(fc);
-                return;
-
-            case Mangle.Tstruct:
-            {        TypeInfo_Struct tis = cast(TypeInfo_Struct)ti;
-                if (tis.xtoString is null)
-                    throw new FormatException("Can't convert " ~ tis.toString()
-                            ~ " to string: \"string toString()\" not defined");
-                s = tis.xtoString(skipArg(tis));
-                goto Lputstr;
-            }
-
-            default:
-                goto Lerror;
-        }
-
-    Lnumber:
-        switch (fc)
-        {
-            case 's':
-            case 'd':
-                if (signed)
-                {   if (cast(long)vnumber < 0)
-                    {        prefix = "-";
-                        vnumber = -vnumber;
-                    }
-                    else if (flags & FLplus)
-                        prefix = "+";
-                    else if (flags & FLspace)
-                        prefix = " ";
-                }
-                break;
-
-            case 'b':
-                signed = 0;
-                base = 2;
-                break;
-
-            case 'o':
-                signed = 0;
-                base = 8;
-                break;
-
-            case 'X':
-                uc = 1;
-                if (flags & FLhash && vnumber)
-                    prefix = "0X";
-                signed = 0;
-                base = 16;
-                break;
-
-            case 'x':
-                if (flags & FLhash && vnumber)
-                    prefix = "0x";
-                signed = 0;
-                base = 16;
-                break;
-
-            default:
-                goto Lerror;
-        }
-
-        if (!signed)
-        {
-            switch (m)
-            {
-                case Mangle.Tbyte:
-                    vnumber &= 0xFF;
-                    break;
-
-                case Mangle.Tshort:
-                    vnumber &= 0xFFFF;
-                    break;
-
-                case Mangle.Tint:
-                    vnumber &= 0xFFFFFFFF;
-                    break;
-
-                default:
-                    break;
-            }
-        }
-
-        if (flags & FLprecision && fc != 'p')
-            flags &= ~FL0pad;
-
-        if (vnumber < base)
-        {
-            if (vnumber == 0 && precision == 0 && flags & FLprecision &&
-                !(fc == 'o' && flags & FLhash))
-            {
-                putstr(null);
-                return;
-            }
-            if (precision == 0 || !(flags & FLprecision))
-            {        vchar = cast(char)('0' + vnumber);
-                if (vnumber < 10)
-                    vchar = cast(char)('0' + vnumber);
-                else
-                    vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber);
-                goto L2;
-            }
-        }
-
-        {
-            ptrdiff_t n = tmpbuf.length;
-            char c;
-            int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1));
-
-            while (vnumber)
-            {
-                c = cast(char)((vnumber % base) + '0');
-                if (c > '9')
-                    c += hexoffset;
-                vnumber /= base;
-                tmpbuf[--n] = c;
-            }
-            if (tmpbuf.length - n < precision && precision < tmpbuf.length)
-            {
-                ptrdiff_t m = tmpbuf.length - precision;
-                tmpbuf[m .. n] = '0';
-                n = m;
-            }
-            else if (flags & FLhash && fc == 'o')
-                prefix = "0";
-            putstr(tmpbuf[n .. tmpbuf.length]);
-            return;
-        }
-
-    Lreal:
-        putreal(vreal);
-        return;
-
-    Lcomplex:
-        putreal(vcreal.re);
-        if (vcreal.im >= 0)
-        {
-            putc('+');
-        }
-        putreal(vcreal.im);
-        putc('i');
-        return;
-
-    Lerror:
-        throw new FormatException("formatArg");
-    }
-
-    for (int j = 0; j < arguments.length; )
-    {
-        ti = arguments[j++];
-        //printf("arg[%d]: '%.*s' %d\n", j, typeid(ti).name.length, typeid(ti).name.ptr, typeid(ti).name.length);
-        //ti.print();
-
-        flags = 0;
-        precision = 0;
-        field_width = 0;
-
-        ti = skipCI(ti);
-        int mi = 9;
-        do
-        {
-            if (typeid(ti).name.length <= mi)
-                goto Lerror;
-            m = cast(Mangle)typeid(ti).name[mi++];
-        } while (m == Mangle.Tconst || m == Mangle.Timmutable);
-
-        if (m == Mangle.Tarray)
-        {
-            if (typeid(ti).name.length == 14 &&
-                    typeid(ti).name[9..14] == "Array")
-            {
-                TypeInfo tn = (cast(TypeInfo_Array)ti).next;
-                tn = skipCI(tn);
-                switch (cast(Mangle)typeid(tn).name[9])
-                {
-                case Mangle.Tchar:
-                case Mangle.Twchar:
-                case Mangle.Tdchar:
-                    ti = tn;
-                    mi = 9;
-                    break;
-                default:
-                    break;
-                }
-            }
-          L1:
-            Mangle m2 = cast(Mangle)typeid(ti).name[mi];
-            string  fmt;                        // format string
-            wstring wfmt;
-            dstring dfmt;
-
-            /* For performance reasons, this code takes advantage of the
-             * fact that most format strings will be ASCII, and that the
-             * format specifiers are always ASCII. This means we only need
-             * to deal with UTF in a couple of isolated spots.
-             */
-
-            switch (m2)
-            {
-            case Mangle.Tchar:
-                fmt = getArg!(string)();
-                break;
-
-            case Mangle.Twchar:
-                wfmt = getArg!(wstring)();
-                fmt = toUTF8(wfmt);
-                break;
-
-            case Mangle.Tdchar:
-                dfmt = getArg!(dstring)();
-                fmt = toUTF8(dfmt);
-                break;
-
-            case Mangle.Tconst:
-            case Mangle.Timmutable:
-                mi++;
-                goto L1;
-
-            default:
-                formatArg('s');
-                continue;
-            }
-
-            for (size_t i = 0; i < fmt.length; )
-            {        dchar c = fmt[i++];
-
-                dchar getFmtChar()
-                {   // Valid format specifier characters will never be UTF
-                    if (i == fmt.length)
-                        throw new FormatException("invalid specifier");
-                    return fmt[i++];
-                }
-
-                int getFmtInt()
-                {   int n;
-
-                    while (1)
-                    {
-                        n = n * 10 + (c - '0');
-                        if (n < 0)        // overflow
-                            throw new FormatException("int overflow");
-                        c = getFmtChar();
-                        if (c < '0' || c > '9')
-                            break;
-                    }
-                    return n;
-                }
-
-                int getFmtStar()
-                {   Mangle m;
-                    TypeInfo ti;
-
-                    if (j == arguments.length)
-                        throw new FormatException("too few arguments");
-                    ti = arguments[j++];
-                    m = cast(Mangle)typeid(ti).name[9];
-                    if (m != Mangle.Tint)
-                        throw new FormatException("int argument expected");
-                    return getArg!(int)();
-                }
-
-                if (c != '%')
-                {
-                    if (c > 0x7F)        // if UTF sequence
-                    {
-                        i--;                // back up and decode UTF sequence
-                        import std.utf : decode;
-                        c = decode(fmt, i);
-                    }
-                  Lputc:
-                    putc(c);
-                    continue;
-                }
-
-                // Get flags {-+ #}
-                flags = 0;
-                while (1)
-                {
-                    c = getFmtChar();
-                    switch (c)
-                    {
-                    case '-':        flags |= FLdash;        continue;
-                    case '+':        flags |= FLplus;        continue;
-                    case ' ':        flags |= FLspace;        continue;
-                    case '#':        flags |= FLhash;        continue;
-                    case '0':        flags |= FL0pad;        continue;
-
-                    case '%':        if (flags == 0)
-                                          goto Lputc;
-                                     break;
-
-                    default:         break;
-                    }
-                    break;
-                }
-
-                // Get field width
-                field_width = 0;
-                if (c == '*')
-                {
-                    field_width = getFmtStar();
-                    if (field_width < 0)
-                    {   flags |= FLdash;
-                        field_width = -field_width;
-                    }
-
-                    c = getFmtChar();
-                }
-                else if (c >= '0' && c <= '9')
-                    field_width = getFmtInt();
-
-                if (flags & FLplus)
-                    flags &= ~FLspace;
-                if (flags & FLdash)
-                    flags &= ~FL0pad;
-
-                // Get precision
-                precision = 0;
-                if (c == '.')
-                {   flags |= FLprecision;
-                    //flags &= ~FL0pad;
-
-                    c = getFmtChar();
-                    if (c == '*')
-                    {
-                        precision = getFmtStar();
-                        if (precision < 0)
-                        {   precision = 0;
-                            flags &= ~FLprecision;
-                        }
-
-                        c = getFmtChar();
-                    }
-                    else if (c >= '0' && c <= '9')
-                        precision = getFmtInt();
-                }
-
-                if (j == arguments.length)
-                    goto Lerror;
-                ti = arguments[j++];
-                ti = skipCI(ti);
-                mi = 9;
-                do
-                {
-                    m = cast(Mangle)typeid(ti).name[mi++];
-                } while (m == Mangle.Tconst || m == Mangle.Timmutable);
-
-                if (c > 0x7F)                // if UTF sequence
-                    goto Lerror;        // format specifiers can't be UTF
-                formatArg(cast(char)c);
-            }
-        }
-        else
-        {
-            formatArg('s');
-        }
-    }
-    return;
-
-  Lerror:
-    throw new FormatException();
-}
-
-
-private bool needToSwapEndianess(Char)(ref FormatSpec!Char f)
-{
-    import std.system : endian, Endian;
-
-    return endian == Endian.littleEndian && f.flPlus
-        || endian == Endian.bigEndian && f.flDash;
-}
-
-/* ======================== Unit Tests ====================================== */
-
-unittest
-{
-    import std.conv : octal;
-
-    int i;
-    string s;
-
-    debug(format) printf("std.format.format.unittest\n");
-
-    s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo");
-    assert(s == "hello world! true 57 1000000000x foo");
-
-    s = format("%s %A %s", 1.67, -1.28, float.nan);
-    /* The host C library is used to format floats.
-     * C99 doesn't specify what the hex digit before the decimal point
-     * is for %A.
-     */
-    //version (linux)
-    //    assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan");
-    //else version (OSX)
-    //    assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s);
-    //else
-    version (MinGW)
-        assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s);
-    else version (CRuntime_Microsoft)
-        assert(s == "1.67 -0X1.47AE14P+0 nan"
-            || s == "1.67 -0X1.47AE147AE147BP+0 nan", s); // MSVCRT 14+ (VS 2015)
-    else
-        assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s);
-
-    s = format("%x %X", 0x1234AF, 0xAFAFAFAF);
-    assert(s == "1234af AFAFAFAF");
-
-    s = format("%b %o", 0x1234AF, 0xAFAFAFAF);
-    assert(s == "100100011010010101111 25753727657");
-
-    s = format("%d %s", 0x1234AF, 0xAFAFAFAF);
-    assert(s == "1193135 2947526575");
-
-    //version(X86_64)
-    //{
-    //    pragma(msg, "several format tests disabled on x86_64 due to bug 5625");
-    //}
-    //else
-    //{
-        s = format("%s", 1.2 + 3.4i);
-        assert(s == "1.2+3.4i", s);
-
-        //s = format("%x %X", 1.32, 6.78f);
-        //assert(s == "3ff51eb851eb851f 40D8F5C3");
-
-    //}
-
-    s = format("%#06.*f",2,12.345);
-    assert(s == "012.35");
-
-    s = format("%#0*.*f",6,2,12.345);
-    assert(s == "012.35");
-
-    s = format("%7.4g:", 12.678);
-    assert(s == "  12.68:");
-
-    s = format("%7.4g:", 12.678L);
-    assert(s == "  12.68:");
-
-    s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1);
-    assert(s == "-4.000000|-0010|0x001|  0x1");
-
-    i = -10;
-    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
-    assert(s == "-10|-10|-10|-10|-10.0000");
-
-    i = -5;
-    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
-    assert(s == "-5| -5|-05|-5|-5.0000");
-
-    i = 0;
-    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
-    assert(s == "0|  0|000|0|0.0000");
-
-    i = 5;
-    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
-    assert(s == "5|  5|005|5|5.0000");
-
-    i = 10;
-    s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
-    assert(s == "10| 10|010|10|10.0000");
-
-    s = format("%.0d", 0);
-    assert(s == "");
-
-    s = format("%.g", .34);
-    assert(s == "0.3");
-
-    s = format("%.0g", .34);
-    assert(s == "0.3");
-
-    s = format("%.2g", .34);
-    assert(s == "0.34");
-
-    s = format("%0.0008f", 1e-08);
-    assert(s == "0.00000001");
-
-    s = format("%0.0008f", 1e-05);
-    assert(s == "0.00001000");
-
-    s = "helloworld";
-    string r;
-    r = format("%.2s", s[0..5]);
-    assert(r == "he");
-    r = format("%.20s", s[0..5]);
-    assert(r == "hello");
-    r = format("%8s", s[0..5]);
-    assert(r == "   hello");
-
-    byte[] arrbyte = new byte[4];
-    arrbyte[0] = 100;
-    arrbyte[1] = -99;
-    arrbyte[3] = 0;
-    r = format("%s", arrbyte);
-    assert(r == "[100, -99, 0, 0]");
-
-    ubyte[] arrubyte = new ubyte[4];
-    arrubyte[0] = 100;
-    arrubyte[1] = 200;
-    arrubyte[3] = 0;
-    r = format("%s", arrubyte);
-    assert(r == "[100, 200, 0, 0]");
-
-    short[] arrshort = new short[4];
-    arrshort[0] = 100;
-    arrshort[1] = -999;
-    arrshort[3] = 0;
-    r = format("%s", arrshort);
-    assert(r == "[100, -999, 0, 0]");
-
-    ushort[] arrushort = new ushort[4];
-    arrushort[0] = 100;
-    arrushort[1] = 20_000;
-    arrushort[3] = 0;
-    r = format("%s", arrushort);
-    assert(r == "[100, 20000, 0, 0]");
-
-    int[] arrint = new int[4];
-    arrint[0] = 100;
-    arrint[1] = -999;
-    arrint[3] = 0;
-    r = format("%s", arrint);
-    assert(r == "[100, -999, 0, 0]");
-
-    long[] arrlong = new long[4];
-    arrlong[0] = 100;
-    arrlong[1] = -999;
-    arrlong[3] = 0;
-    r = format("%s", arrlong);
-    assert(r == "[100, -999, 0, 0]");
-
-    ulong[] arrulong = new ulong[4];
-    arrulong[0] = 100;
-    arrulong[1] = 999;
-    arrulong[3] = 0;
-    r = format("%s", arrulong);
-    assert(r == "[100, 999, 0, 0]");
-
-    string[] arr2 = new string[4];
-    arr2[0] = "hello";
-    arr2[1] = "world";
-    arr2[3] = "foo";
-    r = format("%s", arr2);
-    assert(r == `["hello", "world", "", "foo"]`);
-
-    r = format("%.8d", 7);
-    assert(r == "00000007");
-    r = format("%.8x", 10);
-    assert(r == "0000000a");
-
-    r = format("%-3d", 7);
-    assert(r == "7  ");
-
-    r = format("%*d", -3, 7);
-    assert(r == "7  ");
-
-    r = format("%.*d", -3, 7);
-    assert(r == "7");
-
-    r = format("abc"c);
-    assert(r == "abc");
-
-    //format() returns the same type as inputted.
-    wstring wr;
-    wr = format("def"w);
-    assert(wr == "def"w);
-
-    dstring dr;
-    dr = format("ghi"d);
-    assert(dr == "ghi"d);
-
-    void* p = cast(void*)0xDEADBEEF;
-    r = format("%s", p);
-    assert(r == "DEADBEEF");
-
-    r = format("%#x", 0xabcd);
-    assert(r == "0xabcd");
-    r = format("%#X", 0xABCD);
-    assert(r == "0XABCD");
-
-    r = format("%#o", octal!12345);
-    assert(r == "012345");
-    r = format("%o", 9);
-    assert(r == "11");
-    r = format("%#o", 0);   // issue 15663
-    assert(r == "0");
-
-    r = format("%+d", 123);
-    assert(r == "+123");
-    r = format("%+d", -123);
-    assert(r == "-123");
-    r = format("% d", 123);
-    assert(r == " 123");
-    r = format("% d", -123);
-    assert(r == "-123");
-
-    r = format("%%");
-    assert(r == "%");
-
-    r = format("%d", true);
-    assert(r == "1");
-    r = format("%d", false);
-    assert(r == "0");
-
-    r = format("%d", 'a');
-    assert(r == "97");
-    wchar wc = 'a';
-    r = format("%d", wc);
-    assert(r == "97");
-    dchar dc = 'a';
-    r = format("%d", dc);
-    assert(r == "97");
-
-    byte b = byte.max;
-    r = format("%x", b);
-    assert(r == "7f");
-    r = format("%x", ++b);
-    assert(r == "80");
-    r = format("%x", ++b);
-    assert(r == "81");
-
-    short sh = short.max;
-    r = format("%x", sh);
-    assert(r == "7fff");
-    r = format("%x", ++sh);
-    assert(r == "8000");
-    r = format("%x", ++sh);
-    assert(r == "8001");
-
-    i = int.max;
-    r = format("%x", i);
-    assert(r == "7fffffff");
-    r = format("%x", ++i);
-    assert(r == "80000000");
-    r = format("%x", ++i);
-    assert(r == "80000001");
-
-    r = format("%x", 10);
-    assert(r == "a");
-    r = format("%X", 10);
-    assert(r == "A");
-    r = format("%x", 15);
-    assert(r == "f");
-    r = format("%X", 15);
-    assert(r == "F");
-
-    Object c = null;
-    r = format("%s", c);
-    assert(r == "null");
-
-    enum TestEnum
-    {
-        Value1, Value2
-    }
-    r = format("%s", TestEnum.Value2);
-    assert(r == "Value2");
-
-    immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
-    r = format("%s", aa.values);
-    assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`);
-    r = format("%s", aa);
-    assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`);
-
-    static const dchar[] ds = ['a','b'];
-    for (int j = 0; j < ds.length; ++j)
-    {
-        r = format(" %d", ds[j]);
-        if (j == 0)
-            assert(r == " 97");
-        else
-            assert(r == " 98");
-    }
-
-    r = format(">%14d<, %s", 15, [1,2,3]);
-    assert(r == ">            15<, [1, 2, 3]");
-
-    assert(format("%8s", "bar") == "     bar");
-    assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4");
-}
diff --git a/src/undead/internal/file.d b/src/undead/internal/file.d
deleted file mode 100644
index f756674..0000000
--- a/src/undead/internal/file.d
+++ /dev/null
@@ -1,25 +0,0 @@
-// Written in the D programming language
-
-module undead.internal.file;
-
-// Copied from std.file. undead doesn't have access to it, but some modules
-// in undead used std.file.deleteme when they were in Phobos, so this gives
-// them access to a version of it.
-public @property string deleteme() @safe
-{
-    import std.conv : to;
-    import std.file : tempDir;
-    import std.path : buildPath;
-    import std.process : thisProcessID;
-    static _deleteme = "deleteme.dmd.unittest.pid";
-    static _first = true;
-
-    if(_first)
-    {
-        _deleteme = buildPath(tempDir(), _deleteme) ~ to!string(thisProcessID);
-        _first = false;
-    }
-
-    return _deleteme;
-}
-
diff --git a/src/undead/stream.d b/src/undead/stream.d
deleted file mode 100644
index dc81b7f..0000000
--- a/src/undead/stream.d
+++ /dev/null
@@ -1,3076 +0,0 @@
-// Written in the D programming language
-
-/**
- * $(RED Deprecated: This module is considered out-dated and not up to Phobos'
- *       current standards.)
- *
- * Source:    $(PHOBOSSRC std/_stream.d)
- * Macros:
- *      WIKI = Phobos/StdStream
- */
-
-/*
- * Copyright (c) 2001-2005
- * Pavel "EvilOne" Minayev
- *  with buffering and endian support added by Ben Hinkle
- *  with buffered readLine performance improvements by Dave Fladebo
- *  with opApply inspired by (and mostly copied from) Regan Heath
- *  with bug fixes and MemoryStream/SliceStream enhancements by Derick Eddington
- *
- * Permission to use, copy, modify, distribute and sell this software
- * and its documentation for any purpose is hereby granted without fee,
- * provided that the above copyright notice appear in all copies and
- * that both that copyright notice and this permission notice appear
- * in supporting documentation.  Author makes no representations about
- * the suitability of this software for any purpose. It is provided
- * "as is" without express or implied warranty.
- */
-module undead.stream;
-
-import std.internal.cstring;
-
-/* Class structure:
- *  InputStream       interface for reading
- *  OutputStream      interface for writing
- *  Stream            abstract base of stream implementations
- *    File            an OS file stream
- *    FilterStream    a base-class for wrappers around another stream
- *      BufferedStream  a buffered stream wrapping another stream
- *        BufferedFile  a buffered File
- *      EndianStream    a wrapper stream for swapping byte order and BOMs
- *      SliceStream     a portion of another stream
- *    MemoryStream    a stream entirely stored in main memory
- *    TArrayStream    a stream wrapping an array-like buffer
- */
-
-/// A base class for stream exceptions.
-class StreamException: Exception {
-  /// Construct a StreamException with given error message.
-  this(string msg) { super(msg); }
-}
-
-/// Thrown when unable to read data from Stream.
-class ReadException: StreamException {
-  /// Construct a ReadException with given error message.
-  this(string msg) { super(msg); }
-}
-
-/// Thrown when unable to write data to Stream.
-class WriteException: StreamException {
-  /// Construct a WriteException with given error message.
-  this(string msg) { super(msg); }
-}
-
-/// Thrown when unable to move Stream pointer.
-class SeekException: StreamException {
-  /// Construct a SeekException with given error message.
-  this(string msg) { super(msg); }
-}
-
-// seek whence...
-enum SeekPos {
-  Set,
-  Current,
-  End
-}
-
-private {
-  import std.conv;
-  import std.algorithm;
-  import std.ascii;
-  //import std.format;
-  import std.system;    // for Endian enumeration
-  import std.utf;
-  import core.bitop; // for bswap
-  import core.vararg;
-  import std.file;
-  import undead.internal.file;
-  import undead.doformat;
-}
-
-/// InputStream is the interface for readable streams.
-
-interface InputStream {
-
-  /***
-   * Read exactly size bytes into the buffer.
-   *
-   * Throws a ReadException if it is not correct.
-   */
-  void readExact(void* buffer, size_t size);
-
-  /***
-   * Read a block of data big enough to fill the given array buffer.
-   *
-   * Returns: the actual number of bytes read. Unfilled bytes are not modified.
-   */
-  size_t read(ubyte[] buffer);
-
-  /***
-   * Read a basic type or counted string.
-   *
-   * Throw a ReadException if it could not be read.
-   * Outside of byte, ubyte, and char, the format is
-   * implementation-specific and should not be used except as opposite actions
-   * to write.
-   */
-  void read(out byte x);
-  void read(out ubyte x);       /// ditto
-  void read(out short x);       /// ditto
-  void read(out ushort x);      /// ditto
-  void read(out int x);         /// ditto
-  void read(out uint x);        /// ditto
-  void read(out long x);        /// ditto
-  void read(out ulong x);       /// ditto
-  void read(out float x);       /// ditto
-  void read(out double x);      /// ditto
-  void read(out real x);        /// ditto
-  void read(out ifloat x);      /// ditto
-  void read(out idouble x);     /// ditto
-  void read(out ireal x);       /// ditto
-  void read(out cfloat x);      /// ditto
-  void read(out cdouble x);     /// ditto
-  void read(out creal x);       /// ditto
-  void read(out char x);        /// ditto
-  void read(out wchar x);       /// ditto
-  void read(out dchar x);       /// ditto
-
-  // reads a string, written earlier by write()
-  void read(out char[] s);      /// ditto
-
-  // reads a Unicode string, written earlier by write()
-  void read(out wchar[] s);     /// ditto
-
-  /***
-   * Read a line that is terminated with some combination of carriage return and
-   * line feed or end-of-file.
-   *
-   * The terminators are not included. The wchar version
-   * is identical. The optional buffer parameter is filled (reallocating
-   * it if necessary) and a slice of the result is returned.
-   */
-  char[] readLine();
-  char[] readLine(char[] result);       /// ditto
-  wchar[] readLineW();                  /// ditto
-  wchar[] readLineW(wchar[] result);    /// ditto
-
-  /***
-   * Overload foreach statements to read the stream line by line and call the
-   * supplied delegate with each line or with each line with line number.
-   *
-   * The string passed in line may be reused between calls to the delegate.
-   * Line numbering starts at 1.
-   * Breaking out of the foreach will leave the stream
-   * position at the beginning of the next line to be read.
-   * For example, to echo a file line-by-line with line numbers run:
-   * ------------------------------------
-   * Stream file = new BufferedFile("sample.txt");
-   * foreach(ulong n, char[] line; file)
-   * {
-   *     writefln("line %d: %s", n, line);
-   * }
-   * file.close();
-   * ------------------------------------
-   */
-
-  // iterate through the stream line-by-line
-  int opApply(scope int delegate(ref char[] line) dg);
-  int opApply(scope int delegate(ref ulong n, ref char[] line) dg);  /// ditto
-  int opApply(scope int delegate(ref wchar[] line) dg);            /// ditto
-  int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg); /// ditto
-
-  /// Read a string of the given length,
-  /// throwing ReadException if there was a problem.
-  char[] readString(size_t length);
-
-  /***
-   * Read a string of the given length, throwing ReadException if there was a
-   * problem.
-   *
-   * The file format is implementation-specific and should not be used
-   * except as opposite actions to <b>write</b>.
-   */
-
-  wchar[] readStringW(size_t length);
-
-
-  /***
-   * Read and return the next character in the stream.
-   *
-   * This is the only method that will handle ungetc properly.
-   * getcw's format is implementation-specific.
-   * If EOF is reached then getc returns char.init and getcw returns wchar.init.
-   */
-
-  char getc();
-  wchar getcw(); /// ditto
-
-  /***
-   * Push a character back onto the stream.
-   *
-   * They will be returned in first-in last-out order from getc/getcw.
-   * Only has effect on further calls to getc() and getcw().
-   */
-  char ungetc(char c);
-  wchar ungetcw(wchar c); /// ditto
-
-  /***
-   * Scan a string from the input using a similar form to C's scanf
-   * and <a href="std_format.html">std.format</a>.
-   *
-   * An argument of type string is interpreted as a format string.
-   * All other arguments must be pointer types.
-   * If a format string is not present a default will be supplied computed from
-   * the base type of the pointer type. An argument of type string* is filled
-   * (possibly with appending characters) and a slice of the result is assigned
-   * back into the argument. For example the following readf statements
-   * are equivalent:
-   * --------------------------
-   * int x;
-   * double y;
-   * string s;
-   * file.readf(&x, " hello ", &y, &s);
-   * file.readf("%d hello %f %s", &x, &y, &s);
-   * file.readf("%d hello %f", &x, &y, "%s", &s);
-   * --------------------------
-   */
-  int vreadf(TypeInfo[] arguments, va_list args);
-  int readf(...); /// ditto
-
-  /// Retrieve the number of bytes available for immediate reading.
-  @property size_t available();
-
-  /***
-   * Return whether the current file position is the same as the end of the
-   * file.
-   *
-   * This does not require actually reading past the end, as with stdio. For
-   * non-seekable streams this might only return true after attempting to read
-   * past the end.
-   */
-
-  @property bool eof();
-
-  @property bool isOpen();        /// Return true if the stream is currently open.
-}
-
-/// Interface for writable streams.
-interface OutputStream {
-
-  /***
-   * Write exactly size bytes from buffer, or throw a WriteException if that
-   * could not be done.
-   */
-  void writeExact(const void* buffer, size_t size);
-
-  /***
-   * Write as much of the buffer as possible,
-   * returning the number of bytes written.
-   */
-  size_t write(const(ubyte)[] buffer);
-
-  /***
-   * Write a basic type.
-   *
-   * Outside of byte, ubyte, and char, the format is implementation-specific
-   * and should only be used in conjunction with read.
-   * Throw WriteException on error.
-   */
-  void write(byte x);
-  void write(ubyte x);          /// ditto
-  void write(short x);          /// ditto
-  void write(ushort x);         /// ditto
-  void write(int x);            /// ditto
-  void write(uint x);           /// ditto
-  void write(long x);           /// ditto
-  void write(ulong x);          /// ditto
-  void write(float x);          /// ditto
-  void write(double x);         /// ditto
-  void write(real x);           /// ditto
-  void write(ifloat x);         /// ditto
-  void write(idouble x);        /// ditto
-  void write(ireal x);          /// ditto
-  void write(cfloat x);         /// ditto
-  void write(cdouble x);        /// ditto
-  void write(creal x);          /// ditto
-  void write(char x);           /// ditto
-  void write(wchar x);          /// ditto
-  void write(dchar x);          /// ditto
-
-  /***
-   * Writes a string, together with its length.
-   *
-   * The format is implementation-specific
-   * and should only be used in conjunction with read.
-   * Throw WriteException on error.
-   */
-    void write(const(char)[] s);
-    void write(const(wchar)[] s); /// ditto
-
-  /***
-   * Write a line of text,
-   * appending the line with an operating-system-specific line ending.
-   *
-   * Throws WriteException on error.
-   */
-  void writeLine(const(char)[] s);
-
-  /***
-   * Write a line of text,
-   * appending the line with an operating-system-specific line ending.
-   *
-   * The format is implementation-specific.
-   * Throws WriteException on error.
-   */
-    void writeLineW(const(wchar)[] s);
-
-  /***
-   * Write a string of text.
-   *
-   * Throws WriteException if it could not be fully written.
-   */
-    void writeString(const(char)[] s);
-
-  /***
-   * Write a string of text.
-   *
-   * The format is implementation-specific.
-   * Throws WriteException if it could not be fully written.
-   */
-  void writeStringW(const(wchar)[] s);
-
-  /***
-   * Print a formatted string into the stream using printf-style syntax,
-   * returning the number of bytes written.
-   */
-  size_t vprintf(const(char)[] format, va_list args);
-  size_t printf(const(char)[] format, ...);    /// ditto
-
-  /***
-   * Print a formatted string into the stream using writef-style syntax.
-   * References: <a href="std_format.html">std.format</a>.
-   * Returns: self to chain with other stream commands like flush.
-   */
-  OutputStream writef(...);
-  OutputStream writefln(...); /// ditto
-  OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline = false);  /// ditto
-
-  void flush(); /// Flush pending output if appropriate.
-  void close(); /// Close the stream, flushing output if appropriate.
-  @property bool isOpen(); /// Return true if the stream is currently open.
-}
-
-
-/***
- * Stream is the base abstract class from which the other stream classes derive.
- *
- * Stream's byte order is the format native to the computer.
- *
- * Reading:
- * These methods require that the readable flag be set.
- * Problems with reading result in a ReadException being thrown.
- * Stream implements the InputStream interface in addition to the
- * readBlock method.
- *
- * Writing:
- * These methods require that the writeable flag be set. Problems with writing
- * result in a WriteException being thrown. Stream implements the OutputStream
- * interface in addition to the following methods:
- * writeBlock
- * copyFrom
- * copyFrom
- *
- * Seeking:
- * These methods require that the seekable flag be set.
- * Problems with seeking result in a SeekException being thrown.
- * seek, seekSet, seekCur, seekEnd, position, size, toString, toHash
- */
-
-// not really abstract, but its instances will do nothing useful
-class Stream : InputStream, OutputStream {
-  private import std.string, std.digest.crc, core.stdc.stdlib, core.stdc.stdio;
-
-  // stream abilities
-  bool readable = false;        /// Indicates whether this stream can be read from.
-  bool writeable = false;       /// Indicates whether this stream can be written to.
-  bool seekable = false;        /// Indicates whether this stream can be seeked within.
-  protected bool isopen = true; /// Indicates whether this stream is open.
-
-  protected bool readEOF = false; /** Indicates whether this stream is at eof
-                                   * after the last read attempt.
-                                   */
-
-  protected bool prevCr = false; /** For a non-seekable stream indicates that
-                                  * the last readLine or readLineW ended on a
-                                  * '\r' character.
-                                  */
-
-  this() {}
-
-  /***
-   * Read up to size bytes into the buffer and return the number of bytes
-   * actually read. A return value of 0 indicates end-of-file.
-   */
-  abstract size_t readBlock(void* buffer, size_t size);
-
-  // reads block of data of specified size,
-  // throws ReadException on error
-  void readExact(void* buffer, size_t size) {
-    for(;;) {
-      if (!size) return;
-      size_t readsize = readBlock(buffer, size); // return 0 on eof
-      if (readsize == 0) break;
-      buffer += readsize;
-      size -= readsize;
-    }
-    if (size != 0)
-      throw new ReadException("not enough data in stream");
-  }
-
-  // reads block of data big enough to fill the given
-  // array, returns actual number of bytes read
-  size_t read(ubyte[] buffer) {
-    return readBlock(buffer.ptr, buffer.length);
-  }
-
-  // read a single value of desired type,
-  // throw ReadException on error
-  void read(out byte x) { readExact(&x, x.sizeof); }
-  void read(out ubyte x) { readExact(&x, x.sizeof); }
-  void read(out short x) { readExact(&x, x.sizeof); }
-  void read(out ushort x) { readExact(&x, x.sizeof); }
-  void read(out int x) { readExact(&x, x.sizeof); }
-  void read(out uint x) { readExact(&x, x.sizeof); }
-  void read(out long x) { readExact(&x, x.sizeof); }
-  void read(out ulong x) { readExact(&x, x.sizeof); }
-  void read(out float x) { readExact(&x, x.sizeof); }
-  void read(out double x) { readExact(&x, x.sizeof); }
-  void read(out real x) { readExact(&x, x.sizeof); }
-  void read(out ifloat x) { readExact(&x, x.sizeof); }
-  void read(out idouble x) { readExact(&x, x.sizeof); }
-  void read(out ireal x) { readExact(&x, x.sizeof); }
-  void read(out cfloat x) { readExact(&x, x.sizeof); }
-  void read(out cdouble x) { readExact(&x, x.sizeof); }
-  void read(out creal x) { readExact(&x, x.sizeof); }
-  void read(out char x) { readExact(&x, x.sizeof); }
-  void read(out wchar x) { readExact(&x, x.sizeof); }
-  void read(out dchar x) { readExact(&x, x.sizeof); }
-
-  // reads a string, written earlier by write()
-  void read(out char[] s) {
-    size_t len;
-    read(len);
-    s = readString(len);
-  }
-
-  // reads a Unicode string, written earlier by write()
-  void read(out wchar[] s) {
-    size_t len;
-    read(len);
-    s = readStringW(len);
-  }
-
-  // reads a line, terminated by either CR, LF, CR/LF, or EOF
-  char[] readLine() {
-    return readLine(null);
-  }
-
-  // reads a line, terminated by either CR, LF, CR/LF, or EOF
-  // reusing the memory in buffer if result will fit and otherwise
-  // allocates a new string
-  char[] readLine(char[] result) {
-    size_t strlen = 0;
-    char ch = getc();
-    while (readable) {
-      switch (ch) {
-      case '\r':
-        if (seekable) {
-          ch = getc();
-          if (ch != '\n')
-            ungetc(ch);
-        } else {
-          prevCr = true;
-        }
-        goto case;
-      case '\n':
-      case char.init:
-        result.length = strlen;
-        return result;
-
-      default:
-        if (strlen < result.length) {
-          result[strlen] = ch;
-        } else {
-          result ~= ch;
-        }
-        strlen++;
-      }
-      ch = getc();
-    }
-    result.length = strlen;
-    return result;
-  }
-
-  // reads a Unicode line, terminated by either CR, LF, CR/LF,
-  // or EOF; pretty much the same as the above, working with
-  // wchars rather than chars
-  wchar[] readLineW() {
-    return readLineW(null);
-  }
-
-  // reads a Unicode line, terminated by either CR, LF, CR/LF,
-  // or EOF;
-  // fills supplied buffer if line fits and otherwise allocates a new string.
-  wchar[] readLineW(wchar[] result) {
-    size_t strlen = 0;
-    wchar c = getcw();
-    while (readable) {
-      switch (c) {
-      case '\r':
-        if (seekable) {
-          c = getcw();
-          if (c != '\n')
-            ungetcw(c);
-        } else {
-          prevCr = true;
-        }
-        goto case;
-      case '\n':
-      case wchar.init:
-        result.length = strlen;
-        return result;
-
-      default:
-        if (strlen < result.length) {
-          result[strlen] = c;
-        } else {
-          result ~= c;
-        }
-        strlen++;
-      }
-      c = getcw();
-    }
-    result.length = strlen;
-    return result;
-  }
-
-  // iterate through the stream line-by-line - due to Regan Heath
-  int opApply(scope int delegate(ref char[] line) dg) {
-    int res = 0;
-    char[128] buf;
-    while (!eof) {
-      char[] line = readLine(buf);
-      res = dg(line);
-      if (res) break;
-    }
-    return res;
-  }
-
-  // iterate through the stream line-by-line with line count and string
-  int opApply(scope int delegate(ref ulong n, ref char[] line) dg) {
-    int res = 0;
-    ulong n = 1;
-    char[128] buf;
-    while (!eof) {
-      auto line = readLine(buf);
-      res = dg(n,line);
-      if (res) break;
-      n++;
-    }
-    return res;
-  }
-
-  // iterate through the stream line-by-line with wchar[]
-  int opApply(scope int delegate(ref wchar[] line) dg) {
-    int res = 0;
-    wchar[128] buf;
-    while (!eof) {
-      auto line = readLineW(buf);
-      res = dg(line);
-      if (res) break;
-    }
-    return res;
-  }
-
-  // iterate through the stream line-by-line with line count and wchar[]
-  int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg) {
-    int res = 0;
-    ulong n = 1;
-    wchar[128] buf;
-    while (!eof) {
-      auto line = readLineW(buf);
-      res = dg(n,line);
-      if (res) break;
-      n++;
-    }
-    return res;
-  }
-
-  // reads a string of given length, throws
-  // ReadException on error
-  char[] readString(size_t length) {
-    char[] result = new char[length];
-    readExact(result.ptr, length);
-    return result;
-  }
-
-  // reads a Unicode string of given length, throws
-  // ReadException on error
-  wchar[] readStringW(size_t length) {
-    auto result = new wchar[length];
-    readExact(result.ptr, result.length * wchar.sizeof);
-    return result;
-  }
-
-  // unget buffer
-  private wchar[] unget;
-  final bool ungetAvailable() { return unget.length > 1; }
-
-  // reads and returns next character from the stream,
-  // handles characters pushed back by ungetc()
-  // returns char.init on eof.
-  char getc() {
-    char c;
-    if (prevCr) {
-      prevCr = false;
-      c = getc();
-      if (c != '\n')
-        return c;
-    }
-    if (unget.length > 1) {
-      c = cast(char)unget[unget.length - 1];
-      unget.length = unget.length - 1;
-    } else {
-      readBlock(&c,1);
-    }
-    return c;
-  }
-
-  // reads and returns next Unicode character from the
-  // stream, handles characters pushed back by ungetc()
-  // returns wchar.init on eof.
-  wchar getcw() {
-    wchar c;
-    if (prevCr) {
-      prevCr = false;
-      c = getcw();
-      if (c != '\n')
-        return c;
-    }
-    if (unget.length > 1) {
-      c = unget[unget.length - 1];
-      unget.length = unget.length - 1;
-    } else {
-      void* buf = &c;
-      size_t n = readBlock(buf,2);
-      if (n == 1 && readBlock(buf+1,1) == 0)
-          throw new ReadException("not enough data in stream");
-    }
-    return c;
-  }
-
-  // pushes back character c into the stream; only has
-  // effect on further calls to getc() and getcw()
-  char ungetc(char c) {
-    if (c == c.init) return c;
-    // first byte is a dummy so that we never set length to 0
-    if (unget.length == 0)
-      unget.length = 1;
-    unget ~= c;
-    return c;
-  }
-
-  // pushes back Unicode character c into the stream; only
-  // has effect on further calls to getc() and getcw()
-  wchar ungetcw(wchar c) {
-    if (c == c.init) return c;
-    // first byte is a dummy so that we never set length to 0
-    if (unget.length == 0)
-      unget.length = 1;
-    unget ~= c;
-    return c;
-  }
-
-  int vreadf(TypeInfo[] arguments, va_list args) {
-    string fmt;
-    int j = 0;
-    int count = 0, i = 0;
-    char c;
-    bool firstCharacter = true;
-    while ((j < arguments.length || i < fmt.length) && !eof) {
-      if(firstCharacter) {
-        c = getc();
-        firstCharacter = false;
-      }
-      if (fmt.length == 0 || i == fmt.length) {
-        i = 0;
-        if (arguments[j] is typeid(string) || arguments[j] is typeid(char[])
-            || arguments[j] is typeid(const(char)[])) {
-          fmt = va_arg!(string)(args);
-          j++;
-          continue;
-        } else if (arguments[j] is typeid(int*) ||
-                   arguments[j] is typeid(byte*) ||
-                   arguments[j] is typeid(short*) ||
-                   arguments[j] is typeid(long*)) {
-          fmt = "%d";
-        } else if (arguments[j] is typeid(uint*) ||
-                   arguments[j] is typeid(ubyte*) ||
-                   arguments[j] is typeid(ushort*) ||
-                   arguments[j] is typeid(ulong*)) {
-          fmt = "%d";
-        } else if (arguments[j] is typeid(float*) ||
-                   arguments[j] is typeid(double*) ||
-                   arguments[j] is typeid(real*)) {
-          fmt = "%f";
-        } else if (arguments[j] is typeid(char[]*) ||
-                   arguments[j] is typeid(wchar[]*) ||
-                   arguments[j] is typeid(dchar[]*)) {
-          fmt = "%s";
-        } else if (arguments[j] is typeid(char*)) {
-          fmt = "%c";
-        }
-      }
-      if (fmt[i] == '%') {      // a field
-        i++;
-        bool suppress = false;
-        if (fmt[i] == '*') {    // suppress assignment
-          suppress = true;
-          i++;
-        }
-        // read field width
-        int width = 0;
-        while (isDigit(fmt[i])) {
-          width = width * 10 + (fmt[i] - '0');
-          i++;
-        }
-        if (width == 0)
-          width = -1;
-        // skip any modifier if present
-        if (fmt[i] == 'h' || fmt[i] == 'l' || fmt[i] == 'L')
-          i++;
-        // check the typechar and act accordingly
-        switch (fmt[i]) {
-        case 'd':       // decimal/hexadecimal/octal integer
-        case 'D':
-        case 'u':
-        case 'U':
-        case 'o':
-        case 'O':
-        case 'x':
-        case 'X':
-        case 'i':
-        case 'I':
-          {
-            while (isWhite(c)) {
-              c = getc();
-              count++;
-            }
-            bool neg = false;
-            if (c == '-') {
-              neg = true;
-              c = getc();
-              count++;
-            } else if (c == '+') {
-              c = getc();
-              count++;
-            }
-            char ifmt = cast(char)(fmt[i] | 0x20);
-            if (ifmt == 'i')    { // undetermined base
-              if (c == '0')     { // octal or hex
-                c = getc();
-                count++;
-                if (c == 'x' || c == 'X')       { // hex
-                  ifmt = 'x';
-                  c = getc();
-                  count++;
-                } else {        // octal
-                  ifmt = 'o';
-                }
-              }
-              else      // decimal
-                ifmt = 'd';
-            }
-            long n = 0;
-            switch (ifmt)
-            {
-                case 'd':       // decimal
-                case 'u': {
-                  while (isDigit(c) && width) {
-                    n = n * 10 + (c - '0');
-                    width--;
-                    c = getc();
-                    count++;
-                  }
-                } break;
-
-                case 'o': {     // octal
-                  while (isOctalDigit(c) && width) {
-                    n = n * 8 + (c - '0');
-                    width--;
-                    c = getc();
-                    count++;
-                  }
-                } break;
-
-                case 'x': {     // hexadecimal
-                  while (isHexDigit(c) && width) {
-                    n *= 0x10;
-                    if (isDigit(c))
-                      n += c - '0';
-                    else
-                      n += 0xA + (c | 0x20) - 'a';
-                    width--;
-                    c = getc();
-                    count++;
-                  }
-                } break;
-
-                default:
-                    assert(0);
-            }
-            if (neg)
-              n = -n;
-            if (arguments[j] is typeid(int*)) {
-              int* p = va_arg!(int*)(args);
-              *p = cast(int)n;
-            } else if (arguments[j] is typeid(short*)) {
-              short* p = va_arg!(short*)(args);
-              *p = cast(short)n;
-            } else if (arguments[j] is typeid(byte*)) {
-              byte* p = va_arg!(byte*)(args);
-              *p = cast(byte)n;
-            } else if (arguments[j] is typeid(long*)) {
-              long* p = va_arg!(long*)(args);
-              *p = n;
-            } else if (arguments[j] is typeid(uint*)) {
-              uint* p = va_arg!(uint*)(args);
-              *p = cast(uint)n;
-            } else if (arguments[j] is typeid(ushort*)) {
-              ushort* p = va_arg!(ushort*)(args);
-              *p = cast(ushort)n;
-            } else if (arguments[j] is typeid(ubyte*)) {
-              ubyte* p = va_arg!(ubyte*)(args);
-              *p = cast(ubyte)n;
-            } else if (arguments[j] is typeid(ulong*)) {
-              ulong* p = va_arg!(ulong*)(args);
-              *p = cast(ulong)n;
-            }
-            j++;
-            i++;
-          } break;
-
-        case 'f':       // float
-        case 'F':
-        case 'e':
-        case 'E':
-        case 'g':
-        case 'G':
-          {
-            while (isWhite(c)) {
-              c = getc();
-              count++;
-            }
-            bool neg = false;
-            if (c == '-') {
-              neg = true;
-              c = getc();
-              count++;
-            } else if (c == '+') {
-              c = getc();
-              count++;
-            }
-            real r = 0;
-            while (isDigit(c) && width) {
-              r = r * 10 + (c - '0');
-              width--;
-              c = getc();
-              count++;
-            }
-            if (width && c == '.') {
-              width--;
-              c = getc();
-              count++;
-              double frac = 1;
-              while (isDigit(c) && width) {
-                r = r * 10 + (c - '0');
-                frac *= 10;
-                width--;
-                c = getc();
-                count++;
-              }
-              r /= frac;
-            }
-            if (width && (c == 'e' || c == 'E')) {
-              width--;
-              c = getc();
-              count++;
-              if (width) {
-                bool expneg = false;
-                if (c == '-') {
-                  expneg = true;
-                  width--;
-                  c = getc();
-                  count++;
-                } else if (c == '+') {
-                  width--;
-                  c = getc();
-                  count++;
-                }
-                real exp = 0;
-                while (isDigit(c) && width) {
-                  exp = exp * 10 + (c - '0');
-                  width--;
-                  c = getc();
-                  count++;
-                }
-                if (expneg) {
-                  while (exp--)
-                    r /= 10;
-                } else {
-                  while (exp--)
-                    r *= 10;
-                }
-              }
-            }
-            if(width && (c == 'n' || c == 'N')) {
-              width--;
-              c = getc();
-              count++;
-              if(width && (c == 'a' || c == 'A')) {
-                width--;
-                c = getc();
-                count++;
-                if(width && (c == 'n' || c == 'N')) {
-                  width--;
-                  c = getc();
-                  count++;
-                  r = real.nan;
-                }
-              }
-            }
-            if(width && (c == 'i' || c == 'I')) {
-              width--;
-              c = getc();
-              count++;
-              if(width && (c == 'n' || c == 'N')) {
-                width--;
-                c = getc();
-                count++;
-                if(width && (c == 'f' || c == 'F')) {
-                  width--;
-                  c = getc();
-                  count++;
-                  r = real.infinity;
-                }
-              }
-            }
-            if (neg)
-              r = -r;
-            if (arguments[j] is typeid(float*)) {
-              float* p = va_arg!(float*)(args);
-              *p = r;
-            } else if (arguments[j] is typeid(double*)) {
-              double* p = va_arg!(double*)(args);
-              *p = r;
-            } else if (arguments[j] is typeid(real*)) {
-              real* p = va_arg!(real*)(args);
-              *p = r;
-            }
-            j++;
-            i++;
-          } break;
-
-        case 's': {     // string
-          while (isWhite(c)) {
-            c = getc();
-            count++;
-          }
-          char[] s;
-          char[]* p;
-          size_t strlen;
-          if (arguments[j] is typeid(char[]*)) {
-            p = va_arg!(char[]*)(args);
-            s = *p;
-          }
-          while (!isWhite(c) && c != char.init) {
-            if (strlen < s.length) {
-              s[strlen] = c;
-            } else {
-              s ~= c;
-            }
-            strlen++;
-            c = getc();
-            count++;
-          }
-          s = s[0 .. strlen];
-          if (arguments[j] is typeid(char[]*)) {
-            *p = s;
-          } else if (arguments[j] is typeid(char*)) {
-            s ~= 0;
-            auto q = va_arg!(char*)(args);
-            q[0 .. s.length] = s[];
-          } else if (arguments[j] is typeid(wchar[]*)) {
-            auto q = va_arg!(const(wchar)[]*)(args);
-            *q = toUTF16(s);
-          } else if (arguments[j] is typeid(dchar[]*)) {
-            auto q = va_arg!(const(dchar)[]*)(args);
-            *q = toUTF32(s);
-          }
-          j++;
-          i++;
-        } break;
-
-        case 'c': {     // character(s)
-          char* s = va_arg!(char*)(args);
-          if (width < 0)
-            width = 1;
-          else
-            while (isWhite(c)) {
-            c = getc();
-            count++;
-          }
-          while (width-- && !eof) {
-            *(s++) = c;
-            c = getc();
-            count++;
-          }
-          j++;
-          i++;
-        } break;
-
-        case 'n': {     // number of chars read so far
-          int* p = va_arg!(int*)(args);
-          *p = count;
-          j++;
-          i++;
-        } break;
-
-        default:        // read character as is
-          goto nws;
-        }
-      } else if (isWhite(fmt[i])) {     // skip whitespace
-        while (isWhite(c))
-          c = getc();
-        i++;
-      } else {  // read character as is
-      nws:
-        if (fmt[i] != c)
-          break;
-        c = getc();
-        i++;
-      }
-    }
-    ungetc(c);
-    return count;
-  }
-
-  int readf(...) {
-    return vreadf(_arguments, _argptr);
-  }
-
-  // returns estimated number of bytes available for immediate reading
-  @property size_t available() { return 0; }
-
-  /***
-   * Write up to size bytes from buffer in the stream, returning the actual
-   * number of bytes that were written.
-   */
-  abstract size_t writeBlock(const void* buffer, size_t size);
-
-  // writes block of data of specified size,
-  // throws WriteException on error
-  void writeExact(const void* buffer, size_t size) {
-    const(void)* p = buffer;
-    for(;;) {
-      if (!size) return;
-      size_t writesize = writeBlock(p, size);
-      if (writesize == 0) break;
-      p += writesize;
-      size -= writesize;
-    }
-    if (size != 0)
-      throw new WriteException("unable to write to stream");
-  }
-
-  // writes the given array of bytes, returns
-  // actual number of bytes written
-  size_t write(const(ubyte)[] buffer) {
-    return writeBlock(buffer.ptr, buffer.length);
-  }
-
-  // write a single value of desired type,
-  // throw WriteException on error
-  void write(byte x) { writeExact(&x, x.sizeof); }
-  void write(ubyte x) { writeExact(&x, x.sizeof); }
-  void write(short x) { writeExact(&x, x.sizeof); }
-  void write(ushort x) { writeExact(&x, x.sizeof); }
-  void write(int x) { writeExact(&x, x.sizeof); }
-  void write(uint x) { writeExact(&x, x.sizeof); }
-  void write(long x) { writeExact(&x, x.sizeof); }
-  void write(ulong x) { writeExact(&x, x.sizeof); }
-  void write(float x) { writeExact(&x, x.sizeof); }
-  void write(double x) { writeExact(&x, x.sizeof); }
-  void write(real x) { writeExact(&x, x.sizeof); }
-  void write(ifloat x) { writeExact(&x, x.sizeof); }
-  void write(idouble x) { writeExact(&x, x.sizeof); }
-  void write(ireal x) { writeExact(&x, x.sizeof); }
-  void write(cfloat x) { writeExact(&x, x.sizeof); }
-  void write(cdouble x) { writeExact(&x, x.sizeof); }
-  void write(creal x) { writeExact(&x, x.sizeof); }
-  void write(char x) { writeExact(&x, x.sizeof); }
-  void write(wchar x) { writeExact(&x, x.sizeof); }
-  void write(dchar x) { writeExact(&x, x.sizeof); }
-
-  // writes a string, together with its length
-  void write(const(char)[] s) {
-    write(s.length);
-    writeString(s);
-  }
-
-  // writes a Unicode string, together with its length
-  void write(const(wchar)[] s) {
-    write(s.length);
-    writeStringW(s);
-  }
-
-  // writes a line, throws WriteException on error
-  void writeLine(const(char)[] s) {
-    writeString(s);
-    version (Windows)
-      writeString("\r\n");
-    else version (Mac)
-      writeString("\r");
-    else
-      writeString("\n");
-  }
-
-  // writes a Unicode line, throws WriteException on error
-  void writeLineW(const(wchar)[] s) {
-    writeStringW(s);
-    version (Windows)
-      writeStringW("\r\n");
-    else version (Mac)
-      writeStringW("\r");
-    else
-      writeStringW("\n");
-  }
-
-  // writes a string, throws WriteException on error
-  void writeString(const(char)[] s) {
-    writeExact(s.ptr, s.length);
-  }
-
-  // writes a Unicode string, throws WriteException on error
-  void writeStringW(const(wchar)[] s) {
-    writeExact(s.ptr, s.length * wchar.sizeof);
-  }
-
-  // writes data to stream using vprintf() syntax,
-  // returns number of bytes written
-  size_t vprintf(const(char)[] format, va_list args) {
-    // shamelessly stolen from OutBuffer,
-    // by Walter's permission
-    char[1024] buffer;
-    char* p = buffer.ptr;
-    // Can't use `tempCString()` here as it will result in compilation error:
-    // "cannot mix core.std.stdlib.alloca() and exception handling".
-    auto f = toStringz(format);
-    size_t psize = buffer.length;
-    size_t count;
-    while (true) {
-      version (Windows) {
-        count = vsnprintf(p, psize, f, args);
-        if (count != -1)
-          break;
-        psize *= 2;
-        p = cast(char*) alloca(psize);
-      } else version (Posix) {
-        count = vsnprintf(p, psize, f, args);
-        if (count == -1)
-          psize *= 2;
-        else if (count >= psize)
-          psize = count + 1;
-        else
-          break;
-        p = cast(char*) alloca(psize);
-      } else
-          throw new Exception("unsupported platform");
-    }
-    writeString(p[0 .. count]);
-    return count;
-  }
-
-  // writes data to stream using printf() syntax,
-  // returns number of bytes written
-  size_t printf(const(char)[] format, ...) {
-    va_list ap;
-    va_start(ap, format);
-    auto result = vprintf(format, ap);
-    va_end(ap);
-    return result;
-  }
-
-  private void doFormatCallback(dchar c) {
-    char[4] buf;
-    auto b = std.utf.toUTF8(buf, c);
-    writeString(b);
-  }
-
-  // writes data to stream using writef() syntax,
-  OutputStream writef(...) {
-    return writefx(_arguments,_argptr,0);
-  }
-
-  // writes data with trailing newline
-  OutputStream writefln(...) {
-    return writefx(_arguments,_argptr,1);
-  }
-
-  // writes data with optional trailing newline
-  OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline=false) {
-    doFormat(&doFormatCallback,arguments,argptr);
-    if (newline)
-      writeLine("");
-    return this;
-  }
-
-  /***
-   * Copies all data from s into this stream.
-   * This may throw ReadException or WriteException on failure.
-   * This restores the file position of s so that it is unchanged.
-   */
-  void copyFrom(Stream s) {
-    if (seekable) {
-      ulong pos = s.position;
-      s.position = 0;
-      copyFrom(s, s.size);
-      s.position = pos;
-    } else {
-      ubyte[128] buf;
-      while (!s.eof) {
-        size_t m = s.readBlock(buf.ptr, buf.length);
-        writeExact(buf.ptr, m);
-      }
-    }
-  }
-
-  /***
-   * Copy a specified number of bytes from the given stream into this one.
-   * This may throw ReadException or WriteException on failure.
-   * Unlike the previous form, this doesn't restore the file position of s.
-   */
-  void copyFrom(Stream s, ulong count) {
-    ubyte[128] buf;
-    while (count > 0) {
-      size_t n = cast(size_t)(count<buf.length ? count : buf.length);
-      s.readExact(buf.ptr, n);
-      writeExact(buf.ptr, n);
-      count -= n;
-    }
-  }
-
-  /***
-   * Change the current position of the stream. whence is either SeekPos.Set, in
-   which case the offset is an absolute index from the beginning of the stream,
-   SeekPos.Current, in which case the offset is a delta from the current
-   position, or SeekPos.End, in which case the offset is a delta from the end of
-   the stream (negative or zero offsets only make sense in that case). This
-   returns the new file position.
-   */
-  abstract ulong seek(long offset, SeekPos whence);
-
-  /***
-   * Aliases for their normal seek counterparts.
-   */
-  ulong seekSet(long offset) { return seek (offset, SeekPos.Set); }
-  ulong seekCur(long offset) { return seek (offset, SeekPos.Current); } /// ditto
-  ulong seekEnd(long offset) { return seek (offset, SeekPos.End); }     /// ditto
-
-  /***
-   * Sets file position. Equivalent to calling seek(pos, SeekPos.Set).
-   */
-  @property void position(ulong pos) { seek(cast(long)pos, SeekPos.Set); }
-
-  /***
-   * Returns current file position. Equivalent to seek(0, SeekPos.Current).
-   */
-  @property ulong position() { return seek(0, SeekPos.Current); }
-
-  /***
-   * Retrieve the size of the stream in bytes.
-   * The stream must be seekable or a SeekException is thrown.
-   */
-  @property ulong size() {
-    assertSeekable();
-    ulong pos = position, result = seek(0, SeekPos.End);
-    position = pos;
-    return result;
-  }
-
-  // returns true if end of stream is reached, false otherwise
-  @property bool eof() {
-    // for unseekable streams we only know the end when we read it
-    if (readEOF && !ungetAvailable())
-      return true;
-    else if (seekable)
-      return position == size;
-    else
-      return false;
-  }
-
-  // returns true if the stream is open
-  @property bool isOpen() { return isopen; }
-
-  // flush the buffer if writeable
-  void flush() {
-    if (unget.length > 1)
-      unget.length = 1; // keep at least 1 so that data ptr stays
-  }
-
-  // close the stream somehow; the default just flushes the buffer
-  void close() {
-    if (isopen)
-      flush();
-    readEOF = prevCr = isopen = readable = writeable = seekable = false;
-  }
-
-  /***
-   * Read the entire stream and return it as a string.
-   * If the stream is not seekable the contents from the current position to eof
-   * is read and returned.
-   */
-  override string toString() {
-    if (!readable)
-      return super.toString();
-    try
-    {
-        size_t pos;
-        size_t rdlen;
-        size_t blockSize;
-        char[] result;
-        if (seekable) {
-          ulong orig_pos = position;
-          scope(exit) position = orig_pos;
-          position = 0;
-          blockSize = cast(size_t)size;
-          result = new char[blockSize];
-          while (blockSize > 0) {
-            rdlen = readBlock(&result[pos], blockSize);
-            pos += rdlen;
-            blockSize -= rdlen;
-          }
-        } else {
-          blockSize = 4096;
-          result = new char[blockSize];
-          while ((rdlen = readBlock(&result[pos], blockSize)) > 0) {
-            pos += rdlen;
-            blockSize += rdlen;
-            result.length = result.length + blockSize;
-          }
-        }
-        return cast(string) result[0 .. pos];
-    }
-    catch (Throwable)
-    {
-        return super.toString();
-    }
-  }
-
-  /***
-   * Get a hash of the stream by reading each byte and using it in a CRC-32
-   * checksum.
-   */
-  override size_t toHash() @trusted {
-    if (!readable || !seekable)
-      return super.toHash();
-    try
-    {
-        ulong pos = position;
-        scope(exit) position = pos;
-        CRC32 crc;
-        crc.start();
-        position = 0;
-        ulong len = size;
-        for (ulong i = 0; i < len; i++)
-        {
-          ubyte c;
-          read(c);
-          crc.put(c);
-        }
-
-        union resUnion
-        {
-            size_t hash;
-            ubyte[4] crcVal;
-        }
-        resUnion res;
-        res.crcVal = crc.finish();
-        return res.hash;
-    }
-    catch (Throwable)
-    {
-        return super.toHash();
-    }
-  }
-
-  // helper for checking that the stream is readable
-  final protected void assertReadable() {
-    if (!readable)
-      throw new ReadException("Stream is not readable");
-  }
-  // helper for checking that the stream is writeable
-  final protected void assertWriteable() {
-    if (!writeable)
-      throw new WriteException("Stream is not writeable");
-  }
-  // helper for checking that the stream is seekable
-  final protected void assertSeekable() {
-    if (!seekable)
-      throw new SeekException("Stream is not seekable");
-  }
-  /+
-  unittest { // unit test for Issue 3363
-    import std.stdio;
-    immutable fileName = undead.internal.file.deleteme ~ "-issue3363.txt";
-    auto w = File(fileName, "w");
-    scope (exit) remove(fileName.ptr);
-    w.write("one two three");
-    w.close();
-    auto r = File(fileName, "r");
-    const(char)[] constChar;
-    string str;
-    char[] chars;
-    r.readf("%s %s %s", &constChar, &str, &chars);
-    assert (constChar == "one", constChar);
-    assert (str == "two", str);
-    assert (chars == "three", chars);
-  }
-
-  unittest { //unit tests for Issue 1668
-    void tryFloatRoundtrip(float x, string fmt = "", string pad = "") {
-      auto s = new MemoryStream();
-      s.writef(fmt, x, pad);
-      s.position = 0;
-
-      float f;
-      assert(s.readf(&f));
-      assert(x == f || (x != x && f != f)); //either equal or both NaN
-    }
-
-    tryFloatRoundtrip(1.0);
-    tryFloatRoundtrip(1.0, "%f");
-    tryFloatRoundtrip(1.0, "", " ");
-    tryFloatRoundtrip(1.0, "%f", " ");
-
-    tryFloatRoundtrip(3.14);
-    tryFloatRoundtrip(3.14, "%f");
-    tryFloatRoundtrip(3.14, "", " ");
-    tryFloatRoundtrip(3.14, "%f", " ");
-
-    float nan = float.nan;
-    tryFloatRoundtrip(nan);
-    tryFloatRoundtrip(nan, "%f");
-    tryFloatRoundtrip(nan, "", " ");
-    tryFloatRoundtrip(nan, "%f", " ");
-
-    float inf = 1.0/0.0;
-    tryFloatRoundtrip(inf);
-    tryFloatRoundtrip(inf, "%f");
-    tryFloatRoundtrip(inf, "", " ");
-    tryFloatRoundtrip(inf, "%f", " ");
-
-    tryFloatRoundtrip(-inf);
-    tryFloatRoundtrip(-inf,"%f");
-    tryFloatRoundtrip(-inf, "", " ");
-    tryFloatRoundtrip(-inf, "%f", " ");
-  }
-  +/
-}
-
-/***
- * A base class for streams that wrap a source stream with additional
- * functionality.
- *
- * The method implementations forward read/write/seek calls to the
- * source stream. A FilterStream can change the position of the source stream
- * arbitrarily and may not keep the source stream state in sync with the
- * FilterStream, even upon flushing and closing the FilterStream. It is
- * recommended to not make any assumptions about the state of the source position
- * and read/write state after a FilterStream has acted upon it. Specifc subclasses
- * of FilterStream should document how they modify the source stream and if any
- * invariants hold true between the source and filter.
- */
-class FilterStream : Stream {
-  private Stream s;              // source stream
-
-  /// Property indicating when this stream closes to close the source stream as
-  /// well.
-  /// Defaults to true.
-  bool nestClose = true;
-
-  /// Construct a FilterStream for the given source.
-  this(Stream source) {
-    s = source;
-    resetSource();
-  }
-
-  // source getter/setter
-
-  /***
-   * Get the current source stream.
-   */
-  final Stream source(){return s;}
-
-  /***
-   * Set the current source stream.
-   *
-   * Setting the source stream closes this stream before attaching the new
-   * source. Attaching an open stream reopens this stream and resets the stream
-   * state.
-   */
-  void source(Stream s) {
-    close();
-    this.s = s;
-    resetSource();
-  }
-
-  /***
-   * Indicates the source stream changed state and that this stream should reset
-   * any readable, writeable, seekable, isopen and buffering flags.
-   */
-  void resetSource() {
-    if (s !is null) {
-      readable = s.readable;
-      writeable = s.writeable;
-      seekable = s.seekable;
-      isopen = s.isOpen;
-    } else {
-      readable = writeable = seekable = false;
-      isopen = false;
-    }
-    readEOF = prevCr = false;
-  }
-
-  // read from source
-  override size_t readBlock(void* buffer, size_t size) {
-    size_t res = s.readBlock(buffer,size);
-    readEOF = res == 0;
-    return res;
-  }
-
-  // write to source
-  override size_t writeBlock(const void* buffer, size_t size) {
-    return s.writeBlock(buffer,size);
-  }
-
-  // close stream
-  override void close() {
-    if (isopen) {
-      super.close();
-      if (nestClose)
-        s.close();
-    }
-  }
-
-  // seek on source
-  override ulong seek(long offset, SeekPos whence) {
-    readEOF = false;
-    return s.seek(offset,whence);
-  }
-
-  override @property size_t available() { return s.available; }
-  override void flush() { super.flush(); s.flush(); }
-}
-
-/***
- * This subclass is for buffering a source stream.
- *
- * A buffered stream must be
- * closed explicitly to ensure the final buffer content is written to the source
- * stream. The source stream position is changed according to the block size so
- * reading or writing to the BufferedStream may not change the source stream
- * position by the same amount.
- */
-class BufferedStream : FilterStream {
-  ubyte[] buffer;       // buffer, if any
-  size_t bufferCurPos;    // current position in buffer
-  size_t bufferLen;       // amount of data in buffer
-  bool bufferDirty = false;
-  size_t bufferSourcePos; // position in buffer of source stream position
-  ulong streamPos;      // absolute position in source stream
-
-  /* Example of relationship between fields:
-   *
-   *  s             ...01234567890123456789012EOF
-   *  buffer                |--                     --|
-   *  bufferCurPos                       |
-   *  bufferLen             |--            --|
-   *  bufferSourcePos                        |
-   *
-   */
-
-  invariant() {
-    assert(bufferSourcePos <= bufferLen);
-    assert(bufferCurPos <= bufferLen);
-    assert(bufferLen <= buffer.length);
-  }
-
-  enum size_t DefaultBufferSize = 8192;
-
-  /***
-   * Create a buffered stream for the stream source with the buffer size
-   * bufferSize.
-   */
-  this(Stream source, size_t bufferSize = DefaultBufferSize) {
-    super(source);
-    if (bufferSize)
-      buffer = new ubyte[bufferSize];
-  }
-
-  override protected void resetSource() {
-    super.resetSource();
-    streamPos = 0;
-    bufferLen = bufferSourcePos = bufferCurPos = 0;
-    bufferDirty = false;
-  }
-
-  // reads block of data of specified size using any buffered data
-  // returns actual number of bytes read
-  override size_t readBlock(void* result, size_t len) {
-    if (len == 0) return 0;
-
-    assertReadable();
-
-    ubyte* outbuf = cast(ubyte*)result;
-    size_t readsize = 0;
-
-    if (bufferCurPos + len < bufferLen) {
-      // buffer has all the data so copy it
-      outbuf[0 .. len] = buffer[bufferCurPos .. bufferCurPos+len];
-      bufferCurPos += len;
-      readsize = len;
-      goto ExitRead;
-    }
-
-    readsize = bufferLen - bufferCurPos;
-    if (readsize > 0) {
-      // buffer has some data so copy what is left
-      outbuf[0 .. readsize] = buffer[bufferCurPos .. bufferLen];
-      outbuf += readsize;
-      bufferCurPos += readsize;
-      len -= readsize;
-    }
-
-    flush();
-
-    if (len >= buffer.length) {
-      // buffer can't hold the data so fill output buffer directly
-      size_t siz = super.readBlock(outbuf, len);
-      readsize += siz;
-      streamPos += siz;
-    } else {
-      // read a new block into buffer
-        bufferLen = super.readBlock(buffer.ptr, buffer.length);
-        if (bufferLen < len) len = bufferLen;
-        outbuf[0 .. len] = buffer[0 .. len];
-        bufferSourcePos = bufferLen;
-        streamPos += bufferLen;
-        bufferCurPos = len;
-        readsize += len;
-    }
-
-  ExitRead:
-    return readsize;
-  }
-
-  // write block of data of specified size
-  // returns actual number of bytes written
-  override size_t writeBlock(const void* result, size_t len) {
-    assertWriteable();
-
-    ubyte* buf = cast(ubyte*)result;
-    size_t writesize = 0;
-
-    if (bufferLen == 0) {
-      // buffer is empty so fill it if possible
-      if ((len < buffer.length) && (readable)) {
-        // read in data if the buffer is currently empty
-        bufferLen = s.readBlock(buffer.ptr, buffer.length);
-        bufferSourcePos = bufferLen;
-        streamPos += bufferLen;
-
-      } else if (len >= buffer.length) {
-        // buffer can't hold the data so write it directly and exit
-        writesize = s.writeBlock(buf,len);
-        streamPos += writesize;
-        goto ExitWrite;
-      }
-    }
-
-    if (bufferCurPos + len <= buffer.length) {
-      // buffer has space for all the data so copy it and exit
-      buffer[bufferCurPos .. bufferCurPos+len] = buf[0 .. len];
-      bufferCurPos += len;
-      bufferLen = bufferCurPos > bufferLen ? bufferCurPos : bufferLen;
-      writesize = len;
-      bufferDirty = true;
-      goto ExitWrite;
-    }
-
-    writesize = buffer.length - bufferCurPos;
-    if (writesize > 0) {
-      // buffer can take some data
-      buffer[bufferCurPos .. buffer.length] = buf[0 .. writesize];
-      bufferCurPos = bufferLen = buffer.length;
-      buf += writesize;
-      len -= writesize;
-      bufferDirty = true;
-    }
-
-    assert(bufferCurPos == buffer.length);
-    assert(bufferLen == buffer.length);
-
-    flush();
-
-    writesize += writeBlock(buf,len);
-
-  ExitWrite:
-    return writesize;
-  }
-
-  override ulong seek(long offset, SeekPos whence) {
-    assertSeekable();
-
-    if ((whence != SeekPos.Current) ||
-        (offset + bufferCurPos < 0) ||
-        (offset + bufferCurPos >= bufferLen)) {
-      flush();
-      streamPos = s.seek(offset,whence);
-    } else {
-      bufferCurPos += offset;
-    }
-    readEOF = false;
-    return streamPos-bufferSourcePos+bufferCurPos;
-  }
-
-  // Buffered readLine - Dave Fladebo
-  // reads a line, terminated by either CR, LF, CR/LF, or EOF
-  // reusing the memory in buffer if result will fit, otherwise
-  // will reallocate (using concatenation)
-  template TreadLine(T) {
-      T[] readLine(T[] inBuffer)
-      {
-          size_t    lineSize = 0;
-          bool    haveCR = false;
-          T       c = '\0';
-          size_t    idx = 0;
-          ubyte*  pc = cast(ubyte*)&c;
-
-        L0:
-          for(;;) {
-              size_t start = bufferCurPos;
-            L1:
-              foreach(ubyte b; buffer[start .. bufferLen]) {
-                  bufferCurPos++;
-                  pc[idx] = b;
-                  if(idx < T.sizeof - 1) {
-                      idx++;
-                      continue L1;
-                  } else {
-                      idx = 0;
-                  }
-                  if(c == '\n' || haveCR) {
-                      if(haveCR && c != '\n') bufferCurPos--;
-                      break L0;
-                  } else {
-                      if(c == '\r') {
-                          haveCR = true;
-                      } else {
-                          if(lineSize < inBuffer.length) {
-                              inBuffer[lineSize] = c;
-                          } else {
-                              inBuffer ~= c;
-                          }
-                          lineSize++;
-                      }
-                  }
-              }
-              flush();
-              size_t res = super.readBlock(buffer.ptr, buffer.length);
-              if(!res) break L0; // EOF
-              bufferSourcePos = bufferLen = res;
-              streamPos += res;
-          }
-          return inBuffer[0 .. lineSize];
-      }
-  } // template TreadLine(T)
-
-  override char[] readLine(char[] inBuffer) {
-    if (ungetAvailable())
-      return super.readLine(inBuffer);
-    else
-      return TreadLine!(char).readLine(inBuffer);
-  }
-  alias readLine = Stream.readLine;
-
-  override wchar[] readLineW(wchar[] inBuffer) {
-    if (ungetAvailable())
-      return super.readLineW(inBuffer);
-    else
-      return TreadLine!(wchar).readLine(inBuffer);
-  }
-  alias readLineW = Stream.readLineW;
-
-  override void flush()
-  out {
-    assert(bufferCurPos == 0);
-    assert(bufferSourcePos == 0);
-    assert(bufferLen == 0);
-  }
-  body {
-    if (writeable && bufferDirty) {
-      if (bufferSourcePos != 0 && seekable) {
-        // move actual file pointer to front of buffer
-        streamPos = s.seek(-bufferSourcePos, SeekPos.Current);
-      }
-      // write buffer out
-      bufferSourcePos = s.writeBlock(buffer.ptr, bufferLen);
-      if (bufferSourcePos != bufferLen) {
-        throw new WriteException("Unable to write to stream");
-      }
-    }
-    super.flush();
-    long diff = cast(long)bufferCurPos-bufferSourcePos;
-    if (diff != 0 && seekable) {
-      // move actual file pointer to current position
-      streamPos = s.seek(diff, SeekPos.Current);
-    }
-    // reset buffer data to be empty
-    bufferSourcePos = bufferCurPos = bufferLen = 0;
-    bufferDirty = false;
-  }
-
-  // returns true if end of stream is reached, false otherwise
-  override @property bool eof() {
-    if ((buffer.length == 0) || !readable) {
-      return super.eof;
-    }
-    // some simple tests to avoid flushing
-    if (ungetAvailable() || bufferCurPos != bufferLen)
-      return false;
-    if (bufferLen == buffer.length)
-      flush();
-    size_t res = super.readBlock(&buffer[bufferLen],buffer.length-bufferLen);
-    bufferSourcePos +=  res;
-    bufferLen += res;
-    streamPos += res;
-    return readEOF;
-  }
-
-  // returns size of stream
-  override @property ulong size() {
-    if (bufferDirty) flush();
-    return s.size;
-  }
-
-  // returns estimated number of bytes available for immediate reading
-  override @property size_t available() {
-    return bufferLen - bufferCurPos;
-  }
-}
-
-/// An exception for File errors.
-class StreamFileException: StreamException {
-  /// Construct a StreamFileException with given error message.
-  this(string msg) { super(msg); }
-}
-
-/// An exception for errors during File.open.
-class OpenException: StreamFileException {
-  /// Construct an OpenFileException with given error message.
-  this(string msg) { super(msg); }
-}
-
-/// Specifies the $(LREF File) access mode used when opening the file.
-enum FileMode {
-  In = 1,     /// Opens the file for reading.
-  Out = 2,    /// Opens the file for writing.
-  OutNew = 6, /// Opens the file for writing, creates a new file if it doesn't exist.
-  Append = 10 /// Opens the file for writing, appending new data to the end of the file.
-}
-
-version (Windows) {
-  private import core.sys.windows.windows;
-  extern (Windows) {
-    void FlushFileBuffers(HANDLE hFile);
-    DWORD  GetFileType(HANDLE hFile);
-  }
-}
-version (Posix) {
-  private import core.sys.posix.fcntl;
-  private import core.sys.posix.unistd;
-  alias HANDLE = int;
-}
-
-/// This subclass is for unbuffered file system streams.
-class File: Stream {
-
-  version (Windows) {
-    private HANDLE hFile;
-  }
-  version (Posix) {
-    private HANDLE hFile = -1;
-  }
-
-  this() {
-    super();
-    version (Windows) {
-      hFile = null;
-    }
-    version (Posix) {
-      hFile = -1;
-    }
-    isopen = false;
-  }
-
-  // opens existing handle; use with care!
-  this(HANDLE hFile, FileMode mode) {
-    super();
-    this.hFile = hFile;
-    readable = cast(bool)(mode & FileMode.In);
-    writeable = cast(bool)(mode & FileMode.Out);
-    version(Windows) {
-      seekable = GetFileType(hFile) == 1; // FILE_TYPE_DISK
-    } else {
-      auto result = lseek(hFile, 0, 0);
-      seekable = (result != ~0);
-    }
-  }
-
-  /***
-   * Create the stream with no open file, an open file in read mode, or an open
-   * file with explicit file mode.
-   * mode, if given, is a combination of FileMode.In
-   * (indicating a file that can be read) and FileMode.Out (indicating a file
-   * that can be written).
-   * Opening a file for reading that doesn't exist will error.
-   * Opening a file for writing that doesn't exist will create the file.
-   * The FileMode.OutNew mode will open the file for writing and reset the
-   * length to zero.
-   * The FileMode.Append mode will open the file for writing and move the
-   * file position to the end of the file.
-   */
-  this(string filename, FileMode mode = FileMode.In)
-  {
-      this();
-      open(filename, mode);
-  }
-
-
-  /***
-   * Open a file for the stream, in an identical manner to the constructors.
-   * If an error occurs an OpenException is thrown.
-   */
-  void open(string filename, FileMode mode = FileMode.In) {
-    close();
-    int access, share, createMode;
-    parseMode(mode, access, share, createMode);
-    seekable = true;
-    readable = cast(bool)(mode & FileMode.In);
-    writeable = cast(bool)(mode & FileMode.Out);
-    version (Windows) {
-      hFile = CreateFileW(filename.tempCStringW(), access, share,
-                          null, createMode, 0, null);
-      isopen = hFile != INVALID_HANDLE_VALUE;
-    }
-    version (Posix) {
-      hFile = core.sys.posix.fcntl.open(filename.tempCString(), access | createMode, share);
-      isopen = hFile != -1;
-    }
-    if (!isopen)
-      throw new OpenException(cast(string) ("Cannot open or create file '"
-                                            ~ filename ~ "'"));
-    else if ((mode & FileMode.Append) == FileMode.Append)
-      seekEnd(0);
-  }
-
-  private void parseMode(int mode,
-                         out int access,
-                         out int share,
-                         out int createMode) {
-    version (Windows) {
-      share |= FILE_SHARE_READ | FILE_SHARE_WRITE;
-      if (mode & FileMode.In) {
-        access |= GENERIC_READ;
-        createMode = OPEN_EXISTING;
-      }
-      if (mode & FileMode.Out) {
-        access |= GENERIC_WRITE;
-        createMode = OPEN_ALWAYS; // will create if not present
-      }
-      if ((mode & FileMode.OutNew) == FileMode.OutNew) {
-        createMode = CREATE_ALWAYS; // resets file
-      }
-    }
-    version (Posix) {
-      share = octal!666;
-      if (mode & FileMode.In) {
-        access = O_RDONLY;
-      }
-      if (mode & FileMode.Out) {
-        createMode = O_CREAT; // will create if not present
-        access = O_WRONLY;
-      }
-      if (access == (O_WRONLY | O_RDONLY)) {
-        access = O_RDWR;
-      }
-      if ((mode & FileMode.OutNew) == FileMode.OutNew) {
-        access |= O_TRUNC; // resets file
-      }
-    }
-  }
-
-  /// Create a file for writing.
-  void create(string filename) {
-    create(filename, FileMode.OutNew);
-  }
-
-  /// ditto
-  void create(string filename, FileMode mode) {
-    close();
-    open(filename, mode | FileMode.OutNew);
-  }
-
-  /// Close the current file if it is open; otherwise it does nothing.
-  override void close() {
-    if (isopen) {
-      super.close();
-      if (hFile) {
-        version (Windows) {
-          CloseHandle(hFile);
-          hFile = null;
-        } else version (Posix) {
-          core.sys.posix.unistd.close(hFile);
-          hFile = -1;
-        }
-      }
-    }
-  }
-
-  // destructor, closes file if still opened
-  ~this() { close(); }
-
-  version (Windows) {
-    // returns size of stream
-    override @property ulong size() {
-      assertSeekable();
-      uint sizehi;
-      uint sizelow = GetFileSize(hFile,&sizehi);
-      return (cast(ulong)sizehi << 32) + sizelow;
-    }
-  }
-
-  override size_t readBlock(void* buffer, size_t size) {
-    assertReadable();
-    version (Windows) {
-      auto dwSize = to!DWORD(size);
-      ReadFile(hFile, buffer, dwSize, &dwSize, null);
-      size = dwSize;
-    } else version (Posix) {
-      size = core.sys.posix.unistd.read(hFile, buffer, size);
-      if (size == -1)
-        size = 0;
-    }
-    readEOF = (size == 0);
-    return size;
-  }
-
-  override size_t writeBlock(const void* buffer, size_t size) {
-    assertWriteable();
-    version (Windows) {
-      auto dwSize = to!DWORD(size);
-      WriteFile(hFile, buffer, dwSize, &dwSize, null);
-      size = dwSize;
-    } else version (Posix) {
-      size = core.sys.posix.unistd.write(hFile, buffer, size);
-      if (size == -1)
-        size = 0;
-    }
-    return size;
-  }
-
-  override ulong seek(long offset, SeekPos rel) {
-    assertSeekable();
-    version (Windows) {
-      int hi = cast(int)(offset>>32);
-      uint low = SetFilePointer(hFile, cast(int)offset, &hi, rel);
-      if ((low == INVALID_SET_FILE_POINTER) && (GetLastError() != 0))
-        throw new SeekException("unable to move file pointer");
-      ulong result = (cast(ulong)hi << 32) + low;
-    } else version (Posix) {
-      auto result = lseek(hFile, cast(off_t)offset, rel);
-      if (result == cast(typeof(result))-1)
-        throw new SeekException("unable to move file pointer");
-    }
-    readEOF = false;
-    return cast(ulong)result;
-  }
-
-  /***
-   * For a seekable file returns the difference of the size and position and
-   * otherwise returns 0.
-   */
-
-  override @property size_t available() {
-    if (seekable) {
-      ulong lavail = size - position;
-      if (lavail > size_t.max) lavail = size_t.max;
-      return cast(size_t)lavail;
-    }
-    return 0;
-  }
-
-  // OS-specific property, just in case somebody wants
-  // to mess with underlying API
-  HANDLE handle() { return hFile; }
-
-  // run a few tests
-  /+
-  unittest {
-    import std.internal.cstring : tempCString;
-
-    File file = new File;
-    int i = 666;
-    auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$";
-    file.create(stream_file);
-    // should be ok to write
-    assert(file.writeable);
-    file.writeLine("Testing stream.d:");
-    file.writeString("Hello, world!");
-    file.write(i);
-    // string#1 + string#2 + int should give exacly that
-    version (Windows)
-      assert(file.position == 19 + 13 + 4);
-    version (Posix)
-      assert(file.position == 18 + 13 + 4);
-    // we must be at the end of file
-    assert(file.eof);
-    file.close();
-    // no operations are allowed when file is closed
-    assert(!file.readable && !file.writeable && !file.seekable);
-    file.open(stream_file);
-    // should be ok to read
-    assert(file.readable);
-    assert(file.available == file.size);
-    char[] line = file.readLine();
-    char[] exp = "Testing stream.d:".dup;
-    assert(line[0] == 'T');
-    assert(line.length == exp.length);
-    assert(!std.algorithm.cmp(line, "Testing stream.d:"));
-    // jump over "Hello, "
-    file.seek(7, SeekPos.Current);
-    version (Windows)
-      assert(file.position == 19 + 7);
-    version (Posix)
-      assert(file.position == 18 + 7);
-    assert(!std.algorithm.cmp(file.readString(6), "world!"));
-    i = 0; file.read(i);
-    assert(i == 666);
-    // string#1 + string#2 + int should give exacly that
-    version (Windows)
-      assert(file.position == 19 + 13 + 4);
-    version (Posix)
-      assert(file.position == 18 + 13 + 4);
-    // we must be at the end of file
-    assert(file.eof);
-    file.close();
-    file.open(stream_file,FileMode.OutNew | FileMode.In);
-    file.writeLine("Testing stream.d:");
-    file.writeLine("Another line");
-    file.writeLine("");
-    file.writeLine("That was blank");
-    file.position = 0;
-    char[][] lines;
-    foreach(char[] line; file) {
-      lines ~= line.dup;
-    }
-    assert( lines.length == 4 );
-    assert( lines[0] == "Testing stream.d:");
-    assert( lines[1] == "Another line");
-    assert( lines[2] == "");
-    assert( lines[3] == "That was blank");
-    file.position = 0;
-    lines = new char[][4];
-    foreach(ulong n, char[] line; file) {
-      lines[cast(size_t)(n-1)] = line.dup;
-    }
-    assert( lines[0] == "Testing stream.d:");
-    assert( lines[1] == "Another line");
-    assert( lines[2] == "");
-    assert( lines[3] == "That was blank");
-    file.close();
-    remove(stream_file.tempCString());
-  }
-  +/
-}
-
-/***
- * This subclass is for buffered file system streams.
- *
- * It is a convenience class for wrapping a File in a BufferedStream.
- * A buffered stream must be closed explicitly to ensure the final buffer
- * content is written to the file.
- */
-class BufferedFile: BufferedStream {
-
-  /// opens file for reading
-  this() { super(new File()); }
-
-  /// opens file in requested mode and buffer size
-  this(string filename, FileMode mode = FileMode.In,
-       size_t bufferSize = DefaultBufferSize) {
-    super(new File(filename,mode),bufferSize);
-  }
-
-  /// opens file for reading with requested buffer size
-  this(File file, size_t bufferSize = DefaultBufferSize) {
-    super(file,bufferSize);
-  }
-
-  /// opens existing handle; use with care!
-  this(HANDLE hFile, FileMode mode, size_t buffersize = DefaultBufferSize) {
-    super(new File(hFile,mode),buffersize);
-  }
-
-  /// opens file in requested mode
-  void open(string filename, FileMode mode = FileMode.In) {
-    File sf = cast(File)s;
-    sf.open(filename,mode);
-    resetSource();
-  }
-
-  /// creates file in requested mode
-  void create(string filename, FileMode mode = FileMode.OutNew) {
-    File sf = cast(File)s;
-    sf.create(filename,mode);
-    resetSource();
-  }
-
-  // run a few tests same as File
-  /+
-  unittest {
-    import std.internal.cstring : tempCString;
-
-    BufferedFile file = new BufferedFile;
-    int i = 666;
-    auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$";
-    file.create(stream_file);
-    // should be ok to write
-    assert(file.writeable);
-    file.writeLine("Testing stream.d:");
-    file.writeString("Hello, world!");
-    file.write(i);
-    // string#1 + string#2 + int should give exacly that
-    version (Windows)
-      assert(file.position == 19 + 13 + 4);
-    version (Posix)
-      assert(file.position == 18 + 13 + 4);
-    // we must be at the end of file
-    assert(file.eof);
-    long oldsize = cast(long)file.size;
-    file.close();
-    // no operations are allowed when file is closed
-    assert(!file.readable && !file.writeable && !file.seekable);
-    file.open(stream_file);
-    // should be ok to read
-    assert(file.readable);
-    // test getc/ungetc and size
-    char c1 = file.getc();
-    file.ungetc(c1);
-    assert( file.size == oldsize );
-    assert(!std.algorithm.cmp(file.readLine(), "Testing stream.d:"));
-    // jump over "Hello, "
-    file.seek(7, SeekPos.Current);
-    version (Windows)
-      assert(file.position == 19 + 7);
-    version (Posix)
-      assert(file.position == 18 + 7);
-    assert(!std.algorithm.cmp(file.readString(6), "world!"));
-    i = 0; file.read(i);
-    assert(i == 666);
-    // string#1 + string#2 + int should give exacly that
-    version (Windows)
-      assert(file.position == 19 + 13 + 4);
-    version (Posix)
-      assert(file.position == 18 + 13 + 4);
-    // we must be at the end of file
-    assert(file.eof);
-    file.close();
-    remove(stream_file.tempCString());
-  }
-  +/
-
-}
-
-/// UTF byte-order-mark signatures
-enum BOM {
-        UTF8,           /// UTF-8
-        UTF16LE,        /// UTF-16 Little Endian
-        UTF16BE,        /// UTF-16 Big Endian
-        UTF32LE,        /// UTF-32 Little Endian
-        UTF32BE,        /// UTF-32 Big Endian
-}
-
-private enum int NBOMS = 5;
-immutable Endian[NBOMS] BOMEndian =
-[ std.system.endian,
-  Endian.littleEndian, Endian.bigEndian,
-  Endian.littleEndian, Endian.bigEndian
-  ];
-
-immutable ubyte[][NBOMS] ByteOrderMarks =
-[ [0xEF, 0xBB, 0xBF],
-  [0xFF, 0xFE],
-  [0xFE, 0xFF],
-  [0xFF, 0xFE, 0x00, 0x00],
-  [0x00, 0x00, 0xFE, 0xFF]
-  ];
-
-
-/***
- * This subclass wraps a stream with big-endian or little-endian byte order
- * swapping.
- *
- * UTF Byte-Order-Mark (BOM) signatures can be read and deduced or
- * written.
- * Note that an EndianStream should not be used as the source of another
- * FilterStream since a FilterStream call the source with byte-oriented
- * read/write requests and the EndianStream will not perform any byte swapping.
- * The EndianStream reads and writes binary data (non-getc functions) in a
- * one-to-one
- * manner with the source stream so the source stream's position and state will be
- * kept in sync with the EndianStream if only non-getc functions are called.
- */
-class EndianStream : FilterStream {
-
-  Endian endian;        /// Endianness property of the source stream.
-
-  /***
-   * Create the endian stream for the source stream source with endianness end.
-   * The default endianness is the native byte order.
-   * The Endian type is defined
-   * in the std.system module.
-   */
-  this(Stream source, Endian end = std.system.endian) {
-    super(source);
-    endian = end;
-  }
-
-  /***
-   * Return -1 if no BOM and otherwise read the BOM and return it.
-   *
-   * If there is no BOM or if bytes beyond the BOM are read then the bytes read
-   * are pushed back onto the ungetc buffer or ungetcw buffer.
-   * Pass ungetCharSize == 2 to use
-   * ungetcw instead of ungetc when no BOM is present.
-   */
-  int readBOM(int ungetCharSize = 1) {
-    ubyte[4] BOM_buffer;
-    int n = 0;       // the number of read bytes
-    int result = -1; // the last match or -1
-    for (int i=0; i < NBOMS; ++i) {
-      int j;
-      immutable ubyte[] bom = ByteOrderMarks[i];
-      for (j=0; j < bom.length; ++j) {
-        if (n <= j) { // have to read more
-          if (eof)
-            break;
-          readExact(&BOM_buffer[n++],1);
-        }
-        if (BOM_buffer[j] != bom[j])
-          break;
-      }
-      if (j == bom.length) // found a match
-        result = i;
-    }
-    ptrdiff_t m = 0;
-    if (result != -1) {
-      endian = BOMEndian[result]; // set stream endianness
-      m = ByteOrderMarks[result].length;
-    }
-    if ((ungetCharSize == 1 && result == -1) || (result == BOM.UTF8)) {
-      while (n-- > m)
-        ungetc(BOM_buffer[n]);
-    } else { // should eventually support unget for dchar as well
-      if (n & 1) // make sure we have an even number of bytes
-        readExact(&BOM_buffer[n++],1);
-      while (n > m) {
-        n -= 2;
-        wchar cw = *(cast(wchar*)&BOM_buffer[n]);
-        fixBO(&cw,2);
-        ungetcw(cw);
-      }
-    }
-    return result;
-  }
-
-  /***
-   * Correct the byte order of buffer to match native endianness.
-   * size must be even.
-   */
-  final void fixBO(const(void)* buffer, size_t size) {
-    if (endian != std.system.endian) {
-      ubyte* startb = cast(ubyte*)buffer;
-      uint* start = cast(uint*)buffer;
-      switch (size) {
-      case 0: break;
-      case 2: {
-        ubyte x = *startb;
-        *startb = *(startb+1);
-        *(startb+1) = x;
-        break;
-      }
-      case 4: {
-        *start = bswap(*start);
-        break;
-      }
-      default: {
-        uint* end = cast(uint*)(buffer + size - uint.sizeof);
-        while (start < end) {
-          uint x = bswap(*start);
-          *start = bswap(*end);
-          *end = x;
-          ++start;
-          --end;
-        }
-        startb = cast(ubyte*)start;
-        ubyte* endb = cast(ubyte*)end;
-        auto len = uint.sizeof - (startb - endb);
-        if (len > 0)
-          fixBO(startb,len);
-      }
-      }
-    }
-  }
-
-  /***
-   * Correct the byte order of the given buffer in blocks of the given size and
-   * repeated the given number of times.
-   * size must be even.
-   */
-  final void fixBlockBO(void* buffer, uint size, size_t repeat) {
-    while (repeat--) {
-      fixBO(buffer,size);
-      buffer += size;
-    }
-  }
-
-  override void read(out byte x) { readExact(&x, x.sizeof); }
-  override void read(out ubyte x) { readExact(&x, x.sizeof); }
-  override void read(out short x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out ushort x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out int x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out uint x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out long x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out ulong x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out float x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out double x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out real x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out ifloat x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out idouble x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out ireal x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out cfloat x) { readExact(&x, x.sizeof); fixBlockBO(&x,float.sizeof,2); }
-  override void read(out cdouble x) { readExact(&x, x.sizeof); fixBlockBO(&x,double.sizeof,2); }
-  override void read(out creal x) { readExact(&x, x.sizeof); fixBlockBO(&x,real.sizeof,2); }
-  override void read(out char x) { readExact(&x, x.sizeof); }
-  override void read(out wchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-  override void read(out dchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); }
-
-  override wchar getcw() {
-    wchar c;
-    if (prevCr) {
-      prevCr = false;
-      c = getcw();
-      if (c != '\n')
-        return c;
-    }
-    if (unget.length > 1) {
-      c = unget[unget.length - 1];
-      unget.length = unget.length - 1;
-    } else {
-      void* buf = &c;
-      size_t n = readBlock(buf,2);
-      if (n == 1 && readBlock(buf+1,1) == 0)
-          throw new ReadException("not enough data in stream");
-      fixBO(&c,c.sizeof);
-    }
-    return c;
-  }
-
-  override wchar[] readStringW(size_t length) {
-    wchar[] result = new wchar[length];
-    readExact(result.ptr, length * wchar.sizeof);
-    fixBlockBO(result.ptr, wchar.sizeof, length);
-    return result;
-  }
-
-  /// Write the specified BOM b to the source stream.
-  void writeBOM(BOM b) {
-    immutable ubyte[] bom = ByteOrderMarks[b];
-    writeBlock(bom.ptr, bom.length);
-  }
-
-  override void write(byte x) { writeExact(&x, x.sizeof); }
-  override void write(ubyte x) { writeExact(&x, x.sizeof); }
-  override void write(short x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(ushort x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(int x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(uint x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(long x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(ulong x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(float x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(double x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(real x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(ifloat x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(idouble x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(ireal x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(cfloat x) { fixBlockBO(&x,float.sizeof,2); writeExact(&x, x.sizeof); }
-  override void write(cdouble x) { fixBlockBO(&x,double.sizeof,2); writeExact(&x, x.sizeof); }
-  override void write(creal x) { fixBlockBO(&x,real.sizeof,2); writeExact(&x, x.sizeof);  }
-  override void write(char x) { writeExact(&x, x.sizeof); }
-  override void write(wchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-  override void write(dchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); }
-
-  override void writeStringW(const(wchar)[] str) {
-    foreach(wchar cw;str) {
-      fixBO(&cw,2);
-      s.writeExact(&cw, 2);
-    }
-  }
-
-  override @property bool eof() { return s.eof && !ungetAvailable();  }
-  override @property ulong size() { return s.size;  }
-
-  unittest {
-    MemoryStream m;
-    m = new MemoryStream ();
-    EndianStream em = new EndianStream(m,Endian.bigEndian);
-    uint x = 0x11223344;
-    em.write(x);
-    assert( m.data[0] == 0x11 );
-    assert( m.data[1] == 0x22 );
-    assert( m.data[2] == 0x33 );
-    assert( m.data[3] == 0x44 );
-    em.position = 0;
-    ushort x2 = 0x5566;
-    em.write(x2);
-    assert( m.data[0] == 0x55 );
-    assert( m.data[1] == 0x66 );
-    em.position = 0;
-    static ubyte[12] x3 = [1,2,3,4,5,6,7,8,9,10,11,12];
-    em.fixBO(x3.ptr,12);
-    if (std.system.endian == Endian.littleEndian) {
-      assert( x3[0] == 12 );
-      assert( x3[1] == 11 );
-      assert( x3[2] == 10 );
-      assert( x3[4] == 8 );
-      assert( x3[5] == 7 );
-      assert( x3[6] == 6 );
-      assert( x3[8] == 4 );
-      assert( x3[9] == 3 );
-      assert( x3[10] == 2 );
-      assert( x3[11] == 1 );
-    }
-    em.endian = Endian.littleEndian;
-    em.write(x);
-    assert( m.data[0] == 0x44 );
-    assert( m.data[1] == 0x33 );
-    assert( m.data[2] == 0x22 );
-    assert( m.data[3] == 0x11 );
-    em.position = 0;
-    em.write(x2);
-    assert( m.data[0] == 0x66 );
-    assert( m.data[1] == 0x55 );
-    em.position = 0;
-    em.fixBO(x3.ptr,12);
-    if (std.system.endian == Endian.bigEndian) {
-      assert( x3[0] == 12 );
-      assert( x3[1] == 11 );
-      assert( x3[2] == 10 );
-      assert( x3[4] == 8 );
-      assert( x3[5] == 7 );
-      assert( x3[6] == 6 );
-      assert( x3[8] == 4 );
-      assert( x3[9] == 3 );
-      assert( x3[10] == 2 );
-      assert( x3[11] == 1 );
-    }
-    em.writeBOM(BOM.UTF8);
-    assert( m.position == 3 );
-    assert( m.data[0] == 0xEF );
-    assert( m.data[1] == 0xBB );
-    assert( m.data[2] == 0xBF );
-    em.writeString ("Hello, world");
-    em.position = 0;
-    assert( m.position == 0 );
-    assert( em.readBOM() == BOM.UTF8 );
-    assert( m.position == 3 );
-    assert( em.getc() == 'H' );
-    em.position = 0;
-    em.writeBOM(BOM.UTF16BE);
-    assert( m.data[0] == 0xFE );
-    assert( m.data[1] == 0xFF );
-    em.position = 0;
-    em.writeBOM(BOM.UTF16LE);
-    assert( m.data[0] == 0xFF );
-    assert( m.data[1] == 0xFE );
-    em.position = 0;
-    em.writeString ("Hello, world");
-    em.position = 0;
-    assert( em.readBOM() == -1 );
-    assert( em.getc() == 'H' );
-    assert( em.getc() == 'e' );
-    assert( em.getc() == 'l' );
-    assert( em.getc() == 'l' );
-    em.position = 0;
-  }
-}
-
-/***
- * Parameterized subclass that wraps an array-like buffer with a stream
- * interface.
- *
- * The type Buffer must support the length property, opIndex and opSlice.
- * Compile in release mode when directly instantiating a TArrayStream to avoid
- * link errors.
- */
-class TArrayStream(Buffer): Stream {
-  Buffer buf; // current data
-  ulong len;  // current data length
-  ulong cur;  // current file position
-
-  /// Create the stream for the the buffer buf. Non-copying.
-  this(Buffer buf) {
-    super ();
-    this.buf = buf;
-    this.len = buf.length;
-    readable = writeable = seekable = true;
-  }
-
-  // ensure subclasses don't violate this
-  invariant() {
-    assert(len <= buf.length);
-    assert(cur <= len);
-  }
-
-  override size_t readBlock(void* buffer, size_t size) {
-    assertReadable();
-    ubyte* cbuf = cast(ubyte*) buffer;
-    if (len - cur < size)
-      size = cast(size_t)(len - cur);
-    ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)];
-    cbuf[0 .. size] = ubuf[];
-    cur += size;
-    return size;
-  }
-
-  override size_t writeBlock(const void* buffer, size_t size) {
-    assertWriteable();
-    ubyte* cbuf = cast(ubyte*) buffer;
-    ulong blen = buf.length;
-    if (cur + size > blen)
-      size = cast(size_t)(blen - cur);
-    ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)];
-    ubuf[] = cbuf[0 .. size];
-    cur += size;
-    if (cur > len)
-      len = cur;
-    return size;
-  }
-
-  override ulong seek(long offset, SeekPos rel) {
-    assertSeekable();
-    long scur; // signed to saturate to 0 properly
-
-    switch (rel) {
-    case SeekPos.Set: scur = offset; break;
-    case SeekPos.Current: scur = cast(long)(cur + offset); break;
-    case SeekPos.End: scur = cast(long)(len + offset); break;
-    default:
-        assert(0);
-    }
-
-    if (scur < 0)
-      cur = 0;
-    else if (scur > len)
-      cur = len;
-    else
-      cur = cast(ulong)scur;
-
-    return cur;
-  }
-
-  override @property size_t available () { return cast(size_t)(len - cur); }
-
-  /// Get the current memory data in total.
-  @property ubyte[] data() {
-    if (len > size_t.max)
-      throw new StreamException("Stream too big");
-    const(void)[] res = buf[0 .. cast(size_t)len];
-    return cast(ubyte[])res;
-  }
-
-  override string toString() {
-      // assume data is UTF8
-      return to!(string)(cast(char[])data);
-  }
-}
-
-/* Test the TArrayStream */
-unittest {
-  char[100] buf;
-  TArrayStream!(char[]) m;
-
-  m = new TArrayStream!(char[]) (buf);
-  assert (m.isOpen);
-  m.writeString ("Hello, world");
-  assert (m.position == 12);
-  assert (m.available == 88);
-  assert (m.seekSet (0) == 0);
-  assert (m.available == 100);
-  assert (m.seekCur (4) == 4);
-  assert (m.available == 96);
-  assert (m.seekEnd (-8) == 92);
-  assert (m.available == 8);
-  assert (m.size == 100);
-  assert (m.seekSet (4) == 4);
-  assert (m.readString (4) == "o, w");
-  m.writeString ("ie");
-  assert (buf[0..12] == "Hello, wield");
-  assert (m.position == 10);
-  assert (m.available == 90);
-  assert (m.size == 100);
-  m.seekSet (0);
-  assert (m.printf ("Answer is %d", 42) == 12);
-  assert (buf[0..12] == "Answer is 42");
-}
-
-/// This subclass reads and constructs an array of bytes in memory.
-class MemoryStream: TArrayStream!(ubyte[]) {
-
-  /// Create the output buffer and setup for reading, writing, and seeking.
-  // clear to an empty buffer.
-  this() { this(cast(ubyte[]) null); }
-
-  /***
-   * Create the output buffer and setup for reading, writing, and seeking.
-   * Load it with specific input data.
-   */
-  this(ubyte[] buf) { super (buf); }
-  this(byte[] buf) { this(cast(ubyte[]) buf); } /// ditto
-  this(char[] buf) { this(cast(ubyte[]) buf); } /// ditto
-
-  /// Ensure the stream can write count extra bytes from cursor position without an allocation.
-  void reserve(size_t count) {
-    if (cur + count > buf.length)
-      buf.length = cast(uint)((cur + count) * 2);
-  }
-
-  override size_t writeBlock(const void* buffer, size_t size) {
-    reserve(size);
-    return super.writeBlock(buffer,size);
-  }
-
-  unittest {
-    MemoryStream m;
-
-    m = new MemoryStream ();
-    assert (m.isOpen);
-    m.writeString ("Hello, world");
-    assert (m.position == 12);
-    assert (m.seekSet (0) == 0);
-    assert (m.available == 12);
-    assert (m.seekCur (4) == 4);
-    assert (m.available == 8);
-    assert (m.seekEnd (-8) == 4);
-    assert (m.available == 8);
-    assert (m.size == 12);
-    assert (m.readString (4) == "o, w");
-    m.writeString ("ie");
-    assert (cast(char[]) m.data == "Hello, wield");
-    m.seekEnd (0);
-    m.writeString ("Foo");
-    assert (m.position == 15);
-    assert (m.available == 0);
-    m.writeString ("Foo foo foo foo foo foo foo");
-    assert (m.position == 42);
-    m.position = 0;
-    assert (m.available == 42);
-    m.writef("%d %d %s",100,345,"hello");
-    auto str = m.toString();
-    assert (str[0..13] == "100 345 hello", str[0 .. 13]);
-    assert (m.available == 29);
-    assert (m.position == 13);
-
-    MemoryStream m2;
-    m.position = 3;
-    m2 = new MemoryStream ();
-    m2.writeString("before");
-    m2.copyFrom(m,10);
-    str = m2.toString();
-    assert (str[0..16] == "before 345 hello");
-    m2.position = 3;
-    m2.copyFrom(m);
-    auto str2 = m.toString();
-    str = m2.toString();
-    assert (str == ("bef" ~ str2));
-  }
-}
-
-import std.mmfile;
-
-/***
- * This subclass wraps a memory-mapped file with the stream API.
- * See std.mmfile module.
- */
-class MmFileStream : TArrayStream!(MmFile) {
-
-  /// Create stream wrapper for file.
-  this(MmFile file) {
-    super (file);
-    MmFile.Mode mode = file.mode();
-    writeable = mode > MmFile.Mode.read;
-  }
-
-  override void flush() {
-    if (isopen) {
-      super.flush();
-      buf.flush();
-    }
-  }
-
-  override void close() {
-    if (isopen) {
-      super.close();
-      delete buf;
-      buf = null;
-    }
-  }
-}
-
-unittest {
-  auto test_file = undead.internal.file.deleteme ~ "-testing.txt";
-  MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew,100,null);
-  MmFileStream m;
-  m = new MmFileStream (mf);
-  m.writeString ("Hello, world");
-  assert (m.position == 12);
-  assert (m.seekSet (0) == 0);
-  assert (m.seekCur (4) == 4);
-  assert (m.seekEnd (-8) == 92);
-  assert (m.size == 100);
-  assert (m.seekSet (4));
-  assert (m.readString (4) == "o, w");
-  m.writeString ("ie");
-  ubyte[] dd = m.data;
-  assert ((cast(char[]) dd)[0 .. 12] == "Hello, wield");
-  m.position = 12;
-  m.writeString ("Foo");
-  assert (m.position == 15);
-  m.writeString ("Foo foo foo foo foo foo foo");
-  assert (m.position == 42);
-  m.close();
-  mf = new MmFile(test_file);
-  m = new MmFileStream (mf);
-  assert (!m.writeable);
-  char[] str = m.readString(12);
-  assert (str == "Hello, wield");
-  m.close();
-  std.file.remove(test_file);
-}
-
-
-/***
- * This subclass slices off a portion of another stream, making seeking relative
- * to the boundaries of the slice.
- *
- * It could be used to section a large file into a
- * set of smaller files, such as with tar archives. Reading and writing a
- * SliceStream does not modify the position of the source stream if it is
- * seekable.
- */
-class SliceStream : FilterStream {
-  private {
-    ulong pos;  // our position relative to low
-    ulong low; // low stream offset.
-    ulong high; // high stream offset.
-    bool bounded; // upper-bounded by high.
-  }
-
-  /***
-   * Indicate both the source stream to use for reading from and the low part of
-   * the slice.
-   *
-   * The high part of the slice is dependent upon the end of the source
-   * stream, so that if you write beyond the end it resizes the stream normally.
-   */
-  this (Stream s, ulong low)
-  in {
-    assert (low <= s.size);
-  }
-  body {
-    super(s);
-    this.low = low;
-    this.high = 0;
-    this.bounded = false;
-  }
-
-  /***
-   * Indicate the high index as well.
-   *
-   * Attempting to read or write past the high
-   * index results in the end being clipped off.
-   */
-  this (Stream s, ulong low, ulong high)
-  in {
-    assert (low <= high);
-    assert (high <= s.size);
-  }
-  body {
-    super(s);
-    this.low = low;
-    this.high = high;
-    this.bounded = true;
-  }
-
-  invariant() {
-    if (bounded)
-      assert (pos <= high - low);
-    else
-      // size() does not appear to be const, though it should be
-      assert (pos <= (cast()s).size - low);
-  }
-
-  override size_t readBlock (void *buffer, size_t size) {
-    assertReadable();
-    if (bounded && size > high - low - pos)
-        size = cast(size_t)(high - low - pos);
-    ulong bp = s.position;
-    if (seekable)
-      s.position = low + pos;
-    size_t ret = super.readBlock(buffer, size);
-    if (seekable) {
-      pos = s.position - low;
-      s.position = bp;
-    }
-    return ret;
-  }
-
-  override size_t writeBlock (const void *buffer, size_t size) {
-    assertWriteable();
-    if (bounded && size > high - low - pos)
-        size = cast(size_t)(high - low - pos);
-    ulong bp = s.position;
-    if (seekable)
-      s.position = low + pos;
-    size_t ret = s.writeBlock(buffer, size);
-    if (seekable) {
-      pos = s.position - low;
-      s.position = bp;
-    }
-    return ret;
-  }
-
-  override ulong seek(long offset, SeekPos rel) {
-    assertSeekable();
-    long spos;
-
-    switch (rel) {
-      case SeekPos.Set:
-        spos = offset;
-        break;
-      case SeekPos.Current:
-        spos = cast(long)(pos + offset);
-        break;
-      case SeekPos.End:
-        if (bounded)
-          spos = cast(long)(high - low + offset);
-        else
-          spos = cast(long)(s.size - low + offset);
-        break;
-      default:
-        assert(0);
-    }
-
-    if (spos < 0)
-      pos = 0;
-    else if (bounded && spos > high - low)
-      pos = high - low;
-    else if (!bounded && spos > s.size - low)
-      pos = s.size - low;
-    else
-      pos = cast(ulong)spos;
-
-    readEOF = false;
-    return pos;
-  }
-
-  override @property size_t available() {
-    size_t res = s.available;
-    ulong bp = s.position;
-    if (bp <= pos+low && pos+low <= bp+res) {
-      if (!bounded || bp+res <= high)
-        return cast(size_t)(bp + res - pos - low);
-      else if (high <= bp+res)
-        return cast(size_t)(high - pos - low);
-    }
-    return 0;
-  }
-
-  unittest {
-    MemoryStream m;
-    SliceStream s;
-
-    m = new MemoryStream ((cast(char[])"Hello, world").dup);
-    s = new SliceStream (m, 4, 8);
-    assert (s.size == 4);
-    assert (m.position == 0);
-    assert (s.position == 0);
-    assert (m.available == 12);
-    assert (s.available == 4);
-
-    assert (s.writeBlock (cast(char *) "Vroom", 5) == 4);
-    assert (m.position == 0);
-    assert (s.position == 4);
-    assert (m.available == 12);
-    assert (s.available == 0);
-    assert (s.seekEnd (-2) == 2);
-    assert (s.available == 2);
-    assert (s.seekEnd (2) == 4);
-    assert (s.available == 0);
-    assert (m.position == 0);
-    assert (m.available == 12);
-
-    m.seekEnd(0);
-    m.writeString("\nBlaho");
-    assert (m.position == 18);
-    assert (m.available == 0);
-    assert (s.position == 4);
-    assert (s.available == 0);
-
-    s = new SliceStream (m, 4);
-    assert (s.size == 14);
-    assert (s.toString () == "Vrooorld\nBlaho");
-    s.seekEnd (0);
-    assert (s.available == 0);
-
-    s.writeString (", etcetera.");
-    assert (s.position == 25);
-    assert (s.seekSet (0) == 0);
-    assert (s.size == 25);
-    assert (m.position == 18);
-    assert (m.size == 29);
-    assert (m.toString() == "HellVrooorld\nBlaho, etcetera.");
-  }
-}
-- 
cgit v1.2.3