erlydtl_compiler.erl 65 KB

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