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 }