Skip to content
Browse files

Runtime blocktrans compiler and runtime.

Will generate runtime translation code only when 'blocktrans_fun'
isn't available on compile-time.
  • Loading branch information...
1 parent 6539729 commit a73d77c8932f09b147ec3fb164560df17fba8628 @seriyps seriyps committed Feb 14, 2014
Showing with 77 additions and 1 deletion.
  1. +31 −1 src/erlydtl_compiler.erl
  2. +46 −0 src/erlydtl_runtime.erl
View
32 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
View
46 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) ->

0 comments on commit a73d77c

Please sign in to comment.
Something went wrong with that request. Please try again.