1 /*
2  * Hunt - A high-level D Programming Language Web framework that encourages rapid development and clean, pragmatic design.
3  *
4  * Copyright (C) 2015-2019, HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.framework.view.Parser;
13 
14 public
15 {
16     import hunt.framework.view.Lexer : Position;
17 }
18 
19 private
20 {
21     import std.array : appender;
22     import std.conv : to;
23     import std.file : exists, read;
24     import std.path : dirName,absolutePath,dirSeparator;
25     import std.format: fmt = format;
26     import std.range;
27 
28     import hunt.logging;
29     import hunt.framework.view.ast;
30     import hunt.framework.view.Lexer;
31     import hunt.framework.view.Exception : TemplateParserException,
32                               assertTemplate = assertTemplateParser;
33 }
34 
35 
36 struct Parser(Lexer)
37 {
38     struct ParserState
39     {
40         Token[] tokens;
41         BlockNode[string] blocks;
42     }
43 
44     private
45     {
46         TemplateNode[string] _parsedFiles;
47 
48         Token[] _tokens;
49         BlockNode[string] _blocks;
50 
51         ParserState[] _states;
52     }
53 
54     void preprocess()
55     {
56         import std.string : stripRight, stripLeft;
57 
58         auto newTokens = appender!(Token[]);
59 
60         for (int i = 0; i < _tokens.length;)
61         {
62             if (i < _tokens.length - 1
63                 && _tokens[i] == Type.StmtBegin
64                 && _tokens[i+1] == Operator.Minus)
65             {
66                 newTokens.put(_tokens[i]);
67                 i += 2;
68             }
69             else if(i < _tokens.length - 1
70                     && _tokens[i] == Operator.Minus
71                     && _tokens[i+1] == Type.StmtEnd)
72             {
73                 newTokens.put(_tokens[i+1]);
74                 i += 2;
75             }
76             else if (_tokens[i] == Type.Raw)
77             {
78                 bool stripR = false;
79                 bool stripL = false;
80                 bool stripInlineR = false;
81 
82                 if (i >= 2 
83                     && _tokens[i-2] == Operator.Minus
84                     && _tokens[i-1] == Type.StmtEnd
85                     )
86                     stripL = true;
87 
88                 if (i < _tokens.length - 2 && _tokens[i+1] == Type.StmtBegin)
89                 {
90                     if (_tokens[i+2] == Operator.Minus)
91                         stripR = true;
92                     else if (_tokens[i+1].value == Lexer.stmtInline)
93                         stripInlineR = true;
94                 }
95 
96                 auto str = _tokens[i].value;
97                 str = stripR ? str.stripRight : str;
98                 str = stripL ? str.stripLeft : str;
99                 str = stripInlineR ? str.stripOnceRight : str;
100                 newTokens.put(Token(Type.Raw, str, _tokens[i].pos));
101                 i++;
102             }
103             else
104             {
105                 newTokens.put(_tokens[i]);
106                 i++;
107             }
108         }
109         _tokens = newTokens.data;
110     }
111 
112 
113     TemplateNode parseTree(string str, string filename ,string dirPath)
114     {
115         stashState();
116 
117         auto lexer = Lexer(str, filename);
118         auto newTokens = appender!(Token[]);
119 
120         while (true)
121         {
122             auto tkn = lexer.nextToken;
123             newTokens.put(tkn); 
124             if (tkn.type == Type.EOF)
125                 break;
126         }
127         _tokens = newTokens.data;
128 
129         preprocess();
130 
131         auto root = parseStatementBlock(dirPath);
132         auto blocks = _blocks;
133 
134         if (front.type != Type.EOF)
135             assertTemplate(0, "Expected EOF found %s(%s)".fmt(front.type, front.value), front.pos);
136 
137         popState();
138 
139         return new TemplateNode(Position(filename, 1, 1), root, blocks);
140     }
141 
142 
143     TemplateNode parseTreeFromFile(string path)
144     {
145         string dirPath = dirName(path) ~ dirSeparator;
146         // path = path.absolute(_path);
147         version(HUNT_FM_DEBUG) logDebug("parse file absolute path : ",path);
148         if (auto cached = path in _parsedFiles)
149         {
150             if (*cached is null)
151                 assertTemplate(0, "Recursive imports/includes/extends not allowed: ".fmt(path), front.pos);
152             else
153                 return *cached;
154         }
155 
156         // Prevent recursive imports
157         auto str = cast(string)read(path);
158         _parsedFiles[path] = parseTree(str, path,dirPath);
159 
160         return _parsedFiles[path];
161     }
162 
163 
164 private:
165     /**
166       * exprblock = EXPRBEGIN expr (IF expr (ELSE expr)? )? EXPREND
167       */
168     ExprNode parseExpression(string dirPath)
169     {
170         Node expr;
171         auto pos = front.pos;
172 
173         pop(Type.ExprBegin);
174         expr = parseHighLevelExpression(dirPath);
175         pop(Type.ExprEnd);
176 
177         return new ExprNode(pos, expr);
178     }
179 
180     StmtBlockNode parseStatementBlock(string dirPath)
181     {
182         auto block = new StmtBlockNode(front.pos);
183 
184         while (front.type != Type.EOF)
185         {
186             auto pos = front.pos;
187             switch(front.type) with (Type)
188             {
189                 case Raw:
190                     auto raw = pop.value;
191                     if (raw.length)
192                         block.children ~= new RawNode(pos, raw);
193                     break;
194 
195                 case ExprBegin:
196                     block.children ~= parseExpression(dirPath);
197                     break;
198 
199                 case CmntBegin:
200                     parseComment(dirPath);
201                     break;
202 
203                 case CmntInline:
204                     pop();
205                     break;
206 
207                 case StmtBegin:
208                     if (next.type == Type.Keyword
209                         && next.value.toKeyword.isBeginingKeyword)
210                         block.children ~= parseStatement(dirPath);
211                     else
212                         return block;
213                     break;
214 
215                 default:
216                     return block;
217             }
218         }
219 
220         return block;
221     }
222 
223 
224     Node parseStatement(string dirPath)
225     {
226         pop(Type.StmtBegin);
227 
228         switch(front.value) with (Keyword)
229         {
230             case If:      return parseIf(dirPath);
231             case For:     return parseFor(dirPath);
232             case Set:     return parseSet(dirPath);
233             case Macro:   return parseMacro(dirPath);
234             case Call:    return parseCall(dirPath);
235             case Filter:  return parseFilterBlock(dirPath);
236             case With:    return parseWith(dirPath);
237             case Import:  return parseImport(dirPath);
238             case From:    return parseImportFrom(dirPath);
239             case Include: return parseInclude(dirPath);
240             case Extends: return parseExtends(dirPath);
241 
242             case Block:
243                 auto block = parseBlock( dirPath);
244                 _blocks[block.name] = block;
245                 return block;
246             default:
247                 assert(0, "Not implemented kw %s".fmt(front.value));
248         }
249     }
250 
251 
252     ForNode parseFor(string dirPath)
253     {
254         string[] keys;
255         bool isRecursive = false;
256         Node cond = null;
257         auto pos = front.pos;
258 
259         pop(Keyword.For);
260 
261         keys ~= pop(Type.Ident).value;
262         while(front != Operator.In)
263         {
264             pop(Type.Comma);
265             keys ~= pop(Type.Ident).value;
266         }
267 
268         pop(Operator.In);
269 
270         Node iterable;
271 
272         switch (front.type) with (Type)
273         {
274             case LParen:  iterable = parseTuple(dirPath); break;
275             case LSParen: iterable = parseList(dirPath); break;
276             case LBrace:  iterable = parseDict(dirPath); break;
277             default:      iterable = parseIdent(dirPath);
278         }
279 
280         if (front == Keyword.If)
281         {
282             pop(Keyword.If);
283             cond = parseHighLevelExpression(dirPath);
284         }
285 
286         if (front == Keyword.Recursive)
287         {
288             pop(Keyword.Recursive);
289             isRecursive = true;
290         }
291 
292         pop(Type.StmtEnd);
293 
294         auto block = parseStatementBlock(dirPath);
295 
296         pop(Type.StmtBegin);
297 
298         switch (front.value) with (Keyword)
299         {
300             case EndFor:
301                 pop(Keyword.EndFor);
302                 pop(Type.StmtEnd);
303                 return new ForNode(pos, keys, iterable, block, null, cond, isRecursive);
304             case Else:
305                 pop(Keyword.Else);
306                 pop(Type.StmtEnd);
307                 auto other = parseStatementBlock(dirPath);
308                 pop(Type.StmtBegin);
309                 pop(Keyword.EndFor);
310                 pop(Type.StmtEnd);
311                 return new ForNode(pos, keys, iterable, block, other, cond, isRecursive);
312             default:
313                 assertTemplate(0, "Unexpected token %s(%s)".fmt(front.type, front.value), front.pos);
314                 assert(0);
315         }
316     }
317 
318 
319     IfNode parseIf(string dirPath)
320     {
321         auto pos = front.pos;
322         assertTemplate(front == Keyword.If || front == Keyword.ElIf, "Expected If/Elif", pos);
323         pop();
324         auto cond = parseHighLevelExpression(dirPath);
325         pop(Type.StmtEnd);
326 
327         auto then = parseStatementBlock(dirPath);
328 
329         pop(Type.StmtBegin);
330 
331         switch (front.value) with (Keyword)
332         {
333             case ElIf:
334                 auto other = parseIf(dirPath);
335                 return new IfNode(pos, cond, then, other);
336             case Else:
337                 pop(Keyword.Else, Type.StmtEnd);
338                 auto other = parseStatementBlock(dirPath);
339                 pop(Type.StmtBegin, Keyword.EndIf, Type.StmtEnd);
340                 return new IfNode(pos, cond, then, other);
341             case EndIf:
342                 pop(Keyword.EndIf, Type.StmtEnd);
343                 return new IfNode(pos, cond, then, null);
344             default:
345                 assertTemplate(0, "Unexpected token %s(%s)".fmt(front.type, front.value), front.pos);
346                 assert(0);
347         }
348     }
349 
350 
351     SetNode parseSet(string dirPath)
352     {
353         auto setPos = front.pos;
354 
355         pop(Keyword.Set);
356 
357         auto assigns = parseSequenceOf!parseAssignable(dirPath,Type.Operator);
358 
359         pop(Operator.Assign);
360 
361         auto listPos = front.pos;
362         auto exprs = parseSequenceOf!parseHighLevelExpression(dirPath,Type.StmtEnd);
363         Node expr = exprs.length == 1 ? exprs[0] : new ListNode(listPos, exprs);
364 
365         pop(Type.StmtEnd);
366 
367         return new SetNode(setPos, assigns, expr);
368     }
369 
370 
371     AssignableNode parseAssignable(string dirPath)
372     {
373         auto pos = front.pos;
374         string name = pop(Type.Ident).value;
375         Node[] subIdents = [];
376 
377         while (true)
378         {
379             switch (front.type) with (Type)
380             {
381                 case Dot:
382                     pop(Dot);
383                     auto strPos = front.pos;
384                     subIdents ~= new StringNode(strPos, pop(Ident).value);
385                     break;
386                 case LSParen:
387                     pop(LSParen);
388                     subIdents ~= parseHighLevelExpression(dirPath);
389                     pop(RSParen);
390                     break;
391                 default:
392                     return new AssignableNode(pos, name, subIdents);
393             }
394         }
395     }
396 
397 
398     MacroNode parseMacro(string dirPath)
399     {
400         auto pos = front.pos;
401         pop(Keyword.Macro);
402 
403         auto name = pop(Type.Ident).value;
404         Arg[] args;
405 
406         if (front.type == Type.LParen)
407         {
408             pop(Type.LParen);
409             args = parseFormalArgs( dirPath);
410             pop(Type.RParen);
411         }
412 
413         pop(Type.StmtEnd);
414 
415         auto block = parseStatementBlock( dirPath);
416 
417         pop(Type.StmtBegin, Keyword.EndMacro);
418 
419         bool ret = false;
420         if (front.type == Type.Keyword && front.value == Keyword.Return)
421         {
422             pop(Keyword.Return);
423             block.children ~= parseHighLevelExpression(dirPath);
424             ret = true;
425         }
426         else
427             block.children ~= new NilNode; // void return
428 
429         pop(Type.StmtEnd);
430 
431         return new MacroNode(pos, name, args, block, ret);
432     }
433 
434 
435     CallNode parseCall(string dirPath)
436     {
437         auto pos = front.pos;
438         pop(Keyword.Call);
439 
440         Arg[] formalArgs;
441 
442         if (front.type == Type.LParen)
443         {
444             pop(Type.LParen);
445             formalArgs = parseFormalArgs( dirPath);
446             pop(Type.RParen);
447         }
448 
449         auto macroName = front.value;
450         auto factArgs = parseCallExpr( dirPath);
451 
452         pop(Type.StmtEnd);
453 
454         auto block = parseStatementBlock( dirPath);
455         block.children ~= new NilNode; // void return
456 
457         pop(Type.StmtBegin, Keyword.EndCall, Type.StmtEnd);
458 
459         return new CallNode(pos, macroName, formalArgs, factArgs, block);
460     }
461 
462 
463     FilterBlockNode parseFilterBlock(string dirPath)
464     {
465         auto pos = front.pos;
466         pop(Keyword.Filter);
467 
468         auto filterName = front.value;
469         auto args = parseCallExpr(dirPath);
470 
471         pop(Type.StmtEnd);
472 
473         auto block = parseStatementBlock(dirPath);
474 
475         pop(Type.StmtBegin, Keyword.EndFilter, Type.StmtEnd);
476 
477         return new FilterBlockNode(pos, filterName, args, block);
478     }
479 
480     StmtBlockNode parseWith(string dirPath)
481     {
482         pop(Keyword.With, Type.StmtEnd);
483         auto block = parseStatementBlock(dirPath);
484         pop(Type.StmtBegin, Keyword.EndWith, Type.StmtEnd);
485 
486         return block;
487     }
488 
489 
490     ImportNode parseImport(string dirPath)
491     {
492         auto pos = front.pos;
493         pop(Keyword.Import);
494         auto path = pop(Type.String).value.absolute(dirPath);
495         bool withContext = false;
496 
497         if (front == Keyword.With)
498         {
499             withContext = true;
500             pop(Keyword.With, Keyword.Context);
501         }
502 
503         if (front == Keyword.Without)
504         {
505             withContext = false;
506             pop(Keyword.Without, Keyword.Context);
507         }
508 
509         pop(Type.StmtEnd);
510 
511         assertTemplate(path.fileExist(dirPath), "Non existing file `%s`".fmt(path), pos);
512 
513         auto stmtBlock = parseTreeFromFile(path);
514 
515         return new ImportNode(pos, path, cast(ImportNode.Rename[])[], stmtBlock, withContext);
516     }
517 
518 
519     ImportNode parseImportFrom(string dirPath)
520     {
521         auto pos = front.pos;
522         pop(Keyword.From);
523         auto path = pop(Type.String).value.absolute(dirPath);
524         pop(Keyword.Import);
525 
526         ImportNode.Rename[] macros;
527 
528         bool firstName = true;
529         while (front == Type.Comma || firstName)
530         {
531             if (!firstName)
532                 pop(Type.Comma);
533 
534             auto was = pop(Type.Ident).value;
535             auto become = was;
536 
537             if (front == Keyword.As)
538             {
539                 pop(Keyword.As);
540                 become = pop(Type.Ident).value;
541             }
542 
543             macros ~= ImportNode.Rename(was, become);
544 
545             firstName = false;
546         }
547 
548         bool withContext = false;
549 
550         if (front == Keyword.With)
551         {
552             withContext = true;
553             pop(Keyword.With, Keyword.Context);
554         }
555 
556         if (front == Keyword.Without)
557         {
558             withContext = false;
559             pop(Keyword.Without, Keyword.Context);
560         }
561 
562         pop(Type.StmtEnd);
563 
564         assertTemplate(path.fileExist(dirPath), "Non existing file `%s`".fmt(path), pos);
565 
566         auto stmtBlock = parseTreeFromFile(path);
567 
568         return new ImportNode(pos, path, macros, stmtBlock, withContext);
569     }
570 
571 
572     IncludeNode parseInclude(string dirPath)
573     {
574         auto pos = front.pos;
575         pop(Keyword.Include);
576 
577         string[] names;
578 
579         if (front == Type.LSParen)
580         {
581             pop(Type.LSParen);
582 
583             names ~= pop(Type.String).value;
584             while (front == Type.Comma)
585             {
586                 pop(Type.Comma);
587                 names ~= pop(Type.String).value;
588             }
589 
590             pop(Type.RSParen);
591         }
592         else
593             names ~= pop(Type.String).value;
594 
595 
596         bool ignoreMissing = false;
597         if (front == Keyword.Ignore)
598         {
599             pop(Keyword.Ignore, Keyword.Missing);
600             ignoreMissing = true;
601         }
602 
603         bool withContext = true;
604 
605         if (front == Keyword.With)
606         {
607             withContext = true;
608             pop(Keyword.With, Keyword.Context);
609         }
610 
611         if (front == Keyword.Without)
612         {
613             withContext = false;
614             pop(Keyword.Without, Keyword.Context);
615         }
616 
617         pop(Type.StmtEnd);
618 
619         foreach (name; names)
620             if (name.fileExist(dirPath))
621                 return new IncludeNode(pos, name, parseTreeFromFile(dirPath ~ name), withContext);
622  
623         assertTemplate(ignoreMissing, "No existing files `%s`".fmt(names), pos);
624 
625         return new IncludeNode(pos, "", null, withContext);
626     }
627 
628 
629     ExtendsNode parseExtends(string dirPath)
630     {
631         auto pos = front.pos;
632         pop(Keyword.Extends);
633         auto path = pop(Type.String).value.absolute(dirPath);
634         pop(Type.StmtEnd);
635 
636         assertTemplate(path.fileExist(dirPath), "Non existing file `%s`".fmt(path), pos);
637 
638         auto stmtBlock = parseTreeFromFile(path);
639 
640         return new ExtendsNode(pos, path, stmtBlock);
641     }
642 
643 
644     BlockNode parseBlock(string dirPath)
645     {
646         auto pos = front.pos;
647         pop(Keyword.Block);
648         auto name = pop(Type.Ident).value;
649         pop(Type.StmtEnd);
650 
651         auto stmt = parseStatementBlock( dirPath);
652 
653         pop(Type.StmtBegin, Keyword.EndBlock);
654 
655         auto posNameEnd = front.pos;
656         if (front == Type.Ident)
657             assertTemplate(pop.value == name, "Missmatching block's begin/end names", posNameEnd);
658 
659         pop(Type.StmtEnd);
660 
661         return new BlockNode(pos, name, stmt);
662     }
663 
664     Arg[] parseFormalArgs(string dirPath)
665     {
666         Arg[] args = [];
667         bool isVarargs = true;
668 
669         while(front.type != Type.EOF && front.type != Type.RParen)
670         {
671             auto name = pop(Type.Ident).value;
672             Node def = null;
673 
674             if (!isVarargs || front.type == Type.Operator && front.value == Operator.Assign)
675             {
676                 isVarargs = false;
677                 pop(Operator.Assign);
678                 def = parseHighLevelExpression(dirPath);
679             }
680 
681             args ~= Arg(name, def);
682 
683             if (front.type != Type.RParen)
684                 pop(Type.Comma);
685         }
686         return args;
687     }
688 
689 
690     Node parseHighLevelExpression(string dirPath)
691     {
692         return parseInlineIf(dirPath);
693     }
694 
695 
696     /**
697       * inlineif = orexpr (IF orexpr (ELSE orexpr)? )?
698       */
699     Node parseInlineIf(string dirPath)
700     {
701         Node expr;
702         Node cond = null;
703         Node other = null;
704 
705         auto pos = front.pos;
706         expr = parseOrExpr( dirPath);
707 
708         if (front == Keyword.If)
709         {
710             pop(Keyword.If);
711             cond = parseOrExpr( dirPath);
712 
713             if (front == Keyword.Else)
714             {
715                 pop(Keyword.Else);
716                 other = parseOrExpr(dirPath);
717             }
718 
719             return new InlineIfNode(pos, expr, cond, other);
720         }
721 
722         return expr;
723     }
724 
725     /**
726       * Parse Or Expression
727       * or = and (OR or)?
728       */
729     Node parseOrExpr(string dirPath)
730     {
731         auto lhs = parseAndExpr(dirPath);
732 
733         while(true)
734         {
735             if (front.type == Type.Operator && front.value == Operator.Or)
736             {
737                 auto pos = front.pos;
738                 pop(Operator.Or);
739                 auto rhs = parseAndExpr( dirPath);
740                 lhs = new BinOpNode(pos, Operator.Or, lhs, rhs);
741             }
742             else
743                 return lhs;
744         }
745     }
746 
747     /**
748       * Parse And Expression:
749       * and = inis (AND inis)*
750       */
751     Node parseAndExpr(string dirPath)
752     {
753         auto lhs = parseInIsExpr( dirPath);
754 
755         while(true)
756         {
757             if (front.type == Type.Operator && front.value == Operator.And)
758             {
759                 auto pos = front.pos;
760                 pop(Operator.And);
761                 auto rhs = parseInIsExpr( dirPath);
762                 lhs = new BinOpNode(pos, Operator.And, lhs, rhs);
763             }
764             else
765                 return lhs;
766         }
767     }
768 
769     /**
770       * Parse inis:
771       * inis = cmp ( (NOT)? (IN expr |IS callexpr) )?
772       */
773     Node parseInIsExpr(string dirPath)
774     {
775         auto inis = parseCmpExpr( dirPath);
776 
777         auto notPos = front.pos;
778         bool hasNot = false;
779         if (front == Operator.Not && (next == Operator.In || next == Operator.Is))
780         {
781             pop(Operator.Not);
782             hasNot = true;
783         }
784 
785         auto inisPos = front.pos;
786 
787         if (front == Operator.In)
788         {
789             auto op = pop().value;
790             auto rhs = parseHighLevelExpression( dirPath);
791             inis = new BinOpNode(inisPos, op, inis, rhs);
792         }
793 
794         if (front == Operator.Is)
795         {
796             auto op = pop().value;
797             auto rhs = parseCallExpr( dirPath);
798             inis = new BinOpNode(inisPos, op, inis, rhs);
799         }
800 
801         if (hasNot)
802             inis = new UnaryOpNode(notPos, Operator.Not, inis);
803 
804         return inis;
805     }
806 
807 
808     /**
809       * Parse compare expression:
810       * cmp = concatexpr (CMPOP concatexpr)?
811       */
812     Node parseCmpExpr(string dirPath)
813     {
814         auto lhs = parseConcatExpr( dirPath);
815 
816         if (front.type == Type.Operator && front.value.toOperator.isCmpOperator)
817         {
818             auto pos = front.pos;
819             auto op = pop(Type.Operator).value;
820             return new BinOpNode(pos, op, lhs, parseConcatExpr( dirPath));
821         }
822 
823         return lhs;
824     }
825 
826     /**
827       * Parse expression:
828       * concatexpr = filterexpr (CONCAT filterexpr)*
829       */
830     Node parseConcatExpr(string dirPath)
831     {
832         auto lhsTerm = parseFilterExpr( dirPath);
833 
834         while (front == Operator.Concat)
835         {
836             auto pos = front.pos;
837             auto op = pop(Operator.Concat).value;
838             lhsTerm = new BinOpNode(pos, op, lhsTerm, parseFilterExpr( dirPath));
839         }
840 
841         return lhsTerm;
842     }
843 
844     /**
845       * filterexpr = mathexpr (FILTER callexpr)*
846       */
847     Node parseFilterExpr(string dirPath)
848     {
849         auto filterexpr = parseMathExpr( dirPath);
850 
851         while (front == Operator.Filter)
852         {
853             auto pos = front.pos;
854             auto op = pop(Operator.Filter).value;
855             filterexpr = new BinOpNode(pos, op, filterexpr, parseCallExpr( dirPath));
856         }
857 
858         return filterexpr;
859     }
860 
861     /**
862       * Parse math expression:
863       * mathexpr = term((PLUS|MINUS)term)*
864       */
865     Node parseMathExpr(string dirPath)
866     {
867         auto lhsTerm = parseTerm( dirPath);
868 
869         while (true)
870         {
871             if (front.type != Type.Operator)
872                 return lhsTerm;
873 
874             auto pos = front.pos;
875             switch (front.value) with (Operator)
876             {
877                 case Plus:
878                 case Minus:
879                     auto op = pop.value;
880                     lhsTerm = new BinOpNode(pos, op, lhsTerm, parseTerm( dirPath));
881                     break;
882                 default:
883                     return lhsTerm;
884             }
885         }
886     }
887 
888     /**
889       * Parse term:
890       * term = unary((MUL|DIVI|DIVF|REM)unary)*
891       */
892     Node parseTerm(string dirPath)
893     {
894         auto lhsFactor = parseUnary( dirPath);
895 
896         while(true)
897         {
898             if (front.type != Type.Operator)
899                 return lhsFactor;
900 
901             auto pos = front.pos;
902             switch (front.value) with (Operator)
903             {
904                 case DivInt:
905                 case DivFloat:
906                 case Mul:
907                 case Rem:
908                     auto op = pop.value;
909                     lhsFactor = new BinOpNode(pos, op, lhsFactor, parseUnary( dirPath));
910                     break;
911                 default:
912                     return lhsFactor;
913             }
914         } 
915     }
916 
917     /**
918       * Parse unary:
919       * unary = (pow | (PLUS|MINUS|NOT)unary)
920       */
921     Node parseUnary(string dirPath)
922     {
923         if (front.type != Type.Operator)
924             return parsePow( dirPath);
925 
926         auto pos = front.pos;
927         switch (front.value) with (Operator)
928         {
929             case Plus:
930             case Minus:
931             case Not:
932                 auto op = pop.value;
933                 return new UnaryOpNode(pos, op, parseUnary( dirPath));
934             default:
935                 assertTemplate(0, "Unexpected operator `%s`".fmt(front.value), front.pos);
936                 assert(0);
937         }
938     }
939 
940     /**
941       * Parse pow:
942       * pow = factor (POW pow)?
943       */
944     Node parsePow(string dirPath)
945     {
946         auto lhs = parseFactor(dirPath);
947 
948         if (front.type == Type.Operator && front.value == Operator.Pow)
949         {
950             auto pos = front.pos;
951             auto op = pop(Operator.Pow).value;
952             return new BinOpNode(pos, op, lhs, parsePow( dirPath));
953         }
954 
955         return lhs;
956     }
957 
958 
959     /**
960       * Parse factor:
961       * factor = (ident|(tuple|LPAREN HighLevelExpr RPAREN)|literal)
962       */
963     Node parseFactor(string dirPath)
964     {
965         switch (front.type) with (Type)
966         {
967             case Ident:
968                 return parseIdent( dirPath);
969 
970             case LParen:
971                 auto pos = front.pos;
972                 pop(LParen);
973                 bool hasCommas;
974                 auto exprList = parseSequenceOf!parseHighLevelExpression( dirPath,RParen, hasCommas);
975                 pop(RParen);
976                 return hasCommas ? new ListNode(pos, exprList) : exprList[0];
977 
978             default:
979                 return parseLiteral( dirPath);
980         }
981     }
982 
983     /**
984       * Parse ident:
985       * ident = IDENT (LPAREN ARGS RPAREN)? (DOT IDENT (LP ARGS RP)?| LSPAREN STR LRPAREN)*
986       */
987     Node parseIdent(string dirPath)
988     {
989         string name = "";
990         Node[] subIdents = [];
991         auto pos = front.pos;
992 
993         if (next.type == Type.LParen)
994             subIdents ~= parseCallExpr( dirPath);
995         else
996             name = pop(Type.Ident).value;
997 
998         while (true)
999         {
1000             switch (front.type) with (Type)
1001             {
1002                 case Dot:
1003                     pop(Dot);
1004                     auto posStr = front.pos;
1005                     if (next.type == Type.LParen)
1006                         subIdents ~= parseCallExpr( dirPath);
1007                     else
1008                         subIdents ~= new StringNode(posStr, pop(Ident).value);
1009                     break;
1010                 case LSParen:
1011                     pop(LSParen);
1012                     subIdents ~= parseHighLevelExpression( dirPath);
1013                     pop(RSParen);
1014                     break;
1015                 default:
1016                     return new IdentNode(pos, name, subIdents);
1017             }
1018         }
1019     }
1020 
1021 
1022     IdentNode parseCallIdent(string dirPath)
1023     {
1024         auto pos = front.pos;
1025         return new IdentNode(pos, "", [parseCallExpr( dirPath)]);
1026     }
1027 
1028 
1029     DictNode parseCallExpr(string dirPath)
1030     {
1031         auto pos = front.pos;
1032         string name = pop(Type.Ident).value;
1033         Node[] varargs;
1034         Node[string] kwargs;
1035 
1036         bool parsingKwargs = false;
1037         void parse(string dirPath)
1038         {
1039             if (parsingKwargs || front.type == Type.Ident && next.value == Operator.Assign)
1040             {
1041                 parsingKwargs = true;
1042                 auto name = pop(Type.Ident).value;
1043                 pop(Operator.Assign);
1044                 kwargs[name] = parseHighLevelExpression( dirPath);
1045             }
1046             else
1047                 varargs ~= parseHighLevelExpression( dirPath);
1048         }
1049 
1050         if (front.type == Type.LParen)
1051         {
1052             pop(Type.LParen);
1053 
1054             while (front.type != Type.EOF && front.type != Type.RParen)
1055             {
1056                 parse( dirPath);
1057 
1058                 if (front.type != Type.RParen)
1059                     pop(Type.Comma);
1060             }
1061 
1062             pop(Type.RParen);
1063         }
1064 
1065         Node[string] callDict;
1066         callDict["name"] = new StringNode(pos, name);
1067         callDict["varargs"] = new ListNode(pos, varargs);
1068         callDict["kwargs"] = new DictNode(pos, kwargs);
1069 
1070         return new DictNode(pos, callDict);
1071     }
1072 
1073     /**
1074       * literal = string|number|list|tuple|dict
1075       */
1076     Node parseLiteral(string dirPath)
1077     {
1078         auto pos = front.pos;
1079         switch (front.type) with (Type)
1080         {
1081             case Integer: return new NumNode(pos, pop.value.to!long);
1082             case Float:   return new NumNode(pos, pop.value.to!double);
1083             case String:  return new StringNode(pos, pop.value);
1084             case Boolean: return new BooleanNode(pos, pop.value.to!bool);
1085             case LParen:  return parseTuple( dirPath);
1086             case LSParen: return parseList( dirPath);
1087             case LBrace:  return parseDict( dirPath);
1088             default:
1089                 assertTemplate(0, "Unexpected token while parsing expression: %s(%s)".fmt(front.type, front.value), front.pos);
1090                 assert(0);
1091         }
1092     }
1093 
1094 
1095     Node parseTuple(string dirPath)
1096     {
1097         //Literally array right now
1098 
1099         auto pos = front.pos;
1100         pop(Type.LParen);
1101         auto tuple = parseSequenceOf!parseHighLevelExpression( dirPath,Type.RParen);
1102         pop(Type.RParen);
1103 
1104         return new ListNode(pos, tuple);
1105     }
1106 
1107 
1108     Node parseList(string dirPath)
1109     {
1110         auto pos = front.pos;
1111         pop(Type.LSParen);
1112         auto list = parseSequenceOf!parseHighLevelExpression( dirPath,Type.RSParen);
1113         pop(Type.RSParen);
1114 
1115         return new ListNode(pos, list);
1116     }
1117 
1118 
1119     Node[] parseSequenceOf(alias parser)(string dirPath,Type stopSymbol)
1120     {
1121         bool hasCommas;
1122         return parseSequenceOf!parser(dirPath,stopSymbol, hasCommas);
1123     }
1124 
1125 
1126     Node[] parseSequenceOf(alias parser)(string dirPath,Type stopSymbol, ref bool hasCommas)
1127     {
1128         Node[] seq;
1129 
1130         hasCommas = false;
1131         while (front.type != stopSymbol && front.type != Type.EOF)
1132         {
1133             seq ~= parser(dirPath);
1134 
1135             if (front.type != stopSymbol)
1136             {
1137                 pop(Type.Comma);
1138                 hasCommas = true;
1139             }
1140         }
1141 
1142         return seq;
1143     }
1144 
1145 
1146     Node parseDict(string dirPath)
1147     {
1148         Node[string] dict;
1149         auto pos = front.pos;
1150 
1151         pop(Type.LBrace);
1152 
1153         bool isFirst = true;
1154         while (front.type != Type.RBrace && front.type != Type.EOF)
1155         {
1156             if (!isFirst)
1157                 pop(Type.Comma);
1158 
1159             string key;
1160             if (front.type == Type.Ident)
1161                 key = pop(Type.Ident).value;
1162             else
1163                 key = pop(Type.String).value;
1164 
1165             pop(Type.Colon);
1166             dict[key] = parseHighLevelExpression( dirPath);
1167             isFirst = false;
1168         }
1169 
1170         if (front.type == Type.Comma)
1171             pop(Type.Comma);
1172 
1173         pop(Type.RBrace);
1174 
1175         return new DictNode(pos, dict);
1176     }
1177 
1178 
1179     void parseComment(string dirPath)
1180     {
1181         pop(Type.CmntBegin);
1182         while (front.type != Type.CmntEnd && front.type != Type.EOF)
1183             pop();
1184         pop(Type.CmntEnd);
1185     }
1186 
1187 
1188     Token front()
1189     {
1190         if (_tokens.length)
1191             return _tokens[0];
1192         return Token.EOF;
1193     }
1194 
1195     Token next()
1196     {
1197         if (_tokens.length > 1)
1198             return _tokens[1];
1199         return Token.EOF;
1200     }
1201 
1202 
1203     Token pop()
1204     {
1205         auto tkn = front();
1206         if (_tokens.length)
1207             _tokens = _tokens[1 .. $];
1208         return tkn;
1209     }
1210 
1211 
1212     Token pop(Type t)
1213     {
1214         if (front.type != t)
1215             assertTemplate(0, "Unexpected token %s(%s), expected: `%s`".fmt(front.type, front.value, t), front.pos);
1216         return pop();
1217     }
1218 
1219 
1220     Token pop(Keyword kw)
1221     {
1222         if (front.type != Type.Keyword || front.value != kw)
1223             assertTemplate(0, "Unexpected token %s(%s), expected kw: %s".fmt(front.type, front.value, kw), front.pos);
1224         return pop();
1225     }
1226 
1227 
1228     Token pop(Operator op)
1229     {
1230         if (front.type != Type.Operator || front.value != op)
1231             assertTemplate(0, "Unexpected token %s(%s), expected op: %s".fmt(front.type, front.value, op), front.pos);
1232         return pop();
1233     }
1234 
1235 
1236     void pop(T...)(T args)
1237         if (args.length > 1)
1238     {
1239         foreach(arg; args)
1240             pop(arg);
1241     }
1242 
1243 
1244     void stashState()
1245     {
1246         ParserState old;
1247         old.tokens = _tokens;
1248         old.blocks = _blocks;
1249         _states ~= old;
1250         _tokens = [];
1251         _blocks = (BlockNode[string]).init;
1252     }
1253 
1254 
1255     void popState()
1256     {
1257         assertTemplate(_states.length > 0, "Unexpected empty state stack");
1258 
1259         auto state = _states.back;
1260         _states.popBack;
1261         _tokens = state.tokens;
1262         _blocks = state.blocks;
1263     }
1264 }
1265 
1266 
1267 private:
1268 
1269 
1270 string absolute(string file,string path)
1271 {
1272     //TODO
1273     // return path;
1274     import std.path : absolutePath;
1275     return (path ~ file);
1276 }
1277 
1278 bool fileExist(string file,string path)
1279 {
1280     import std.path : absolutePath;
1281     version(HUNT_FM_DEBUG) logDebug("path.absolutePath : ",(path ~ file).absolutePath);
1282     return (file.exists) || ((path ~ file).absolutePath.exists);
1283 }
1284 
1285 string stripOnceRight(string str)
1286 {
1287     import std.uni;
1288     import std.utf : codeLength;
1289 
1290     import std.traits;
1291     alias C = Unqual!(ElementEncodingType!string);
1292 
1293     bool stripped = false;
1294     foreach_reverse (i, dchar c; str)
1295     {
1296         if (!isWhite(c))
1297             return str[0 .. i + codeLength!C(c)];
1298 
1299         if (c == '\n' || c == '\r' || c == 0x2028 || c == 0x2029)
1300         {
1301             return str[0 .. i];
1302         }
1303     }
1304 
1305     return str[0 .. 0];
1306 }
1307 
1308 unittest
1309 {
1310     assert(stripOnceRight("\n") == "", stripOnceRight("\n"));
1311 }