erlydtl_beam_compiler.erl 72 KB

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