1 /**
2   *  Djinja render
3   *
4   * Copyright:
5   *     Copyright (c) 2018, Maxim Tyapkin.
6   * Authors:
7   *     Maxim Tyapkin
8   * License:
9   *     This software is licensed under the terms of the BSD 3-clause license.
10   *     The full terms of the license can be found in the LICENSE.md file.
11   */
12 
13 module djinja.render;
14 
15 private
16 {
17     import std.range;
18     import std.format: fmt = format;
19 
20     import djinja.ast.node;
21     import djinja.ast.visitor;
22     import djinja.algo;
23     import djinja.algo.wrapper;
24     import djinja.lexer;
25     import djinja.parser;
26     import djinja.exception : JinjaRenderException,
27                               assertJinja = assertJinjaRender;
28 
29     import djinja.uninode;
30 }
31 
32 
33 
34 
35 struct FormArg
36 {
37     string name;
38     Nullable!UniNode def;
39 
40     this (string name)
41     {
42         this.name = name;
43         this.def = Nullable!UniNode.init;
44     }
45 
46     this (string name, UniNode def)
47     {
48         this.name = name;
49         this.def = Nullable!UniNode(def);
50     }
51 }
52 
53 
54 struct Macro
55 {
56     FormArg[] args;
57     Nullable!Context context;
58     Nullable!Node block;
59 
60     this(FormArg[] args, Context context, Node block)
61     {
62         this.args = args;
63         this.context = context.toNullable;
64         this.block = block.toNullable;
65     }
66 }
67 
68 
69 class Context
70 {
71     private Context prev;
72 
73     UniNode data;
74     Function[string] functions;
75     Macro[string] macros;
76 
77     this ()
78     {
79         prev = null;
80         data = UniNode.emptyObject();
81     }
82 
83     this (Context ctx, UniNode data)
84     {
85         prev = ctx;
86         this.data = data;
87     }
88 
89     Context previos() @property
90     {
91         if (prev !is null)
92             return prev;
93         return this;
94     }
95 
96     bool has(string name)
97     {
98         if (name in data)
99             return true;
100         if (prev is null)
101             return false;
102         return prev.has(name);
103     }
104     
105     UniNode get(string name)
106     {
107         if (name in data)
108             return data[name];
109         if (prev is null)
110             return UniNode(null);
111         return prev.get(name);
112     }
113 
114     UniNode* getPtr(string name)
115     {
116         if (name in data)
117             return &(data[name]);
118         if (prev is null)
119             assertJinja(0, "Non declared var `%s`".fmt(name));
120         return prev.getPtr(name);
121     }
122     
123     T get(T)(string name)
124     {
125         return this.get(name).get!T;
126     }
127     
128 
129     bool hasFunc(string name)
130     {
131         if (name in functions)
132             return true;
133         if (prev is null)
134             return false;
135         return prev.hasFunc(name);
136     }
137 
138     
139     Function getFunc(string name)
140     {
141         if (name in functions)
142             return functions[name];
143         if (prev is null)
144             assertJinja(0, "Non declared function `%s`".fmt(name));
145         return prev.getFunc(name);
146     }
147 
148 
149     bool hasMacro(string name)
150     {
151         if (name in macros)
152             return true;
153         if (prev is null)
154             return false;
155         return prev.hasMacro(name);
156     }
157 
158     
159     Macro getMacro(string name)
160     {
161         if (name in macros)
162             return macros[name];
163         if (prev is null)
164             assertJinja(0, "Non declared macro `%s`".fmt(name));
165         return prev.getMacro(name);
166     }
167 }
168 
169 
170 struct AppliedFilter
171 {
172     string name;
173     UniNode args;
174 }
175 
176 
177 class Render : IVisitor
178 {
179     private
180     {
181         TemplateNode    _root;
182         Context         _globalContext;
183         Context         _rootContext;
184         UniNode[]       _dataStack;
185         AppliedFilter[] _appliedFilters;
186         TemplateNode[]  _extends;
187 
188         Context         _context;
189 
190         string          _renderedResult;
191         bool            _isExtended;
192     }
193 
194     this(TemplateNode root)
195     {
196         _root = root;
197         _rootContext = new Context();
198 
199         foreach(key, value; globalFunctions)
200             _rootContext.functions[key] = cast(Function)value;
201         foreach(key, value; globalFilters)
202             _rootContext.functions[key] = cast(Function)value;
203         foreach(key, value; globalTests)
204             _rootContext.functions[key] = cast(Function)value;
205     }
206 
207 
208     string render(UniNode data)
209     {
210         _context = new Context(_rootContext, data);
211         _globalContext = _context;
212 
213         _extends = [_root];
214         _isExtended = false;
215 
216         _renderedResult = "";
217         if (_root !is null)
218             tryAccept(_root);
219         return _renderedResult;
220     }
221 
222 
223     override void visit(TemplateNode node)
224     {
225         tryAccept(node.stmt);
226     }
227 
228     override void visit(BlockNode node)
229     {
230         void super_()
231         {
232             tryAccept(node.stmt);
233         }
234 
235         foreach (tmpl; _extends[0 .. $-1])
236             if (node.name in tmpl.blocks)
237             {
238                 pushNewContext();
239                 _context.functions["super"] = wrapper!super_;
240                 tryAccept(tmpl.blocks[node.name].stmt);
241                 popContext();
242                 return;
243             }
244 
245         super_();
246     }
247 
248 
249     override void visit(StmtBlockNode node)
250     {
251         pushNewContext();
252         foreach(ch; node.children)
253             tryAccept(ch);
254         popContext();
255     }
256 
257     override void visit(RawNode node)
258     {
259         writeToResult(node.raw);
260     }
261 
262     override void visit(ExprNode node)
263     {
264         tryAccept(node.expr);
265         auto n = pop();
266         n.toStringType;
267         writeToResult(n.get!string);
268     }
269 
270     override void visit(InlineIfNode node)
271     {
272         bool condition = true;
273 
274         if (!node.cond.isNull)
275         {
276             tryAccept(node.cond);
277             auto res = pop();
278             res.toBoolType;
279             condition = res.get!bool;
280         }
281 
282         if (condition)
283         {
284             tryAccept(node.expr);
285         }
286         else if (!node.other.isNull)
287         {
288             tryAccept(node.other);
289         }
290         else
291         {
292             push(UniNode(null));
293         }
294     }
295 
296     override void visit(BinOpNode node)
297     {
298         UniNode calc(Operator op)()
299         {
300             tryAccept(node.lhs);
301             auto lhs = pop();
302 
303             tryAccept(node.rhs);
304             auto rhs = pop();
305 
306             return binary!op(lhs, rhs);
307         }
308 
309         UniNode calcLogic(bool stopCondition)()
310         {
311             tryAccept(node.lhs);
312             auto lhs = pop();
313             lhs.toBoolType;
314             if (lhs.get!bool == stopCondition)
315                 return UniNode(stopCondition);
316 
317             tryAccept(node.rhs);
318             auto rhs = pop();
319             rhs.toBoolType;
320             return UniNode(rhs.get!bool);
321         }
322 
323         UniNode calcCall(string type)()
324         {
325             tryAccept(node.lhs);
326             auto lhs = pop();
327 
328             tryAccept(node.rhs);
329             auto args = pop();
330             auto name = args["name"].get!string;
331             args["varargs"] = UniNode([lhs] ~ args["varargs"].get!(UniNode[]));
332             
333             if (_context.hasFunc(name))
334                 return visitFunc(name, args);
335             else if (_context.hasMacro(name))
336                 return visitMacro(name, args);
337             else
338                 assertJinja(0, "Undefined " ~ type ~ " %s".fmt(name), node.pos);
339             assert(0);
340         }
341         
342         UniNode calcFilter()
343         {
344             return calcCall!"filter";
345         }
346 
347         UniNode calcIs()
348         {
349             auto res = calcCall!"test";
350             res.toBoolType;
351             return res;
352         }
353 
354         UniNode doSwitch()
355         {
356             switch (node.op) with (Operator)
357             {
358                 case Concat:    return calc!Concat;
359                 case Plus:      return calc!Plus;
360                 case Minus:     return calc!Minus;
361                 case DivInt:    return calc!DivInt;
362                 case DivFloat:  return calc!DivFloat;
363                 case Rem:       return calc!Rem;
364                 case Mul:       return calc!Mul;
365                 case Greater:   return calc!Greater;
366                 case Less:      return calc!Less;
367                 case GreaterEq: return calc!GreaterEq;
368                 case LessEq:    return calc!LessEq;
369                 case Eq:        return calc!Eq;
370                 case NotEq:     return calc!NotEq;
371                 case Pow:       return calc!Pow;
372                 case In:        return calc!In;
373 
374                 case Or:        return calcLogic!true;
375                 case And:       return calcLogic!false;
376 
377                 case Filter:    return calcFilter;
378                 case Is:        return calcIs;
379 
380                 default:
381                     assert(0, "Not implemented binary operator");
382             }
383         }
384 
385         push(doSwitch());
386     }
387 
388     override void visit(UnaryOpNode node)
389     {
390         tryAccept(node.expr);
391         auto res = pop();
392         UniNode doSwitch()
393         {
394             switch (node.op) with (Operator)
395             {
396                 case Plus:      return unary!Plus(res);
397                 case Minus:     return unary!Minus(res);
398                 case Not:       return unary!Not(res);
399                 default:
400                     assert(0, "Not implemented unary operator");
401             }
402         }
403 
404         push(doSwitch());
405     }
406 
407     override void visit(NumNode node)
408     {
409         if (node.type == NumNode.Type.Integer)
410             push(UniNode(node.data._integer));
411         else
412             push(UniNode(node.data._float));
413     }
414 
415     override void visit(BooleanNode node)
416     {
417         push(UniNode(node.boolean));
418     }
419 
420     override void visit(NilNode node)
421     {
422         push(UniNode(null));
423     }
424 
425     override void visit(IdentNode node)
426     {
427         UniNode curr;
428         if (node.name.length)
429             curr = _context.get(node.name);
430         else
431             curr = UniNode(null);
432 
433         auto lastPos = node.pos;
434         foreach (sub; node.subIdents)
435         {
436             tryAccept(sub);
437             auto key = pop();
438 
439             switch (key.kind) with (UniNode.Kind)
440             {
441                 // Index of list/tuple
442                 case integer:
443                 case uinteger:
444                     curr.checkNodeType(array, lastPos);
445                     if (key.get!long < curr.length)
446                         curr = curr[key.get!long];
447                     else
448                         assertJinja(0, "Range violation  on %s...[%d]".fmt(node.name, key.get!long), sub.pos);
449                     break;
450 
451                 // Key of dict
452                 case text:
453                     auto keyStr = key.get!string;
454                     if (curr.kind == UniNode.Kind.object && keyStr in curr)
455                         curr = curr[keyStr];
456                     else if (_context.hasFunc(keyStr))
457                     {
458                         auto args = [
459                             "name": UniNode(keyStr),
460                             "varargs": UniNode([curr]),
461                             "kwargs": UniNode.emptyObject
462                         ];
463                         curr = visitFunc(keyStr, UniNode(args));
464                     }
465                     else if (_context.hasMacro(keyStr))
466                     {
467                         auto args = [
468                             "name": UniNode(keyStr),
469                             "varargs": UniNode([curr]),
470                             "kwargs": UniNode.emptyObject
471                         ];
472                         curr = visitMacro(keyStr, UniNode(args));
473                     }
474                     else
475                     {
476                         curr.checkNodeType(object, lastPos);
477                         assertJinja(0, "Unknown attribute %s".fmt(key.get!string), sub.pos);
478                     }
479                     break;
480 
481                 // Call of function
482                 case object:
483                     auto name = key["name"].get!string;
484 
485                     if (!curr.isNull)
486                         key["varargs"] = UniNode([curr] ~ key["varargs"].get!(UniNode[]));
487 
488                     if (_context.hasFunc(name))
489                     {
490                         curr = visitFunc(name, key);
491                     }
492                     else if (_context.hasMacro(name))
493                     {
494                         curr = visitMacro(name, key);
495                     }
496                     else
497                         assertJinja(0, "Not found any macro, function or filter `%s`".fmt(name), sub.pos);
498                     break;
499 
500                 default:
501                     assertJinja(0, "Unknown attribute %s for %s".fmt(key.toString, node.name), sub.pos);
502             }
503             
504             lastPos = sub.pos;
505         }
506 
507         push(curr);
508     }
509 
510     override void visit(AssignableNode node)
511     {
512         auto expr = pop();
513 
514         // TODO: check flag of set scope
515         if (!_context.has(node.name))
516         {
517             if (node.subIdents.length)
518                 assertJinja(0, "Unknow variable %s".fmt(node.name), node.pos);
519             _context.data[node.name] = expr;
520             return;
521         }
522 
523         UniNode* curr = _context.getPtr(node.name);
524 
525         if (!node.subIdents.length)
526         {
527             (*curr) = expr;
528             return;
529         }
530 
531         auto lastPos = node.pos;
532         for(int i = 0; i < cast(long)(node.subIdents.length) - 1; i++)
533         {
534             tryAccept(node.subIdents[i]);
535             auto key = pop();
536 
537             switch (key.kind) with (UniNode.Kind)
538             {
539                 // Index of list/tuple
540                 case integer:
541                 case uinteger:
542                     checkNodeType(*curr, array, lastPos);
543                     if (key.get!long < curr.length)
544                         curr = &((*curr)[key.get!long]);
545                     else
546                         assertJinja(0, "Range violation  on %s...[%d]".fmt(node.name, key.get!long), node.subIdents[i].pos);
547                     break;
548 
549                 // Key of dict
550                 case text:
551                     checkNodeType(*curr, object, lastPos);
552                     if (key.get!string in *curr)
553                         curr = &((*curr)[key.get!string]);
554                     else
555                         assertJinja(0, "Unknown attribute %s".fmt(key.get!string), node.subIdents[i].pos);
556                     break;
557 
558                 default:
559                     assertJinja(0, "Unknown attribute %s for %s".fmt(key.toString, node.name), node.subIdents[i].pos);
560             }
561             lastPos = node.subIdents[i].pos;
562         }
563 
564         if (node.subIdents.length)
565         {
566             tryAccept(node.subIdents[$-1]);
567             auto key = pop();
568 
569             switch (key.kind) with (UniNode.Kind)
570             {
571                 // Index of list/tuple
572                 case integer:
573                 case uinteger:
574                     checkNodeType(*curr, array, lastPos);
575                     if (key.get!long < curr.length)
576                         (*curr).opIndex(key.get!long) = expr; // ¯\_(ツ)_/¯
577                     else
578                         assertJinja(0, "Range violation  on %s...[%d]".fmt(node.name, key.get!long), node.subIdents[$-1].pos);
579                     break;
580 
581                 // Key of dict
582                 case text:
583                     checkNodeType(*curr, object, lastPos);
584                     (*curr)[key.get!string] = expr;
585                     break;
586 
587                 default:
588                     assertJinja(0, "Unknown attribute %s for %s".fmt(key.toString, node.name, node.subIdents[$-1].pos));
589             }
590         }
591     }
592 
593     override void visit(StringNode node)
594     {
595         push(UniNode(node.str));
596     }
597 
598     override void visit(ListNode node)
599     {
600         UniNode[] list = [];
601         foreach (l; node.list)
602         {
603             tryAccept(l);
604             list ~= pop();
605         }
606         push(UniNode(list));
607     }
608 
609     override void visit(DictNode node)
610     {
611         UniNode[string] dict;
612         foreach (key, value; node.dict)
613         {
614             tryAccept(value);
615             dict[key] = pop();
616         }
617         push(UniNode(dict));
618     }
619 
620     override void visit(IfNode node)
621     {
622         tryAccept(node.cond);
623 
624         auto cond = pop();
625         cond.toBoolType;
626 
627         if (cond.get!bool)
628         {
629             tryAccept(node.then);
630         }
631         else if (node.other)
632         {
633             tryAccept(node.other);
634         }
635     }
636 
637     override void visit(ForNode node)
638     {
639         bool iterated = false;
640         int depth = 0; 
641 
642         bool calcCondition()
643         {
644             bool condition = true;
645             if (!node.cond.isNull)
646             {
647                 tryAccept(node.cond);
648                 auto cond = pop();
649                 cond.toBoolType;
650                 condition = cond.get!bool;
651             }
652             return condition;
653         }
654 
655         UniNode cycle(UniNode loop, UniNode varargs)
656         {
657             if (!varargs.length)
658                 return UniNode(null);
659             return varargs[loop["index0"].get!long % varargs.length];
660         }
661 
662 
663         void loop(UniNode iterable)
664         {
665             Nullable!UniNode lastVal;
666             bool changed(UniNode loop, UniNode val)
667             {
668                 if (!lastVal.isNull && val == lastVal.get)
669                     return false;
670                 lastVal = val;
671                 return true;
672             }
673 
674             depth++;
675             pushNewContext();
676 
677             iterable.toIterableNode;
678 
679             if (!node.cond.isNull)
680             {
681                 auto newIterable = UniNode.emptyArray;
682                 for (int i = 0; i < iterable.length; i++)
683                 {
684                     if (node.keys.length == 1)
685                         _context.data[node.keys[0]] = iterable[i];
686                     else
687                     {
688                         iterable[i].checkNodeType(UniNode.Kind.array, node.iterable.pos);
689                         assertJinja(iterable[i].length >= node.keys.length, "Num of keys less then values", node.iterable.pos);
690                         foreach(j, key; node.keys)
691                             _context.data[key] = iterable[i][j];
692                     }
693 
694                     if (calcCondition())
695                         newIterable ~= iterable[i];
696                 }
697                 iterable = newIterable;
698             }
699 
700             _context.data["loop"] = UniNode.emptyObject;
701             _context.data["loop"]["length"] = UniNode(iterable.length);
702             _context.data["loop"]["depth"] = UniNode(depth);
703             _context.data["loop"]["depth0"] = UniNode(depth - 1);
704             _context.functions["cycle"] = wrapper!cycle;
705             _context.functions["changed"] = wrapper!changed;
706 
707             for (int i = 0; i < iterable.length; i++)
708             {
709                 _context.data["loop"]["index"] = UniNode(i + 1);
710                 _context.data["loop"]["index0"] = UniNode(i);
711                 _context.data["loop"]["revindex"] = UniNode(iterable.length - i);
712                 _context.data["loop"]["revindex0"] = UniNode(iterable.length - i - 1);
713                 _context.data["loop"]["first"] = UniNode(i == 0);
714                 _context.data["loop"]["last"] = UniNode(i == iterable.length - 1);
715                 _context.data["loop"]["previtem"] = i > 0 ? iterable[i - 1] : UniNode(null);
716                 _context.data["loop"]["nextitem"] = i < iterable.length - 1 ? iterable[i + 1] : UniNode(null);
717 
718                 if (node.isRecursive)
719                     _context.functions["loop"] = wrapper!loop;
720 
721                 if (node.keys.length == 1)
722                     _context.data[node.keys[0]] = iterable[i];
723                 else
724                 {
725                     iterable[i].checkNodeType(UniNode.Kind.array, node.iterable.pos);
726                     assertJinja(iterable[i].length >= node.keys.length, "Num of keys less then values", node.iterable.pos);
727                     foreach(j, key; node.keys)
728                         _context.data[key] = iterable[i][j];
729                 }
730 
731                 tryAccept(node.block);
732                 iterated = true;
733             }
734             popContext();
735             depth--;
736         }
737 
738 
739 
740         tryAccept(node.iterable);
741         UniNode iterable = pop();
742         loop(iterable);
743 
744         if (!iterated && !node.other.isNull)
745             tryAccept(node.other);
746     }
747 
748 
749     override void visit(SetNode node)
750     {
751         tryAccept(node.expr);
752 
753         if (node.assigns.length == 1)
754             tryAccept(node.assigns[0]);            
755         else
756         {
757             auto expr = pop();
758             expr.checkNodeType(UniNode.Kind.array, node.expr.pos);
759             
760             if (expr.length < node.assigns.length)
761                 assertJinja(0, "Iterable length less then number of assigns", node.expr.pos);
762 
763             foreach(idx, assign; node.assigns)
764             {
765                 push(expr[idx]);
766                 tryAccept(assign);
767             }
768         }
769     }
770 
771 
772     override void visit(MacroNode node)
773     {
774         FormArg[] args;
775 
776         foreach(arg; node.args)
777         {
778             if (arg.defaultExpr.isNull)
779                 args ~= FormArg(arg.name);
780             else
781             {
782                 tryAccept(arg.defaultExpr);
783                 args ~= FormArg(arg.name, pop());
784             }
785         }
786 
787         _context.macros[node.name] = Macro(args, _context, node.block);
788     }
789 
790 
791     override void visit(CallNode node)
792     {
793         FormArg[] args;
794 
795         foreach(arg; node.formArgs)
796         {
797             if (arg.defaultExpr.isNull)
798                 args ~= FormArg(arg.name);
799             else
800             {
801                 tryAccept(arg.defaultExpr);
802                 args ~= FormArg(arg.name, pop());
803             }
804         }
805 
806         auto caller = Macro(args, _context, node.block);
807 
808         tryAccept(node.factArgs);
809         auto factArgs = pop();
810 
811         visitMacro(node.macroName, factArgs, caller.nullable);
812     }
813 
814 
815     override void visit(FilterBlockNode node)
816     {
817         tryAccept(node.args);
818         auto args = pop();
819 
820         pushFilter(node.filterName, args);
821         tryAccept(node.block);
822         popFilter();
823     }
824 
825 
826     override void visit(ImportNode node)
827     {
828         if (node.tmplBlock.isNull)
829             return;
830 
831         auto stashedContext = _context;
832         auto stashedResult = _renderedResult;
833 
834         if (!node.withContext)
835             _context = _globalContext;
836 
837         _renderedResult = "";
838 
839         pushNewContext();
840 
841         foreach (child; node.tmplBlock.stmt.children)
842             tryAccept(child);
843 
844         auto macros = _context.macros;
845 
846         popContext();
847 
848         _renderedResult = stashedResult;
849 
850         if (!node.withContext)
851             _context = stashedContext;
852 
853         if (node.macrosNames.length)
854             foreach (name; node.macrosNames)
855             {
856                 assertJinja(cast(bool)(name.was in macros), "Undefined macro `%s` in `%s`".fmt(name.was, node.fileName), node.pos);
857                 _context.macros[name.become] = macros[name.was];
858             }
859         else
860             foreach (key, val; macros)
861                 _context.macros[key] = val;
862     }
863 
864 
865     override void visit(IncludeNode node)
866     {
867         if (node.tmplBlock.isNull)
868             return;
869 
870         auto stashedContext = _context;
871 
872         if (!node.withContext)
873             _context = _globalContext;
874 
875         tryAccept(node.tmplBlock);
876 
877         if (!node.withContext)
878             _context = stashedContext;
879     }
880 
881 
882     override void visit(ExtendsNode node)
883     {
884         _extends ~= node.tmplBlock;
885         tryAccept(node.tmplBlock);
886         _extends.popBack;
887         _isExtended = true;
888     }
889 
890 private:
891 
892     void tryAccept(Node node)
893     {
894         if (!_isExtended)
895             node.accept(this);
896     }
897 
898 
899     UniNode visitFunc(string name, UniNode args)
900     {
901         return _context.getFunc(name)(args);
902     }
903 
904 
905     UniNode visitMacro(string name, UniNode args, Nullable!Macro caller = Nullable!Macro.init)
906     {
907         UniNode result;
908 
909         auto macro_ = _context.getMacro(name);
910         auto stashedContext = _context;
911         _context = macro_.context.get;
912         pushNewContext();
913 
914         UniNode[] varargs;
915         UniNode[string] kwargs;
916 
917         foreach(arg; macro_.args)
918             if (!arg.def.isNull)
919                 _context.data[arg.name] = arg.def;
920 
921         for(int i = 0; i < args["varargs"].length; i++)
922         {
923             if (i < macro_.args.length)
924                 _context.data[macro_.args[i].name] = args["varargs"][i];
925             else
926                 varargs ~= args["varargs"][i];
927         }
928 
929         foreach (string key, value; args["kwargs"])
930         {
931             if (macro_.args.has(key))
932                 _context.data[key] = value;
933             else
934                 kwargs[key] = value;
935         }
936 
937         _context.data["varargs"] = UniNode(varargs);
938         _context.data["kwargs"] = UniNode(kwargs);
939 
940         foreach(arg; macro_.args)
941             if (arg.name !in _context.data)
942                 assertJinja(0, "Missing value for argument `%s` in macro `%s`".fmt(arg.name, name));
943 
944         if (!caller.isNull)
945             _context.macros["caller"] = caller;
946 
947         tryAccept(macro_.block);
948         result = pop();
949 
950         popContext();
951         _context = stashedContext;
952 
953         return result;
954     }
955 
956 
957     void writeToResult(string str)
958     {
959         if (!_appliedFilters.length)
960         {
961             _renderedResult ~= str;
962         }
963         else
964         {
965             UniNode curr = UniNode(str); 
966 
967 
968             foreach_reverse (filter; _appliedFilters)
969             {
970                 auto args = filter.args;
971                 args["varargs"] = UniNode([curr] ~ args["varargs"].get!(UniNode[]));
972 
973                 if (_context.hasFunc(filter.name))
974                     curr = visitFunc(filter.name, args);
975                 else if (_context.hasMacro(filter.name))
976                     curr = visitMacro(filter.name, args);
977                 else
978                     assert(0);
979 
980                 curr.toStringType;
981             }
982 
983             _renderedResult ~= curr.get!string;
984         }
985     }
986 
987 
988 private:
989 
990 
991     void pushNewContext()
992     {
993         _context = new Context(_context, UniNode.emptyObject);
994     }
995 
996 
997     void popContext()
998     {
999         _context = _context.previos;
1000     }
1001 
1002     
1003     void push(UniNode un)
1004     {
1005         _dataStack ~= un;
1006     }
1007 
1008 
1009     UniNode pop()
1010     {
1011         if (!_dataStack.length)
1012             assertJinja(0, "Unexpected empty stack");
1013 
1014         auto un = _dataStack.back;
1015         _dataStack.popBack;
1016         return un;
1017     }
1018 
1019 
1020     void pushFilter(string name, UniNode args)
1021     {
1022         _appliedFilters ~= AppliedFilter(name, args);
1023     }
1024 
1025 
1026     void popFilter()
1027     {
1028         if (!_appliedFilters.length)
1029             assertJinja(0, "Unexpected empty filter stack");
1030 
1031         _appliedFilters.popBack;
1032     }
1033 }
1034 
1035 
1036 void registerFunction(alias func)(Render render, string name)
1037 {
1038     render._rootContext.functions[name] = wrapper!func;
1039 }
1040 
1041 
1042 void registerFunction(alias func)(Render render)
1043 {
1044     enum name = __traits(identifier, func);
1045     render._rootContext.functions[name] = wrapper!func;
1046 }
1047 
1048 
1049 private:
1050 
1051 
1052 bool has(FormArg[] arr, string name)
1053 {
1054     foreach(a; arr)
1055         if (a.name == name)
1056             return true;
1057     return false;
1058 }