Просмотр исходного кода

1. Can resolve variables from function calls to parameterized modules, e.g.

    -module(foo, [Var]).
    get_var() -> Var.
    ...
    render([{var1, foo:new("bar")}]).
    ...
    {{ var1.get_var }} => <<"bar">>

2. Support for recursive variable attributes, e.g. {{ var.attr.attr.attr }}

Tests added for both.
Evan Miller 17 лет назад
Родитель
Сommit
b4bf177fc2

+ 34 - 36
src/erlydtl/erlydtl_compiler.erl

@@ -299,34 +299,39 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
             ({'comment', _Contents}, TreeWalkerAcc) ->
                 empty_ast(TreeWalkerAcc);
             ({'autoescape', {identifier, _, OnOrOff}, Contents}, TreeWalkerAcc) ->
-                body_ast(Contents, Context#dtl_context{auto_escape = list_to_atom(OnOrOff)}, TreeWalkerAcc);
+                body_ast(Contents, Context#dtl_context{auto_escape = list_to_atom(OnOrOff)}, 
+                    TreeWalkerAcc);
             ({'text', _Pos, String}, TreeWalkerAcc) -> 
                 string_ast(String, TreeWalkerAcc);
             ({'string_literal', _Pos, String}, TreeWalkerAcc) ->
-                {{auto_escape(erl_syntax:string(unescape_string_literal(String)), Context), #ast_info{}}, TreeWalkerAcc};
+                {{auto_escape(erl_syntax:string(unescape_string_literal(String)), Context), 
+                        #ast_info{}}, TreeWalkerAcc};
             ({'number_literal', _Pos, Number}, TreeWalkerAcc) ->
                 string_ast(Number, TreeWalkerAcc);
-            ({'variable', Variable}, TreeWalkerAcc) ->
+            ({'attribute', _} = Variable, TreeWalkerAcc) ->
+                {Ast, VarName} = resolve_variable_ast(Variable, Context),
+                {{format(Ast, Context), #ast_info{var_names = [VarName]}}, TreeWalkerAcc};
+            ({'variable', _} = Variable, TreeWalkerAcc) ->
                 {Ast, VarName} = resolve_variable_ast(Variable, Context),
                 {{format(Ast, Context), #ast_info{var_names = [VarName]}}, TreeWalkerAcc};              
             ({'include', {string_literal, _, File}}, TreeWalkerAcc) ->
                 include_ast(unescape_string_literal(File), Context, TreeWalkerAcc);
-            ({'if', {variable, Variable}, Contents}, TreeWalkerAcc) ->
-                {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
-                {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
-                ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
-            ({'if', {'not', {variable, Variable}}, Contents}, TreeWalkerAcc) ->
+            ({'if', {'not', Variable}, Contents}, TreeWalkerAcc) ->
                 {IfAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
                 {ElseAstInfo, TreeWalker2} = body_ast(Contents, Context, TreeWalker1),
                 ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
-            ({'ifelse', {variable, Variable}, IfContents, ElseContents}, TreeWalkerAcc) ->
-                {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
-                {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
+            ({'if', Variable, Contents}, TreeWalkerAcc) ->
+                {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
+                {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
                 ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
-            ({'ifelse', {'not', {variable, Variable}}, IfContents, ElseContents}, TreeWalkerAcc) ->
+            ({'ifelse', {'not', Variable}, IfContents, ElseContents}, TreeWalkerAcc) ->
                 {IfAstInfo, TreeWalker1} = body_ast(ElseContents, Context, TreeWalkerAcc),
                 {ElseAstInfo, TreeWalker2} = body_ast(IfContents, Context, TreeWalker1),
                 ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);                  
+            ({'ifelse', Variable, IfContents, ElseContents}, TreeWalkerAcc) ->
+                {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
+                {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
+                ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
             ({'ifequal', Args, Contents}, TreeWalkerAcc) ->
                 {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
                 {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
@@ -353,8 +358,8 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
                 tag_ast(Name, Args, Context, TreeWalkerAcc);            
             ({'call', {'identifier', _, Name}}, TreeWalkerAcc) ->
             	call_ast(Name, TreeWalkerAcc);
-            ({'call', {'identifier', _, Name}, {variable, With}}, TreeWalkerAcc) ->
-            	call_with_ast(Name, With, Context, TreeWalkerAcc)                
+            ({'call', {'identifier', _, Name}, With}, TreeWalkerAcc) ->
+            	call_with_ast(Name, With, Context, TreeWalkerAcc)
         end, TreeWalker, DjangoParseTree),   
     {AstList, {Info, TreeWalker3}} = lists:mapfoldl(
         fun({Ast, Info}, {InfoAcc, TreeWalkerAcc}) -> 
@@ -481,33 +486,26 @@ resolve_variable_ast(VarTuple, Context) ->
 resolve_ifvariable_ast(VarTuple, Context) ->
     resolve_variable_ast(VarTuple, Context, 'find_value').
            
-resolve_variable_ast({{identifier, _, VarName}}, Context, FinderFunction) ->
-    {resolve_variable_name_ast(VarName, Context, FinderFunction), VarName};
-
-resolve_variable_ast({{identifier, _, VarName}, {identifier, _, AttrName}}, Context, FinderFunction) ->
+resolve_variable_ast({attribute, {{identifier, _, AttrName}, Variable}}, Context, FinderFunction) ->
+    {VarAst, VarName} = resolve_variable_ast(Variable, Context, FinderFunction),
     {erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(FinderFunction),
-                    [erl_syntax:atom(AttrName), resolve_variable_name_ast(VarName, Context)]), VarName}.
+                    [erl_syntax:atom(AttrName), VarAst]), VarName};
 
-resolve_variable_name_ast(VarName, Context) ->
-    resolve_variable_name_ast(VarName, Context, 'fetch_value').
-    
-resolve_variable_name_ast(VarName, Context, FinderFunction) ->
+resolve_variable_ast({variable, {identifier, _, VarName}}, Context, FinderFunction) ->
     VarValue = lists:foldl(fun(Scope, Value) ->
                 case Value of
-                    undefined ->
-                        proplists:get_value(list_to_atom(VarName), Scope);
-                    _ ->
-                        Value
+                    undefined -> proplists:get_value(list_to_atom(VarName), Scope);
+                    _ -> Value
                 end
         end, undefined, Context#dtl_context.local_scopes),
-    case VarValue of
+    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.
-
+    end,
+    {VarValue1, VarName}.
 
 format(Ast, Context) ->
     auto_escape(format_integer_ast(Ast), Context).
@@ -545,13 +543,13 @@ ifequalelse_ast(Args, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseCon
     {[Arg1Ast, Arg2Ast], VarNames} = lists:foldl(fun
             (X, {Asts, AccVarNames}) ->
                 case X of
-                    {variable, Var} ->
-                        {Ast, VarName} = resolve_ifvariable_ast(Var, Context),
-                        {[Ast | Asts], [VarName | AccVarNames]};
                     {string_literal, _, Literal} ->
                         {[erl_syntax:string(unescape_string_literal(Literal)) | Asts], AccVarNames};
                     {number_literal, _, Literal} ->
-                        {[erl_syntax:integer(list_to_integer(Literal)) | Asts], AccVarNames}
+                        {[erl_syntax:integer(list_to_integer(Literal)) | Asts], AccVarNames};
+                    Variable ->
+                        {Ast, VarName} = resolve_ifvariable_ast(Variable, Context),
+                        {[Ast | Asts], [VarName | AccVarNames]}
                 end                
         end,
         {[], Info#ast_info.var_names},
@@ -565,7 +563,7 @@ ifequalelse_ast(Args, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseCon
     {{Ast, Info#ast_info{var_names = VarNames}}, TreeWalker}.         
 
 
-for_loop_ast(IteratorList, {variable, Variable}, Contents, Context, TreeWalker) ->
+for_loop_ast(IteratorList, Variable, Contents, Context, TreeWalker) ->
     Vars = lists:map(fun({identifier, _, Iterator}) -> 
                     erl_syntax:variable("Var_" ++ Iterator) 
             end, IteratorList),
@@ -641,7 +639,7 @@ tag_ast(Name, Args, Context, TreeWalker) ->
             InterpretedArgs = lists:map(fun
                     ({{identifier, _, Key}, {string_literal, _, Value}}) ->
                         {list_to_atom(Key), erl_syntax:string(unescape_string_literal(Value))};
-                    ({{identifier, _, Key}, {variable, Value}}) ->
+                    ({{identifier, _, Key}, Value}) ->
                         {list_to_atom(Key), format(resolve_variable_ast(Value, Context), Context)}
                 end, Args),
             DefaultFilePath = filename:join([erlydtl_deps:get_base_dir(), "priv", "custom_tags", Name]),

+ 17 - 16
src/erlydtl/erlydtl_parser.yrl

@@ -36,7 +36,7 @@ Nonterminals
     Elements
     Literal
 
-    VariableBraced
+    ValueBraced
 
     ExtendsTag
     IncludeTag
@@ -75,6 +75,7 @@ Nonterminals
     AutoEscapeBraced
     EndAutoEscapeBraced
 
+    Value
     Variable
     Filter
     
@@ -129,7 +130,7 @@ Rootsymbol
 
 Elements -> '$empty' : [].
 Elements -> Elements text : '$1' ++ ['$2'].
-Elements -> Elements VariableBraced : '$1' ++ ['$2'].
+Elements -> Elements ValueBraced : '$1' ++ ['$2'].
 Elements -> Elements ExtendsTag : '$1' ++ ['$2'].
 Elements -> Elements IncludeTag : '$1' ++ ['$2'].
 Elements -> Elements LoadTag : '$1' ++ ['$2'].
@@ -144,13 +145,14 @@ Elements -> Elements CustomTag : '$1' ++ ['$2'].
 Elements -> Elements CallTag : '$1' ++ ['$2'].
 Elements -> Elements CallWithTag : '$1' ++ ['$2'].
 
-VariableBraced -> open_var Variable close_var : '$2'.
+ValueBraced -> open_var Value close_var : '$2'.
 
-Variable -> Variable pipe Filter : {apply_filter, '$1', '$3'}.
-Variable -> identifier : {variable, {'$1'}}.
-Variable -> identifier dot identifier : {variable, {'$1', '$3'}}.
-Variable -> string_literal : '$1'.
-Variable -> number_literal : '$1'.
+Value -> Value pipe Filter : {apply_filter, '$1', '$3'}.
+Value -> Variable : '$1'.
+Value -> Literal : '$1'.
+
+Variable -> identifier : {variable, '$1'}.
+Variable -> Value dot identifier : {attribute, {'$3', '$1'}}.
 
 ExtendsTag -> open_tag extends_keyword string_literal close_tag : {extends, '$3'}.
 IncludeTag -> open_tag include_keyword string_literal close_tag : {include, '$3'}.
@@ -170,7 +172,6 @@ EndCommentBraced -> open_tag endcomment_keyword close_tag.
 ForBlock -> ForBraced Elements EndForBraced : {for, '$1', '$2'}.
 ForBraced -> open_tag for_keyword ForExpression close_tag : '$3'.
 EndForBraced -> open_tag endfor_keyword close_tag.
-ForExpression -> Variable : '$1'.
 ForExpression -> ForGroup in_keyword Variable : {'in', '$1', '$3'}.
 ForGroup -> identifier : ['$1'].
 ForGroup -> ForGroup comma identifier : '$1' ++ ['$3'].
@@ -179,21 +180,21 @@ IfBlock -> IfBraced Elements ElseBraced Elements EndIfBraced : {ifelse, '$1', '$
 IfBlock -> IfBraced Elements EndIfBraced : {'if', '$1', '$2'}.
 IfBraced -> open_tag if_keyword IfExpression close_tag : '$3'.
 IfExpression -> not_keyword IfExpression : {'not', '$2'}.
-IfExpression -> Variable : '$1'.
+IfExpression -> Value : '$1'.
 
 ElseBraced -> open_tag else_keyword close_tag.
 EndIfBraced -> open_tag endif_keyword close_tag.
 
 IfEqualBlock -> IfEqualBraced Elements ElseBraced Elements EndIfEqualBraced : {ifequalelse, '$1', '$2', '$4'}.
 IfEqualBlock -> IfEqualBraced Elements EndIfEqualBraced : {ifequal, '$1', '$2'}.
-IfEqualBraced -> open_tag ifequal_keyword IfEqualExpression Variable close_tag : ['$3', '$4'].
-IfEqualExpression -> Variable : '$1'.
+IfEqualBraced -> open_tag ifequal_keyword IfEqualExpression Value close_tag : ['$3', '$4'].
+IfEqualExpression -> Value : '$1'.
 EndIfEqualBraced -> open_tag endifequal_keyword close_tag.
 
 IfNotEqualBlock -> IfNotEqualBraced Elements ElseBraced Elements EndIfNotEqualBraced : {ifnotequalelse, '$1', '$2', '$4'}.
 IfNotEqualBlock -> IfNotEqualBraced Elements EndIfNotEqualBraced : {ifnotequal, '$1', '$2'}.
-IfNotEqualBraced -> open_tag ifnotequal_keyword IfNotEqualExpression Variable close_tag : ['$3', '$4'].
-IfNotEqualExpression -> Variable : '$1'.
+IfNotEqualBraced -> open_tag ifnotequal_keyword IfNotEqualExpression Value close_tag : ['$3', '$4'].
+IfNotEqualExpression -> Value : '$1'.
 EndIfNotEqualBraced -> open_tag endifnotequal_keyword close_tag.
 
 AutoEscapeBlock -> AutoEscapeBraced Elements EndAutoEscapeBraced : {autoescape, '$1', '$2'}.
@@ -209,7 +210,7 @@ Literal -> number_literal : '$1'.
 CustomTag -> open_tag identifier Args close_tag : {tag, '$2', '$3'}.
 
 Args -> '$empty' : [].
-Args -> Args identifier equal Variable : '$1' ++ [{'$2', '$4'}].
+Args -> Args identifier equal Value : '$1' ++ [{'$2', '$4'}].
 
 CallTag -> open_tag call_keyword identifier close_tag : {call, '$3'}.
-CallWithTag -> open_tag call_keyword identifier with_keyword Variable close_tag: {call, '$3', '$5'}.
+CallWithTag -> open_tag call_keyword identifier with_keyword Value close_tag : {call, '$3', '$5'}.

+ 18 - 7
src/erlydtl/erlydtl_runtime.erl

@@ -4,19 +4,30 @@
 
 find_value(Key, L) when is_list(L) ->
     proplists:get_value(Key, L);
-find_value(Key, {GBSize, GBData}) ->
+find_value(Key, {GBSize, GBData}) when is_integer(GBSize) ->
     case gb_trees:lookup(Key, {GBSize, GBData}) of
         {value, Val} ->
             Val;
         _ ->
             undefined
     end;
-find_value(Key, Dict) ->
-    case dict:find(Key, Dict) of
-        {ok, Val} ->
-            Val;
-        _ ->
-            undefined
+find_value(Key, Tuple) when is_tuple(Tuple) ->
+    Module = element(1, Tuple),
+    case Module of
+        dict -> 
+            case dict:find(Key, Tuple) of
+                {ok, Val} ->
+                    Val;
+                _ ->
+                    undefined
+            end;
+        Module ->
+            case proplists:get_value(Key, Module:module_info(exports)) of
+                1 ->
+                    Tuple:Key();
+                _ ->
+                    undefined
+            end
     end.
 
 fetch_value(Key, Data) ->

+ 5 - 0
src/tests/erlydtl_example_variable_storage.erl

@@ -0,0 +1,5 @@
+-module(erlydtl_example_variable_storage, [SomeVar]).
+-compile(export_all).
+
+some_var() ->
+    SomeVar.

+ 9 - 1
src/tests/erlydtl_unittests.erl

@@ -42,7 +42,12 @@ tests() ->
                 {"Render variable with attribute in dict",
                     <<"{{ var1.attr }}">>, [{var1, dict:store(attr, "Othello", dict:new())}], <<"Othello">>},
                 {"Render variable with attribute in gb_tree",
-                    <<"{{ var1.attr }}">>, [{var1, gb_trees:insert(attr, "Othello", gb_trees:empty())}], <<"Othello">>}
+                    <<"{{ var1.attr }}">>, [{var1, gb_trees:insert(attr, "Othello", gb_trees:empty())}], <<"Othello">>},
+                {"Render variable in parameterized module",
+                    <<"{{ var1.some_var }}">>, [{var1, erlydtl_example_variable_storage:new("foo")}], <<"foo">>},
+                {"Nested attributes",
+                    <<"{{ person.city.state.country }}">>, [{person, [{city, [{state, [{country, "Italy"}]}]}]}],
+                    <<"Italy">>}
             ]},
         {"if", [
                 {"If/else",
@@ -77,6 +82,9 @@ tests() ->
                 {"Resolve variable attribute",
                     <<"{% for number in person.numbers %}{{ number }}\n{% endfor %}">>, [{person, [{numbers, ["411", "911"]}]}],
                     <<"411\n911\n">>},
+                {"Resolve nested variable attribute",
+                    <<"{% for number in person.home.numbers %}{{ number }}\n{% endfor %}">>, [{person, [{home, [{numbers, ["411", "911"]}]}]}],
+                    <<"411\n911\n">>},
                 {"Nested for loop",
                     <<"{% for outer in list %}{% for inner in outer %}{{ inner }}\n{% endfor %}{% endfor %}">>,
                     [{'list', [["Al", "Albert"], ["Jo", "Joseph"]]}],