1 module pry.combinators; 2 3 import pry.traits; 4 import std.meta, std.range.primitives, std.typecons, std.traits; 5 6 private: 7 8 public struct Nullable(T){ 9 bool isNull = true; 10 T value; 11 12 this(T val){ 13 value = val; 14 isNull = false; 15 } 16 17 void opAssign(T value){ 18 this.value = value; 19 isNull = false; 20 } 21 22 void nullify(){ 23 isNull = true; 24 static if(is(T == struct)) 25 value.tupleof = T.init.tupleof; 26 else 27 value = T.init; 28 } 29 30 alias value this; 31 } 32 33 struct RepImpl(bool collect, Array, size_t minTimes, size_t maxTimes, Parser){ 34 alias Stream = ParserStream!Parser; 35 static if(collect) 36 alias Value = Array; 37 else 38 alias Value = Stream.Range; 39 private Parser parser; 40 41 bool parse(ref Stream stream, ref Value value, ref Stream.Error err) const { 42 auto start = stream.mark; 43 ParserValue!Parser tmp; 44 size_t i = 0; 45 static if(collect) value = null; 46 for(; i<minTimes; i++) { 47 if(!parser.parse(stream, tmp, err)){ 48 stream.restore(start); 49 return false; 50 } 51 static if(collect) value ~= tmp; 52 } 53 for(; i<maxTimes; i++){ 54 if(!parser.parse(stream, tmp, err)) break; 55 static if(collect) value ~= tmp; 56 } 57 static if(!collect) 58 value = stream.slice(start); 59 return true; 60 } 61 } 62 63 64 /// Apply parser for minTimes times or more and return consumed range. 65 public auto rep(size_t minTimes=1, size_t maxTimes=size_t.max, Parser)(Parser parser) 66 if(isParser!Parser){ 67 return RepImpl!(false, ParserValue!Parser[], minTimes, maxTimes, Parser)(parser); 68 } 69 70 /// Apply parser for minTimes times or more and return array of results. 71 public auto array(size_t minTimes=1, size_t maxTimes=size_t.max, Parser)(Parser parser) 72 if(isParser!Parser){ 73 return RepImpl!(true, ParserValue!Parser[], minTimes, maxTimes, Parser)(parser); 74 } 75 76 /// Apply parser for minTimes times or more and encode results to an UTF string. 77 public auto utfString(Char, size_t minTimes=1, size_t maxTimes=size_t.max, Parser)(Parser parser) 78 if(isParser!Parser){ 79 return RepImpl!(true, immutable(Char)[], minTimes, maxTimes, Parser)(parser); 80 } 81 82 unittest 83 { 84 import pry.atoms, pry.stream; 85 alias S = SimpleStream!string; 86 with(parsers!S) 87 { 88 auto p = tk!'a'.rep!2; 89 string r; 90 S.Error err; 91 auto s = S("aaac"); 92 assert(p.parse(s, r, err)); 93 assert(r == "aaa"); 94 95 s = S("a"); 96 assert(!p.parse(s, r, err)); 97 assert(err.location == 1); 98 assert(err.reason == "unexpected end of stream"); 99 100 auto p2 = range!('a', 'b').array; 101 dchar[] r2; 102 s = S("aba"); 103 assert(p2.parse(s, r2, err)); 104 assert(r2 == "aba"d); 105 assert(s.empty); 106 107 108 s = S("aaaa"); 109 auto p3 = tk!'a'.rep!(2,3); 110 assert(p3.parse(s, r, err)); 111 assert(r == "aaa"); 112 assert(!s.empty); 113 114 s = S("ФЯ"); 115 auto p4 = any(tk!'Ф', tk!'Я').utfString!char; 116 assert(p4.parse(s, r, err)); 117 assert(r == "ФЯ"); 118 assert(s.empty); 119 } 120 } 121 122 123 struct Map(Parser, alias f) { 124 alias Stream = ParserStream!Parser; 125 alias Value = typeof(f(ParserValue!Parser.init)); 126 alias mapper = f; 127 Parser parser; 128 129 bool parse(ref Stream stream, ref Value value, ref Stream.Error err) const { 130 ParserValue!Parser tmp; 131 if(parser.parse(stream, tmp, err)){ 132 value = f(tmp); 133 return true; 134 } 135 else 136 return false; 137 } 138 } 139 140 /// Apply a mapping function to the value of parser. 141 public template map(alias f) 142 { 143 auto map(Parser)(Parser parser) 144 if(isParser!Parser){ 145 return Map!(Parser, f)(parser); 146 } 147 } 148 149 /// 150 unittest { 151 import std.conv; 152 import pry.atoms, pry.stream; 153 alias S = SimpleStream!string; 154 with(parsers!S) { 155 auto digits = range!('0', '9').rep.map!(x=>x.to!int); 156 S s = S("90"); 157 int r; 158 S.Error err; 159 assert(digits.parse(s, r, err)); 160 assert(r == 90); 161 s = S("a"); 162 assert(!digits.parse(s, r, err)); 163 assert(err.location == 0); 164 } 165 } 166 167 struct Seq(P...){ 168 alias Stream = ParserStream!(P[0]); 169 alias Unfiltered = staticMap!(ParserValue, P); 170 alias Values = EraseAll!(Nothing, Unfiltered); 171 enum mapIndex(size_t index) = (){ 172 size_t j = 0; 173 foreach(i, Type; Unfiltered[0..index]){ 174 static if(!is(Type == Nothing)){ 175 j++; 176 } 177 } 178 return j; 179 }(); 180 P parsers; 181 182 bool parse(ref Stream stream, ref Tuple!Values value, ref Stream.Error err) const { 183 Nothing nothing; 184 auto save = stream.mark; 185 foreach(i, ref p; parsers) { 186 static if(is(ParserValue!(typeof(p)) == Nothing)) { 187 if(!p.parse(stream, nothing, err)){ 188 stream.restore(save); 189 return false; 190 } 191 } 192 else { 193 enum j = mapIndex!i; 194 if(!p.parse(stream, value[j], err)){ 195 stream.restore(save); 196 return false; 197 } 198 } 199 } 200 return true; 201 } 202 } 203 204 /// Apply multiple parsers one after another as a sequence. 205 public auto seq(P...)(P parsers) 206 if(allSatisfy!(isParser, P)){ 207 static if(P.length == 0) 208 return Nothing(); 209 else 210 return Seq!P(parsers); 211 } 212 213 /// 214 unittest { 215 import pry.atoms, pry.stream; 216 import std.range.primitives, std.typecons; 217 alias S = SimpleStream!string; 218 with(parsers!S) { 219 auto elements = seq(tk!'a', range!('a', 'd'), tk!'c'); 220 S s = S("abc"); 221 Tuple!(dchar, dchar, dchar) val; 222 S.Error err; 223 assert(elements.parse(s, val, err)); 224 assert(s.empty); 225 assert(val == tuple('a', 'b', 'c')); 226 s = S("axc"); 227 assert(!elements.parse(s, val, err)); 228 assert(s.front == 'a'); 229 assert(err.location == 1); 230 } 231 } 232 233 /// 234 unittest { 235 import pry.atoms, pry.stream; 236 alias S = SimpleStream!string; 237 with(parsers!S) { 238 auto p = seq(tk!'a', tk!'b', eof); 239 auto s1 = "abc".stream; 240 auto s2 = "ab".stream; 241 S.Error err; 242 Tuple!(dchar, dchar) val; 243 assert(!p.parse(s1, val, err)); 244 assert(p.parse(s2, val, err)); 245 } 246 } 247 248 struct TList(T...){} 249 250 template commonPrefixLength(T1, T2){ 251 static if(is(T1 == TList!U1, U1...)){ 252 static if(is(T2 == TList!U2, U2...)){ 253 static if(U1.length == 0 || U2.length == 0){ 254 enum commonPrefixLength = 0; 255 } 256 else static if(is(Unqual!(U1[0]) == Unqual!(U2[0]))){ 257 enum commonPrefixLength = 1 + 258 commonPrefixLength!(TList!(U1[1..$]), TList!(U2[1..$])); 259 } 260 else { 261 enum commonPrefixLength = 0; 262 } 263 } 264 } 265 } 266 267 auto commonPrefix(P1, P2)(P1 p1, P2 p2){ 268 static if(is(P1 : Seq!U1, U1...)){ 269 enum isSeq = true; 270 alias T1 = TList!U1; 271 } 272 else{ 273 enum isSeq = false; 274 alias T1 = TList!P1; 275 } 276 static if(is(P2 : Seq!U2, U2...)){ 277 alias T2 = TList!U2; 278 } 279 else{ 280 alias T2 = TList!P2; 281 } 282 enum len = commonPrefixLength!(T1,T2); 283 static if(isSeq) 284 return seq(p1.parsers[0..len]); 285 else static if(len) 286 return seq(p1); 287 else 288 return Nothing(); 289 } 290 291 auto commonPrefix(P...)(P p) 292 if(P.length > 2){ 293 return commonPrefix(commonPrefix(p[0], p[1]), p[2..$]); 294 } 295 296 unittest { 297 import pry.atoms, pry.stream; 298 alias S = SimpleStream!string; 299 with(parsers!S){ 300 auto p1 = seq(tk!'a', tk!'b'); 301 auto p2 = seq(tk!'a'); 302 auto p3 = tk!'a'; 303 auto p4 = seq(tk!'b', tk!'a'); 304 assert(commonPrefix(p1, p2) == seq(tk!'a')); 305 assert(commonPrefix(p2, p3) == seq(tk!'a')); 306 assert(commonPrefix(p3, p3) == seq(tk!'a')); 307 assert(commonPrefix(p3, p1) == seq(tk!'a')); 308 assert(commonPrefix(p1, p4) == Nothing()); 309 assert(commonPrefix(p3, p4) == Nothing()); 310 assert(commonPrefix(p4, p3) == Nothing()); 311 assert(commonPrefix(p1, p2, p3) == seq(tk!'a')); 312 assert(commonPrefix(p1, p2, p3, p4) == Nothing()); 313 } 314 } 315 316 auto suffix(P1, P2)(P1 prefix, P2 parser) 317 if(isParser!P1 && isParser!P2){ 318 static if(is(P1 : Seq!U1, U1...)){ 319 alias T1 = TList!U1; 320 } 321 else{ 322 alias T1 = TList!P1; 323 } 324 static if(is(P2 : Seq!U2, U2...)){ 325 enum isSeq = true; 326 alias T2 = TList!U2; 327 } 328 else{ 329 enum isSeq = false; 330 alias T2 = TList!P2; 331 } 332 enum len = commonPrefixLength!(T1,T2); 333 static if(is(T1 : TList!U, U...)){ 334 static assert(len == U.length); 335 } 336 static if(isSeq){ 337 return seq(parser.parsers[len..$]); 338 } 339 else{ 340 static if(len > 0) 341 return Nothing(); 342 else 343 return seq(parser); 344 } 345 } 346 347 unittest{ 348 import pry.atoms, pry.stream; 349 alias S = SimpleStream!string; 350 with(parsers!S){ 351 auto p1 = seq(tk!'a', tk!'b'); 352 auto p2 = seq(tk!'a'); 353 auto p3 = tk!'a'; 354 auto p4 = seq(tk!'b', tk!'a'); 355 assert(suffix(p2, p1) == seq(tk!'b')); 356 assert(suffix(p3, p1) == seq(tk!'b')); 357 assert(suffix(p2, p3) == Nothing()); 358 } 359 } 360 361 template Unmap(P){ 362 static if(is(P : Map!(U, f), alias f, U)){ 363 alias Unmap = U; 364 } 365 else static if(is(P : const(Map!(U, f)), alias f, U)){ 366 alias Unmap = const(U); 367 } 368 else{ 369 alias Unmap = P; 370 } 371 } 372 373 auto unmap(P)(P parser){ 374 static if(is(P : Map!(U, f), alias f, U)){ 375 return parser.parser; 376 } 377 else { 378 return parser; 379 } 380 } 381 382 unittest{ 383 import pry.atoms, pry.stream; 384 alias S = SimpleStream!string; 385 with(parsers!S){ 386 auto x = tk!'a'.map!(x => 1); 387 assert(unmap(x) == tk!'a'); 388 assert(unmap(unmap(x)) == tk!'a'); 389 } 390 } 391 392 struct Any(P...){ 393 import std.variant, std.typecons; 394 alias Stream = ParserStream!(P[0]); 395 alias Values = NoDuplicates!(staticMap!(ParserValue, P)); 396 397 static if(Values.length == 1) 398 alias Value = Values[0]; 399 else 400 alias Value = Algebraic!Values; 401 402 P parsers; 403 alias Prefix = typeof(extractPrefix()); 404 Prefix prefix; 405 406 template mapper(size_t i){ 407 static if(is(P[i] == Map!(U, f), U, alias f)) 408 alias mapper = P[i].mapper; 409 else 410 alias mapper = x => x; 411 } 412 413 this(P parsers){ 414 this.parsers = parsers; 415 prefix = extractPrefix(); 416 } 417 418 auto extractPrefix() const { 419 staticMap!(Unmap, P) unmapped = void; 420 foreach(i, ref p; parsers){ 421 unmapped[i] = cast()unmap(p); 422 } 423 return commonPrefix(unmapped); 424 } 425 426 static auto combine(T1, T2)(T1 prefixValue, T2 suffixValue){ 427 static if(is(T1 == Nothing)){ 428 return suffixValue; 429 } 430 else{ 431 return tuple(prefixValue.expand, suffixValue.expand); 432 } 433 } 434 435 bool parse(ref Stream stream, ref Value value, ref Stream.Error err) const { 436 static if(is(Prefix == Nothing)){ 437 Nothing prefixValue; 438 } 439 else{ 440 ParserValue!Prefix prefixValue; 441 if(!prefix.parse(stream, prefixValue, err)){ 442 return false; 443 } 444 } 445 Stream.Error current, deepest; 446 bool ret = false; 447 foreach(i, ref p; parsers) { 448 static if(is(Prefix == Nothing)) 449 auto sp = unmap(p); 450 else 451 auto sp = suffix(prefix, unmap(p)); 452 alias Suffix = typeof(sp); 453 static if(is(Suffix == Nothing)){ 454 value = mapper!i(prefixValue.expand); 455 ret = true; 456 goto L_end; 457 } 458 else { 459 ParserValue!Suffix suffixValue; 460 static if(i == 0){ 461 if(sp.parse(stream, suffixValue, deepest)){ 462 value = mapper!i(combine(prefixValue, suffixValue)); 463 return true; 464 } 465 } 466 else { 467 if(sp.parse(stream, suffixValue, current)){ 468 value = mapper!i(combine(prefixValue, suffixValue)); 469 return true; 470 } 471 // pick the deeper error 472 if(deepest.location < current.location){ 473 deepest = current; 474 } 475 } 476 } 477 } 478 L_end: 479 err = deepest; 480 return ret; 481 } 482 } 483 484 /// Try each of provided parsers until one succeeds. 485 public auto any(P...)(P parsers) 486 if(allSatisfy!(isParser, P)){ 487 return Any!P(parsers); 488 } 489 490 /// 491 unittest { 492 import pry.atoms, pry.stream; 493 import std.range.primitives, std.conv, std.variant; 494 alias S = SimpleStream!string; 495 with(parsers!S) { 496 auto digits = range!('0', '9').rep.map!(x => x.to!int); 497 static assert(isParser!(typeof(digits))); 498 auto parser = any(tk!'a', digits); 499 S s = "10a"; 500 S.Error err; 501 Algebraic!(dchar, int) value; 502 assert(parser.parse(s, value, err)); 503 assert(value == 10); 504 assert(parser.parse(s, value, err)); 505 assert(value == cast(dchar)'a'); 506 assert(s.empty); 507 } 508 } 509 510 unittest { 511 import pry.atoms, pry.stream; 512 alias S = SimpleStream!string; 513 with(parsers!S) { 514 auto e = dynamic!int; 515 e = any( 516 seq(tk!'0', e).map!(x => 1), 517 tk!'1'.map!(x => 0) 518 ); 519 S.Error err; 520 int val; 521 S s = S("0001"); 522 assert(e.parse(s, val, err)); 523 } 524 } 525 526 unittest { 527 import pry.atoms, pry.stream; 528 import std.typecons; 529 alias S = SimpleStream!string; 530 with(parsers!S) { 531 auto p = any( 532 seq(tk!'0', tk!'0'), 533 seq(tk!'1', tk!'1') 534 ); 535 S s = "01".stream; 536 Tuple!(dchar, dchar) value; 537 S.Error err; 538 assert(!p.parse(s, value, err)); 539 assert(err.location == 1); 540 } 541 } 542 543 544 struct SkipWs(P) { 545 private P parser; 546 alias Stream = ParserStream!P; 547 alias Value = ParserValue!P; 548 549 bool parse(ref Stream s, ref Value v, ref Stream.Error err) const { 550 import std.uni; 551 while(!s.empty && isWhite(s.front)) s.popFront(); 552 return parser.parse(s, v, err); 553 } 554 } 555 556 /// Skip whitespace at front then apply the `parser`. 557 public auto skipWs(P)(P parser) 558 if(isParser!P && is(ElementType!(ParserStream!P) : dchar)){ 559 return SkipWs!P(parser); 560 } 561 562 /// 563 unittest { 564 import pry.atoms, pry.stream; 565 alias S = SimpleStream!string; 566 with(parsers!S) { 567 auto normal = range!('0', '9').rep; 568 auto skipping = range!('0', '9').skipWs.rep; 569 auto s1 = "0 9 1".stream; 570 string r; 571 S.Error err; 572 assert(skipping.parse(s1, r, err)); 573 assert(s1.empty); 574 assert(r == "0 9 1"); 575 576 auto s2 = "0 9 1".stream; 577 assert(normal.parse(s2, r, err)); 578 assert(!s2.empty); 579 assert(r == "0"); 580 assert(s2.front == ' '); 581 } 582 } 583 584 struct AAImpl(size_t minTimes, size_t maxTimes, P) { 585 private P parser; 586 alias Stream = ParserStream!P; 587 alias Pair = ParserValue!P; 588 alias Key = typeof(Pair.init[0]); 589 alias Value = typeof(Pair.init[1]); 590 591 bool parse(ref Stream stream, ref Value[Key] aa, ref Stream.Error err) const { 592 auto start = stream.mark; 593 Pair tmp; 594 size_t i = 0; 595 for(; i<minTimes; i++) { 596 if(!parser.parse(stream, tmp, err)){ 597 stream.restore(start); 598 return false; 599 } 600 aa[tmp[0]] = tmp[1]; 601 } 602 for(; i<maxTimes; i++){ 603 if(!parser.parse(stream, tmp, err)) break; 604 aa[tmp[0]] = tmp[1]; 605 } 606 return true; 607 } 608 } 609 610 /++ 611 Apply parser of key-value pairs (2-tuples) for minTimes times or more up to maxTimes, 612 construct an AA out of the results of parsing. 613 +/ 614 public auto aa(size_t minTimes=1, size_t maxTimes=size_t.max, P)(P parser) 615 if(isParser!P && isTuple!(ParserValue!P) && ParserValue!P.length == 2){ 616 return AAImpl!(minTimes, maxTimes, P)(parser); 617 } 618 619 /// 620 unittest { 621 import pry.atoms, pry.stream; 622 import std.conv; 623 alias S = SimpleStream!string; 624 with(parsers!S) { 625 auto p = seq(range!('a', 'z').rep.skipWs, stk!'=', range!('0', '9').rep.skipWs) 626 .map!(x => tuple(x[0], to!int(x[2]))).aa; 627 auto s = "a = 1 b = 3 temp = 36".stream; 628 int[string] table; 629 S.Error err; 630 assert(p.parse(s, table, err)); 631 assert(s.empty); 632 assert(table["a"] == 1); 633 assert(table["b"] == 3); 634 assert(table["temp"] == 36); 635 } 636 } 637 638 struct Optional(P) { 639 private P parser; 640 alias Stream = ParserStream!P; 641 alias Value = ParserValue!P; 642 643 bool parse(ref Stream stream, ref Nullable!Value option, ref Stream.Error err) const { 644 Value tmp; 645 if(parser.parse(stream, tmp, err)) { 646 option = tmp; 647 return true; 648 } 649 option.nullify(); 650 return true; 651 } 652 } 653 654 /// Try to apply `parser`, produce null on failure. 655 public auto optional(P)(P parser) 656 if(isParser!P){ 657 return Optional!P(parser); 658 } 659 660 /// 661 unittest { 662 import pry.atoms, pry.stream; 663 alias S = SimpleStream!string; 664 with(parsers!S){ 665 auto p = range!('a', 'z').optional; 666 auto s = "a".stream; 667 Nullable!dchar c; 668 S.Error err; 669 assert(p.parse(s, c, err)); 670 assert(c == 'a'); 671 assert(s.empty); 672 assert(p.parse(s, c, err)); 673 assert(c.isNull); 674 } 675 } 676 677 struct Slice(P){ 678 private P parser; 679 alias Stream = ParserStream!P; 680 alias Value = ParserValue!P; 681 682 bool parse(ref Stream stream, ref Stream.Range range, ref Stream.Error err) const { 683 auto start = stream.mark(); 684 Value val; 685 if(parser.parse(stream, val, err)) { 686 range = stream.slice(start); 687 return true; 688 } 689 return false; 690 } 691 } 692 693 /// Apply parser, discard the value and instead produce the slice of parsed stream. 694 public auto slice(P)(P parser) 695 if(isParser!P){ 696 return Slice!P(parser); 697 } 698 699 /// 700 unittest { 701 import pry.atoms, pry.stream, std.conv; 702 alias S = SimpleStream!string; 703 with(parsers!S){ 704 auto p = seq(tk!'-'.optional, range!('0', '9').rep) 705 .slice 706 .map!(x => to!int(x)); 707 auto s = "-13".stream; 708 int value; 709 S.Error err; 710 assert(p.parse(s, value, err)); 711 assert(value == -13); 712 s = "5".stream; 713 assert(p.parse(s, value, err)); 714 assert(value == 5); 715 } 716 } 717 718 struct Delimited(Item, Delimiter){ 719 private Item item; 720 private Delimiter delimiter; 721 alias Stream = ParserStream!Item; 722 alias Value = ParserValue!Item; 723 724 bool parse(ref Stream stream, ref Value[] values, ref Stream.Error err) const { 725 import std.array; 726 values = Value[].init; 727 bool first = true; 728 Value val; 729 ParserValue!Delimiter tmp; 730 auto app = appender!(Value[]); 731 auto m = stream.mark(); 732 if (item.parse(stream, val, err)){ 733 app.put(val); 734 for(;;) { 735 if(!delimiter.parse(stream, tmp, err)) break; 736 if (item.parse(stream, val, err)) 737 app.put(val); 738 else { 739 stream.restore(m); 740 return false; 741 } 742 } 743 } 744 values = app.data(); 745 return true; 746 } 747 } 748 749 /// Parse a sequence of `item` delimited by `del` 750 public auto delimited(Item, Delimiter)(Item item, Delimiter del) 751 if(isParser!Item && isParser!Delimiter){ 752 return Delimited!(Item, Delimiter)(item, del); 753 } 754 755 /// 756 unittest{ 757 import pry.atoms, pry.stream, std.conv; 758 alias S = SimpleStream!string; 759 with(parsers!S){ 760 auto p = delimited( 761 range!('0', '9').rep.skipWs.map!(x => to!int(x)), 762 stk!',' 763 ); 764 auto s = " 2, 4,5".stream; 765 int[] values; 766 S.Error err; 767 assert(p.parse(s, values, err)); 768 assert(values == [2, 4, 5]); 769 s = "10".stream; 770 assert(p.parse(s, values, err)); 771 assert(values == [10]); 772 s = "".stream; 773 assert(p.parse(s, values, err)); 774 assert(values == []); 775 } 776 } 777 778 struct Lookahead(Parser) { 779 Parser parser; 780 alias Stream = ParserStream!Parser; 781 alias Value = ParserValue!Parser; 782 783 bool parse(ref Stream stream, ref Nothing _, ref Stream.Error err) const { 784 auto m = stream.mark(); 785 Value val; 786 bool r = parser.parse(stream, val, err); 787 stream.restore(m); 788 return r; 789 } 790 } 791 792 /// Use Parser as lookahead, doesn't consume input stream. 793 auto lookahead(Parser)(Parser p) 794 if(isParser!Parser){ 795 return Lookahead!Parser(p); 796 } 797 798 struct NegLookahead(Parser) { 799 Parser parser; 800 alias Stream = ParserStream!Parser; 801 alias Value = ParserValue!Parser; 802 803 bool parse(ref Stream stream, ref Nothing _, ref Stream.Error err) const { 804 auto m = stream.mark(); 805 Value val; 806 bool r = !parser.parse(stream, val, err); 807 stream.restore(m); 808 return r; 809 } 810 } 811 812 /// Use Parser as lookahead, doesn't consume input stream. 813 auto negLookahead(Parser)(Parser p) 814 if(isParser!Parser){ 815 return NegLookahead!Parser(p); 816 } 817 818 unittest { 819 import pry.atoms, pry.stream, std.conv; 820 alias S = SimpleStream!string; 821 with(parsers!S){ 822 auto p1 = seq(tk!'a', tk!'b'.lookahead); 823 auto s1 = "ab".stream; 824 Tuple!dchar val; 825 S.Error err; 826 assert(p1.parse(s1, val, err)); 827 assert(val[0] == 'a'); 828 auto p2 = seq(tk!'a', tk!'b'.negLookahead); 829 s1 = "ab".stream; 830 assert(!p2.parse(s1, val, err)); 831 auto s2 = "ac".stream; 832 assert(p2.parse(s2, val, err)); 833 } 834 }