erlydtl_beam_compiler.erl 75 KB

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