erlydtl_beam_compiler.erl 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536
  1. %%%-------------------------------------------------------------------
  2. %%% File: erlydtl_beam_compiler.erl
  3. %%% @author Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
  4. %%% @author Evan Miller <emmiller@gmail.com>
  5. %%% @author Andreas Stenius <kaos@astekk.se>
  6. %%% @copyright 2008 Roberto Saccon, Evan Miller
  7. %%% @copyright 2014 Andreas Stenius
  8. %%% @doc
  9. %%% ErlyDTL template compiler for beam targets.
  10. %%% @end
  11. %%%
  12. %%% The MIT License
  13. %%%
  14. %%% Copyright (c) 2007 Roberto Saccon, Evan Miller
  15. %%% Copyright (c) 2014 Andreas Stenius
  16. %%%
  17. %%% Permission is hereby granted, free of charge, to any person obtaining a copy
  18. %%% of this software and associated documentation files (the "Software"), to deal
  19. %%% in the Software without restriction, including without limitation the rights
  20. %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  21. %%% copies of the Software, and to permit persons to whom the Software is
  22. %%% furnished to do so, subject to the following conditions:
  23. %%%
  24. %%% The above copyright notice and this permission notice shall be included in
  25. %%% all copies or substantial portions of the Software.
  26. %%%
  27. %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  28. %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  29. %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  30. %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  31. %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  32. %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  33. %%% THE SOFTWARE.
  34. %%%
  35. %%% @since 2007-12-16 by Roberto Saccon, Evan Miller
  36. %%% @since 2014 by Andreas Stenius
  37. %%%-------------------------------------------------------------------
  38. -module(erlydtl_beam_compiler).
  39. -author('rsaccon@gmail.com').
  40. -author('emmiller@gmail.com').
  41. -author('Andreas Stenius <kaos@astekk.se>').
  42. %% --------------------------------------------------------------------
  43. %% Definitions
  44. %% --------------------------------------------------------------------
  45. -export([compile/3, compile_dir/2, format_error/1]).
  46. %% internal use
  47. -export([
  48. is_up_to_date/2,
  49. format/1,
  50. value_ast/4,
  51. interpret_args/2
  52. ]).
  53. -import(erlydtl_compiler, [parse_file/2, do_parse_template/2]).
  54. -import(erlydtl_compiler_utils,
  55. [unescape_string_literal/1, full_path/2, push_scope/2,
  56. restore_scope/2, begin_scope/1, begin_scope/2, end_scope/4,
  57. empty_scope/0, get_current_file/1, add_errors/2,
  58. add_warnings/2, merge_info/2, call_extension/3,
  59. init_treewalker/1, resolve_variable/2, resolve_variable/3,
  60. reset_block_dict/2, reset_parse_trail/2, load_library/3,
  61. load_library/4, shorten_filename/2, push_auto_escape/2,
  62. pop_auto_escape/1]).
  63. -include_lib("merl/include/merl.hrl").
  64. -include("erlydtl_ext.hrl").
  65. %% --------------------------------------------------------------------
  66. %% API
  67. %% --------------------------------------------------------------------
  68. compile(DjangoParseTree, CheckSum, Context) ->
  69. compile_to_binary(DjangoParseTree, CheckSum, Context).
  70. compile_dir(Dir, Context) ->
  71. do_compile_dir(Dir, Context).
  72. format_error(no_out_dir) ->
  73. "Compiled template not saved (need out_dir option)";
  74. format_error(unexpected_extends_tag) ->
  75. "The extends tag must be at the very top of the template";
  76. format_error(circular_include) ->
  77. "Circular file inclusion!";
  78. format_error({write_file, Error}) ->
  79. io_lib:format(
  80. "Failed to write file: ~s",
  81. [file:format_error(Error)]);
  82. format_error(compile_beam) ->
  83. "Failed to compile template to BEAM code";
  84. format_error({unknown_filter, Name, Arity}) ->
  85. io_lib:format("Unknown filter '~p' (arity ~p)", [Name, Arity]);
  86. format_error({filter_args, Name, {Mod, Fun}, Arity}) ->
  87. io_lib:format("Wrong number of arguments to filter '~p' (~p:~p): ~p", [Name, Mod, Fun, Arity]);
  88. format_error({missing_tag, Name, {Mod, Fun}}) ->
  89. io_lib:format("Custom tag '~p' not exported (~p:~p)", [Name, Mod, Fun]);
  90. format_error({bad_tag, Name, {Mod, Fun}, Arity}) ->
  91. io_lib:format("Invalid tag '~p' (~p:~p/~p)", [Name, Mod, Fun, Arity]);
  92. format_error({load_code, Error}) ->
  93. io_lib:format("Failed to load BEAM code: ~p", [Error]);
  94. format_error({reserved_variable, ReservedName}) ->
  95. io_lib:format("Variable '~s' is reserved for internal use.", [ReservedName]);
  96. format_error({translation_fun, Fun}) ->
  97. io_lib:format("Invalid translation function: ~s~n",
  98. [if is_function(Fun) ->
  99. Info = erlang:fun_info(Fun),
  100. io_lib:format("~s:~s/~p", [proplists:get_value(K, Info) || K <- [module, name, arity]]);
  101. true -> io_lib:format("~p", [Fun])
  102. end]);
  103. format_error(Error) ->
  104. erlydtl_compiler:format_error(Error).
  105. %%====================================================================
  106. %% Internal functions
  107. %%====================================================================
  108. do_compile_dir(Dir, Context) ->
  109. %% Find all files in Dir (recursively), matching the regex (no
  110. %% files ending in "~").
  111. Files = filelib:fold_files(Dir, ".+[^~]$", true, fun(F1,Acc1) -> [F1 | Acc1] end, []),
  112. {ParserResults,
  113. #dtl_context{ errors=#error_info{ list=ParserErrors } }=Context1}
  114. = lists:foldl(
  115. fun (File, {ResultAcc, Ctx}) ->
  116. case filename:basename(File) of
  117. "."++_ ->
  118. {ResultAcc, Ctx};
  119. _ ->
  120. FilePath = filename:absname(File),
  121. case filelib:is_dir(FilePath) of
  122. true ->
  123. {ResultAcc, Ctx};
  124. false ->
  125. case parse_file(FilePath, Ctx) of
  126. up_to_date -> {ResultAcc, Ctx};
  127. {ok, DjangoParseTree, CheckSum} ->
  128. {[{FilePath, DjangoParseTree, CheckSum}|ResultAcc], Ctx};
  129. {error, Reason} -> {ResultAcc, ?ERR(Reason, Ctx)}
  130. end
  131. end
  132. end
  133. end,
  134. {[], Context},
  135. Files),
  136. if length(ParserErrors) == 0 ->
  137. compile_multiple_to_binary(Dir, ParserResults, Context1);
  138. true -> Context1
  139. end.
  140. compile_multiple_to_binary(Dir, ParserResults, Context) ->
  141. MatchAst = options_match_ast(Context),
  142. {Functions,
  143. {AstInfo,
  144. #treewalker{
  145. context=#dtl_context{
  146. errors=#error_info{ list=Errors }
  147. }=Context1 } }
  148. } = lists:mapfoldl(
  149. fun ({File, DjangoParseTree, CheckSum},
  150. {AstInfo, #treewalker{ context=Ctx }=TreeWalker}) ->
  151. try
  152. FilePath = full_path(File, Ctx#dtl_context.doc_root),
  153. {{BodyAst, BodyInfo}, TreeWalker1} = with_dependency(
  154. {FilePath, CheckSum},
  155. body_ast(DjangoParseTree, TreeWalker)),
  156. FunctionName = filename:rootname(filename:basename(File)),
  157. FunctionDefs = ?Q(["'@func'(Variables) -> _@func(Variables, []).",
  158. "'@func'(_Variables, RenderOptions) ->",
  159. " _@MatchAst, _@body."],
  160. [{func, erl_syntax:atom(FunctionName)},
  161. {body, stringify(BodyAst, Ctx)}]),
  162. {{FunctionName, FunctionDefs}, {merge_info(AstInfo, BodyInfo), TreeWalker1}}
  163. catch
  164. throw:Error ->
  165. {error, {AstInfo, TreeWalker#treewalker{ context=?ERR(Error, Ctx) }}}
  166. end
  167. end,
  168. {#ast_info{}, init_treewalker(Context)},
  169. ParserResults),
  170. if length(Errors) == 0 ->
  171. Forms = custom_forms(Dir, Context1#dtl_context.module, Functions, AstInfo),
  172. compile_forms(Forms, Context1);
  173. true ->
  174. Context1
  175. end.
  176. compile_to_binary(DjangoParseTree, CheckSum, Context) ->
  177. try body_ast(DjangoParseTree, init_treewalker(Context)) of
  178. {{BodyAst, BodyInfo}, BodyTreeWalker} ->
  179. try custom_tags_ast(BodyInfo#ast_info.custom_tags, BodyTreeWalker) of
  180. {CustomTags,
  181. #treewalker{
  182. context=#dtl_context{
  183. errors=#error_info{ list=Errors }
  184. } }=CustomTagsTreeWalker}
  185. when length(Errors) == 0 ->
  186. Forms = forms(
  187. {BodyAst, BodyInfo},
  188. CustomTags, CheckSum,
  189. CustomTagsTreeWalker),
  190. compile_forms(Forms, CustomTagsTreeWalker#treewalker.context);
  191. {_, #treewalker{ context=Context1 }} ->
  192. Context1
  193. catch
  194. throw:Error -> ?ERR(Error, BodyTreeWalker#treewalker.context)
  195. end
  196. catch
  197. throw:Error -> ?ERR(Error, Context)
  198. end.
  199. compile_forms(Forms, Context) ->
  200. maybe_debug_template(Forms, Context),
  201. Options = Context#dtl_context.compiler_options,
  202. case compile:forms(Forms, Options) of
  203. Compiled when element(1, Compiled) =:= ok ->
  204. [ok, Module, Bin|Info] = tuple_to_list(Compiled),
  205. lists:foldl(
  206. fun (F, C) -> F(Module, Bin, C) end,
  207. Context#dtl_context{ bin=Bin },
  208. [fun maybe_write/3,
  209. fun maybe_load/3,
  210. fun (_, _, C) ->
  211. case Info of
  212. [Ws] when length(Ws) > 0 ->
  213. add_warnings(Ws, C);
  214. _ -> C
  215. end
  216. end
  217. ]);
  218. error ->
  219. ?ERR(compile_beam, Context);
  220. {error, Es, Ws} ->
  221. add_warnings(Ws, add_errors(Es, Context))
  222. end.
  223. maybe_write(Module, Bin, Context) ->
  224. case proplists:get_value(out_dir, Context#dtl_context.all_options) of
  225. false -> Context;
  226. undefined ->
  227. ?WARN(no_out_dir, Context);
  228. OutDir ->
  229. BeamFile = filename:join([OutDir, [Module, ".beam"]]),
  230. ?LOG_INFO("Template module: ~w -> ~s\n", [Module, BeamFile], Context),
  231. case file:write_file(BeamFile, Bin) of
  232. ok -> Context;
  233. {error, Reason} ->
  234. ?ERR({write_file, Reason}, Context)
  235. end
  236. end.
  237. maybe_load(Module, Bin, Context) ->
  238. case proplists:get_bool(no_load, Context#dtl_context.all_options) of
  239. true -> Context;
  240. false -> load_code(Module, Bin, Context)
  241. end.
  242. load_code(Module, Bin, Context) ->
  243. code:purge(Module),
  244. case code:load_binary(Module, atom_to_list(Module) ++ ".erl", Bin) of
  245. {module, Module} -> Context;
  246. Error -> ?WARN({load_code, Error}, Context)
  247. end.
  248. maybe_debug_template(Forms, Context) ->
  249. case proplists:get_bool(debug_compiler, Context#dtl_context.all_options) of
  250. false -> nop;
  251. true ->
  252. Options = Context#dtl_context.compiler_options,
  253. ?LOG_DEBUG("Compiler options: ~p~n", [Options], Context),
  254. try
  255. Source = erl_prettypr:format(erl_syntax:form_list(Forms)),
  256. SourceFile = lists:concat(
  257. [proplists:get_value(source, Options),".erl"]),
  258. File = case proplists:get_value(
  259. debug_root,
  260. Context#dtl_context.all_options) of
  261. false -> undefined;
  262. undefined -> SourceFile;
  263. Dir ->
  264. Abs = filename:absname(
  265. shorten_filename(
  266. SourceFile,
  267. Context#dtl_context.doc_root),
  268. Dir),
  269. case filelib:ensure_dir(Abs) of
  270. ok -> Abs;
  271. {error, Reason} ->
  272. io:format(
  273. "Failed to ensure directories for file '~s': ~p~n",
  274. [Abs, Reason]),
  275. undefined
  276. end
  277. end,
  278. if File =/= undefined ->
  279. io:format("Saving template source to: ~s.. ~p~n",
  280. [File, file:write_file(File, Source)]);
  281. true -> ok
  282. end
  283. catch
  284. error:Err ->
  285. io:format("Pretty printing failed: ~p~n"
  286. "Context: ~n~p~n"
  287. "Forms: ~n~p~n",
  288. [Err, Context, Forms])
  289. end
  290. end.
  291. is_up_to_date(CheckSum, Context) ->
  292. Module = Context#dtl_context.module,
  293. {M, F} = Context#dtl_context.reader,
  294. case catch Module:source() of
  295. {_, CheckSum} ->
  296. case catch Module:dependencies() of
  297. L when is_list(L) ->
  298. RecompileList = lists:foldl(
  299. fun ({XFile, XCheckSum}, Acc) ->
  300. case catch M:F(XFile) of
  301. {ok, Data} ->
  302. case binary_to_list(erlang:md5(Data)) of
  303. XCheckSum ->
  304. Acc;
  305. _ ->
  306. [recompile | Acc]
  307. end;
  308. _ ->
  309. [recompile | Acc]
  310. end
  311. end, [], L),
  312. case RecompileList of
  313. [] -> true;
  314. _ -> false
  315. end;
  316. _ ->
  317. false
  318. end;
  319. _ ->
  320. false
  321. end.
  322. %%====================================================================
  323. %% AST functions
  324. %%====================================================================
  325. custom_tags_ast(CustomTags, TreeWalker) ->
  326. %% avoid adding the render_tag/3 fun if it isn't used,
  327. %% since we can't add a -compile({nowarn_unused_function, render_tag/3}).
  328. %% attribute due to a bug in syntax_tools.
  329. case custom_tags_clauses_ast(CustomTags, TreeWalker) of
  330. skip ->
  331. {{erl_syntax:comment(
  332. ["% render_tag/3 is not used in this template."]),
  333. #ast_info{}},
  334. TreeWalker};
  335. {{CustomTagsClauses, CustomTagsInfo}, TreeWalker1} ->
  336. {{erl_syntax:function(
  337. erl_syntax:atom(render_tag),
  338. CustomTagsClauses),
  339. CustomTagsInfo},
  340. TreeWalker1}
  341. end.
  342. custom_tags_clauses_ast([], _TreeWalker) -> skip;
  343. custom_tags_clauses_ast(CustomTags, TreeWalker) ->
  344. custom_tags_clauses_ast1(CustomTags, [], [], #ast_info{}, TreeWalker).
  345. custom_tags_clauses_ast1([], _ExcludeTags, ClauseAcc, InfoAcc, TreeWalker) ->
  346. {{DefaultAst, DefaultInfo}, TreeWalker1} =
  347. case call_extension(TreeWalker, custom_tag_ast, [TreeWalker]) of
  348. undefined ->
  349. {{?Q("(_TagName, _, _) -> []"), InfoAcc}, TreeWalker};
  350. {{ExtAst, ExtInfo}, ExtTreeWalker} ->
  351. Clause = ?Q("(TagName, _Variables, RenderOptions) -> _@tag",
  352. [{tag, options_match_ast(ExtTreeWalker) ++ [ExtAst]}]),
  353. {{Clause, merge_info(ExtInfo, InfoAcc)}, ExtTreeWalker}
  354. end,
  355. {{lists:reverse([DefaultAst|ClauseAcc]), DefaultInfo}, TreeWalker1};
  356. custom_tags_clauses_ast1([Tag|CustomTags], ExcludeTags, ClauseAcc, InfoAcc, TreeWalker) ->
  357. case lists:member(Tag, ExcludeTags) of
  358. true ->
  359. custom_tags_clauses_ast1(CustomTags, ExcludeTags, ClauseAcc, InfoAcc, TreeWalker);
  360. false ->
  361. Context = TreeWalker#treewalker.context,
  362. CustomTagFile = full_path(Tag, Context#dtl_context.custom_tags_dir),
  363. case filelib:is_file(CustomTagFile) of
  364. true ->
  365. case parse_file(CustomTagFile, Context) of
  366. {ok, DjangoParseTree, CheckSum} ->
  367. {{BodyAst, BodyAstInfo}, TreeWalker1} = with_dependency(
  368. {CustomTagFile, CheckSum},
  369. body_ast(DjangoParseTree, TreeWalker)),
  370. MatchAst = options_match_ast(TreeWalker1),
  371. Clause = ?Q("(_@Tag@, _Variables, RenderOptions) -> _@MatchAst, _@BodyAst"),
  372. custom_tags_clauses_ast1(
  373. CustomTags, [Tag|ExcludeTags], [Clause|ClauseAcc],
  374. merge_info(BodyAstInfo, InfoAcc), TreeWalker1);
  375. {error, Reason} ->
  376. empty_ast(?ERR(Reason, TreeWalker))
  377. end;
  378. false ->
  379. case call_extension(TreeWalker, custom_tag_ast, [Tag, TreeWalker]) of
  380. undefined ->
  381. custom_tags_clauses_ast1(
  382. CustomTags, [Tag | ExcludeTags],
  383. ClauseAcc, InfoAcc, TreeWalker);
  384. {{Ast, Info}, TW} ->
  385. Clause = ?Q("(_@Tag@, _Variables, RenderOptions) -> _@match, _@Ast",
  386. [{match, options_match_ast(TW)}]),
  387. custom_tags_clauses_ast1(
  388. CustomTags, [Tag | ExcludeTags], [Clause|ClauseAcc],
  389. merge_info(Info, InfoAcc), TW)
  390. end
  391. end
  392. end.
  393. custom_forms(Dir, Module, Functions, AstInfo) ->
  394. Dependencies = AstInfo#ast_info.dependencies,
  395. TranslatableStrings = AstInfo#ast_info.translatable_strings,
  396. erl_syntax:revert_forms(
  397. lists:flatten(
  398. ?Q(["-module('@Module@').",
  399. "-export([source_dir/0, dependencies/0, translatable_strings/0,",
  400. " render/1, render/2, render/3]).",
  401. "-export(['@__export_functions'/0]).",
  402. "source_dir() -> _@Dir@.",
  403. "dependencies() -> _@Dependencies@.",
  404. "translatable_strings() -> _@TranslatableStrings@.",
  405. "render(Tag) -> render(Tag, [], []).",
  406. "render(Tag, Vars) -> render(Tag, Vars, []).",
  407. "render(Tag, Vars, Opts) ->",
  408. " try '@Module@':Tag(Vars, Opts) of",
  409. " Val -> {ok, Val}",
  410. " catch",
  411. " Err -> {error, Err}",
  412. " end.",
  413. "'@_functions'() -> _."
  414. ],
  415. [{export_functions,
  416. erl_syntax:list(
  417. [erl_syntax:arity_qualifier(erl_syntax:atom(FName), erl_syntax:integer(Arity))
  418. || {FName, _} <- Functions, Arity <- [1, 2]])},
  419. {functions, [Ast || {_, Ast} <- Functions]}
  420. ]))
  421. ).
  422. stringify(BodyAst, #dtl_context{ binary_strings=BinaryStrings }) ->
  423. [?Q("erlydtl_runtime:stringify_final(_@BodyAst, '@BinaryStrings@')")].
  424. forms({BodyAst, BodyInfo}, {CustomTagsFunctionAst, CustomTagsInfo}, CheckSum,
  425. #treewalker{
  426. context=#dtl_context{
  427. module=Module,
  428. parse_trail=[File|_]
  429. }=Context
  430. }=TreeWalker) ->
  431. MergedInfo = merge_info(BodyInfo, CustomTagsInfo),
  432. Dependencies = MergedInfo#ast_info.dependencies,
  433. TranslatableStrings = MergedInfo#ast_info.translatable_strings,
  434. TranslatedBlocks = MergedInfo#ast_info.translated_blocks,
  435. Variables = lists:usort(MergedInfo#ast_info.var_names),
  436. DefaultVariables = lists:usort(MergedInfo#ast_info.def_names),
  437. Constants = lists:usort(MergedInfo#ast_info.const_names),
  438. FinalBodyAst = options_match_ast(TreeWalker) ++ stringify(BodyAst, Context),
  439. erl_syntax:revert_forms(
  440. ?Q(["-module('@Module@').",
  441. "-export([render/0, render/1, render/2, source/0, dependencies/0,",
  442. " translatable_strings/0, translated_blocks/0, variables/0,",
  443. " default_variables/0, constants/0]).",
  444. "source() -> {_@File@, _@CheckSum@}.",
  445. "dependencies() -> _@Dependencies@.",
  446. "variables() -> _@Variables@.",
  447. "default_variables() -> _@DefaultVariables@.",
  448. "constants() -> _@Constants@.",
  449. "translatable_strings() -> _@TranslatableStrings@.",
  450. "translated_blocks() -> _@TranslatedBlocks@.",
  451. "'@_CustomTagsFunctionAst'() -> _.",
  452. "render() -> render([], []).",
  453. "render(Variables) -> render(Variables, []).",
  454. "render(Variables, RenderOptions) ->",
  455. " try render_internal(Variables, RenderOptions) of",
  456. " Val -> {ok, Val}",
  457. " catch",
  458. " Err -> {error, Err}",
  459. " end.",
  460. "render_internal(_Variables, RenderOptions) -> _@FinalBodyAst."
  461. ])).
  462. options_match_ast(#treewalker{ context=Context }=TreeWalker) ->
  463. options_match_ast(Context, TreeWalker);
  464. options_match_ast(Context) ->
  465. options_match_ast(Context, undefined).
  466. options_match_ast(Context, TreeWalker) ->
  467. [
  468. ?Q(["_TranslationFun = erlydtl_runtime:init_translation(",
  469. " proplists:get_value(translation_fun, RenderOptions, none))"]),
  470. ?Q("_CurrentLocale = proplists:get_value(locale, RenderOptions, default)"),
  471. ?Q("_RecordInfo = _@info", [{info, merl:term(Context#dtl_context.record_info)}])
  472. | case call_extension(Context, setup_render_ast, [Context, TreeWalker]) of
  473. undefined -> [];
  474. Ast when is_list(Ast) -> Ast
  475. end].
  476. %% child templates should only consist of blocks at the top level
  477. body_ast([{'extends', {string_literal, _Pos, String}} | ThisParseTree], #treewalker{ context=Context }=TreeWalker) ->
  478. File = full_path(unescape_string_literal(String), Context#dtl_context.doc_root),
  479. case lists:member(File, Context#dtl_context.parse_trail) of
  480. true ->
  481. empty_ast(?ERR(circular_include, TreeWalker));
  482. _ ->
  483. case parse_file(File, Context) of
  484. {ok, ParentParseTree, CheckSum} ->
  485. BlockDict = lists:foldl(
  486. fun ({block, {identifier, _, Name}, Contents}, Dict) ->
  487. dict:store(Name, Contents, Dict);
  488. (_, Dict) -> Dict
  489. end,
  490. dict:new(),
  491. ThisParseTree),
  492. {Info, TreeWalker1} = with_dependency(
  493. {File, CheckSum},
  494. body_ast(
  495. ParentParseTree,
  496. TreeWalker#treewalker{
  497. context=Context#dtl_context{
  498. block_dict = dict:merge(
  499. fun(_Key, _ParentVal, ChildVal) -> ChildVal end,
  500. BlockDict, Context#dtl_context.block_dict),
  501. parse_trail = [File | Context#dtl_context.parse_trail]
  502. }
  503. })),
  504. {Info, reset_parse_trail(
  505. Context#dtl_context.parse_trail,
  506. reset_block_dict(
  507. Context#dtl_context.block_dict,
  508. TreeWalker1))};
  509. {error, Reason} ->
  510. empty_ast(?ERR(Reason, TreeWalker))
  511. end
  512. end;
  513. body_ast(DjangoParseTree, TreeWalker) ->
  514. body_ast(DjangoParseTree, empty_scope(), TreeWalker).
  515. body_ast(DjangoParseTree, BodyScope, TreeWalker) ->
  516. {ScopeId, TreeWalkerScope} = begin_scope(BodyScope, TreeWalker),
  517. BodyFun =
  518. fun ({'autoescape', {identifier, _, OnOrOff}, Contents}, TW) ->
  519. {Info, BodyTW} = body_ast(Contents, push_auto_escape(OnOrOff, TW)),
  520. {Info, pop_auto_escape(BodyTW)};
  521. ({'block', {identifier, Pos, Name}, Contents}, #treewalker{ context=Context }=TW) ->
  522. {Block, BlockScope} =
  523. case dict:find(Name, Context#dtl_context.block_dict) of
  524. {ok, ChildBlock} ->
  525. {{ContentsAst, _ContentsInfo}, _ContentsTW} = body_ast(Contents, TW),
  526. {ChildBlock,
  527. create_scope(
  528. [{block, ?Q("[{super, _@ContentsAst}]")}],
  529. Pos, TW)
  530. };
  531. _ ->
  532. {Contents, empty_scope()}
  533. end,
  534. body_ast(Block, BlockScope, TW);
  535. ({'blocktrans', Args, Contents, PluralContents}, TW) ->
  536. blocktrans_ast(Args, Contents, PluralContents, TW);
  537. ({'call', {identifier, _, Name}}, TW) ->
  538. call_ast(Name, TW);
  539. ({'call', {identifier, _, Name}, With}, TW) ->
  540. call_with_ast(Name, With, TW);
  541. ({'comment', _Contents}, TW) ->
  542. empty_ast(TW);
  543. ({'comment_tag', _, _}, TW) ->
  544. empty_ast(TW);
  545. ({'cycle', Names}, TW) ->
  546. cycle_ast(Names, TW);
  547. ({'cycle_compat', Names}, TW) ->
  548. cycle_compat_ast(Names, TW);
  549. ({'date', 'now', {string_literal, _Pos, FormatString}}, TW) ->
  550. now_ast(FormatString, TW);
  551. ({'filter', FilterList, Contents}, TW) ->
  552. filter_tag_ast(FilterList, Contents, TW);
  553. ({'firstof', Vars}, TW) ->
  554. firstof_ast(Vars, TW);
  555. ({'for', {'in', IteratorList, Variable, Reversed}, Contents}, TW) ->
  556. {EmptyAstInfo, TW1} = empty_ast(TW),
  557. for_loop_ast(IteratorList, Variable, Reversed, Contents, EmptyAstInfo, TW1);
  558. ({'for', {'in', IteratorList, Variable, Reversed}, Contents, EmptyPartContents}, TW) ->
  559. {EmptyAstInfo, TW1} = body_ast(EmptyPartContents, TW),
  560. for_loop_ast(IteratorList, Variable, Reversed, Contents, EmptyAstInfo, TW1);
  561. ({'if', Expression, Contents, Elif}, TW) ->
  562. {IfAstInfo, TW1} = body_ast(Contents, TW),
  563. {ElifAstInfo, TW2} = body_ast(Elif, TW1),
  564. ifelse_ast(Expression, IfAstInfo, ElifAstInfo, TW2);
  565. ({'if', Expression, Contents}, TW) ->
  566. {IfAstInfo, TW1} = body_ast(Contents, TW),
  567. {ElseAstInfo, TW2} = empty_ast(TW1),
  568. ifelse_ast(Expression, IfAstInfo, ElseAstInfo, TW2);
  569. ({'ifchanged', '$undefined', Contents}, TW) ->
  570. {IfAstInfo, TW1} = body_ast(Contents, TW),
  571. {ElseAstInfo, TW2} = empty_ast(TW1),
  572. ifchanged_contents_ast(Contents, IfAstInfo, ElseAstInfo, TW2);
  573. ({'ifchanged', Values, Contents}, TW) ->
  574. {IfAstInfo, TW1} = body_ast(Contents, TW),
  575. {ElseAstInfo, TW2} = empty_ast(TW1),
  576. ifchanged_values_ast(Values, IfAstInfo, ElseAstInfo, TW2);
  577. ({'ifchangedelse', '$undefined', IfContents, ElseContents}, TW) ->
  578. {IfAstInfo, TW1} = body_ast(IfContents, TW),
  579. {ElseAstInfo, TW2} = body_ast(ElseContents, TW1),
  580. ifchanged_contents_ast(IfContents, IfAstInfo, ElseAstInfo, TW2);
  581. ({'ifchangedelse', Values, IfContents, ElseContents}, TW) ->
  582. {IfAstInfo, TW1} = body_ast(IfContents, TW),
  583. {ElseAstInfo, TW2} = body_ast(ElseContents, TW1),
  584. ifchanged_values_ast(Values, IfAstInfo, ElseAstInfo, TW2);
  585. ({'ifelse', Expression, IfContents, ElseContents}, TW) ->
  586. {IfAstInfo, TW1} = body_ast(IfContents, TW),
  587. {ElseAstInfo, TW2} = body_ast(ElseContents, TW1),
  588. ifelse_ast(Expression, IfAstInfo, ElseAstInfo, TW2);
  589. ({'ifequal', [Arg1, Arg2], Contents}, TW) ->
  590. {IfAstInfo, TW1} = body_ast(Contents, TW),
  591. {ElseAstInfo, TW2} = empty_ast(TW1),
  592. ifelse_ast({'expr', "eq", Arg1, Arg2}, IfAstInfo, ElseAstInfo, TW2);
  593. ({'ifequalelse', [Arg1, Arg2], IfContents, ElseContents}, TW) ->
  594. {IfAstInfo, TW1} = body_ast(IfContents, TW),
  595. {ElseAstInfo, TW2} = body_ast(ElseContents,TW1),
  596. ifelse_ast({'expr', "eq", Arg1, Arg2}, IfAstInfo, ElseAstInfo, TW2);
  597. ({'ifnotequal', [Arg1, Arg2], Contents}, TW) ->
  598. {IfAstInfo, TW1} = body_ast(Contents, TW),
  599. {ElseAstInfo, TW2} = empty_ast(TW1),
  600. ifelse_ast({'expr', "ne", Arg1, Arg2}, IfAstInfo, ElseAstInfo, TW2);
  601. ({'ifnotequalelse', [Arg1, Arg2], IfContents, ElseContents}, TW) ->
  602. {IfAstInfo, TW1} = body_ast(IfContents, TW),
  603. {ElseAstInfo, TW2} = body_ast(ElseContents, TW1),
  604. ifelse_ast({'expr', "ne", Arg1, Arg2}, IfAstInfo, ElseAstInfo, TW2);
  605. ({'include', {string_literal, _, File}, Args}, #treewalker{ context=Context }=TW) ->
  606. include_ast(unescape_string_literal(File), Args, Context#dtl_context.local_scopes, TW);
  607. ({'include_only', {string_literal, _, File}, Args}, TW) ->
  608. {Info, IncTW} = include_ast(unescape_string_literal(File), Args, [], TW),
  609. {Info, restore_scope(TW, IncTW)};
  610. ({'load_libs', Libs}, TW) ->
  611. load_libs_ast(Libs, TW);
  612. ({'load_from_lib', What, Lib}, TW) ->
  613. load_from_lib_ast(What, Lib, TW);
  614. ({'regroup', {ListVariable, Grouper, {identifier, _, NewVariable}}}, TW) ->
  615. regroup_ast(ListVariable, Grouper, NewVariable, TW);
  616. ('end_regroup', TW) ->
  617. {{end_scope, #ast_info{}}, TW};
  618. ({'spaceless', Contents}, TW) ->
  619. spaceless_ast(Contents, TW);
  620. ({'ssi', Arg}, TW) ->
  621. ssi_ast(Arg, TW);
  622. ({'ssi_parsed', {string_literal, _, FileName}}, #treewalker{ context=Context }=TW) ->
  623. include_ast(unescape_string_literal(FileName), [], Context#dtl_context.local_scopes, TW);
  624. ({'string', _Pos, String}, TW) ->
  625. string_ast(String, TW);
  626. ({'tag', Name, Args}, TW) ->
  627. tag_ast(Name, Args, TW);
  628. ({'templatetag', {_, _, TagName}}, TW) ->
  629. templatetag_ast(TagName, TW);
  630. ({'trans', Value}, TW) ->
  631. translated_ast(Value, TW);
  632. ({'trans', Value, Context}, TW) ->
  633. translated_ast(Value, Context, TW);
  634. ({'widthratio', Numerator, Denominator, Scale}, TW) ->
  635. widthratio_ast(Numerator, Denominator, Scale, TW);
  636. ({'with', Args, Contents}, TW) ->
  637. with_ast(Args, Contents, TW);
  638. ({'scope_as', {identifier, _, Name}, Contents}, TW) ->
  639. scope_as(Name, Contents, TW);
  640. ({'extension', Tag}, TW) ->
  641. extension_ast(Tag, TW);
  642. ({'extends', _}, TW) ->
  643. empty_ast(?ERR(unexpected_extends_tag, TW));
  644. (ValueToken, TW) ->
  645. format(value_ast(ValueToken, true, true, TW))
  646. end,
  647. {AstInfoList, TreeWalker1} = lists:mapfoldl(BodyFun, TreeWalkerScope, DjangoParseTree),
  648. {AstList, Info} =
  649. lists:mapfoldl(
  650. fun ({Ast, Info}, InfoAcc) ->
  651. {Ast, merge_info(Info, InfoAcc)}
  652. end, #ast_info{}, AstInfoList),
  653. {Ast, TreeWalker2} = end_scope(
  654. fun ([ScopeVars|ScopeBody]) -> [?Q("begin _@ScopeVars, [_@ScopeBody] end")] end,
  655. ScopeId, AstList, TreeWalker1),
  656. {{erl_syntax:list(Ast), Info}, TreeWalker2}.
  657. value_ast(ValueToken, AsString, EmptyIfUndefined, TreeWalker) ->
  658. case ValueToken of
  659. {'expr', Operator, Value} ->
  660. {{ValueAst,InfoValue}, TreeWalker1} = value_ast(Value, false, EmptyIfUndefined, TreeWalker),
  661. Op = list_to_atom(Operator),
  662. Ast = ?Q("erlydtl_runtime:_@Op@(_@ValueAst)"),
  663. {{Ast, InfoValue}, TreeWalker1};
  664. {'expr', Operator, Value1, Value2} ->
  665. {{Value1Ast,InfoValue1}, TreeWalker1} = value_ast(Value1, false, EmptyIfUndefined, TreeWalker),
  666. {{Value2Ast,InfoValue2}, TreeWalker2} = value_ast(Value2, false, EmptyIfUndefined, TreeWalker1),
  667. Op = list_to_atom(Operator),
  668. Ast = ?Q("erlydtl_runtime:_@Op@(_@Value1Ast, _@Value2Ast)"),
  669. {{Ast, merge_info(InfoValue1,InfoValue2)}, TreeWalker2};
  670. {'string_literal', _Pos, String} ->
  671. string_ast(unescape_string_literal(String), TreeWalker);
  672. {'number_literal', _Pos, Number} ->
  673. case AsString of
  674. true -> string_ast(Number, TreeWalker);
  675. false -> {{erl_syntax:integer(Number), #ast_info{}}, TreeWalker}
  676. end;
  677. {'apply_filter', Variable, Filter} ->
  678. filter_ast(Variable, Filter, TreeWalker);
  679. {'attribute', _} = Variable ->
  680. resolve_variable_ast(Variable, EmptyIfUndefined, TreeWalker);
  681. {'variable', _} = Variable ->
  682. resolve_variable_ast(Variable, EmptyIfUndefined, TreeWalker);
  683. {extension, Tag} ->
  684. extension_ast(Tag, TreeWalker)
  685. end.
  686. extension_ast(Tag, TreeWalker) ->
  687. case call_extension(TreeWalker, compile_ast, [Tag, TreeWalker]) of
  688. undefined ->
  689. empty_ast(?WARN({unknown_extension, Tag}, TreeWalker));
  690. Result ->
  691. Result
  692. end.
  693. with_dependencies([], Args) ->
  694. Args;
  695. with_dependencies([Dependency | Rest], Args) ->
  696. with_dependencies(Rest, with_dependency(Dependency, Args)).
  697. with_dependency(FilePath, {{Ast, Info}, TreeWalker}) ->
  698. {{Ast, Info#ast_info{dependencies = [FilePath | Info#ast_info.dependencies]}}, TreeWalker}.
  699. empty_ast(TreeWalker) ->
  700. {{erl_syntax:list([]), #ast_info{}}, TreeWalker}.
  701. %%% Note: Context here refers to the translation context, not the #dtl_context{} record
  702. blocktrans_ast(Args, Contents, PluralContents, TreeWalker) ->
  703. %% get args, count and context
  704. ArgList = [{Name, Value}
  705. || {{identifier, _, Name}, Value}
  706. <- proplists:get_value(args, Args, [])],
  707. Count = proplists:get_value(count, Args),
  708. Context = case proplists:get_value(context, Args) of
  709. undefined -> undefined;
  710. {string_literal, _, S} ->
  711. unescape_string_literal(S)
  712. end,
  713. %% add new scope using 'with' values
  714. {NewScope, {ArgInfo, TreeWalker1}} =
  715. lists:mapfoldl(
  716. fun ({LocalVarName, Value}, {AstInfoAcc, TreeWalkerAcc}) ->
  717. {{Ast, Info}, TW} = value_ast(Value, false, false, TreeWalkerAcc),
  718. {{LocalVarName, Ast}, {merge_info(AstInfoAcc, Info), TW}}
  719. end,
  720. {#ast_info{}, TreeWalker},
  721. case Count of
  722. {{identifier, _, Name}, Value} ->
  723. [{Name, Value}|ArgList];
  724. _ ->
  725. ArgList
  726. end),
  727. TreeWalker2 = push_scope(NewScope, TreeWalker1),
  728. %% key for translation lookup
  729. SourceText = erlydtl_unparser:unparse(Contents),
  730. {{DefaultAst, AstInfo}, TreeWalker3} = body_ast(Contents, TreeWalker2),
  731. MergedInfo = merge_info(AstInfo, ArgInfo),
  732. #dtl_context{
  733. trans_fun = TFun,
  734. trans_locales = TLocales } = TreeWalker3#treewalker.context,
  735. if TFun =:= none; PluralContents =/= undefined ->
  736. %% translate in runtime
  737. {FinalAst, FinalTW} = blocktrans_runtime_ast(
  738. {DefaultAst, MergedInfo}, SourceText, Contents, Context,
  739. plural_contents(PluralContents, Count, TreeWalker3)),
  740. {FinalAst, restore_scope(TreeWalker1, FinalTW)};
  741. is_function(TFun, 2) ->
  742. %% translate in compile-time
  743. {FinalAstInfo, FinalTreeWalker, Clauses} =
  744. lists:foldr(
  745. fun (Locale, {AstInfoAcc, TreeWalkerAcc, ClauseAcc}) ->
  746. case TFun(SourceText, phrase_locale(Locale, Context)) of
  747. default ->
  748. {AstInfoAcc, TreeWalkerAcc, ClauseAcc};
  749. Body ->
  750. {ok, DjangoParseTree} = do_parse_template(Body, TreeWalkerAcc#treewalker.context),
  751. {{BodyAst, BodyInfo}, BodyTreeWalker} = body_ast(DjangoParseTree, TreeWalkerAcc),
  752. {merge_info(BodyInfo, AstInfoAcc), BodyTreeWalker,
  753. [?Q("_@Locale@ -> _@BodyAst")|ClauseAcc]}
  754. end
  755. end,
  756. {MergedInfo, TreeWalker3, []}, TLocales),
  757. FinalAst = ?Q("case _CurrentLocale of _@_Clauses -> _; _ -> _@DefaultAst end"),
  758. {{FinalAst, FinalAstInfo#ast_info{ translated_blocks = [SourceText] }},
  759. restore_scope(TreeWalker1, FinalTreeWalker)};
  760. true ->
  761. empty_ast(?ERR({translation_fun, TFun}, TreeWalker3))
  762. end.
  763. blocktrans_runtime_ast({DefaultAst, Info}, SourceText, Contents, Context, {Plural, TreeWalker}) ->
  764. %% Contents is flat - only strings and '{{var}}' allowed.
  765. %% build sorted list (orddict) of pre-resolved variables to pass to runtime translation function
  766. USortedVariables = lists:usort(fun({variable, {identifier, _, A}},
  767. {variable, {identifier, _, B}}) ->
  768. A =< B
  769. end, [Var || {variable, _}=Var
  770. <- Contents ++ maybe_plural_contents(Plural)]),
  771. VarBuilder = fun({variable, {identifier, _, Name}}=Var, TW) ->
  772. {{VarAst, _VarInfo}, VarTW} = resolve_variable_ast(Var, false, TW),
  773. {?Q("{_@name, _@VarAst}", [{name, merl:term(atom_to_list(Name))}]), VarTW}
  774. end,
  775. {VarAsts, TreeWalker1} = lists:mapfoldl(VarBuilder, TreeWalker, USortedVariables),
  776. VarListAst = erl_syntax:list(VarAsts),
  777. BlockTransAst = ?Q(["begin",
  778. " case erlydtl_runtime:translate_block(",
  779. " _@phrase, _@locale,",
  780. " _@VarListAst, _TranslationFun) of",
  781. " default -> _@DefaultAst;",
  782. " Text -> Text",
  783. " end",
  784. "end"],
  785. [{phrase, phrase_ast(SourceText, Plural)},
  786. {locale, phrase_locale_ast(Context)}]),
  787. {{BlockTransAst, merge_count_info(Info, Plural)}, TreeWalker1}.
  788. maybe_plural_contents(undefined) -> [];
  789. maybe_plural_contents({Contents, _}) -> Contents.
  790. merge_count_info(Info, undefined) -> Info;
  791. merge_count_info(Info, {_Contents, {_CountAst, CountInfo}}) ->
  792. merge_info(Info, CountInfo).
  793. plural_contents(undefined, _, TreeWalker) -> {undefined, TreeWalker};
  794. plural_contents(Contents, {_CountVarName, Value}, TreeWalker) ->
  795. {CountAst, TW} = value_ast(Value, false, false, TreeWalker),
  796. {{Contents, CountAst}, TW}.
  797. phrase_ast(Text, undefined) -> merl:term(Text);
  798. phrase_ast(Text, {Contents, {CountAst, _CountInfo}}) ->
  799. erl_syntax:tuple(
  800. [merl:term(Text),
  801. erl_syntax:tuple(
  802. [merl:term(erlydtl_unparser:unparse(Contents)),
  803. CountAst])
  804. ]).
  805. phrase_locale_ast(undefined) -> merl:var('_CurrentLocale');
  806. phrase_locale_ast(Context) -> erl_syntax:tuple([merl:var('_CurrentLocale'), merl:term(Context)]).
  807. phrase_locale(Locale, undefined) -> Locale;
  808. phrase_locale(Locale, Context) -> {Locale, Context}.
  809. translated_ast(Text, TreeWalker) ->
  810. translated_ast(Text, undefined, TreeWalker).
  811. translated_ast({noop, Value}, _, TreeWalker) ->
  812. value_ast(Value, true, true, TreeWalker);
  813. translated_ast(Text, {string_literal, _, Context}, TreeWalker) ->
  814. translated_ast(Text, unescape_string_literal(Context), TreeWalker);
  815. translated_ast({string_literal, _, String}, Context, TreeWalker) ->
  816. Text = unescape_string_literal(String),
  817. case call_extension(TreeWalker, translate_ast, [Text, Context, TreeWalker]) of
  818. undefined ->
  819. case TreeWalker#treewalker.context#dtl_context.trans_fun of
  820. none -> runtime_trans_ast(Text, Context, TreeWalker);
  821. Fun when is_function(Fun, 2) ->
  822. compiletime_trans_ast(Fun, Text, Context, TreeWalker);
  823. Fun when is_function(Fun, 1) ->
  824. compiletime_trans_ast(fun (T, _) -> Fun(T) end,
  825. Text, Context, TreeWalker);
  826. Fun ->
  827. empty_ast(?ERR({translation_fun, Fun}, TreeWalker))
  828. end;
  829. TranslatedAst ->
  830. TranslatedAst
  831. end;
  832. translated_ast(Value, Context, TreeWalker) ->
  833. runtime_trans_ast(value_ast(Value, true, false, TreeWalker), Context).
  834. runtime_trans_ast(Text, Context, TreeWalker) ->
  835. Info = #ast_info{ translatable_strings = [Text] },
  836. runtime_trans_ast({{merl:term(Text), Info}, TreeWalker}, Context).
  837. runtime_trans_ast({{ValueAst, AstInfo}, TreeWalker}, undefined) ->
  838. {{?Q("erlydtl_runtime:translate(_@ValueAst, _CurrentLocale, _TranslationFun)"),
  839. AstInfo}, TreeWalker};
  840. runtime_trans_ast({{ValueAst, AstInfo}, TreeWalker}, Context) ->
  841. {{?Q("erlydtl_runtime:translate(_@ValueAst, {_CurrentLocale, _@Context@}, _TranslationFun)"),
  842. AstInfo}, TreeWalker}.
  843. compiletime_trans_ast(TFun, Text, LContext,
  844. #treewalker{
  845. context=#dtl_context{
  846. trans_locales=TLocales
  847. }=Context
  848. }=TreeWalker) ->
  849. ClAst = lists:foldl(
  850. fun(Locale, ClausesAcc) ->
  851. [?Q("_@Locale@ -> _@translated",
  852. [{translated, case TFun(Text, phrase_locale(Locale, LContext)) of
  853. default -> string_ast(Text, Context);
  854. Translated -> string_ast(Translated, Context)
  855. end}])
  856. |ClausesAcc]
  857. end,
  858. [], TLocales),
  859. {{?Q(["case _CurrentLocale of",
  860. " _@_ClAst -> _;",
  861. " _ -> _@string",
  862. "end"],
  863. [{string, string_ast(Text, Context)}]),
  864. #ast_info{ translatable_strings = [Text] }},
  865. TreeWalker}.
  866. %%% end of context being translation context
  867. %% Completely unnecessary in ErlyDTL (use {{ "{%" }} etc), but implemented for compatibility.
  868. templatetag_ast("openblock", TreeWalker) ->
  869. string_ast("{%", TreeWalker);
  870. templatetag_ast("closeblock", TreeWalker) ->
  871. string_ast("%}", TreeWalker);
  872. templatetag_ast("openvariable", TreeWalker) ->
  873. string_ast("{{", TreeWalker);
  874. templatetag_ast("closevariable", TreeWalker) ->
  875. string_ast("}}", TreeWalker);
  876. templatetag_ast("openbrace", TreeWalker) ->
  877. string_ast("{", TreeWalker);
  878. templatetag_ast("closebrace", TreeWalker) ->
  879. string_ast("}", TreeWalker);
  880. templatetag_ast("opencomment", TreeWalker) ->
  881. string_ast("{#", TreeWalker);
  882. templatetag_ast("closecomment", TreeWalker) ->
  883. string_ast("#}", TreeWalker).
  884. widthratio_ast(Numerator, Denominator, Scale, TreeWalker) ->
  885. {{NumAst, NumInfo}, TreeWalker1} = value_ast(Numerator, false, true, TreeWalker),
  886. {{DenAst, DenInfo}, TreeWalker2} = value_ast(Denominator, false, true, TreeWalker1),
  887. {{ScaleAst, ScaleInfo}, TreeWalker3} = value_ast(Scale, false, true, TreeWalker2),
  888. {{format_number_ast(?Q("erlydtl_runtime:widthratio(_@NumAst, _@DenAst, _@ScaleAst)")),
  889. merge_info(ScaleInfo, merge_info(NumInfo, DenInfo))},
  890. TreeWalker3}.
  891. string_ast(Arg, #treewalker{ context=Context }=TreeWalker) ->
  892. {{string_ast(Arg, Context), #ast_info{}}, TreeWalker};
  893. string_ast(Arg, Context) ->
  894. merl:term(erlydtl_compiler_utils:to_string(Arg, Context)).
  895. include_ast(File, ArgList, Scopes, #treewalker{ context=Context }=TreeWalker) ->
  896. FilePath = full_path(File, Context#dtl_context.doc_root),
  897. case parse_file(FilePath, Context) of
  898. {ok, InclusionParseTree, CheckSum} ->
  899. {NewScope, {ArgInfo, TreeWalker1}}
  900. = lists:mapfoldl(
  901. fun ({{identifier, _, LocalVarName}, Value}, {AstInfoAcc, TreeWalkerAcc}) ->
  902. {{Ast, Info}, TW} = value_ast(Value, false, false, TreeWalkerAcc),
  903. {{LocalVarName, Ast}, {merge_info(AstInfoAcc, Info), TW}}
  904. end, {#ast_info{}, TreeWalker}, ArgList),
  905. C = TreeWalker1#treewalker.context,
  906. {{BodyAst, BodyInfo}, TreeWalker2} = with_dependency(
  907. {FilePath, CheckSum},
  908. body_ast(
  909. InclusionParseTree,
  910. TreeWalker1#treewalker{
  911. context=C#dtl_context{
  912. parse_trail = [FilePath | C#dtl_context.parse_trail],
  913. local_scopes = [NewScope|Scopes]
  914. }
  915. })),
  916. {{BodyAst, merge_info(BodyInfo, ArgInfo)},
  917. reset_parse_trail(C#dtl_context.parse_trail, TreeWalker2)};
  918. {error, Reason} ->
  919. empty_ast(?ERR(Reason, TreeWalker))
  920. end.
  921. %% include at run-time
  922. ssi_ast(FileName, #treewalker{
  923. context=#dtl_context{
  924. reader = {Mod, Fun},
  925. doc_root = Dir
  926. }
  927. }=TreeWalker) ->
  928. {{FileAst, Info}, TreeWalker1} = value_ast(FileName, true, true, TreeWalker),
  929. {{?Q("erlydtl_runtime:read_file(_@Mod@, _@Fun@, _@Dir@, _@FileAst)"), Info}, TreeWalker1}.
  930. filter_tag_ast(FilterList, Contents, TreeWalker) ->
  931. {{InnerAst, Info}, TreeWalker1} = body_ast(Contents, push_auto_escape(did, TreeWalker)),
  932. {{FilteredAst, FilteredInfo}, TreeWalker2} =
  933. lists:foldl(
  934. fun ({{identifier, _, Name}, []}, {{AstAcc, InfoAcc}, TreeWalkerAcc})
  935. when Name =:= 'escape'; Name =:= 'safe'; Name =:= 'safeseq' ->
  936. {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{ safe = true }};
  937. (Filter, {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
  938. {{Ast, AstInfo}, TW} = filter_ast1(Filter, AstAcc, TreeWalkerAcc),
  939. {{Ast, merge_info(InfoAcc, AstInfo)}, TW}
  940. end,
  941. {{?Q("erlang:iolist_to_binary(_@InnerAst)"), Info},
  942. pop_auto_escape(TreeWalker1)},
  943. FilterList),
  944. EscapedAst = case search_for_escape_filter(
  945. lists:reverse(FilterList),
  946. TreeWalker2#treewalker.context) of
  947. on -> ?Q("erlydtl_filters:force_escape(_@FilteredAst)");
  948. _ -> FilteredAst
  949. end,
  950. {{EscapedAst, FilteredInfo}, TreeWalker2}.
  951. search_for_escape_filter(FilterList, #dtl_context{auto_escape = [on|_]}) ->
  952. search_for_safe_filter(FilterList);
  953. search_for_escape_filter(_, #dtl_context{auto_escape = [did|_]}) -> off;
  954. search_for_escape_filter([{{identifier, _, 'escape'}, []}|Rest], _Context) ->
  955. search_for_safe_filter(Rest);
  956. search_for_escape_filter([_|Rest], Context) ->
  957. search_for_escape_filter(Rest, Context);
  958. search_for_escape_filter([], _Context) -> off.
  959. search_for_safe_filter([{{identifier, _, Name}, []}|_])
  960. when Name =:= 'safe'; Name =:= 'safeseq' -> off;
  961. search_for_safe_filter([_|Rest]) -> search_for_safe_filter(Rest);
  962. search_for_safe_filter([]) -> on.
  963. filter_ast(Variable, Filter, TreeWalker) ->
  964. %% the escape filter is special; it is always applied last, so we have to go digging for it
  965. %% AutoEscape = 'did' means we (will have) decided whether to escape the current variable,
  966. %% so don't do any more escaping
  967. {{UnescapedAst, Info}, TreeWalker1} =
  968. filter_ast_noescape(
  969. Variable, Filter,
  970. push_auto_escape(did, TreeWalker)),
  971. {EscapedAst, TreeWalker2} =
  972. case search_for_escape_filter(Variable, Filter, TreeWalker#treewalker.context) of
  973. on -> {?Q("erlydtl_filters:force_escape(_@UnescapedAst)"),
  974. TreeWalker1#treewalker{ safe = true }};
  975. _ -> {UnescapedAst, TreeWalker1}
  976. end,
  977. {{EscapedAst, Info}, pop_auto_escape(TreeWalker2)}.
  978. filter_ast_noescape(Variable, {{identifier, _, Name}, []}, TreeWalker)
  979. when Name =:= 'escape'; Name =:= 'safe'; Name =:= 'safeseq' ->
  980. value_ast(Variable, true, false, TreeWalker#treewalker{safe = true});
  981. filter_ast_noescape(Variable, Filter, TreeWalker) ->
  982. {{ValueAst, Info1}, TreeWalker2} = value_ast(Variable, true, false, TreeWalker),
  983. {{VarValue, Info2}, TreeWalker3} = filter_ast1(Filter, ValueAst, TreeWalker2),
  984. {{VarValue, merge_info(Info1, Info2)}, TreeWalker3}.
  985. filter_ast1({{identifier, Pos, Name}, Args}, ValueAst, TreeWalker) ->
  986. {{ArgsAst, ArgsInfo}, TreeWalker1} =
  987. lists:foldr(
  988. fun (Arg, {{AccAst, AccInfo}, AccTreeWalker}) ->
  989. {{ArgAst, ArgInfo}, ArgTreeWalker} = value_ast(Arg, false, false, AccTreeWalker),
  990. {{[ArgAst|AccAst], merge_info(ArgInfo, AccInfo)}, ArgTreeWalker}
  991. end,
  992. {{[], #ast_info{}}, TreeWalker},
  993. Args),
  994. case filter_ast2(Name, [ValueAst|ArgsAst], TreeWalker1#treewalker.context) of
  995. {ok, FilterAst} ->
  996. {{FilterAst, ArgsInfo}, TreeWalker1};
  997. Error ->
  998. empty_ast(?WARN({Pos, Error}, TreeWalker1))
  999. end.
  1000. filter_ast2(Name, Args, #dtl_context{ filters = Filters }) ->
  1001. case proplists:get_value(Name, Filters) of
  1002. {Mod, Fun}=Filter ->
  1003. case erlang:function_exported(Mod, Fun, length(Args)) of
  1004. true -> {ok, ?Q("'@Mod@':'@Fun@'(_@Args)")};
  1005. false ->
  1006. {filter_args, Name, Filter, length(Args)}
  1007. end;
  1008. undefined ->
  1009. {unknown_filter, Name, length(Args)}
  1010. end.
  1011. search_for_escape_filter(Variable, Filter, #dtl_context{auto_escape = [on|_]}) ->
  1012. search_for_safe_filter(Variable, Filter);
  1013. search_for_escape_filter(_, _, #dtl_context{auto_escape = [did|_]}) ->
  1014. off;
  1015. search_for_escape_filter(Variable, {{identifier, _, 'escape'}, []} = Filter, _Context) ->
  1016. search_for_safe_filter(Variable, Filter);
  1017. search_for_escape_filter({apply_filter, Variable, Filter}, _, Context) ->
  1018. search_for_escape_filter(Variable, Filter, Context);
  1019. search_for_escape_filter(_Variable, _Filter, _Context) ->
  1020. off.
  1021. search_for_safe_filter(_, {{identifier, _, 'safe'}, []}) ->
  1022. off;
  1023. search_for_safe_filter(_, {{identifier, _, 'safeseq'}, []}) ->
  1024. off;
  1025. search_for_safe_filter({apply_filter, Variable, Filter}, _) ->
  1026. search_for_safe_filter(Variable, Filter);
  1027. search_for_safe_filter(_Variable, _Filter) ->
  1028. on.
  1029. finder_function(true) -> {erlydtl_runtime, fetch_value};
  1030. finder_function(false) -> {erlydtl_runtime, find_value}.
  1031. finder_function(EmptyIfUndefined, TreeWalker) ->
  1032. case call_extension(TreeWalker, finder_function, [EmptyIfUndefined]) of
  1033. undefined -> finder_function(EmptyIfUndefined);
  1034. Result -> Result
  1035. end.
  1036. resolve_variable_ast({extension, Tag}, _, TreeWalker) ->
  1037. extension_ast(Tag, TreeWalker);
  1038. resolve_variable_ast(VarTuple, EmptyIfUndefined, TreeWalker)
  1039. when is_boolean(EmptyIfUndefined) ->
  1040. resolve_variable_ast(VarTuple, finder_function(EmptyIfUndefined, TreeWalker), TreeWalker);
  1041. resolve_variable_ast(VarTuple, FinderFunction, TreeWalker) ->
  1042. resolve_variable_ast1(VarTuple, FinderFunction, TreeWalker).
  1043. resolve_variable_ast1({attribute, {{_, Pos, Attr}, Variable}}, {Runtime, Finder}=FinderFunction, TreeWalker) ->
  1044. {{VarAst, VarInfo}, TreeWalker1} = resolve_variable_ast(Variable, FinderFunction, TreeWalker),
  1045. #treewalker{
  1046. context=#dtl_context{
  1047. lists_0_based = Lists0Based,
  1048. tuples_0_based = Tuples0Based
  1049. }
  1050. } = TreeWalker,
  1051. FileName = get_current_file(TreeWalker1),
  1052. {{?Q(["'@Runtime@':'@Finder@'(",
  1053. " _@Attr@, _@VarAst,",
  1054. " [{lists_0_based, _@Lists0Based@},",
  1055. " {tuples_0_based, _@Tuples0Based@},",
  1056. " {render_options, RenderOptions},",
  1057. " {record_info, _RecordInfo},",
  1058. " {filename, _@FileName@},",
  1059. " {pos, _@Pos@}",
  1060. " ])"]),
  1061. VarInfo},
  1062. TreeWalker1};
  1063. resolve_variable_ast1({variable, {identifier, Pos, VarName}}, {Runtime, Finder}, TreeWalker) ->
  1064. Ast = case resolve_variable(VarName, TreeWalker) of
  1065. {_, undefined} ->
  1066. FileName = get_current_file(TreeWalker),
  1067. {?Q(["'@Runtime@':'@Finder@'(",
  1068. " _@VarName@, _Variables,",
  1069. " [{filename, _@FileName@},",
  1070. " {pos, _@Pos@},",
  1071. " {record_info, _RecordInfo},",
  1072. " {render_options, RenderOptions}])"
  1073. ]),
  1074. #ast_info{ var_names=[VarName] }};
  1075. {default_vars, Val} ->
  1076. FileName = get_current_file(TreeWalker),
  1077. {?Q(["'@Runtime@':fetch_value(",
  1078. " _@VarName@, _Variables,",
  1079. " [{filename, _@FileName@},",
  1080. " {pos, _@Pos@},",
  1081. " {record_info, _RecordInfo},",
  1082. " {render_options, RenderOptions}],",
  1083. " _@val)"
  1084. ],
  1085. [{val, merl:term(erlydtl_filters:format_number(Val))}]),
  1086. #ast_info{ var_names=[VarName], def_names=[VarName] }};
  1087. {constant, Val} ->
  1088. {merl:term(erlydtl_filters:format_number(Val)),
  1089. #ast_info{ const_names=[VarName] }};
  1090. {scope, Val} ->
  1091. {Val, #ast_info{}}
  1092. end,
  1093. {Ast, TreeWalker}.
  1094. resolve_reserved_variable(ReservedName, TreeWalker) ->
  1095. resolve_reserved_variable(ReservedName, merl:term(undefined), TreeWalker).
  1096. resolve_reserved_variable(ReservedName, Default, TreeWalker) ->
  1097. case resolve_variable(ReservedName, Default, TreeWalker) of
  1098. {Src, Value} when Src =:= scope; Value =:= Default ->
  1099. {Value, TreeWalker};
  1100. _ ->
  1101. {Default, ?ERR({reserved_variable, ReservedName}, TreeWalker)}
  1102. end.
  1103. format({{Ast, Info}, TreeWalker}) ->
  1104. auto_escape({{format_number_ast(Ast), Info}, TreeWalker}).
  1105. format_number_ast(Ast) ->
  1106. ?Q("erlydtl_filters:format_number(_@Ast)").
  1107. auto_escape({AstInfo, #treewalker{ safe = true }=TW}) ->
  1108. {AstInfo, TW#treewalker{ safe = false }};
  1109. auto_escape({{Value, Info}, #treewalker{ context=#dtl_context{auto_escape=[on|_]} }=TW}) ->
  1110. {{?Q("erlydtl_filters:force_escape(_@Value)"), Info}, TW};
  1111. auto_escape(Value) -> Value.
  1112. firstof_ast(Vars, TreeWalker) ->
  1113. body_ast(
  1114. [lists:foldr(
  1115. fun ({L, _, _}=Var, [])
  1116. when L=:=string_literal;L=:=number_literal ->
  1117. Var;
  1118. ({L, _, _}, _)
  1119. when L=:=string_literal;L=:=number_literal ->
  1120. erlang:error(errbadliteral);
  1121. (Var, []) ->
  1122. {'ifelse', Var, [Var], []};
  1123. (Var, Acc) ->
  1124. {'ifelse', Var, [Var], [Acc]}
  1125. end,
  1126. [], Vars)
  1127. ],
  1128. TreeWalker).
  1129. ifelse_ast(Expression, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, TreeWalker) ->
  1130. Info = merge_info(IfContentsInfo, ElseContentsInfo),
  1131. {{Ast, ExpressionInfo}, TreeWalker1} = value_ast(Expression, false, false, TreeWalker),
  1132. {{?Q(["case erlydtl_runtime:is_true(_@Ast) of",
  1133. " true -> _@IfContentsAst;",
  1134. " _ -> _@ElseContentsAst",
  1135. "end"]),
  1136. merge_info(ExpressionInfo, Info)},
  1137. TreeWalker1}.
  1138. with_ast(ArgList, Contents, TreeWalker) ->
  1139. {ArgAstList, {ArgInfo, TreeWalker1}} =
  1140. lists:mapfoldl(
  1141. fun ({{identifier, _, _LocalVarName}, Value}, {AstInfoAcc, TreeWalkerAcc}) ->
  1142. {{Ast, Info}, TW} = value_ast(Value, false, false, TreeWalkerAcc),
  1143. {Ast, {merge_info(AstInfoAcc, Info), TW}}
  1144. end,
  1145. {#ast_info{}, TreeWalker},
  1146. ArgList),
  1147. NewScope = lists:map(
  1148. fun ({{identifier, _, LocalVarName}, _Value}) ->
  1149. {LocalVarName, varname_ast(LocalVarName)}
  1150. end,
  1151. ArgList),
  1152. {{InnerAst, InnerInfo}, TreeWalker2} =
  1153. body_ast(
  1154. Contents,
  1155. push_scope(NewScope, TreeWalker1)),
  1156. {{?Q("fun (_@args) -> _@InnerAst end (_@ArgAstList)",
  1157. [{args, element(2, lists:unzip(NewScope))}]),
  1158. merge_info(ArgInfo, InnerInfo)},
  1159. restore_scope(TreeWalker1, TreeWalker2)}.
  1160. scope_as(VarName, Contents, TreeWalker) ->
  1161. {{ContentsAst, ContentsInfo}, TreeWalker1} = body_ast(Contents, TreeWalker),
  1162. VarAst = varname_ast(VarName),
  1163. {Id, TreeWalker2} = begin_scope(
  1164. {[{VarName, VarAst}],
  1165. [?Q("_@VarAst = _@ContentsAst")]},
  1166. TreeWalker1),
  1167. {{Id, ContentsInfo}, TreeWalker2}.
  1168. regroup_ast(ListVariable, GrouperVariable, LocalVarName, TreeWalker) ->
  1169. {{ListAst, ListInfo}, TreeWalker1} = value_ast(ListVariable, false, true, TreeWalker),
  1170. LocalVarAst = varname_ast(LocalVarName),
  1171. {Id, TreeWalker2} = begin_scope(
  1172. {[{LocalVarName, LocalVarAst}],
  1173. [?Q("_@LocalVarAst = erlydtl_runtime:regroup(_@ListAst, _@regroup)",
  1174. [{regroup, regroup_filter(GrouperVariable, [])}])
  1175. ]},
  1176. TreeWalker1),
  1177. {{Id, ListInfo}, TreeWalker2}.
  1178. regroup_filter({attribute,{{identifier,_,Ident},Next}},Acc) ->
  1179. regroup_filter(Next,[erl_syntax:atom(Ident)|Acc]);
  1180. regroup_filter({variable,{identifier,_,Var}},Acc) ->
  1181. erl_syntax:list([erl_syntax:atom(Var)|Acc]).
  1182. to_list_ast(Value, IsReversed) ->
  1183. ?Q("erlydtl_runtime:to_list(_@Value, _@IsReversed)").
  1184. to_list_ast(Value, IsReversed, TreeWalker) ->
  1185. case call_extension(TreeWalker, to_list_ast, [Value, IsReversed, TreeWalker]) of
  1186. undefined -> to_list_ast(Value, IsReversed);
  1187. Result -> Result
  1188. end.
  1189. for_loop_ast(IteratorList, LoopValue, IsReversed, Contents,
  1190. {EmptyContentsAst, EmptyContentsInfo},
  1191. #treewalker{ context=Context }=TreeWalker) ->
  1192. %% create unique namespace for this instance
  1193. Level = length(Context#dtl_context.local_scopes),
  1194. {Row, Col} = element(2, hd(IteratorList)),
  1195. ForId = lists:concat(["/", Level, "_", Row, ":", Col]),
  1196. Counters = merl:var(lists:concat(["Counters", ForId])),
  1197. Vars = merl:var(lists:concat(["Vars", ForId])),
  1198. %% setup
  1199. VarScope = lists:map(
  1200. fun({identifier, {R, C}, Iterator}) ->
  1201. {Iterator, varname_ast(lists:concat([
  1202. Iterator,"/", Level, "_", R, ":", C]))}
  1203. end, IteratorList),
  1204. {Iterators, IteratorVars} = lists:unzip(VarScope),
  1205. IteratorCount = length(IteratorVars),
  1206. {{LoopBodyAst, Info}, TreeWalker1} =
  1207. body_ast(
  1208. Contents,
  1209. push_scope([{'forloop', Counters} | VarScope],
  1210. TreeWalker)),
  1211. {{LoopValueAst, LoopValueInfo}, TreeWalker2} =
  1212. value_ast(LoopValue, false, true, restore_scope(TreeWalker, TreeWalker1)),
  1213. LoopValueAst0 = to_list_ast(LoopValueAst, merl:term(IsReversed), TreeWalker2),
  1214. {ParentLoop, TreeWalker3} = resolve_reserved_variable('forloop', TreeWalker2),
  1215. {{?Q(["erlydtl_runtime:forloop(",
  1216. " fun (_@Vars, _@Counters) ->",
  1217. " {_@IteratorVars} = if is_tuple(_@Vars), size(_@Vars) == _@IteratorCount@ -> _@Vars;",
  1218. " _@___ifclauses -> _",
  1219. " end,",
  1220. " {_@LoopBodyAst, erlydtl_runtime:increment_counter_stats(_@Counters)}",
  1221. " end,",
  1222. " _@LoopValueAst0, _@ParentLoop, _@EmptyContentsAst)"],
  1223. [{ifclauses, if IteratorCount > 1 ->
  1224. ?Q(["() when is_list(_@Vars), length(_@Vars) == _@IteratorCount@ ->",
  1225. " list_to_tuple(_@Vars);",
  1226. "() when true -> throw({for_loop, _@Iterators@, _@Vars})"]);
  1227. true ->
  1228. ?Q("() when true -> {_@Vars}")
  1229. end}]),
  1230. merge_info(merge_info(Info, EmptyContentsInfo), LoopValueInfo)},
  1231. TreeWalker3}.
  1232. ifchanged_values_ast(Values, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, TreeWalker) ->
  1233. Info = merge_info(IfContentsInfo, ElseContentsInfo),
  1234. ValueAstFun = fun (Expr, {LTreeWalker, LInfo, Acc}) ->
  1235. {{EAst, EInfo}, ETw} = value_ast(Expr, false, true, LTreeWalker),
  1236. {ETw, merge_info(LInfo, EInfo),
  1237. [?Q("{_@hash, _@EAst}",
  1238. [{hash, merl:term(erlang:phash2(Expr))}])
  1239. |Acc]}
  1240. end,
  1241. {TreeWalker1, MergedInfo, Changed} = lists:foldl(ValueAstFun, {TreeWalker, Info, []}, Values),
  1242. {{?Q(["case erlydtl_runtime:ifchanged([_@Changed]) of",
  1243. " true -> _@IfContentsAst;",
  1244. " _ -> _@ElseContentsAst",
  1245. "end"]),
  1246. MergedInfo},
  1247. TreeWalker1}.
  1248. ifchanged_contents_ast(Contents, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, TreeWalker) ->
  1249. {{?Q(["case erlydtl_runtime:ifchanged([{_@hash, _@IfContentsAst}]) of",
  1250. " true -> _@IfContentsAst;",
  1251. " _ -> _@ElseContentsAst",
  1252. "end"],
  1253. [{hash, merl:term(erlang:phash2(Contents))}]),
  1254. merge_info(IfContentsInfo, ElseContentsInfo)},
  1255. TreeWalker}.
  1256. cycle_ast(Names, #treewalker{ context=Context }=TreeWalker) ->
  1257. {NamesTuple, VarNames}
  1258. = lists:mapfoldl(
  1259. fun ({string_literal, _, Str}, VarNamesAcc) ->
  1260. S = string_ast(unescape_string_literal(Str), Context),
  1261. {S, VarNamesAcc};
  1262. ({variable, _}=Var, VarNamesAcc) ->
  1263. {{V, #ast_info{ var_names=[VarName] }}, _} = resolve_variable_ast(Var, true, TreeWalker),
  1264. {V, [VarName|VarNamesAcc]};
  1265. ({number_literal, _, Num}, VarNamesAcc) ->
  1266. {format_number_ast(erl_syntax:integer(Num)), VarNamesAcc};
  1267. (_, VarNamesAcc) ->
  1268. {[], VarNamesAcc}
  1269. end, [], Names),
  1270. {ForLoop, TreeWalker1} = resolve_reserved_variable('forloop', TreeWalker),
  1271. {{?Q("erlydtl_runtime:cycle({_@NamesTuple}, _@ForLoop)"),
  1272. #ast_info{ var_names = VarNames }},
  1273. TreeWalker1}.
  1274. %% Older Django templates treat cycle with comma-delimited elements as strings
  1275. cycle_compat_ast(Names, #treewalker{ context=Context }=TreeWalker) ->
  1276. NamesTuple = lists:map(
  1277. fun ({identifier, _, X}) ->
  1278. string_ast(X, Context)
  1279. end, Names),
  1280. {ForLoop, TreeWalker1} = resolve_reserved_variable('forloop', TreeWalker),
  1281. {{?Q("erlydtl_runtime:cycle({_@NamesTuple}, _@ForLoop)"),
  1282. #ast_info{}},
  1283. TreeWalker1}.
  1284. now_ast(FormatString, TreeWalker) ->
  1285. %% Note: we can't use unescape_string_literal here
  1286. %% because we want to allow escaping in the format string.
  1287. %% We only want to remove the surrounding escapes,
  1288. %% i.e. \"foo\" becomes "foo"
  1289. UnescapeOuter = string:strip(FormatString, both, 34),
  1290. {{StringAst, Info}, TreeWalker1} = string_ast(UnescapeOuter, TreeWalker),
  1291. {{?Q("erlydtl_dateformat:format(_@StringAst)"), Info}, TreeWalker1}.
  1292. spaceless_ast(Contents, TreeWalker) ->
  1293. {{Ast, Info}, TreeWalker1} = body_ast(Contents, TreeWalker),
  1294. {{?Q("erlydtl_runtime:spaceless(_@Ast)"), Info}, TreeWalker1}.
  1295. load_libs_ast(Libs, TreeWalker) ->
  1296. TreeWalker1 = lists:foldl(
  1297. fun ({identifier, Pos, Lib}, TW) ->
  1298. load_library(Pos, Lib, TW)
  1299. end,
  1300. TreeWalker, Libs),
  1301. empty_ast(TreeWalker1).
  1302. load_from_lib_ast(What, {identifier, Pos, Lib}, TreeWalker) ->
  1303. Names = lists:foldl(
  1304. fun ({identifier, _, Name}, Acc) -> [Name|Acc] end,
  1305. [], What),
  1306. empty_ast(load_library(Pos, Lib, Names, TreeWalker)).
  1307. %%-------------------------------------------------------------------
  1308. %% Custom tags
  1309. %%-------------------------------------------------------------------
  1310. interpret_value({trans, StringLiteral}, TreeWalker) ->
  1311. translated_ast(StringLiteral, TreeWalker);
  1312. interpret_value(Value, TreeWalker) ->
  1313. value_ast(Value, false, false, TreeWalker).
  1314. interpret_args(Args, TreeWalker) ->
  1315. lists:foldr(
  1316. fun ({{identifier, _, Key}, Value}, {{ArgsAcc, AstInfoAcc}, TreeWalkerAcc}) ->
  1317. {{Ast0, AstInfo0}, TreeWalker0} = interpret_value(Value, TreeWalkerAcc),
  1318. {{[?Q("{_@Key@, _@Ast0}")|ArgsAcc], merge_info(AstInfo0, AstInfoAcc)}, TreeWalker0};
  1319. (Value, {{ArgsAcc, AstInfoAcc}, TreeWalkerAcc}) ->
  1320. {{Ast0, AstInfo0}, TreeWalker0} = value_ast(Value, false, false, TreeWalkerAcc),
  1321. {{[Ast0|ArgsAcc], merge_info(AstInfo0, AstInfoAcc)}, TreeWalker0}
  1322. end, {{[], #ast_info{}}, TreeWalker}, Args).
  1323. tag_ast(Name, Args, TreeWalker) ->
  1324. {{InterpretedArgs, AstInfo1}, TreeWalker1} = interpret_args(Args, TreeWalker),
  1325. {{RenderAst, RenderInfo}, TreeWalker2} = custom_tags_modules_ast(Name, InterpretedArgs, TreeWalker1),
  1326. {{RenderAst, merge_info(AstInfo1, RenderInfo)}, TreeWalker2}.
  1327. custom_tags_modules_ast({identifier, Pos, Name}, InterpretedArgs,
  1328. #treewalker{
  1329. context=#dtl_context{
  1330. tags = Tags,
  1331. module = Module,
  1332. is_compiling_dir=IsCompilingDir
  1333. }
  1334. }=TreeWalker) ->
  1335. case proplists:get_value(Name, Tags) of
  1336. {Mod, Fun}=Tag ->
  1337. case lists:max([-1] ++ [I || {N,I} <- Mod:module_info(exports), N =:= Fun]) of
  1338. 2 ->
  1339. {{?Q("'@Mod@':'@Fun@'([_@InterpretedArgs], RenderOptions)"),
  1340. #ast_info{}}, TreeWalker};
  1341. 1 ->
  1342. {{?Q("'@Mod@':'@Fun@'([_@InterpretedArgs])"),
  1343. #ast_info{}}, TreeWalker};
  1344. -1 ->
  1345. empty_ast(?WARN({Pos, {missing_tag, Name, Tag}}, TreeWalker));
  1346. I ->
  1347. empty_ast(?WARN({Pos, {bad_tag, Name, Tag, I}}, TreeWalker))
  1348. end;
  1349. undefined ->
  1350. if IsCompilingDir =/= false ->
  1351. {{?Q("'@Module@':'@Name@'([_@InterpretedArgs], RenderOptions)"),
  1352. #ast_info{ custom_tags = [Name] }}, TreeWalker};
  1353. true ->
  1354. {{?Q("render_tag(_@Name@, [_@InterpretedArgs], RenderOptions)"),
  1355. #ast_info{ custom_tags = [Name] }}, TreeWalker}
  1356. end
  1357. end.
  1358. call_ast(Module, TreeWalker) ->
  1359. call_ast(Module, merl:var("_Variables"), #ast_info{}, TreeWalker).
  1360. call_with_ast(Module, Variable, TreeWalker) ->
  1361. {{VarAst, VarInfo}, TreeWalker2} = resolve_variable_ast(Variable, false, TreeWalker),
  1362. call_ast(Module, VarAst, VarInfo, TreeWalker2).
  1363. call_ast(Module, Variable, AstInfo, TreeWalker) ->
  1364. Ast = ?Q(["case '@Module@':render(_@Variable, RenderOptions) of",
  1365. " {ok, Rendered} -> Rendered;",
  1366. " {error, Reason} -> io_lib:format(\"error: ~p\", [Reason])",
  1367. "end"]),
  1368. with_dependencies(Module:dependencies(), {{Ast, AstInfo}, TreeWalker}).
  1369. create_scope(Vars, VarScope) ->
  1370. {Scope, Values} =
  1371. lists:foldl(
  1372. fun ({Name, Value}, {VarAcc, ValueAcc}) ->
  1373. NameAst = varname_ast(lists:concat(["_", Name, VarScope])),
  1374. {[{Name, NameAst}|VarAcc],
  1375. [?Q("_@NameAst = _@Value")|ValueAcc]
  1376. }
  1377. end,
  1378. empty_scope(),
  1379. Vars),
  1380. {Scope, [Values]}.
  1381. create_scope(Vars, {Row, Col}, #treewalker{ context=Context }) ->
  1382. Level = length(Context#dtl_context.local_scopes),
  1383. create_scope(Vars, lists:concat(["/", Level, "_", Row, ":", Col])).
  1384. varname_ast([$_|VarName]) ->
  1385. merl:var(lists:concat(["_Var__", VarName]));
  1386. varname_ast(VarName) ->
  1387. merl:var(lists:concat(["Var_", VarName])).