Browse Source

Add extension point to resolve_variable_ast.

For this, we need to pass the TreeWalker to resolve_variable_ast,
which affects the filter and cycle functions as well..

Allow the extension module to provide it's own find/fetch value runtime function.
Andreas Stenius 11 years ago
parent
commit
aa679b200f
1 changed files with 82 additions and 60 deletions
  1. 82 60
      src/erlydtl_compiler.erl

+ 82 - 60
src/erlydtl_compiler.erl

@@ -840,11 +840,9 @@ value_ast(ValueToken, AsString, EmptyIfUndefined, Context, TreeWalker) ->
         {'apply_filter', Variable, Filter} ->
         {'apply_filter', Variable, Filter} ->
             filter_ast(Variable, Filter, Context, TreeWalker);
             filter_ast(Variable, Filter, Context, TreeWalker);
         {'attribute', _} = Variable ->
         {'attribute', _} = Variable ->
-            {Ast, VarName} = resolve_variable_ast(Variable, Context, EmptyIfUndefined),
-            {{Ast, #ast_info{var_names = [VarName]}}, TreeWalker};
+            resolve_variable_ast(Variable, Context, TreeWalker, EmptyIfUndefined);
         {'variable', _} = Variable ->
         {'variable', _} = Variable ->
-            {Ast, VarName} = resolve_variable_ast(Variable, Context, EmptyIfUndefined),
-            {{Ast, #ast_info{var_names = [VarName]}}, TreeWalker};
+            resolve_variable_ast(Variable, Context, TreeWalker, EmptyIfUndefined);
         {extension, Tag} ->
         {extension, Tag} ->
             extension_ast(Tag, Context, TreeWalker)
             extension_ast(Tag, Context, TreeWalker)
     end.
     end.
@@ -1026,8 +1024,8 @@ filter_tag_ast(FilterList, Contents, Context, TreeWalker) ->
 								 ([{identifier, _, 'safeseq'}], {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
 								 ([{identifier, _, 'safeseq'}], {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
 								    {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{safe = true}};
 								    {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{safe = true}};
 								 (Filter, {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
 								 (Filter, {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
-								    {Ast, AstInfo} = filter_ast1(Filter, AstAcc, Context),
-								    {{Ast, merge_info(InfoAcc, AstInfo)}, TreeWalkerAcc}
+								    {{Ast, AstInfo}, TW} = filter_ast1(Filter, AstAcc, Context, TreeWalkerAcc),
+								    {{Ast, merge_info(InfoAcc, AstInfo)}, TW}
 							    end, {{erl_syntax:application(
 							    end, {{erl_syntax:application(
 								     erl_syntax:atom(erlang),
 								     erl_syntax:atom(erlang),
 								     erl_syntax:atom(iolist_to_binary),
 								     erl_syntax:atom(iolist_to_binary),
@@ -1090,36 +1088,44 @@ filter_ast_noescape(Variable, [{identifier, _, 'safeseq'}], Context, TreeWalker)
     value_ast(Variable, true, false, Context, TreeWalker#treewalker{safe = true});
     value_ast(Variable, true, false, Context, TreeWalker#treewalker{safe = true});
 filter_ast_noescape(Variable, Filter, Context, TreeWalker) ->
 filter_ast_noescape(Variable, Filter, Context, TreeWalker) ->
     {{VariableAst, Info1}, TreeWalker2} = value_ast(Variable, true, false, Context, TreeWalker),
     {{VariableAst, Info1}, TreeWalker2} = value_ast(Variable, true, false, Context, TreeWalker),
-    {VarValue, Info2} = filter_ast1(Filter, VariableAst, Context),
-    {{VarValue, merge_info(Info1, Info2)}, TreeWalker2}.
-
-filter_ast1([{identifier, _, Name}, {string_literal, _, ArgName}], VariableAst, #dtl_context{ binary_strings = true } = Context) ->
-    filter_ast2(Name, VariableAst, [binary_string(unescape_string_literal(ArgName))], [], Context);
-filter_ast1([{identifier, _, Name}, {string_literal, _, ArgName}], VariableAst, #dtl_context{ binary_strings = false } = Context) ->
-    filter_ast2(Name, VariableAst, [erl_syntax:string(unescape_string_literal(ArgName))], [], Context);
-filter_ast1([{identifier, _, Name}, {number_literal, _, ArgName}], VariableAst, Context) ->
-    filter_ast2(Name, VariableAst, [erl_syntax:integer(list_to_integer(ArgName))], [], Context);
-filter_ast1([{identifier, _, Name}, ArgVariable], VariableAst, Context) ->
-    {ArgAst, ArgVarName} = resolve_variable_ast(ArgVariable, Context, false),
-    filter_ast2(Name, VariableAst, [ArgAst], [ArgVarName], Context);
-filter_ast1([{identifier, _, Name}], VariableAst, Context) ->
-    filter_ast2(Name, VariableAst, [], [], Context).
-
-filter_ast2(Name, VariableAst, [], VarNames, #dtl_context{ filter_modules = [Module|Rest] } = Context) ->
+    {{VarValue, Info2}, TreeWalker3} = filter_ast1(Filter, VariableAst, Context, TreeWalker2),
+    {{VarValue, merge_info(Info1, Info2)}, TreeWalker3}.
+
+filter_ast1([{identifier, _, Name}, {string_literal, _, ArgName}], VariableAst,
+            #dtl_context{ binary_strings = true } = Context, TreeWalker) ->
+    filter_ast2(Name, VariableAst, [binary_string(unescape_string_literal(ArgName))], #ast_info{}, Context, TreeWalker);
+filter_ast1([{identifier, _, Name}, {string_literal, _, ArgName}], VariableAst,
+            #dtl_context{ binary_strings = false } = Context, TreeWalker) ->
+    filter_ast2(Name, VariableAst, [erl_syntax:string(unescape_string_literal(ArgName))], #ast_info{}, Context, TreeWalker);
+filter_ast1([{identifier, _, Name}, {number_literal, _, ArgName}], VariableAst, Context, TreeWalker) ->
+    filter_ast2(Name, VariableAst, [erl_syntax:integer(list_to_integer(ArgName))], #ast_info{}, Context, TreeWalker);
+filter_ast1([{identifier, _, Name}, ArgVariable], VariableAst, Context, TreeWalker) ->
+    {{ArgAst, ArgInfo}, TreeWalker2} = resolve_variable_ast(ArgVariable, Context, TreeWalker, false),
+    filter_ast2(Name, VariableAst, [ArgAst], ArgInfo, Context, TreeWalker2);
+filter_ast1([{identifier, _, Name}], VariableAst, Context, TreeWalker) ->
+    filter_ast2(Name, VariableAst, [], #ast_info{}, Context, TreeWalker).
+
+filter_ast2(Name, VariableAst, [], VarInfo, #dtl_context{ filter_modules = [Module|Rest] } = Context, TreeWalker) ->
     case lists:member({Name, 1}, Module:module_info(exports)) of
     case lists:member({Name, 1}, Module:module_info(exports)) of
         true ->
         true ->
-            {erl_syntax:application(erl_syntax:atom(Module), erl_syntax:atom(Name), 
-				    [VariableAst]), #ast_info{var_names = VarNames}};
+            {{erl_syntax:application(
+                erl_syntax:atom(Module), erl_syntax:atom(Name),
+                [VariableAst]),
+              VarInfo},
+             TreeWalker};
         false ->
         false ->
-            filter_ast2(Name, VariableAst, [], VarNames, Context#dtl_context{ filter_modules = Rest })
+            filter_ast2(Name, VariableAst, [], VarInfo, Context#dtl_context{ filter_modules = Rest }, TreeWalker)
     end;
     end;
-filter_ast2(Name, VariableAst, [Arg], VarNames, #dtl_context{ filter_modules = [Module|Rest] } = Context) ->
+filter_ast2(Name, VariableAst, [Arg], VarInfo, #dtl_context{ filter_modules = [Module|Rest] } = Context, TreeWalker) ->
     case lists:member({Name, 2}, Module:module_info(exports)) of
     case lists:member({Name, 2}, Module:module_info(exports)) of
         true ->
         true ->
-            {erl_syntax:application(erl_syntax:atom(Module), erl_syntax:atom(Name),
-				    [VariableAst, Arg]), #ast_info{var_names = VarNames}};
+            {{erl_syntax:application(
+                erl_syntax:atom(Module), erl_syntax:atom(Name),
+                [VariableAst, Arg]),
+              VarInfo},
+             TreeWalker};
         false ->
         false ->
-            filter_ast2(Name, VariableAst, [Arg], VarNames, Context#dtl_context{ filter_modules = Rest })
+            filter_ast2(Name, VariableAst, [Arg], VarInfo, Context#dtl_context{ filter_modules = Rest }, TreeWalker)
     end.
     end.
 
 
 search_for_escape_filter(Variable, Filter, #dtl_context{auto_escape = on}) ->
 search_for_escape_filter(Variable, Filter, #dtl_context{auto_escape = on}) ->
@@ -1142,40 +1148,56 @@ search_for_safe_filter({apply_filter, Variable, Filter}, _) ->
 search_for_safe_filter(_Variable, _Filter) ->
 search_for_safe_filter(_Variable, _Filter) ->
     on.
     on.
 
 
-resolve_variable_ast(VarTuple, Context, true) ->
-    resolve_variable_ast1(VarTuple, Context, 'fetch_value');
-resolve_variable_ast(VarTuple, Context, false) ->
-    resolve_variable_ast1(VarTuple, Context, 'find_value').
+finder_function(true) -> {erlydtl_runtime, fetch_value};
+finder_function(false) -> {erlydtl_runtime, find_value}.
+
+finder_function(EmptyIfUndefined, Context) ->
+    case call_extension(Context, finder_function, [EmptyIfUndefined]) of
+        undefined -> finder_function(EmptyIfUndefined);
+        Result -> Result
+    end.
+
+resolve_variable_ast({extension, Tag}, Context, TreeWalker, _) ->
+    extension_ast(Tag, Context, TreeWalker);
+resolve_variable_ast(VarTuple, Context, TreeWalker, EmptyIfUndefined)
+  when is_boolean(EmptyIfUndefined) ->
+    resolve_variable_ast(VarTuple, Context, TreeWalker, finder_function(EmptyIfUndefined, Context));
+resolve_variable_ast(VarTuple, Context, TreeWalker, FinderFunction) ->
+    resolve_variable_ast1(VarTuple, Context, TreeWalker, FinderFunction).
 
 
-resolve_variable_ast1({attribute, {{identifier, {Row, Col}, AttrName}, Variable}}, Context, FinderFunction) ->
-    {VarAst, VarName} = resolve_variable_ast1(Variable, Context, FinderFunction),
+resolve_variable_ast1({attribute, {{identifier, {Row, Col}, AttrName}, Variable}}, Context, TreeWalker, FinderFunction) ->
+    {{VarAst, VarInfo}, TreeWalker1} = resolve_variable_ast(Variable, Context, TreeWalker, FinderFunction),
     FileNameAst = case Context#dtl_context.parse_trail of 
     FileNameAst = case Context#dtl_context.parse_trail of 
 		      [] -> erl_syntax:atom(undefined); 
 		      [] -> erl_syntax:atom(undefined); 
 		      [H|_] -> erl_syntax:string(H)
 		      [H|_] -> erl_syntax:string(H)
 		  end,
 		  end,
-    {erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(FinderFunction),
-			    [erl_syntax:atom(AttrName), VarAst, FileNameAst,
-			     erl_syntax:tuple([erl_syntax:integer(Row), erl_syntax:integer(Col)])
-			    ]), VarName};
-
-resolve_variable_ast1({variable, {identifier, {Row, Col}, VarName}}, Context, FinderFunction) ->
+    {Runtime, Finder} = FinderFunction,
+    {{erl_syntax:application(
+        erl_syntax:atom(Runtime),
+        erl_syntax:atom(Finder),
+        [erl_syntax:atom(AttrName), VarAst, FileNameAst,
+         erl_syntax:tuple([erl_syntax:integer(Row), erl_syntax:integer(Col)])
+        ]),
+      VarInfo},
+     TreeWalker1};
+
+resolve_variable_ast1({variable, {identifier, {Row, Col}, VarName}}, Context, TreeWalker, FinderFunction) ->
     VarValue = case resolve_scoped_variable_ast(VarName, Context) of
     VarValue = case resolve_scoped_variable_ast(VarName, Context) of
-		   undefined ->
-		       FileNameAst = case Context#dtl_context.parse_trail of 
-					 [] -> erl_syntax:atom(undefined); 
-					 [H|_] -> erl_syntax:string(H)
-				     end,
-		       erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(FinderFunction),
-					      [erl_syntax:atom(VarName), erl_syntax:variable("_Variables"), FileNameAst,
-					       erl_syntax:tuple([erl_syntax:integer(Row), erl_syntax:integer(Col)])
-					      ]);
-		   Val ->
-		       Val
-	       end,
-    {VarValue, VarName};
-
-resolve_variable_ast1(What, _Context, _FinderFunction) ->
-    error_logger:error_msg("~p:resolve_variable_ast unhandled: ~p~n", [?MODULE, What]).
+                   undefined ->
+                       FileNameAst = case Context#dtl_context.parse_trail of
+                                         [] -> erl_syntax:atom(undefined);
+                                         [H|_] -> erl_syntax:string(H)
+                                     end,
+                       {Runtime, Finder} = FinderFunction,
+                       erl_syntax:application(
+                         erl_syntax:atom(Runtime), erl_syntax:atom(Finder),
+                         [erl_syntax:atom(VarName), erl_syntax:variable("_Variables"), FileNameAst,
+                          erl_syntax:tuple([erl_syntax:integer(Row), erl_syntax:integer(Col)])
+                         ]);
+                   Val ->
+                       Val
+               end,
+    {{VarValue, #ast_info{ var_names=[VarName] }}, TreeWalker}.
 
 
 resolve_scoped_variable_ast(VarName, Context) ->
 resolve_scoped_variable_ast(VarName, Context) ->
     lists:foldl(fun(Scope, Value) ->
     lists:foldl(fun(Scope, Value) ->
@@ -1341,7 +1363,7 @@ cycle_ast(Names, Context, TreeWalker) ->
 						   {{S, _}, _} = string_ast(unescape_string_literal(Str), Context, TreeWalker),
 						   {{S, _}, _} = string_ast(unescape_string_literal(Str), Context, TreeWalker),
 						   {S, VarNamesAcc};
 						   {S, VarNamesAcc};
 						({variable, _}=Var, VarNamesAcc) ->
 						({variable, _}=Var, VarNamesAcc) ->
-						   {V, VarName} = resolve_variable_ast(Var, Context, true),
+						   {{V, #ast_info{ var_names=[VarName] }}, _} = resolve_variable_ast(Var, Context, TreeWalker, true),
 						   {V, [VarName|VarNamesAcc]};
 						   {V, [VarName|VarNamesAcc]};
 						({number_literal, _, Num}, VarNamesAcc) ->
 						({number_literal, _, Num}, VarNamesAcc) ->
 						   {format(erl_syntax:integer(Num), Context, TreeWalker), VarNamesAcc};
 						   {format(erl_syntax:integer(Num), Context, TreeWalker), VarNamesAcc};
@@ -1464,8 +1486,8 @@ call_ast(Module, TreeWalkerAcc) ->
     call_ast(Module, erl_syntax:variable("_Variables"), #ast_info{}, TreeWalkerAcc).
     call_ast(Module, erl_syntax:variable("_Variables"), #ast_info{}, TreeWalkerAcc).
 
 
 call_with_ast(Module, Variable, Context, TreeWalker) ->
 call_with_ast(Module, Variable, Context, TreeWalker) ->
-    {VarAst, VarName} = resolve_variable_ast(Variable, Context, false),
-    call_ast(Module, VarAst, #ast_info{var_names=[VarName]}, TreeWalker).
+    {{VarAst, VarInfo}, TreeWalker2} = resolve_variable_ast(Variable, Context, TreeWalker, false),
+    call_ast(Module, VarAst, VarInfo, TreeWalker2).
 
 
 call_ast(Module, Variable, AstInfo, TreeWalker) ->
 call_ast(Module, Variable, AstInfo, TreeWalker) ->
     AppAst = erl_syntax:application(
     AppAst = erl_syntax:application(