1 /// Module for easily making parsers. 2 /// 3 /// Can be used for both lexing and parsing grammars. 4 module rcdata.parser; 5 6 import std.range; 7 import std.traits; 8 import std.exception; 9 10 import rcdata.utils; 11 12 // TODO for each matcher, write a set of tests with no captures, one capture and multiple captures, mix them all too! 13 14 15 /// Check if the given function is a valid parser data supplier. 16 enum isMatchDataSupplier(Input, alias supply) = __traits(compiles, MatchData!(Input, supply)); 17 18 /// Check if given struct has a default constructor. True for other types. 19 enum hasDefaultConstructor(T) = __traits(compiles, { 20 T t; 21 }); 22 23 /// Get the result type of the given supplier. 24 template MatchData(Input, alias supply) { 25 26 import std.range; 27 28 auto supplierResultA() { return supply(Input.init.take(1)); } 29 auto supplierResultB() { return supply(supplierResultA, supplierResultA); } 30 31 alias SupplierResultA = typeof(supplierResultA()); 32 alias SupplierResultB = typeof(supplierResultB()); 33 34 alias MatchData = CommonType!(SupplierResultA, SupplierResultB); 35 36 static assert(!is(MatchData == void), 37 "Return types of supply(" ~ Take!Input.stringof ~ ") (" ~ SupplierResultA.stringof ~ ")" 38 ~ " and supply(supply(...), supply(...)) (" ~ SupplierResultB.stringof ~ ")" 39 ~ " are not compatible"); 40 41 } 42 43 /// Result of the match. 44 /// Params: 45 /// Input = Input range used by the parser. 46 /// supply = Function supplying user data such as tokens. 47 /// Ts = Optional types for additional values to hold within the match, used to add result data specific to 48 /// a matcher. If provided, the match can be implicitly casted to the original match type. 49 struct MatchImpl(Input, alias supply, Ts...) { 50 51 alias Data = MatchData!(Input, supply); 52 alias ParserException = ParserExceptionImpl!(Input, supply); 53 alias Match = typeof(this); 54 alias BaseMatch = MatchImpl!(Input, supply); 55 56 /// Types of the captured values. 57 alias Capture = Ts; 58 59 static assert(hasDefaultConstructor!Data, Data.stringof ~ " must support default construction"); 60 static assert(hasDefaultConstructor!(Take!Input), Take!Input.stringof ~ " must support default construction"); 61 62 63 /// If **not null**, the match failed with this message. Empty, but non-null, strings also count as successful 64 /// matches. Use the Match directly as a boolean to see. 65 string error; 66 67 /// Result of the `supply` function. 68 Data data; 69 70 /// Source matched by the result. In case of failure, this is an empty range, but `matched.source` can still be 71 /// used, and will contain the source range starting at the point of the match start. 72 Take!Input matched; 73 74 /// Additional data added by a specific matcher function. 75 Ts capture; 76 77 // Holding an additional value, make the struct implicitly convert into its base 78 static if (Ts.length != 0) { 79 80 alias base this; 81 82 } 83 84 invariant { 85 86 assert(error is null || matched.maxLength == 0, "`matched` must be empty if there's an error"); 87 88 } 89 90 91 /// Match succeeded. Calls `supply`. 92 auto this(Input source, size_t consumed, Ts values, string filename = __FILE__, size_t line = __LINE__) { 93 94 this.matched = source.take(consumed); 95 this.data = supply(this.matched); 96 this.capture = values; 97 98 } 99 100 /// Match succeeded, but pass the data directly instead of calling `supply`. Useful for altering existing matches. 101 this(Take!Input matched, Data data, Ts values, string filename = __FILE__, size_t line = __LINE__) nothrow pure @safe @nogc { 102 103 this.matched = matched; 104 this.data = data; 105 this.capture = values; 106 107 } 108 109 private this(inout typeof(this.tupleof) args) inout nothrow pure @safe @nogc { 110 111 this.tupleof = args; 112 113 } 114 115 static ok(Input source, size_t consumed, Ts values) { 116 117 return Match(source, consumed, values); 118 119 } 120 121 static ok(Take!Input matched, Data data, Ts values) { 122 123 return Match(matched, data, values); 124 125 } 126 127 /// Match failed. 128 /// 129 /// It's recommended not to use runtime formatted strings for match errors, to avoid constant GC allocations during 130 /// runtime. Formatting should be limited to critical errors, see `matchCritical`. 131 /// 132 /// Params: 133 /// source = Source that failed to match. 134 /// error = Error message, a reason why the match failed. 135 /// data = Optionally, context data for the failure, i.e. result data that matched successfully right before. 136 static Match fail(Input source, string error, Data data = Data.init) nothrow pure @safe @nogc { 137 138 assert(error !is null, "Error for Match.fail must not be null. Note an empty string literal is a valid value."); 139 140 return Match(error, data, source.take(0), Ts.init); 141 142 } 143 144 /// Get the data of the match. Throws `ParserException` if the match has failed. 145 inout(Data) tryData() inout pure @safe { 146 147 // Throw the exception on error 148 if (error) throw new ParserException(error, matched.source); 149 150 // Return the data otherwise 151 else return data; 152 153 } 154 155 /// Number of source elements consumed. 156 size_t consumed() const nothrow pure @safe @nogc { 157 158 return matched.maxLength; 159 160 } 161 162 /// Get the base match struct, without capture data. 163 inout(BaseMatch) base() inout nothrow pure @safe @nogc { 164 165 return inout BaseMatch(error, data, matched); 166 167 } 168 169 /// Assign a slice of the capture output. 170 void captureFrom(size_t index, Ts...)(Ts values) { 171 172 static assert(values.length != 0 || Ts.length == 0); 173 174 this.capture[index .. index + Ts.length] = values; 175 176 } 177 178 /// Check if the match succeeded. 179 bool opCast(T : bool)() const nothrow pure @safe @nogc { 180 181 return error is null; 182 183 } 184 185 const toString() { 186 187 import std.conv; 188 import std.meta; 189 import std.format; 190 191 enum maxLength = 32; 192 193 // Copy the match range into scope so we can iterate over it 194 Take!Input matched = matched; 195 196 // Get the first characters from it 197 const matchedArr = this 198 ? matched.take(maxLength+1).array 199 : matched.source.take(maxLength+1).array; 200 201 string matchedText; 202 203 // Include an ellipsis in the format data if the match is longer than length limit 204 if (matchedArr.length == maxLength+1) { 205 206 // Include length of the original range if available 207 static if (hasLength!(Take!Input)) 208 matchedText = format!"%(%s%)... %s"(matchedArr[0..$-1].only, matchedArr.length); 209 else 210 matchedText = format!"%(%s%)..."(matchedArr[0..$-1].only); 211 212 } 213 214 // Match fits, include the full text 215 else matchedText = format!"%(%s%)"(matchedArr.only); 216 217 218 static if (Capture.length) 219 const joined = ", " ~ only(tupleMap!text(capture).expand).join(", "); 220 else 221 const joined = ""; 222 223 return this 224 ? format!"Match(%s, %s%s)"(matchedText, data, joined) 225 : format!"Match.fail(%s, %(%s%)%s)"(matchedText, error.only, joined); 226 227 } 228 229 } 230 231 232 unittest { 233 234 import std.conv; 235 236 template supply() { 237 238 string[] supply(Take!string input) { 239 return [input.to!string]; 240 } 241 242 string[] supply(string[] a, string[] b) { 243 return a ~ b; 244 } 245 246 } 247 248 static assert(isMatchDataSupplier!(string, supply)); 249 250 } 251 252 /// Mixin to produce matcher templates for processing `Input` input range and creating a `Match` output range. 253 /// 254 /// The parser requires a function with two overloads called `supply`. Both are expected to output a user data type for 255 /// storing the parsing result (eg. a list of tokens). The first one should take a slice of the input range as 256 /// a `std.range.Take!Input` and will be called for any successful match. The second should take two instances of the 257 /// data type and combine them into once. 258 /// 259 /// A basic implementation of a supply string (with `string` as the input range) would be this: 260 /// 261 /// --- 262 /// string[] supply(Take!string input) { 263 /// return [input.to!string]; 264 /// } 265 /// 266 /// string[] supply(string[] a, string[] b) { 267 /// return a ~ b; 268 /// } 269 /// --- 270 /// 271 /// Notes: 272 /// 273 /// * Performance of the parser heavily depends on `std.range.popFrontN`, so it's the fastest if the range supports 274 /// slicing and has length. Alternatively, the range may define a `popFrontN` method itself. 275 /// * The mixin can work both in module scope and at `struct`/`class` scope. No context is required for the matcher 276 /// functions to work. 277 /// 278 /// Internally the template only defines aliases to closures rather than functions. This allows them to 279 /// work without an instance in structs and classes, but they may still use context for template parameters. 280 mixin template makeParser(Input, alias supply) 281 if (is(ElementType!Input : dchar)) { 282 283 import std.string; 284 285 mixin makeParser!(Input, supply, matchText); 286 287 static auto matchText(dstring text)(Input input) 288 out (r; text.length == 0 || !r || r.consumed != 0, format!"matchText `%s` consumed nothing"(text)) 289 do { 290 291 import std.range; 292 import std.string; 293 import std.exception; 294 295 import rcdata.utils; 296 297 // Match EOF 298 static if (text.length == 0) { 299 300 return input.empty 301 ? Match(input, 0) 302 : Match.fail(input, "Expected end of file"); 303 304 } 305 306 // Match the text 307 else return input.startsWith(text) 308 ? Match(input, text.length) 309 : Match.fail(input, "Couldn't match"); 310 311 } 312 313 } 314 315 /// ditto 316 mixin template makeParser(Input, alias supply, alias basicMatcher) { 317 318 import std.meta; 319 import std.format; 320 import std.range; 321 import std.traits; 322 import std.sumtype; 323 import std.functional; 324 325 import rcdata.utils; 326 import rcdata.parser; 327 328 static: 329 330 // Check arguments 331 static assert(isInputRange!Input, Input.stringof ~ " isn't an input range"); 332 333 alias Match = rcdata.parser.MatchImpl!(Input, supply); 334 alias MatchCapture(Ts...) = rcdata.parser.MatchImpl!(Input, supply, Ts); 335 alias ParserException = rcdata.parser.ParserExceptionImpl!(Input, supply); 336 337 338 /// Get an AliasSeq of capture types held by a given list of `Match` and `MatchCapture` types. 339 template MatchCaptureTypes(Ts...) { 340 341 alias MatchCaptureTypes = AliasSeq!(); 342 343 static foreach (T; Ts) { 344 345 static assert(is(T : Match), T.stringof ~ " is not a Match subtype."); 346 347 // Found a match type 348 static if (is(T : Match)) { 349 350 MatchCaptureTypes = AliasSeq!(MatchCaptureTypes, T.Capture); 351 352 } 353 354 } 355 356 } 357 358 /// Check the match type returned by `match` for the given pattern. 359 template MatchType(pattern...) { 360 361 static if (pattern.length == 1) { 362 363 alias MatchType = typeof(matchImpl!pattern(Input.init)); 364 365 } 366 367 // Note: We return the type of (match!pattern) rather than the tuple of match types. 368 else alias MatchType = MatchCapture!(PatternCaptureTypes!pattern); 369 370 } 371 372 /// Get an alias seq of capture types, similarly to `PatternCaptureTypes`, but based on a pattern for `match`. 373 alias PatternCaptureTypes(pattern...) = MatchCaptureTypes!(staticMap!(MatchType, pattern)); 374 375 /// Iterate on the given pattern tuple. 376 struct CaptureTupleIterator(patterns...) { 377 378 static int opApply(scope int delegate(size_t patternIndex, size_t captureIndex) dg) @system { 379 380 size_t patternIndex, captureIndex; 381 382 // Check each item in the pattern 383 static foreach (pattern; patterns) {{ 384 385 alias T = MatchType!pattern; 386 387 static assert(is(T : Match), 388 pattern.stringof ~ " does not return a valid Match instance but a " ~ T.stringof); 389 390 // Return the pattern and capture indices 391 if (auto result = dg(patternIndex, captureIndex)) { 392 393 return result; 394 395 } 396 397 // Bump them 398 patternIndex++; 399 captureIndex += T.Capture.length; 400 401 }} 402 403 return 0; 404 405 } 406 407 } 408 409 /// Wrap the tuple in a std.typecons.Tuple if its length isn't 1, otherwise return the sole item. 410 template TupleWrap(items...) { 411 412 import std.typecons : Tuple; 413 414 // One item, return it 415 static if (items.length == 1) alias TupleWrap = items[0]; 416 417 // Different count, wrap in a tuple 418 else alias TupleWrap = Tuple!items; 419 420 } 421 422 TupleWrap!Ts tupleWrap(Ts...)(Ts args) { 423 424 static if (Ts.length == 1) 425 return args[0]; 426 else 427 return TupleWrap!Ts(args); 428 429 } 430 431 432 static assert(is(Match == MatchCapture!())); 433 static assert(is(MatchCapture!int == MatchCapture!(MatchCaptureTypes!(Match, MatchCapture!int)))); 434 435 /// Match anything. 436 alias matchAny() = (Input input) 437 438 => matchAny!((ElementType!Input item) => true)(input); 439 440 441 /// Match one single item if `fun` returns `true`. 442 alias matchAny(alias fun) = (Input input) { 443 444 alias funCC = unaryFun!fun; 445 446 // Check for EOF 447 return input.empty ? Match.fail(input, "Unexpected end of file") 448 449 // Check if the function matches 450 : funCC(input.front) ? Match(input, 1) 451 452 // Fail if it doesn't 453 : Match.fail(input, "Delegate didn't match the element"); 454 455 }; 456 457 /// Match multiple items in order. Each matcher in the pattern will be tested against the source in order, and will 458 /// advance the range before the next item in the pattern. The full pattern has to match. 459 /// 460 /// `match` is a building block for any other pattern matching function. It will automatically call the proper 461 /// matcher (either the given matcher function, or `basicMatcher` as a fallback for convenience), and it can be used 462 /// to build any other sequential patterns (such as `matchRepeat`) by utilising the `context` parameter. 463 /// 464 /// A matcher function will be tried with each of the following: 465 /// 466 /// * `fun(input, capture)` if the function returns capture data, to pass on data from its previous iterations. 467 /// * `fun(input)` to perform a regular match. 468 /// * `basicMatcher!fun(input)` as a convenience call for the chosen `basicMatcher`; for example, the default 469 /// `basicMatcher` for any dchar range would allow using strings as match patterns: 470 /// `match!"struct"("struct Foo")` 471 /// * If none of those can compile, `match` will output the errors of `fun(input)` and `basicMatcher!fun(input)`. 472 /// 473 /// Params: 474 /// pattern = Pattern that has to be matched. 475 /// input = Source to parse. 476 /// context = Optionally, previous match result if used within repetitive matchers. Must be successful. 477 template match(pattern...) 478 if (is(MatchType!pattern)) { 479 480 import std.typecons; 481 482 // If compatible with the pattern 483 alias match = (Input input, MatchType!pattern context = MatchType!pattern.init) { 484 485 assert(context, "Given context match has failed"); 486 487 alias Return = MatchType!pattern; 488 489 Input source = input; 490 Return result = context; 491 492 // This function cannot match less than matched by the context 493 scope (exit) assert(result.consumed >= context.consumed); 494 495 // Evaluate each matcher 496 try static foreach (index, captureIndex; CaptureTupleIterator!pattern) {{ 497 498 alias Local = MatchType!(pattern[index]); 499 500 // Get the last capture for this matcher, if any 501 auto lastCapture = result.capture[captureIndex..captureIndex + Local.Capture.length]; 502 503 // Try the match 504 Local local = matchImpl!(pattern[index])(source, lastCapture); 505 506 // Combine data 507 auto data = supply(result.data, local.data); 508 509 // If the match failed 510 if (!local) { 511 512 // Return its match with updated data 513 return Return.fail(local.matched.source, local.error, data); 514 515 } 516 517 // Success, expand the match to contain the full result 518 result.matched = result.matched.source.take(result.consumed + local.consumed); 519 520 // Add user data 521 result.data = data; 522 result.capture[captureIndex .. captureIndex + Local.Capture.length] = local.capture; 523 524 assert(result); 525 526 // Advance the input range 527 source.popFrontN(local.consumed); 528 529 }} 530 531 // Expand any caught critical exceptions with context info 532 catch (ParserException exc) { 533 534 throw exc.extend(result.data); 535 536 } 537 538 return result; 539 540 }; 541 542 } 543 544 // Try to alias to std.sumtype.match to help with namespace clash 545 template match(handlers...) 546 if (!is(MatchType!handlers)) { 547 548 alias match = std.sumtype.match!handlers; 549 550 } 551 552 /// Try the overloads of the function to perform the match. 553 private template matchImpl(alias fun) { 554 555 // Overload 1: regular match 556 alias matchImpl = (Input input) { 557 558 // Try to run the matcher 559 static if (is(typeof(fun(input)) : Match)) { 560 561 return fun(input); 562 563 } 564 565 // It's not callable. But maybe basicMatcher can handle it? 566 else static if (is(typeof(basicMatcher!fun(input)) : Match)) { 567 568 // Alias to basicMatcher 569 return basicMatcher!fun(input); 570 571 } 572 573 // Try both to see what the errors are 574 else { 575 576 Match local = fun(input); 577 local = basicMatcher!fun(input); 578 579 } 580 581 assert(false); 582 583 }; 584 585 alias Capture = typeof(matchImpl(Input.init)).Capture; 586 587 // Overload 2, if no.1 has a capture, pass the previous capture data into it, to allow things like matchRepeat 588 // keep context. 589 static if (Capture.length != 0) 590 alias matchImpl = (Input input, Capture capture) { 591 592 alias stringofone(funs...) = funs.stringof; 593 594 // Special overload supported 595 static if (is(typeof(fun(input, capture)) : Match)) { 596 597 static assert(is(typeof(fun(input, capture)) == typeof(fun(input))), 598 format!("`%s %s(input, capture)` return type doesn't match the one of `%s %2$s(input)`") 599 (typeof(fun(input, capture)).stringof, stringofone!fun, typeof(fun(input)).stringof)); 600 601 return fun(input, capture); 602 603 } 604 605 // Run the default overload otherwise 606 else return matchImpl(input); 607 608 }; 609 610 } 611 612 /// Match one of the given items. 613 alias matchOr(pattern...) = (Input input) { 614 615 alias ReturnMatch = MatchOr!pattern; 616 alias Capture = ReturnMatch.Capture; 617 618 // Evaluate each matcher 619 foreach (fun; pattern) { 620 621 // Return the result of the first one succeeding 622 if (auto result = match!fun(input)) { 623 624 // Cast the capture to our expected type 625 Capture capture = tupleWrap(result.capture); 626 627 return ReturnMatch(result.matched, result.data, capture); 628 629 } 630 631 } 632 633 return ReturnMatch.fail(input, "No match found for matchOr!(" ~ pattern.stringof ~ ")"); 634 635 }; 636 637 /// Return the `matchOr` return value for the given pattern. 638 template MatchOr(pattern...) { 639 640 import std.meta; 641 import std.typecons : Tuple; 642 643 // Collect possible values for the capture. 644 alias Types = AliasSeq!(); 645 646 // Check each pattern 647 static foreach (fun; pattern) { 648 649 // Add it to the type list, wrap in a tuple if item count != 1 650 Types = AliasSeq!(Types, TupleWrap!(MatchType!fun.Capture)); 651 652 } 653 654 // Get rid of duplicates 655 Types = NoDuplicates!Types; 656 657 658 // No types, return a plain match 659 static if (Types.length == 0 || is(Types[0] == Tuple!())) 660 alias MatchOr = Match; 661 662 // One type only, return it 663 else static if (Types.length == 1) 664 alias MatchOr = MatchCapture!Types; 665 666 // Multiple types, make it a sum type 667 else alias MatchOr = MatchCapture!(SumType!Types); 668 669 } 670 671 /// Repeat the token sequence (as in `match`) zero to infinity times 672 alias matchRepeat(pattern...) = (Input input, MatchType!pattern context = MatchType!pattern.init) 673 674 => matchRepeatMin!(0, pattern)(input, context); 675 676 677 /// Repeat the token sequence at least once. 678 alias matchRepeatMinOnce(pattern...) = (Input input, MatchType!pattern context = MatchType!pattern.init) 679 680 => matchRepeatMin!(1, pattern)(input, context); 681 682 683 alias matchRepeatMin(size_t minMatches, pattern...) = (Input input, 684 MatchType!pattern context = MatchType!pattern.init) 685 686 => matchRepeatRange!pattern(input, context, minMatches); 687 688 689 /// Repeat the token sequence (as in match) `minMatches` to `maxMatches` times 690 alias matchRepeatRange(size_t minMatches, size_t maxMatches, pattern...) = (Input input, 691 MatchType!pattern context = MatchType!pattern.init) 692 693 => matchRepeatRange!pattern(input, context, minMatches, maxMatches); 694 695 696 /// ditto 697 template matchRepeatRange(pattern...) 698 if (!is(typeof(pattern[0]) : size_t)) { 699 700 alias matchRepeatRange = (Input input, MatchType!pattern context = MatchType!pattern.init, 701 size_t minMatches = 0, size_t maxMatches = size_t.max) 702 { 703 704 size_t matches; 705 706 while (true) { 707 708 MatchType!pattern local; 709 710 // Match the token 711 local = match!pattern(input, context); 712 713 // Stop if match failed, we don't care about it 714 if (!local) break; 715 716 // Count the match 717 matches++; 718 719 const length = local.consumed - context.consumed; 720 721 // Advance the input 722 input.popFrontN(length); 723 context = local; 724 725 // Special case: Match is empty, stop to prevent loops 726 if (length == 0) break; 727 728 // Stop if matched enough 729 if (matches == maxMatches) break; 730 731 } 732 733 // Check if matched enough times 734 return matches < minMatches 735 ? context.fail(input, "matchRepeat didn't match enough times") 736 : context; 737 738 }; 739 740 } 741 742 /// Repeat the sequence until another token matches. Does NOT match the terminator. 743 /// 744 /// If the pattern fails, the failure will be propagated, and `matchUntil` will also report failure. 745 /// 746 /// Params: 747 /// terminator = Matcher to stop the loop when successful. 748 /// pattern = Pattern to match. Defaults to `matchAny`. 749 alias matchUntil(alias terminator) = (Input input) 750 751 => matchUntil!(terminator, matchAny!())(input); 752 753 754 /// ditto 755 template matchUntil(alias terminator, pattern...) 756 if (pattern.length != 0) { 757 758 alias matchUntil = (Input input) { 759 760 MatchType!pattern context; 761 762 while (true) { 763 764 // Match the terminator 765 if (match!terminator(input, context)) break; 766 767 // Match the token 768 auto local = match!pattern(input, context); 769 770 // Propagate failures 771 if (!local) return local; 772 773 // Count consumed tokens 774 const length = local.consumed - context.consumed; 775 776 // Special case: Match is empty, stop to prevent loops 777 if (length == 0) break; 778 779 // Advance the range 780 input.popFrontN(length); 781 context = local; 782 783 } 784 785 return context; 786 787 }; 788 789 } 790 791 /// Match zero or one instances of a token. 792 /// Returns: `Match` or if the value contains captures, `MatchCapture!(Nullable!Match)`. 793 alias matchOptional(pattern...) = (Input input) { 794 795 import std.typecons; 796 797 // Match the token 798 auto ret = match!pattern(input); 799 800 // No capture 801 static if (ret.Capture.length == 0) { 802 803 return ret 804 ? ret 805 : Match.ok(input, 0); 806 807 } 808 809 // Capture on, add a nullable value 810 else { 811 812 alias Capture = Nullable!(TupleWrap!(ret.Capture)); 813 alias Return = MatchCapture!Capture; 814 815 return ret 816 ? Return(ret.matched, ret.data, Capture(tupleWrap(ret.capture))) 817 : Return(input, 0, Capture()); 818 819 } 820 821 }; 822 823 /// Construct `T` by matching given `pattern`. 824 /// 825 /// Returns: `MatchCapture!T` holding the constructed value. 826 alias matchCapture(T, pattern...) = (Input input, T value = T.init) { 827 828 // This function is an ugly mess. I don't blame myself. I don't see how it could've been done better. 829 830 alias Result = MatchCapture!T; 831 832 Input source = input; 833 auto result = Result(input, 0, value); 834 835 /// Union to hold results of each pattern match 836 union LastResult { 837 838 // Create storage for each value 839 static foreach (ptrdiff_t i, fun; pattern) { 840 841 static if (!is(ByIndex!i == void)) 842 mixin("ByIndex!i c", i, ";"); 843 844 else mixin("typeof(null) c", i, ";"); 845 846 } 847 848 /// Type of the value at given index. 849 template ByIndex(ptrdiff_t i) { 850 851 // Defined 852 static if (__traits(compiles, mixin("c", i))) alias ByIndex = typeof(byIndex!i); 853 854 // Not defined yet 855 else { 856 857 alias ByIndex = typeof( 858 matchCaptureImpl!(T, pattern[i], typeof(byIndex!(i-1)()))(source, result.capture, byIndex!(i-1)) 859 ); 860 861 } 862 863 } 864 865 /// Get a stored value by its index. 866 auto ref byIndex(ptrdiff_t i)() @trusted { 867 868 static if (i < 0) return null; 869 else return mixin("c", i); 870 871 } 872 873 } 874 875 LastResult lastResult; 876 877 /// Run the rule at given index. Does not store the result. 878 auto run(ptrdiff_t i)(LastResult lastResult, Input input, ref Result match) { 879 880 // This must have no context other than pattern[i] 881 882 auto last = lastResult.byIndex!(i-1); 883 884 // Run this rule 885 return matchCaptureImpl!(T, pattern[i], typeof(last))(input, match.capture, last); 886 887 } 888 889 import core.lifetime; 890 891 // Evaluate each rule 892 try static foreach (i, fun; pattern) {{ 893 894 // If this is a matcher 895 static if (is(lastResult.ByIndex!i : Match)) { 896 897 // Run the rule 898 auto local = run!i(lastResult, source, result); 899 900 // Combine data 901 auto data = supply(result.data, local.data); 902 903 // Match failed 904 if (!local) { 905 906 return Result.fail(local.matched.source, local.error, data); 907 908 } 909 910 // Success 911 result = Result( 912 result.matched.source.take(result.consumed + local.consumed), 913 data, 914 result.capture, 915 ); 916 917 // Advance the input range 918 source.popFrontN(local.consumed); 919 920 // Move into the register 921 move(local, lastResult.byIndex!i()); 922 923 } 924 925 // Void 926 else static if (is(lastResult.ByIndex!i == void)) { 927 928 run!i(lastResult, source, result); 929 930 } 931 932 // Non-void, run the function and read the value 933 else lastResult.byIndex!i = run!i(lastResult, source, result); 934 935 }} 936 937 // Expand any caught exceptions with context info 938 catch (ParserException exc) { 939 940 throw exc.extend(result.data); 941 942 } 943 944 return result; 945 946 }; 947 948 private alias matchCaptureImpl(T, alias fun, LastResult) = (Input input, ref T value, LastResult lastResult) { 949 950 // TODO maybe this function should unconditionally return a match? 951 952 // Got a match 953 static if (is(LastResult : Match)) { 954 955 auto capture = lastResult.capture; 956 957 } 958 959 960 // Option 1, (LastResult, ref T) 961 static if (__traits(compiles, fun(lastResult, value))) { 962 963 return fun(lastResult, value); 964 965 } 966 967 // Option 2, (LastLastResult.capture, ref T) 968 else static if (__traits(compiles, fun(capture, value))) { 969 970 return fun(capture, value); 971 972 } 973 974 // Option 3, matchImpl(input) 975 else static if (__traits(compiles, matchImpl!fun(input))) { 976 977 return matchImpl!fun(input); 978 979 } 980 981 else { 982 983 fun(capture, value); 984 static assert(false, stringofone!fun ~ " is not a valid matchCapture predicate; " 985 ~ "LastResult = `" ~ LastResult.stringof ~ "`"); 986 987 } 988 989 }; 990 991 /// Require the given rule to match, otherwise throw given exception. 992 /// Params: 993 /// Exc = Exception type to instantiate and throw. Will be passed a message string and, if supported, input 994 /// range of the following source text. If omitted, but `message` is given, throws `ParserException`. 995 /// message = Message of the exception to throw. 996 /// instance = Already constructed instance of an exception to throw. 997 /// pattern = Pattern to match. If empty, trying to match this pattern will result with the exception. 998 alias matchCritical(Exc : Throwable, string message, pattern...) = (Input input) { 999 1000 // Exception accepting source data 1001 static if (__traits(compiles, new Exc(message, input))) { 1002 1003 return matchCriticalImpl!pattern(input, new Exc(message, input)); 1004 1005 } 1006 1007 // Regular exception 1008 else return matchCriticalImpl!pattern(input, new Exc(message)); 1009 1010 }; 1011 1012 /// ditto 1013 alias matchCritical(string message, pattern...) = (Input input) 1014 1015 => matchCritical!(ParserException, message, pattern)(input); 1016 1017 1018 /// ditto 1019 alias matchCritical(Throwable instance, pattern...) = (Input input) 1020 1021 => matchCriticalImpl!pattern(input, instance); 1022 1023 1024 /// ditto 1025 alias matchCriticalImpl(pattern...) = (Input input, Throwable instance) { 1026 1027 // If there's a sequence to check 1028 static if (pattern.length) { 1029 1030 // Try to match 1031 if (auto result = match!pattern(input)) { 1032 1033 return result; 1034 1035 } 1036 1037 // Match failed, throw the chosen exception 1038 else throw instance; 1039 1040 } 1041 1042 // Just throw on encounter 1043 else { 1044 1045 // Let the compiler infer the return type 1046 if (false) return match!pattern(input); 1047 1048 throw instance; 1049 1050 } 1051 1052 }; 1053 1054 /// Adjust failure message of the given pattern. 1055 alias matchFailMessage(string message, pattern...) = (Input input) { 1056 1057 auto result = match!pattern(input); 1058 1059 return result 1060 ? result 1061 : result.fail(input, message); 1062 1063 }; 1064 1065 /// Check if the pattern matches, but do not supply it. 1066 alias lookAhead(pattern...) = (Input input) { 1067 1068 auto result = match!pattern(input); 1069 1070 // Return empty match on success, the result if failed 1071 return result 1072 ? result.ok(input, 0, result.capture) 1073 : result; 1074 1075 }; 1076 1077 /// Fail if the pattern matches. Succeeds if the rule fails. Doesn't supply anything. 1078 alias failAhead(pattern...) = (Input input) { 1079 1080 return match!pattern(input) 1081 ? Match.fail(input, "Rule matched but shouldn't have") 1082 : Match(input, 0); 1083 1084 }; 1085 1086 /// Never matches. 1087 alias matchNever(string msg) = (Input input) { 1088 1089 return Match.fail(input, msg); 1090 1091 }; 1092 1093 } 1094 1095 /// Exception type thrown on a parser failure, usually `matchCritical`. 1096 class ParserExceptionImpl(Input, alias supply) : RCDataException { 1097 1098 import std.range; 1099 1100 static assert(isInputRange!Input, Input.stringof ~ " isn't an input range"); 1101 1102 alias Match = MatchData!(Input, supply); 1103 1104 Match context; 1105 Input source; 1106 1107 this(string msg, Input source, string filename = __FILE__, size_t line = __LINE__) pure @safe { 1108 1109 super(msg, filename, line); 1110 this.source = source; 1111 1112 } 1113 1114 this(string msg, Match context, Input source, string filename = __FILE__, size_t line = __LINE__) pure @safe { 1115 1116 this(msg, source, filename, line); 1117 this.context = context; 1118 1119 } 1120 1121 mixin template parserExeptionCtors() { 1122 1123 this(string msg, Input source, string filename = __FILE__, size_t line = __LINE__) pure @safe { 1124 1125 super(msg, source, filename, line); 1126 1127 } 1128 1129 this(string msg, Match context, Input source, string filename = __FILE__, size_t line = __LINE__) 1130 pure @safe 1131 do { 1132 super(msg, context, source, filename, line); 1133 } 1134 1135 } 1136 1137 /// Extend the exception with data about the parent context. 1138 auto extend(Match context) { 1139 1140 this.context = supply(context, this.context); 1141 return this; 1142 1143 } 1144 1145 }