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 }