erlydtl_compiler.erl 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175
  1. %%%-------------------------------------------------------------------
  2. %%% File: erlydtl_compiler.erl
  3. %%% @author Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
  4. %%% @author Evan Miller <emmiller@gmail.com>
  5. %%% @copyright 2008 Roberto Saccon, Evan Miller
  6. %%% @doc
  7. %%% ErlyDTL template compiler
  8. %%% @end
  9. %%%
  10. %%% The MIT License
  11. %%%
  12. %%% Copyright (c) 2007 Roberto Saccon, Evan Miller
  13. %%%
  14. %%% Permission is hereby granted, free of charge, to any person obtaining a copy
  15. %%% of this software and associated documentation files (the "Software"), to deal
  16. %%% in the Software without restriction, including without limitation the rights
  17. %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  18. %%% copies of the Software, and to permit persons to whom the Software is
  19. %%% furnished to do so, subject to the following conditions:
  20. %%%
  21. %%% The above copyright notice and this permission notice shall be included in
  22. %%% all copies or substantial portions of the Software.
  23. %%%
  24. %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  25. %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  26. %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  27. %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  28. %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  29. %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  30. %%% THE SOFTWARE.
  31. %%%
  32. %%% @since 2007-12-16 by Roberto Saccon, Evan Miller
  33. %%%-------------------------------------------------------------------
  34. -module(erlydtl_compiler).
  35. -author('rsaccon@gmail.com').
  36. -author('emmiller@gmail.com').
  37. %% --------------------------------------------------------------------
  38. %% Definitions
  39. %% --------------------------------------------------------------------
  40. -export([compile/2, compile/3, compile_dir/2, compile_dir/3, parse/1]).
  41. -record(dtl_context, {
  42. local_scopes = [],
  43. block_dict = dict:new(),
  44. blocktrans_fun = none,
  45. blocktrans_locales = [],
  46. auto_escape = off,
  47. doc_root = "",
  48. parse_trail = [],
  49. vars = [],
  50. custom_tags_dir = [],
  51. custom_tags_modules = [],
  52. reader = {file, read_file},
  53. module = [],
  54. compiler_options = [verbose, report_errors],
  55. binary_strings = true,
  56. force_recompile = false,
  57. locale = none,
  58. is_compiling_dir = false}).
  59. -record(ast_info, {
  60. dependencies = [],
  61. translatable_strings = [],
  62. translatable_blocks= [],
  63. custom_tags = [],
  64. var_names = [],
  65. pre_render_asts = []}).
  66. -record(treewalker, {
  67. counter = 0,
  68. safe = false
  69. }).
  70. compile(Binary, Module) when is_binary(Binary) ->
  71. compile(Binary, Module, []);
  72. compile(File, Module) ->
  73. compile(File, Module, []).
  74. compile(Binary, Module, Options) when is_binary(Binary) ->
  75. File = "",
  76. CheckSum = "",
  77. case parse(Binary) of
  78. {ok, DjangoParseTree} ->
  79. case compile_to_binary(File, DjangoParseTree,
  80. init_dtl_context(File, Module, Options), CheckSum) of
  81. {ok, Module1, _} ->
  82. {ok, Module1};
  83. Err ->
  84. Err
  85. end;
  86. Err ->
  87. Err
  88. end;
  89. compile(File, Module, Options) ->
  90. Context = init_dtl_context(File, Module, Options),
  91. case parse(File, Context) of
  92. ok ->
  93. ok;
  94. {ok, DjangoParseTree, CheckSum} ->
  95. case compile_to_binary(File, DjangoParseTree, Context, CheckSum) of
  96. {ok, Module1, Bin} ->
  97. write_binary(Module1, Bin, Options);
  98. Err ->
  99. Err
  100. end;
  101. Err ->
  102. Err
  103. end.
  104. compile_dir(Dir, Module) ->
  105. compile_dir(Dir, Module, []).
  106. compile_dir(Dir, Module, Options) ->
  107. Context = init_dtl_context_dir(Dir, Module, Options),
  108. Files = filelib:fold_files(Dir, ".*", true, fun(F1,Acc1) -> [F1 | Acc1] end, []),
  109. {ParserResults, ParserErrors} = lists:foldl(fun
  110. (File, {ResultAcc, ErrorAcc}) ->
  111. case filename:basename(File) of
  112. "."++_ ->
  113. {ResultAcc, ErrorAcc};
  114. _ ->
  115. FilePath = filename:absname(File),
  116. case filelib:is_dir(FilePath) of
  117. true ->
  118. {ResultAcc, ErrorAcc};
  119. false ->
  120. case parse(FilePath, Context) of
  121. ok -> {ResultAcc, ErrorAcc};
  122. {ok, DjangoParseTree, CheckSum} ->
  123. {[{File, DjangoParseTree, CheckSum}|ResultAcc], ErrorAcc};
  124. Err -> {ResultAcc, [Err|ErrorAcc]}
  125. end
  126. end
  127. end
  128. end, {[], []}, Files),
  129. case ParserErrors of
  130. [] ->
  131. case compile_multiple_to_binary(Dir, ParserResults, Context) of
  132. {ok, Module1, Bin} ->
  133. write_binary(Module1, Bin, Options);
  134. Err ->
  135. Err
  136. end;
  137. [Error|_] ->
  138. Error
  139. end.
  140. %%====================================================================
  141. %% Internal functions
  142. %%====================================================================
  143. write_binary(Module1, Bin, Options) ->
  144. case proplists:get_value(out_dir, Options) of
  145. undefined ->
  146. ok;
  147. OutDir ->
  148. BeamFile = filename:join([OutDir, atom_to_list(Module1) ++ ".beam"]),
  149. case file:write_file(BeamFile, Bin) of
  150. ok ->
  151. ok;
  152. {error, Reason} ->
  153. {error, lists:concat(["beam generation failed (", Reason, "): ", BeamFile])}
  154. end
  155. end.
  156. compile_multiple_to_binary(Dir, ParserResults, Context) ->
  157. {Functions, {AstInfo, _}} = lists:mapfoldl(fun({File, DjangoParseTree, CheckSum}, {AstInfo, TreeWalker}) ->
  158. FilePath = full_path(File, Context#dtl_context.doc_root),
  159. {{BodyAst, BodyInfo}, TreeWalker1} = with_dependency({FilePath, CheckSum}, body_ast(DjangoParseTree, Context, TreeWalker)),
  160. FunctionName = filename:rootname(filename:basename(File)),
  161. Function1 = erl_syntax:function(erl_syntax:atom(FunctionName),
  162. [erl_syntax:clause([erl_syntax:variable("Variables")], none,
  163. [erl_syntax:application(none, erl_syntax:atom(FunctionName),
  164. [erl_syntax:variable("Variables"), erl_syntax:atom(none)])])]),
  165. Function2 = erl_syntax:function(erl_syntax:atom(FunctionName),
  166. [erl_syntax:clause([erl_syntax:variable("Variables"), erl_syntax:variable("TranslationFun")], none,
  167. [BodyAst])]),
  168. {{FunctionName, Function1, Function2}, {merge_info(AstInfo, BodyInfo), TreeWalker1}}
  169. end, {#ast_info{}, #treewalker{}}, ParserResults),
  170. Forms = custom_forms(Dir, Context#dtl_context.module, Functions, AstInfo),
  171. compile_forms_and_reload(Dir, Forms, Context#dtl_context.compiler_options).
  172. compile_to_binary(File, DjangoParseTree, Context, CheckSum) ->
  173. try body_ast(DjangoParseTree, Context, #treewalker{}) of
  174. {{BodyAst, BodyInfo}, BodyTreeWalker} ->
  175. try custom_tags_ast(BodyInfo#ast_info.custom_tags, Context, BodyTreeWalker) of
  176. {{CustomTagsAst, CustomTagsInfo}, _} ->
  177. Forms = forms(File, Context#dtl_context.module, {BodyAst, BodyInfo},
  178. {CustomTagsAst, CustomTagsInfo}, Context#dtl_context.binary_strings, CheckSum),
  179. compile_forms_and_reload(File, Forms, Context#dtl_context.compiler_options)
  180. catch
  181. throw:Error -> Error
  182. end
  183. catch
  184. throw:Error -> Error
  185. end.
  186. compile_forms_and_reload(File, Forms, CompilerOptions) ->
  187. case compile:forms(Forms, CompilerOptions) of
  188. {ok, Module1, Bin} ->
  189. code:purge(Module1),
  190. case code:load_binary(Module1, atom_to_list(Module1) ++ ".erl", Bin) of
  191. {module, _} -> {ok, Module1, Bin};
  192. _ -> {error, lists:concat(["code reload failed: ", Module1])}
  193. end;
  194. error ->
  195. {error, lists:concat(["compilation failed: ", File])};
  196. OtherError ->
  197. OtherError
  198. end.
  199. init_dtl_context(File, Module, Options) when is_list(Module) ->
  200. init_dtl_context(File, list_to_atom(Module), Options);
  201. init_dtl_context(File, Module, Options) ->
  202. Ctx = #dtl_context{},
  203. #dtl_context{
  204. parse_trail = [File],
  205. module = Module,
  206. doc_root = proplists:get_value(doc_root, Options, filename:dirname(File)),
  207. custom_tags_dir = proplists:get_value(custom_tags_dir, Options, filename:join([erlydtl_deps:get_base_dir(), "priv", "custom_tags"])),
  208. custom_tags_modules = proplists:get_value(custom_tags_modules, Options, Ctx#dtl_context.custom_tags_modules),
  209. blocktrans_fun = proplists:get_value(blocktrans_fun, Options, Ctx#dtl_context.blocktrans_fun),
  210. blocktrans_locales = proplists:get_value(blocktrans_locales, Options, Ctx#dtl_context.blocktrans_locales),
  211. vars = proplists:get_value(vars, Options, Ctx#dtl_context.vars),
  212. reader = proplists:get_value(reader, Options, Ctx#dtl_context.reader),
  213. compiler_options = proplists:get_value(compiler_options, Options, Ctx#dtl_context.compiler_options),
  214. binary_strings = proplists:get_value(binary_strings, Options, Ctx#dtl_context.binary_strings),
  215. force_recompile = proplists:get_value(force_recompile, Options, Ctx#dtl_context.force_recompile),
  216. locale = proplists:get_value(locale, Options, Ctx#dtl_context.locale),
  217. is_compiling_dir = false}.
  218. init_dtl_context_dir(Dir, Module, Options) when is_list(Module) ->
  219. init_dtl_context_dir(Dir, list_to_atom(Module), Options);
  220. init_dtl_context_dir(Dir, Module, Options) ->
  221. Ctx = #dtl_context{},
  222. #dtl_context{
  223. parse_trail = [],
  224. module = Module,
  225. doc_root = proplists:get_value(doc_root, Options, Dir),
  226. custom_tags_dir = proplists:get_value(custom_tags_dir, Options, filename:join([erlydtl_deps:get_base_dir(), "priv", "custom_tags"])),
  227. custom_tags_modules = proplists:get_value(custom_tags_modules, Options, Ctx#dtl_context.custom_tags_modules),
  228. blocktrans_fun = proplists:get_value(blocktrans_fun, Options, Ctx#dtl_context.blocktrans_fun),
  229. blocktrans_locales = proplists:get_value(blocktrans_locales, Options, Ctx#dtl_context.blocktrans_locales),
  230. vars = proplists:get_value(vars, Options, Ctx#dtl_context.vars),
  231. reader = proplists:get_value(reader, Options, Ctx#dtl_context.reader),
  232. compiler_options = proplists:get_value(compiler_options, Options, Ctx#dtl_context.compiler_options),
  233. binary_strings = proplists:get_value(binary_strings, Options, Ctx#dtl_context.binary_strings),
  234. force_recompile = proplists:get_value(force_recompile, Options, Ctx#dtl_context.force_recompile),
  235. locale = proplists:get_value(locale, Options, Ctx#dtl_context.locale),
  236. is_compiling_dir = true}.
  237. is_up_to_date(_, #dtl_context{force_recompile = true}) ->
  238. false;
  239. is_up_to_date(CheckSum, Context) ->
  240. Module = Context#dtl_context.module,
  241. {M, F} = Context#dtl_context.reader,
  242. case catch Module:source() of
  243. {_, CheckSum} ->
  244. case catch Module:dependencies() of
  245. L when is_list(L) ->
  246. RecompileList = lists:foldl(fun
  247. ({XFile, XCheckSum}, Acc) ->
  248. case catch M:F(XFile) of
  249. {ok, Data} ->
  250. case binary_to_list(erlang:md5(Data)) of
  251. XCheckSum ->
  252. Acc;
  253. _ ->
  254. [recompile | Acc]
  255. end;
  256. _ ->
  257. [recompile | Acc]
  258. end
  259. end, [], L),
  260. case RecompileList of
  261. [] -> true;
  262. _ -> false
  263. end;
  264. _ ->
  265. false
  266. end;
  267. _ ->
  268. false
  269. end.
  270. parse(File, Context) ->
  271. {M, F} = Context#dtl_context.reader,
  272. case catch M:F(File) of
  273. {ok, Data} ->
  274. CheckSum = binary_to_list(erlang:md5(Data)),
  275. case parse(CheckSum, Data, Context) of
  276. {error, Msg} when is_list(Msg) ->
  277. {error, File ++ ": " ++ Msg};
  278. {error, Msg} ->
  279. {error, {File, [Msg]}};
  280. Result ->
  281. Result
  282. end;
  283. _ ->
  284. {error, {File, [{0, Context#dtl_context.module, "Failed to read file"}]}}
  285. end.
  286. parse(CheckSum, Data, Context) ->
  287. case is_up_to_date(CheckSum, Context) of
  288. true ->
  289. ok;
  290. _ ->
  291. case parse(Data) of
  292. {ok, Val} ->
  293. {ok, Val, CheckSum};
  294. Err ->
  295. Err
  296. end
  297. end.
  298. parse(Data) ->
  299. case erlydtl_scanner:scan(binary_to_list(Data)) of
  300. {ok, Tokens} ->
  301. erlydtl_parser:parse(Tokens);
  302. Err ->
  303. Err
  304. end.
  305. custom_tags_ast(CustomTags, Context, TreeWalker) ->
  306. {{CustomTagsClauses, CustomTagsInfo}, TreeWalker1} = custom_tags_clauses_ast(CustomTags, Context, TreeWalker),
  307. {{erl_syntax:function(erl_syntax:atom(render_tag), CustomTagsClauses), CustomTagsInfo}, TreeWalker1}.
  308. custom_tags_clauses_ast(CustomTags, Context, TreeWalker) ->
  309. custom_tags_clauses_ast1(CustomTags, [], [], #ast_info{}, Context, TreeWalker).
  310. custom_tags_clauses_ast1([], _ExcludeTags, ClauseAcc, InfoAcc, _Context, TreeWalker) ->
  311. {{lists:reverse([erl_syntax:clause([erl_syntax:underscore(), erl_syntax:underscore(), erl_syntax:underscore()], none,
  312. [erl_syntax:list([])])|ClauseAcc]), InfoAcc}, TreeWalker};
  313. custom_tags_clauses_ast1([Tag|CustomTags], ExcludeTags, ClauseAcc, InfoAcc, Context, TreeWalker) ->
  314. case lists:member(Tag, ExcludeTags) of
  315. true ->
  316. custom_tags_clauses_ast1(CustomTags, ExcludeTags, ClauseAcc, InfoAcc, Context, TreeWalker);
  317. false ->
  318. CustomTagFile = full_path(Tag, Context#dtl_context.custom_tags_dir),
  319. case parse(CustomTagFile, Context) of
  320. {ok, DjangoParseTree, CheckSum} ->
  321. {{BodyAst, BodyAstInfo}, TreeWalker1} = with_dependency({CustomTagFile, CheckSum},
  322. body_ast(DjangoParseTree, Context, TreeWalker)),
  323. Clause = erl_syntax:clause([erl_syntax:string(Tag), erl_syntax:variable("Variables"), options_ast()],
  324. none, [BodyAst]),
  325. custom_tags_clauses_ast1(CustomTags, [Tag|ExcludeTags], [Clause|ClauseAcc], merge_info(BodyAstInfo, InfoAcc),
  326. Context, TreeWalker1);
  327. Error ->
  328. throw(Error)
  329. end
  330. end.
  331. dependencies_function(Dependencies) ->
  332. erl_syntax:function(
  333. erl_syntax:atom(dependencies), [erl_syntax:clause([], none,
  334. [erl_syntax:list(lists:map(fun
  335. ({XFile, XCheckSum}) ->
  336. erl_syntax:tuple([erl_syntax:string(XFile), erl_syntax:string(XCheckSum)])
  337. end, Dependencies))])]).
  338. translatable_strings_function(TranslatableStrings) ->
  339. erl_syntax:function(
  340. erl_syntax:atom(translatable_strings), [erl_syntax:clause([], none,
  341. [erl_syntax:list(lists:map(fun(String) ->
  342. erl_syntax:string(String)
  343. end,
  344. TranslatableStrings))])]).
  345. custom_forms(Dir, Module, Functions, AstInfo) ->
  346. ModuleAst = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
  347. ExportAst = erl_syntax:attribute(erl_syntax:atom(export),
  348. [erl_syntax:list([
  349. erl_syntax:arity_qualifier(erl_syntax:atom(source_dir), erl_syntax:integer(0)),
  350. erl_syntax:arity_qualifier(erl_syntax:atom(dependencies), erl_syntax:integer(0)),
  351. erl_syntax:arity_qualifier(erl_syntax:atom(translatable_strings), erl_syntax:integer(0))
  352. |
  353. lists:foldl(fun({FunctionName, _, _}, Acc) ->
  354. [erl_syntax:arity_qualifier(erl_syntax:atom(FunctionName), erl_syntax:integer(1)),
  355. erl_syntax:arity_qualifier(erl_syntax:atom(FunctionName), erl_syntax:integer(2))|Acc]
  356. end, [], Functions)]
  357. )]),
  358. SourceFunctionAst = erl_syntax:function(
  359. erl_syntax:atom(source_dir), [erl_syntax:clause([], none, [erl_syntax:string(Dir)])]),
  360. DependenciesFunctionAst = dependencies_function(AstInfo#ast_info.dependencies),
  361. TranslatableStringsFunctionAst = translatable_strings_function(AstInfo#ast_info.translatable_strings),
  362. FunctionAsts = lists:foldl(fun({_, Function1, Function2}, Acc) -> [Function1, Function2 | Acc] end, [], Functions),
  363. [erl_syntax:revert(X) || X <- [ModuleAst, ExportAst, SourceFunctionAst, DependenciesFunctionAst, TranslatableStringsFunctionAst
  364. | FunctionAsts] ++ AstInfo#ast_info.pre_render_asts].
  365. forms(File, Module, {BodyAst, BodyInfo}, {CustomTagsFunctionAst, CustomTagsInfo}, BinaryStrings, CheckSum) ->
  366. MergedInfo = merge_info(BodyInfo, CustomTagsInfo),
  367. Render0FunctionAst = erl_syntax:function(erl_syntax:atom(render),
  368. [erl_syntax:clause([], none, [erl_syntax:application(none,
  369. erl_syntax:atom(render), [erl_syntax:list([])])])]),
  370. Render1FunctionAst = erl_syntax:function(erl_syntax:atom(render),
  371. [erl_syntax:clause([erl_syntax:variable("Variables")], none,
  372. [erl_syntax:application(none,
  373. erl_syntax:atom(render),
  374. [erl_syntax:variable("Variables"), erl_syntax:list([])])])]),
  375. Function2 = erl_syntax:application(none, erl_syntax:atom(render_internal),
  376. [erl_syntax:variable("Variables"),
  377. erl_syntax:application(
  378. erl_syntax:atom(proplists),
  379. erl_syntax:atom(get_value),
  380. [erl_syntax:atom(translation_fun), erl_syntax:variable("Options"), erl_syntax:atom(none)]),
  381. erl_syntax:application(
  382. erl_syntax:atom(proplists),
  383. erl_syntax:atom(get_value),
  384. [erl_syntax:atom(locale), erl_syntax:variable("Options"), erl_syntax:atom(none)]),
  385. erl_syntax:application(
  386. erl_syntax:atom(proplists),
  387. erl_syntax:atom(get_value),
  388. [erl_syntax:atom(custom_tags_context), erl_syntax:variable("Options"), erl_syntax:atom(none)])
  389. ]),
  390. ClauseOk = erl_syntax:clause([erl_syntax:variable("Val")], none,
  391. [erl_syntax:tuple([erl_syntax:atom(ok), erl_syntax:variable("Val")])]),
  392. ClauseCatch = erl_syntax:clause([erl_syntax:variable("Err")], none,
  393. [erl_syntax:tuple([erl_syntax:atom(error), erl_syntax:variable("Err")])]),
  394. Render2FunctionAst = erl_syntax:function(erl_syntax:atom(render),
  395. [erl_syntax:clause([erl_syntax:variable("Variables"),
  396. erl_syntax:variable("Options")], none,
  397. [erl_syntax:try_expr([Function2], [ClauseOk], [ClauseCatch])])]),
  398. SourceFunctionTuple = erl_syntax:tuple(
  399. [erl_syntax:string(File), erl_syntax:string(CheckSum)]),
  400. SourceFunctionAst = erl_syntax:function(
  401. erl_syntax:atom(source),
  402. [erl_syntax:clause([], none, [SourceFunctionTuple])]),
  403. DependenciesFunctionAst = dependencies_function(MergedInfo#ast_info.dependencies),
  404. TranslatableStringsAst = translatable_strings_function(MergedInfo#ast_info.translatable_strings),
  405. BodyAstTmp = erl_syntax:application(
  406. erl_syntax:atom(erlydtl_runtime),
  407. erl_syntax:atom(stringify_final),
  408. [BodyAst, erl_syntax:atom(BinaryStrings)]),
  409. RenderInternalFunctionAst = erl_syntax:function(
  410. erl_syntax:atom(render_internal),
  411. [erl_syntax:clause([erl_syntax:variable("Variables"), erl_syntax:variable("TranslationFun"),
  412. erl_syntax:variable("CurrentLocale"), erl_syntax:variable("CustomTagsContext")], none,
  413. [BodyAstTmp])]),
  414. ModuleAst = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
  415. ExportAst = erl_syntax:attribute(erl_syntax:atom(export),
  416. [erl_syntax:list([erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(0)),
  417. erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(1)),
  418. erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(2)),
  419. erl_syntax:arity_qualifier(erl_syntax:atom(source), erl_syntax:integer(0)),
  420. erl_syntax:arity_qualifier(erl_syntax:atom(dependencies), erl_syntax:integer(0)),
  421. erl_syntax:arity_qualifier(erl_syntax:atom(translatable_strings), erl_syntax:integer(0))])]),
  422. [erl_syntax:revert(X) || X <- [ModuleAst, ExportAst, Render0FunctionAst, Render1FunctionAst, Render2FunctionAst,
  423. SourceFunctionAst, DependenciesFunctionAst, TranslatableStringsAst, RenderInternalFunctionAst, CustomTagsFunctionAst
  424. | BodyInfo#ast_info.pre_render_asts]].
  425. % child templates should only consist of blocks at the top level
  426. body_ast([{extends, {string_literal, _Pos, String}} | ThisParseTree], Context, TreeWalker) ->
  427. File = full_path(unescape_string_literal(String), Context#dtl_context.doc_root),
  428. case lists:member(File, Context#dtl_context.parse_trail) of
  429. true ->
  430. throw({error, "Circular file inclusion!"});
  431. _ ->
  432. case parse(File, Context) of
  433. {ok, ParentParseTree, CheckSum} ->
  434. BlockDict = lists:foldl(
  435. fun
  436. ({block, {identifier, _, Name}, Contents}, Dict) ->
  437. dict:store(Name, Contents, Dict);
  438. (_, Dict) ->
  439. Dict
  440. end, dict:new(), ThisParseTree),
  441. with_dependency({File, CheckSum}, body_ast(ParentParseTree, Context#dtl_context{
  442. block_dict = dict:merge(fun(_Key, _ParentVal, ChildVal) -> ChildVal end,
  443. BlockDict, Context#dtl_context.block_dict),
  444. parse_trail = [File | Context#dtl_context.parse_trail]}, TreeWalker));
  445. Err ->
  446. throw(Err)
  447. end
  448. end;
  449. body_ast(DjangoParseTree, Context, TreeWalker) ->
  450. {AstInfoList, TreeWalker2} = lists:mapfoldl(
  451. fun
  452. ({'autoescape', {identifier, _, OnOrOff}, Contents}, TreeWalkerAcc) ->
  453. body_ast(Contents, Context#dtl_context{auto_escape = OnOrOff},
  454. TreeWalkerAcc);
  455. ({'block', {identifier, _, Name}, Contents}, TreeWalkerAcc) ->
  456. Block = case dict:find(Name, Context#dtl_context.block_dict) of
  457. {ok, ChildBlock} -> ChildBlock;
  458. _ -> Contents
  459. end,
  460. body_ast(Block, Context, TreeWalkerAcc);
  461. ({'blocktrans', {identifier, _, Name}, Args, Contents}, TreeWalkerAcc) ->
  462. blocktrans_ast(Name, Args, Contents, Context, TreeWalkerAcc);
  463. ({'call', {'identifier', _, Name}}, TreeWalkerAcc) ->
  464. call_ast(Name, TreeWalkerAcc);
  465. ({'call', {'identifier', _, Name}, With}, TreeWalkerAcc) ->
  466. call_with_ast(Name, With, Context, TreeWalkerAcc);
  467. ({'comment', _Contents}, TreeWalkerAcc) ->
  468. empty_ast(TreeWalkerAcc);
  469. ({'cycle', Names}, TreeWalkerAcc) ->
  470. cycle_ast(Names, Context, TreeWalkerAcc);
  471. ({'cycle_compat', Names}, TreeWalkerAcc) ->
  472. cycle_compat_ast(Names, Context, TreeWalkerAcc);
  473. ({'date', 'now', {string_literal, _Pos, FormatString}}, TreeWalkerAcc) ->
  474. now_ast(FormatString, Context, TreeWalkerAcc);
  475. ({'filter', FilterList, Contents}, TreeWalkerAcc) ->
  476. filter_tag_ast(FilterList, Contents, Context, TreeWalkerAcc);
  477. ({'firstof', Vars}, TreeWalkerAcc) ->
  478. firstof_ast(Vars, Context, TreeWalkerAcc);
  479. ({'for', {'in', IteratorList, Variable}, Contents}, TreeWalkerAcc) ->
  480. {EmptyAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
  481. for_loop_ast(IteratorList, Variable, Contents, EmptyAstInfo, Context, TreeWalker1);
  482. ({'for', {'in', IteratorList, Variable}, Contents, EmptyPartContents}, TreeWalkerAcc) ->
  483. {EmptyAstInfo, TreeWalker1} = body_ast(EmptyPartContents, Context, TreeWalkerAcc),
  484. for_loop_ast(IteratorList, Variable, Contents, EmptyAstInfo, Context, TreeWalker1);
  485. ({'if', Expression, Contents}, TreeWalkerAcc) ->
  486. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  487. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  488. ifelse_ast(Expression, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  489. ({'ifelse', Expression, IfContents, ElseContents}, TreeWalkerAcc) ->
  490. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  491. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
  492. ifelse_ast(Expression, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  493. ({'ifequal', [Arg1, Arg2], Contents}, TreeWalkerAcc) ->
  494. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  495. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  496. ifelse_ast({'expr', "eq", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  497. ({'ifequalelse', [Arg1, Arg2], IfContents, ElseContents}, TreeWalkerAcc) ->
  498. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  499. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context,TreeWalker1),
  500. ifelse_ast({'expr', "eq", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  501. ({'ifnotequal', [Arg1, Arg2], Contents}, TreeWalkerAcc) ->
  502. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  503. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  504. ifelse_ast({'expr', "ne", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  505. ({'ifnotequalelse', [Arg1, Arg2], IfContents, ElseContents}, TreeWalkerAcc) ->
  506. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  507. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
  508. ifelse_ast({'expr', "ne", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  509. ({'include', {string_literal, _, File}, Args}, TreeWalkerAcc) ->
  510. include_ast(unescape_string_literal(File), Args, Context#dtl_context.local_scopes, Context, TreeWalkerAcc);
  511. ({'include_only', {string_literal, _, File}, Args}, TreeWalkerAcc) ->
  512. include_ast(unescape_string_literal(File), Args, [], Context, TreeWalkerAcc);
  513. ({'spaceless', Contents}, TreeWalkerAcc) ->
  514. spaceless_ast(Contents, Context, TreeWalkerAcc);
  515. ({'ssi', Arg}, TreeWalkerAcc) ->
  516. ssi_ast(Arg, Context, TreeWalkerAcc);
  517. ({'ssi_parsed', {string_literal, _, FileName}}, TreeWalkerAcc) ->
  518. include_ast(unescape_string_literal(FileName), [], Context#dtl_context.local_scopes, Context, TreeWalkerAcc);
  519. ({'string', _Pos, String}, TreeWalkerAcc) ->
  520. string_ast(String, Context, TreeWalkerAcc);
  521. ({'tag', {'identifier', _, Name}, Args}, TreeWalkerAcc) ->
  522. tag_ast(Name, Args, Context, TreeWalkerAcc);
  523. ({'templatetag', {_, _, TagName}}, TreeWalkerAcc) ->
  524. templatetag_ast(TagName, Context, TreeWalkerAcc);
  525. ({'trans', Value}, TreeWalkerAcc) ->
  526. translated_ast(Value, Context, TreeWalkerAcc);
  527. ({'widthratio', Numerator, Denominator, Scale}, TreeWalkerAcc) ->
  528. widthratio_ast(Numerator, Denominator, Scale, Context, TreeWalkerAcc);
  529. ({'with', Args, Contents}, TreeWalkerAcc) ->
  530. with_ast(Args, Contents, Context, TreeWalkerAcc);
  531. (ValueToken, TreeWalkerAcc) ->
  532. {{ValueAst,ValueInfo},ValueTreeWalker} = value_ast(ValueToken, true, Context, TreeWalkerAcc),
  533. {{format(ValueAst, Context, ValueTreeWalker),ValueInfo},ValueTreeWalker}
  534. end, TreeWalker, DjangoParseTree),
  535. {AstList, {Info, TreeWalker3}} = lists:mapfoldl(
  536. fun({Ast, Info}, {InfoAcc, TreeWalkerAcc}) ->
  537. PresetVars = lists:foldl(fun
  538. (X, Acc) ->
  539. case proplists:lookup(X, Context#dtl_context.vars) of
  540. none ->
  541. Acc;
  542. Val ->
  543. [erl_syntax:abstract(Val) | Acc]
  544. end
  545. end, [], Info#ast_info.var_names),
  546. case PresetVars of
  547. [] ->
  548. {Ast, {merge_info(Info, InfoAcc), TreeWalkerAcc}};
  549. _ ->
  550. Counter = TreeWalkerAcc#treewalker.counter,
  551. Name = lists:concat([pre_render, Counter]),
  552. Ast1 = erl_syntax:application(none, erl_syntax:atom(Name),
  553. [erl_syntax:list(PresetVars)]),
  554. PreRenderAst = erl_syntax:function(erl_syntax:atom(Name),
  555. [erl_syntax:clause([erl_syntax:variable("Variables")], none, [Ast])]),
  556. PreRenderAsts = Info#ast_info.pre_render_asts,
  557. Info1 = Info#ast_info{pre_render_asts = [PreRenderAst | PreRenderAsts]},
  558. {Ast1, {merge_info(Info1, InfoAcc), TreeWalkerAcc#treewalker{counter = Counter + 1}}}
  559. end
  560. end, {#ast_info{}, TreeWalker2}, AstInfoList),
  561. {{erl_syntax:list(AstList), Info}, TreeWalker3}.
  562. value_ast(ValueToken, AsString, Context, TreeWalker) ->
  563. case ValueToken of
  564. {'expr', Operator, Value} ->
  565. {{ValueAst,InfoValue}, TreeWalker1} = value_ast(Value, false, Context, TreeWalker),
  566. Ast = erl_syntax:application(erl_syntax:atom(erlydtl_runtime),
  567. erl_syntax:atom(Operator),
  568. [ValueAst]),
  569. {{Ast, InfoValue}, TreeWalker1};
  570. {'expr', Operator, Value1, Value2} ->
  571. {{Value1Ast,InfoValue1}, TreeWalker1} = value_ast(Value1, false, Context, TreeWalker),
  572. {{Value2Ast,InfoValue2}, TreeWalker2} = value_ast(Value2, false, Context, TreeWalker1),
  573. Ast = erl_syntax:application(erl_syntax:atom(erlydtl_runtime),
  574. erl_syntax:atom(Operator),
  575. [Value1Ast, Value2Ast]),
  576. {{Ast, merge_info(InfoValue1,InfoValue2)}, TreeWalker2};
  577. {'string_literal', _Pos, String} ->
  578. string_ast(unescape_string_literal(String), Context, TreeWalker);
  579. {'number_literal', _Pos, Number} ->
  580. case AsString of
  581. true -> string_ast(Number, Context, TreeWalker);
  582. false -> {{erl_syntax:integer(list_to_integer(Number)), #ast_info{}}, TreeWalker}
  583. end;
  584. {'apply_filter', Variable, Filter} ->
  585. filter_ast(Variable, Filter, Context, TreeWalker);
  586. {'attribute', _} = Variable ->
  587. {Ast, VarName} = resolve_variable_ast(Variable, Context),
  588. {{Ast, #ast_info{var_names = [VarName]}}, TreeWalker};
  589. {'variable', _} = Variable ->
  590. {Ast, VarName} = resolve_variable_ast(Variable, Context),
  591. {{Ast, #ast_info{var_names = [VarName]}}, TreeWalker}
  592. end.
  593. merge_info(Info1, Info2) ->
  594. #ast_info{
  595. dependencies =
  596. lists:merge(
  597. lists:sort(Info1#ast_info.dependencies),
  598. lists:sort(Info2#ast_info.dependencies)),
  599. var_names =
  600. lists:merge(
  601. lists:sort(Info1#ast_info.var_names),
  602. lists:sort(Info2#ast_info.var_names)),
  603. translatable_strings =
  604. lists:merge(
  605. lists:sort(Info1#ast_info.translatable_strings),
  606. lists:sort(Info2#ast_info.translatable_strings)),
  607. custom_tags =
  608. lists:merge(
  609. lists:sort(Info1#ast_info.custom_tags),
  610. lists:sort(Info2#ast_info.custom_tags)),
  611. pre_render_asts =
  612. lists:merge(
  613. Info1#ast_info.pre_render_asts,
  614. Info2#ast_info.pre_render_asts)}.
  615. with_dependencies([], Args) ->
  616. Args;
  617. with_dependencies([Dependency | Rest], Args) ->
  618. with_dependencies(Rest, with_dependency(Dependency, Args)).
  619. with_dependency(FilePath, {{Ast, Info}, TreeWalker}) ->
  620. {{Ast, Info#ast_info{dependencies = [FilePath | Info#ast_info.dependencies]}}, TreeWalker}.
  621. empty_ast(TreeWalker) ->
  622. {{erl_syntax:list([]), #ast_info{}}, TreeWalker}.
  623. blocktrans_ast(Name, ArgList, Contents, Context, TreeWalker) ->
  624. {NewScope, {ArgInfo, TreeWalker1}} = lists:mapfoldl(fun
  625. ({{identifier, _, LocalVarName}, Value}, {AstInfo1, TreeWalker1}) ->
  626. {{Ast, Info}, TreeWalker2} = value_ast(Value, false, Context, TreeWalker1),
  627. {{LocalVarName, Ast}, {merge_info(AstInfo1, Info), TreeWalker2}}
  628. end, {#ast_info{}, TreeWalker}, ArgList),
  629. NewContext = Context#dtl_context{ local_scopes = [NewScope|Context#dtl_context.local_scopes] },
  630. {{DefaultAst, AstInfo}, TreeWalker2} = body_ast(Contents, NewContext, TreeWalker1),
  631. MergedInfo = merge_info(AstInfo, ArgInfo),
  632. case Context#dtl_context.blocktrans_fun of
  633. none ->
  634. {{DefaultAst, MergedInfo}, TreeWalker2};
  635. BlockTransFun when is_function(BlockTransFun) ->
  636. {FinalAstInfo, FinalTreeWalker, Clauses} = lists:foldr(fun(Locale, {AstInfoAcc, ThisTreeWalker, ClauseAcc}) ->
  637. case BlockTransFun(Name, Locale) of
  638. default ->
  639. {AstInfoAcc, ThisTreeWalker, ClauseAcc};
  640. Body ->
  641. {ok, DjangoParseTree} = parse(Body),
  642. {{ThisAst, ThisAstInfo}, TreeWalker3} = body_ast(DjangoParseTree, NewContext, ThisTreeWalker),
  643. {merge_info(ThisAstInfo, AstInfoAcc), TreeWalker3,
  644. [erl_syntax:clause([erl_syntax:string(Locale)], none, [ThisAst])|ClauseAcc]}
  645. end
  646. end, {MergedInfo, TreeWalker2, []}, Context#dtl_context.blocktrans_locales),
  647. Ast = erl_syntax:case_expr(erl_syntax:variable("CurrentLocale"),
  648. Clauses ++ [erl_syntax:clause([erl_syntax:underscore()], none, [DefaultAst])]),
  649. {{Ast, FinalAstInfo}, FinalTreeWalker}
  650. end.
  651. translated_ast({string_literal, _, String}, Context, TreeWalker) ->
  652. NewStr = unescape_string_literal(String),
  653. DefaultString = case Context#dtl_context.locale of
  654. none -> NewStr;
  655. Locale -> erlydtl_i18n:translate(NewStr,Locale)
  656. end,
  657. translated_ast2(erl_syntax:string(NewStr), erl_syntax:string(DefaultString),
  658. #ast_info{translatable_strings = [NewStr]}, TreeWalker);
  659. translated_ast(ValueToken, Context, TreeWalker) ->
  660. {{Ast, Info}, TreeWalker1} = value_ast(ValueToken, true, Context, TreeWalker),
  661. translated_ast2(Ast, Ast, Info, TreeWalker1).
  662. translated_ast2(NewStrAst, DefaultStringAst, AstInfo, TreeWalker) ->
  663. StringLookupAst = erl_syntax:application(
  664. erl_syntax:atom(erlydtl_runtime),
  665. erl_syntax:atom(translate),
  666. [NewStrAst, erl_syntax:variable("TranslationFun"), DefaultStringAst]),
  667. {{StringLookupAst, AstInfo}, TreeWalker}.
  668. % Completely unnecessary in ErlyDTL (use {{ "{%" }} etc), but implemented for compatibility.
  669. templatetag_ast('openblock', Context, TreeWalker) ->
  670. string_ast("{%", Context, TreeWalker);
  671. templatetag_ast('closeblock', Context, TreeWalker) ->
  672. string_ast("%}", Context, TreeWalker);
  673. templatetag_ast('openvariable', Context, TreeWalker) ->
  674. string_ast("{{", Context, TreeWalker);
  675. templatetag_ast('closevariable', Context, TreeWalker) ->
  676. string_ast("}}", Context, TreeWalker);
  677. templatetag_ast('openbrace', Context, TreeWalker) ->
  678. string_ast("{", Context, TreeWalker);
  679. templatetag_ast('closebrace', Context, TreeWalker) ->
  680. string_ast("}", Context, TreeWalker);
  681. templatetag_ast('opencomment', Context, TreeWalker) ->
  682. string_ast("{#", Context, TreeWalker);
  683. templatetag_ast('closecomment', Context, TreeWalker) ->
  684. string_ast("#}", Context, TreeWalker).
  685. widthratio_ast(Numerator, Denominator, Scale, Context, TreeWalker) ->
  686. {{NumAst, NumInfo}, TreeWalker1} = value_ast(Numerator, false, Context, TreeWalker),
  687. {{DenAst, DenInfo}, TreeWalker2} = value_ast(Denominator, false, Context, TreeWalker1),
  688. {{ScaleAst, ScaleInfo}, TreeWalker3} = value_ast(Scale, false, Context, TreeWalker2),
  689. {{format_number_ast(erl_syntax:application(
  690. erl_syntax:atom(erlydtl_runtime),
  691. erl_syntax:atom(widthratio),
  692. [NumAst, DenAst, ScaleAst])), merge_info(ScaleInfo, merge_info(NumInfo, DenInfo))},
  693. TreeWalker3}.
  694. binary_string(String) ->
  695. erl_syntax:binary([erl_syntax:binary_field(erl_syntax:integer(X)) || X <- String]).
  696. string_ast(String, #dtl_context{ binary_strings = true }, TreeWalker) ->
  697. {{binary_string(String), #ast_info{}}, TreeWalker};
  698. string_ast(String, #dtl_context{ binary_strings = false }, TreeWalker) ->
  699. {{erl_syntax:string(String), #ast_info{}}, TreeWalker}. %% less verbose AST, better for development and debugging
  700. include_ast(File, ArgList, Scopes, Context, TreeWalker) ->
  701. FilePath = full_path(File, Context#dtl_context.doc_root),
  702. case parse(FilePath, Context) of
  703. {ok, InclusionParseTree, CheckSum} ->
  704. {NewScope, {ArgInfo, TreeWalker1}} = lists:mapfoldl(fun
  705. ({{identifier, _, LocalVarName}, Value}, {AstInfo1, TreeWalker1}) ->
  706. {{Ast, Info}, TreeWalker2} = value_ast(Value, false, Context, TreeWalker1),
  707. {{LocalVarName, Ast}, {merge_info(AstInfo1, Info), TreeWalker2}}
  708. end, {#ast_info{}, TreeWalker}, ArgList),
  709. {{BodyAst, BodyInfo}, TreeWalker2} = with_dependency({FilePath, CheckSum},
  710. body_ast(InclusionParseTree, Context#dtl_context{
  711. parse_trail = [FilePath | Context#dtl_context.parse_trail],
  712. local_scopes = [NewScope|Scopes]
  713. }, TreeWalker1)),
  714. {{BodyAst, merge_info(BodyInfo, ArgInfo)}, TreeWalker2};
  715. Err ->
  716. throw(Err)
  717. end.
  718. % include at run-time
  719. ssi_ast(FileName, Context, TreeWalker) ->
  720. {{Ast, Info}, TreeWalker1} = value_ast(FileName, true, Context, TreeWalker),
  721. {Mod, Fun} = Context#dtl_context.reader,
  722. {{erl_syntax:application(
  723. erl_syntax:atom(erlydtl_runtime),
  724. erl_syntax:atom(read_file),
  725. [erl_syntax:atom(Mod), erl_syntax:atom(Fun), erl_syntax:string(Context#dtl_context.doc_root), Ast]), Info}, TreeWalker1}.
  726. filter_tag_ast(FilterList, Contents, Context, TreeWalker) ->
  727. {{InnerAst, Info}, TreeWalker1} = body_ast(Contents, Context#dtl_context{auto_escape = did}, TreeWalker),
  728. {{FilteredAst, FilteredInfo}, TreeWalker2} = lists:foldl(fun
  729. ([{identifier, _, 'escape'}], {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
  730. {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{safe = true}};
  731. ([{identifier, _, 'safe'}], {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
  732. {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{safe = true}};
  733. ([{identifier, _, 'safeseq'}], {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
  734. {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{safe = true}};
  735. (Filter, {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
  736. {Ast, AstInfo} = filter_ast1(Filter, AstAcc, Context),
  737. {{Ast, merge_info(InfoAcc, AstInfo)}, TreeWalkerAcc}
  738. end, {{erl_syntax:application(
  739. erl_syntax:atom(erlang),
  740. erl_syntax:atom(iolist_to_binary),
  741. [InnerAst]), Info}, TreeWalker1}, FilterList),
  742. EscapedAst = case search_for_escape_filter(lists:reverse(FilterList), Context) of
  743. on ->
  744. erl_syntax:application(
  745. erl_syntax:atom(erlydtl_filters),
  746. erl_syntax:atom(force_escape),
  747. [FilteredAst]);
  748. _ ->
  749. FilteredAst
  750. end,
  751. {{EscapedAst, FilteredInfo}, TreeWalker2}.
  752. search_for_escape_filter(FilterList, #dtl_context{auto_escape = on}) ->
  753. search_for_safe_filter(FilterList);
  754. search_for_escape_filter(_, #dtl_context{auto_escape = did}) ->
  755. off;
  756. search_for_escape_filter([[{identifier, _, 'escape'}]|Rest], _Context) ->
  757. search_for_safe_filter(Rest);
  758. search_for_escape_filter([_|Rest], Context) ->
  759. search_for_escape_filter(Rest, Context);
  760. search_for_escape_filter([], _Context) ->
  761. off.
  762. search_for_safe_filter([[{identifier, _, 'safe'}]|_]) ->
  763. off;
  764. search_for_safe_filter([[{identifier, _, 'safeseq'}]|_]) ->
  765. off;
  766. search_for_safe_filter([_|Rest]) ->
  767. search_for_safe_filter(Rest);
  768. search_for_safe_filter([]) ->
  769. on.
  770. filter_ast(Variable, Filter, Context, TreeWalker) ->
  771. % the escape filter is special; it is always applied last, so we have to go digging for it
  772. % AutoEscape = 'did' means we (will have) decided whether to escape the current variable,
  773. % so don't do any more escaping
  774. {{UnescapedAst, Info}, TreeWalker2} = filter_ast_noescape(Variable, Filter,
  775. Context#dtl_context{auto_escape = did}, TreeWalker),
  776. EscapedAst = case search_for_escape_filter(Variable, Filter, Context) of
  777. on ->
  778. erl_syntax:application(
  779. erl_syntax:atom(erlydtl_filters),
  780. erl_syntax:atom(force_escape),
  781. [UnescapedAst]);
  782. _ ->
  783. UnescapedAst
  784. end,
  785. {{EscapedAst, Info}, TreeWalker2}.
  786. filter_ast_noescape(Variable, [{identifier, _, 'escape'}], Context, TreeWalker) ->
  787. value_ast(Variable, true, Context, TreeWalker#treewalker{safe = true});
  788. filter_ast_noescape(Variable, [{identifier, _, 'safe'}], Context, TreeWalker) ->
  789. value_ast(Variable, true, Context, TreeWalker#treewalker{safe = true});
  790. filter_ast_noescape(Variable, [{identifier, _, 'safeseq'}], Context, TreeWalker) ->
  791. value_ast(Variable, true, Context, TreeWalker#treewalker{safe = true});
  792. filter_ast_noescape(Variable, Filter, Context, TreeWalker) ->
  793. {{VariableAst, Info1}, TreeWalker2} = value_ast(Variable, true, Context, TreeWalker),
  794. {VarValue, Info2} = filter_ast1(Filter, VariableAst, Context),
  795. {{VarValue, merge_info(Info1, Info2)}, TreeWalker2}.
  796. filter_ast1([{identifier, _, Name}, {string_literal, _, ArgName}], VariableAst, #dtl_context{ binary_strings = true }) ->
  797. filter_ast2(Name, VariableAst, [binary_string(unescape_string_literal(ArgName))], []);
  798. filter_ast1([{identifier, _, Name}, {string_literal, _, ArgName}], VariableAst, #dtl_context{ binary_strings = false }) ->
  799. filter_ast2(Name, VariableAst, [erl_syntax:string(unescape_string_literal(ArgName))], []);
  800. filter_ast1([{identifier, _, Name}, {number_literal, _, ArgName}], VariableAst, _Context) ->
  801. filter_ast2(Name, VariableAst, [erl_syntax:integer(list_to_integer(ArgName))], []);
  802. filter_ast1([{identifier, _, Name}, ArgVariable], VariableAst, Context) ->
  803. {ArgAst, ArgVarName} = resolve_variable_ast(ArgVariable, Context),
  804. filter_ast2(Name, VariableAst, [ArgAst], [ArgVarName]);
  805. filter_ast1([{identifier, _, Name}], VariableAst, _Context) ->
  806. filter_ast2(Name, VariableAst, [], []).
  807. filter_ast2(Name, VariableAst, AdditionalArgs, VarNames) ->
  808. {erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(Name),
  809. [VariableAst | AdditionalArgs]), #ast_info{var_names = VarNames}}.
  810. search_for_escape_filter(Variable, Filter, #dtl_context{auto_escape = on}) ->
  811. search_for_safe_filter(Variable, Filter);
  812. search_for_escape_filter(_, _, #dtl_context{auto_escape = did}) ->
  813. off;
  814. search_for_escape_filter(Variable, [{identifier, _, 'escape'}] = Filter, _Context) ->
  815. search_for_safe_filter(Variable, Filter);
  816. search_for_escape_filter({apply_filter, Variable, Filter}, _, Context) ->
  817. search_for_escape_filter(Variable, Filter, Context);
  818. search_for_escape_filter(_Variable, _Filter, _Context) ->
  819. off.
  820. search_for_safe_filter(_, [{identifier, _, 'safe'}]) ->
  821. off;
  822. search_for_safe_filter(_, [{identifier, _, 'safeseq'}]) ->
  823. off;
  824. search_for_safe_filter({apply_filter, Variable, Filter}, _) ->
  825. search_for_safe_filter(Variable, Filter);
  826. search_for_safe_filter(_Variable, _Filter) ->
  827. on.
  828. resolve_variable_ast(VarTuple, Context) ->
  829. resolve_variable_ast(VarTuple, Context, 'find_value').
  830. resolve_variable_ast({attribute, {{identifier, _, AttrName}, Variable}}, Context, FinderFunction) ->
  831. {VarAst, VarName} = resolve_variable_ast(Variable, Context, FinderFunction),
  832. {erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(FinderFunction),
  833. [erl_syntax:atom(AttrName), VarAst]), VarName};
  834. resolve_variable_ast({variable, {identifier, _, VarName}}, Context, FinderFunction) ->
  835. VarValue = case resolve_scoped_variable_ast(VarName, Context) of
  836. undefined ->
  837. erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(FinderFunction),
  838. [erl_syntax:atom(VarName), erl_syntax:variable("Variables")]);
  839. Val ->
  840. Val
  841. end,
  842. {VarValue, VarName};
  843. resolve_variable_ast(What, _Context, _FinderFunction) ->
  844. error_logger:error_msg("~p:resolve_variable_ast unhandled: ~p~n", [?MODULE, What]).
  845. resolve_scoped_variable_ast(VarName, Context) ->
  846. lists:foldl(fun(Scope, Value) ->
  847. case Value of
  848. undefined -> proplists:get_value(VarName, Scope);
  849. _ -> Value
  850. end
  851. end, undefined, Context#dtl_context.local_scopes).
  852. format(Ast, Context, TreeWalker) ->
  853. auto_escape(format_number_ast(Ast), Context, TreeWalker).
  854. format_number_ast(Ast) ->
  855. erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(format_number),
  856. [Ast]).
  857. auto_escape(Value, _, #treewalker{safe = true}) ->
  858. Value;
  859. auto_escape(Value, #dtl_context{auto_escape = on}, _) ->
  860. erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(force_escape), [Value]);
  861. auto_escape(Value, _, _) ->
  862. Value.
  863. firstof_ast(Vars, Context, TreeWalker) ->
  864. body_ast([lists:foldr(fun
  865. ({L, _, _}=Var, []) when L=:=string_literal;L=:=number_literal ->
  866. Var;
  867. ({L, _, _}, _) when L=:=string_literal;L=:=number_literal ->
  868. erlang:error(errbadliteral);
  869. (Var, []) ->
  870. {'ifelse', Var, [Var], []};
  871. (Var, Acc) ->
  872. {'ifelse', Var, [Var], [Acc]} end,
  873. [], Vars)], Context, TreeWalker).
  874. ifelse_ast(Expression, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
  875. Info = merge_info(IfContentsInfo, ElseContentsInfo),
  876. {{Ast, ExpressionInfo}, TreeWalker1} = value_ast(Expression, false, Context, TreeWalker),
  877. {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(is_true), [Ast]),
  878. [erl_syntax:clause([erl_syntax:atom(true)], none,
  879. [IfContentsAst]),
  880. erl_syntax:clause([erl_syntax:underscore()], none,
  881. [ElseContentsAst])
  882. ]), merge_info(ExpressionInfo, Info)}, TreeWalker1}.
  883. with_ast(ArgList, Contents, Context, TreeWalker) ->
  884. {ArgAstList, {ArgInfo, TreeWalker1}} = lists:mapfoldl(fun
  885. ({{identifier, _, _LocalVarName}, Value}, {AstInfo1, TreeWalker1}) ->
  886. {{Ast, Info}, TreeWalker2} = value_ast(Value, false, Context, TreeWalker1),
  887. {Ast, {merge_info(AstInfo1, Info), TreeWalker2}}
  888. end, {#ast_info{}, TreeWalker}, ArgList),
  889. NewScope = lists:map(fun({{identifier, _, LocalVarName}, _Value}) ->
  890. {LocalVarName, erl_syntax:variable(lists:concat(["Var_", LocalVarName]))}
  891. end, ArgList),
  892. {{InnerAst, InnerInfo}, TreeWalker2} = body_ast(Contents,
  893. Context#dtl_context{local_scopes = [NewScope|Context#dtl_context.local_scopes]}, TreeWalker1),
  894. {{erl_syntax:application(
  895. erl_syntax:fun_expr([
  896. erl_syntax:clause(lists:map(fun({_, Var}) -> Var end, NewScope), none,
  897. [InnerAst])]), ArgAstList), merge_info(ArgInfo, InnerInfo)}, TreeWalker2}.
  898. for_loop_ast(IteratorList, LoopValue, Contents, {EmptyContentsAst, EmptyContentsInfo}, Context, TreeWalker) ->
  899. Vars = lists:map(fun({identifier, _, Iterator}) ->
  900. erl_syntax:variable(lists:concat(["Var_", Iterator]))
  901. end, IteratorList),
  902. {{InnerAst, Info}, TreeWalker1} = body_ast(Contents,
  903. Context#dtl_context{local_scopes = [
  904. [{'forloop', erl_syntax:variable("Counters")} | lists:map(
  905. fun({identifier, _, Iterator}) ->
  906. {Iterator, erl_syntax:variable(lists:concat(["Var_", Iterator]))}
  907. end, IteratorList)] | Context#dtl_context.local_scopes]}, TreeWalker),
  908. CounterAst = erl_syntax:application(erl_syntax:atom(erlydtl_runtime),
  909. erl_syntax:atom(increment_counter_stats), [erl_syntax:variable("Counters")]),
  910. {{LoopValueAst, LoopValueInfo}, TreeWalker2} = value_ast(LoopValue, false, Context, TreeWalker1),
  911. CounterVars0 = case resolve_scoped_variable_ast('forloop', Context) of
  912. undefined ->
  913. erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(init_counter_stats), [LoopValueAst]);
  914. Value ->
  915. erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(init_counter_stats), [LoopValueAst, Value])
  916. end,
  917. {{erl_syntax:case_expr(
  918. erl_syntax:application(
  919. erl_syntax:atom('lists'), erl_syntax:atom('mapfoldl'),
  920. [erl_syntax:fun_expr([
  921. erl_syntax:clause([erl_syntax:tuple(Vars), erl_syntax:variable("Counters")], none,
  922. [erl_syntax:tuple([InnerAst, CounterAst])]),
  923. erl_syntax:clause(case Vars of [H] -> [H, erl_syntax:variable("Counters")];
  924. _ -> [erl_syntax:list(Vars), erl_syntax:variable("Counters")] end, none,
  925. [erl_syntax:tuple([InnerAst, CounterAst])])
  926. ]),
  927. CounterVars0, LoopValueAst]),
  928. [erl_syntax:clause(
  929. [erl_syntax:tuple([erl_syntax:underscore(),
  930. erl_syntax:list([erl_syntax:tuple([erl_syntax:atom(counter), erl_syntax:integer(1)])],
  931. erl_syntax:underscore())])],
  932. none, [EmptyContentsAst]),
  933. erl_syntax:clause(
  934. [erl_syntax:tuple([erl_syntax:variable("L"), erl_syntax:underscore()])],
  935. none, [erl_syntax:variable("L")])]
  936. ),
  937. merge_info(merge_info(Info, EmptyContentsInfo), LoopValueInfo)
  938. }, TreeWalker2}.
  939. cycle_ast(Names, Context, TreeWalker) ->
  940. NamesTuple = lists:map(fun
  941. ({string_literal, _, Str}) ->
  942. {{S, _}, _} = string_ast(unescape_string_literal(Str), Context, TreeWalker),
  943. S;
  944. ({variable, _}=Var) ->
  945. {V, _} = resolve_variable_ast(Var, Context),
  946. V;
  947. ({number_literal, _, Num}) ->
  948. format(erl_syntax:integer(Num), Context, TreeWalker);
  949. (_) ->
  950. []
  951. end, Names),
  952. {{erl_syntax:application(
  953. erl_syntax:atom('erlydtl_runtime'), erl_syntax:atom('cycle'),
  954. [erl_syntax:tuple(NamesTuple), erl_syntax:variable("Counters")]), #ast_info{}}, TreeWalker}.
  955. %% Older Django templates treat cycle with comma-delimited elements as strings
  956. cycle_compat_ast(Names, Context, TreeWalker) ->
  957. NamesTuple = lists:map(fun
  958. ({identifier, _, X}) ->
  959. {{S, _}, _} = string_ast(X, Context, TreeWalker),
  960. S
  961. end, Names),
  962. {{erl_syntax:application(
  963. erl_syntax:atom('erlydtl_runtime'), erl_syntax:atom('cycle'),
  964. [erl_syntax:tuple(NamesTuple), erl_syntax:variable("Counters")]), #ast_info{}}, TreeWalker}.
  965. now_ast(FormatString, Context, TreeWalker) ->
  966. % Note: we can't use unescape_string_literal here
  967. % because we want to allow escaping in the format string.
  968. % We only want to remove the surrounding escapes,
  969. % i.e. \"foo\" becomes "foo"
  970. UnescapeOuter = string:strip(FormatString, both, 34),
  971. {{StringAst, Info}, TreeWalker1} = string_ast(UnescapeOuter, Context, TreeWalker),
  972. {{erl_syntax:application(
  973. erl_syntax:atom(erlydtl_dateformat),
  974. erl_syntax:atom(format),
  975. [StringAst]), Info}, TreeWalker1}.
  976. spaceless_ast(Contents, Context, TreeWalker) ->
  977. {{Ast, Info}, TreeWalker1} = body_ast(Contents, Context, TreeWalker),
  978. {{erl_syntax:application(
  979. erl_syntax:atom(erlydtl_runtime),
  980. erl_syntax:atom(spaceless),
  981. [Ast]), Info}, TreeWalker1}.
  982. unescape_string_literal(String) ->
  983. unescape_string_literal(string:strip(String, both, 34), [], noslash).
  984. unescape_string_literal([], Acc, noslash) ->
  985. lists:reverse(Acc);
  986. unescape_string_literal([$\\ | Rest], Acc, noslash) ->
  987. unescape_string_literal(Rest, Acc, slash);
  988. unescape_string_literal([C | Rest], Acc, noslash) ->
  989. unescape_string_literal(Rest, [C | Acc], noslash);
  990. unescape_string_literal("n" ++ Rest, Acc, slash) ->
  991. unescape_string_literal(Rest, [$\n | Acc], noslash);
  992. unescape_string_literal("r" ++ Rest, Acc, slash) ->
  993. unescape_string_literal(Rest, [$\r | Acc], noslash);
  994. unescape_string_literal("t" ++ Rest, Acc, slash) ->
  995. unescape_string_literal(Rest, [$\t | Acc], noslash);
  996. unescape_string_literal([C | Rest], Acc, slash) ->
  997. unescape_string_literal(Rest, [C | Acc], noslash).
  998. full_path(File, DocRoot) ->
  999. case filename:absname(File) of
  1000. File -> File;
  1001. _ -> filename:join([DocRoot, File])
  1002. end.
  1003. %%-------------------------------------------------------------------
  1004. %% Custom tags
  1005. %%-------------------------------------------------------------------
  1006. tag_ast(Name, Args, Context, TreeWalker) ->
  1007. {InterpretedArgs, AstInfo} = lists:mapfoldl(fun
  1008. ({{identifier, _, Key}, {string_literal, _, Value}}, AstInfoAcc) ->
  1009. {{StringAst, StringAstInfo}, _} = string_ast(unescape_string_literal(Value), Context, TreeWalker),
  1010. {erl_syntax:tuple([erl_syntax:string(Key), StringAst]), merge_info(StringAstInfo, AstInfoAcc)};
  1011. ({{identifier, _, Key}, {trans, StringLiteral}}, AstInfoAcc) ->
  1012. {{TransAst, TransAstInfo}, _} = translated_ast(StringLiteral, Context, TreeWalker),
  1013. {erl_syntax:tuple([erl_syntax:string(Key), TransAst]), merge_info(TransAstInfo, AstInfoAcc)};
  1014. ({{identifier, _, Key}, Value}, AstInfoAcc) ->
  1015. {AST, VarName} = resolve_variable_ast(Value, Context),
  1016. {erl_syntax:tuple([erl_syntax:string(Key), format(AST,Context, TreeWalker)]), merge_info(#ast_info{var_names=[VarName]}, AstInfoAcc)}
  1017. end, #ast_info{}, Args),
  1018. {RenderAst, RenderInfo} = custom_tags_modules_ast(Name, InterpretedArgs, Context),
  1019. {{RenderAst, merge_info(AstInfo, RenderInfo)}, TreeWalker}.
  1020. custom_tags_modules_ast(Name, InterpretedArgs, #dtl_context{ custom_tags_modules = [], is_compiling_dir = false }) ->
  1021. {erl_syntax:application(none, erl_syntax:atom(render_tag),
  1022. [erl_syntax:string(Name), erl_syntax:list(InterpretedArgs), erl_syntax:variable("CustomTagsContext")]),
  1023. #ast_info{custom_tags = [Name]}};
  1024. custom_tags_modules_ast(Name, InterpretedArgs, #dtl_context{ custom_tags_modules = [], is_compiling_dir = true, module = Module }) ->
  1025. {erl_syntax:application(erl_syntax:atom(Module), erl_syntax:atom(Name),
  1026. [erl_syntax:list(InterpretedArgs), erl_syntax:variable("CustomTagsContext")]), #ast_info{ custom_tags = [Name] }};
  1027. custom_tags_modules_ast(Name, InterpretedArgs, #dtl_context{ custom_tags_modules = [Module|Rest] } = Context) ->
  1028. case lists:member({Name, 2}, Module:module_info(exports)) of
  1029. true ->
  1030. {erl_syntax:application(erl_syntax:atom(Module), erl_syntax:atom(Name),
  1031. [erl_syntax:list(InterpretedArgs), erl_syntax:variable("CustomTagsContext")]), #ast_info{}};
  1032. false ->
  1033. custom_tags_modules_ast(Name, InterpretedArgs, Context#dtl_context{ custom_tags_modules = Rest })
  1034. end.
  1035. options_ast() ->
  1036. erl_syntax:list([
  1037. erl_syntax:tuple([erl_syntax:atom(translation_fun), erl_syntax:variable("TranslationFun")]),
  1038. erl_syntax:tuple([erl_syntax:atom(locale), erl_syntax:variable("CurrentLocale")])
  1039. ]).
  1040. call_ast(Module, TreeWalkerAcc) ->
  1041. call_ast(Module, erl_syntax:variable("Variables"), #ast_info{}, TreeWalkerAcc).
  1042. call_with_ast(Module, Variable, Context, TreeWalker) ->
  1043. {VarAst, VarName} = resolve_variable_ast(Variable, Context),
  1044. call_ast(Module, VarAst, #ast_info{var_names=[VarName]}, TreeWalker).
  1045. call_ast(Module, Variable, AstInfo, TreeWalker) ->
  1046. AppAst = erl_syntax:application(
  1047. erl_syntax:atom(Module),
  1048. erl_syntax:atom(render),
  1049. [Variable, options_ast()]),
  1050. RenderedAst = erl_syntax:variable("Rendered"),
  1051. OkAst = erl_syntax:clause(
  1052. [erl_syntax:tuple([erl_syntax:atom(ok), RenderedAst])],
  1053. none,
  1054. [RenderedAst]),
  1055. ReasonAst = erl_syntax:variable("Reason"),
  1056. ErrStrAst = erl_syntax:application(
  1057. erl_syntax:atom(io_lib),
  1058. erl_syntax:atom(format),
  1059. [erl_syntax:string("error: ~p"), erl_syntax:list([ReasonAst])]),
  1060. ErrorAst = erl_syntax:clause(
  1061. [erl_syntax:tuple([erl_syntax:atom(error), ReasonAst])],
  1062. none,
  1063. [ErrStrAst]),
  1064. CallAst = erl_syntax:case_expr(AppAst, [OkAst, ErrorAst]),
  1065. with_dependencies(Module:dependencies(), {{CallAst, AstInfo}, TreeWalker}).