erlydtl_compiler.erl 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512
  1. %%%-------------------------------------------------------------------
  2. %%% File: erlydtl_compiler.erl
  3. %%% @author Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
  4. %%% @author Evan Miller <emmiller@gmail.com>
  5. %%% @author Andreas Stenius <kaos@astekk.se>
  6. %%% @copyright 2008 Roberto Saccon, Evan Miller
  7. %%% @doc
  8. %%% ErlyDTL template compiler
  9. %%% @end
  10. %%%
  11. %%% The MIT License
  12. %%%
  13. %%% Copyright (c) 2007 Roberto Saccon, Evan Miller
  14. %%%
  15. %%% Permission is hereby granted, free of charge, to any person obtaining a copy
  16. %%% of this software and associated documentation files (the "Software"), to deal
  17. %%% in the Software without restriction, including without limitation the rights
  18. %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  19. %%% copies of the Software, and to permit persons to whom the Software is
  20. %%% furnished to do so, subject to the following conditions:
  21. %%%
  22. %%% The above copyright notice and this permission notice shall be included in
  23. %%% all copies or substantial portions of the Software.
  24. %%%
  25. %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  26. %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  27. %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  28. %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  29. %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  30. %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  31. %%% THE SOFTWARE.
  32. %%%
  33. %%% @since 2007-12-16 by Roberto Saccon, Evan Miller
  34. %%%-------------------------------------------------------------------
  35. -module(erlydtl_compiler).
  36. -author('rsaccon@gmail.com').
  37. -author('emmiller@gmail.com').
  38. -author('Andreas Stenius <kaos@astekk.se>').
  39. %% --------------------------------------------------------------------
  40. %% Definitions
  41. %% --------------------------------------------------------------------
  42. -export([compile/2, compile/3, compile_dir/2, compile_dir/3, parse/1]).
  43. %% exported for use by extension modules
  44. -export([
  45. merge_info/2,
  46. format/3,
  47. value_ast/5,
  48. resolve_scoped_variable_ast/2,
  49. interpret_args/3
  50. ]).
  51. -include("erlydtl_ext.hrl").
  52. compile(Binary, Module) when is_binary(Binary) ->
  53. compile(Binary, Module, []);
  54. compile(File, Module) ->
  55. compile(File, Module, []).
  56. compile(Binary, Module, Options) when is_binary(Binary) ->
  57. File = "",
  58. CheckSum = "",
  59. Context = init_dtl_context(File, Module, Options),
  60. case parse(Binary, Context) of
  61. {ok, DjangoParseTree} ->
  62. case compile_to_binary(File, DjangoParseTree, Context, CheckSum) of
  63. {ok, Module1, _, _} ->
  64. {ok, Module1};
  65. Err ->
  66. Err
  67. end;
  68. Err ->
  69. Err
  70. end;
  71. compile(File, Module, Options) ->
  72. Context = init_dtl_context(File, Module, Options),
  73. case parse(File, Context) of
  74. ok ->
  75. ok;
  76. {ok, DjangoParseTree, CheckSum} ->
  77. case compile_to_binary(File, DjangoParseTree, Context, CheckSum) of
  78. {ok, Module1, Bin, Warnings} ->
  79. write_binary(Module1, Bin, Options, Warnings);
  80. Err ->
  81. Err
  82. end;
  83. Err ->
  84. Err
  85. end.
  86. compile_dir(Dir, Module) ->
  87. compile_dir(Dir, Module, []).
  88. compile_dir(Dir, Module, Options) ->
  89. Context = init_dtl_context_dir(Dir, Module, Options),
  90. %% Find all files in Dir (recursively), matching the regex (no
  91. %% files ending in "~").
  92. Files = filelib:fold_files(Dir, ".+[^~]$", true, fun(F1,Acc1) -> [F1 | Acc1] end, []),
  93. {ParserResults, ParserErrors} = lists:foldl(fun
  94. (File, {ResultAcc, ErrorAcc}) ->
  95. case filename:basename(File) of
  96. "."++_ ->
  97. {ResultAcc, ErrorAcc};
  98. _ ->
  99. FilePath = filename:absname(File),
  100. case filelib:is_dir(FilePath) of
  101. true ->
  102. {ResultAcc, ErrorAcc};
  103. false ->
  104. case parse(FilePath, Context) of
  105. ok -> {ResultAcc, ErrorAcc};
  106. {ok, DjangoParseTree, CheckSum} ->
  107. {[{File, DjangoParseTree, CheckSum}|ResultAcc], ErrorAcc};
  108. Err -> {ResultAcc, [Err|ErrorAcc]}
  109. end
  110. end
  111. end
  112. end, {[], []}, Files),
  113. case ParserErrors of
  114. [] ->
  115. case compile_multiple_to_binary(Dir, ParserResults, Context) of
  116. {ok, Module1, Bin, Warnings} ->
  117. write_binary(Module1, Bin, Options, Warnings);
  118. Err ->
  119. Err
  120. end;
  121. [Error|_] ->
  122. Error
  123. end.
  124. %%====================================================================
  125. %% Internal functions
  126. %%====================================================================
  127. write_binary(Module1, Bin, Options, Warnings) ->
  128. Verbose = proplists:get_value(verbose, Options, false),
  129. case proplists:get_value(out_dir, Options) of
  130. undefined ->
  131. print(Verbose, "Template module: ~w not saved (no out_dir option)\n", [Module1]),
  132. ok;
  133. OutDir ->
  134. BeamFile = filename:join([OutDir, atom_to_list(Module1) ++ ".beam"]),
  135. print(Verbose, "Template module: ~w -> ~s~s\n",
  136. [Module1, BeamFile,
  137. case Warnings of
  138. [] -> "";
  139. _ -> io_lib:format("\n Warnings: ~p", [Warnings])
  140. end]),
  141. case file:write_file(BeamFile, Bin) of
  142. ok ->
  143. ok;
  144. {error, Reason} ->
  145. {error, lists:flatten(
  146. io_lib:format("Beam generation of '~s' failed: ~p",
  147. [BeamFile, file:format_error(Reason)]))}
  148. end
  149. end.
  150. compile_multiple_to_binary(Dir, ParserResults, Context) ->
  151. MatchAst = options_match_ast(Context),
  152. {Functions, {AstInfo, _}} = lists:mapfoldl(fun({File, DjangoParseTree, CheckSum}, {AstInfo, TreeWalker}) ->
  153. FilePath = full_path(File, Context#dtl_context.doc_root),
  154. {{BodyAst, BodyInfo}, TreeWalker1} = with_dependency({FilePath, CheckSum}, body_ast(DjangoParseTree, Context, TreeWalker)),
  155. FunctionName = filename:rootname(filename:basename(File)),
  156. Function1 = erl_syntax:function(erl_syntax:atom(FunctionName),
  157. [erl_syntax:clause([erl_syntax:variable("_Variables")], none,
  158. [erl_syntax:application(none, erl_syntax:atom(FunctionName),
  159. [erl_syntax:variable("_Variables"), erl_syntax:list([])])])]),
  160. Function2 = erl_syntax:function(erl_syntax:atom(FunctionName),
  161. [erl_syntax:clause([erl_syntax:variable("_Variables"), erl_syntax:variable("RenderOptions")], none,
  162. MatchAst ++ [BodyAst])]),
  163. {{FunctionName, Function1, Function2}, {merge_info(AstInfo, BodyInfo), TreeWalker1}}
  164. end, {#ast_info{}, init_treewalker(Context)}, ParserResults),
  165. Forms = custom_forms(Dir, Context#dtl_context.module, Functions, AstInfo),
  166. compile_forms_and_reload(Dir, Forms, Context).
  167. compile_to_binary(File, DjangoParseTree, Context, CheckSum) ->
  168. try body_ast(DjangoParseTree, Context, init_treewalker(Context)) of
  169. {{BodyAst, BodyInfo}, BodyTreeWalker} ->
  170. try custom_tags_ast(BodyInfo#ast_info.custom_tags, Context, BodyTreeWalker) of
  171. {{CustomTagsAst, CustomTagsInfo}, _} ->
  172. Forms = forms(File, Context#dtl_context.module, {BodyAst, BodyInfo},
  173. {CustomTagsAst, CustomTagsInfo}, CheckSum, Context, BodyTreeWalker),
  174. compile_forms_and_reload(File, Forms, Context)
  175. catch
  176. throw:Error -> Error
  177. end
  178. catch
  179. throw:Error -> Error
  180. end.
  181. compile_forms_and_reload(File, Forms, Context) ->
  182. case proplists:get_value(debug_compiler, Context#dtl_context.compiler_options) of
  183. true ->
  184. io:format("template source:~n~s~n",
  185. [try
  186. erl_prettypr:format(erl_syntax:form_list(Forms))
  187. catch
  188. error:Err ->
  189. io_lib:format("Pretty printing failed: ~p~nContext: ~n~p~nForms: ~n~p~n",
  190. [Err, Context, Forms])
  191. end
  192. ]);
  193. _ -> nop
  194. end,
  195. case compile:forms(Forms, Context#dtl_context.compiler_options) of
  196. {ok, Module1, Bin} ->
  197. load_code(Module1, Bin, []);
  198. {ok, Module1, Bin, Warnings} ->
  199. load_code(Module1, Bin, Warnings);
  200. error ->
  201. {error, lists:concat(["compilation failed: ", File])};
  202. OtherError ->
  203. OtherError
  204. end.
  205. load_code(Module, Bin, Warnings) ->
  206. code:purge(Module),
  207. case code:load_binary(Module, atom_to_list(Module) ++ ".erl", Bin) of
  208. {module, _} -> {ok, Module, Bin, Warnings};
  209. _ -> {error, lists:concat(["code reload failed: ", Module])}
  210. end.
  211. init_context(IsCompilingDir, ParseTrail, DefDir, Module, Options) ->
  212. Ctx = #dtl_context{},
  213. Context = #dtl_context{
  214. parse_trail = ParseTrail,
  215. module = Module,
  216. doc_root = proplists:get_value(doc_root, Options, DefDir),
  217. filter_modules = proplists:get_value(custom_filters_modules, Options, Ctx#dtl_context.filter_modules) ++ [erlydtl_filters],
  218. custom_tags_dir = proplists:get_value(custom_tags_dir, Options, filename:join([erlydtl_deps:get_base_dir(), "priv", "custom_tags"])),
  219. custom_tags_modules = proplists:get_value(custom_tags_modules, Options, Ctx#dtl_context.custom_tags_modules),
  220. blocktrans_fun = proplists:get_value(blocktrans_fun, Options, Ctx#dtl_context.blocktrans_fun),
  221. blocktrans_locales = proplists:get_value(blocktrans_locales, Options, Ctx#dtl_context.blocktrans_locales),
  222. vars = proplists:get_value(vars, Options, Ctx#dtl_context.vars),
  223. reader = proplists:get_value(reader, Options, Ctx#dtl_context.reader),
  224. compiler_options = proplists:get_value(compiler_options, Options, Ctx#dtl_context.compiler_options),
  225. binary_strings = proplists:get_value(binary_strings, Options, Ctx#dtl_context.binary_strings),
  226. force_recompile = proplists:get_value(force_recompile, Options, Ctx#dtl_context.force_recompile),
  227. locale = proplists:get_value(locale, Options, Ctx#dtl_context.locale),
  228. verbose = proplists:get_value(verbose, Options, Ctx#dtl_context.verbose),
  229. is_compiling_dir = IsCompilingDir,
  230. extension_module = proplists:get_value(extension_module, Options, Ctx#dtl_context.extension_module)
  231. },
  232. case call_extension(Context, init_context, [Context]) of
  233. {ok, C} when is_record(C, dtl_context) -> C;
  234. undefined -> Context
  235. end.
  236. init_dtl_context(File, Module, Options) when is_list(Module) ->
  237. init_dtl_context(File, list_to_atom(Module), Options);
  238. init_dtl_context(File, Module, Options) ->
  239. init_context(false, [File], filename:dirname(File), Module, Options).
  240. init_dtl_context_dir(Dir, Module, Options) when is_list(Module) ->
  241. init_dtl_context_dir(Dir, list_to_atom(Module), Options);
  242. init_dtl_context_dir(Dir, Module, Options) ->
  243. init_context(true, [], Dir, Module, Options).
  244. init_treewalker(Context) ->
  245. TreeWalker = #treewalker{},
  246. case call_extension(Context, init_treewalker, [TreeWalker]) of
  247. {ok, TW} when is_record(TW, treewalker) -> TW;
  248. undefined -> TreeWalker
  249. end.
  250. is_up_to_date(_, #dtl_context{force_recompile = true}) ->
  251. false;
  252. is_up_to_date(CheckSum, Context) ->
  253. Module = Context#dtl_context.module,
  254. {M, F} = Context#dtl_context.reader,
  255. case catch Module:source() of
  256. {_, CheckSum} ->
  257. case catch Module:dependencies() of
  258. L when is_list(L) ->
  259. RecompileList = lists:foldl(fun
  260. ({XFile, XCheckSum}, Acc) ->
  261. case catch M:F(XFile) of
  262. {ok, Data} ->
  263. case binary_to_list(erlang:md5(Data)) of
  264. XCheckSum ->
  265. Acc;
  266. _ ->
  267. [recompile | Acc]
  268. end;
  269. _ ->
  270. [recompile | Acc]
  271. end
  272. end, [], L),
  273. case RecompileList of
  274. [] -> true;
  275. _ -> false
  276. end;
  277. _ ->
  278. false
  279. end;
  280. _ ->
  281. false
  282. end.
  283. parse(Data) ->
  284. parse(Data, #dtl_context{}).
  285. parse(Data, Context) when is_binary(Data) ->
  286. check_scan(erlydtl_scanner:scan(binary_to_list(Data)), Context);
  287. parse(File, Context) ->
  288. {M, F} = Context#dtl_context.reader,
  289. case catch M:F(File) of
  290. {ok, Data} ->
  291. CheckSum = binary_to_list(erlang:md5(Data)),
  292. case parse(CheckSum, Data, Context) of
  293. {error, Msg} when is_list(Msg) ->
  294. {error, File ++ ": " ++ Msg};
  295. {error, Msg} ->
  296. {error, {File, [Msg]}};
  297. Result ->
  298. Result
  299. end;
  300. _ ->
  301. {error, {File, [{0, Context#dtl_context.module, "Failed to read file"}]}}
  302. end.
  303. parse(CheckSum, Data, Context) ->
  304. case is_up_to_date(CheckSum, Context) of
  305. true ->
  306. ok;
  307. _ ->
  308. case parse(Data, Context) of
  309. {ok, Val} ->
  310. {ok, Val, CheckSum};
  311. Err ->
  312. Err
  313. end
  314. end.
  315. call_extension(#dtl_context{ extension_module=undefined }, _Fun, _Args) ->
  316. undefined;
  317. call_extension(#dtl_context{ extension_module=Mod }, Fun, Args)
  318. when is_atom(Mod), is_atom(Fun), is_list(Args) ->
  319. M = case code:is_loaded(Mod) of
  320. false ->
  321. case code:load_file(Mod) of
  322. {module, Mod} ->
  323. Mod;
  324. _ ->
  325. undefined
  326. end;
  327. _ -> Mod
  328. end,
  329. if M /= undefined ->
  330. case erlang:function_exported(M, Fun, length(Args)) of
  331. true ->
  332. apply(M, Fun, Args);
  333. false ->
  334. undefined
  335. end;
  336. true ->
  337. undefined
  338. end.
  339. check_scan({ok, Tokens}, Context) ->
  340. Tokens1 = case call_extension(Context, post_scan, [Tokens]) of
  341. undefined -> Tokens;
  342. {ok, T} -> T
  343. end,
  344. check_parse(erlydtl_parser:parse(Tokens1), [], Context#dtl_context{ scanned_tokens=Tokens1 });
  345. check_scan({error, Err, State}, Context) ->
  346. case call_extension(Context, scan, [State]) of
  347. undefined ->
  348. {error, Err};
  349. {ok, NewState} ->
  350. check_scan(erlydtl_scanner:resume(NewState), Context);
  351. ExtRes ->
  352. ExtRes
  353. end.
  354. check_parse({ok, _}=Ok, [], _Context) -> Ok;
  355. check_parse({ok, _, _}=Ok, [], _Context) -> Ok;
  356. check_parse({ok, Parsed}, Acc, _Context) -> {ok, Acc ++ Parsed};
  357. check_parse({ok, Parsed, C}, Acc, _Context) -> {ok, Acc ++ Parsed, C};
  358. check_parse({error, _}=Err, _, _Context) -> Err;
  359. check_parse({error, Err, State}, Acc, Context) ->
  360. {State1, Parsed} = reset_parse_state(State, Context),
  361. case call_extension(Context, parse, [State1]) of
  362. undefined ->
  363. {error, Err};
  364. {ok, ExtParsed} ->
  365. {ok, Acc ++ Parsed ++ ExtParsed};
  366. {error, ExtErr, ExtState} ->
  367. case reset_parse_state(ExtState, Context) of
  368. {_, []} ->
  369. %% todo: see if this is indeed a sensible ext error,
  370. %% or if we should rather present the original Err message
  371. {error, ExtErr};
  372. {State2, ExtParsed} ->
  373. check_parse(erlydtl_parser:resume(State2), Acc ++ Parsed ++ ExtParsed, Context)
  374. end;
  375. ExtRes ->
  376. ExtRes
  377. end.
  378. %% backtrack up to the nearest opening tag, and keep the value stack parsed ok so far
  379. reset_parse_state([[{Tag, _, _}|_]=Ts, Tzr, _, _, Stack], Context)
  380. when Tag==open_tag; Tag==open_var ->
  381. %% reached opening tag, so the stack should be sensible here
  382. {[reset_token_stream(Ts, Context#dtl_context.scanned_tokens),
  383. Tzr, 0, [], []], lists:flatten(Stack)};
  384. reset_parse_state([_, _, 0, [], []]=State, _Context) ->
  385. %% top of (empty) stack
  386. {State, []};
  387. reset_parse_state([Ts, Tzr, _, [0 | []], [Parsed | []]], Context)
  388. when is_list(Parsed) ->
  389. %% top of good stack
  390. {[reset_token_stream(Ts, Context#dtl_context.scanned_tokens),
  391. Tzr, 0, [], []], Parsed};
  392. reset_parse_state([Ts, Tzr, _, [S | Ss], [T | Stack]], Context) ->
  393. %% backtrack...
  394. reset_parse_state([[T|Ts], Tzr, S, Ss, Stack], Context).
  395. reset_token_stream([T|_], [T|Ts]) -> [T|Ts];
  396. reset_token_stream(Ts, [_|S]) ->
  397. reset_token_stream(Ts, S).
  398. %% we should find the next token in the list of scanned tokens, or something is real fishy
  399. custom_tags_ast(CustomTags, Context, TreeWalker) ->
  400. {{CustomTagsClauses, CustomTagsInfo}, TreeWalker1} = custom_tags_clauses_ast(CustomTags, Context, TreeWalker),
  401. %% This doesn't work since erl_syntax:revert/1 chokes on the airity_qualifier in the -compile attribute..
  402. %% bug report sent to the erlang-bugs mailing list.
  403. %% {{erl_syntax:form_list(
  404. %% [erl_syntax:attribute(
  405. %% erl_syntax:atom(compile),
  406. %% [erl_syntax:tuple(
  407. %% [erl_syntax:atom(nowarn_unused_function),
  408. %% erl_syntax:arity_qualifier(
  409. %% erl_syntax:atom(render_tag),
  410. %% erl_syntax:integer(3))])]),
  411. %% erl_syntax:function(erl_syntax:atom(render_tag), CustomTagsClauses)]),
  412. {{erl_syntax:function(erl_syntax:atom(render_tag), CustomTagsClauses),
  413. CustomTagsInfo},
  414. TreeWalker1}.
  415. custom_tags_clauses_ast(CustomTags, Context, TreeWalker) ->
  416. custom_tags_clauses_ast1(CustomTags, [], [], #ast_info{}, Context, TreeWalker).
  417. custom_tags_clauses_ast1([], _ExcludeTags, ClauseAcc, InfoAcc, Context, TreeWalker) ->
  418. {{DefaultAst, DefaultInfo}, TreeWalker1} =
  419. case call_extension(Context, custom_tag_ast, [Context, TreeWalker]) of
  420. undefined ->
  421. {{erl_syntax:clause(
  422. [erl_syntax:variable("_TagName"), erl_syntax:underscore(), erl_syntax:underscore()],
  423. none,
  424. [erl_syntax:list([])]),
  425. InfoAcc},
  426. TreeWalker};
  427. {{ExtAst, ExtInfo}, ExtTreeWalker} ->
  428. Clause = erl_syntax:clause(
  429. [erl_syntax:variable("TagName"), erl_syntax:variable("_Variables"), erl_syntax:variable("RenderOptions")],
  430. none, options_match_ast(Context, ExtTreeWalker) ++ [ExtAst]),
  431. {{Clause, merge_info(ExtInfo, InfoAcc)}, ExtTreeWalker}
  432. end,
  433. {{lists:reverse([DefaultAst|ClauseAcc]), DefaultInfo}, TreeWalker1};
  434. custom_tags_clauses_ast1([Tag|CustomTags], ExcludeTags, ClauseAcc, InfoAcc, Context, TreeWalker) ->
  435. case lists:member(Tag, ExcludeTags) of
  436. true ->
  437. custom_tags_clauses_ast1(CustomTags, ExcludeTags, ClauseAcc, InfoAcc, Context, TreeWalker);
  438. false ->
  439. CustomTagFile = full_path(Tag, Context#dtl_context.custom_tags_dir),
  440. case filelib:is_file(CustomTagFile) of
  441. true ->
  442. case parse(CustomTagFile, Context) of
  443. {ok, DjangoParseTree, CheckSum} ->
  444. {{BodyAst, BodyAstInfo}, TreeWalker1} = with_dependency(
  445. {CustomTagFile, CheckSum}, body_ast(DjangoParseTree, Context, TreeWalker)),
  446. MatchAst = options_match_ast(Context, TreeWalker),
  447. Clause = erl_syntax:clause(
  448. [erl_syntax:atom(Tag), erl_syntax:variable("_Variables"), erl_syntax:variable("RenderOptions")],
  449. none, MatchAst ++ [BodyAst]),
  450. custom_tags_clauses_ast1(CustomTags, [Tag|ExcludeTags],
  451. [Clause|ClauseAcc], merge_info(BodyAstInfo, InfoAcc),
  452. Context, TreeWalker1);
  453. Error ->
  454. throw(Error)
  455. end;
  456. false ->
  457. case call_extension(Context, custom_tag_ast, [Tag, Context, TreeWalker]) of
  458. undefined ->
  459. custom_tags_clauses_ast1(
  460. CustomTags, [Tag | ExcludeTags],
  461. ClauseAcc, InfoAcc, Context, TreeWalker);
  462. {{Ast, Info}, TW} ->
  463. Clause = erl_syntax:clause(
  464. [erl_syntax:atom(Tag), erl_syntax:variable("_Variables"), erl_syntax:variable("RenderOptions")],
  465. none, options_match_ast(Context, TW) ++ [Ast]),
  466. custom_tags_clauses_ast1(
  467. CustomTags, [Tag | ExcludeTags],
  468. [Clause|ClauseAcc], merge_info(Info, InfoAcc),
  469. Context, TW)
  470. end
  471. end
  472. end.
  473. dependencies_function(Dependencies) ->
  474. erl_syntax:function(
  475. erl_syntax:atom(dependencies), [erl_syntax:clause([], none,
  476. [erl_syntax:list(lists:map(fun
  477. ({XFile, XCheckSum}) ->
  478. erl_syntax:tuple([erl_syntax:string(XFile), erl_syntax:string(XCheckSum)])
  479. end, Dependencies))])]).
  480. translatable_strings_function(TranslatableStrings) ->
  481. erl_syntax:function(
  482. erl_syntax:atom(translatable_strings), [erl_syntax:clause([], none,
  483. [erl_syntax:list(lists:map(fun(String) ->
  484. erl_syntax:string(String)
  485. end,
  486. TranslatableStrings))])]).
  487. translated_blocks_function(TranslatedBlocks) ->
  488. erl_syntax:function(
  489. erl_syntax:atom(translated_blocks), [erl_syntax:clause([], none,
  490. [erl_syntax:list(lists:map(fun(String) ->
  491. erl_syntax:string(String)
  492. end,
  493. TranslatedBlocks))])]).
  494. variables_function(Variables) ->
  495. erl_syntax:function(
  496. erl_syntax:atom(variables), [erl_syntax:clause([], none,
  497. [erl_syntax:list([erl_syntax:atom(S) || S <- lists:usort(Variables)])])]).
  498. custom_forms(Dir, Module, Functions, AstInfo) ->
  499. ModuleAst = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
  500. ExportAst = erl_syntax:attribute(erl_syntax:atom(export),
  501. [erl_syntax:list([
  502. erl_syntax:arity_qualifier(erl_syntax:atom(source_dir), erl_syntax:integer(0)),
  503. erl_syntax:arity_qualifier(erl_syntax:atom(dependencies), erl_syntax:integer(0)),
  504. erl_syntax:arity_qualifier(erl_syntax:atom(translatable_strings), erl_syntax:integer(0))
  505. |
  506. lists:foldl(fun({FunctionName, _, _}, Acc) ->
  507. [erl_syntax:arity_qualifier(erl_syntax:atom(FunctionName), erl_syntax:integer(1)),
  508. erl_syntax:arity_qualifier(erl_syntax:atom(FunctionName), erl_syntax:integer(2))|Acc]
  509. end, [], Functions)]
  510. )]),
  511. SourceFunctionAst = erl_syntax:function(
  512. erl_syntax:atom(source_dir), [erl_syntax:clause([], none, [erl_syntax:string(Dir)])]),
  513. DependenciesFunctionAst = dependencies_function(AstInfo#ast_info.dependencies),
  514. TranslatableStringsFunctionAst = translatable_strings_function(AstInfo#ast_info.translatable_strings),
  515. FunctionAsts = lists:foldl(fun({_, Function1, Function2}, Acc) -> [Function1, Function2 | Acc] end, [], Functions),
  516. [erl_syntax:revert(X) || X <- [ModuleAst, ExportAst, SourceFunctionAst, DependenciesFunctionAst, TranslatableStringsFunctionAst
  517. | FunctionAsts] ++ AstInfo#ast_info.pre_render_asts].
  518. forms(File, Module, {BodyAst, BodyInfo}, {CustomTagsFunctionAst, CustomTagsInfo}, CheckSum, Context, TreeWalker) ->
  519. MergedInfo = merge_info(BodyInfo, CustomTagsInfo),
  520. Render0FunctionAst = erl_syntax:function(erl_syntax:atom(render),
  521. [erl_syntax:clause([], none, [erl_syntax:application(none,
  522. erl_syntax:atom(render), [erl_syntax:list([])])])]),
  523. Render1FunctionAst = erl_syntax:function(erl_syntax:atom(render),
  524. [erl_syntax:clause([erl_syntax:variable("_Variables")], none,
  525. [erl_syntax:application(none,
  526. erl_syntax:atom(render),
  527. [erl_syntax:variable("_Variables"), erl_syntax:list([])])])]),
  528. Function2 = erl_syntax:application(none, erl_syntax:atom(render_internal),
  529. [erl_syntax:variable("_Variables"), erl_syntax:variable("RenderOptions")]),
  530. ClauseOk = erl_syntax:clause([erl_syntax:variable("Val")], none,
  531. [erl_syntax:tuple([erl_syntax:atom(ok), erl_syntax:variable("Val")])]),
  532. ClauseCatch = erl_syntax:clause([erl_syntax:variable("Err")], none,
  533. [erl_syntax:tuple([erl_syntax:atom(error), erl_syntax:variable("Err")])]),
  534. Render2FunctionAst = erl_syntax:function(erl_syntax:atom(render),
  535. [erl_syntax:clause([erl_syntax:variable("_Variables"),
  536. erl_syntax:variable("RenderOptions")], none,
  537. [erl_syntax:try_expr([Function2], [ClauseOk], [ClauseCatch])])]),
  538. SourceFunctionTuple = erl_syntax:tuple(
  539. [erl_syntax:string(File), erl_syntax:string(CheckSum)]),
  540. SourceFunctionAst = erl_syntax:function(
  541. erl_syntax:atom(source),
  542. [erl_syntax:clause([], none, [SourceFunctionTuple])]),
  543. DependenciesFunctionAst = dependencies_function(MergedInfo#ast_info.dependencies),
  544. TranslatableStringsAst = translatable_strings_function(MergedInfo#ast_info.translatable_strings),
  545. TranslatedBlocksAst = translated_blocks_function(MergedInfo#ast_info.translated_blocks),
  546. VariablesAst = variables_function(MergedInfo#ast_info.var_names),
  547. MatchAst = options_match_ast(Context, TreeWalker),
  548. BodyAstTmp = MatchAst ++ [
  549. erl_syntax:application(
  550. erl_syntax:atom(erlydtl_runtime),
  551. erl_syntax:atom(stringify_final),
  552. [BodyAst, erl_syntax:atom(Context#dtl_context.binary_strings)])
  553. ],
  554. RenderInternalFunctionAst = erl_syntax:function(
  555. erl_syntax:atom(render_internal),
  556. [erl_syntax:clause([
  557. erl_syntax:variable("_Variables"),
  558. erl_syntax:variable("RenderOptions")],
  559. none, BodyAstTmp)]
  560. ),
  561. ModuleAst = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
  562. ExportAst = erl_syntax:attribute(erl_syntax:atom(export),
  563. [erl_syntax:list([erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(0)),
  564. erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(1)),
  565. erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(2)),
  566. erl_syntax:arity_qualifier(erl_syntax:atom(source), erl_syntax:integer(0)),
  567. erl_syntax:arity_qualifier(erl_syntax:atom(dependencies), erl_syntax:integer(0)),
  568. erl_syntax:arity_qualifier(erl_syntax:atom(translatable_strings), erl_syntax:integer(0)),
  569. erl_syntax:arity_qualifier(erl_syntax:atom(translated_blocks), erl_syntax:integer(0)),
  570. erl_syntax:arity_qualifier(erl_syntax:atom(variables), erl_syntax:integer(0))
  571. ])]),
  572. erl_syntax:revert_forms(
  573. erl_syntax:form_list(
  574. [ModuleAst, ExportAst, Render0FunctionAst, Render1FunctionAst, Render2FunctionAst,
  575. SourceFunctionAst, DependenciesFunctionAst, TranslatableStringsAst,
  576. TranslatedBlocksAst, VariablesAst, RenderInternalFunctionAst,
  577. CustomTagsFunctionAst
  578. |BodyInfo#ast_info.pre_render_asts])).
  579. options_match_ast(Context) -> options_match_ast(Context, undefined).
  580. options_match_ast(Context, TreeWalker) ->
  581. [
  582. erl_syntax:match_expr(
  583. erl_syntax:variable("_TranslationFun"),
  584. erl_syntax:application(
  585. erl_syntax:atom(proplists),
  586. erl_syntax:atom(get_value),
  587. [erl_syntax:atom(translation_fun), erl_syntax:variable("RenderOptions"), erl_syntax:atom(none)])),
  588. erl_syntax:match_expr(
  589. erl_syntax:variable("_CurrentLocale"),
  590. erl_syntax:application(
  591. erl_syntax:atom(proplists),
  592. erl_syntax:atom(get_value),
  593. [erl_syntax:atom(locale), erl_syntax:variable("RenderOptions"), erl_syntax:atom(none)]))
  594. | case call_extension(Context, setup_render_ast, [Context, TreeWalker]) of
  595. undefined -> [];
  596. Ast when is_list(Ast) -> Ast
  597. end
  598. ].
  599. % child templates should only consist of blocks at the top level
  600. body_ast([{'extends', {string_literal, _Pos, String}} | ThisParseTree], Context, TreeWalker) ->
  601. File = full_path(unescape_string_literal(String), Context#dtl_context.doc_root),
  602. case lists:member(File, Context#dtl_context.parse_trail) of
  603. true ->
  604. throw({error, "Circular file inclusion!"});
  605. _ ->
  606. case parse(File, Context) of
  607. {ok, ParentParseTree, CheckSum} ->
  608. BlockDict = lists:foldl(
  609. fun
  610. ({block, {identifier, _, Name}, Contents}, Dict) ->
  611. dict:store(Name, Contents, Dict);
  612. (_, Dict) ->
  613. Dict
  614. end, dict:new(), ThisParseTree),
  615. with_dependency({File, CheckSum}, body_ast(ParentParseTree, Context#dtl_context{
  616. block_dict = dict:merge(fun(_Key, _ParentVal, ChildVal) -> ChildVal end,
  617. BlockDict, Context#dtl_context.block_dict),
  618. parse_trail = [File | Context#dtl_context.parse_trail]}, TreeWalker));
  619. Err ->
  620. throw(Err)
  621. end
  622. end;
  623. body_ast(DjangoParseTree, Context, TreeWalker) ->
  624. {AstInfoList, TreeWalker2} = lists:mapfoldl(
  625. fun
  626. ({'autoescape', {identifier, _, OnOrOff}, Contents}, TreeWalkerAcc) ->
  627. body_ast(Contents, Context#dtl_context{auto_escape = OnOrOff},
  628. TreeWalkerAcc);
  629. ({'block', {identifier, _, Name}, Contents}, TreeWalkerAcc) ->
  630. Block = case dict:find(Name, Context#dtl_context.block_dict) of
  631. {ok, ChildBlock} -> ChildBlock;
  632. _ -> Contents
  633. end,
  634. body_ast(Block, Context, TreeWalkerAcc);
  635. ({'blocktrans', Args, Contents}, TreeWalkerAcc) ->
  636. blocktrans_ast(Args, Contents, Context, TreeWalkerAcc);
  637. ({'call', {identifier, _, Name}}, TreeWalkerAcc) ->
  638. call_ast(Name, TreeWalkerAcc);
  639. ({'call', {identifier, _, Name}, With}, TreeWalkerAcc) ->
  640. call_with_ast(Name, With, Context, TreeWalkerAcc);
  641. ({'comment', _Contents}, TreeWalkerAcc) ->
  642. empty_ast(TreeWalkerAcc);
  643. ({'cycle', Names}, TreeWalkerAcc) ->
  644. cycle_ast(Names, Context, TreeWalkerAcc);
  645. ({'cycle_compat', Names}, TreeWalkerAcc) ->
  646. cycle_compat_ast(Names, Context, TreeWalkerAcc);
  647. ({'date', 'now', {string_literal, _Pos, FormatString}}, TreeWalkerAcc) ->
  648. now_ast(FormatString, Context, TreeWalkerAcc);
  649. ({'filter', FilterList, Contents}, TreeWalkerAcc) ->
  650. filter_tag_ast(FilterList, Contents, Context, TreeWalkerAcc);
  651. ({'firstof', Vars}, TreeWalkerAcc) ->
  652. firstof_ast(Vars, Context, TreeWalkerAcc);
  653. ({'for', {'in', IteratorList, Variable, Reversed}, Contents}, TreeWalkerAcc) ->
  654. {EmptyAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
  655. for_loop_ast(IteratorList, Variable, Reversed, Contents, EmptyAstInfo, Context, TreeWalker1);
  656. ({'for', {'in', IteratorList, Variable, Reversed}, Contents, EmptyPartContents}, TreeWalkerAcc) ->
  657. {EmptyAstInfo, TreeWalker1} = body_ast(EmptyPartContents, Context, TreeWalkerAcc),
  658. for_loop_ast(IteratorList, Variable, Reversed, Contents, EmptyAstInfo, Context, TreeWalker1);
  659. ({'if', Expression, Contents, Elif}, TreeWalkerAcc) ->
  660. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  661. {ElifAstInfo, TreeWalker2} = body_ast(Elif, Context, TreeWalker1),
  662. ifelse_ast(Expression, IfAstInfo, ElifAstInfo, Context, TreeWalker2);
  663. ({'if', Expression, Contents}, TreeWalkerAcc) ->
  664. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  665. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  666. ifelse_ast(Expression, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  667. ({'ifchanged', '$undefined', Contents}, TreeWalkerAcc) ->
  668. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  669. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  670. ifchanged_contents_ast(Contents, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  671. ({'ifchanged', Values, Contents}, TreeWalkerAcc) ->
  672. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  673. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  674. ifchanged_values_ast(Values, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  675. ({'ifchangedelse', '$undefined', IfContents, ElseContents}, TreeWalkerAcc) ->
  676. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  677. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
  678. ifchanged_contents_ast(IfContents, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  679. ({'ifchangedelse', Values, IfContents, ElseContents}, TreeWalkerAcc) ->
  680. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  681. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
  682. ifchanged_values_ast(Values, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  683. ({'ifelse', Expression, IfContents, ElseContents}, TreeWalkerAcc) ->
  684. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  685. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
  686. ifelse_ast(Expression, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  687. ({'ifequal', [Arg1, Arg2], Contents}, TreeWalkerAcc) ->
  688. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  689. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  690. ifelse_ast({'expr', "eq", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  691. ({'ifequalelse', [Arg1, Arg2], IfContents, ElseContents}, TreeWalkerAcc) ->
  692. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  693. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context,TreeWalker1),
  694. ifelse_ast({'expr', "eq", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  695. ({'ifnotequal', [Arg1, Arg2], Contents}, TreeWalkerAcc) ->
  696. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  697. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  698. ifelse_ast({'expr', "ne", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  699. ({'ifnotequalelse', [Arg1, Arg2], IfContents, ElseContents}, TreeWalkerAcc) ->
  700. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  701. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
  702. ifelse_ast({'expr', "ne", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  703. ({'include', {string_literal, _, File}, Args}, TreeWalkerAcc) ->
  704. include_ast(unescape_string_literal(File), Args, Context#dtl_context.local_scopes, Context, TreeWalkerAcc);
  705. ({'include_only', {string_literal, _, File}, Args}, TreeWalkerAcc) ->
  706. include_ast(unescape_string_literal(File), Args, [], Context, TreeWalkerAcc);
  707. ({'regroup', {ListVariable, Grouper, {identifier, _, NewVariable}}, Contents}, TreeWalkerAcc) ->
  708. regroup_ast(ListVariable, Grouper, NewVariable, Contents, Context, TreeWalkerAcc);
  709. ({'spaceless', Contents}, TreeWalkerAcc) ->
  710. spaceless_ast(Contents, Context, TreeWalkerAcc);
  711. ({'ssi', Arg}, TreeWalkerAcc) ->
  712. ssi_ast(Arg, Context, TreeWalkerAcc);
  713. ({'ssi_parsed', {string_literal, _, FileName}}, TreeWalkerAcc) ->
  714. include_ast(unescape_string_literal(FileName), [], Context#dtl_context.local_scopes, Context, TreeWalkerAcc);
  715. ({'string', _Pos, String}, TreeWalkerAcc) ->
  716. string_ast(String, Context, TreeWalkerAcc);
  717. ({'tag', {identifier, _, Name}, Args}, TreeWalkerAcc) ->
  718. tag_ast(Name, Args, Context, TreeWalkerAcc);
  719. ({'templatetag', {_, _, TagName}}, TreeWalkerAcc) ->
  720. templatetag_ast(TagName, Context, TreeWalkerAcc);
  721. ({'trans', Value}, TreeWalkerAcc) ->
  722. translated_ast(Value, Context, TreeWalkerAcc);
  723. ({'widthratio', Numerator, Denominator, Scale}, TreeWalkerAcc) ->
  724. widthratio_ast(Numerator, Denominator, Scale, Context, TreeWalkerAcc);
  725. ({'with', Args, Contents}, TreeWalkerAcc) ->
  726. with_ast(Args, Contents, Context, TreeWalkerAcc);
  727. ({'extension', Tag}, TreeWalkerAcc) ->
  728. extension_ast(Tag, Context, TreeWalkerAcc);
  729. (ValueToken, TreeWalkerAcc) ->
  730. {{ValueAst,ValueInfo},ValueTreeWalker} = value_ast(ValueToken, true, true, Context, TreeWalkerAcc),
  731. {{format(ValueAst, Context, ValueTreeWalker),ValueInfo},ValueTreeWalker}
  732. end, TreeWalker, DjangoParseTree),
  733. {AstList, {Info, TreeWalker3}} = lists:mapfoldl(
  734. fun({Ast, Info}, {InfoAcc, TreeWalkerAcc}) ->
  735. PresetVars = lists:foldl(fun
  736. (X, Acc) ->
  737. case proplists:lookup(X, Context#dtl_context.vars) of
  738. none ->
  739. Acc;
  740. Val ->
  741. [erl_syntax:abstract(Val) | Acc]
  742. end
  743. end, [], Info#ast_info.var_names),
  744. case PresetVars of
  745. [] ->
  746. {Ast, {merge_info(Info, InfoAcc), TreeWalkerAcc}};
  747. _ ->
  748. Counter = TreeWalkerAcc#treewalker.counter,
  749. Name = lists:concat([pre_render, Counter]),
  750. Ast1 = erl_syntax:application(none, erl_syntax:atom(Name),
  751. [erl_syntax:list(PresetVars)]),
  752. PreRenderAst = erl_syntax:function(erl_syntax:atom(Name),
  753. [erl_syntax:clause([erl_syntax:variable("_Variables")], none, [Ast])]),
  754. PreRenderAsts = Info#ast_info.pre_render_asts,
  755. Info1 = Info#ast_info{pre_render_asts = [PreRenderAst | PreRenderAsts]},
  756. {Ast1, {merge_info(Info1, InfoAcc), TreeWalkerAcc#treewalker{counter = Counter + 1}}}
  757. end
  758. end, {#ast_info{}, TreeWalker2}, AstInfoList),
  759. {{erl_syntax:list(AstList), Info}, TreeWalker3}.
  760. value_ast(ValueToken, AsString, EmptyIfUndefined, Context, TreeWalker) ->
  761. case ValueToken of
  762. {'expr', Operator, Value} ->
  763. {{ValueAst,InfoValue}, TreeWalker1} = value_ast(Value, false, EmptyIfUndefined, Context, TreeWalker),
  764. Ast = erl_syntax:application(erl_syntax:atom(erlydtl_runtime),
  765. erl_syntax:atom(Operator),
  766. [ValueAst]),
  767. {{Ast, InfoValue}, TreeWalker1};
  768. {'expr', Operator, Value1, Value2} ->
  769. {{Value1Ast,InfoValue1}, TreeWalker1} = value_ast(Value1, false, EmptyIfUndefined, Context, TreeWalker),
  770. {{Value2Ast,InfoValue2}, TreeWalker2} = value_ast(Value2, false, EmptyIfUndefined, Context, TreeWalker1),
  771. Ast = erl_syntax:application(erl_syntax:atom(erlydtl_runtime),
  772. erl_syntax:atom(Operator),
  773. [Value1Ast, Value2Ast]),
  774. {{Ast, merge_info(InfoValue1,InfoValue2)}, TreeWalker2};
  775. {'string_literal', _Pos, String} ->
  776. string_ast(unescape_string_literal(String), Context, TreeWalker);
  777. {'number_literal', _Pos, Number} ->
  778. case AsString of
  779. true -> string_ast(Number, Context, TreeWalker);
  780. false -> {{erl_syntax:integer(list_to_integer(Number)), #ast_info{}}, TreeWalker}
  781. end;
  782. {'apply_filter', Variable, Filter} ->
  783. filter_ast(Variable, Filter, Context, TreeWalker);
  784. {'attribute', _} = Variable ->
  785. resolve_variable_ast(Variable, Context, TreeWalker, EmptyIfUndefined);
  786. {'variable', _} = Variable ->
  787. resolve_variable_ast(Variable, Context, TreeWalker, EmptyIfUndefined);
  788. {extension, Tag} ->
  789. extension_ast(Tag, Context, TreeWalker)
  790. end.
  791. extension_ast(Tag, Context, TreeWalker) ->
  792. case call_extension(Context, compile_ast, [Tag, Context, TreeWalker]) of
  793. undefined ->
  794. throw({error, {unknown_extension, Tag}});
  795. Result ->
  796. Result
  797. end.
  798. merge_info(Info1, Info2) ->
  799. #ast_info{
  800. dependencies =
  801. lists:merge(
  802. lists:sort(Info1#ast_info.dependencies),
  803. lists:sort(Info2#ast_info.dependencies)),
  804. var_names =
  805. lists:merge(
  806. lists:sort(Info1#ast_info.var_names),
  807. lists:sort(Info2#ast_info.var_names)),
  808. translatable_strings =
  809. lists:merge(
  810. lists:sort(Info1#ast_info.translatable_strings),
  811. lists:sort(Info2#ast_info.translatable_strings)),
  812. translated_blocks =
  813. lists:merge(
  814. lists:sort(Info1#ast_info.translated_blocks),
  815. lists:sort(Info2#ast_info.translated_blocks)),
  816. custom_tags =
  817. lists:merge(
  818. lists:sort(Info1#ast_info.custom_tags),
  819. lists:sort(Info2#ast_info.custom_tags)),
  820. pre_render_asts =
  821. lists:merge(
  822. Info1#ast_info.pre_render_asts,
  823. Info2#ast_info.pre_render_asts)}.
  824. with_dependencies([], Args) ->
  825. Args;
  826. with_dependencies([Dependency | Rest], Args) ->
  827. with_dependencies(Rest, with_dependency(Dependency, Args)).
  828. with_dependency(FilePath, {{Ast, Info}, TreeWalker}) ->
  829. {{Ast, Info#ast_info{dependencies = [FilePath | Info#ast_info.dependencies]}}, TreeWalker}.
  830. empty_ast(TreeWalker) ->
  831. {{erl_syntax:list([]), #ast_info{}}, TreeWalker}.
  832. blocktrans_ast(ArgList, Contents, Context, TreeWalker) ->
  833. {NewScope, {ArgInfo, TreeWalker1}} = lists:mapfoldl(fun
  834. ({{identifier, _, LocalVarName}, Value}, {AstInfo1, TreeWalker1}) ->
  835. {{Ast, Info}, TreeWalker2} = value_ast(Value, false, false, Context, TreeWalker1),
  836. {{LocalVarName, Ast}, {merge_info(AstInfo1, Info), TreeWalker2}}
  837. end, {#ast_info{}, TreeWalker}, ArgList),
  838. NewContext = Context#dtl_context{ local_scopes = [NewScope|Context#dtl_context.local_scopes] },
  839. SourceText = lists:flatten(erlydtl_unparser:unparse(Contents)),
  840. {{DefaultAst, AstInfo}, TreeWalker2} = body_ast(Contents, NewContext, TreeWalker1),
  841. MergedInfo = merge_info(AstInfo, ArgInfo),
  842. case Context#dtl_context.blocktrans_fun of
  843. none ->
  844. {{DefaultAst, MergedInfo}, TreeWalker2};
  845. BlockTransFun when is_function(BlockTransFun) ->
  846. {FinalAstInfo, FinalTreeWalker, Clauses} = lists:foldr(fun(Locale, {AstInfoAcc, ThisTreeWalker, ClauseAcc}) ->
  847. case BlockTransFun(SourceText, Locale) of
  848. default ->
  849. {AstInfoAcc, ThisTreeWalker, ClauseAcc};
  850. Body ->
  851. {ok, DjangoParseTree} = parse(Body, Context),
  852. {{ThisAst, ThisAstInfo}, TreeWalker3} = body_ast(DjangoParseTree, NewContext, ThisTreeWalker),
  853. {merge_info(ThisAstInfo, AstInfoAcc), TreeWalker3,
  854. [erl_syntax:clause([erl_syntax:string(Locale)], none, [ThisAst])|ClauseAcc]}
  855. end
  856. end, {MergedInfo, TreeWalker2, []}, Context#dtl_context.blocktrans_locales),
  857. Ast = erl_syntax:case_expr(erl_syntax:variable("_CurrentLocale"),
  858. Clauses ++ [erl_syntax:clause([erl_syntax:underscore()], none, [DefaultAst])]),
  859. {{Ast, FinalAstInfo#ast_info{ translated_blocks = [SourceText] }}, FinalTreeWalker}
  860. end.
  861. translated_ast({string_literal, _, String}, Context, TreeWalker) ->
  862. NewStr = unescape_string_literal(String),
  863. DefaultString = case Context#dtl_context.locale of
  864. none -> NewStr;
  865. Locale -> erlydtl_i18n:translate(NewStr,Locale)
  866. end,
  867. translated_ast2(erl_syntax:string(NewStr), erl_syntax:string(DefaultString),
  868. #ast_info{translatable_strings = [NewStr]}, TreeWalker);
  869. translated_ast(ValueToken, Context, TreeWalker) ->
  870. {{Ast, Info}, TreeWalker1} = value_ast(ValueToken, true, false, Context, TreeWalker),
  871. translated_ast2(Ast, Ast, Info, TreeWalker1).
  872. translated_ast2(NewStrAst, DefaultStringAst, AstInfo, TreeWalker) ->
  873. StringLookupAst = erl_syntax:application(
  874. erl_syntax:atom(erlydtl_runtime),
  875. erl_syntax:atom(translate),
  876. [NewStrAst, erl_syntax:variable("_TranslationFun"), DefaultStringAst]),
  877. {{StringLookupAst, AstInfo}, TreeWalker}.
  878. % Completely unnecessary in ErlyDTL (use {{ "{%" }} etc), but implemented for compatibility.
  879. templatetag_ast("openblock", Context, TreeWalker) ->
  880. string_ast("{%", Context, TreeWalker);
  881. templatetag_ast("closeblock", Context, TreeWalker) ->
  882. string_ast("%}", Context, TreeWalker);
  883. templatetag_ast("openvariable", Context, TreeWalker) ->
  884. string_ast("{{", Context, TreeWalker);
  885. templatetag_ast("closevariable", Context, TreeWalker) ->
  886. string_ast("}}", Context, TreeWalker);
  887. templatetag_ast("openbrace", Context, TreeWalker) ->
  888. string_ast("{", Context, TreeWalker);
  889. templatetag_ast("closebrace", Context, TreeWalker) ->
  890. string_ast("}", Context, TreeWalker);
  891. templatetag_ast("opencomment", Context, TreeWalker) ->
  892. string_ast("{#", Context, TreeWalker);
  893. templatetag_ast("closecomment", Context, TreeWalker) ->
  894. string_ast("#}", Context, TreeWalker).
  895. widthratio_ast(Numerator, Denominator, Scale, Context, TreeWalker) ->
  896. {{NumAst, NumInfo}, TreeWalker1} = value_ast(Numerator, false, true, Context, TreeWalker),
  897. {{DenAst, DenInfo}, TreeWalker2} = value_ast(Denominator, false, true, Context, TreeWalker1),
  898. {{ScaleAst, ScaleInfo}, TreeWalker3} = value_ast(Scale, false, true, Context, TreeWalker2),
  899. {{format_number_ast(erl_syntax:application(
  900. erl_syntax:atom(erlydtl_runtime),
  901. erl_syntax:atom(widthratio),
  902. [NumAst, DenAst, ScaleAst])), merge_info(ScaleInfo, merge_info(NumInfo, DenInfo))},
  903. TreeWalker3}.
  904. binary_string(String) ->
  905. erl_syntax:binary([erl_syntax:binary_field(erl_syntax:integer(X)) || X <- String]).
  906. string_ast(String, #dtl_context{ binary_strings = true }, TreeWalker) when is_list(String) ->
  907. {{binary_string(String), #ast_info{}}, TreeWalker};
  908. string_ast(String, #dtl_context{ binary_strings = false }, TreeWalker) when is_list(String) ->
  909. {{erl_syntax:string(String), #ast_info{}}, TreeWalker}; %% less verbose AST, better for development and debugging
  910. string_ast(S, Context, TreeWalker) when is_atom(S) ->
  911. string_ast(atom_to_list(S), Context, TreeWalker).
  912. include_ast(File, ArgList, Scopes, Context, TreeWalker) ->
  913. FilePath = full_path(File, Context#dtl_context.doc_root),
  914. case parse(FilePath, Context) of
  915. {ok, InclusionParseTree, CheckSum} ->
  916. {NewScope, {ArgInfo, TreeWalker1}} = lists:mapfoldl(fun
  917. ({{identifier, _, LocalVarName}, Value}, {AstInfo1, TreeWalker1}) ->
  918. {{Ast, Info}, TreeWalker2} = value_ast(Value, false, false, Context, TreeWalker1),
  919. {{LocalVarName, Ast}, {merge_info(AstInfo1, Info), TreeWalker2}}
  920. end, {#ast_info{}, TreeWalker}, ArgList),
  921. {{BodyAst, BodyInfo}, TreeWalker2} = with_dependency({FilePath, CheckSum},
  922. body_ast(InclusionParseTree, Context#dtl_context{
  923. parse_trail = [FilePath | Context#dtl_context.parse_trail],
  924. local_scopes = [NewScope|Scopes]
  925. }, TreeWalker1)),
  926. {{BodyAst, merge_info(BodyInfo, ArgInfo)}, TreeWalker2};
  927. Err ->
  928. throw(Err)
  929. end.
  930. % include at run-time
  931. ssi_ast(FileName, Context, TreeWalker) ->
  932. {{Ast, Info}, TreeWalker1} = value_ast(FileName, true, true, Context, TreeWalker),
  933. {Mod, Fun} = Context#dtl_context.reader,
  934. {{erl_syntax:application(
  935. erl_syntax:atom(erlydtl_runtime),
  936. erl_syntax:atom(read_file),
  937. [erl_syntax:atom(Mod), erl_syntax:atom(Fun), erl_syntax:string(Context#dtl_context.doc_root), Ast]), Info}, TreeWalker1}.
  938. filter_tag_ast(FilterList, Contents, Context, TreeWalker) ->
  939. {{InnerAst, Info}, TreeWalker1} = body_ast(Contents, Context#dtl_context{auto_escape = did}, TreeWalker),
  940. {{FilteredAst, FilteredInfo}, TreeWalker2} = lists:foldl(fun
  941. ([{identifier, _, 'escape'}], {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
  942. {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{safe = true}};
  943. ([{identifier, _, 'safe'}], {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
  944. {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{safe = true}};
  945. ([{identifier, _, 'safeseq'}], {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
  946. {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{safe = true}};
  947. (Filter, {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
  948. {{Ast, AstInfo}, TW} = filter_ast1(Filter, AstAcc, Context, TreeWalkerAcc),
  949. {{Ast, merge_info(InfoAcc, AstInfo)}, TW}
  950. end, {{erl_syntax:application(
  951. erl_syntax:atom(erlang),
  952. erl_syntax:atom(iolist_to_binary),
  953. [InnerAst]), Info}, TreeWalker1}, FilterList),
  954. EscapedAst = case search_for_escape_filter(lists:reverse(FilterList), Context) of
  955. on ->
  956. erl_syntax:application(
  957. erl_syntax:atom(erlydtl_filters),
  958. erl_syntax:atom(force_escape),
  959. [FilteredAst]);
  960. _ ->
  961. FilteredAst
  962. end,
  963. {{EscapedAst, FilteredInfo}, TreeWalker2}.
  964. search_for_escape_filter(FilterList, #dtl_context{auto_escape = on}) ->
  965. search_for_safe_filter(FilterList);
  966. search_for_escape_filter(_, #dtl_context{auto_escape = did}) ->
  967. off;
  968. search_for_escape_filter([[{identifier, _, 'escape'}]|Rest], _Context) ->
  969. search_for_safe_filter(Rest);
  970. search_for_escape_filter([_|Rest], Context) ->
  971. search_for_escape_filter(Rest, Context);
  972. search_for_escape_filter([], _Context) ->
  973. off.
  974. search_for_safe_filter([[{identifier, _, 'safe'}]|_]) ->
  975. off;
  976. search_for_safe_filter([[{identifier, _, 'safeseq'}]|_]) ->
  977. off;
  978. search_for_safe_filter([_|Rest]) ->
  979. search_for_safe_filter(Rest);
  980. search_for_safe_filter([]) ->
  981. on.
  982. filter_ast(Variable, Filter, Context, TreeWalker) ->
  983. % the escape filter is special; it is always applied last, so we have to go digging for it
  984. % AutoEscape = 'did' means we (will have) decided whether to escape the current variable,
  985. % so don't do any more escaping
  986. {{UnescapedAst, Info}, TreeWalker2} = filter_ast_noescape(Variable, Filter,
  987. Context#dtl_context{auto_escape = did}, TreeWalker),
  988. EscapedAst = case search_for_escape_filter(Variable, Filter, Context) of
  989. on ->
  990. erl_syntax:application(
  991. erl_syntax:atom(erlydtl_filters),
  992. erl_syntax:atom(force_escape),
  993. [UnescapedAst]);
  994. _ ->
  995. UnescapedAst
  996. end,
  997. {{EscapedAst, Info}, TreeWalker2}.
  998. filter_ast_noescape(Variable, [{identifier, _, 'escape'}], Context, TreeWalker) ->
  999. value_ast(Variable, true, false, Context, TreeWalker#treewalker{safe = true});
  1000. filter_ast_noescape(Variable, [{identifier, _, 'safe'}], Context, TreeWalker) ->
  1001. value_ast(Variable, true, false, Context, TreeWalker#treewalker{safe = true});
  1002. filter_ast_noescape(Variable, [{identifier, _, 'safeseq'}], Context, TreeWalker) ->
  1003. value_ast(Variable, true, false, Context, TreeWalker#treewalker{safe = true});
  1004. filter_ast_noescape(Variable, Filter, Context, TreeWalker) ->
  1005. {{VariableAst, Info1}, TreeWalker2} = value_ast(Variable, true, false, Context, TreeWalker),
  1006. {{VarValue, Info2}, TreeWalker3} = filter_ast1(Filter, VariableAst, Context, TreeWalker2),
  1007. {{VarValue, merge_info(Info1, Info2)}, TreeWalker3}.
  1008. filter_ast1([{identifier, _, Name}, {string_literal, _, ArgName}], VariableAst,
  1009. #dtl_context{ binary_strings = true } = Context, TreeWalker) ->
  1010. filter_ast2(Name, VariableAst, [binary_string(unescape_string_literal(ArgName))], #ast_info{}, Context, TreeWalker);
  1011. filter_ast1([{identifier, _, Name}, {string_literal, _, ArgName}], VariableAst,
  1012. #dtl_context{ binary_strings = false } = Context, TreeWalker) ->
  1013. filter_ast2(Name, VariableAst, [erl_syntax:string(unescape_string_literal(ArgName))], #ast_info{}, Context, TreeWalker);
  1014. filter_ast1([{identifier, _, Name}, {number_literal, _, ArgName}], VariableAst, Context, TreeWalker) ->
  1015. filter_ast2(Name, VariableAst, [erl_syntax:integer(list_to_integer(ArgName))], #ast_info{}, Context, TreeWalker);
  1016. filter_ast1([{identifier, _, Name}, ArgVariable], VariableAst, Context, TreeWalker) ->
  1017. {{ArgAst, ArgInfo}, TreeWalker2} = resolve_variable_ast(ArgVariable, Context, TreeWalker, false),
  1018. filter_ast2(Name, VariableAst, [ArgAst], ArgInfo, Context, TreeWalker2);
  1019. filter_ast1([{identifier, _, Name}], VariableAst, Context, TreeWalker) ->
  1020. filter_ast2(Name, VariableAst, [], #ast_info{}, Context, TreeWalker).
  1021. filter_ast2(Name, VariableAst, [], VarInfo, #dtl_context{ filter_modules = [Module|Rest] } = Context, TreeWalker) ->
  1022. case lists:member({Name, 1}, Module:module_info(exports)) of
  1023. true ->
  1024. {{erl_syntax:application(
  1025. erl_syntax:atom(Module), erl_syntax:atom(Name),
  1026. [VariableAst]),
  1027. VarInfo},
  1028. TreeWalker};
  1029. false ->
  1030. filter_ast2(Name, VariableAst, [], VarInfo, Context#dtl_context{ filter_modules = Rest }, TreeWalker)
  1031. end;
  1032. filter_ast2(Name, VariableAst, [Arg], VarInfo, #dtl_context{ filter_modules = [Module|Rest] } = Context, TreeWalker) ->
  1033. case lists:member({Name, 2}, Module:module_info(exports)) of
  1034. true ->
  1035. {{erl_syntax:application(
  1036. erl_syntax:atom(Module), erl_syntax:atom(Name),
  1037. [VariableAst, Arg]),
  1038. VarInfo},
  1039. TreeWalker};
  1040. false ->
  1041. filter_ast2(Name, VariableAst, [Arg], VarInfo, Context#dtl_context{ filter_modules = Rest }, TreeWalker)
  1042. end.
  1043. search_for_escape_filter(Variable, Filter, #dtl_context{auto_escape = on}) ->
  1044. search_for_safe_filter(Variable, Filter);
  1045. search_for_escape_filter(_, _, #dtl_context{auto_escape = did}) ->
  1046. off;
  1047. search_for_escape_filter(Variable, [{identifier, _, 'escape'}] = Filter, _Context) ->
  1048. search_for_safe_filter(Variable, Filter);
  1049. search_for_escape_filter({apply_filter, Variable, Filter}, _, Context) ->
  1050. search_for_escape_filter(Variable, Filter, Context);
  1051. search_for_escape_filter(_Variable, _Filter, _Context) ->
  1052. off.
  1053. search_for_safe_filter(_, [{identifier, _, 'safe'}]) ->
  1054. off;
  1055. search_for_safe_filter(_, [{identifier, _, 'safeseq'}]) ->
  1056. off;
  1057. search_for_safe_filter({apply_filter, Variable, Filter}, _) ->
  1058. search_for_safe_filter(Variable, Filter);
  1059. search_for_safe_filter(_Variable, _Filter) ->
  1060. on.
  1061. finder_function(true) -> {erlydtl_runtime, fetch_value};
  1062. finder_function(false) -> {erlydtl_runtime, find_value}.
  1063. finder_function(EmptyIfUndefined, Context) ->
  1064. case call_extension(Context, finder_function, [EmptyIfUndefined]) of
  1065. undefined -> finder_function(EmptyIfUndefined);
  1066. Result -> Result
  1067. end.
  1068. resolve_variable_ast({extension, Tag}, Context, TreeWalker, _) ->
  1069. extension_ast(Tag, Context, TreeWalker);
  1070. resolve_variable_ast(VarTuple, Context, TreeWalker, EmptyIfUndefined)
  1071. when is_boolean(EmptyIfUndefined) ->
  1072. resolve_variable_ast(VarTuple, Context, TreeWalker, finder_function(EmptyIfUndefined, Context));
  1073. resolve_variable_ast(VarTuple, Context, TreeWalker, FinderFunction) ->
  1074. resolve_variable_ast1(VarTuple, Context, TreeWalker, FinderFunction).
  1075. resolve_variable_ast1({attribute, {{identifier, {Row, Col}, AttrName}, Variable}}, Context, TreeWalker, FinderFunction) ->
  1076. {{VarAst, VarInfo}, TreeWalker1} = resolve_variable_ast(Variable, Context, TreeWalker, FinderFunction),
  1077. FileNameAst = case Context#dtl_context.parse_trail of
  1078. [] -> erl_syntax:atom(undefined);
  1079. [H|_] -> erl_syntax:string(H)
  1080. end,
  1081. {Runtime, Finder} = FinderFunction,
  1082. {{erl_syntax:application(
  1083. erl_syntax:atom(Runtime),
  1084. erl_syntax:atom(Finder),
  1085. [erl_syntax:atom(AttrName), VarAst, FileNameAst,
  1086. erl_syntax:tuple([erl_syntax:integer(Row), erl_syntax:integer(Col)])
  1087. ]),
  1088. VarInfo},
  1089. TreeWalker1};
  1090. resolve_variable_ast1({variable, {identifier, {Row, Col}, VarName}}, Context, TreeWalker, FinderFunction) ->
  1091. VarValue = case resolve_scoped_variable_ast(VarName, Context) of
  1092. undefined ->
  1093. FileNameAst = case Context#dtl_context.parse_trail of
  1094. [] -> erl_syntax:atom(undefined);
  1095. [H|_] -> erl_syntax:string(H)
  1096. end,
  1097. {Runtime, Finder} = FinderFunction,
  1098. erl_syntax:application(
  1099. erl_syntax:atom(Runtime), erl_syntax:atom(Finder),
  1100. [erl_syntax:atom(VarName), erl_syntax:variable("_Variables"), FileNameAst,
  1101. erl_syntax:tuple([erl_syntax:integer(Row), erl_syntax:integer(Col)])
  1102. ]);
  1103. Val ->
  1104. Val
  1105. end,
  1106. {{VarValue, #ast_info{ var_names=[VarName] }}, TreeWalker}.
  1107. resolve_scoped_variable_ast(VarName, Context) ->
  1108. lists:foldl(fun(Scope, Value) ->
  1109. case Value of
  1110. undefined -> proplists:get_value(VarName, Scope);
  1111. _ -> Value
  1112. end
  1113. end, undefined, Context#dtl_context.local_scopes).
  1114. format(Ast, Context, TreeWalker) ->
  1115. auto_escape(format_number_ast(Ast), Context, TreeWalker).
  1116. format_number_ast(Ast) ->
  1117. erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(format_number),
  1118. [Ast]).
  1119. auto_escape(Value, _, #treewalker{safe = true}) ->
  1120. Value;
  1121. auto_escape(Value, #dtl_context{auto_escape = on}, _) ->
  1122. erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(force_escape), [Value]);
  1123. auto_escape(Value, _, _) ->
  1124. Value.
  1125. firstof_ast(Vars, Context, TreeWalker) ->
  1126. body_ast([lists:foldr(fun
  1127. ({L, _, _}=Var, []) when L=:=string_literal;L=:=number_literal ->
  1128. Var;
  1129. ({L, _, _}, _) when L=:=string_literal;L=:=number_literal ->
  1130. erlang:error(errbadliteral);
  1131. (Var, []) ->
  1132. {'ifelse', Var, [Var], []};
  1133. (Var, Acc) ->
  1134. {'ifelse', Var, [Var], [Acc]} end,
  1135. [], Vars)], Context, TreeWalker).
  1136. ifelse_ast(Expression, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
  1137. Info = merge_info(IfContentsInfo, ElseContentsInfo),
  1138. {{Ast, ExpressionInfo}, TreeWalker1} = value_ast(Expression, false, false, Context, TreeWalker),
  1139. {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(is_true), [Ast]),
  1140. [erl_syntax:clause([erl_syntax:atom(true)], none,
  1141. [IfContentsAst]),
  1142. erl_syntax:clause([erl_syntax:underscore()], none,
  1143. [ElseContentsAst])
  1144. ]), merge_info(ExpressionInfo, Info)}, TreeWalker1}.
  1145. with_ast(ArgList, Contents, Context, TreeWalker) ->
  1146. {ArgAstList, {ArgInfo, TreeWalker1}} = lists:mapfoldl(fun
  1147. ({{identifier, _, _LocalVarName}, Value}, {AstInfo1, TreeWalker1}) ->
  1148. {{Ast, Info}, TreeWalker2} = value_ast(Value, false, false, Context, TreeWalker1),
  1149. {Ast, {merge_info(AstInfo1, Info), TreeWalker2}}
  1150. end, {#ast_info{}, TreeWalker}, ArgList),
  1151. NewScope = lists:map(fun({{identifier, _, LocalVarName}, _Value}) ->
  1152. {LocalVarName, erl_syntax:variable(lists:concat(["Var_", LocalVarName]))}
  1153. end, ArgList),
  1154. {{InnerAst, InnerInfo}, TreeWalker2} = body_ast(Contents,
  1155. Context#dtl_context{local_scopes = [NewScope|Context#dtl_context.local_scopes]}, TreeWalker1),
  1156. {{erl_syntax:application(
  1157. erl_syntax:fun_expr([
  1158. erl_syntax:clause(lists:map(fun({_, Var}) -> Var end, NewScope), none,
  1159. [InnerAst])]), ArgAstList), merge_info(ArgInfo, InnerInfo)}, TreeWalker2}.
  1160. regroup_ast(ListVariable, GrouperVariable, LocalVarName, Contents, Context, TreeWalker) ->
  1161. {{ListAst, ListInfo}, TreeWalker1} = value_ast(ListVariable, false, true, Context, TreeWalker),
  1162. NewScope = [{LocalVarName, erl_syntax:variable(lists:concat(["Var_", LocalVarName]))}],
  1163. {{InnerAst, InnerInfo}, TreeWalker2} = body_ast(Contents,
  1164. Context#dtl_context{ local_scopes = [NewScope|Context#dtl_context.local_scopes] }, TreeWalker1),
  1165. Ast = {erl_syntax:application(
  1166. erl_syntax:fun_expr([
  1167. erl_syntax:clause([erl_syntax:variable(lists:concat(["Var_", LocalVarName]))], none,
  1168. [InnerAst])]),
  1169. [erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(regroup),
  1170. [ListAst, regroup_filter(GrouperVariable,[])])]), merge_info(ListInfo, InnerInfo)},
  1171. {Ast,TreeWalker2}.
  1172. regroup_filter({attribute,{{identifier,_,Ident},Next}},Acc) ->
  1173. regroup_filter(Next,[erl_syntax:atom(Ident)|Acc]);
  1174. regroup_filter({variable,{identifier,_,Var}},Acc) ->
  1175. erl_syntax:list([erl_syntax:atom(Var)|Acc]).
  1176. for_loop_ast(IteratorList, LoopValue, IsReversed, Contents, {EmptyContentsAst, EmptyContentsInfo}, Context, TreeWalker) ->
  1177. Vars = lists:map(fun({identifier, _, Iterator}) ->
  1178. erl_syntax:variable(lists:concat(["Var_", Iterator]))
  1179. end, IteratorList),
  1180. {{InnerAst, Info}, TreeWalker1} = body_ast(Contents,
  1181. Context#dtl_context{local_scopes = [
  1182. [{'forloop', erl_syntax:variable("Counters")} | lists:map(
  1183. fun({identifier, _, Iterator}) ->
  1184. {Iterator, erl_syntax:variable(lists:concat(["Var_", Iterator]))}
  1185. end, IteratorList)] | Context#dtl_context.local_scopes]}, TreeWalker),
  1186. CounterAst = erl_syntax:application(erl_syntax:atom(erlydtl_runtime),
  1187. erl_syntax:atom(increment_counter_stats), [erl_syntax:variable("Counters")]),
  1188. {{LoopValueAst, LoopValueInfo}, TreeWalker2} = value_ast(LoopValue, false, true, Context, TreeWalker1),
  1189. LoopValueAst0 = case IsReversed of
  1190. true -> erl_syntax:application(erl_syntax:atom(lists), erl_syntax:atom(reverse), [LoopValueAst]);
  1191. false -> LoopValueAst
  1192. end,
  1193. CounterVars0 = case resolve_scoped_variable_ast('forloop', Context) of
  1194. undefined ->
  1195. erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(init_counter_stats), [LoopValueAst0]);
  1196. Value ->
  1197. erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(init_counter_stats), [LoopValueAst0, Value])
  1198. end,
  1199. {{erl_syntax:case_expr(
  1200. erl_syntax:application(
  1201. erl_syntax:atom('erlydtl_runtime'), erl_syntax:atom('forloop'),
  1202. [erl_syntax:fun_expr([
  1203. erl_syntax:clause([erl_syntax:tuple(Vars), erl_syntax:variable("Counters")], none,
  1204. [erl_syntax:tuple([InnerAst, CounterAst])]),
  1205. erl_syntax:clause(case Vars of [H] -> [H, erl_syntax:variable("Counters")];
  1206. _ -> [erl_syntax:list(Vars), erl_syntax:variable("Counters")] end, none,
  1207. [erl_syntax:tuple([InnerAst, CounterAst])])
  1208. ]),
  1209. CounterVars0, LoopValueAst0]),
  1210. [erl_syntax:clause(
  1211. [erl_syntax:tuple([erl_syntax:underscore(),
  1212. erl_syntax:list([erl_syntax:tuple([erl_syntax:atom(counter), erl_syntax:integer(1)])],
  1213. erl_syntax:underscore())])],
  1214. none, [EmptyContentsAst]),
  1215. erl_syntax:clause(
  1216. [erl_syntax:tuple([erl_syntax:variable("L"), erl_syntax:underscore()])],
  1217. none, [erl_syntax:variable("L")])]
  1218. ),
  1219. merge_info(merge_info(Info, EmptyContentsInfo), LoopValueInfo)
  1220. }, TreeWalker2}.
  1221. ifchanged_values_ast(Values, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
  1222. Info = merge_info(IfContentsInfo, ElseContentsInfo),
  1223. ValueAstFun = fun(Expr, {LTreeWalker, LInfo, Acc}) ->
  1224. {{EAst, EInfo}, ETw} = value_ast(Expr, false, true, Context, LTreeWalker),
  1225. {ETw, merge_info(LInfo, EInfo), [erl_syntax:tuple([erl_syntax:integer(erlang:phash2(Expr)), EAst])|Acc]} end,
  1226. {TreeWalker1, MergedInfo, Changed} = lists:foldl(ValueAstFun, {TreeWalker, Info, []}, Values),
  1227. {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(ifchanged), [erl_syntax:list(Changed)]),
  1228. [erl_syntax:clause([erl_syntax:atom(true)], none,
  1229. [IfContentsAst]),
  1230. erl_syntax:clause([erl_syntax:underscore()], none,
  1231. [ElseContentsAst])
  1232. ]), MergedInfo}, TreeWalker1}.
  1233. ifchanged_contents_ast(Contents, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, _Context, TreeWalker) ->
  1234. Info = merge_info(IfContentsInfo, ElseContentsInfo),
  1235. Key = erl_syntax:integer(erlang:phash2(Contents)),
  1236. {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(ifchanged), [erl_syntax:list([erl_syntax:tuple([Key, IfContentsAst])])]),
  1237. [erl_syntax:clause([erl_syntax:atom(true)], none,
  1238. [IfContentsAst]),
  1239. erl_syntax:clause([erl_syntax:underscore()], none,
  1240. [ElseContentsAst])
  1241. ]), Info}, TreeWalker}.
  1242. cycle_ast(Names, Context, TreeWalker) ->
  1243. {NamesTuple, VarNames} = lists:mapfoldl(fun
  1244. ({string_literal, _, Str}, VarNamesAcc) ->
  1245. {{S, _}, _} = string_ast(unescape_string_literal(Str), Context, TreeWalker),
  1246. {S, VarNamesAcc};
  1247. ({variable, _}=Var, VarNamesAcc) ->
  1248. {{V, #ast_info{ var_names=[VarName] }}, _} = resolve_variable_ast(Var, Context, TreeWalker, true),
  1249. {V, [VarName|VarNamesAcc]};
  1250. ({number_literal, _, Num}, VarNamesAcc) ->
  1251. {format(erl_syntax:integer(Num), Context, TreeWalker), VarNamesAcc};
  1252. (_, VarNamesAcc) ->
  1253. {[], VarNamesAcc}
  1254. end, [], Names),
  1255. {{erl_syntax:application(
  1256. erl_syntax:atom('erlydtl_runtime'), erl_syntax:atom('cycle'),
  1257. [erl_syntax:tuple(NamesTuple), erl_syntax:variable("Counters")]), #ast_info{ var_names = VarNames }}, TreeWalker}.
  1258. %% Older Django templates treat cycle with comma-delimited elements as strings
  1259. cycle_compat_ast(Names, Context, TreeWalker) ->
  1260. NamesTuple = lists:map(fun
  1261. ({identifier, _, X}) ->
  1262. {{S, _}, _} = string_ast(X, Context, TreeWalker),
  1263. S
  1264. end, Names),
  1265. {{erl_syntax:application(
  1266. erl_syntax:atom('erlydtl_runtime'), erl_syntax:atom('cycle'),
  1267. [erl_syntax:tuple(NamesTuple), erl_syntax:variable("Counters")]), #ast_info{}}, TreeWalker}.
  1268. now_ast(FormatString, Context, TreeWalker) ->
  1269. % Note: we can't use unescape_string_literal here
  1270. % because we want to allow escaping in the format string.
  1271. % We only want to remove the surrounding escapes,
  1272. % i.e. \"foo\" becomes "foo"
  1273. UnescapeOuter = string:strip(FormatString, both, 34),
  1274. {{StringAst, Info}, TreeWalker1} = string_ast(UnescapeOuter, Context, TreeWalker),
  1275. {{erl_syntax:application(
  1276. erl_syntax:atom(erlydtl_dateformat),
  1277. erl_syntax:atom(format),
  1278. [StringAst]), Info}, TreeWalker1}.
  1279. spaceless_ast(Contents, Context, TreeWalker) ->
  1280. {{Ast, Info}, TreeWalker1} = body_ast(Contents, Context, TreeWalker),
  1281. {{erl_syntax:application(
  1282. erl_syntax:atom(erlydtl_runtime),
  1283. erl_syntax:atom(spaceless),
  1284. [Ast]), Info}, TreeWalker1}.
  1285. unescape_string_literal(String) ->
  1286. unescape_string_literal(string:strip(String, both, 34), [], noslash).
  1287. unescape_string_literal([], Acc, noslash) ->
  1288. lists:reverse(Acc);
  1289. unescape_string_literal([$\\ | Rest], Acc, noslash) ->
  1290. unescape_string_literal(Rest, Acc, slash);
  1291. unescape_string_literal([C | Rest], Acc, noslash) ->
  1292. unescape_string_literal(Rest, [C | Acc], noslash);
  1293. unescape_string_literal("n" ++ Rest, Acc, slash) ->
  1294. unescape_string_literal(Rest, [$\n | Acc], noslash);
  1295. unescape_string_literal("r" ++ Rest, Acc, slash) ->
  1296. unescape_string_literal(Rest, [$\r | Acc], noslash);
  1297. unescape_string_literal("t" ++ Rest, Acc, slash) ->
  1298. unescape_string_literal(Rest, [$\t | Acc], noslash);
  1299. unescape_string_literal([C | Rest], Acc, slash) ->
  1300. unescape_string_literal(Rest, [C | Acc], noslash).
  1301. full_path(File, DocRoot) ->
  1302. case filename:absname(File) of
  1303. File -> File;
  1304. _ -> filename:join([DocRoot, File])
  1305. end.
  1306. %%-------------------------------------------------------------------
  1307. %% Custom tags
  1308. %%-------------------------------------------------------------------
  1309. interpret_args(Args, Context, TreeWalker) ->
  1310. lists:foldr(
  1311. fun ({{identifier, _, Key}, {trans, StringLiteral}}, {{ArgsAcc, AstInfoAcc}, TreeWalkerAcc}) ->
  1312. {{TransAst, TransAstInfo}, TreeWalker0} = translated_ast(StringLiteral, Context, TreeWalkerAcc),
  1313. {{[erl_syntax:tuple([erl_syntax:atom(Key), TransAst])|ArgsAcc], merge_info(TransAstInfo, AstInfoAcc)}, TreeWalker0};
  1314. ({{identifier, _, Key}, Value}, {{ArgsAcc, AstInfoAcc}, TreeWalkerAcc}) ->
  1315. {{Ast0, AstInfo0}, TreeWalker0} = value_ast(Value, false, false, Context, TreeWalkerAcc),
  1316. {{[erl_syntax:tuple([erl_syntax:atom(Key), Ast0])|ArgsAcc], merge_info(AstInfo0, AstInfoAcc)}, TreeWalker0};
  1317. ({extension, Tag}, {{ArgsAcc, AstInfoAcc}, TreeWalkerAcc}) ->
  1318. {{ExtAst, ExtInfo}, ExtTreeWalker} = extension_ast(Tag, Context, TreeWalkerAcc),
  1319. {{[ExtAst|ArgsAcc], merge_info(ExtInfo, AstInfoAcc)}, ExtTreeWalker}
  1320. end, {{[], #ast_info{}}, TreeWalker}, Args).
  1321. tag_ast(Name, Args, Context, TreeWalker) ->
  1322. {{InterpretedArgs, AstInfo1}, TreeWalker1} = interpret_args(Args, Context, TreeWalker),
  1323. {RenderAst, RenderInfo} = custom_tags_modules_ast(Name, InterpretedArgs, Context),
  1324. {{RenderAst, merge_info(AstInfo1, RenderInfo)}, TreeWalker1}.
  1325. custom_tags_modules_ast(Name, InterpretedArgs, #dtl_context{ custom_tags_modules = [], is_compiling_dir = false }) ->
  1326. {erl_syntax:application(none, erl_syntax:atom(render_tag),
  1327. [erl_syntax:atom(Name), erl_syntax:list(InterpretedArgs),
  1328. erl_syntax:variable("RenderOptions")]),
  1329. #ast_info{custom_tags = [Name]}};
  1330. custom_tags_modules_ast(Name, InterpretedArgs, #dtl_context{ custom_tags_modules = [], is_compiling_dir = true, module = Module }) ->
  1331. {erl_syntax:application(erl_syntax:atom(Module), erl_syntax:atom(Name),
  1332. [erl_syntax:list(InterpretedArgs), erl_syntax:variable("RenderOptions")]),
  1333. #ast_info{ custom_tags = [Name] }};
  1334. custom_tags_modules_ast(Name, InterpretedArgs, #dtl_context{ custom_tags_modules = [Module|Rest] } = Context) ->
  1335. try lists:max([I || {N,I} <- Module:module_info(exports), N =:= Name]) of
  1336. 2 ->
  1337. {erl_syntax:application(erl_syntax:atom(Module), erl_syntax:atom(Name),
  1338. [erl_syntax:list(InterpretedArgs),
  1339. erl_syntax:variable("RenderOptions")]), #ast_info{}};
  1340. 1 ->
  1341. {erl_syntax:application(erl_syntax:atom(Module), erl_syntax:atom(Name),
  1342. [erl_syntax:list(InterpretedArgs)]), #ast_info{}};
  1343. I ->
  1344. throw({unsupported_custom_tag_fun, {Module, Name, I}})
  1345. catch _:function_clause ->
  1346. custom_tags_modules_ast(Name, InterpretedArgs,
  1347. Context#dtl_context{ custom_tags_modules = Rest })
  1348. end.
  1349. print(true, Fmt, Args) ->
  1350. io:format(Fmt, Args);
  1351. print(_, _Fmt, _Args) ->
  1352. ok.
  1353. call_ast(Module, TreeWalkerAcc) ->
  1354. call_ast(Module, erl_syntax:variable("_Variables"), #ast_info{}, TreeWalkerAcc).
  1355. call_with_ast(Module, Variable, Context, TreeWalker) ->
  1356. {{VarAst, VarInfo}, TreeWalker2} = resolve_variable_ast(Variable, Context, TreeWalker, false),
  1357. call_ast(Module, VarAst, VarInfo, TreeWalker2).
  1358. call_ast(Module, Variable, AstInfo, TreeWalker) ->
  1359. AppAst = erl_syntax:application(
  1360. erl_syntax:atom(Module),
  1361. erl_syntax:atom(render),
  1362. [Variable, erl_syntax:variable("RenderOptions")]),
  1363. RenderedAst = erl_syntax:variable("Rendered"),
  1364. OkAst = erl_syntax:clause(
  1365. [erl_syntax:tuple([erl_syntax:atom(ok), RenderedAst])],
  1366. none,
  1367. [RenderedAst]),
  1368. ReasonAst = erl_syntax:variable("Reason"),
  1369. ErrStrAst = erl_syntax:application(
  1370. erl_syntax:atom(io_lib),
  1371. erl_syntax:atom(format),
  1372. [erl_syntax:string("error: ~p"), erl_syntax:list([ReasonAst])]),
  1373. ErrorAst = erl_syntax:clause(
  1374. [erl_syntax:tuple([erl_syntax:atom(error), ReasonAst])],
  1375. none,
  1376. [ErrStrAst]),
  1377. CallAst = erl_syntax:case_expr(AppAst, [OkAst, ErrorAst]),
  1378. with_dependencies(Module:dependencies(), {{CallAst, AstInfo}, TreeWalker}).