erlydtl_beam_compiler.erl 78 KB

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