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 	Values value;
404 
405 	this(P parsers){
406 		this.parsers = parsers;
407 	}
408 
409 	bool parse(ref Stream stream, ref Value value, ref Stream.Error err) const {
410 		Stream.Error current, deepest;
411 		bool ret = false;
412 		foreach(i, ref p; parsers) {
413 			ParserValue!(typeof(p)) suffixValue;
414 			static if(i == 0){
415 				if(p.parse(stream, suffixValue, deepest)){
416 					value = suffixValue;
417 					return true;
418 				}
419 			}
420 			else {
421 				if(p.parse(stream, suffixValue, current)){
422 					value = suffixValue;
423 					return true;
424 				}
425 				// pick the deeper error
426 				if(deepest.location < current.location){
427 					deepest = current;
428 				}
429 			}
430 		}
431 L_end:
432 		err = deepest;
433 		return ret;
434 	}
435 }
436 
437 /// Try each of provided parsers until one succeeds.
438 public auto any(P...)(P parsers)
439 if(allSatisfy!(isParser, P)){
440 	return Any!P(parsers);
441 }
442 
443 ///
444 unittest {
445 	import pry.atoms, pry.stream;
446 	import std.range.primitives, std.conv, std.variant;
447 	alias S = SimpleStream!string;
448 	with(parsers!S) {
449 		auto digits = range!('0', '9').rep.map!(x => x.to!int);
450 		static assert(isParser!(typeof(digits)));
451 		auto parser = any(tk!'a', digits);
452 		S s = "10a";
453 		S.Error err;
454 		Algebraic!(dchar, int) value;
455 		assert(parser.parse(s, value, err));
456 		assert(value == 10);
457 		assert(parser.parse(s, value, err));
458 		assert(value == cast(dchar)'a');
459 		assert(s.empty);
460 	}
461 }
462 
463 unittest {
464 	import pry.atoms, pry.stream;
465 	alias S = SimpleStream!string;
466 	with(parsers!S) {
467 		auto e = dynamic!int;
468 		e = any(
469 			seq(tk!'0', e).map!(x => 1),
470 			tk!'1'.map!(x => 0)
471 		);
472 		S.Error err;
473 		int val;
474 		S s = S("0001");
475 		assert(e.parse(s, val, err));
476 	}
477 }
478 
479 unittest {
480 	import pry.atoms, pry.stream;
481 	import std.typecons;
482 	alias S = SimpleStream!string;
483 	with(parsers!S) {
484 		auto p = any(
485 			seq(tk!'0', tk!'0'),
486 			seq(tk!'1', tk!'1')
487 		);
488 		S s = "01".stream;
489 		Tuple!(dchar, dchar) value;
490 		S.Error err;
491 		assert(!p.parse(s, value, err));
492 		assert(err.location == 1);
493 	}
494 }
495 
496 
497 struct SkipWs(P) {
498 	private P parser;
499 	alias Stream = ParserStream!P;
500 	alias Value = ParserValue!P;
501 
502 	bool parse(ref Stream s, ref Value v, ref Stream.Error err) const {
503 		import std.uni;
504 		while(!s.empty && isWhite(s.front)) s.popFront();
505 		return parser.parse(s, v, err);
506 	}
507 }
508 
509 /// Skip whitespace at front then apply the `parser`.
510 public auto skipWs(P)(P parser)
511 if(isParser!P && is(ElementType!(ParserStream!P) : dchar)){
512 	return SkipWs!P(parser);
513 }
514 
515 ///
516 unittest {
517 	import pry.atoms, pry.stream;
518 	alias S = SimpleStream!string;
519 	with(parsers!S) {
520 		auto normal = range!('0', '9').rep;
521 		auto skipping = range!('0', '9').skipWs.rep;
522 		auto s1 = "0 9 1".stream;
523 		string r;
524 		S.Error err;
525 		assert(skipping.parse(s1, r, err));
526 		assert(s1.empty);
527 		assert(r == "0 9 1");
528 
529 		auto s2 = "0 9 1".stream;
530 		assert(normal.parse(s2, r, err));
531 		assert(!s2.empty);
532 		assert(r == "0");
533 		assert(s2.front == ' ');
534 	}
535 }
536 
537 struct AAImpl(size_t minTimes, size_t maxTimes, P) {
538 	private P parser;
539 	alias Stream = ParserStream!P;
540 	alias Pair = ParserValue!P;
541 	alias Key = typeof(Pair.init[0]);
542 	alias Value = typeof(Pair.init[1]);
543 
544 	bool parse(ref Stream stream, ref Value[Key] aa, ref Stream.Error err) const {
545 		auto start = stream.mark;
546 		Pair tmp;
547 		size_t i = 0;
548 		for(; i<minTimes; i++) {
549 			if(!parser.parse(stream, tmp, err)){
550 				stream.restore(start);
551 				return false;
552 			}
553 			aa[tmp[0]] = tmp[1];
554 		}
555 		for(; i<maxTimes; i++){
556 			if(!parser.parse(stream, tmp, err)) break;
557 			aa[tmp[0]] = tmp[1];
558 		}
559 		return true;
560 	}
561 }
562 
563 /++
564 	Apply parser of key-value pairs (2-tuples) for minTimes times or more up to maxTimes,
565 	construct an AA out of the results of parsing. 
566 +/
567 public auto aa(size_t minTimes=1, size_t maxTimes=size_t.max, P)(P parser)
568 if(isParser!P && isTuple!(ParserValue!P) && ParserValue!P.length == 2){
569 	return AAImpl!(minTimes, maxTimes, P)(parser);
570 }
571 
572 ///
573 unittest {
574 	import pry.atoms, pry.stream;
575 	import std.conv;
576 	alias S = SimpleStream!string;
577 	with(parsers!S) {
578 		auto p = seq(range!('a', 'z').rep.skipWs, stk!'=', range!('0', '9').rep.skipWs)
579 				.map!(x => tuple(x[0], to!int(x[2]))).aa;
580 		auto s = "a = 1 b = 3 temp = 36".stream;
581 		int[string] table;
582 		S.Error err;
583 		assert(p.parse(s, table, err));
584 		assert(s.empty);
585 		assert(table["a"] == 1);
586 		assert(table["b"] == 3);
587 		assert(table["temp"] == 36);
588 	}
589 }
590 
591 struct Optional(P) {
592 	private P parser;
593 	alias Stream = ParserStream!P;
594 	alias Value = ParserValue!P;
595 
596 	bool parse(ref Stream stream, ref Nullable!Value option, ref Stream.Error err) const {
597 		Value tmp;
598 		if(parser.parse(stream, tmp, err)) {
599 			option = tmp;
600 			return true;
601 		}
602 		option.nullify();
603 		return true;
604 	}
605 }
606 
607 /// Try to apply `parser`, produce null on failure.
608 public auto optional(P)(P parser)
609 if(isParser!P){
610 	return Optional!P(parser);
611 }
612 
613 ///
614 unittest {
615 	import pry.atoms, pry.stream;
616 	alias S = SimpleStream!string;
617 	with(parsers!S){
618 		auto p = range!('a', 'z').optional;
619 		auto s = "a".stream;
620 		Nullable!dchar c;
621 		S.Error err;
622 		assert(p.parse(s, c, err));
623 		assert(c == 'a');
624 		assert(s.empty);
625 		assert(p.parse(s, c, err));
626 		assert(c.isNull);
627 	}
628 }
629 
630 struct Slice(P){
631 	private P parser;
632 	alias Stream = ParserStream!P;
633 	alias Value = ParserValue!P;
634 
635 	bool parse(ref Stream stream, ref Stream.Range range, ref Stream.Error err) const {
636 		auto start = stream.mark();
637 		Value val;
638 		if(parser.parse(stream, val, err)) {
639 			range = stream.slice(start);
640 			return true;
641 		}
642 		return false;
643 	}
644 }
645 
646 /// Apply parser, discard the value and instead produce the slice of parsed stream.
647 public auto slice(P)(P parser)
648 if(isParser!P){
649 	return Slice!P(parser);
650 }
651 
652 ///
653 unittest {
654 	import pry.atoms, pry.stream, std.conv;
655 	alias S = SimpleStream!string;
656 	with(parsers!S){
657 		auto p = seq(tk!'-'.optional, range!('0', '9').rep)
658 						.slice
659 						.map!(x => to!int(x));
660 		auto s = "-13".stream;
661 		int value;
662 		S.Error err;
663 		assert(p.parse(s, value, err));
664 		assert(value == -13);
665 		s = "5".stream;
666 		assert(p.parse(s, value, err));
667 		assert(value == 5);
668 	}
669 }
670 
671 struct Delimited(Item, Delimiter){
672 	private Item item;
673 	private Delimiter delimiter;
674 	alias Stream = ParserStream!Item;
675 	alias Value = ParserValue!Item;
676 
677 	bool parse(ref Stream stream, ref Value[] values, ref Stream.Error err) const {
678 		import std.array;
679 		values = Value[].init;
680 		bool first = true;
681 		Value val;
682 		ParserValue!Delimiter tmp;
683 		auto app = appender!(Value[]);
684 		auto m = stream.mark();
685 		if (item.parse(stream, val, err)){
686 			app.put(val);
687 			for(;;) {
688 				if(!delimiter.parse(stream, tmp, err)) break;
689 				if (item.parse(stream, val, err))
690 					app.put(val);
691 				else {
692 					stream.restore(m);
693 					return false;
694 				}
695 			}
696 		}
697 		values = app.data();
698 		return true;
699 	}
700 }
701 
702 /// Parse a sequence of `item` delimited by `del`
703 public auto delimited(Item, Delimiter)(Item item, Delimiter del)
704 if(isParser!Item && isParser!Delimiter){
705 	return Delimited!(Item, Delimiter)(item, del);
706 }
707 
708 ///
709 unittest{
710 	import pry.atoms, pry.stream, std.conv;
711 	alias S = SimpleStream!string;
712 	with(parsers!S){
713 		auto p = delimited(
714 			range!('0', '9').rep.skipWs.map!(x => to!int(x)),
715 			stk!','
716 		);
717 		auto s = " 2, 4,5".stream;
718 		int[] values;
719 		S.Error err;
720 		assert(p.parse(s, values, err));
721 		assert(values == [2, 4, 5]);
722 		s = "10".stream;
723 		assert(p.parse(s, values, err));
724 		assert(values == [10]);
725 		s = "".stream;
726 		assert(p.parse(s, values, err));
727 		assert(values == []);
728 	}
729 }
730 
731 struct Lookahead(Parser) {
732 	Parser parser;
733 	alias Stream = ParserStream!Parser;
734 	alias Value = ParserValue!Parser;
735 
736 	bool parse(ref Stream stream, ref Nothing _, ref Stream.Error err) const {
737 		auto m = stream.mark();
738 		Value val;
739 		bool r = parser.parse(stream, val, err);
740 		stream.restore(m);
741 		return r;
742 	}
743 }
744 
745 /// Use Parser as lookahead, doesn't consume input stream. 
746 auto lookahead(Parser)(Parser p)
747 if(isParser!Parser){
748 	return Lookahead!Parser(p);
749 }
750 
751 struct NegLookahead(Parser) {
752 	Parser parser;
753 	alias Stream = ParserStream!Parser;
754 	alias Value = ParserValue!Parser;
755 
756 	bool parse(ref Stream stream, ref Nothing _, ref Stream.Error err) const {
757 		auto m = stream.mark();
758 		Value val;
759 		bool r = !parser.parse(stream, val, err);
760 		stream.restore(m);
761 		return r;
762 	}
763 }
764 
765 /// Use Parser as lookahead, doesn't consume input stream. 
766 auto negLookahead(Parser)(Parser p)
767 if(isParser!Parser){
768 	return NegLookahead!Parser(p);
769 }
770 
771 unittest {
772 	import pry.atoms, pry.stream, std.conv;
773 	alias S = SimpleStream!string;
774 	with(parsers!S){
775 		auto p1 = seq(tk!'a', tk!'b'.lookahead);
776 		auto s1 = "ab".stream;
777 		Tuple!dchar val;
778 		S.Error err;
779 		assert(p1.parse(s1, val, err));
780 		assert(val[0] == 'a');
781 		auto p2 = seq(tk!'a', tk!'b'.negLookahead);
782 		s1 = "ab".stream;
783 		assert(!p2.parse(s1, val, err));
784 		auto s2 = "ac".stream;
785 		assert(p2.parse(s2, val, err));
786 	}	
787 }