1 // SDLang-D
2 // Written in the D programming language.
3 
4 module gfx.decl.sdlang.ast;
5 
6 import std.algorithm;
7 import std.array;
8 import std.conv;
9 import std.range;
10 import std..string;
11 
12 import gfx.decl.sdlang.exception;
13 import gfx.decl.sdlang.token;
14 import gfx.decl.sdlang.util;
15 
16 class Attribute
17 {
18 	Value    value;
19 	Location location;
20 
21 	private Tag _parent;
22 	/// Get parent tag. To set a parent, attach this Attribute to its intended
23 	/// parent tag by calling `Tag.add(...)`, or by passing it to
24 	/// the parent tag's constructor.
25 	@property Tag parent()
26 	{
27 		return _parent;
28 	}
29 
30 	private string _namespace;
31 	/++
32 	This tag's namespace. Empty string if no namespace.
33 
34 	Note that setting this value is O(n) because internal lookup structures
35 	need to be updated.
36 
37 	Note also, that setting this may change where this tag is ordered among
38 	its parent's list of tags.
39 	+/
40 	@property string namespace()
41 	{
42 		return _namespace;
43 	}
44 	///ditto
45 	@property void namespace(string value)
46 	{
47 		if(_parent && _namespace != value)
48 		{
49 			// Remove
50 			auto saveParent = _parent;
51 			if(_parent)
52 				this.remove();
53 
54 			// Change namespace
55 			_namespace = value;
56 
57 			// Re-add
58 			if(saveParent)
59 				saveParent.add(this);
60 		}
61 		else
62 			_namespace = value;
63 	}
64 
65 	private string _name;
66 	/++
67 	This attribute's name, not including namespace.
68 
69 	Use `getFullName().toString` if you want the namespace included.
70 
71 	Note that setting this value is O(n) because internal lookup structures
72 	need to be updated.
73 
74 	Note also, that setting this may change where this attribute is ordered
75 	among its parent's list of tags.
76 	+/
77 	@property string name()
78 	{
79 		return _name;
80 	}
81 	///ditto
82 	@property void name(string value)
83 	{
84 		if(_parent && _name != value)
85 		{
86 			_parent.updateId++;
87 
88 			void removeFromGroupedLookup(string ns)
89 			{
90 				// Remove from _parent._attributes[ns]
91 				auto sameNameAttrs = _parent._attributes[ns][_name];
92 				auto targetIndex = sameNameAttrs.countUntil(this);
93 				_parent._attributes[ns][_name].removeIndex(targetIndex);
94 			}
95 
96 			// Remove from _parent._tags
97 			removeFromGroupedLookup(_namespace);
98 			removeFromGroupedLookup("*");
99 
100 			// Change name
101 			_name = value;
102 
103 			// Add to new locations in _parent._attributes
104 			_parent._attributes[_namespace][_name] ~= this;
105 			_parent._attributes["*"][_name] ~= this;
106 		}
107 		else
108 			_name = value;
109 	}
110 
111 	/// This tag's name, including namespace if one exists.
112 	deprecated("Use 'getFullName().toString()'")
113 	@property string fullName()
114 	{
115 		return getFullName().toString();
116 	}
117 
118 	/// This tag's name, including namespace if one exists.
119 	FullName getFullName()
120 	{
121 		return FullName(_namespace, _name);
122 	}
123 
124 	this(string namespace, string name, Value value, Location location = Location(0, 0, 0))
125 	{
126 		this._namespace = namespace;
127 		this._name      = name;
128 		this.location   = location;
129 		this.value      = value;
130 	}
131 
132 	this(string name, Value value, Location location = Location(0, 0, 0))
133 	{
134 		this._namespace = "";
135 		this._name      = name;
136 		this.location   = location;
137 		this.value      = value;
138 	}
139 
140 	/// Copy this Attribute.
141 	/// The clone does $(B $(I not)) have a parent, even if the original does.
142 	Attribute clone()
143 	{
144 		return new Attribute(_namespace, _name, value, location);
145 	}
146 
147 	/// Removes `this` from its parent, if any. Returns `this` for chaining.
148 	/// Inefficient ATM, but it works.
149 	Attribute remove()
150 	{
151 		if(!_parent)
152 			return this;
153 
154 		void removeFromGroupedLookup(string ns)
155 		{
156 			// Remove from _parent._attributes[ns]
157 			auto sameNameAttrs = _parent._attributes[ns][_name];
158 			auto targetIndex = sameNameAttrs.countUntil(this);
159 			_parent._attributes[ns][_name].removeIndex(targetIndex);
160 		}
161 
162 		// Remove from _parent._attributes
163 		removeFromGroupedLookup(_namespace);
164 		removeFromGroupedLookup("*");
165 
166 		// Remove from _parent.allAttributes
167 		auto allAttrsIndex = _parent.allAttributes.countUntil(this);
168 		_parent.allAttributes.removeIndex(allAttrsIndex);
169 
170 		// Remove from _parent.attributeIndicies
171 		auto sameNamespaceAttrs = _parent.attributeIndicies[_namespace];
172 		auto attrIndiciesIndex = sameNamespaceAttrs.countUntil(allAttrsIndex);
173 		_parent.attributeIndicies[_namespace].removeIndex(attrIndiciesIndex);
174 
175 		// Fixup other indicies
176 		foreach(ns, ref nsAttrIndicies; _parent.attributeIndicies)
177 		foreach(k, ref v; nsAttrIndicies)
178 		if(v > allAttrsIndex)
179 			v--;
180 
181 		_parent.removeNamespaceIfEmpty(_namespace);
182 		_parent.updateId++;
183 		_parent = null;
184 		return this;
185 	}
186 
187 	override bool opEquals(Object o)
188 	{
189 		auto a = cast(Attribute)o;
190 		if(!a)
191 			return false;
192 
193 		return
194 			_namespace == a._namespace &&
195 			_name      == a._name      &&
196 			value      == a.value;
197 	}
198 
199 	string toSDLString()()
200 	{
201 		Appender!string sink;
202 		this.toSDLString(sink);
203 		return sink.data;
204 	}
205 
206 	void toSDLString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char))
207 	{
208 		if(_namespace != "")
209 		{
210 			sink.put(_namespace);
211 			sink.put(':');
212 		}
213 
214 		sink.put(_name);
215 		sink.put('=');
216 		value.toSDLString(sink);
217 	}
218 }
219 
220 /// Deep-copy an array of Tag or Attribute.
221 /// The top-level clones are $(B $(I not)) attached to any parent, even if the originals are.
222 T[] clone(T)(T[] arr) if(is(T==Tag) || is(T==Attribute))
223 {
224 	T[] newArr;
225 	newArr.length = arr.length;
226 
227 	foreach(i; 0..arr.length)
228 		newArr[i] = arr[i].clone();
229 
230 	return newArr;
231 }
232 
233 class Tag
234 {
235 	/// File/Line/Column/Index information for where this tag was located in
236 	/// its original SDLang file.
237 	Location location;
238 
239 	/// Access all this tag's values, as an array of type `sdlang.token.Value`.
240 	Value[]  values;
241 
242 	private Tag _parent;
243 	/// Get parent tag. To set a parent, attach this Tag to its intended
244 	/// parent tag by calling `Tag.add(...)`, or by passing it to
245 	/// the parent tag's constructor.
246 	@property Tag parent()
247 	{
248 		return _parent;
249 	}
250 
251 	private string _namespace;
252 	/++
253 	This tag's namespace. Empty string if no namespace.
254 
255 	Note that setting this value is O(n) because internal lookup structures
256 	need to be updated.
257 
258 	Note also, that setting this may change where this tag is ordered among
259 	its parent's list of tags.
260 	+/
261 	@property string namespace()
262 	{
263 		return _namespace;
264 	}
265 	///ditto
266 	@property void namespace(string value)
267 	{
268 		//TODO: Can we do this in-place, without removing/adding and thus
269 		//      modyfying the internal order?
270 		if(_parent && _namespace != value)
271 		{
272 			// Remove
273 			auto saveParent = _parent;
274 			if(_parent)
275 				this.remove();
276 
277 			// Change namespace
278 			_namespace = value;
279 
280 			// Re-add
281 			if(saveParent)
282 				saveParent.add(this);
283 		}
284 		else
285 			_namespace = value;
286 	}
287 
288 	private string _name;
289 	/++
290 	This tag's name, not including namespace.
291 
292 	Use `getFullName().toString` if you want the namespace included.
293 
294 	Note that setting this value is O(n) because internal lookup structures
295 	need to be updated.
296 
297 	Note also, that setting this may change where this tag is ordered among
298 	its parent's list of tags.
299 	+/
300 	@property string name()
301 	{
302 		return _name;
303 	}
304 	///ditto
305 	@property void name(string value)
306 	{
307 		//TODO: Seriously? Can't we at least do the "*" modification *in-place*?
308 
309 		if(_parent && _name != value)
310 		{
311 			_parent.updateId++;
312 
313 			// Not the most efficient, but it works.
314 			void removeFromGroupedLookup(string ns)
315 			{
316 				// Remove from _parent._tags[ns]
317 				auto sameNameTags = _parent._tags[ns][_name];
318 				auto targetIndex = sameNameTags.countUntil(this);
319 				_parent._tags[ns][_name].removeIndex(targetIndex);
320 			}
321 
322 			// Remove from _parent._tags
323 			removeFromGroupedLookup(_namespace);
324 			removeFromGroupedLookup("*");
325 
326 			// Change name
327 			_name = value;
328 
329 			// Add to new locations in _parent._tags
330 			//TODO: Can we re-insert while preserving the original order?
331 			_parent._tags[_namespace][_name] ~= this;
332 			_parent._tags["*"][_name] ~= this;
333 		}
334 		else
335 			_name = value;
336 	}
337 
338 	/// This tag's name, including namespace if one exists.
339 	deprecated("Use 'getFullName().toString()'")
340 	@property string fullName()
341 	{
342 		return getFullName().toString();
343 	}
344 
345 	/// This tag's name, including namespace if one exists.
346 	FullName getFullName()
347 	{
348 		return FullName(_namespace, _name);
349 	}
350 
351 	// Tracks dirtiness. This is incremented every time a change is made which
352 	// could invalidate existing ranges. This way, the ranges can detect when
353 	// they've been invalidated.
354 	private size_t updateId=0;
355 
356 	this(Tag parent = null)
357 	{
358 		if(parent)
359 			parent.add(this);
360 	}
361 
362 	this(
363 		string namespace, string name,
364 		Value[] values=null, Attribute[] attributes=null, Tag[] children=null
365 	)
366 	{
367 		this(null, namespace, name, values, attributes, children);
368 	}
369 
370 	this(
371 		Tag parent, string namespace, string name,
372 		Value[] values=null, Attribute[] attributes=null, Tag[] children=null
373 	)
374 	{
375 		this._namespace = namespace;
376 		this._name      = name;
377 
378 		if(parent)
379 			parent.add(this);
380 
381 		this.values = values;
382 		this.add(attributes);
383 		this.add(children);
384 	}
385 
386 	/// Deep-copy this Tag.
387 	/// The clone does $(B $(I not)) have a parent, even if the original does.
388 	Tag clone()
389 	{
390 		auto newTag = new Tag(_namespace, _name, values.dup, allAttributes.clone(), allTags.clone());
391 		newTag.location = location;
392 		return newTag;
393 	}
394 
395 	private Attribute[] allAttributes; // In same order as specified in SDL file.
396 	private Tag[]       allTags;       // In same order as specified in SDL file.
397 	private string[]    allNamespaces; // In same order as specified in SDL file.
398 
399 	private size_t[][string] attributeIndicies; // allAttributes[ attributes[namespace][i] ]
400 	private size_t[][string] tagIndicies;       // allTags[ tags[namespace][i] ]
401 
402 	private Attribute[][string][string] _attributes; // attributes[namespace or "*"][name][i]
403 	private Tag[][string][string]       _tags;       // tags[namespace or "*"][name][i]
404 
405 	/// Adds a Value, Attribute, Tag (or array of such) as a member/child of this Tag.
406 	/// Returns `this` for chaining.
407 	/// Throws `ValidationException` if trying to add an Attribute or Tag
408 	/// that already has a parent.
409 	Tag add(Value val)
410 	{
411 		values ~= val;
412 		updateId++;
413 		return this;
414 	}
415 
416 	///ditto
417 	Tag add(Value[] vals)
418 	{
419 		foreach(val; vals)
420 			add(val);
421 
422 		return this;
423 	}
424 
425 	///ditto
426 	Tag add(Attribute attr)
427 	{
428 		if(attr._parent)
429 		{
430 			throw new ValidationException(
431 				"Attribute is already attached to a parent tag. "~
432 				"Use Attribute.remove() before adding it to another tag."
433 			);
434 		}
435 
436 		if(!allNamespaces.canFind(attr._namespace))
437 			allNamespaces ~= attr._namespace;
438 
439 		attr._parent = this;
440 
441 		allAttributes ~= attr;
442 		attributeIndicies[attr._namespace] ~= allAttributes.length-1;
443 		_attributes[attr._namespace][attr._name] ~= attr;
444 		_attributes["*"]            [attr._name] ~= attr;
445 
446 		updateId++;
447 		return this;
448 	}
449 
450 	///ditto
451 	Tag add(Attribute[] attrs)
452 	{
453 		foreach(attr; attrs)
454 			add(attr);
455 
456 		return this;
457 	}
458 
459 	///ditto
460 	Tag add(Tag tag)
461 	{
462 		if(tag._parent)
463 		{
464 			throw new ValidationException(
465 				"Tag is already attached to a parent tag. "~
466 				"Use Tag.remove() before adding it to another tag."
467 			);
468 		}
469 
470 		if(!allNamespaces.canFind(tag._namespace))
471 			allNamespaces ~= tag._namespace;
472 
473 		tag._parent = this;
474 
475 		allTags ~= tag;
476 		tagIndicies[tag._namespace] ~= allTags.length-1;
477 		_tags[tag._namespace][tag._name] ~= tag;
478 		_tags["*"]           [tag._name] ~= tag;
479 
480 		updateId++;
481 		return this;
482 	}
483 
484 	///ditto
485 	Tag add(Tag[] tags)
486 	{
487 		foreach(tag; tags)
488 			add(tag);
489 
490 		return this;
491 	}
492 
493 	/// Removes `this` from its parent, if any. Returns `this` for chaining.
494 	/// Inefficient ATM, but it works.
495 	Tag remove()
496 	{
497 		if(!_parent)
498 			return this;
499 
500 		void removeFromGroupedLookup(string ns)
501 		{
502 			// Remove from _parent._tags[ns]
503 			auto sameNameTags = _parent._tags[ns][_name];
504 			auto targetIndex = sameNameTags.countUntil(this);
505 			_parent._tags[ns][_name].removeIndex(targetIndex);
506 		}
507 
508 		// Remove from _parent._tags
509 		removeFromGroupedLookup(_namespace);
510 		removeFromGroupedLookup("*");
511 
512 		// Remove from _parent.allTags
513 		auto allTagsIndex = _parent.allTags.countUntil(this);
514 		_parent.allTags.removeIndex(allTagsIndex);
515 
516 		// Remove from _parent.tagIndicies
517 		auto sameNamespaceTags = _parent.tagIndicies[_namespace];
518 		auto tagIndiciesIndex = sameNamespaceTags.countUntil(allTagsIndex);
519 		_parent.tagIndicies[_namespace].removeIndex(tagIndiciesIndex);
520 
521 		// Fixup other indicies
522 		foreach(ns, ref nsTagIndicies; _parent.tagIndicies)
523 		foreach(k, ref v; nsTagIndicies)
524 		if(v > allTagsIndex)
525 			v--;
526 
527 		_parent.removeNamespaceIfEmpty(_namespace);
528 		_parent.updateId++;
529 		_parent = null;
530 		return this;
531 	}
532 
533 	private void removeNamespaceIfEmpty(string namespace)
534 	{
535 		// If namespace has no attributes, remove it from attributeIndicies/_attributes
536 		if(namespace in attributeIndicies && attributeIndicies[namespace].length == 0)
537 		{
538 			attributeIndicies.remove(namespace);
539 			_attributes.remove(namespace);
540 		}
541 
542 		// If namespace has no tags, remove it from tagIndicies/_tags
543 		if(namespace in tagIndicies && tagIndicies[namespace].length == 0)
544 		{
545 			tagIndicies.remove(namespace);
546 			_tags.remove(namespace);
547 		}
548 
549 		// If namespace is now empty, remove it from allNamespaces
550 		if(
551 			namespace !in tagIndicies &&
552 			namespace !in attributeIndicies
553 		)
554 		{
555 			auto allNamespacesIndex = allNamespaces.length - allNamespaces.find(namespace).length;
556 			allNamespaces = allNamespaces[0..allNamespacesIndex] ~ allNamespaces[allNamespacesIndex+1..$];
557 		}
558 	}
559 
560 	struct NamedMemberRange(T, string membersGrouped)
561 	{
562 		private Tag tag;
563 		private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name)
564 		private string name;
565 		private size_t updateId;  // Tag's updateId when this range was created.
566 
567 		this(Tag tag, string namespace, string name, size_t updateId)
568 		{
569 			this.tag       = tag;
570 			this.namespace = namespace;
571 			this.name      = name;
572 			this.updateId  = updateId;
573 			frontIndex = 0;
574 
575 			if(
576 				tag !is null &&
577 				namespace in mixin("tag."~membersGrouped) &&
578 				name in mixin("tag."~membersGrouped~"[namespace]")
579 			)
580 				endIndex = mixin("tag."~membersGrouped~"[namespace][name].length");
581 			else
582 				endIndex = 0;
583 		}
584 
585 		invariant()
586 		{
587 			assert(
588 				this.updateId == tag.updateId,
589 				"This range has been invalidated by a change to the tag."
590 			);
591 		}
592 
593 		@property bool empty()
594 		{
595 			return tag is null || frontIndex == endIndex;
596 		}
597 
598 		private size_t frontIndex;
599 		@property T front()
600 		{
601 			return this[0];
602 		}
603 		void popFront()
604 		{
605 			if(empty)
606 				throw new DOMRangeException(tag, "Range is empty");
607 
608 			frontIndex++;
609 		}
610 
611 		private size_t endIndex; // One past the last element
612 		@property T back()
613 		{
614 			return this[$-1];
615 		}
616 		void popBack()
617 		{
618 			if(empty)
619 				throw new DOMRangeException(tag, "Range is empty");
620 
621 			endIndex--;
622 		}
623 
624 		alias length opDollar;
625 		@property size_t length()
626 		{
627 			return endIndex - frontIndex;
628 		}
629 
630 		@property typeof(this) save()
631 		{
632 			auto r = typeof(this)(this.tag, this.namespace, this.name, this.updateId);
633 			r.frontIndex = this.frontIndex;
634 			r.endIndex   = this.endIndex;
635 			return r;
636 		}
637 
638 		typeof(this) opSlice()
639 		{
640 			return save();
641 		}
642 
643 		typeof(this) opSlice(size_t start, size_t end)
644 		{
645 			auto r = save();
646 			r.frontIndex = this.frontIndex + start;
647 			r.endIndex   = this.frontIndex + end;
648 
649 			if(
650 				r.frontIndex > this.endIndex ||
651 				r.endIndex > this.endIndex ||
652 				r.frontIndex > r.endIndex
653 			)
654 				throw new DOMRangeException(tag, "Slice out of range");
655 
656 			return r;
657 		}
658 
659 		T opIndex(size_t index)
660 		{
661 			if(empty)
662 				throw new DOMRangeException(tag, "Range is empty");
663 
664 			return mixin("tag."~membersGrouped~"[namespace][name][frontIndex+index]");
665 		}
666 	}
667 
668 	struct MemberRange(T, string allMembers, string memberIndicies, string membersGrouped)
669 	{
670 		private Tag tag;
671 		private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name)
672 		private bool isMaybe;
673 		private size_t updateId;  // Tag's updateId when this range was created.
674 		private size_t initialEndIndex;
675 
676 		this(Tag tag, string namespace, bool isMaybe)
677 		{
678 			this.tag       = tag;
679 			this.namespace = namespace;
680 			this.isMaybe   = isMaybe;
681 			frontIndex = 0;
682 
683 			if(tag is null)
684 				endIndex = 0;
685 			else
686 			{
687 				this.updateId = tag.updateId;
688 
689 				if(namespace == "*")
690 					initialEndIndex = mixin("tag."~allMembers~".length");
691 				else if(namespace in mixin("tag."~memberIndicies))
692 					initialEndIndex = mixin("tag."~memberIndicies~"[namespace].length");
693 				else
694 					initialEndIndex = 0;
695 
696 				endIndex = initialEndIndex;
697 			}
698 		}
699 
700 		invariant()
701 		{
702 			assert(
703 				this.updateId == tag.updateId,
704 				"This range has been invalidated by a change to the tag."
705 			);
706 		}
707 
708 		@property bool empty()
709 		{
710 			return tag is null || frontIndex == endIndex;
711 		}
712 
713 		private size_t frontIndex;
714 		@property T front()
715 		{
716 			return this[0];
717 		}
718 		void popFront()
719 		{
720 			if(empty)
721 				throw new DOMRangeException(tag, "Range is empty");
722 
723 			frontIndex++;
724 		}
725 
726 		private size_t endIndex; // One past the last element
727 		@property T back()
728 		{
729 			return this[$-1];
730 		}
731 		void popBack()
732 		{
733 			if(empty)
734 				throw new DOMRangeException(tag, "Range is empty");
735 
736 			endIndex--;
737 		}
738 
739 		alias length opDollar;
740 		@property size_t length()
741 		{
742 			return endIndex - frontIndex;
743 		}
744 
745 		@property typeof(this) save()
746 		{
747 			auto r = typeof(this)(this.tag, this.namespace, this.isMaybe);
748 			r.frontIndex      = this.frontIndex;
749 			r.endIndex        = this.endIndex;
750 			r.initialEndIndex = this.initialEndIndex;
751 			r.updateId        = this.updateId;
752 			return r;
753 		}
754 
755 		typeof(this) opSlice()
756 		{
757 			return save();
758 		}
759 
760 		typeof(this) opSlice(size_t start, size_t end)
761 		{
762 			auto r = save();
763 			r.frontIndex = this.frontIndex + start;
764 			r.endIndex   = this.frontIndex + end;
765 
766 			if(
767 				r.frontIndex > this.endIndex ||
768 				r.endIndex > this.endIndex ||
769 				r.frontIndex > r.endIndex
770 			)
771 				throw new DOMRangeException(tag, "Slice out of range");
772 
773 			return r;
774 		}
775 
776 		T opIndex(size_t index)
777 		{
778 			if(empty)
779 				throw new DOMRangeException(tag, "Range is empty");
780 
781 			if(namespace == "*")
782 				return mixin("tag."~allMembers~"[ frontIndex+index ]");
783 			else
784 				return mixin("tag."~allMembers~"[ tag."~memberIndicies~"[namespace][frontIndex+index] ]");
785 		}
786 
787 		alias NamedMemberRange!(T,membersGrouped) ThisNamedMemberRange;
788 		ThisNamedMemberRange opIndex(string name)
789 		{
790 			if(frontIndex != 0 || endIndex != initialEndIndex)
791 			{
792 				throw new DOMRangeException(tag,
793 					"Cannot lookup tags/attributes by name on a subset of a range, "~
794 					"only across the entire tag. "~
795 					"Please make sure you haven't called popFront or popBack on this "~
796 					"range and that you aren't using a slice of the range."
797 				);
798 			}
799 
800 			if(!isMaybe && empty)
801 				throw new DOMRangeException(tag, "Range is empty");
802 
803 			if(!isMaybe && name !in this)
804 				throw new DOMRangeException(tag, `No such `~T.stringof~` named: "`~name~`"`);
805 
806 			return ThisNamedMemberRange(tag, namespace, name, updateId);
807 		}
808 
809 		bool opBinaryRight(string op)(string name) if(op=="in")
810 		{
811 			if(frontIndex != 0 || endIndex != initialEndIndex)
812 			{
813 				throw new DOMRangeException(tag,
814 					"Cannot lookup tags/attributes by name on a subset of a range, "~
815 					"only across the entire tag. "~
816 					"Please make sure you haven't called popFront or popBack on this "~
817 					"range and that you aren't using a slice of the range."
818 				);
819 			}
820 
821 			if(tag is null)
822 				return false;
823 
824 			return
825 				namespace in mixin("tag."~membersGrouped) &&
826 				name in mixin("tag."~membersGrouped~"[namespace]") &&
827 				mixin("tag."~membersGrouped~"[namespace][name].length") > 0;
828 		}
829 	}
830 
831 	struct NamespaceRange
832 	{
833 		private Tag tag;
834 		private bool isMaybe;
835 		private size_t updateId;  // Tag's updateId when this range was created.
836 
837 		this(Tag tag, bool isMaybe)
838 		{
839 			this.tag     = tag;
840 			this.isMaybe = isMaybe;
841 			if(tag !is null)
842 				this.updateId = tag.updateId;
843 			frontIndex = 0;
844 			endIndex = tag.allNamespaces.length;
845 		}
846 
847 		invariant()
848 		{
849 			assert(
850 				this.updateId == tag.updateId,
851 				"This range has been invalidated by a change to the tag."
852 			);
853 		}
854 
855 		@property bool empty()
856 		{
857 			return frontIndex == endIndex;
858 		}
859 
860 		private size_t frontIndex;
861 		@property NamespaceAccess front()
862 		{
863 			return this[0];
864 		}
865 		void popFront()
866 		{
867 			if(empty)
868 				throw new DOMRangeException(tag, "Range is empty");
869 
870 			frontIndex++;
871 		}
872 
873 		private size_t endIndex; // One past the last element
874 		@property NamespaceAccess back()
875 		{
876 			return this[$-1];
877 		}
878 		void popBack()
879 		{
880 			if(empty)
881 				throw new DOMRangeException(tag, "Range is empty");
882 
883 			endIndex--;
884 		}
885 
886 		alias length opDollar;
887 		@property size_t length()
888 		{
889 			return endIndex - frontIndex;
890 		}
891 
892 		@property NamespaceRange save()
893 		{
894 			auto r = NamespaceRange(this.tag, this.isMaybe);
895 			r.frontIndex = this.frontIndex;
896 			r.endIndex   = this.endIndex;
897 			r.updateId   = this.updateId;
898 			return r;
899 		}
900 
901 		typeof(this) opSlice()
902 		{
903 			return save();
904 		}
905 
906 		typeof(this) opSlice(size_t start, size_t end)
907 		{
908 			auto r = save();
909 			r.frontIndex = this.frontIndex + start;
910 			r.endIndex   = this.frontIndex + end;
911 
912 			if(
913 				r.frontIndex > this.endIndex ||
914 				r.endIndex > this.endIndex ||
915 				r.frontIndex > r.endIndex
916 			)
917 				throw new DOMRangeException(tag, "Slice out of range");
918 
919 			return r;
920 		}
921 
922 		NamespaceAccess opIndex(size_t index)
923 		{
924 			if(empty)
925 				throw new DOMRangeException(tag, "Range is empty");
926 
927 			auto namespace = tag.allNamespaces[frontIndex+index];
928 			return NamespaceAccess(
929 				namespace,
930 				AttributeRange(tag, namespace, isMaybe),
931 				TagRange(tag, namespace, isMaybe)
932 			);
933 		}
934 
935 		NamespaceAccess opIndex(string namespace)
936 		{
937 			if(!isMaybe && empty)
938 				throw new DOMRangeException(tag, "Range is empty");
939 
940 			if(!isMaybe && namespace !in this)
941 				throw new DOMRangeException(tag, `No such namespace: "`~namespace~`"`);
942 
943 			return NamespaceAccess(
944 				namespace,
945 				AttributeRange(tag, namespace, isMaybe),
946 				TagRange(tag, namespace, isMaybe)
947 			);
948 		}
949 
950 		/// Inefficient when range is a slice or has used popFront/popBack, but it works.
951 		bool opBinaryRight(string op)(string namespace) if(op=="in")
952 		{
953 			if(frontIndex == 0 && endIndex == tag.allNamespaces.length)
954 			{
955 				return
956 					namespace in tag.attributeIndicies ||
957 					namespace in tag.tagIndicies;
958 			}
959 			else
960 				// Slower fallback method
961 				return tag.allNamespaces[frontIndex..endIndex].canFind(namespace);
962 		}
963 	}
964 
965 	static struct NamespaceAccess
966 	{
967 		string name;
968 		AttributeRange attributes;
969 		TagRange tags;
970 	}
971 
972 	alias MemberRange!(Attribute, "allAttributes", "attributeIndicies", "_attributes") AttributeRange;
973 	alias MemberRange!(Tag,       "allTags",       "tagIndicies",       "_tags"      ) TagRange;
974 	static assert(isRandomAccessRange!AttributeRange);
975 	static assert(isRandomAccessRange!TagRange);
976 	static assert(isRandomAccessRange!NamespaceRange);
977 
978 	/++
979 	Access all attributes that don't have a namespace
980 
981 	Returns a random access range of `Attribute` objects that supports
982 	numeric-indexing, string-indexing, slicing and length.
983 
984 	Since SDLang allows multiple attributes with the same name,
985 	string-indexing returns a random access range of all attributes
986 	with the given name.
987 
988 	The string-indexing does $(B $(I not)) support namespace prefixes.
989 	Use `namespace[string]`.`attributes` or `all`.`attributes` for that.
990 
991 	See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview)
992 	for a high-level overview (and examples) of how to use this.
993 	+/
994 	@property AttributeRange attributes()
995 	{
996 		return AttributeRange(this, "", false);
997 	}
998 
999 	/++
1000 	Access all direct-child tags that don't have a namespace.
1001 
1002 	Returns a random access range of `Tag` objects that supports
1003 	numeric-indexing, string-indexing, slicing and length.
1004 
1005 	Since SDLang allows multiple tags with the same name, string-indexing
1006 	returns a random access range of all immediate child tags with the
1007 	given name.
1008 
1009 	The string-indexing does $(B $(I not)) support namespace prefixes.
1010 	Use `namespace[string]`.`attributes` or `all`.`attributes` for that.
1011 
1012 	See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview)
1013 	for a high-level overview (and examples) of how to use this.
1014 	+/
1015 	@property TagRange tags()
1016 	{
1017 		return TagRange(this, "", false);
1018 	}
1019 
1020 	/++
1021 	Access all namespaces in this tag, and the attributes/tags within them.
1022 
1023 	Returns a random access range of `NamespaceAccess` elements that supports
1024 	numeric-indexing, string-indexing, slicing and length.
1025 
1026 	See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview)
1027 	for a high-level overview (and examples) of how to use this.
1028 	+/
1029 	@property NamespaceRange namespaces()
1030 	{
1031 		return NamespaceRange(this, false);
1032 	}
1033 
1034 	/// Access all attributes and tags regardless of namespace.
1035 	///
1036 	/// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview)
1037 	/// for a better understanding (and examples) of how to use this.
1038 	@property NamespaceAccess all()
1039 	{
1040 		// "*" isn't a valid namespace name, so we can use it to indicate "all namespaces"
1041 		return NamespaceAccess(
1042 			"*",
1043 			AttributeRange(this, "*", false),
1044 			TagRange(this, "*", false)
1045 		);
1046 	}
1047 
1048 	struct MaybeAccess
1049 	{
1050 		Tag tag;
1051 
1052 		/// Access all attributes that don't have a namespace
1053 		@property AttributeRange attributes()
1054 		{
1055 			return AttributeRange(tag, "", true);
1056 		}
1057 
1058 		/// Access all direct-child tags that don't have a namespace
1059 		@property TagRange tags()
1060 		{
1061 			return TagRange(tag, "", true);
1062 		}
1063 
1064 		/// Access all namespaces in this tag, and the attributes/tags within them.
1065 		@property NamespaceRange namespaces()
1066 		{
1067 			return NamespaceRange(tag, true);
1068 		}
1069 
1070 		/// Access all attributes and tags regardless of namespace.
1071 		@property NamespaceAccess all()
1072 		{
1073 			// "*" isn't a valid namespace name, so we can use it to indicate "all namespaces"
1074 			return NamespaceAccess(
1075 				"*",
1076 				AttributeRange(tag, "*", true),
1077 				TagRange(tag, "*", true)
1078 			);
1079 		}
1080 	}
1081 
1082 	/// Access `attributes`, `tags`, `namespaces` and `all` like normal,
1083 	/// except that looking up a non-existant name/namespace with
1084 	/// opIndex(string) results in an empty array instead of
1085 	/// a thrown `sdlang.exception.DOMRangeException`.
1086 	///
1087 	/// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview)
1088 	/// for a more information (and examples) of how to use this.
1089 	@property MaybeAccess maybe()
1090 	{
1091 		return MaybeAccess(this);
1092 	}
1093 
1094 	// Internal implementations for the get/expect functions further below:
1095 
1096 	private Tag getTagImpl(FullName tagFullName, Tag defaultValue=null, bool useDefaultValue=true)
1097 	{
1098 		auto tagNS   = tagFullName.namespace;
1099 		auto tagName = tagFullName.name;
1100 
1101 		// Can find namespace?
1102 		if(tagNS !in _tags)
1103 		{
1104 			if(useDefaultValue)
1105 				return defaultValue;
1106 			else
1107 				throw new TagNotFoundException(this, tagFullName, "No tags found in namespace '"~namespace~"'");
1108 		}
1109 
1110 		// Can find tag in namespace?
1111 		if(tagName !in _tags[tagNS] || _tags[tagNS][tagName].length == 0)
1112 		{
1113 			if(useDefaultValue)
1114 				return defaultValue;
1115 			else
1116 				throw new TagNotFoundException(this, tagFullName, "Can't find tag '"~tagFullName.toString()~"'");
1117 		}
1118 
1119 		// Return last matching tag found
1120 		return _tags[tagNS][tagName][$-1];
1121 	}
1122 
1123 	private T getValueImpl(T)(T defaultValue, bool useDefaultValue=true)
1124 	if(isValueType!T)
1125 	{
1126 		// Find value
1127 		foreach(value; this.values)
1128 		{
1129 			if(value.type == typeid(T))
1130 				return value.get!T();
1131 		}
1132 
1133 		// No value of type T found
1134 		if(useDefaultValue)
1135 			return defaultValue;
1136 		else
1137 		{
1138 			throw new ValueNotFoundException(
1139 				this,
1140 				FullName(this.namespace, this.name),
1141 				typeid(T),
1142 				"No value of type "~T.stringof~" found."
1143 			);
1144 		}
1145 	}
1146 
1147 	private T getAttributeImpl(T)(FullName attrFullName, T defaultValue, bool useDefaultValue=true)
1148 	if(isValueType!T)
1149 	{
1150 		auto attrNS   = attrFullName.namespace;
1151 		auto attrName = attrFullName.name;
1152 
1153 		// Can find namespace and attribute name?
1154 		if(attrNS !in this._attributes || attrName !in this._attributes[attrNS])
1155 		{
1156 			if(useDefaultValue)
1157 				return defaultValue;
1158 			else
1159 			{
1160 				throw new AttributeNotFoundException(
1161 					this, this.getFullName(), attrFullName, typeid(T),
1162 					"Can't find attribute '"~FullName.combine(attrNS, attrName)~"'"
1163 				);
1164 			}
1165 		}
1166 
1167 		// Find value with chosen type
1168 		foreach(attr; this._attributes[attrNS][attrName])
1169 		{
1170 			if(attr.value.type == typeid(T))
1171 				return attr.value.get!T();
1172 		}
1173 
1174 		// Chosen type not found
1175 		if(useDefaultValue)
1176 			return defaultValue;
1177 		else
1178 		{
1179 			throw new AttributeNotFoundException(
1180 				this, this.getFullName(), attrFullName, typeid(T),
1181 				"Can't find attribute '"~FullName.combine(attrNS, attrName)~"' of type "~T.stringof
1182 			);
1183 		}
1184 	}
1185 
1186 	// High-level interfaces for get/expect funtions:
1187 
1188 	/++
1189 	Lookup a child tag by name. Returns null if not found.
1190 
1191 	Useful if you only expect one, and only one, child tag of a given name.
1192 	Only looks for immediate child tags of `this`, doesn't search recursively.
1193 
1194 	If you expect multiple tags by the same name and want to get them all,
1195 	use `maybe`.`tags[string]` instead.
1196 
1197 	The name can optionally include a namespace, as in `"namespace:name"`.
1198 	Or, you can search all namespaces using `"*:name"`. Use an empty string
1199 	to search for anonymous tags, or `"namespace:"` for anonymous tags inside
1200 	a namespace. Wildcard searching is only supported for namespaces, not names.
1201 	Use `maybe`.`tags[0]` if you don't care about the name.
1202 
1203 	If there are multiple tags by the chosen name, the $(B $(I last tag)) will
1204 	always be chosen. That is, this function considers later tags with the
1205 	same name to override previous ones.
1206 
1207 	If the tag cannot be found, and you provides a default value, the default
1208 	value is returned. Otherwise null is returned. If you'd prefer an
1209 	exception thrown, use `expectTag` instead.
1210 	+/
1211 	Tag getTag(string fullTagName, Tag defaultValue=null)
1212 	{
1213 		auto parsedName = FullName.parse(fullTagName);
1214 		parsedName.ensureNoWildcardName(
1215 			"Instead, use 'Tag.maybe.tags[0]', 'Tag.maybe.all.tags[0]' or 'Tag.maybe.namespace[ns].tags[0]'."
1216 		);
1217 		return getTagImpl(parsedName, defaultValue);
1218 	}
1219 
1220 	///
1221 	@("Tag.getTag")
1222 	unittest
1223 	{
1224 		import std.exception;
1225 		import gfx.decl.sdlang.parser;
1226 
1227 		auto root = parseSource(`
1228 			foo 1
1229 			foo 2  // getTag considers this to override the first foo
1230 
1231 			ns1:foo 3
1232 			ns1:foo 4   // getTag considers this to override the first ns1:foo
1233 			ns2:foo 33
1234 			ns2:foo 44  // getTag considers this to override the first ns2:foo
1235 		`);
1236 		assert( root.getTag("foo"    ).values[0].get!int() == 2  );
1237 		assert( root.getTag("ns1:foo").values[0].get!int() == 4  );
1238 		assert( root.getTag("*:foo"  ).values[0].get!int() == 44 ); // Search all namespaces
1239 
1240 		// Not found
1241 		// If you'd prefer an exception, use `expectTag` instead.
1242 		assert( root.getTag("doesnt-exist") is null );
1243 
1244 		// Default value
1245 		auto foo = root.getTag("foo");
1246 		assert( root.getTag("doesnt-exist", foo) is foo );
1247 	}
1248 
1249 	/++
1250 	Lookup a child tag by name. Throws if not found.
1251 
1252 	Useful if you only expect one, and only one, child tag of a given name.
1253 	Only looks for immediate child tags of `this`, doesn't search recursively.
1254 
1255 	If you expect multiple tags by the same name and want to get them all,
1256 	use `tags[string]` instead.
1257 
1258 	The name can optionally include a namespace, as in `"namespace:name"`.
1259 	Or, you can search all namespaces using `"*:name"`. Use an empty string
1260 	to search for anonymous tags, or `"namespace:"` for anonymous tags inside
1261 	a namespace. Wildcard searching is only supported for namespaces, not names.
1262 	Use `tags[0]` if you don't care about the name.
1263 
1264 	If there are multiple tags by the chosen name, the $(B $(I last tag)) will
1265 	always be chosen. That is, this function considers later tags with the
1266 	same name to override previous ones.
1267 
1268 	If no such tag is found, an `sdlang.exception.TagNotFoundException` will
1269 	be thrown. If you'd rather receive a default value, use `getTag` instead.
1270 	+/
1271 	Tag expectTag(string fullTagName)
1272 	{
1273 		auto parsedName = FullName.parse(fullTagName);
1274 		parsedName.ensureNoWildcardName(
1275 			"Instead, use 'Tag.tags[0]', 'Tag.all.tags[0]' or 'Tag.namespace[ns].tags[0]'."
1276 		);
1277 		return getTagImpl(parsedName, null, false);
1278 	}
1279 
1280 	///
1281 	@("Tag.expectTag")
1282 	unittest
1283 	{
1284 		import std.exception;
1285 		import gfx.decl.sdlang.parser;
1286 
1287 		auto root = parseSource(`
1288 			foo 1
1289 			foo 2  // expectTag considers this to override the first foo
1290 
1291 			ns1:foo 3
1292 			ns1:foo 4   // expectTag considers this to override the first ns1:foo
1293 			ns2:foo 33
1294 			ns2:foo 44  // expectTag considers this to override the first ns2:foo
1295 		`);
1296 		assert( root.expectTag("foo"    ).values[0].get!int() == 2  );
1297 		assert( root.expectTag("ns1:foo").values[0].get!int() == 4  );
1298 		assert( root.expectTag("*:foo"  ).values[0].get!int() == 44 ); // Search all namespaces
1299 
1300 		// Not found
1301 		// If you'd rather receive a default value than an exception, use `getTag` instead.
1302 		assertThrown!TagNotFoundException( root.expectTag("doesnt-exist") );
1303 	}
1304 
1305 	/++
1306 	Retrieve a value of type T from `this` tag. Returns a default value if not found.
1307 
1308 	Useful if you only expect one value of type T from this tag. Only looks for
1309 	values of `this` tag, it does not search child tags. If you wish to search
1310 	for a value in a child tag (for example, if this current tag is a root tag),
1311 	try `getTagValue`.
1312 
1313 	If you want to get more than one value from this tag, use `values` instead.
1314 
1315 	If this tag has multiple values, the $(B $(I first)) value matching the
1316 	requested type will be returned. Ie, Extra values in the tag are ignored.
1317 
1318 	You may provide a default value to be returned in case no value of
1319 	the requested type can be found. If you don't provide a default value,
1320 	`T.init` will be used.
1321 
1322 	If you'd rather an exception be thrown when a value cannot be found,
1323 	use `expectValue` instead.
1324 	+/
1325 	T getValue(T)(T defaultValue = T.init) if(isValueType!T)
1326 	{
1327 		return getValueImpl!T(defaultValue, true);
1328 	}
1329 
1330 	///
1331 	@("Tag.getValue")
1332 	unittest
1333 	{
1334 		import std.exception;
1335 		import std.math;
1336 		import gfx.decl.sdlang.parser;
1337 
1338 		auto root = parseSource(`
1339 			foo 1 true 2 false
1340 		`);
1341 		auto foo = root.getTag("foo");
1342 		assert( foo.getValue!int() == 1 );
1343 		assert( foo.getValue!bool() == true );
1344 
1345 		// Value found, default value ignored.
1346 		assert( foo.getValue!int(999) == 1 );
1347 
1348 		// No strings found
1349 		// If you'd prefer an exception, use `expectValue` instead.
1350 		assert( foo.getValue!string("Default") == "Default" );
1351 		assert( foo.getValue!string() is null );
1352 
1353 		// No floats found
1354 		assert( foo.getValue!float(99.9).approxEqual(99.9) );
1355 		assert( foo.getValue!float().isNaN() );
1356 	}
1357 
1358 	/++
1359 	Retrieve a value of type T from `this` tag. Throws if not found.
1360 
1361 	Useful if you only expect one value of type T from this tag. Only looks
1362 	for values of `this` tag, it does not search child tags. If you wish to
1363 	search for a value in a child tag (for example, if this current tag is a
1364 	root tag), try `expectTagValue`.
1365 
1366 	If you want to get more than one value from this tag, use `values` instead.
1367 
1368 	If this tag has multiple values, the $(B $(I first)) value matching the
1369 	requested type will be returned. Ie, Extra values in the tag are ignored.
1370 
1371 	An `sdlang.exception.ValueNotFoundException` will be thrown if no value of
1372 	the requested type can be found. If you'd rather receive a default value,
1373 	use `getValue` instead.
1374 	+/
1375 	T expectValue(T)() if(isValueType!T)
1376 	{
1377 		return getValueImpl!T(T.init, false);
1378 	}
1379 
1380 	///
1381 	@("Tag.expectValue")
1382 	unittest
1383 	{
1384 		import std.exception;
1385 		import std.math;
1386 		import gfx.decl.sdlang.parser;
1387 
1388 		auto root = parseSource(`
1389 			foo 1 true 2 false
1390 		`);
1391 		auto foo = root.getTag("foo");
1392 		assert( foo.expectValue!int() == 1 );
1393 		assert( foo.expectValue!bool() == true );
1394 
1395 		// No strings or floats found
1396 		// If you'd rather receive a default value than an exception, use `getValue` instead.
1397 		assertThrown!ValueNotFoundException( foo.expectValue!string() );
1398 		assertThrown!ValueNotFoundException( foo.expectValue!float() );
1399 	}
1400 
1401 	/++
1402 	Lookup a child tag by name, and retrieve a value of type T from it.
1403 	Returns a default value if not found.
1404 
1405 	Useful if you only expect one value of type T from a given tag. Only looks
1406 	for immediate child tags of `this`, doesn't search recursively.
1407 
1408 	This is a shortcut for `getTag().getValue()`, except if the tag isn't found,
1409 	then instead of a null reference error, it will return the requested
1410 	`defaultValue` (or T.init by default).
1411 	+/
1412 	T getTagValue(T)(string fullTagName, T defaultValue = T.init) if(isValueType!T)
1413 	{
1414 		auto tag = getTag(fullTagName);
1415 		if(!tag)
1416 			return defaultValue;
1417 
1418 		return tag.getValue!T(defaultValue);
1419 	}
1420 
1421 	///
1422 	@("Tag.getTagValue")
1423 	unittest
1424 	{
1425 		import std.exception;
1426 		import gfx.decl.sdlang.parser;
1427 
1428 		auto root = parseSource(`
1429 			foo 1 "a" 2 "b"
1430 			foo 3 "c" 4 "d"  // getTagValue considers this to override the first foo
1431 
1432 			bar "hi"
1433 			bar 379  // getTagValue considers this to override the first bar
1434 		`);
1435 		assert( root.getTagValue!int("foo") == 3 );
1436 		assert( root.getTagValue!string("foo") == "c" );
1437 
1438 		// Value found, default value ignored.
1439 		assert( root.getTagValue!int("foo", 999) == 3 );
1440 
1441 		// Tag not found
1442 		// If you'd prefer an exception, use `expectTagValue` instead.
1443 		assert( root.getTagValue!int("doesnt-exist", 999) == 999 );
1444 		assert( root.getTagValue!int("doesnt-exist") == 0 );
1445 
1446 		// The last "bar" tag doesn't have an int (only the first "bar" tag does)
1447 		assert( root.getTagValue!string("bar", "Default") == "Default" );
1448 		assert( root.getTagValue!string("bar") is null );
1449 
1450 		// Using namespaces:
1451 		root = parseSource(`
1452 			ns1:foo 1 "a" 2 "b"
1453 			ns1:foo 3 "c" 4 "d"
1454 			ns2:foo 11 "aa" 22 "bb"
1455 			ns2:foo 33 "cc" 44 "dd"
1456 
1457 			ns1:bar "hi"
1458 			ns1:bar 379  // getTagValue considers this to override the first bar
1459 		`);
1460 		assert( root.getTagValue!int("ns1:foo") == 3  );
1461 		assert( root.getTagValue!int("*:foo"  ) == 33 ); // Search all namespaces
1462 
1463 		assert( root.getTagValue!string("ns1:foo") == "c"  );
1464 		assert( root.getTagValue!string("*:foo"  ) == "cc" ); // Search all namespaces
1465 
1466 		// The last "bar" tag doesn't have a string (only the first "bar" tag does)
1467 		assert( root.getTagValue!string("*:bar", "Default") == "Default" );
1468 		assert( root.getTagValue!string("*:bar") is null );
1469 	}
1470 
1471 	/++
1472 	Lookup a child tag by name, and retrieve a value of type T from it.
1473 	Throws if not found,
1474 
1475 	Useful if you only expect one value of type T from a given tag. Only
1476 	looks for immediate child tags of `this`, doesn't search recursively.
1477 
1478 	This is a shortcut for `expectTag().expectValue()`.
1479 	+/
1480 	T expectTagValue(T)(string fullTagName) if(isValueType!T)
1481 	{
1482 		return expectTag(fullTagName).expectValue!T();
1483 	}
1484 
1485 	///
1486 	@("Tag.expectTagValue")
1487 	unittest
1488 	{
1489 		import std.exception;
1490 		import gfx.decl.sdlang.parser;
1491 
1492 		auto root = parseSource(`
1493 			foo 1 "a" 2 "b"
1494 			foo 3 "c" 4 "d"  // expectTagValue considers this to override the first foo
1495 
1496 			bar "hi"
1497 			bar 379  // expectTagValue considers this to override the first bar
1498 		`);
1499 		assert( root.expectTagValue!int("foo") == 3 );
1500 		assert( root.expectTagValue!string("foo") == "c" );
1501 
1502 		// The last "bar" tag doesn't have a string (only the first "bar" tag does)
1503 		// If you'd rather receive a default value than an exception, use `getTagValue` instead.
1504 		assertThrown!ValueNotFoundException( root.expectTagValue!string("bar") );
1505 
1506 		// Tag not found
1507 		assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist") );
1508 
1509 		// Using namespaces:
1510 		root = parseSource(`
1511 			ns1:foo 1 "a" 2 "b"
1512 			ns1:foo 3 "c" 4 "d"
1513 			ns2:foo 11 "aa" 22 "bb"
1514 			ns2:foo 33 "cc" 44 "dd"
1515 
1516 			ns1:bar "hi"
1517 			ns1:bar 379  // expectTagValue considers this to override the first bar
1518 		`);
1519 		assert( root.expectTagValue!int("ns1:foo") == 3  );
1520 		assert( root.expectTagValue!int("*:foo"  ) == 33 ); // Search all namespaces
1521 
1522 		assert( root.expectTagValue!string("ns1:foo") == "c"  );
1523 		assert( root.expectTagValue!string("*:foo"  ) == "cc" ); // Search all namespaces
1524 
1525 		// The last "bar" tag doesn't have a string (only the first "bar" tag does)
1526 		assertThrown!ValueNotFoundException( root.expectTagValue!string("*:bar") );
1527 
1528 		// Namespace not found
1529 		assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist:bar") );
1530 	}
1531 
1532 	/++
1533 	Lookup an attribute of `this` tag by name, and retrieve a value of type T
1534 	from it. Returns a default value if not found.
1535 
1536 	Useful if you only expect one attribute of the given name and type.
1537 
1538 	Only looks for attributes of `this` tag, it does not search child tags.
1539 	If you wish to search for a value in a child tag (for example, if this
1540 	current tag is a root tag), try `getTagAttribute`.
1541 
1542 	If you expect multiple attributes by the same name and want to get them all,
1543 	use `maybe`.`attributes[string]` instead.
1544 
1545 	The attribute name can optionally include a namespace, as in
1546 	`"namespace:name"`. Or, you can search all namespaces using `"*:name"`.
1547 	(Note that unlike tags. attributes can't be anonymous - that's what
1548 	values are.) Wildcard searching is only supported for namespaces, not names.
1549 	Use `maybe`.`attributes[0]` if you don't care about the name.
1550 
1551 	If this tag has multiple attributes, the $(B $(I first)) attribute
1552 	matching the requested name and type will be returned. Ie, Extra
1553 	attributes in the tag are ignored.
1554 
1555 	You may provide a default value to be returned in case no attribute of
1556 	the requested name and type can be found. If you don't provide a default
1557 	value, `T.init` will be used.
1558 
1559 	If you'd rather an exception be thrown when an attribute cannot be found,
1560 	use `expectAttribute` instead.
1561 	+/
1562 	T getAttribute(T)(string fullAttributeName, T defaultValue = T.init) if(isValueType!T)
1563 	{
1564 		auto parsedName = FullName.parse(fullAttributeName);
1565 		parsedName.ensureNoWildcardName(
1566 			"Instead, use 'Attribute.maybe.tags[0]', 'Attribute.maybe.all.tags[0]' or 'Attribute.maybe.namespace[ns].tags[0]'."
1567 		);
1568 		return getAttributeImpl!T(parsedName, defaultValue);
1569 	}
1570 
1571 	///
1572 	@("Tag.getAttribute")
1573 	unittest
1574 	{
1575 		import std.exception;
1576 		import std.math;
1577 		import gfx.decl.sdlang.parser;
1578 
1579 		auto root = parseSource(`
1580 			foo z=0 X=1 X=true X=2 X=false
1581 		`);
1582 		auto foo = root.getTag("foo");
1583 		assert( foo.getAttribute!int("X") == 1 );
1584 		assert( foo.getAttribute!bool("X") == true );
1585 
1586 		// Value found, default value ignored.
1587 		assert( foo.getAttribute!int("X", 999) == 1 );
1588 
1589 		// Attribute name not found
1590 		// If you'd prefer an exception, use `expectValue` instead.
1591 		assert( foo.getAttribute!int("doesnt-exist", 999) == 999 );
1592 		assert( foo.getAttribute!int("doesnt-exist") == 0 );
1593 
1594 		// No strings found
1595 		assert( foo.getAttribute!string("X", "Default") == "Default" );
1596 		assert( foo.getAttribute!string("X") is null );
1597 
1598 		// No floats found
1599 		assert( foo.getAttribute!float("X", 99.9).approxEqual(99.9) );
1600 		assert( foo.getAttribute!float("X").isNaN() );
1601 
1602 
1603 		// Using namespaces:
1604 		root = parseSource(`
1605 			foo  ns1:z=0  ns1:X=1  ns1:X=2  ns2:X=3  ns2:X=4
1606 		`);
1607 		foo = root.getTag("foo");
1608 		assert( foo.getAttribute!int("ns2:X") == 3 );
1609 		assert( foo.getAttribute!int("*:X") == 1 ); // Search all namespaces
1610 
1611 		// Namespace not found
1612 		assert( foo.getAttribute!int("doesnt-exist:X", 999) == 999 );
1613 
1614 		// No attribute X is in the default namespace
1615 		assert( foo.getAttribute!int("X", 999) == 999 );
1616 
1617 		// Attribute name not found
1618 		assert( foo.getAttribute!int("ns1:doesnt-exist", 999) == 999 );
1619 	}
1620 
1621 	/++
1622 	Lookup an attribute of `this` tag by name, and retrieve a value of type T
1623 	from it. Throws if not found.
1624 
1625 	Useful if you only expect one attribute of the given name and type.
1626 
1627 	Only looks for attributes of `this` tag, it does not search child tags.
1628 	If you wish to search for a value in a child tag (for example, if this
1629 	current tag is a root tag), try `expectTagAttribute`.
1630 
1631 	If you expect multiple attributes by the same name and want to get them all,
1632 	use `attributes[string]` instead.
1633 
1634 	The attribute name can optionally include a namespace, as in
1635 	`"namespace:name"`. Or, you can search all namespaces using `"*:name"`.
1636 	(Note that unlike tags. attributes can't be anonymous - that's what
1637 	values are.) Wildcard searching is only supported for namespaces, not names.
1638 	Use `attributes[0]` if you don't care about the name.
1639 
1640 	If this tag has multiple attributes, the $(B $(I first)) attribute
1641 	matching the requested name and type will be returned. Ie, Extra
1642 	attributes in the tag are ignored.
1643 
1644 	An `sdlang.exception.AttributeNotFoundException` will be thrown if no
1645 	value of the requested type can be found. If you'd rather receive a
1646 	default value, use `getAttribute` instead.
1647 	+/
1648 	T expectAttribute(T)(string fullAttributeName) if(isValueType!T)
1649 	{
1650 		auto parsedName = FullName.parse(fullAttributeName);
1651 		parsedName.ensureNoWildcardName(
1652 			"Instead, use 'Attribute.tags[0]', 'Attribute.all.tags[0]' or 'Attribute.namespace[ns].tags[0]'."
1653 		);
1654 		return getAttributeImpl!T(parsedName, T.init, false);
1655 	}
1656 
1657 	///
1658 	@("Tag.expectAttribute")
1659 	unittest
1660 	{
1661 		import std.exception;
1662 		import std.math;
1663 		import gfx.decl.sdlang.parser;
1664 
1665 		auto root = parseSource(`
1666 			foo z=0 X=1 X=true X=2 X=false
1667 		`);
1668 		auto foo = root.getTag("foo");
1669 		assert( foo.expectAttribute!int("X") == 1 );
1670 		assert( foo.expectAttribute!bool("X") == true );
1671 
1672 		// Attribute name not found
1673 		// If you'd rather receive a default value than an exception, use `getAttribute` instead.
1674 		assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist") );
1675 
1676 		// No strings found
1677 		assertThrown!AttributeNotFoundException( foo.expectAttribute!string("X") );
1678 
1679 		// No floats found
1680 		assertThrown!AttributeNotFoundException( foo.expectAttribute!float("X") );
1681 
1682 
1683 		// Using namespaces:
1684 		root = parseSource(`
1685 			foo  ns1:z=0  ns1:X=1  ns1:X=2  ns2:X=3  ns2:X=4
1686 		`);
1687 		foo = root.getTag("foo");
1688 		assert( foo.expectAttribute!int("ns2:X") == 3 );
1689 		assert( foo.expectAttribute!int("*:X") == 1 ); // Search all namespaces
1690 
1691 		// Namespace not found
1692 		assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist:X") );
1693 
1694 		// No attribute X is in the default namespace
1695 		assertThrown!AttributeNotFoundException( foo.expectAttribute!int("X") );
1696 
1697 		// Attribute name not found
1698 		assertThrown!AttributeNotFoundException( foo.expectAttribute!int("ns1:doesnt-exist") );
1699 	}
1700 
1701 	/++
1702 	Lookup a child tag and attribute by name, and retrieve a value of type T
1703 	from it. Returns a default value if not found.
1704 
1705 	Useful if you only expect one attribute of type T from given
1706 	the tag and attribute names. Only looks for immediate child tags of
1707 	`this`, doesn't search recursively.
1708 
1709 	This is a shortcut for `getTag().getAttribute()`, except if the tag isn't
1710 	found, then instead of a null reference error, it will return the requested
1711 	`defaultValue` (or T.init by default).
1712 	+/
1713 	T getTagAttribute(T)(string fullTagName, string fullAttributeName, T defaultValue = T.init) if(isValueType!T)
1714 	{
1715 		auto tag = getTag(fullTagName);
1716 		if(!tag)
1717 			return defaultValue;
1718 
1719 		return tag.getAttribute!T(fullAttributeName, defaultValue);
1720 	}
1721 
1722 	///
1723 	@("Tag.getTagAttribute")
1724 	unittest
1725 	{
1726 		import std.exception;
1727 		import gfx.decl.sdlang.parser;
1728 
1729 		auto root = parseSource(`
1730 			foo X=1 X="a" X=2 X="b"
1731 			foo X=3 X="c" X=4 X="d"  // getTagAttribute considers this to override the first foo
1732 
1733 			bar X="hi"
1734 			bar X=379  // getTagAttribute considers this to override the first bar
1735 		`);
1736 		assert( root.getTagAttribute!int("foo", "X") == 3 );
1737 		assert( root.getTagAttribute!string("foo", "X") == "c" );
1738 
1739 		// Value found, default value ignored.
1740 		assert( root.getTagAttribute!int("foo", "X", 999) == 3 );
1741 
1742 		// Tag not found
1743 		// If you'd prefer an exception, use `expectTagAttribute` instead of `getTagAttribute`
1744 		assert( root.getTagAttribute!int("doesnt-exist", "X", 999)   == 999 );
1745 		assert( root.getTagAttribute!int("doesnt-exist", "X")        == 0   );
1746 		assert( root.getTagAttribute!int("foo", "doesnt-exist", 999) == 999 );
1747 		assert( root.getTagAttribute!int("foo", "doesnt-exist")      == 0   );
1748 
1749 		// The last "bar" tag doesn't have a string (only the first "bar" tag does)
1750 		assert( root.getTagAttribute!string("bar", "X", "Default") == "Default" );
1751 		assert( root.getTagAttribute!string("bar", "X") is null );
1752 
1753 
1754 		// Using namespaces:
1755 		root = parseSource(`
1756 			ns1:foo X=1 X="a" X=2 X="b"
1757 			ns1:foo X=3 X="c" X=4 X="d"
1758 			ns2:foo X=11 X="aa" X=22 X="bb"
1759 			ns2:foo X=33 X="cc" X=44 X="dd"
1760 
1761 			ns1:bar attrNS:X="hi"
1762 			ns1:bar attrNS:X=379  // getTagAttribute considers this to override the first bar
1763 		`);
1764 		assert( root.getTagAttribute!int("ns1:foo", "X") == 3  );
1765 		assert( root.getTagAttribute!int("*:foo",   "X") == 33 ); // Search all namespaces
1766 
1767 		assert( root.getTagAttribute!string("ns1:foo", "X") == "c"  );
1768 		assert( root.getTagAttribute!string("*:foo",   "X") == "cc" ); // Search all namespaces
1769 
1770 		// bar's attribute X is't in the default namespace
1771 		assert( root.getTagAttribute!int("*:bar", "X", 999) == 999 );
1772 		assert( root.getTagAttribute!int("*:bar", "X") == 0 );
1773 
1774 		// The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does)
1775 		assert( root.getTagAttribute!string("*:bar", "attrNS:X", "Default") == "Default" );
1776 		assert( root.getTagAttribute!string("*:bar", "attrNS:X") is null);
1777 	}
1778 
1779 	/++
1780 	Lookup a child tag and attribute by name, and retrieve a value of type T
1781 	from it. Throws if not found.
1782 
1783 	Useful if you only expect one attribute of type T from given
1784 	the tag and attribute names. Only looks for immediate child tags of
1785 	`this`, doesn't search recursively.
1786 
1787 	This is a shortcut for `expectTag().expectAttribute()`.
1788 	+/
1789 	T expectTagAttribute(T)(string fullTagName, string fullAttributeName) if(isValueType!T)
1790 	{
1791 		return expectTag(fullTagName).expectAttribute!T(fullAttributeName);
1792 	}
1793 
1794 	///
1795 	@("Tag.expectTagAttribute")
1796 	unittest
1797 	{
1798 		import std.exception;
1799 		import gfx.decl.sdlang.parser;
1800 
1801 		auto root = parseSource(`
1802 			foo X=1 X="a" X=2 X="b"
1803 			foo X=3 X="c" X=4 X="d"  // expectTagAttribute considers this to override the first foo
1804 
1805 			bar X="hi"
1806 			bar X=379  // expectTagAttribute considers this to override the first bar
1807 		`);
1808 		assert( root.expectTagAttribute!int("foo", "X") == 3 );
1809 		assert( root.expectTagAttribute!string("foo", "X") == "c" );
1810 
1811 		// The last "bar" tag doesn't have an int attribute named "X" (only the first "bar" tag does)
1812 		// If you'd rather receive a default value than an exception, use `getAttribute` instead.
1813 		assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("bar", "X") );
1814 
1815 		// Tag not found
1816 		assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist", "X") );
1817 
1818 		// Using namespaces:
1819 		root = parseSource(`
1820 			ns1:foo X=1 X="a" X=2 X="b"
1821 			ns1:foo X=3 X="c" X=4 X="d"
1822 			ns2:foo X=11 X="aa" X=22 X="bb"
1823 			ns2:foo X=33 X="cc" X=44 X="dd"
1824 
1825 			ns1:bar attrNS:X="hi"
1826 			ns1:bar attrNS:X=379  // expectTagAttribute considers this to override the first bar
1827 		`);
1828 		assert( root.expectTagAttribute!int("ns1:foo", "X") == 3  );
1829 		assert( root.expectTagAttribute!int("*:foo",   "X") == 33 ); // Search all namespaces
1830 
1831 		assert( root.expectTagAttribute!string("ns1:foo", "X") == "c"  );
1832 		assert( root.expectTagAttribute!string("*:foo",   "X") == "cc" ); // Search all namespaces
1833 
1834 		// bar's attribute X is't in the default namespace
1835 		assertThrown!AttributeNotFoundException( root.expectTagAttribute!int("*:bar", "X") );
1836 
1837 		// The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does)
1838 		assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("*:bar", "attrNS:X") );
1839 
1840 		// Tag's namespace not found
1841 		assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist:bar", "attrNS:X") );
1842 	}
1843 
1844 	/++
1845 	Lookup a child tag by name, and retrieve all values from it.
1846 
1847 	This just like using `getTag()`.`values`, except if the tag isn't found,
1848 	it safely returns null (or an optional array of default values) instead of
1849 	a dereferencing null error.
1850 
1851 	Note that, unlike `getValue`, this doesn't discriminate by the value's
1852 	type. It simply returns all values of a single tag as a `Value[]`.
1853 
1854 	If you'd prefer an exception thrown when the tag isn't found, use
1855 	`expectTag`.`values` instead.
1856 	+/
1857 	Value[] getTagValues(string fullTagName, Value[] defaultValues = null)
1858 	{
1859 		auto tag = getTag(fullTagName);
1860 		if(tag)
1861 			return tag.values;
1862 		else
1863 			return defaultValues;
1864 	}
1865 
1866 	///
1867 	@("getTagValues")
1868 	unittest
1869 	{
1870 		import std.exception;
1871 		import gfx.decl.sdlang.parser;
1872 
1873 		auto root = parseSource(`
1874 			foo 1 "a" 2 "b"
1875 			foo 3 "c" 4 "d"  // getTagValues considers this to override the first foo
1876 		`);
1877 		assert( root.getTagValues("foo") == [Value(3), Value("c"), Value(4), Value("d")] );
1878 
1879 		// Tag not found
1880 		// If you'd prefer an exception, use `expectTag.values` instead.
1881 		assert( root.getTagValues("doesnt-exist") is null );
1882 		assert( root.getTagValues("doesnt-exist", [ Value(999), Value("Not found") ]) ==
1883 			[ Value(999), Value("Not found") ] );
1884 	}
1885 
1886 	/++
1887 	Lookup a child tag by name, and retrieve all attributes in a chosen
1888 	(or default) namespace from it.
1889 
1890 	This just like using `getTag()`.`attributes` (or
1891 	`getTag()`.`namespace[...]`.`attributes`, or `getTag()`.`all`.`attributes`),
1892 	except if the tag isn't found, it safely returns an empty range instead
1893 	of a dereferencing null error.
1894 
1895 	If provided, the `attributeNamespace` parameter can be either the name of
1896 	a namespace, or an empty string for the default namespace (the default),
1897 	or `"*"` to retreive attributes from all namespaces.
1898 
1899 	Note that, unlike `getAttributes`, this doesn't discriminate by the
1900 	value's type. It simply returns the usual `attributes` range.
1901 
1902 	If you'd prefer an exception thrown when the tag isn't found, use
1903 	`expectTag`.`attributes` instead.
1904 	+/
1905 	auto getTagAttributes(string fullTagName, string attributeNamespace = null)
1906 	{
1907 		auto tag = getTag(fullTagName);
1908 		if(tag)
1909 		{
1910 			if(attributeNamespace && attributeNamespace in tag.namespaces)
1911 				return tag.namespaces[attributeNamespace].attributes;
1912 			else if(attributeNamespace == "*")
1913 				return tag.all.attributes;
1914 			else
1915 				return tag.attributes;
1916 		}
1917 
1918 		return AttributeRange(null, null, false);
1919 	}
1920 
1921 	///
1922 	@("getTagAttributes")
1923 	unittest
1924 	{
1925 		import std.exception;
1926 		import gfx.decl.sdlang.parser;
1927 
1928 		auto root = parseSource(`
1929 			foo X=1 X=2
1930 
1931 			// getTagAttributes considers this to override the first foo
1932 			foo X1=3 X2="c" namespace:bar=7 X3=4 X4="d"
1933 		`);
1934 
1935 		auto fooAttrs = root.getTagAttributes("foo");
1936 		assert( !fooAttrs.empty );
1937 		assert( fooAttrs.length == 4 );
1938 		assert( fooAttrs[0].name == "X1" && fooAttrs[0].value == Value(3)   );
1939 		assert( fooAttrs[1].name == "X2" && fooAttrs[1].value == Value("c") );
1940 		assert( fooAttrs[2].name == "X3" && fooAttrs[2].value == Value(4)   );
1941 		assert( fooAttrs[3].name == "X4" && fooAttrs[3].value == Value("d") );
1942 
1943 		fooAttrs = root.getTagAttributes("foo", "namespace");
1944 		assert( !fooAttrs.empty );
1945 		assert( fooAttrs.length == 1 );
1946 		assert( fooAttrs[0].name == "bar" && fooAttrs[0].value == Value(7) );
1947 
1948 		fooAttrs = root.getTagAttributes("foo", "*");
1949 		assert( !fooAttrs.empty );
1950 		assert( fooAttrs.length == 5 );
1951 		assert( fooAttrs[0].name == "X1"  && fooAttrs[0].value == Value(3)   );
1952 		assert( fooAttrs[1].name == "X2"  && fooAttrs[1].value == Value("c") );
1953 		assert( fooAttrs[2].name == "bar" && fooAttrs[2].value == Value(7)   );
1954 		assert( fooAttrs[3].name == "X3"  && fooAttrs[3].value == Value(4)   );
1955 		assert( fooAttrs[4].name == "X4"  && fooAttrs[4].value == Value("d") );
1956 
1957 		// Tag not found
1958 		// If you'd prefer an exception, use `expectTag.attributes` instead.
1959 		assert( root.getTagValues("doesnt-exist").empty );
1960 	}
1961 
1962 	@("*: Disallow wildcards for names")
1963 	unittest
1964 	{
1965 		import std.exception;
1966 		import std.math;
1967 		import gfx.decl.sdlang.parser;
1968 
1969 		auto root = parseSource(`
1970 			foo 1 X=2
1971 			ns:foo 3 ns:X=4
1972 		`);
1973 		auto foo = root.getTag("foo");
1974 		auto nsfoo = root.getTag("ns:foo");
1975 
1976 		// Sanity check
1977 		assert( foo !is null );
1978 		assert( foo.name == "foo" );
1979 		assert( foo.namespace == "" );
1980 
1981 		assert( nsfoo !is null );
1982 		assert( nsfoo.name == "foo" );
1983 		assert( nsfoo.namespace == "ns" );
1984 
1985 		assert( foo.getValue     !int() == 1 );
1986 		assert( foo.expectValue  !int() == 1 );
1987 		assert( nsfoo.getValue   !int() == 3 );
1988 		assert( nsfoo.expectValue!int() == 3 );
1989 
1990 		assert( root.getTagValue   !int("foo")    == 1 );
1991 		assert( root.expectTagValue!int("foo")    == 1 );
1992 		assert( root.getTagValue   !int("ns:foo") == 3 );
1993 		assert( root.expectTagValue!int("ns:foo") == 3 );
1994 
1995 		assert( foo.getAttribute     !int("X")    == 2 );
1996 		assert( foo.expectAttribute  !int("X")    == 2 );
1997 		assert( nsfoo.getAttribute   !int("ns:X") == 4 );
1998 		assert( nsfoo.expectAttribute!int("ns:X") == 4 );
1999 
2000 		assert( root.getTagAttribute   !int("foo", "X")       == 2 );
2001 		assert( root.expectTagAttribute!int("foo", "X")       == 2 );
2002 		assert( root.getTagAttribute   !int("ns:foo", "ns:X") == 4 );
2003 		assert( root.expectTagAttribute!int("ns:foo", "ns:X") == 4 );
2004 
2005 		// No namespace
2006 		assertThrown!ArgumentException( root.getTag   ("*") );
2007 		assertThrown!ArgumentException( root.expectTag("*") );
2008 
2009 		assertThrown!ArgumentException( root.getTagValue   !int("*") );
2010 		assertThrown!ArgumentException( root.expectTagValue!int("*") );
2011 
2012 		assertThrown!ArgumentException( foo.getAttribute       !int("*")        );
2013 		assertThrown!ArgumentException( foo.expectAttribute    !int("*")        );
2014 		assertThrown!ArgumentException( root.getTagAttribute   !int("*", "X")   );
2015 		assertThrown!ArgumentException( root.expectTagAttribute!int("*", "X")   );
2016 		assertThrown!ArgumentException( root.getTagAttribute   !int("foo", "*") );
2017 		assertThrown!ArgumentException( root.expectTagAttribute!int("foo", "*") );
2018 
2019 		// With namespace
2020 		assertThrown!ArgumentException( root.getTag   ("ns:*") );
2021 		assertThrown!ArgumentException( root.expectTag("ns:*") );
2022 
2023 		assertThrown!ArgumentException( root.getTagValue   !int("ns:*") );
2024 		assertThrown!ArgumentException( root.expectTagValue!int("ns:*") );
2025 
2026 		assertThrown!ArgumentException( nsfoo.getAttribute     !int("ns:*")           );
2027 		assertThrown!ArgumentException( nsfoo.expectAttribute  !int("ns:*")           );
2028 		assertThrown!ArgumentException( root.getTagAttribute   !int("ns:*",   "ns:X") );
2029 		assertThrown!ArgumentException( root.expectTagAttribute!int("ns:*",   "ns:X") );
2030 		assertThrown!ArgumentException( root.getTagAttribute   !int("ns:foo", "ns:*") );
2031 		assertThrown!ArgumentException( root.expectTagAttribute!int("ns:foo", "ns:*") );
2032 
2033 		// With wildcard namespace
2034 		assertThrown!ArgumentException( root.getTag   ("*:*") );
2035 		assertThrown!ArgumentException( root.expectTag("*:*") );
2036 
2037 		assertThrown!ArgumentException( root.getTagValue   !int("*:*") );
2038 		assertThrown!ArgumentException( root.expectTagValue!int("*:*") );
2039 
2040 		assertThrown!ArgumentException( nsfoo.getAttribute     !int("*:*")          );
2041 		assertThrown!ArgumentException( nsfoo.expectAttribute  !int("*:*")          );
2042 		assertThrown!ArgumentException( root.getTagAttribute   !int("*:*",   "*:X") );
2043 		assertThrown!ArgumentException( root.expectTagAttribute!int("*:*",   "*:X") );
2044 		assertThrown!ArgumentException( root.getTagAttribute   !int("*:foo", "*:*") );
2045 		assertThrown!ArgumentException( root.expectTagAttribute!int("*:foo", "*:*") );
2046 	}
2047 
2048 	override bool opEquals(Object o)
2049 	{
2050 		auto t = cast(Tag)o;
2051 		if(!t)
2052 			return false;
2053 
2054 		if(_namespace != t._namespace || _name != t._name)
2055 			return false;
2056 
2057 		if(
2058 			values        .length != t.values       .length ||
2059 			allAttributes .length != t.allAttributes.length ||
2060 			allNamespaces .length != t.allNamespaces.length ||
2061 			allTags       .length != t.allTags      .length
2062 		)
2063 			return false;
2064 
2065 		if(values != t.values)
2066 			return false;
2067 
2068 		if(allNamespaces != t.allNamespaces)
2069 			return false;
2070 
2071 		if(allAttributes != t.allAttributes)
2072 			return false;
2073 
2074 		// Ok because cycles are not allowed
2075 		//TODO: Actually check for or prevent cycles.
2076 		return allTags == t.allTags;
2077 	}
2078 
2079 	/// Treats `this` as the root tag. Note that root tags cannot have
2080 	/// values or attributes, and cannot be part of a namespace.
2081 	/// If this isn't a valid root tag, `sdlang.exception.ValidationException`
2082 	/// will be thrown.
2083 	string toSDLDocument()(string indent="\t", int indentLevel=0)
2084 	{
2085 		Appender!string sink;
2086 		toSDLDocument(sink, indent, indentLevel);
2087 		return sink.data;
2088 	}
2089 
2090 	///ditto
2091 	void toSDLDocument(Sink)(ref Sink sink, string indent="\t", int indentLevel=0)
2092 		if(isOutputRange!(Sink,char))
2093 	{
2094 		if(values.length > 0)
2095 			throw new ValidationException("Root tags cannot have any values, only child tags.");
2096 
2097 		if(allAttributes.length > 0)
2098 			throw new ValidationException("Root tags cannot have any attributes, only child tags.");
2099 
2100 		if(_namespace != "")
2101 			throw new ValidationException("Root tags cannot have a namespace.");
2102 
2103 		foreach(tag; allTags)
2104 			tag.toSDLString(sink, indent, indentLevel);
2105 	}
2106 
2107 	/// Output this entire tag in SDL format. Does $(B $(I not)) treat `this` as
2108 	/// a root tag. If you intend this to be the root of a standard SDL
2109 	/// document, use `toSDLDocument` instead.
2110 	string toSDLString()(string indent="\t", int indentLevel=0)
2111 	{
2112 		Appender!string sink;
2113 		toSDLString(sink, indent, indentLevel);
2114 		return sink.data;
2115 	}
2116 
2117 	///ditto
2118 	void toSDLString(Sink)(ref Sink sink, string indent="\t", int indentLevel=0)
2119 		if(isOutputRange!(Sink,char))
2120 	{
2121 		if(_name == "" && values.length == 0)
2122 			throw new ValidationException("Anonymous tags must have at least one value.");
2123 
2124 		if(_name == "" && _namespace != "")
2125 			throw new ValidationException("Anonymous tags cannot have a namespace.");
2126 
2127 		// Indent
2128 		foreach(i; 0..indentLevel)
2129 			sink.put(indent);
2130 
2131 		// Name
2132 		if(_namespace != "")
2133 		{
2134 			sink.put(_namespace);
2135 			sink.put(':');
2136 		}
2137 		sink.put(_name);
2138 
2139 		// Values
2140 		foreach(i, v; values)
2141 		{
2142 			// Omit the first space for anonymous tags
2143 			if(_name != "" || i > 0)
2144 				sink.put(' ');
2145 
2146 			v.toSDLString(sink);
2147 		}
2148 
2149 		// Attributes
2150 		foreach(attr; allAttributes)
2151 		{
2152 			sink.put(' ');
2153 			attr.toSDLString(sink);
2154 		}
2155 
2156 		// Child tags
2157 		bool foundChild=false;
2158 		foreach(tag; allTags)
2159 		{
2160 			if(!foundChild)
2161 			{
2162 				sink.put(" {\n");
2163 				foundChild = true;
2164 			}
2165 
2166 			tag.toSDLString(sink, indent, indentLevel+1);
2167 		}
2168 		if(foundChild)
2169 		{
2170 			foreach(i; 0..indentLevel)
2171 				sink.put(indent);
2172 
2173 			sink.put("}\n");
2174 		}
2175 		else
2176 			sink.put("\n");
2177 	}
2178 
2179 	/// Outputs full information on the tag.
2180 	string toDebugString()
2181 	{
2182 		import std.algorithm : sort;
2183 
2184 		Appender!string buf;
2185 
2186 		buf.put("\n");
2187 		buf.put("Tag ");
2188 		if(_namespace != "")
2189 		{
2190 			buf.put("[");
2191 			buf.put(_namespace);
2192 			buf.put("]");
2193 		}
2194 		buf.put("'%s':\n".format(_name));
2195 
2196 		// Values
2197 		foreach(val; values)
2198 			buf.put("    (%s): %s\n".format(.toString(val.type), val));
2199 
2200 		// Attributes
2201 		foreach(attrNamespace; _attributes.keys.sort())
2202 		if(attrNamespace != "*")
2203 		foreach(attrName; _attributes[attrNamespace].keys.sort())
2204 		foreach(attr; _attributes[attrNamespace][attrName])
2205 		{
2206 			string namespaceStr;
2207 			if(attr._namespace != "")
2208 				namespaceStr = "["~attr._namespace~"]";
2209 
2210 			buf.put(
2211 				"    %s%s(%s): %s\n".format(
2212 					namespaceStr, attr._name, .toString(attr.value.type), attr.value
2213 				)
2214 			);
2215 		}
2216 
2217 		// Children
2218 		foreach(tagNamespace; _tags.keys.sort())
2219 		if(tagNamespace != "*")
2220 		foreach(tagName; _tags[tagNamespace].keys.sort())
2221 		foreach(tag; _tags[tagNamespace][tagName])
2222 			buf.put( tag.toDebugString().replace("\n", "\n    ") );
2223 
2224 		return buf.data;
2225 	}
2226 }
2227 
2228 version(unittest)
2229 {
2230 	private void testRandomAccessRange(R, E)(R range, E[] expected, bool function(E, E) equals=null)
2231 	{
2232 		static assert(isRandomAccessRange!R);
2233 		static assert(is(ElementType!R == E));
2234 		static assert(hasLength!R);
2235 		static assert(!isInfinite!R);
2236 
2237 		assert(range.length == expected.length);
2238 		if(range.length == 0)
2239 		{
2240 			assert(range.empty);
2241 			return;
2242 		}
2243 
2244 		static bool defaultEquals(E e1, E e2)
2245 		{
2246 			return e1 == e2;
2247 		}
2248 		if(equals is null)
2249 			equals = &defaultEquals;
2250 
2251 		assert(equals(range.front, expected[0]));
2252 		assert(equals(range.front, expected[0]));  // Ensure consistent result from '.front'
2253 		assert(equals(range.front, expected[0]));  // Ensure consistent result from '.front'
2254 
2255 		assert(equals(range.back, expected[$-1]));
2256 		assert(equals(range.back, expected[$-1]));  // Ensure consistent result from '.back'
2257 		assert(equals(range.back, expected[$-1]));  // Ensure consistent result from '.back'
2258 
2259 		// Forward iteration
2260 		auto original = range.save;
2261 		auto r2 = range.save;
2262 		foreach(i; 0..expected.length)
2263 		{
2264 			//trace("Forward iteration: ", i);
2265 
2266 			// Test length/empty
2267 			assert(range.length == expected.length - i);
2268 			assert(range.length == r2.length);
2269 			assert(!range.empty);
2270 			assert(!r2.empty);
2271 
2272 			// Test front
2273 			assert(equals(range.front, expected[i]));
2274 			assert(equals(range.front, r2.front));
2275 
2276 			// Test back
2277 			assert(equals(range.back, expected[$-1]));
2278 			assert(equals(range.back, r2.back));
2279 
2280 			// Test opIndex(0)
2281 			assert(equals(range[0], expected[i]));
2282 			assert(equals(range[0], r2[0]));
2283 
2284 			// Test opIndex($-1)
2285 			assert(equals(range[$-1], expected[$-1]));
2286 			assert(equals(range[$-1], r2[$-1]));
2287 
2288 			// Test popFront
2289 			range.popFront();
2290 			assert(range.length == r2.length - 1);
2291 			r2.popFront();
2292 			assert(range.length == r2.length);
2293 		}
2294 		assert(range.empty);
2295 		assert(r2.empty);
2296 		assert(original.length == expected.length);
2297 
2298 		// Backwards iteration
2299 		range = original.save;
2300 		r2    = original.save;
2301 		foreach(i; iota(0, expected.length).retro())
2302 		{
2303 			//trace("Backwards iteration: ", i);
2304 
2305 			// Test length/empty
2306 			assert(range.length == i+1);
2307 			assert(range.length == r2.length);
2308 			assert(!range.empty);
2309 			assert(!r2.empty);
2310 
2311 			// Test front
2312 			assert(equals(range.front, expected[0]));
2313 			assert(equals(range.front, r2.front));
2314 
2315 			// Test back
2316 			assert(equals(range.back, expected[i]));
2317 			assert(equals(range.back, r2.back));
2318 
2319 			// Test opIndex(0)
2320 			assert(equals(range[0], expected[0]));
2321 			assert(equals(range[0], r2[0]));
2322 
2323 			// Test opIndex($-1)
2324 			assert(equals(range[$-1], expected[i]));
2325 			assert(equals(range[$-1], r2[$-1]));
2326 
2327 			// Test popBack
2328 			range.popBack();
2329 			assert(range.length == r2.length - 1);
2330 			r2.popBack();
2331 			assert(range.length == r2.length);
2332 		}
2333 		assert(range.empty);
2334 		assert(r2.empty);
2335 		assert(original.length == expected.length);
2336 
2337 		// Random access
2338 		range = original.save;
2339 		r2    = original.save;
2340 		foreach(i; 0..expected.length)
2341 		{
2342 			//trace("Random access: ", i);
2343 
2344 			// Test length/empty
2345 			assert(range.length == expected.length);
2346 			assert(range.length == r2.length);
2347 			assert(!range.empty);
2348 			assert(!r2.empty);
2349 
2350 			// Test front
2351 			assert(equals(range.front, expected[0]));
2352 			assert(equals(range.front, r2.front));
2353 
2354 			// Test back
2355 			assert(equals(range.back, expected[$-1]));
2356 			assert(equals(range.back, r2.back));
2357 
2358 			// Test opIndex(i)
2359 			assert(equals(range[i], expected[i]));
2360 			assert(equals(range[i], r2[i]));
2361 		}
2362 		assert(!range.empty);
2363 		assert(!r2.empty);
2364 		assert(original.length == expected.length);
2365 	}
2366 }
2367 
2368 @("*: Test sdlang ast")
2369 unittest
2370 {
2371 	import std.exception;
2372 	import gfx.decl.sdlang.parser;
2373 
2374 	Tag root;
2375 	root = parseSource("");
2376 	testRandomAccessRange(root.attributes, cast(          Attribute[])[]);
2377 	testRandomAccessRange(root.tags,       cast(                Tag[])[]);
2378 	testRandomAccessRange(root.namespaces, cast(Tag.NamespaceAccess[])[]);
2379 
2380 	root = parseSource(`
2381 		blue 3 "Lee" isThree=true
2382 		blue 5 "Chan" 12345 isThree=false
2383 		stuff:orange 1 2 3 2 1
2384 		stuff:square points=4 dimensions=2 points="Still four"
2385 		stuff:triangle data:points=3 data:dimensions=2
2386 		nothing
2387 		namespaces small:A=1 med:A=2 big:A=3 small:B=10 big:B=30
2388 
2389 		people visitor:a=1 b=2 {
2390 			chiyo "Small" "Flies?" nemesis="Car" score=100
2391 			yukari
2392 			visitor:sana
2393 			tomo
2394 			visitor:hayama
2395 		}
2396 	`);
2397 
2398 	auto blue3 = new Tag(
2399 		null, "", "blue",
2400 		[ Value(3), Value("Lee") ],
2401 		[ new Attribute("isThree", Value(true)) ],
2402 		null
2403 	);
2404 	auto blue5 = new Tag(
2405 		null, "", "blue",
2406 		[ Value(5), Value("Chan"), Value(12345) ],
2407 		[ new Attribute("isThree", Value(false)) ],
2408 		null
2409 	);
2410 	auto orange = new Tag(
2411 		null, "stuff", "orange",
2412 		[ Value(1), Value(2), Value(3), Value(2), Value(1) ],
2413 		null,
2414 		null
2415 	);
2416 	auto square = new Tag(
2417 		null, "stuff", "square",
2418 		null,
2419 		[
2420 			new Attribute("points", Value(4)),
2421 			new Attribute("dimensions", Value(2)),
2422 			new Attribute("points", Value("Still four")),
2423 		],
2424 		null
2425 	);
2426 	auto triangle = new Tag(
2427 		null, "stuff", "triangle",
2428 		null,
2429 		[
2430 			new Attribute("data", "points", Value(3)),
2431 			new Attribute("data", "dimensions", Value(2)),
2432 		],
2433 		null
2434 	);
2435 	auto nothing = new Tag(
2436 		null, "", "nothing",
2437 		null, null, null
2438 	);
2439 	auto namespaces = new Tag(
2440 		null, "", "namespaces",
2441 		null,
2442 		[
2443 			new Attribute("small", "A", Value(1)),
2444 			new Attribute("med",   "A", Value(2)),
2445 			new Attribute("big",   "A", Value(3)),
2446 			new Attribute("small", "B", Value(10)),
2447 			new Attribute("big",   "B", Value(30)),
2448 		],
2449 		null
2450 	);
2451 	auto chiyo = new Tag(
2452 		null, "", "chiyo",
2453 		[ Value("Small"), Value("Flies?") ],
2454 		[
2455 			new Attribute("nemesis", Value("Car")),
2456 			new Attribute("score", Value(100)),
2457 		],
2458 		null
2459 	);
2460 	auto chiyo_ = new Tag(
2461 		null, "", "chiyo_",
2462 		[ Value("Small"), Value("Flies?") ],
2463 		[
2464 			new Attribute("nemesis", Value("Car")),
2465 			new Attribute("score", Value(100)),
2466 		],
2467 		null
2468 	);
2469 	auto yukari = new Tag(
2470 		null, "", "yukari",
2471 		null, null, null
2472 	);
2473 	auto sana = new Tag(
2474 		null, "visitor", "sana",
2475 		null, null, null
2476 	);
2477 	auto sana_ = new Tag(
2478 		null, "visitor", "sana_",
2479 		null, null, null
2480 	);
2481 	auto sanaVisitor_ = new Tag(
2482 		null, "visitor_", "sana_",
2483 		null, null, null
2484 	);
2485 	auto tomo = new Tag(
2486 		null, "", "tomo",
2487 		null, null, null
2488 	);
2489 	auto hayama = new Tag(
2490 		null, "visitor", "hayama",
2491 		null, null, null
2492 	);
2493 	auto people = new Tag(
2494 		null, "", "people",
2495 		null,
2496 		[
2497 			new Attribute("visitor", "a", Value(1)),
2498 			new Attribute("b", Value(2)),
2499 		],
2500 		[chiyo, yukari, sana, tomo, hayama]
2501 	);
2502 
2503 	assert(blue3      .opEquals( blue3      ));
2504 	assert(blue5      .opEquals( blue5      ));
2505 	assert(orange     .opEquals( orange     ));
2506 	assert(square     .opEquals( square     ));
2507 	assert(triangle   .opEquals( triangle   ));
2508 	assert(nothing    .opEquals( nothing    ));
2509 	assert(namespaces .opEquals( namespaces ));
2510 	assert(people     .opEquals( people     ));
2511 	assert(chiyo      .opEquals( chiyo      ));
2512 	assert(yukari     .opEquals( yukari     ));
2513 	assert(sana       .opEquals( sana       ));
2514 	assert(tomo       .opEquals( tomo       ));
2515 	assert(hayama     .opEquals( hayama     ));
2516 
2517 	assert(!blue3.opEquals(orange));
2518 	assert(!blue3.opEquals(people));
2519 	assert(!blue3.opEquals(sana));
2520 	assert(!blue3.opEquals(blue5));
2521 	assert(!blue5.opEquals(blue3));
2522 
2523 	alias Tag.NamespaceAccess NSA;
2524 	static bool namespaceEquals(NSA n1, NSA n2)
2525 	{
2526 		return n1.name == n2.name;
2527 	}
2528 
2529 	testRandomAccessRange(root.attributes, cast(Attribute[])[]);
2530 	testRandomAccessRange(root.tags,       [blue3, blue5, nothing, namespaces, people]);
2531 	testRandomAccessRange(root.namespaces, [NSA(""), NSA("stuff")], &namespaceEquals);
2532 	testRandomAccessRange(root.namespaces[0].tags, [blue3, blue5, nothing, namespaces, people]);
2533 	testRandomAccessRange(root.namespaces[1].tags, [orange, square, triangle]);
2534 	assert(""        in root.namespaces);
2535 	assert("stuff"   in root.namespaces);
2536 	assert("foobar" !in root.namespaces);
2537 	testRandomAccessRange(root.namespaces[     ""].tags, [blue3, blue5, nothing, namespaces, people]);
2538 	testRandomAccessRange(root.namespaces["stuff"].tags, [orange, square, triangle]);
2539 	testRandomAccessRange(root.all.attributes, cast(Attribute[])[]);
2540 	testRandomAccessRange(root.all.tags,       [blue3, blue5, orange, square, triangle, nothing, namespaces, people]);
2541 	testRandomAccessRange(root.all.tags[],     [blue3, blue5, orange, square, triangle, nothing, namespaces, people]);
2542 	testRandomAccessRange(root.all.tags[3..6], [square, triangle, nothing]);
2543 	assert("blue"    in root.tags);
2544 	assert("nothing" in root.tags);
2545 	assert("people"  in root.tags);
2546 	assert("orange" !in root.tags);
2547 	assert("square" !in root.tags);
2548 	assert("foobar" !in root.tags);
2549 	assert("blue"    in root.all.tags);
2550 	assert("nothing" in root.all.tags);
2551 	assert("people"  in root.all.tags);
2552 	assert("orange"  in root.all.tags);
2553 	assert("square"  in root.all.tags);
2554 	assert("foobar" !in root.all.tags);
2555 	assert("orange"  in root.namespaces["stuff"].tags);
2556 	assert("square"  in root.namespaces["stuff"].tags);
2557 	assert("square"  in root.namespaces["stuff"].tags);
2558 	assert("foobar" !in root.attributes);
2559 	assert("foobar" !in root.all.attributes);
2560 	assert("foobar" !in root.namespaces["stuff"].attributes);
2561 	assert("blue"   !in root.attributes);
2562 	assert("blue"   !in root.all.attributes);
2563 	assert("blue"   !in root.namespaces["stuff"].attributes);
2564 	testRandomAccessRange(root.tags["nothing"],                    [nothing]);
2565 	testRandomAccessRange(root.tags["blue"],                       [blue3, blue5]);
2566 	testRandomAccessRange(root.namespaces["stuff"].tags["orange"], [orange]);
2567 	testRandomAccessRange(root.all.tags["nothing"],                [nothing]);
2568 	testRandomAccessRange(root.all.tags["blue"],                   [blue3, blue5]);
2569 	testRandomAccessRange(root.all.tags["orange"],                 [orange]);
2570 
2571 	assertThrown!DOMRangeException(root.tags["foobar"]);
2572 	assertThrown!DOMRangeException(root.all.tags["foobar"]);
2573 	assertThrown!DOMRangeException(root.attributes["foobar"]);
2574 	assertThrown!DOMRangeException(root.all.attributes["foobar"]);
2575 
2576 	// DMD Issue #12585 causes a segfault in these two tests when using 2.064 or 2.065,
2577 	// so work around it.
2578 	//assertThrown!DOMRangeException(root.namespaces["foobar"].tags["foobar"]);
2579 	//assertThrown!DOMRangeException(root.namespaces["foobar"].attributes["foobar"]);
2580 	bool didCatch = false;
2581 	try
2582 		auto x = root.namespaces["foobar"].tags["foobar"];
2583 	catch(DOMRangeException e)
2584 		didCatch = true;
2585 	assert(didCatch);
2586 
2587 	didCatch = false;
2588 	try
2589 		auto x = root.namespaces["foobar"].attributes["foobar"];
2590 	catch(DOMRangeException e)
2591 		didCatch = true;
2592 	assert(didCatch);
2593 
2594 	testRandomAccessRange(root.maybe.tags["nothing"],                    [nothing]);
2595 	testRandomAccessRange(root.maybe.tags["blue"],                       [blue3, blue5]);
2596 	testRandomAccessRange(root.maybe.namespaces["stuff"].tags["orange"], [orange]);
2597 	testRandomAccessRange(root.maybe.all.tags["nothing"],                [nothing]);
2598 	testRandomAccessRange(root.maybe.all.tags["blue"],                   [blue3, blue5]);
2599 	testRandomAccessRange(root.maybe.all.tags["blue"][],                 [blue3, blue5]);
2600 	testRandomAccessRange(root.maybe.all.tags["blue"][0..1],             [blue3]);
2601 	testRandomAccessRange(root.maybe.all.tags["blue"][1..2],             [blue5]);
2602 	testRandomAccessRange(root.maybe.all.tags["orange"],                 [orange]);
2603 	testRandomAccessRange(root.maybe.tags["foobar"],                      cast(Tag[])[]);
2604 	testRandomAccessRange(root.maybe.all.tags["foobar"],                  cast(Tag[])[]);
2605 	testRandomAccessRange(root.maybe.namespaces["foobar"].tags["foobar"], cast(Tag[])[]);
2606 	testRandomAccessRange(root.maybe.attributes["foobar"],                      cast(Attribute[])[]);
2607 	testRandomAccessRange(root.maybe.all.attributes["foobar"],                  cast(Attribute[])[]);
2608 	testRandomAccessRange(root.maybe.namespaces["foobar"].attributes["foobar"], cast(Attribute[])[]);
2609 
2610 	testRandomAccessRange(blue3.attributes,     [ new Attribute("isThree", Value(true)) ]);
2611 	testRandomAccessRange(blue3.tags,           cast(Tag[])[]);
2612 	testRandomAccessRange(blue3.namespaces,     [NSA("")], &namespaceEquals);
2613 	testRandomAccessRange(blue3.all.attributes, [ new Attribute("isThree", Value(true)) ]);
2614 	testRandomAccessRange(blue3.all.tags,       cast(Tag[])[]);
2615 
2616 	testRandomAccessRange(blue5.attributes,     [ new Attribute("isThree", Value(false)) ]);
2617 	testRandomAccessRange(blue5.tags,           cast(Tag[])[]);
2618 	testRandomAccessRange(blue5.namespaces,     [NSA("")], &namespaceEquals);
2619 	testRandomAccessRange(blue5.all.attributes, [ new Attribute("isThree", Value(false)) ]);
2620 	testRandomAccessRange(blue5.all.tags,       cast(Tag[])[]);
2621 
2622 	testRandomAccessRange(orange.attributes,     cast(Attribute[])[]);
2623 	testRandomAccessRange(orange.tags,           cast(Tag[])[]);
2624 	testRandomAccessRange(orange.namespaces,     cast(NSA[])[], &namespaceEquals);
2625 	testRandomAccessRange(orange.all.attributes, cast(Attribute[])[]);
2626 	testRandomAccessRange(orange.all.tags,       cast(Tag[])[]);
2627 
2628 	testRandomAccessRange(square.attributes, [
2629 		new Attribute("points", Value(4)),
2630 		new Attribute("dimensions", Value(2)),
2631 		new Attribute("points", Value("Still four")),
2632 	]);
2633 	testRandomAccessRange(square.tags,       cast(Tag[])[]);
2634 	testRandomAccessRange(square.namespaces, [NSA("")], &namespaceEquals);
2635 	testRandomAccessRange(square.all.attributes, [
2636 		new Attribute("points", Value(4)),
2637 		new Attribute("dimensions", Value(2)),
2638 		new Attribute("points", Value("Still four")),
2639 	]);
2640 	testRandomAccessRange(square.all.tags, cast(Tag[])[]);
2641 
2642 	testRandomAccessRange(triangle.attributes, cast(Attribute[])[]);
2643 	testRandomAccessRange(triangle.tags,       cast(Tag[])[]);
2644 	testRandomAccessRange(triangle.namespaces, [NSA("data")], &namespaceEquals);
2645 	testRandomAccessRange(triangle.namespaces[0].attributes, [
2646 		new Attribute("data", "points", Value(3)),
2647 		new Attribute("data", "dimensions", Value(2)),
2648 	]);
2649 	assert("data"    in triangle.namespaces);
2650 	assert("foobar" !in triangle.namespaces);
2651 	testRandomAccessRange(triangle.namespaces["data"].attributes, [
2652 		new Attribute("data", "points", Value(3)),
2653 		new Attribute("data", "dimensions", Value(2)),
2654 	]);
2655 	testRandomAccessRange(triangle.all.attributes, [
2656 		new Attribute("data", "points", Value(3)),
2657 		new Attribute("data", "dimensions", Value(2)),
2658 	]);
2659 	testRandomAccessRange(triangle.all.tags, cast(Tag[])[]);
2660 
2661 	testRandomAccessRange(nothing.attributes,     cast(Attribute[])[]);
2662 	testRandomAccessRange(nothing.tags,           cast(Tag[])[]);
2663 	testRandomAccessRange(nothing.namespaces,     cast(NSA[])[], &namespaceEquals);
2664 	testRandomAccessRange(nothing.all.attributes, cast(Attribute[])[]);
2665 	testRandomAccessRange(nothing.all.tags,       cast(Tag[])[]);
2666 
2667 	testRandomAccessRange(namespaces.attributes,   cast(Attribute[])[]);
2668 	testRandomAccessRange(namespaces.tags,         cast(Tag[])[]);
2669 	testRandomAccessRange(namespaces.namespaces,   [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals);
2670 	testRandomAccessRange(namespaces.namespaces[], [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals);
2671 	testRandomAccessRange(namespaces.namespaces[1..2], [NSA("med")], &namespaceEquals);
2672 	testRandomAccessRange(namespaces.namespaces[0].attributes, [
2673 		new Attribute("small", "A", Value(1)),
2674 		new Attribute("small", "B", Value(10)),
2675 	]);
2676 	testRandomAccessRange(namespaces.namespaces[1].attributes, [
2677 		new Attribute("med", "A", Value(2)),
2678 	]);
2679 	testRandomAccessRange(namespaces.namespaces[2].attributes, [
2680 		new Attribute("big", "A", Value(3)),
2681 		new Attribute("big", "B", Value(30)),
2682 	]);
2683 	testRandomAccessRange(namespaces.namespaces[1..2][0].attributes, [
2684 		new Attribute("med", "A", Value(2)),
2685 	]);
2686 	assert("small"   in namespaces.namespaces);
2687 	assert("med"     in namespaces.namespaces);
2688 	assert("big"     in namespaces.namespaces);
2689 	assert("foobar" !in namespaces.namespaces);
2690 	assert("small"  !in namespaces.namespaces[1..2]);
2691 	assert("med"     in namespaces.namespaces[1..2]);
2692 	assert("big"    !in namespaces.namespaces[1..2]);
2693 	assert("foobar" !in namespaces.namespaces[1..2]);
2694 	testRandomAccessRange(namespaces.namespaces["small"].attributes, [
2695 		new Attribute("small", "A", Value(1)),
2696 		new Attribute("small", "B", Value(10)),
2697 	]);
2698 	testRandomAccessRange(namespaces.namespaces["med"].attributes, [
2699 		new Attribute("med", "A", Value(2)),
2700 	]);
2701 	testRandomAccessRange(namespaces.namespaces["big"].attributes, [
2702 		new Attribute("big", "A", Value(3)),
2703 		new Attribute("big", "B", Value(30)),
2704 	]);
2705 	testRandomAccessRange(namespaces.all.attributes, [
2706 		new Attribute("small", "A", Value(1)),
2707 		new Attribute("med",   "A", Value(2)),
2708 		new Attribute("big",   "A", Value(3)),
2709 		new Attribute("small", "B", Value(10)),
2710 		new Attribute("big",   "B", Value(30)),
2711 	]);
2712 	testRandomAccessRange(namespaces.all.attributes[], [
2713 		new Attribute("small", "A", Value(1)),
2714 		new Attribute("med",   "A", Value(2)),
2715 		new Attribute("big",   "A", Value(3)),
2716 		new Attribute("small", "B", Value(10)),
2717 		new Attribute("big",   "B", Value(30)),
2718 	]);
2719 	testRandomAccessRange(namespaces.all.attributes[2..4], [
2720 		new Attribute("big",   "A", Value(3)),
2721 		new Attribute("small", "B", Value(10)),
2722 	]);
2723 	testRandomAccessRange(namespaces.all.tags, cast(Tag[])[]);
2724 	assert("A"      !in namespaces.attributes);
2725 	assert("B"      !in namespaces.attributes);
2726 	assert("foobar" !in namespaces.attributes);
2727 	assert("A"       in namespaces.all.attributes);
2728 	assert("B"       in namespaces.all.attributes);
2729 	assert("foobar" !in namespaces.all.attributes);
2730 	assert("A"       in namespaces.namespaces["small"].attributes);
2731 	assert("B"       in namespaces.namespaces["small"].attributes);
2732 	assert("foobar" !in namespaces.namespaces["small"].attributes);
2733 	assert("A"       in namespaces.namespaces["med"].attributes);
2734 	assert("B"      !in namespaces.namespaces["med"].attributes);
2735 	assert("foobar" !in namespaces.namespaces["med"].attributes);
2736 	assert("A"       in namespaces.namespaces["big"].attributes);
2737 	assert("B"       in namespaces.namespaces["big"].attributes);
2738 	assert("foobar" !in namespaces.namespaces["big"].attributes);
2739 	assert("foobar" !in namespaces.tags);
2740 	assert("foobar" !in namespaces.all.tags);
2741 	assert("foobar" !in namespaces.namespaces["small"].tags);
2742 	assert("A"      !in namespaces.tags);
2743 	assert("A"      !in namespaces.all.tags);
2744 	assert("A"      !in namespaces.namespaces["small"].tags);
2745 	testRandomAccessRange(namespaces.namespaces["small"].attributes["A"], [
2746 		new Attribute("small", "A", Value(1)),
2747 	]);
2748 	testRandomAccessRange(namespaces.namespaces["med"].attributes["A"], [
2749 		new Attribute("med", "A", Value(2)),
2750 	]);
2751 	testRandomAccessRange(namespaces.namespaces["big"].attributes["A"], [
2752 		new Attribute("big", "A", Value(3)),
2753 	]);
2754 	testRandomAccessRange(namespaces.all.attributes["A"], [
2755 		new Attribute("small", "A", Value(1)),
2756 		new Attribute("med",   "A", Value(2)),
2757 		new Attribute("big",   "A", Value(3)),
2758 	]);
2759 	testRandomAccessRange(namespaces.all.attributes["B"], [
2760 		new Attribute("small", "B", Value(10)),
2761 		new Attribute("big",   "B", Value(30)),
2762 	]);
2763 
2764 	testRandomAccessRange(chiyo.attributes, [
2765 		new Attribute("nemesis", Value("Car")),
2766 		new Attribute("score", Value(100)),
2767 	]);
2768 	testRandomAccessRange(chiyo.tags,       cast(Tag[])[]);
2769 	testRandomAccessRange(chiyo.namespaces, [NSA("")], &namespaceEquals);
2770 	testRandomAccessRange(chiyo.all.attributes, [
2771 		new Attribute("nemesis", Value("Car")),
2772 		new Attribute("score", Value(100)),
2773 	]);
2774 	testRandomAccessRange(chiyo.all.tags, cast(Tag[])[]);
2775 
2776 	testRandomAccessRange(yukari.attributes,     cast(Attribute[])[]);
2777 	testRandomAccessRange(yukari.tags,           cast(Tag[])[]);
2778 	testRandomAccessRange(yukari.namespaces,     cast(NSA[])[], &namespaceEquals);
2779 	testRandomAccessRange(yukari.all.attributes, cast(Attribute[])[]);
2780 	testRandomAccessRange(yukari.all.tags,       cast(Tag[])[]);
2781 
2782 	testRandomAccessRange(sana.attributes,     cast(Attribute[])[]);
2783 	testRandomAccessRange(sana.tags,           cast(Tag[])[]);
2784 	testRandomAccessRange(sana.namespaces,     cast(NSA[])[], &namespaceEquals);
2785 	testRandomAccessRange(sana.all.attributes, cast(Attribute[])[]);
2786 	testRandomAccessRange(sana.all.tags,       cast(Tag[])[]);
2787 
2788 	testRandomAccessRange(people.attributes,         [new Attribute("b", Value(2))]);
2789 	testRandomAccessRange(people.tags,               [chiyo, yukari, tomo]);
2790 	testRandomAccessRange(people.namespaces,         [NSA("visitor"), NSA("")], &namespaceEquals);
2791 	testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("visitor", "a", Value(1))]);
2792 	testRandomAccessRange(people.namespaces[1].attributes, [new Attribute("b", Value(2))]);
2793 	testRandomAccessRange(people.namespaces[0].tags,       [sana, hayama]);
2794 	testRandomAccessRange(people.namespaces[1].tags,       [chiyo, yukari, tomo]);
2795 	assert("visitor" in people.namespaces);
2796 	assert(""        in people.namespaces);
2797 	assert("foobar" !in people.namespaces);
2798 	testRandomAccessRange(people.namespaces["visitor"].attributes, [new Attribute("visitor", "a", Value(1))]);
2799 	testRandomAccessRange(people.namespaces[       ""].attributes, [new Attribute("b", Value(2))]);
2800 	testRandomAccessRange(people.namespaces["visitor"].tags,       [sana, hayama]);
2801 	testRandomAccessRange(people.namespaces[       ""].tags,       [chiyo, yukari, tomo]);
2802 	testRandomAccessRange(people.all.attributes, [
2803 		new Attribute("visitor", "a", Value(1)),
2804 		new Attribute("b", Value(2)),
2805 	]);
2806 	testRandomAccessRange(people.all.tags, [chiyo, yukari, sana, tomo, hayama]);
2807 
2808 	people.attributes["b"][0].name = "b_";
2809 	people.namespaces["visitor"].attributes["a"][0].name = "a_";
2810 	people.tags["chiyo"][0].name = "chiyo_";
2811 	people.namespaces["visitor"].tags["sana"][0].name = "sana_";
2812 
2813 	assert("b_"     in people.attributes);
2814 	assert("a_"     in people.namespaces["visitor"].attributes);
2815 	assert("chiyo_" in people.tags);
2816 	assert("sana_"  in people.namespaces["visitor"].tags);
2817 
2818 	assert(people.attributes["b_"][0]                       == new Attribute("b_", Value(2)));
2819 	assert(people.namespaces["visitor"].attributes["a_"][0] == new Attribute("visitor", "a_", Value(1)));
2820 	assert(people.tags["chiyo_"][0]                         == chiyo_);
2821 	assert(people.namespaces["visitor"].tags["sana_"][0]    == sana_);
2822 
2823 	assert("b"     !in people.attributes);
2824 	assert("a"     !in people.namespaces["visitor"].attributes);
2825 	assert("chiyo" !in people.tags);
2826 	assert("sana"  !in people.namespaces["visitor"].tags);
2827 
2828 	assert(people.maybe.attributes["b"].length                       == 0);
2829 	assert(people.maybe.namespaces["visitor"].attributes["a"].length == 0);
2830 	assert(people.maybe.tags["chiyo"].length                         == 0);
2831 	assert(people.maybe.namespaces["visitor"].tags["sana"].length    == 0);
2832 
2833 	people.tags["tomo"][0].remove();
2834 	people.namespaces["visitor"].tags["hayama"][0].remove();
2835 	people.tags["chiyo_"][0].remove();
2836 	testRandomAccessRange(people.tags,               [yukari]);
2837 	testRandomAccessRange(people.namespaces,         [NSA("visitor"), NSA("")], &namespaceEquals);
2838 	testRandomAccessRange(people.namespaces[0].tags, [sana_]);
2839 	testRandomAccessRange(people.namespaces[1].tags, [yukari]);
2840 	assert("visitor" in people.namespaces);
2841 	assert(""        in people.namespaces);
2842 	assert("foobar" !in people.namespaces);
2843 	testRandomAccessRange(people.namespaces["visitor"].tags, [sana_]);
2844 	testRandomAccessRange(people.namespaces[       ""].tags, [yukari]);
2845 	testRandomAccessRange(people.all.tags, [yukari, sana_]);
2846 
2847 	people.attributes["b_"][0].namespace = "_";
2848 	people.namespaces["visitor"].attributes["a_"][0].namespace = "visitor_";
2849 	assert("_"         in people.namespaces);
2850 	assert("visitor_"  in people.namespaces);
2851 	assert(""          in people.namespaces);
2852 	assert("visitor"   in people.namespaces);
2853 	people.namespaces["visitor"].tags["sana_"][0].namespace = "visitor_";
2854 	assert("_"         in people.namespaces);
2855 	assert("visitor_"  in people.namespaces);
2856 	assert(""          in people.namespaces);
2857 	assert("visitor"  !in people.namespaces);
2858 
2859 	assert(people.namespaces["_"       ].attributes["b_"][0] == new Attribute("_", "b_", Value(2)));
2860 	assert(people.namespaces["visitor_"].attributes["a_"][0] == new Attribute("visitor_", "a_", Value(1)));
2861 	assert(people.namespaces["visitor_"].tags["sana_"][0]    == sanaVisitor_);
2862 
2863 	people.tags["yukari"][0].remove();
2864 	people.namespaces["visitor_"].tags["sana_"][0].remove();
2865 	people.namespaces["visitor_"].attributes["a_"][0].namespace = "visitor";
2866 	people.namespaces["_"].attributes["b_"][0].namespace = "";
2867 	testRandomAccessRange(people.tags,               cast(Tag[])[]);
2868 	testRandomAccessRange(people.namespaces,         [NSA("visitor"), NSA("")], &namespaceEquals);
2869 	testRandomAccessRange(people.namespaces[0].tags, cast(Tag[])[]);
2870 	testRandomAccessRange(people.namespaces[1].tags, cast(Tag[])[]);
2871 	assert("visitor" in people.namespaces);
2872 	assert(""        in people.namespaces);
2873 	assert("foobar" !in people.namespaces);
2874 	testRandomAccessRange(people.namespaces["visitor"].tags, cast(Tag[])[]);
2875 	testRandomAccessRange(people.namespaces[       ""].tags, cast(Tag[])[]);
2876 	testRandomAccessRange(people.all.tags, cast(Tag[])[]);
2877 
2878 	people.namespaces["visitor"].attributes["a_"][0].remove();
2879 	testRandomAccessRange(people.attributes,               [new Attribute("b_", Value(2))]);
2880 	testRandomAccessRange(people.namespaces,               [NSA("")], &namespaceEquals);
2881 	testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("b_", Value(2))]);
2882 	assert("visitor" !in people.namespaces);
2883 	assert(""         in people.namespaces);
2884 	assert("foobar"  !in people.namespaces);
2885 	testRandomAccessRange(people.namespaces[""].attributes, [new Attribute("b_", Value(2))]);
2886 	testRandomAccessRange(people.all.attributes, [
2887 		new Attribute("b_", Value(2)),
2888 	]);
2889 
2890 	people.attributes["b_"][0].remove();
2891 	testRandomAccessRange(people.attributes, cast(Attribute[])[]);
2892 	testRandomAccessRange(people.namespaces, cast(NSA[])[], &namespaceEquals);
2893 	assert("visitor" !in people.namespaces);
2894 	assert(""        !in people.namespaces);
2895 	assert("foobar"  !in people.namespaces);
2896 	testRandomAccessRange(people.all.attributes, cast(Attribute[])[]);
2897 
2898 	// Test clone()
2899 	auto rootClone = root.clone();
2900 	assert(rootClone !is root);
2901 	assert(rootClone.parent is null);
2902 	assert(rootClone.name      == root.name);
2903 	assert(rootClone.namespace == root.namespace);
2904 	assert(rootClone.location  == root.location);
2905 	assert(rootClone.values    == root.values);
2906 	assert(rootClone.toSDLDocument() == root.toSDLDocument());
2907 
2908 	auto peopleClone = people.clone();
2909 	assert(peopleClone !is people);
2910 	assert(peopleClone.parent is null);
2911 	assert(peopleClone.name      == people.name);
2912 	assert(peopleClone.namespace == people.namespace);
2913 	assert(peopleClone.location  == people.location);
2914 	assert(peopleClone.values    == people.values);
2915 	assert(peopleClone.toSDLString() == people.toSDLString());
2916 }
2917 
2918 // Regression test, issue #11: https://github.com/Abscissa/SDLang-D/issues/11
2919 @("*: Regression test issue #11")
2920 unittest
2921 {
2922 	import gfx.decl.sdlang.parser;
2923 
2924 	auto root = parseSource(
2925 `//
2926 a`);
2927 
2928 	assert("a" in root.tags);
2929 
2930 	root = parseSource(
2931 `//
2932 parent {
2933 	child
2934 }
2935 `);
2936 
2937 	auto child = new Tag(
2938 		null, "", "child",
2939 		null, null, null
2940 	);
2941 
2942 	assert("parent" in root.tags);
2943 	assert("child" !in root.tags);
2944 	testRandomAccessRange(root.tags["parent"][0].tags, [child]);
2945 	assert("child" in root.tags["parent"][0].tags);
2946 }