1 module ctpg.parsers;
2 
3 import std.algorithm : count;
4 import std.array : empty;
5 import std.conv : to;
6 import std.exception : assertThrown;
7 import std.traits : Unqual, CommonType, isArray, isSomeString, isSomeChar;
8 import std.range : ElementEncodingType, isRandomAccessRange;
9 
10 import ctpg : parse;
11 
12 import ctpg.for_unittest;
13 import ctpg.parse_result : ParseResult, getParseResultType;
14 import ctpg.parser_kind  : ParserKind, ParserKinds;
15 import ctpg.input        : Input;
16 import ctpg.caller       : Caller;
17 import ctpg.none         : None;
18 import ctpg.macro_       : MAKE_RESULT;
19 import ctpg.unsupported_input_type_exception : UnsupportedInputTypeException;
20 
21 import combinators = ctpg.combinators;
22 
23 import selflinoin : makeCompilationErrorMessage;
24 
25 import compile_time_unittest : enableCompileTimeUnittest;
26 mixin enableCompileTimeUnittest;
27 
28 
29 template success()
30 {
31     template build(alias kind, SrcType)
32     {
33         mixin MAKE_RESULT!q{ None };
34 
35         Result parse(Input!SrcType input, in Caller caller)
36         {
37             Result result;
38 
39             result.match = true;
40             result.nextInput = input;
41 
42             return result;
43         }
44     }
45 }
46 
47 debug(ctpg) unittest
48 {
49     foreach(conv; convs) foreach(kind; ParserKinds)
50     {
51         with(parse!(success!(), kind)(conv!"hoge"))
52         {
53             assert(match              == true);
54             assert(nextInput.source   == conv!"hoge");
55             assert(nextInput.position == 0);
56             assert(nextInput.line     == 0);
57         }
58 
59         with(parse!(success!(), kind)(conv!""))
60         {
61             assert(match              == true);
62             assert(nextInput.source   == conv!"");
63             assert(nextInput.position == 0);
64             assert(nextInput.line     == 0);
65         }
66     }
67 }
68 
69 
70 template eof()
71 {
72     template build(alias kind, SrcType)
73     {
74         mixin MAKE_RESULT!q{ None };
75 
76         Result parse(Input!SrcType input, in Caller caller)
77         {
78             Result result;
79 
80             if(input.source.empty)
81             {
82                 result.match     = true;
83                 result.nextInput = input;
84             }
85             else
86             {
87                 static if(kind.hasError)
88                 {
89                     result.error.msg      = "EOF expected";
90                     result.error.position = input.position;
91                 }
92             }
93 
94             return result;
95         }
96     }
97 }
98 
99 debug(ctpg) unittest
100 {
101     foreach(conv; convs) foreach(kind; ParserKinds)
102     {
103         with(parse!(eof!(), kind)(conv!""))
104         {
105             assert(match              == true);
106             assert(nextInput.source   == conv!"");
107             assert(nextInput.position == 0);
108             assert(nextInput.line     == 0);
109         }
110 
111         with(parse!(eof!(), kind)(conv!"hoge"))
112         {
113                                      assert(match          == false);
114             static if(kind.hasError) assert(error.msg      == "EOF expected");
115             static if(kind.hasError) assert(error.position == 0);
116         }
117 
118         assertThrown!UnsupportedInputTypeException(parse!(string_!"hoge", kind)([0, 1]));
119     }
120 }
121 
122 
123 template spaces()
124 {
125     template build(alias kind, SrcType)
126     {
127         mixin MAKE_RESULT!q{ None };
128 
129         Result parse(Input!SrcType input, in Caller caller)
130         {
131             return combinators.none!
132             (
133                 combinators.more0!
134                 (
135                     combinators.choice!
136                     (
137                         string_!" ",
138                         string_!"\n",
139                         string_!"\t",
140                         string_!"\r",
141                         string_!"\f"
142                     )
143                 )
144             ).build!(kind, SrcType).parse(input, caller);
145         }
146     }
147 }
148 
149 alias skip = spaces;
150 
151 debug(ctpg) unittest
152 {
153     foreach(conv; convs) foreach(kind; ParserKinds)
154     {
155         with(parse!(spaces!())(conv!" \n\t\r\f"))
156         {
157             assert(match            == true);
158             assert(nextInput.source == conv!"");
159         }
160 
161         with(parse!(spaces!())(conv!" \n\ta\r\f"))
162         {
163             assert(match            == true);
164             assert(nextInput.source == conv!"a\r\f");
165         }
166 
167         with(parse!(spaces!())(conv!""))
168         {
169             assert(match            == true);
170             assert(nextInput.source == conv!"");
171         }
172     }
173 }
174 
175 
176 template string_(alias string str)
177 {
178     template build(alias kind, SrcType)
179     {
180         static if(is(Unqual!(ElementEncodingType!SrcType) ==  char)) enum adaptedStr =              str;
181         static if(is(Unqual!(ElementEncodingType!SrcType) == wchar)) enum adaptedStr = cast(wstring)str;
182         static if(is(Unqual!(ElementEncodingType!SrcType) == dchar)) enum adaptedStr = cast(dstring)str;
183 
184         enum lines = str.count('\n');
185         enum errorMsg = "'" ~ str ~ "' expected";
186 
187         mixin MAKE_RESULT!q{ string };
188 
189         static if(isSomeString!SrcType)
190         {
191             Result parse(Input!SrcType input, in Caller caller)
192             {
193                 Result result;
194 
195                 if(input.source.length >= adaptedStr.length && input.source[0..adaptedStr.length] == adaptedStr)
196                 {
197                     result.match              = true;
198                     result.nextInput.source   = input.source[str.length..$];
199                     result.nextInput.position = input.position + adaptedStr.length;
200                     result.nextInput.line     = input.line + lines;
201 
202                     static if(kind.hasValue)
203                     {
204                         result.value = str;
205                     }
206                 }
207                 else
208                 {
209                     static if(kind.hasError)
210                     {
211                         result.error.msg      = errorMsg;
212                         result.error.position = input.position;
213                     }
214                 }
215 
216                 return result;
217             }
218         }
219         else static if(isSomeChar!(ElementEncodingType!SrcType))
220         {
221             Result parse(Input!SrcType input, in Caller caller)
222             {
223                 Result result;
224 
225                 foreach(c; adaptedStr)
226                 {
227                     if(input.source.empty || c != input.source.front)
228                     {
229                         static if(kind.hasError)
230                         {
231                             result.error.msg      = errorMsg;
232                             result.error.position = input.position;
233                         }
234 
235                         return result;
236                     }
237                     else
238                     {
239                         input.source.popFront();
240                     }
241                 }
242 
243                 result.match              = true;
244                 result.nextInput.source   = input.source;
245                 result.nextInput.position = input.position + adaptedStr.length;
246                 result.nextInput.line     = input.line + lines;
247 
248                 static if(kind.hasValue)
249                 {
250                     result.value = str;
251                 }
252 
253                 return result;
254             }
255         }
256         else
257         {
258             Result parse(Input!SrcType input, in Caller caller)
259             {
260                 throw new UnsupportedInputTypeException("Input should be some string or a range whose elemement type is some char");
261             }
262         }
263     }
264 }
265 
266 debug(ctpg) unittest
267 {
268     foreach(conv; convs) foreach(kind; ParserKinds)
269     {
270         with(parse!(string_!"hoge", kind)(conv!"hogehoge"))
271         {
272                                      assert(match              == true);
273             static if(kind.hasValue) assert(value              == "hoge");
274                                      assert(nextInput.source   == conv!"hoge");
275                                      assert(nextInput.position == 4);
276                                      assert(nextInput.line     == 0);
277         }
278 
279         with(parse!(string_!"\n\nhoge", kind)(conv!"\n\nhogehoge"))
280         {
281                                      assert(match              == true);
282             static if(kind.hasValue) assert(value              == "\n\nhoge");
283                                      assert(nextInput.source   == conv!"hoge");
284                                      assert(nextInput.position == 6);
285                                      assert(nextInput.line     == 2);
286         }
287 
288         with(parse!(string_!"hoge", kind)(conv!"fuga"))
289         {
290                                      assert(match          == false);
291             static if(kind.hasError) assert(error.msg      == "'hoge' expected");
292             static if(kind.hasError) assert(error.position == 0);
293         }
294 
295         assertThrown!UnsupportedInputTypeException(parse!(string_!"hoge", kind)([0, 1]));
296     }
297 }
298 
299 
300 template charRange(dchar begin, dchar end, size_t line = 0, string file = "")
301 {
302     static if(begin > end)
303     {
304         pragma(msg, makeCompilationErrorMessage("Error: Invalid char range", file, line));
305         static assert(false);
306     }
307 
308     template build(alias kind, SrcType)
309     {
310         dchar decode(ref SrcType input, ref size_t advance)
311         {
312             dchar result;
313 
314             static if(isArray!SrcType || isRandomAccessRange!SrcType)
315             {
316                 static if(is(Unqual!(ElementEncodingType!SrcType) == char))
317                 {
318                     if(!(input[0] & 0b_1000_0000))
319                     {
320                         result  = input[0];
321                         input   = input[1..$];
322                         advance = 1;
323                     }
324                     else if(!(input[0] & 0b_0010_0000))
325                     {
326                         result  = ((input[0] & 0b_0001_1111) << 6) | (input[1] & 0b_0011_1111);
327                         input   = input[2..$];
328                         advance = 2;
329                     }
330                     else if(!(input[0] & 0b_0001_0000))
331                     {
332                         result  = ((input[0] & 0b_0000_1111) << 12) | ((input[1] & 0b_0011_1111) << 6) | (input[2] & 0b_0011_1111);
333                         input   = input[3..$];
334                         advance = 3;
335                     }
336                     else
337                     {
338                         result  = ((input[0] & 0b_0000_0111) << 18) | ((input[1] & 0b_0011_1111) << 12) | ((input[2] & 0b_0011_1111) << 6) | (input[3] & 0b_0011_1111);
339                         input   = input[4..$];
340                         advance = 4;
341                     }
342                 }
343                 else static if(is(Unqual!(ElementEncodingType!SrcType) == wchar))
344                 {
345                     if(input[0] <= 0xD7FF || (0xE000 <= input[0] && input[0] < 0xFFFF))
346                     {
347                         result  = input[0];
348                         input   = input[1..$];
349                         advance = 1;
350                     }
351                     else
352                     {
353                         result  = (input[0] & 0b_0000_0011_1111_1111) * 0x400 + (input[1] & 0b_0000_0011_1111_1111) + 0x10000;
354                         input   = input[2..$];
355                         advance = 2;
356                     }
357                 }
358                 else static if(is(Unqual!(ElementEncodingType!SrcType) == dchar))
359                 {
360                     result  = input[0];
361                     input   = input[1..$];
362                     advance = 1;
363                 }
364             }
365             else
366             {
367                 static if(is(Unqual!(ElementEncodingType!SrcType) == char))
368                 {
369                     if(!(input.front & 0b_1000_0000))
370                     {
371                         result = input.front;
372                         input.popFront();
373                         advance = 1;
374                     }
375                     else if(!(input.front & 0b_0010_0000))
376                     {
377                         result = input.front & 0b_0001_1111;
378                         result <<= 6;
379                         input.popFront();
380                         result |= input.front & 0b_0011_1111;
381                         input.popFront();
382                         advance = 2;
383                     }
384                     else if(!(input.front & 0b_0001_0000))
385                     {
386                         result = input.front & 0b_0000_1111;
387                         result <<= 6;
388                         input.popFront();
389                         result |= input.front & 0b_0011_1111;
390                         result <<= 6;
391                         input.popFront();
392                         result |= input.front & 0b_0011_1111;
393                         input.popFront;
394                         advance = 3;
395                     }
396                     else
397                     {
398                         result = input.front & 0b_0000_0111;
399                         result <<= 6;
400                         input.popFront();
401                         result |= input.front & 0b_0011_1111;
402                         result <<= 6;
403                         input.popFront();
404                         result |= input.front & 0b_0011_1111;
405                         result <<= 6;
406                         input.popFront();
407                         result |= input.front & 0b_0011_1111;
408                         input.popFront();
409                         advance = 4;
410                     }
411                 }
412                 else static if(is(Unqual!(ElementEncodingType!SrcType) == wchar))
413                 {
414                     if(input.front <= 0xD7FF || (0xE000 <= input.front && input.front < 0xFFFF))
415                     {
416                         result = input.front;
417                         input.popFront();
418                         advance = 1;
419                     }
420                     else
421                     {
422                         result = (input.front & 0b_0000_0011_1111_1111) * 0x400;
423                         input.popFront();
424                         result += (input.front & 0b_0000_0011_1111_1111) + 0x10000;
425                         input.popFront();
426                         advance = 2;
427                     }
428                 }
429                 else static if(is(Unqual!(ElementEncodingType!SrcType) == dchar))
430                 {
431                     result = input.front;
432                     input.popFront();
433                     advance = 1;
434                 }
435             }
436 
437             return result;
438         }
439 
440         static if(begin == dchar.min && end == dchar.max)
441         {
442             enum errorMsg = "any char expected";
443         }
444         else
445         {
446             enum errorMsg = "'" ~ begin.to!string() ~ " ~ " ~ end.to!string() ~ "' expected";
447         }
448 
449         mixin MAKE_RESULT!q{ dchar };
450 
451         static if(isSomeChar!(ElementEncodingType!SrcType))
452         {
453             Result parse(Input!SrcType input, in Caller caller)
454             {
455                 Result result;
456 
457                 if(input.source.empty)
458                 {
459                     static if(kind.hasError)
460                     {
461                         result.error.msg      = errorMsg;
462                         result.error.position = input.position;
463                     }
464                 }
465                 else
466                 {
467                     size_t advance;
468                     dchar c = decode(input.source, advance);
469 
470                     if(begin <= c && c <= end)
471                     {
472                         result.match              = true;
473                         result.nextInput.source   = input.source;
474                         result.nextInput.position = input.position + advance;
475                         result.nextInput.line     = input.line + (c == '\n' ? 1 : 0);
476 
477                         static if(kind.hasValue)
478                         {
479                             result.value = c;
480                         }
481                     }
482                     else
483                     {
484                         static if(kind.hasError)
485                         {
486                             result.error.msg      = errorMsg;
487                             result.error.position = input.position;
488                         }
489                     }
490                 }
491 
492                 return result;
493             }
494         }
495         else
496         {
497             Result parse(Input!SrcType input, in Caller caller)
498             {
499                 throw new UnsupportedInputTypeException("Input should be some string or a range whose elemement type is some char");
500             }
501         }
502     }
503 }
504 
505 template anyChar(size_t line = 0, string file = "")
506 {
507     alias anyChar = charRange!(dchar.min, dchar.max, line, file);
508 }
509 
510 debug(ctpg) unittest
511 {
512     foreach(conv; convs) foreach(kind; ParserKinds)
513     {
514         with(parse!(charRange!('a', 'z'), kind)(conv!"hoge"))
515         {
516                                      assert(match              == true);
517             static if(kind.hasValue) assert(value              == 'h');
518                                      assert(nextInput.source   == conv!"oge");
519                                      assert(nextInput.position == 1);
520                                      assert(nextInput.line     == 0);
521         }
522 
523         with(parse!(charRange!(dchar.min, dchar.max), kind)(conv!"\nhoge"))
524         {
525                                      assert(match              == true);
526             static if(kind.hasValue) assert(value              == '\n');
527                                      assert(nextInput.source   == conv!"hoge");
528                                      assert(nextInput.position == 1);
529                                      assert(nextInput.line     == 1);
530         }
531 
532         with(parse!(charRange!('a', 'z'), kind)(conv!""))
533         {
534                                      assert(match          == false);
535             static if(kind.hasError) assert(error.msg      == "'a ~ z' expected");
536             static if(kind.hasError) assert(error.position == 0);
537         }
538 
539         with(parse!(charRange!('a', 'z'), kind)(conv!"鬱"))
540         {
541                                      assert(match          == false);
542             static if(kind.hasError) assert(error.msg      == "'a ~ z' expected");
543             static if(kind.hasError) assert(error.position == 0);
544         }
545 
546         assertThrown!UnsupportedInputTypeException(parse!(charRange!('a', 'z'), kind)([1, 0]));
547 
548         debug(ctpgCompilesTest)
549         {
550             static assert(!__traits(compiles, parse!(charRange!('z', 'a'), kind)(conv!"")));
551         }
552     }
553 }
554 
555 template getCallerLine()
556 {
557     template build(alias kind, SrcType)
558     {
559         mixin MAKE_RESULT!q{ size_t };
560 
561         Result parse(Input!SrcType input, in Caller caller)
562         {
563             Result result;
564 
565             result.match = true;
566             result.nextInput = input;
567 
568             static if(kind.hasValue)
569             {
570                 result.value = caller.line;
571             }
572 
573             return result;
574         }
575     }
576 }
577 
578 debug(ctpg) unittest
579 {
580     foreach(conv; convs) foreach(kind; ParserKinds)
581     {
582         with(parse!(getCallerLine!(), kind)(conv!""))
583         {
584                                      assert(match              == true);
585                                      assert(nextInput.source   == conv!"");
586                                      assert(nextInput.position == 0);
587                                      assert(nextInput.line     == 0);
588             static if(kind.hasValue) assert(value              == __LINE__ - 6);
589         }
590     }
591 }
592 
593 
594 template getCallerFile()
595 {
596     template build(alias kind, SrcType)
597     {
598         mixin MAKE_RESULT!q{ string };
599 
600         Result parse(Input!SrcType input, in Caller caller)
601         {
602             Result result;
603 
604             result.match = true;
605             result.nextInput = input;
606 
607             static if(kind.hasValue)
608             {
609                 result.value = caller.file;
610             }
611 
612             return result;
613         }
614     }
615 }
616 
617 debug(ctpg) unittest
618 {
619     foreach(conv; convs) foreach(kind; ParserKinds)
620     {
621         with(parse!(getCallerFile!(), kind)(conv!""))
622         {
623                                      assert(match              == true);
624                                      assert(nextInput.source   == conv!"");
625                                      assert(nextInput.position == 0);
626                                      assert(nextInput.line     == 0);
627             static if(kind.hasValue) assert(value              == __FILE__);
628         }
629     }
630 }
631 
632 
633 template getLine()
634 {
635     template build(alias kind, SrcType)
636     {
637         mixin MAKE_RESULT!q{ size_t };
638 
639         Result parse(Input!SrcType input, in Caller caller)
640         {
641             Result result;
642 
643             result.match = true;
644             result.nextInput = input;
645 
646             static if(kind.hasValue)
647             {
648                 result.value = input.line;
649             }
650 
651             return result;
652         }
653     }
654 }
655 
656 debug(ctpg) unittest
657 {
658     foreach(conv; convs) foreach(kind; ParserKinds)
659     {
660         with(parse!(getLine!(), kind)(conv!""))
661         {
662                                      assert(match              == true);
663                                      assert(nextInput.source   == conv!"");
664                                      assert(nextInput.position == 0);
665                                      assert(nextInput.line     == 0);
666             static if(kind.hasValue) assert(value              == 0);
667         }
668     }
669 }