Commits

emmi...@a5195066-8e3e-0410-a82a-05b01b1b9875  committed c85f320

Support additional for loop variables: "revcounter", "revcounter0", "first", "last", "parentloop".

  • Participants
  • Parent commits fa760e8

Comments (0)

Files changed (3)

File src/erlydtl/erlydtl_compiler.erl

                     [erl_syntax:atom(AttrName), VarAst]), VarName};
 
 resolve_variable_ast({variable, {identifier, _, VarName}}, Context, FinderFunction) ->
-    VarValue = lists:foldl(fun(Scope, Value) ->
+    VarValue = case resolve_scoped_variable_ast(VarName, Context) of
+        undefined ->
+            erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(FinderFunction),
+                [erl_syntax:atom(VarName), erl_syntax:variable("Variables")]);
+        Val ->
+            Val
+    end,
+    {VarValue, VarName}.
+
+resolve_scoped_variable_ast(VarName, Context) ->
+    lists:foldl(fun(Scope, Value) ->
                 case Value of
                     undefined -> proplists:get_value(list_to_atom(VarName), Scope);
                     _ -> Value
                 end
-        end, undefined, Context#dtl_context.local_scopes),
-    VarValue1 = case VarValue of
-        undefined ->
-            erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(FinderFunction),
-                [erl_syntax:atom(VarName), erl_syntax:variable("Variables")]);
-        _ ->
-            VarValue
-    end,
-    {VarValue1, VarName}.
+        end, undefined, Context#dtl_context.local_scopes).
 
 format(Ast, Context) ->
     auto_escape(format_integer_ast(Ast), Context).
     Vars = lists:map(fun({identifier, _, Iterator}) -> 
                     erl_syntax:variable("Var_" ++ Iterator) 
             end, IteratorList),
-    CounterVars = erl_syntax:list([
-            erl_syntax:tuple([erl_syntax:atom('counter'), erl_syntax:variable("Counter")]),
-            erl_syntax:tuple([erl_syntax:atom('counter0'), erl_syntax:variable("Counter0")])
-        ]),
     {{InnerAst, Info}, TreeWalker2} = body_ast(Contents,
         Context#dtl_context{local_scopes = [
-                [{'forloop', CounterVars} | lists:map(
+                [{'forloop', erl_syntax:variable("Counters")} | lists:map(
                     fun({identifier, _, Iterator}) ->
                             {list_to_atom(Iterator), erl_syntax:variable("Var_" ++ Iterator)} 
                     end, IteratorList)] | Context#dtl_context.local_scopes]}, TreeWalker),
-    CounterAst = erl_syntax:list([
-            erl_syntax:tuple([erl_syntax:atom('counter'), 
-                    erl_syntax:infix_expr(erl_syntax:variable("Counter"), erl_syntax:operator("+"), erl_syntax:integer(1))]),
-            erl_syntax:tuple([erl_syntax:atom('counter0'),
-                    erl_syntax:infix_expr(erl_syntax:variable("Counter0"), erl_syntax:operator("+"), erl_syntax:integer(1))])
-        ]),
+    CounterAst = erl_syntax:application(erl_syntax:atom(erlydtl_runtime), 
+        erl_syntax:atom(increment_counter_stats), [erl_syntax:variable("Counters")]),
     {ListAst, VarName} = resolve_ifvariable_ast(Variable, Context),
-    CounterVars0 = erl_syntax:list([
-            erl_syntax:tuple([erl_syntax:atom('counter'), erl_syntax:integer(1)]),
-            erl_syntax:tuple([erl_syntax:atom('counter0'), erl_syntax:integer(0)])
-        ]),
+    CounterVars0 = case resolve_scoped_variable_ast("forloop", Context) of
+        undefined ->
+            erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(init_counter_stats), [ListAst]);
+        Value ->
+            erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(init_counter_stats), [ListAst, Value])
+    end,
     {{erl_syntax:application(
             erl_syntax:atom('erlang'), erl_syntax:atom('element'),
             [erl_syntax:integer(1), erl_syntax:application(
                     erl_syntax:atom('lists'), erl_syntax:atom('mapfoldl'),
                     [erl_syntax:fun_expr([
-                                erl_syntax:clause([erl_syntax:tuple(Vars), CounterVars], none, 
+                                erl_syntax:clause([erl_syntax:tuple(Vars), erl_syntax:variable("Counters")], none, 
                                     [erl_syntax:tuple([InnerAst, CounterAst])]),
-                                erl_syntax:clause(case Vars of [H] -> [H, CounterVars];
-                                        _ -> [erl_syntax:list(Vars), CounterVars] end, none, 
+                                erl_syntax:clause(case Vars of [H] -> [H, erl_syntax:variable("Counters")];
+                                        _ -> [erl_syntax:list(Vars), erl_syntax:variable("Counters")] end, none, 
                                     [erl_syntax:tuple([InnerAst, CounterAst])])
                             ]),
                         CounterVars0, ListAst])]),

File src/erlydtl/erlydtl_runtime.erl

     true;
 is_false(_) ->
     false.
+
+init_counter_stats(List) ->
+    init_counter_stats(List, undefined).
+
+init_counter_stats(List, Parent) ->
+    [{counter, 1}, 
+        {counter0, 0}, 
+        {revcounter, length(List)}, 
+        {revcounter0, length(List) - 1}, 
+        {first, true}, 
+        {last, length(List) =:= 1},
+        {parentloop, Parent}].
+
+increment_counter_stats([{counter, Counter}, {counter0, Counter0}, {revcounter, RevCounter},
+        {revcounter0, RevCounter0}, {first, _}, {last, _}, {parentloop, Parent}]) ->
+    [{counter, Counter + 1},
+        {counter0, Counter0 + 1},
+        {revcounter, RevCounter - 1},
+        {revcounter0, RevCounter0 - 1},
+        {first, false}, {last, RevCounter0 =:= 1},
+        {parentloop, Parent}].

File src/tests/erlydtl_unittests.erl

                 {"Resolve nested variable attribute",
                     <<"{% for number in person.home.numbers %}{{ number }}\n{% endfor %}">>, [{person, [{home, [{numbers, ["411", "911"]}]}]}],
                     <<"411\n911\n">>},
+                {"Counter0",
+                    <<"{% for number in numbers %}{{ forloop.counter0 }}. {{ number }}\n{% endfor %}">>, 
+                    [{numbers, ["Zero", "One", "Two"]}], <<"0. Zero\n1. One\n2. Two\n">>},
+                {"Counter",
+                    <<"{% for number in numbers %}{{ forloop.counter }}. {{ number }}\n{% endfor %}">>, 
+                    [{numbers, ["One", "Two", "Three"]}], <<"1. One\n2. Two\n3. Three\n">>},
+                {"Reverse Counter0",
+                    <<"{% for number in numbers %}{{ forloop.revcounter0 }}. {{ number }}\n{% endfor %}">>, 
+                    [{numbers, ["Two", "One", "Zero"]}], <<"2. Two\n1. One\n0. Zero\n">>},
+                {"Reverse Counter",
+                    <<"{% for number in numbers %}{{ forloop.revcounter }}. {{ number }}\n{% endfor %}">>, 
+                    [{numbers, ["Three", "Two", "One"]}], <<"3. Three\n2. Two\n1. One\n">>},
+                {"Counter \"first\"",
+                    <<"{% for number in numbers %}{% if forloop.first %}{{ number }}{% endif %}{% endfor %}">>,
+                    [{numbers, ["One", "Two", "Three"]}], <<"One">>},
+                {"Counter \"last\"",
+                    <<"{% for number in numbers %}{% if forloop.last %}{{ number }}{% endif %}{% endfor %}">>,
+                    [{numbers, ["One", "Two", "Three"]}], <<"Three">>},
                 {"Nested for loop",
                     <<"{% for outer in list %}{% for inner in outer %}{{ inner }}\n{% endfor %}{% endfor %}">>,
                     [{'list', [["Al", "Albert"], ["Jo", "Joseph"]]}],
-                    <<"Al\nAlbert\nJo\nJoseph\n">>}
+                    <<"Al\nAlbert\nJo\nJoseph\n">>},
+                {"Access parent loop counters",
+                    <<"{% for outer in list %}{% for inner in outer %}({{ forloop.parentloop.counter0 }}, {{ forloop.counter0 }})\n{% endfor %}{% endfor %}">>,
+                    [{'list', [["One", "two"], ["One", "two"]]}], <<"(0, 0)\n(0, 1)\n(1, 0)\n(1, 1)\n">>}
             ]},
         {"ifequal", [
                 {"Compare variable to literal",