Browse Source

Support for {% empty %} block in for loops.

Evan Miller 15 years ago
parent
commit
25abda55eb

+ 1 - 1
src/erlydtl/erlydtl.app

@@ -1,7 +1,7 @@
 %% -*- mode: erlang -*-
 {application, erlydtl,
  [{description, "ErlyDTL implements most but not all of the Django Template Language"},
-  {vsn, "0.5.3"},
+  {vsn, "0.6.0"},
   {modules, [
              erlydtl,
              erlydtl_compiler,

+ 34 - 19
src/erlydtl/erlydtl_compiler.erl

@@ -352,7 +352,11 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
                 {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
                 ifelse_ast({'expr', "ne", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);                    
             ({'for', {'in', IteratorList, Variable}, Contents}, TreeWalkerAcc) ->
-                for_loop_ast(IteratorList, Variable, Contents, Context, TreeWalkerAcc);
+                {EmptyAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
+                for_loop_ast(IteratorList, Variable, Contents, EmptyAstInfo, Context, TreeWalker1);
+            ({'for', {'in', IteratorList, Variable}, Contents, EmptyPartContents}, TreeWalkerAcc) ->
+                {EmptyAstInfo, TreeWalker1} = body_ast(EmptyPartContents, Context, TreeWalkerAcc),
+                for_loop_ast(IteratorList, Variable, Contents, EmptyAstInfo, Context, TreeWalker1);
             ({'load', Names}, TreeWalkerAcc) ->
                 load_ast(Names, Context, TreeWalkerAcc);
             ({'tag', {identifier, _, Name}, Args}, TreeWalkerAcc) ->
@@ -610,11 +614,11 @@ ifelse_ast(Expression, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseCo
         ]), merge_info(ExpressionInfo, Info)}, TreeWalker1}.
 
 
-for_loop_ast(IteratorList, Variable, Contents, Context, TreeWalker) ->
+for_loop_ast(IteratorList, LoopValue, Contents, {EmptyContentsAst, EmptyContentsInfo}, Context, TreeWalker) ->
     Vars = lists:map(fun({identifier, _, Iterator}) -> 
                     erl_syntax:variable("Var_" ++ Iterator) 
             end, IteratorList),
-    {{InnerAst, Info}, TreeWalker2} = body_ast(Contents,
+    {{InnerAst, Info}, TreeWalker1} = body_ast(Contents,
         Context#dtl_context{local_scopes = [
                 [{'forloop', erl_syntax:variable("Counters")} | lists:map(
                     fun({identifier, _, Iterator}) ->
@@ -622,26 +626,37 @@ for_loop_ast(IteratorList, Variable, Contents, Context, TreeWalker) ->
                     end, IteratorList)] | Context#dtl_context.local_scopes]}, TreeWalker),
     CounterAst = erl_syntax:application(erl_syntax:atom(erlydtl_runtime), 
         erl_syntax:atom(increment_counter_stats), [erl_syntax:variable("Counters")]),
-    {ListAst, VarName} = resolve_variable_ast(Variable, Context),
+
+    {{LoopValueAst, LoopValueInfo}, TreeWalker2} = value_ast(LoopValue, false, Context, TreeWalker1),
+
     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]);
+            erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(init_counter_stats), [LoopValueAst]);
         Value ->
-            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), [LoopValueAst, 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), erl_syntax:variable("Counters")], none, 
-                                    [erl_syntax:tuple([InnerAst, CounterAst])]),
-                                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])]),
-                Info#ast_info{var_names = [VarName]}}, TreeWalker2}.
+    {{erl_syntax:case_expr(
+                    erl_syntax:application(
+                            erl_syntax:atom('lists'), erl_syntax:atom('mapfoldl'),
+                            [erl_syntax:fun_expr([
+                                        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, erl_syntax:variable("Counters")];
+                                                _ -> [erl_syntax:list(Vars), erl_syntax:variable("Counters")] end, none, 
+                                            [erl_syntax:tuple([InnerAst, CounterAst])])
+                                    ]),
+                                CounterVars0, LoopValueAst]),
+                        [erl_syntax:clause(
+                                [erl_syntax:tuple([erl_syntax:underscore(), 
+                                    erl_syntax:list([erl_syntax:tuple([erl_syntax:atom(counter), erl_syntax:integer(1)])], 
+                                        erl_syntax:underscore())])],
+                                none, [EmptyContentsAst]),
+                            erl_syntax:clause(
+                                [erl_syntax:tuple([erl_syntax:variable("L"), erl_syntax:underscore()])],
+                                none, [erl_syntax:variable("L")])]
+                    ),
+                    merge_info(merge_info(Info, EmptyContentsInfo), LoopValueInfo)
+            }, TreeWalker2}.
 
 load_ast(Names, _Context, TreeWalker) ->
     CustomTags = lists:merge([X || {identifier, _ , X} <- Names], TreeWalker#treewalker.custom_tags),

+ 4 - 0
src/erlydtl/erlydtl_parser.yrl

@@ -60,6 +60,7 @@ Nonterminals
 
     ForBlock
     ForBraced
+    EmptyBraced
     EndForBraced
     ForExpression
     ForGroup
@@ -114,6 +115,7 @@ Terminals
     cycle_keyword
     dot
     else_keyword
+    empty_keyword
     endautoescape_keyword
     endblock_keyword
     endcomment_keyword
@@ -221,6 +223,8 @@ FirstofValues -> FirstofValues Value : ['$2'|'$1'].
 FirstofValues -> Value : ['$1'].
 
 ForBlock -> ForBraced Elements EndForBraced : {for, '$1', '$2'}.
+ForBlock -> ForBraced Elements EmptyBraced Elements EndForBraced : {for, '$1', '$2', '$4'}.
+EmptyBraced -> open_tag empty_keyword close_tag.
 ForBraced -> open_tag for_keyword ForExpression close_tag : '$3'.
 EndForBraced -> open_tag endfor_keyword close_tag.
 ForExpression -> ForGroup in_keyword Variable : {'in', '$1', '$3'}.

+ 1 - 1
src/erlydtl/erlydtl_scanner.erl

@@ -57,7 +57,7 @@ scan([], Scanned, _, in_text) ->
                 fun
                     ({identifier, Pos, String}) ->
                         RevString = lists:reverse(String),
-                        Keywords = ["for", "endfor", "in", "include", "block", "endblock",
+                        Keywords = ["for", "empty", "endfor", "in", "include", "block", "endblock",
                             "extends", "autoescape", "endautoescape", "if", "else", "endif",
                             "not", "or", "and", "comment", "endcomment", "cycle", "firstof",
                             "ifchanged", "ifequal", "endifequal", "ifnotequal", "endifnotequal",

+ 8 - 0
src/tests/erlydtl_unittests.erl

@@ -248,6 +248,14 @@ tests() ->
                     <<"{% 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">>}
             ]},
+        {"for/empty", [
+                {"Simple loop",
+                    <<"{% for x in list %}{{ x }}{% empty %}shucks{% endfor %}">>, [{'list', ["1", "2", "3"]}],
+                    <<"123">>},
+                {"Simple loop (empty)",
+                    <<"{% for x in list %}{{ x }}{% empty %}shucks{% endfor %}">>, [{'list', []}],
+                    <<"shucks">>}
+            ]},
         {"ifequal", [
                 {"Compare variable to literal",
                     <<"{% ifequal var1 \"foo\" %}yay{% endifequal %}">>,