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

Runtime blocktrans compiler and runtime.

Will generate runtime translation code only when 'blocktrans_fun'
isn't available on compile-time.
Сергей Прохоров 11 лет назад
Родитель
Сommit
a73d77c893
2 измененных файлов с 77 добавлено и 1 удалено
  1. 31 1
      src/erlydtl_compiler.erl
  2. 46 0
      src/erlydtl_runtime.erl

+ 31 - 1
src/erlydtl_compiler.erl

@@ -1194,19 +1194,23 @@ empty_ast(TreeWalker) ->
     {{erl_syntax:list([]), #ast_info{}}, TreeWalker}.
 
 blocktrans_ast(ArgList, Contents, Context, TreeWalker) ->
+    %% add new scope using 'with' values
     {NewScope, {ArgInfo, TreeWalker1}} = lists:mapfoldl(fun
                                                             ({{identifier, _, LocalVarName}, Value}, {AstInfo1, TreeWalker1}) ->
                                                                {{Ast, Info}, TreeWalker2} = value_ast(Value, false, false, Context, TreeWalker1),
                                                                {{LocalVarName, Ast}, {merge_info(AstInfo1, Info), TreeWalker2}}
                                                        end, {#ast_info{}, TreeWalker}, ArgList),
     NewContext = Context#dtl_context{ local_scopes = [NewScope|Context#dtl_context.local_scopes] },
+    %% key for translation lookup
     SourceText = lists:flatten(erlydtl_unparser:unparse(Contents)),
     {{DefaultAst, AstInfo}, TreeWalker2} = body_ast(Contents, NewContext, TreeWalker1),
     MergedInfo = merge_info(AstInfo, ArgInfo),
     case Context#dtl_context.blocktrans_fun of
         none ->
-            {{DefaultAst, MergedInfo}, TreeWalker2};
+            %% translate in runtime
+            blocktrans_runtime_ast({DefaultAst, MergedInfo}, TreeWalker2, SourceText, Contents, NewContext);
         BlockTransFun when is_function(BlockTransFun) ->
+            %% translate in compile-time
             {FinalAstInfo, FinalTreeWalker, Clauses} = lists:foldr(fun(Locale, {AstInfoAcc, ThisTreeWalker, ClauseAcc}) ->
                                                                            case BlockTransFun(SourceText, Locale) of
                                                                                default ->
@@ -1223,6 +1227,32 @@ blocktrans_ast(ArgList, Contents, Context, TreeWalker) ->
             {{Ast, FinalAstInfo#ast_info{ translated_blocks = [SourceText] }}, FinalTreeWalker}
     end.
 
+blocktrans_runtime_ast({DefaultAst, Info}, Walker, SourceText, Contents, Context) ->
+    %% Contents is flat - only strings and '{{var}}' allowed.
+    %% build sorted list (orddict) of pre-resolved variables to pass to runtime translation function
+    USortedVariables = lists:usort(fun({variable, {identifier, _, A}},
+                                       {variable, {identifier, _, B}}) ->
+                                           A =< B
+                                   end, [Var || {variable, _}=Var <- Contents]),
+    VarBuilder = fun({variable, {identifier, _, Name}}=Var, Walker1) ->
+                         {{Ast2, _InfoIgn}, Walker2}  = resolve_variable_ast(Var, Context, Walker1, false),
+                         KVAst = erl_syntax:tuple([erl_syntax:string(atom_to_list(Name)), Ast2]),
+                         {KVAst, Walker2}
+                 end,
+    {VarAsts, Walker2} = lists:mapfoldl(VarBuilder, Walker, USortedVariables),
+    VarListAst = erl_syntax:list(VarAsts),
+    RuntimeTransAst =  [erl_syntax:application(
+                          erl_syntax:atom(erlydtl_runtime),
+                          erl_syntax:atom(translate_block),
+                          [erl_syntax:string(SourceText),
+                           erl_syntax:variable("_TranslationFun"),
+                           VarListAst])],
+    Ast1 = erl_syntax:case_expr(erl_syntax:variable("_TranslationFun"),
+                                [erl_syntax:clause([erl_syntax:atom(none)], none, [DefaultAst]),
+                                 erl_syntax:clause([erl_syntax:underscore()], none,
+                                                   RuntimeTransAst)]),
+    {{Ast1, Info}, Walker2}.
+
 translated_ast({string_literal, _, String}, Context, TreeWalker) ->
     UnescapedStr = unescape_string_literal(String),
     case call_extension(Context, translate_ast, [UnescapedStr, Context, TreeWalker]) of

+ 46 - 0
src/erlydtl_runtime.erl

@@ -2,6 +2,8 @@
 
 -compile(export_all).
 
+-type translate_fun() :: fun((string() | binary()) -> string() | binary() | undefined).
+
 -define(IFCHANGED_CONTEXT_VARIABLE, erlydtl_ifchanged_context).
 
 find_value(Key, Data, Options) when is_atom(Key), is_tuple(Data) ->
@@ -108,6 +110,8 @@ regroup([Item|Rest], Attribute, [[{grouper, PrevGrouper}, {list, PrevList}]|Acc]
             regroup(Rest, Attribute, [[{grouper, Value}, {list, [Item]}], [{grouper, PrevGrouper}, {list, lists:reverse(PrevList)}]|Acc])
     end.
 
+-spec translate(Str, none | translate_fun(), Str) -> Str when
+      Str :: string() | binary().
 translate(_, none, Default) ->
     Default;
 translate(String, TranslationFun, Default) when is_function(TranslationFun) ->
@@ -118,6 +122,48 @@ translate(String, TranslationFun, Default) when is_function(TranslationFun) ->
         Str -> Str
     end.
 
+%% @doc Translate and interpolate 'blocktrans' content.
+%% Pre-requisites:
+%%  * `Variables' should be sorted
+%%  * Each interpolation variable should exist
+%%    (String="{{a}}", Variables=[{"b", "b-val"}] will fall)
+%%  * Orddict keys should be string(), not binary()
+-spec translate_block(string() | binary(), translate_fun(), orddict:orddict()) -> iodata().
+translate_block(String, TranslationFun, Variables) ->
+    TransString = case TranslationFun(String) of
+                      No when (undefined == No)
+                              orelse (<<"">> == No)
+                              orelse ("" == No) -> String;
+                      Str -> Str
+                  end,
+    try interpolate_variables(TransString, Variables)
+    catch _:_ ->
+            %% Fallback to default language in case of errors (like Djando does)
+            interpolate_variables(String, Variables)
+    end.
+
+interpolate_variables(Tpl, []) ->
+    Tpl;
+interpolate_variables(Tpl, Variables) ->
+    BTpl = iolist_to_binary(Tpl),
+    interpolate_variables1(BTpl, Variables).
+
+interpolate_variables1(Tpl, Vars) ->
+    %% pre-compile binary patterns?
+    case binary:split(Tpl, <<"{{">>) of
+        [NotFound] ->
+            [NotFound];
+        [Pre, Post] ->
+            case binary:split(Post, <<"}}">>) of
+                [_] -> throw({no_close_var, Post});
+                [Var, Post1] ->
+                    Var1 = string:strip(binary_to_list(Var)),
+                    Value = orddict:fetch(Var1, Vars),
+                    [Pre, Value | interpolate_variables1(Post1, Vars)]
+            end
+    end.
+
+
 are_equal(Arg1, Arg2) when Arg1 =:= Arg2 ->
     true;
 are_equal(Arg1, Arg2) when is_binary(Arg1) ->