1 module dud.pkgdescription.sdl;
2 
3 import std.algorithm.iteration : map, each, filter, uniq, splitter, joiner;
4 import std.algorithm.searching : any, canFind;
5 import std.algorithm.sorting : sort;
6 import std.array : array, back, empty, front, appender, popFront;
7 import std.conv : to;
8 import std.exception : enforce;
9 import std.format : format, formattedWrite;
10 import std.range : tee;
11 import std.stdio;
12 import std.traits : FieldNameTuple;
13 import std.typecons : nullable, Nullable, tuple;
14 
15 import dud.pkgdescription.exception;
16 import dud.pkgdescription.outpututils;
17 import dud.pkgdescription.udas;
18 import dud.pkgdescription;
19 import dud.semver.semver : SemVer;
20 import dud.semver.parse : parseSemVer;
21 import dud.semver.versionrange;
22 
23 import dud.sdlang;
24 
25 @safe:
26 
27 //
28 // PackageDescription
29 //
30 
31 PackageDescription sdlToPackageDescription(string sdl) @safe {
32 	auto lex = Lexer(sdl);
33 	auto parser = Parser(lex);
34 	Root jv = parser.parseRoot();
35 	PackageDescription ret;
36 	sGetPackageDescription(tags(jv), "dub.sdl", ret);
37 	return ret;
38 }
39 
40 void sGetPackageDescription(TagAccessor ts, string key,
41 		ref PackageDescription ret) @safe
42 {
43 	foreach(Tag t; ts) {
44 		string id = t.fullIdentifier();
45 		sw: switch(id) {
46 			static foreach(mem; FieldNameTuple!PackageDescription) {{
47 				enum Mem = SDLName!mem;
48 				alias get = SDLGet!mem;
49 				case Mem:
50 					get(t, Mem, __traits(getMember, ret, mem));
51 					break sw;
52 			}}
53 			default:
54 				throw new KeyNotHandled(
55 					format("The sdl dud format does not know a key '%s'.", id)
56 				);
57 		}
58 	}
59 }
60 
61 void packageDescriptionsToS(Out)(auto ref Out o, const string key,
62 		const PackageDescription[string] pkgs, const size_t indent)
63 {
64 	pkgs.byKeyValue()
65 		.each!(it =>
66 			packageDescriptionToS(o, it.value.name, it.value, indent + 1)
67 		);
68 }
69 
70 void packageDescriptionToS(Out)(auto ref Out o, const string key,
71 		const PackageDescription pkg, const size_t indent)
72 {
73 	static foreach(mem; FieldNameTuple!PackageDescription) {{
74 		enum Mem = SDLName!mem;
75 		alias put = SDLPut!mem;
76 		alias MemType = typeof(__traits(getMember, PackageDescription, mem));
77 		static if(is(MemType : Nullable!Args, Args...)) {
78 			if(!__traits(getMember, pkg, mem).isNull) {
79 				put(o, Mem, __traits(getMember, pkg, mem).get(), indent);
80 			}
81 		} else {
82 			put(o, Mem, __traits(getMember, pkg, mem), indent);
83 		}
84 	}}
85 }
86 
87 void configurationsToS(Out)(auto ref Out o, const string key,
88 		const PackageDescription[string] pkgs, const size_t indent)
89 {
90 	pkgs.byKeyValue()
91 		.each!(pkg => configurationToS(o, key, pkg.value, indent));
92 }
93 
94 void configurationToS(Out)(auto ref Out o, const string key,
95 		const PackageDescription pkg, const size_t indent)
96 {
97 	formattedWrite(o, "configuration \"%s\" {\n", pkg.name);
98 	packageDescriptionToS(o, pkg.name, pkg, indent + 1);
99 	formattedWrite(o, "}\n");
100 }
101 
102 //
103 // Platform
104 //
105 
106 void sGetPlatform(AttributeAccessor aa, ref Platform[] pls) {
107 	Platform[] tmp = aa
108 		.filter!(a => a.identifier() == "platform")
109 		.tee!(a => typeCheck(a.value, [ ValueType.str ]))
110 		.map!(a => a.value.value.get!string())
111 		.map!(s => s.splitter("-"))
112 		.joiner
113 		.map!(s => to!Platform(s))
114 		.array
115 		~ pls;
116 
117 	pls = tmp.sort.uniq.array;
118 }
119 
120 void sGetPlatforms(Tag t, string key, ref Platform[][] ret) {
121 	auto vals = t.values();
122 	enforce!EmptyInput(!vals.empty,
123 		"The platforms must not by empty");
124 	ret = vals
125 		.tee!(val => typeCheck(val, [ ValueType.str ]))
126 		.map!(val => val.value.get!string())
127 		.map!(s => s.splitter("-").map!(s => to!Platform(s)).array)
128 		.array
129 		.sort
130 		.uniq
131 		.array;
132 
133 	enforce!UnexpectedInput(t.attributes().empty,
134 		"No attributes expected for platforms key");
135 }
136 
137 void platformsToS(Out)(auto ref Out o, const string key,
138 		const Platform[][] plts, const size_t indent)
139 {
140 	if(!plts.empty) {
141 		formatIndent(o, indent, "platforms %-(\"%s\"%| %)\n",
142 			plts.map!(plt => plt.map!(it => to!string(it)).joiner("-")));
143 	}
144 }
145 
146 //
147 // Strings, StringsPlatform
148 //
149 
150 void sGetStringsPlatform(Tag t, string key, ref Strings ret) {
151 	StringsPlatform tmp;
152 	sGetStrings(t.values(), key, tmp.strs);
153 	sGetPlatform(t.attributes(), tmp.platforms);
154 	checkEmptyAttributes(t, key, [ "platform" ]);
155 	ret.platforms ~= tmp;
156 }
157 
158 void stringsPlatformToS(Out)(auto ref Out o, const string key,
159 		const Strings value, const size_t indent)
160 {
161 	value.platforms.each!(s =>
162 		formatIndent(o, indent,
163 			s.strs.any!containsEscapable
164 				? "%s %-(`%s`%| %) %(platform=%s %)\n"
165 				: "%s %(%s %) %(platform=%s %)\n",
166 			key,
167 			s.strs.map!(s => s),
168 			s.platforms.map!(p => to!string(p)))
169 	);
170 }
171 
172 //
173 // String, StringPlatform
174 //
175 
176 void sGetStringPlatform(Tag t, string key, ref String ret) {
177 	StringPlatform tmp;
178 	sGetString(t, key, tmp.str, [ "platform"] );
179 	sGetPlatform(t.attributes(), tmp.platforms);
180 	ret.platforms ~= tmp;
181 }
182 
183 void stringPlatformToS(Out)(auto ref Out o, const string key,
184 		const String value, const size_t indent)
185 {
186 	value.platforms.each!(s =>
187 		formatIndent(o, indent,
188 			s.str.containsEscapable
189 				? "%s `%s` %(platform=%s %)\n"
190 				: "%s \"%s\" %(platform=%s %)\n",
191 			key,
192 			s.str,
193 			s.platforms.map!(p => to!string(p)))
194 	);
195 }
196 
197 //
198 // string
199 //
200 
201 void sGetString(Tag t, string key, ref string ret,
202 		string[] allowedAttributes = string[].init)
203 {
204 	sGetString(t.values(), key, ret);
205 	checkEmptyAttributes(t, key, allowedAttributes);
206 }
207 
208 void sGetString(ValueRange v, string key, ref string ret) {
209 	Token f = expectedSingleValue(v, key);
210 	typeCheck(f, [ ValueType.str ]);
211 	ret = f.value.get!string();
212 }
213 
214 void stringToSName(Out)(auto ref Out o, const string key, const string value,
215 		const size_t indent)
216 {
217 	if(!value.empty) {
218 		formatIndent(o, indent, "%s \"%s\"\n", key, value);
219 	}
220 }
221 
222 void stringToS(Out)(auto ref Out o, const string key, const string value,
223 		const size_t indent)
224 {
225 	if(!value.empty) {
226 		if(value.containsEscapable()) {
227 			formatIndent(o, indent, "%s `%s`\n", key, value);
228 		} else {
229 			formatIndent(o, indent, "%s \"%s\"\n", key, value);
230 		}
231 	}
232 }
233 
234 //
235 // string[]
236 //
237 
238 void sGetStrings(Tag t, string key, ref string[] ret) {
239 	sGetStrings(t.values(), key, ret);
240 }
241 
242 void sGetStrings(ValueRange v, string key, ref string[] ret) {
243 	enforce!EmptyInput(!v.empty, format("Can not get elements of '%s'", key));
244 	v
245 		.tee!(it => typeCheck(it, [ ValueType.str ]))
246 		.each!(it => ret ~= it.value.get!string());
247 }
248 
249 void stringsToS(Out)(auto ref Out o, const string key, const string[] values,
250 		const size_t indent)
251 {
252 	if(!values.empty) {
253 		formatIndent(o, indent,
254 			values.any!containsEscapable
255 				? "%s %-(`%s`%| %)\n"
256 				: "%s %(%s %)\n",
257 			key,
258 			values.map!(s => s));
259 	}
260 }
261 
262 //
263 // SubPackage
264 //
265 
266 void sGetSubPackage(Tag t, string key, ref SubPackage[] ret) {
267 	ValueRange vr = t.values();
268 	SubPackage tmp;
269 	if(!vr.empty) {
270 		sGetPath(t, key, tmp.path);
271 	} else {
272 		PackageDescription iTmp;
273 		sGetPackageDescription(tags(t.oc), key, iTmp);
274 		tmp.inlinePkg = nullable(iTmp);
275 	}
276 	ret ~= tmp;
277 }
278 
279 void subPackagesToS(Out)(auto ref Out o, const string key,
280 		const SubPackage[] sps, const size_t indent)
281 {
282 	foreach(sp; sps) {
283 		if(!sp.path.platforms.empty) {
284 			sp.path.platforms.each!(p =>
285 				formatIndent(o, indent, "subPackage \"%s\" %(platform=%s %)\n",
286 					p.path.path, p.platforms.map!(it => to!string(it)))
287 			);
288 		} else if(!sp.inlinePkg.isNull()) {
289 			formatIndent(o, indent, "subPackage {\n");
290 			packageDescriptionToS(o, "SubPackage", sp.inlinePkg.get(),
291 					indent + 1);
292 			formatIndent(o, indent, "}\n");
293 		} else {
294 			assert(false, "SubPackage without a path or inlinePkg");
295 		}
296 	}
297 }
298 
299 //
300 // SemVer
301 //
302 
303 void sGetSemVer(Tag t, string key, ref SemVer ret) {
304 	sGetSemVer(t.values(), key, ret);
305 	checkEmptyAttributes(t, key, []);
306 }
307 
308 void sGetSemVer(ValueRange v, string key, ref SemVer ver) {
309 	string s;
310 	sGetString(v, "SemVer", s);
311 	ver = nullable(parseSemVer(s)).get();
312 }
313 
314 void semVerToS(Out)(auto ref Out o, const string key, const SemVer sv,
315 		const size_t indent)
316 {
317 	string s = sv.toString();
318 	if(!s.empty) {
319 		formatIndent(o, indent, "%s \"%s\"\n", key, s);
320 	}
321 }
322 
323 //
324 // BuildOption
325 //
326 
327 void sGetBuildOptions(Tag t, string key, ref BuildOptions ret) {
328 	string[] s;
329 	sGetStrings(t, key, s);
330 	enforce!EmptyInput(!s.empty, format("'%s' must not be empty", key));
331 	BuildOption[] bos = s.map!(it => to!BuildOption(it)).array;
332 
333 	Platform[] plts;
334 	sGetPlatform(t.attributes(), plts);
335 	if(!plts.empty) {
336 		immutable(Platform[]) iPlts = plts.idup;
337 		ret.platforms[iPlts] = bos;
338 	} else {
339 		ret.unspecifiedPlatform = bos;
340 	}
341 }
342 
343 void buildOptionsToS(Out)(auto ref Out o, const string key,
344 		const BuildOptions bos, const size_t indent)
345 {
346 	if(!bos.unspecifiedPlatform.empty) {
347 		formatIndent(o, indent, "%s %(%s %)\n", key,
348 				bos.unspecifiedPlatform.map!(bo => to!string(bo)));
349 	}
350 
351 	foreach(plt, bo; bos.platforms) {
352 		formatIndent(o, indent, "%s %(%s %) %(platform=%s %)\n", key,
353 				bo.map!(bo => to!string(bo)),
354 				plt.map!(p => to!string(p)));
355 	}
356 }
357 
358 //
359 // TargetType
360 //
361 
362 void sGetTargetType(Tag t, string key, ref TargetType ret) {
363 	sGetTargetType(t.values(), key, ret);
364 }
365 
366 void sGetTargetType(ValueRange v, string key, ref TargetType p) {
367 	string s;
368 	sGetString(v, "TargetType", s);
369 	p = to!TargetType(s);
370 }
371 
372 void targetTypeToS(Out)(auto ref Out o, const string key, const TargetType p,
373 		const size_t indent)
374 {
375 	if(p != TargetType.autodetect) {
376 		formatIndent(o, indent, "%s \"%s\"\n", key, p);
377 	}
378 }
379 
380 //
381 // BuildRequirements
382 //
383 
384 void sGetBuildRequirements(Tag t, string key, ref BuildRequirements ret) {
385 	Platform[] plts;
386 	sGetPlatform(t.attributes(), plts);
387 	BuildRequirement[] brs = t.values()
388 		.map!(it => it.value.get!string())
389 		.map!(s => to!BuildRequirement(s))
390 		.array;
391 
392 	ret.platforms ~= BuildRequirementPlatform(brs, plts);
393 }
394 
395 void buildRequirementsToS(Out)(auto ref Out o, const string key,
396 		const BuildRequirements ps, const size_t indent)
397 {
398 	if(!ps.platforms.empty) {
399 		ps.platforms.each!(p =>
400 			formatIndent(o, indent, "%s %(%s %) %(platform=%s %)\n", key,
401 				p.requirements.map!(it => to!string(it)),
402 				p.platforms.map!(it => to!string(it))));
403 	}
404 }
405 
406 void sGetSubConfig(Tag t, string key, ref SubConfigs ret) {
407 	string[] s;
408 	sGetStrings(t, key, s);
409 	if(s.length != 2) {
410 		throw new UnexpectedInput(format("Expected two strings not '%s'"),
411 			Location("", t.id.cur.line, t.id.cur.column));
412 	}
413 	Platform[] plts;
414 	sGetPlatform(t.attributes(), plts);
415 	immutable(Platform[]) iPlts = plts.idup;
416 
417 	if(iPlts.empty) {
418 		if(s[0] in ret.unspecifiedPlatform) {
419 			throw new ConflictingInput(
420 				format("Subconfig for '%s' already specified", s[0]),
421 				Location("", t.id.cur.line, t.id.cur.column));
422 		}
423 		ret.unspecifiedPlatform[s[0]] = s[1];
424 	} else {
425 		string[string] tmp;
426 		tmp[s[0]] = s[1];
427 
428 		if(iPlts in ret.configs && s[0] in ret.configs[iPlts]) {
429 			throw new ConflictingInput(
430 				format("Subconfig for '%s' already specified", s[0]),
431 				Location("", t.id.cur.line, t.id.cur.column));
432 		}
433 		ret.configs[iPlts] = tmp;
434 	}
435 }
436 
437 void subConfigsToS(Out)(auto ref Out o, const string key,
438 		const SubConfigs scf, const size_t indent)
439 {
440 	scf.unspecifiedPlatform.byKeyValue()
441 		.each!(sc =>
442 			formatIndent(o, indent, "subConfiguration \"%s\" \"%s\"\n",
443 				sc.key, sc.value)
444 		);
445 	scf.configs.byKeyValue()
446 		.each!(plt =>
447 				plt.value.byKeyValue().each!(sc =>
448 					formatIndent(o, indent,
449 						"subConfiguration \"%s\" \"%s\" %(platform=%s %)\n",
450 						sc.key, sc.value, plt.key.map!(it => to!string(it)))
451 				)
452 			);
453 
454 }
455 
456 //
457 // paths
458 //
459 
460 void sGetPaths(Tag t, string key, ref Paths ret) {
461 	auto v = t.values();
462 	enforce!EmptyInput(!v.empty, format("Can not get elements of '%s'", key));
463 	PathsPlatform pp;
464 	pp.paths = v
465 		.tee!(it => typeCheck(it, [ ValueType.str ]))
466 		.map!(it => it.value.get!string())
467 		.map!(s => UnprocessedPath(s))
468 		.array;
469 	sGetPlatform(t.attributes(), pp.platforms);
470 	ret.platforms ~= pp;
471 }
472 
473 void pathsToS(Out)(auto ref Out o, const string key, const Paths ps,
474 		const size_t indent)
475 {
476 	ps.platforms.each!(p =>
477 		formatIndent(o, indent, "%s %(%s %) %(platform=%s %)\n",
478 			key, p.paths.map!(it => it.path),
479 			p.platforms.map!(it => to!string(it)))
480 	);
481 }
482 
483 //
484 // path
485 //
486 
487 void sGetUnprocessedPath(Tag t, const string key, ref UnprocessedPath ret) {
488 	auto v = t.values();
489 	Token f = expectedSingleValue(v, key);
490 	typeCheck(f, [ ValueType.str ]);
491 	checkEmptyAttributes(t, key, []);
492 	string s = f.value.get!string();
493 	ret = UnprocessedPath(s);
494 }
495 
496 void unprocessedPathToS(Out)(auto ref Out o, const string key,
497 		const UnprocessedPath p, const size_t indent)
498 {
499 	stringToS(o, key, p.path, indent);
500 }
501 
502 void sGetPath(Tag t, const string key, ref Path ret) {
503 	auto v = t.values();
504 	Token f = expectedSingleValue(v, key);
505 	typeCheck(f, [ ValueType.str ]);
506 	string s = f.value.get!string();
507 	PathPlatform pp;
508 	pp.path = UnprocessedPath(s);
509 	sGetPlatform(t.attributes(), pp.platforms);
510 	ret.platforms ~= pp;
511 }
512 
513 void pathToS(Out)(auto ref Out o, const string key, const Path p,
514 		const size_t indent)
515 {
516 	p.platforms.each!(plt =>
517 		formatIndent(o, indent,
518 			(plt.path.path.containsEscapable()
519 				? "%s `%s` %(platform=%s %)\n"
520 				: "%s \"%s\" %(platform=%s %)\n"),
521 			key,
522 			plt.path.path, plt.platforms.map!(it => to!string(it)))
523 	);
524 }
525 
526 //
527 // ToolchainRequirement
528 //
529 
530 void toolchainRequirementToS(Out)(auto ref Out o, const string key,
531 		const ToolchainRequirement[Toolchain] tcrs, const size_t indent)
532 {
533 	if(!tcrs.empty) {
534 		formatIndent(o, indent,
535 			"toolchainRequirements %-(%s %)\n",
536 				tcrs.byKeyValue().map!(kv =>
537 					format("%s=\"%s\"", kv.key,
538 						kv.value.no ? "no" : kv.value.version_.toString()))
539 		);
540 	}
541 }
542 
543 ToolchainRequirement sGetToolchainRequirement(const ref Token f) {
544 	typeCheck(f, [ ValueType.str ]);
545 	const string s = f.value.get!string();
546 	return s == "no"
547 		? ToolchainRequirement(true, VersionRange.init)
548 		: ToolchainRequirement(false, parseVersionRange(s).get());
549 }
550 
551 private immutable string[] tc = ["dmd", "ldc", "gdc", "frontend", "dub", "dud"];
552 
553 void sGetToolchainRequirement(Tag t, string key,
554 		ref ToolchainRequirement[Toolchain] bts)
555 {
556 	checkEmptyAttributes(t.attributes(), key, tc);
557 	t.attributes()
558 		.tee!(attr => typeCheck(attr.value, [ ValueType.str ]))
559 		.tee!(attr => sdlEnforce!UnsupportedAttributes(
560 				!canFind(tc, attr.identifier()),
561 					format("'%s' is an unsupported Toolchain", attr.identifier()),
562 					Location("", attr.id.cur.line, attr.id.cur.column)))
563 		.map!(attr => tuple(to!Toolchain(attr.identifier()), attr))
564 		.tee!(tup => sdlEnforce!ConflictingInput(tup[0] !in bts,
565 				format("'%s' is already in the toolchain requirements", tup[0]),
566 				Location("", tup[1].id.cur.line, tup[1].id.cur.column)))
567 		.map!(tup => tuple(tup[0], sGetToolchainRequirement(tup[1].value)))
568 		.each!(tup => bts[tup[0]] = tup[1]);
569 }
570 
571 //
572 // BuildTypes
573 //
574 
575 void sGetBuildTypes(Tag t, string key, ref BuildType[string] bts) {
576 	string buildTypesName;
577 	sGetString(t, "name", buildTypesName);
578 
579 	PackageDescription pkgDesc;
580 	sGetPackageDescription(tags(t.oc), "buildTypes", pkgDesc);
581 
582 	BuildType bt;
583 	bt.name = buildTypesName;
584 	bt.pkg = pkgDesc;
585 
586 	bts[buildTypesName] = bt;
587 }
588 
589 void buildTypeToS(Out)(auto ref Out o, const string key, const BuildType bt,
590 		const size_t indent)
591 {
592 	formatIndent(o, indent, "buildType \"%s\" {\n", bt.name);
593 	packageDescriptionToS(o, "", bt.pkg, indent + 1);
594 	formatIndent(o, indent, "}\n");
595 }
596 
597 void buildTypesToS(Out)(auto ref Out o, const string key,
598 		const BuildType[string] bts, const size_t indent)
599 {
600 	bts.byValue().each!(bt => buildTypeToS(o, key, bt, indent));
601 }
602 
603 //
604 // PackageDescription
605 //
606 
607 void sGetPackageDescriptions(Tag t, string key,
608 		ref PackageDescription[string] ret) @safe
609 {
610 	string n;
611 	sGetString(t, "name", n);
612 	PackageDescription tmp;
613 	tmp.name = n;
614 	sGetPackageDescription(tags(t.oc), "configuration", tmp);
615 	if(tmp.name != n) {
616 		throw new UnexpectedInput(format(
617 			"Configuration names must not be changed in OptChild '%s' => '%s'",
618 			n, tmp.name),
619 			Location("", t.id.cur.line, t.id.cur.column)
620 		);
621 	}
622 	ret[tmp.name] = tmp;
623 }
624 
625 //
626 // Dependencies
627 //
628 
629 void sGetDependencies(Tag t, string key, ref Dependency[] ret) {
630 	sGetDependencies(t.values(), t.attributes(), key, ret);
631 }
632 
633 void sGetDependencies(ValueRange v, AttributeAccessor ars, string key,
634 		ref Dependency[] deps)
635 {
636 	enforce!EmptyInput(!v.empty,
637 		format("Can not get Dependencies of an empty range", key));
638 	string name;
639 	sGetString(v, key, name);
640 	Dependency ret;
641 	sGetPlatform(ars, ret.platforms);
642 	checkEmptyAttributes(ars, key,
643 		[ "version", "path", "optional", "default", "platform" ]);
644 	ret.name = name;
645 	foreach(Attribute it; ars) {
646 		switch(it.identifier()) {
647 			case "version":
648 				ret.version_ = it
649 					.value
650 					.value
651 					.get!string()
652 					.parseVersionRange;
653 				break;
654 			case "path":
655 				ret.path = UnprocessedPath(it.value.value.get!string());
656 				break;
657 			case "optional":
658 				ret.optional = it
659 					.value
660 					.value
661 					.get!bool()
662 					.nullable;
663 				break;
664 			case "default":
665 				ret.default_ = it
666 					.value
667 					.value
668 					.get!bool()
669 					.nullable;
670 				break;
671 			case "platform":
672 				sGetPlatform(ars, ret.platforms);
673 				break;
674 			default:
675 				throw new Exception(format(
676 					"Key '%s' is not part of a Dependency declaration",
677 					it.identifier()));
678 		}
679 	}
680 	deps ~= ret;
681 }
682 
683 void dependenciesToS(Out)(auto ref Out o, const string key,
684 		const Dependency[] deps, const size_t indent)
685 {
686 	foreach(value; deps) {
687 		formatIndent(o, indent, "dependency \"%s\"", value.name);
688 		if(!value.version_.isNull()) {
689 			formattedWrite(o, " version=\"%s\"", value.version_.get());
690 		}
691 		if(!value.path.path.empty) {
692 			formattedWrite(o, " path=\"%s\"", value.path.path);
693 		}
694 		if(!value.default_.isNull()) {
695 			formattedWrite(o, " default=%s", value.default_.get());
696 		}
697 		if(!value.optional.isNull()) {
698 			formattedWrite(o, " optional=%s", value.optional.get());
699 		}
700 		formattedWrite(o, " %(platform=%s %)",
701 			value.platforms.map!(p => to!string(p)));
702 		formattedWrite(o, "\n");
703 	}
704 }
705 
706 //
707 // Output Helper
708 //
709 
710 private void indent(Out)(auto ref Out o, const size_t indent) {
711 	foreach(i; 0 .. indent) {
712 		formattedWrite(o, "\t");
713 	}
714 }
715 
716 private void formatIndent(Out, Args...)(auto ref Out o, const size_t i,
717 		string str, Args args)
718 {
719 	indent(o, i);
720 	formattedWrite(o, str, args);
721 }
722 
723 //
724 // Helper
725 //
726 
727 void typeCheck(const Token got, const ValueType[] exp,
728 		string filename = __FILE__, size_t line = __LINE__)
729 {
730 	if(!canFind(exp, got.value.type)) {
731 		throw new WrongTypeSDL(
732 			exp.length == 1
733 				? format("Expected a Value of type '%s' but got '%s'",
734 					exp.front, got.value.type)
735 				: format("Expected a Value of types [%(%s, %)]' but got '%s'",
736 					exp, got.value.type),
737 				Location("", got.line, got.column), filename, line
738 		);
739 	}
740 }
741 
742 Token expectedSingleValue(ValueRange vr, const string key,
743 		string filename = __FILE__, size_t line = __LINE__)
744 {
745 	if(vr.empty) {
746 		throw new SingleElement(format(
747 			"ValueRange for key '%s' was incorrectly empty", key
748 			), filename, line);
749 	}
750 	Token ret = vr.front;
751 	vr.popFront();
752 	if(!vr.empty) {
753 		throw new SingleElement(format(
754 			"ValueRange for key '%s' incorrectly contains more than one element",
755 			key), Location("", vr.front.line, vr.front.column), filename, line);
756 	}
757 	return ret;
758 }
759 
760 void checkEmptyAttributes(Tag t, const string key, const string[] toIgnore) {
761 	checkEmptyAttributes(t.attributes(), key, toIgnore);
762 }
763 
764 void checkEmptyAttributes(AttributeAccessor ars, const string key,
765 		const string[] toIgnore)
766 {
767 	auto attrs = ars
768 		.map!(attr => attr.identifier())
769 		.filter!(s => !canFind(toIgnore, s));
770 	if(!attrs.empty) {
771 		throw new UnsupportedAttributes(format(
772 			"The key '%s' does not support attributes [%(%s, %)]",
773 				key, attrs));
774 	}
775 }
776 
777 void sdlEnforce(E)(bool cond, string msg, const Location loc,
778 		const string file = __FILE__, const size_t line = __LINE__)
779 {
780 	if(!cond) {
781 		throw new E(msg, loc, file, line);
782 	}
783 }