/src/erlydtl/erlydtl_compiler.erl

https://code.google.com/p/zotonic/ · Erlang · 1632 lines · 1372 code · 168 blank · 92 comment · 20 complexity · 7f76de8eddde3f0e2f21cd53af559fa5 MD5 · raw file

Large files are truncated click here to view the full file

  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. %%%-------------------------------------------------------------------
  35. %%% Adapted and expanded for Zotonic by Marc Worrell <marc@worrell.nl>
  36. %%%-------------------------------------------------------------------
  37. -module(erlydtl_compiler).
  38. -author('rsaccon@gmail.com').
  39. -author('emmiller@gmail.com').
  40. -author('marc@worrell.nl').
  41. -include_lib("zotonic.hrl").
  42. %% --------------------------------------------------------------------
  43. %% Definitions
  44. %% --------------------------------------------------------------------
  45. -export([compile/3, compile/4, compile/5, parse/1]).
  46. -record(dtl_context, {
  47. local_scopes = [],
  48. block_dict = dict:new(),
  49. auto_escape = off,
  50. parse_trail = [],
  51. extends_trail = [],
  52. block_trail = [],
  53. vars = [],
  54. custom_tags_dir = [],
  55. reader = {file, read_file},
  56. finder = undefined,
  57. module = [],
  58. compiler_options = [verbose, report_errors],
  59. force_recompile = false,
  60. z_context = undefined}).
  61. -record(ast_info, {
  62. dependencies = [],
  63. var_names = [],
  64. pre_render_asts = []}).
  65. -record(treewalker, {
  66. counter = 0,
  67. has_auto_id = false,
  68. custom_tags = []}).
  69. compile(Binary, Module, ZContext) when is_binary(Binary) ->
  70. compile(Binary, Module, [], ZContext);
  71. compile(File, Module, ZContext) ->
  72. compile(File, File, Module, [], ZContext).
  73. compile(Binary, Module, Options, ZContext) when is_binary(Binary) ->
  74. compile(Binary, [], Module, Options, ZContext);
  75. compile(File, Module, Options, ZContext) ->
  76. compile(File, filename:basename(File), Module, Options, ZContext).
  77. compile(Binary, BaseFile, Module, Options, ZContext) when is_binary(Binary) ->
  78. TemplateResetCounter = proplists:get_value(template_reset_counter, Options, 0),
  79. case parse(Binary) of
  80. {ok, DjangoParseTree} ->
  81. case compile_to_binary( BaseFile,
  82. DjangoParseTree,
  83. init_dtl_context(BaseFile, BaseFile, Module, Options, ZContext),
  84. TemplateResetCounter) of
  85. {ok, Module1, _Bin} ->
  86. {ok, Module1};
  87. Err ->
  88. Err
  89. end;
  90. Err ->
  91. Err
  92. end;
  93. compile(File, BaseFile, Module, Options, ZContext) ->
  94. Context = init_dtl_context(File, BaseFile, Module, Options, ZContext),
  95. TemplateResetCounter = proplists:get_value(template_reset_counter, Options, 0),
  96. case parse(File, Context) of
  97. {ok, DjangoParseTree} ->
  98. case compile_to_binary(File, DjangoParseTree, Context, TemplateResetCounter) of
  99. {ok, Module1, _Bin} ->
  100. {ok, Module1};
  101. Err ->
  102. Err
  103. end;
  104. {error, {ErrLoc, ErrModule, ["syntax error before: " = ErrMsg, [$[,Text,$]]]}} when is_list(Text) ->
  105. Text1 = [ list_to_integer(C) || C <- Text, is_list(C) ],
  106. {error, {ErrLoc, ErrModule, ErrMsg ++ Text1}};
  107. {error, {ErrLoc, ErrModule, ErrMsg}} ->
  108. {error, {ErrLoc, ErrModule, lists:flatten(ErrMsg)}};
  109. Err ->
  110. Err
  111. end.
  112. %%====================================================================
  113. %% Internal functions
  114. %%====================================================================
  115. compile_to_binary(File, DjangoParseTree, Context, TemplateResetCounter) ->
  116. try body_ast(DjangoParseTree, Context, #treewalker{}) of
  117. {{Ast, Info}, TreeWalker} ->
  118. case compile:forms(forms(File, Context#dtl_context.module, Ast, Info, Context, TreeWalker, TemplateResetCounter),
  119. Context#dtl_context.compiler_options) of
  120. {ok, Module1, Bin} ->
  121. code:purge(Module1),
  122. case code:load_binary(Module1, atom_to_list(Module1) ++ ".erl", Bin) of
  123. {module, _} -> {ok, Module1, Bin};
  124. _ -> {error, lists:concat(["code reload failed: ", Module1])}
  125. end;
  126. error ->
  127. {error, lists:concat(["compilation failed: ", File])};
  128. OtherError ->
  129. OtherError
  130. end
  131. catch
  132. throw:Error -> Error
  133. end.
  134. init_dtl_context(File, BaseFile, Module, Options, ZContext) when is_list(Module) ->
  135. init_dtl_context(File, BaseFile, list_to_atom(Module), Options, ZContext);
  136. init_dtl_context(File, BaseFile, Module, Options, ZContext) ->
  137. Ctx = #dtl_context{},
  138. #dtl_context{
  139. local_scopes = [ [{'$autoid', erl_syntax:variable("AutoId_"++z_ids:identifier())}] ],
  140. parse_trail = [File],
  141. extends_trail = [BaseFile],
  142. module = Module,
  143. custom_tags_dir = proplists:get_value(custom_tags_dir, Options, Ctx#dtl_context.custom_tags_dir),
  144. vars = proplists:get_value(vars, Options, Ctx#dtl_context.vars),
  145. reader = proplists:get_value(reader, Options, Ctx#dtl_context.reader),
  146. finder = proplists:get_value(finder, Options, Ctx#dtl_context.finder),
  147. compiler_options = proplists:get_value(compiler_options, Options, Ctx#dtl_context.compiler_options),
  148. force_recompile = proplists:get_value(force_recompile, Options, Ctx#dtl_context.force_recompile),
  149. z_context = ZContext}.
  150. parse(File, Context) ->
  151. {M,F} = Context#dtl_context.reader,
  152. case catch M:F(File) of
  153. {ok, Data} ->
  154. case scan_parse(File, Data) of
  155. {ok, Val} ->
  156. {ok, Val};
  157. Err ->
  158. Err
  159. end;
  160. Error ->
  161. {error, io_lib:format("reading ~p failed (~p)", [File, Error])}
  162. end.
  163. % Used for parsing tests
  164. parse(Data) when is_binary(Data) ->
  165. scan_parse("string", Data).
  166. scan_parse(SourceRef, Data) ->
  167. case erlydtl_scanner:scan(SourceRef, binary_to_list(Data)) of
  168. {ok, Tokens} ->
  169. erlydtl_parser:parse(Tokens);
  170. Err ->
  171. Err
  172. end.
  173. forms(File, Module, BodyAst, BodyInfo, Context, TreeWalker, TemplateResetCounter) ->
  174. TemplateResetCounterFunctionAst = erl_syntax:function(
  175. erl_syntax:atom(template_reset_counter),
  176. [ erl_syntax:clause(
  177. [],
  178. none,
  179. [erl_syntax:integer(TemplateResetCounter)]
  180. )
  181. ]),
  182. TransTableFunctionAst = erl_syntax:function(
  183. erl_syntax:atom(trans_table),
  184. [ erl_syntax:clause(
  185. [],
  186. none,
  187. [erl_syntax:abstract(z_trans_server:table(Context#dtl_context.z_context))]
  188. )
  189. ]),
  190. Function2 = erl_syntax:application(none, erl_syntax:atom(render2),
  191. [erl_syntax:variable("Variables"), erl_syntax:variable("ZpContext")]),
  192. ClauseOk = erl_syntax:clause([erl_syntax:variable("Val")], none,
  193. [erl_syntax:tuple([erl_syntax:atom(ok), erl_syntax:variable("Val")])]),
  194. ClauseCatch = erl_syntax:clause([erl_syntax:variable("Err")], none,
  195. [erl_syntax:tuple([erl_syntax:atom(error), erl_syntax:variable("Err")])]),
  196. Render2FunctionAst = erl_syntax:function(erl_syntax:atom(render),
  197. [erl_syntax:clause([erl_syntax:variable("Variables"), erl_syntax:variable("ZpContext")], none,
  198. [erl_syntax:try_expr([Function2], [ClauseOk], [ClauseCatch])])]),
  199. SourceFunctionAst = erl_syntax:function(
  200. erl_syntax:atom(source),
  201. [ erl_syntax:clause([], none, [ erl_syntax:string(File) ]) ]),
  202. Dependencies = lists:usort([{File, filelib:last_modified(File)} | BodyInfo#ast_info.dependencies]),
  203. Dependencies1 = lists:filter(fun({[],0}) -> false; (_) -> true end, Dependencies),
  204. DependenciesFunctionAst = erl_syntax:function(
  205. erl_syntax:atom(dependencies), [
  206. erl_syntax:clause([], none,
  207. [ erl_syntax:list( lists:map(
  208. fun ({XFile, {{Year,Month,Day},{Hour,Min,Sec}}}) ->
  209. erl_syntax:tuple([
  210. erl_syntax:string(XFile),
  211. erl_syntax:tuple([
  212. erl_syntax:tuple([erl_syntax:integer(Year), erl_syntax:integer(Month), erl_syntax:integer(Day)]),
  213. erl_syntax:tuple([erl_syntax:integer(Hour), erl_syntax:integer(Min), erl_syntax:integer(Sec)])
  214. ])
  215. ])
  216. end,
  217. Dependencies1)) ])
  218. ]),
  219. BodyLanguageAst = erl_syntax:match_expr(
  220. erl_syntax:variable("Language"),
  221. erl_syntax:application(
  222. erl_syntax:atom(z_context),
  223. erl_syntax:atom(language),
  224. [ z_context_ast(Context) ]
  225. )
  226. ),
  227. BodyRenderAsts = case TreeWalker#treewalker.has_auto_id of
  228. false ->
  229. [BodyLanguageAst, BodyAst];
  230. true ->
  231. AutoIdVar = resolve_scoped_variable_ast("$autoid", Context),
  232. BodyAutoIdAst = erl_syntax:match_expr(
  233. AutoIdVar,
  234. erl_syntax:application(
  235. erl_syntax:atom(z_ids),
  236. erl_syntax:atom(identifier),
  237. [erl_syntax:integer(8)]
  238. )
  239. ),
  240. [BodyAutoIdAst, BodyLanguageAst, BodyAst]
  241. end,
  242. RenderInternalFunctionAst = erl_syntax:function(
  243. erl_syntax:atom(render2),
  244. [ erl_syntax:clause(
  245. [erl_syntax:variable("Variables"), erl_syntax:variable("ZpContext")],
  246. none,
  247. BodyRenderAsts)
  248. ]),
  249. ModuleAst = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
  250. ExportAst = erl_syntax:attribute(erl_syntax:atom(export),
  251. [erl_syntax:list([
  252. erl_syntax:arity_qualifier(erl_syntax:atom(template_reset_counter), erl_syntax:integer(0)),
  253. erl_syntax:arity_qualifier(erl_syntax:atom(trans_table), erl_syntax:integer(0)),
  254. erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(2)),
  255. erl_syntax:arity_qualifier(erl_syntax:atom(source), erl_syntax:integer(0)),
  256. erl_syntax:arity_qualifier(erl_syntax:atom(dependencies), erl_syntax:integer(0))])]),
  257. [erl_syntax:revert(X) || X <- [ModuleAst, ExportAst, TemplateResetCounterFunctionAst, TransTableFunctionAst,
  258. Render2FunctionAst, SourceFunctionAst, DependenciesFunctionAst, RenderInternalFunctionAst
  259. | BodyInfo#ast_info.pre_render_asts]].
  260. find_next([], _Find) -> error;
  261. find_next([Find,Next|_], Find) -> {ok, Next};
  262. find_next([_|Rest], Find) -> find_next(Rest, Find).
  263. % child templates should only consist of blocks at the top level
  264. body_extends(Extends, File, ThisParseTree, Context, TreeWalker) ->
  265. case lists:member(File, Context#dtl_context.parse_trail) of
  266. true ->
  267. throw({error, "Circular file inclusion: " ++ File});
  268. _ ->
  269. notify({debug, template, {extends, Extends, File}}, Context#dtl_context.z_context),
  270. case parse(File, Context) of
  271. {ok, ParentParseTree} ->
  272. ThisFile = hd(Context#dtl_context.parse_trail),
  273. BlockDict = lists:foldl(
  274. fun
  275. ({block, {identifier, _, Name}, Contents}, Dict) ->
  276. Dict1 = dict:store(Name, {ThisFile, Contents}, Dict),
  277. dict:store({Name, ThisFile}, Contents, Dict1);
  278. (_, Dict) ->
  279. Dict
  280. end, dict:new(), ThisParseTree),
  281. with_dependency(File, body_ast(ParentParseTree, Context#dtl_context{
  282. block_dict = dict:merge(fun(_Key, _ParentVal, ChildVal) -> ChildVal end,
  283. BlockDict,
  284. Context#dtl_context.block_dict),
  285. block_trail = [],
  286. parse_trail = [File | Context#dtl_context.parse_trail],
  287. extends_trail = [Extends | Context#dtl_context.extends_trail]}, TreeWalker));
  288. Err ->
  289. throw(Err)
  290. end
  291. end.
  292. body_ast([overrules | ThisParseTree], Context, TreeWalker) ->
  293. CurrentExtend = hd(Context#dtl_context.extends_trail),
  294. CurrentFile = hd(Context#dtl_context.parse_trail),
  295. Files = full_path(CurrentExtend, true, Context),
  296. % Find the first file after the current file
  297. case find_next(Files, CurrentFile) of
  298. {ok, File} ->
  299. notify({debug, template, {overrules, CurrentExtend, File}}, Context#dtl_context.z_context),
  300. body_extends(CurrentExtend, File, ThisParseTree, Context, TreeWalker);
  301. error ->
  302. ?ERROR("body_ast: could not find overruled template for \"~p\" (~p)", [CurrentExtend,CurrentFile]),
  303. throw({error, "Could not find the template for overrules: '" ++ CurrentExtend ++ "'"}),
  304. {{erl_syntax:string(""), #ast_info{}}, TreeWalker}
  305. end;
  306. body_ast([{extends, {string_literal, _Pos, String}} | ThisParseTree], Context, TreeWalker) ->
  307. Extends = unescape_string_literal(String),
  308. case full_path(Extends, Context) of
  309. {ok, File} ->
  310. body_extends(Extends, File, ThisParseTree, Context, TreeWalker);
  311. {error, Reason} ->
  312. ?ERROR("body_ast: could not find template ~p (~p)", [Extends, Reason]),
  313. throw({error, "Could not find the template for extends: '" ++ Extends ++ "'"}),
  314. {{erl_syntax:string(""), #ast_info{}}, TreeWalker}
  315. end;
  316. body_ast(DjangoParseTree, Context, TreeWalker) ->
  317. {AstInfoList, TreeWalker2} = lists:mapfoldl(
  318. fun
  319. ({'block', {identifier, _, Name}, Contents}, TreeWalkerAcc) ->
  320. CurrentFile = case Context#dtl_context.block_trail of
  321. [] -> hd(Context#dtl_context.parse_trail);
  322. [{_, F}|_] -> F
  323. end,
  324. % remember this block for an 'inherit' tag
  325. Context1 = Context#dtl_context{
  326. block_dict=dict:store({Name,CurrentFile}, Contents, Context#dtl_context.block_dict)
  327. },
  328. % See if this block has been overruled
  329. {BlockFile, Block} = case dict:find(Name, Context#dtl_context.block_dict) of
  330. {ok, {_ChildFile, _ChildBlock} = B} ->
  331. B;
  332. error ->
  333. {CurrentFile, Contents}
  334. end,
  335. % Check if we have a recursive definition
  336. case lists:member({Name,BlockFile}, Context#dtl_context.block_trail) of
  337. true ->
  338. ?ERROR("body_ast: recursive block ~p (~p)", [Name, BlockFile]),
  339. throw({error, "Recursive block definition of '" ++ Name ++ "' (" ++ BlockFile ++ ")"});
  340. false ->
  341. body_ast(Block,
  342. Context1#dtl_context{block_trail=[{Name,BlockFile}|Context1#dtl_context.block_trail]},
  343. TreeWalkerAcc)
  344. end;
  345. ('inherit', TreeWalkerAcc) ->
  346. inherit_ast(Context, TreeWalkerAcc);
  347. ({'comment', _Contents}, TreeWalkerAcc) ->
  348. empty_ast(TreeWalkerAcc);
  349. ({'trans', {trans_text, _Pos, TransLiteral}}, TreeWalkerAcc) ->
  350. trans_ast(TransLiteral, Context, TreeWalkerAcc);
  351. ({'trans_ext', {string_literal, _Pos, String}, Args}, TreeWalkerAcc) ->
  352. trans_ext_ast(String, Args, Context, TreeWalkerAcc);
  353. ({'date', 'now', {string_literal, _Pos, FormatString}}, TreeWalkerAcc) ->
  354. now_ast(FormatString, Context, TreeWalkerAcc);
  355. ({'autoescape', {identifier, _, OnOrOff}, Contents}, TreeWalkerAcc) ->
  356. body_ast(Contents, Context#dtl_context{auto_escape = list_to_atom(OnOrOff)},
  357. TreeWalkerAcc);
  358. ({'text', _Pos, String}, TreeWalkerAcc) ->
  359. string_ast(String, TreeWalkerAcc);
  360. ({'include', {string_literal, _, File}, Args, All}, TreeWalkerAcc) ->
  361. include_ast(unescape_string_literal(File), Args, All, Context, TreeWalkerAcc);
  362. ({'catinclude', {string_literal, _, File}, RscId, Args, All}, TreeWalkerAcc) ->
  363. catinclude_ast(unescape_string_literal(File), RscId, Args, All, Context, TreeWalkerAcc);
  364. ({'if', {'expr', "b_not", E}, Contents}, TreeWalkerAcc) ->
  365. {IfAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
  366. {ElseAstInfo, TreeWalker2} = body_ast(Contents, Context, TreeWalker1),
  367. ifexpr_ast(E, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  368. ({'if', E, Contents}, TreeWalkerAcc) ->
  369. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  370. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  371. ifexpr_ast(E, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  372. ({'ifelse', {'expr', "b_not", E}, IfContents, ElseContents}, TreeWalkerAcc) ->
  373. {IfAstInfo, TreeWalker1} = body_ast(ElseContents, Context, TreeWalkerAcc),
  374. {ElseAstInfo, TreeWalker2} = body_ast(IfContents, Context, TreeWalker1),
  375. ifexpr_ast(E, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  376. ({'ifelse', E, IfContents, ElseContents}, TreeWalkerAcc) ->
  377. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  378. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
  379. ifexpr_ast(E, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  380. ({'ifequal', Args, Contents}, TreeWalkerAcc) ->
  381. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  382. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  383. ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  384. ({'ifequalelse', Args, IfContents, ElseContents}, TreeWalkerAcc) ->
  385. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  386. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context,TreeWalker1),
  387. ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  388. ({'ifnotequal', Args, Contents}, TreeWalkerAcc) ->
  389. {IfAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
  390. {ElseAstInfo, TreeWalker2} = body_ast(Contents, Context, TreeWalker1),
  391. ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  392. ({'ifnotequalelse', Args, IfContents, ElseContents}, TreeWalkerAcc) ->
  393. {IfAstInfo, TreeWalker1} = body_ast(ElseContents, Context, TreeWalkerAcc),
  394. {ElseAstInfo, TreeWalker2} = body_ast(IfContents, Context, TreeWalker1),
  395. ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  396. ({'with', [ExprList, Identifiers], WithContents}, TreeWalkerAcc) ->
  397. with_ast(ExprList, Identifiers, WithContents, Context, TreeWalkerAcc);
  398. ({'for', {'in', IteratorList, Value}, Contents}, TreeWalkerAcc) ->
  399. for_loop_ast(IteratorList, Value, Contents, none, Context, TreeWalkerAcc);
  400. ({'for', {'in', IteratorList, Value}, Contents, EmptyPartContents}, TreeWalkerAcc) ->
  401. for_loop_ast(IteratorList, Value, Contents, EmptyPartContents, Context, TreeWalkerAcc);
  402. ({'load', Names}, TreeWalkerAcc) ->
  403. load_ast(Names, Context, TreeWalkerAcc);
  404. ({'tag', {'identifier', _, Name}, Args, All}, TreeWalkerAcc) ->
  405. tag_ast(Name, Args, All, Context, TreeWalkerAcc);
  406. ({'call_args', {'identifier', _, Name}, Args}, TreeWalkerAcc) ->
  407. call_ast(Name, Args, Context, TreeWalkerAcc);
  408. ({'call_with', {'identifier', _, Name}, With}, TreeWalkerAcc) ->
  409. call_with_ast(Name, With, Context, TreeWalkerAcc);
  410. ({'cycle', Names}, TreeWalkerAcc) ->
  411. cycle_ast(Names, Context, TreeWalkerAcc);
  412. ({'cycle_compat', Names}, TreeWalkerAcc) ->
  413. cycle_compat_ast(Names, Context, TreeWalkerAcc);
  414. ({'media', Variable, Args}, TreeWalkerAcc) ->
  415. media_ast(Variable, Args, Context, TreeWalkerAcc);
  416. ({'image', Variable, Args}, TreeWalkerAcc) ->
  417. image_ast(Variable, Args, Context, TreeWalkerAcc);
  418. ({'image_url', Variable, Args}, TreeWalkerAcc) ->
  419. image_url_ast(Variable, Args, Context, TreeWalkerAcc);
  420. ({'url', {'identifier', _, Name}, Args}, TreeWalkerAcc) ->
  421. url_ast(Name, Args, Context, TreeWalkerAcc);
  422. ({'print', Value}, TreeWalkerAcc) ->
  423. print_ast(Value, Context, TreeWalkerAcc);
  424. ({'lib', LibList, Args}, TreeWalkerAcc) ->
  425. lib_ast(LibList, Args, Context, TreeWalkerAcc);
  426. ({'cache', [MaxAge, Args], CacheContents}, TreeWalkerAcc) ->
  427. cache_ast(MaxAge, Args, CacheContents, Context, TreeWalkerAcc);
  428. (ValueToken, TreeWalkerAcc) ->
  429. {{ValueAst,ValueInfo},ValueTreeWalker} = value_ast(ValueToken, true, Context, TreeWalkerAcc),
  430. {{format(ValueAst, Context),ValueInfo},ValueTreeWalker}
  431. end, TreeWalker, DjangoParseTree),
  432. {AstList, {Info, TreeWalker3}} = lists:mapfoldl(
  433. fun({Ast, Info}, {InfoAcc, TreeWalkerAcc}) ->
  434. PresetVars = lists:foldl(fun
  435. (X, Acc) ->
  436. case proplists:lookup(list_to_atom(X), Context#dtl_context.vars) of
  437. none ->
  438. Acc;
  439. Val ->
  440. [erl_syntax:abstract(Val) | Acc]
  441. end
  442. end, [], Info#ast_info.var_names),
  443. case PresetVars of
  444. [] ->
  445. {Ast, {merge_info(Info, InfoAcc), TreeWalkerAcc}};
  446. _ ->
  447. Counter = TreeWalkerAcc#treewalker.counter,
  448. Name = lists:concat([pre_render, Counter]),
  449. Ast1 = erl_syntax:application(none, erl_syntax:atom(Name),
  450. [erl_syntax:list(PresetVars)]),
  451. PreRenderAst = erl_syntax:function(erl_syntax:atom(Name),
  452. [erl_syntax:clause([erl_syntax:variable("Variables")], none, [Ast])]),
  453. PreRenderAsts = Info#ast_info.pre_render_asts,
  454. Info1 = Info#ast_info{pre_render_asts = [PreRenderAst | PreRenderAsts]},
  455. {Ast1, {merge_info(Info1, InfoAcc), TreeWalkerAcc#treewalker{counter = Counter + 1}}}
  456. end
  457. end, {#ast_info{}, TreeWalker2}, AstInfoList),
  458. {{erl_syntax:list(AstList), Info}, TreeWalker3}.
  459. merge_info(Info1, Info2) ->
  460. #ast_info{dependencies =
  461. lists:merge(
  462. lists:sort(Info1#ast_info.dependencies),
  463. lists:sort(Info2#ast_info.dependencies)),
  464. var_names =
  465. lists:merge(
  466. lists:sort(Info1#ast_info.var_names),
  467. lists:sort(Info2#ast_info.var_names)),
  468. pre_render_asts =
  469. lists:merge(
  470. Info1#ast_info.pre_render_asts,
  471. Info2#ast_info.pre_render_asts)}.
  472. %with_dependencies([], Args) ->
  473. % Args;
  474. %with_dependencies([H, T], Args) ->
  475. % with_dependencies(T, with_dependency(H, Args)).
  476. %
  477. with_dependency(FilePath, {{Ast, Info}, TreeWalker}) ->
  478. {{Ast, Info#ast_info{dependencies = [{FilePath, filelib:last_modified(FilePath)} | Info#ast_info.dependencies]}}, TreeWalker}.
  479. inherit_ast(Context, TreeWalker) ->
  480. {BlockName,BlockFile} = hd(Context#dtl_context.block_trail),
  481. Inherited = [ {F, dict:find({BlockName,F}, Context#dtl_context.block_dict)}
  482. || F <- find_prev_all(Context#dtl_context.parse_trail, BlockFile, []) ],
  483. case [ {F,C} || {F,{ok, C}} <- Inherited ] of
  484. [{InheritedFile,Content}|_] ->
  485. body_ast(Content,
  486. Context#dtl_context{block_trail=[{BlockName,InheritedFile}|Context#dtl_context.block_trail]},
  487. TreeWalker);
  488. [] ->
  489. {{erl_syntax:string(""), #ast_info{}}, TreeWalker}
  490. end.
  491. find_prev_all([], _Find, Acc) -> Acc;
  492. find_prev_all([Find|_], Find, Acc) -> Acc;
  493. find_prev_all([F|Rest], Find, Acc) -> find_prev_all(Rest, Find, [F|Acc]).
  494. empty_ast(TreeWalker) ->
  495. {{erl_syntax:list([]), #ast_info{}}, TreeWalker}.
  496. value_ast(ValueToken, AsString, Context, TreeWalker) ->
  497. case ValueToken of
  498. {'expr', Operator, Value} ->
  499. {{ValueAst,InfoValue}, TreeWalker1} = value_ast(Value, false, Context, TreeWalker),
  500. Ast = erl_syntax:application(erl_syntax:atom(erlydtl_operators),
  501. erl_syntax:atom(Operator),
  502. [ValueAst, z_context_ast(Context)]),
  503. {{Ast, InfoValue}, TreeWalker1};
  504. {'expr', Operator, Value1, Value2} ->
  505. {{Value1Ast,InfoValue1}, TreeWalker1} = value_ast(Value1, false, Context, TreeWalker),
  506. {{Value2Ast,InfoValue2}, TreeWalker2} = value_ast(Value2, false, Context, TreeWalker1),
  507. Ast = erl_syntax:application(erl_syntax:atom(erlydtl_operators),
  508. erl_syntax:atom(Operator),
  509. [Value1Ast, Value2Ast, z_context_ast(Context)]),
  510. {{Ast, merge_info(InfoValue1,InfoValue2)}, TreeWalker2};
  511. {'string_literal', _Pos, String} ->
  512. {{auto_escape(erl_syntax:string(unescape_string_literal(String)), Context),
  513. #ast_info{}}, TreeWalker};
  514. {'trans_literal', _Pos, String} ->
  515. {{auto_escape(trans_literal_ast(String, Context), Context),
  516. #ast_info{}}, TreeWalker};
  517. {'number_literal', _Pos, Number} ->
  518. case AsString of
  519. true -> string_ast(Number, TreeWalker);
  520. false -> {{erl_syntax:integer(list_to_integer(Number)), #ast_info{}}, TreeWalker}
  521. end;
  522. {'atom_literal', _Pos, String} ->
  523. {{erl_syntax:atom(to_atom(unescape_string_literal(String))), #ast_info{}}, TreeWalker};
  524. undefined ->
  525. {{erl_syntax:atom(undefined), #ast_info{}}, TreeWalker};
  526. {'auto_id', Name} ->
  527. auto_id_ast(Name, Context, TreeWalker);
  528. {'apply_filter', Variable, Filter} ->
  529. filter_ast(Variable, Filter, Context, TreeWalker);
  530. {'attribute', _} = Variable ->
  531. {{Ast, VarName, VarInfo}, TreeWalker1} = resolve_variable_ast(Variable, Context, TreeWalker),
  532. {{Ast, merge_info(VarInfo,#ast_info{var_names = [VarName]})}, TreeWalker1};
  533. {'variable', _} = Variable ->
  534. {{Ast, VarName, VarInfo}, TreeWalker1} = resolve_variable_ast(Variable, Context, TreeWalker),
  535. {{Ast, merge_info(VarInfo, #ast_info{var_names = [VarName]})}, TreeWalker1};
  536. {'index_value', _, _} = Variable ->
  537. {{Ast, VarName, VarInfo}, TreeWalker1} = resolve_indexvariable_ast(Variable, Context, TreeWalker),
  538. {{Ast, merge_info(VarInfo, #ast_info{var_names = [VarName]})}, TreeWalker1};
  539. {tuple_value, {identifier, _, TupleName}, TupleArgs} ->
  540. TupleNameAst = erl_syntax:atom(TupleName),
  541. {TupleArgsAst, TreeWalker1} = scomp_ast_list_args(TupleArgs, Context, TreeWalker),
  542. {{erl_syntax:tuple([TupleNameAst, TupleArgsAst]), #ast_info{}}, TreeWalker1};
  543. {value_list, Values} ->
  544. {ValueAstList, ValueInfo, TreeWalker1} = lists:foldl(
  545. fun(V, {Acc,Info,TreeW}) ->
  546. {{Ast,InfoV}, TreeW1} = value_ast(V, false, Context, TreeW),
  547. {[Ast|Acc], merge_info(Info,InfoV), TreeW1}
  548. end,
  549. {[], #ast_info{}, TreeWalker},
  550. Values),
  551. {{erl_syntax:list(lists:reverse(ValueAstList)), ValueInfo},TreeWalker1}
  552. end.
  553. string_ast(String, TreeWalker) ->
  554. % {{erl_syntax:string(String), #ast_info{}}, TreeWalker}. %% less verbose AST, better for development and debugging
  555. {{erl_syntax:binary([erl_syntax:binary_field(erl_syntax:integer(X)) || X <- String]), #ast_info{}}, TreeWalker}.
  556. catinclude_ast(File, Id, Args, All, Context, TreeWalker) ->
  557. Args1 = [ {{identifier, none, "$file"},{string_literal, none, File}},
  558. {{identifier, none, "id"}, Id} | Args],
  559. scomp_ast("catinclude", Args1, All, Context, TreeWalker).
  560. include_ast(File, Args, All, Context, TreeWalker) ->
  561. {UseScomp, IsSudo} = lists:foldl( fun({{identifier, _, Key}, Val}, {IsC,IsSu}) ->
  562. case Key of
  563. "maxage" -> {true, IsSu};
  564. "vary" -> {true, IsSu};
  565. "scomp" -> {true, IsSu};
  566. "visible_for" -> {true, IsSu};
  567. "sudo" ->
  568. case Val of
  569. true -> {IsC, true};
  570. _ -> {IsC, IsSu}
  571. end;
  572. _ -> {IsC, IsSu}
  573. end
  574. end,
  575. {false, false},
  576. Args),
  577. case UseScomp of
  578. false ->
  579. {InterpretedArgs, TreeWalker1} = interpreted_args(Args, Context, TreeWalker),
  580. {ScopedArgs, ArgAsts} = lists:foldr(
  581. fun({AKey, AAst}, {ScopeAcc, AstAcc}) ->
  582. Var = "Arg_" ++ z_ids:identifier(10),
  583. AssignAst = erl_syntax:match_expr(erl_syntax:variable(Var), AAst),
  584. { [{AKey, erl_syntax:variable(Var)}|ScopeAcc], [AssignAst|AstAcc] }
  585. end,
  586. {[], []},
  587. InterpretedArgs),
  588. {ContextInclude,ArgAsts1} = case IsSudo of
  589. true ->
  590. V = "ZpContext_" ++ z_ids:id(10),
  591. ZpContextAst = erl_syntax:match_expr(
  592. erl_syntax:variable(V),
  593. erl_syntax:application(
  594. erl_syntax:atom(z_acl),
  595. erl_syntax:atom(sudo),
  596. [z_context_ast(Context)])),
  597. LocalScope = [{'ZpContext', erl_syntax:variable(V)}],
  598. { Context#dtl_context{local_scopes=[LocalScope|Context#dtl_context.local_scopes]},
  599. [ZpContextAst|ArgAsts]
  600. };
  601. false ->
  602. {Context, ArgAsts}
  603. end,
  604. % {AstList, Info, TreeWalker}
  605. IncludeFun = fun(FilePath, {AstList, InclInfo, TreeW}) ->
  606. notify({debug, template, {include, File, FilePath}}, ContextInclude#dtl_context.z_context),
  607. case parse(FilePath, ContextInclude) of
  608. {ok, InclusionParseTree} ->
  609. AutoIdVar = "AutoId_"++z_ids:identifier(),
  610. IncludeScope = [ {'$autoid', erl_syntax:variable(AutoIdVar)} | ScopedArgs ],
  611. {{Ast,Info}, InclTW2} =
  612. with_dependency(FilePath,
  613. body_ast(
  614. InclusionParseTree,
  615. Context#dtl_context{
  616. local_scopes = [ IncludeScope | ContextInclude#dtl_context.local_scopes ],
  617. parse_trail = [FilePath | ContextInclude#dtl_context.parse_trail]},
  618. TreeW#treewalker{has_auto_id=false})),
  619. Ast1 = case InclTW2#treewalker.has_auto_id of
  620. false -> Ast;
  621. true -> erl_syntax:block_expr(
  622. [
  623. erl_syntax:match_expr(
  624. erl_syntax:variable(AutoIdVar),
  625. erl_syntax:application(
  626. erl_syntax:atom(z_ids),
  627. erl_syntax:atom(identifier),
  628. [])),
  629. Ast])
  630. end,
  631. {[Ast1|AstList], merge_info(InclInfo, Info), InclTW2#treewalker{has_auto_id=TreeW#treewalker.has_auto_id}};
  632. Err ->
  633. throw(Err)
  634. end
  635. end,
  636. % Compile all included files, put them in a block expr with a single assignment of the argument vars at the start.
  637. case lists:foldl(IncludeFun, {[], #ast_info{}, TreeWalker1}, full_path(File, All, Context)) of
  638. {[], _, TreeWalkerN} ->
  639. case All of
  640. false -> ?LOG("include_ast: could not find template ~p", [File]);
  641. true -> ok
  642. end,
  643. {{erl_syntax:string(""), #ast_info{}}, TreeWalkerN};
  644. {AstList, AstInfo, TreeWalkerN} ->
  645. AstN = erl_syntax:block_expr(ArgAsts1 ++ [erl_syntax:list(lists:reverse(AstList))]),
  646. {{AstN, AstInfo}, TreeWalkerN}
  647. end;
  648. true ->
  649. Args1 = [{{identifier, none, "$file"},{string_literal, none, File}} | Args],
  650. scomp_ast("include", Args1, All, Context, TreeWalker)
  651. end.
  652. filter_ast(Variable, Filter, Context, TreeWalker) ->
  653. % the escape filter is special; it is always applied last, so we have to go digging for it
  654. % AutoEscape = 'did' means we (will have) decided whether to escape the current variable,
  655. % so don't do any more escaping
  656. {{UnescapedAst, Info}, TreeWalker2} = filter_ast_noescape(Variable, Filter, Context#dtl_context{auto_escape = did}, TreeWalker),
  657. case search_for_escape_filter(Variable, Filter, Context) of
  658. on ->
  659. {{erl_syntax:application(
  660. erl_syntax:atom(filter_force_escape),
  661. erl_syntax:atom(force_escape),
  662. [UnescapedAst, z_context_ast(Context)]),
  663. Info}, TreeWalker2};
  664. _ ->
  665. {{UnescapedAst, Info}, TreeWalker2}
  666. end.
  667. filter_ast_noescape(Variable, {filter, {identifier, _, "escape"}, []}, Context, TreeWalker) ->
  668. value_ast(Variable, true, Context, TreeWalker);
  669. filter_ast_noescape(Variable, Filter, Context, TreeWalker) ->
  670. {{VariableAst,Info},TreeWalker2} = value_ast(Variable, true, Context, TreeWalker),
  671. {{FilterAst,Info2},TreeWalker3} = filter_ast1(Filter, VariableAst, Context, TreeWalker2),
  672. {{FilterAst, merge_info(Info, Info2)}, TreeWalker3}.
  673. filter_ast1({filter, {identifier, _, Name}, []}, VariableAst, Context, TreeWalker) ->
  674. FilterAst = erl_syntax:application(erl_syntax:atom(list_to_atom("filter_"++Name)), erl_syntax:atom(Name), [VariableAst, z_context_ast(Context)]),
  675. {{FilterAst, #ast_info{}}, TreeWalker};
  676. filter_ast1({filter, {identifier, _, "default"}, [Arg]}, VariableAst, Context, TreeWalker) ->
  677. {{ArgAst, Info},TreeWalker1} = value_ast(Arg, false, Context, TreeWalker),
  678. VarAst = erl_syntax:variable("Default_" ++ z_ids:identifier()),
  679. CaseAst = erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(is_false), [VarAst]),
  680. [erl_syntax:clause([erl_syntax:atom(true)], none,
  681. [ArgAst]),
  682. erl_syntax:clause([erl_syntax:underscore()], none,
  683. [VarAst])
  684. ]),
  685. {{erl_syntax:block_expr([erl_syntax:match_expr(VarAst, VariableAst), CaseAst]), Info}, TreeWalker1};
  686. filter_ast1({filter, {identifier, _, "default_if_none"}, [Arg]}, VariableAst, Context, TreeWalker) ->
  687. {{ArgAst, Info},TreeWalker1} = value_ast(Arg, false, Context, TreeWalker),
  688. VarAst = erl_syntax:variable("Default_" ++ z_ids:identifier()),
  689. CaseAst = erl_syntax:case_expr(VariableAst,
  690. [erl_syntax:clause([erl_syntax:atom(undefined)], none,
  691. [ArgAst]),
  692. erl_syntax:clause([VarAst], none,
  693. [VarAst])
  694. ]),
  695. {{CaseAst, Info}, TreeWalker1};
  696. filter_ast1({filter, {identifier, Pos, "default_if_undefined"}, Args}, VariableAst, Context, TreeWalker) ->
  697. filter_ast1({filter, {identifier, Pos, "default_if_none"}, Args}, VariableAst, Context, TreeWalker);
  698. filter_ast1({filter, {identifier, _, Name}, Args}, VariableAst, Context, TreeWalker) ->
  699. {{ArgAsts, Info}, TreeWalker2} = lists:foldr(
  700. fun(Arg, {{As,In},Tw}) ->
  701. {{ArgAst,ArgIn}, Tw1} = value_ast(Arg, false, Context, Tw),
  702. {{[ArgAst|As], merge_info(In,ArgIn)}, Tw1}
  703. end,
  704. {{[], #ast_info{}}, TreeWalker},
  705. Args),
  706. FilterAst = erl_syntax:application(
  707. erl_syntax:atom(list_to_atom("filter_"++Name)),
  708. erl_syntax:atom(Name),
  709. [VariableAst|ArgAsts] ++ [z_context_ast(Context)]
  710. ),
  711. {{FilterAst, Info}, TreeWalker2}.
  712. search_for_escape_filter(_, _, #dtl_context{auto_escape = on}) ->
  713. on;
  714. search_for_escape_filter(_, _, #dtl_context{auto_escape = did}) ->
  715. off;
  716. search_for_escape_filter(Variable, Filter, _) ->
  717. search_for_escape_filter(Variable, Filter).
  718. search_for_escape_filter(_, {filter, {identifier, _, "escape"}, []}) ->
  719. on;
  720. search_for_escape_filter({apply_filter, Variable, Filter}, _) ->
  721. search_for_escape_filter(Variable, Filter);
  722. search_for_escape_filter(_Variable, _Filter) ->
  723. off.
  724. resolve_variable_ast(VarTuple, Context, TreeWalker) ->
  725. opttrans_variable_ast(resolve_variable_ast(VarTuple, Context, TreeWalker, 'fetch_value'), Context).
  726. resolve_ifvariable_ast(VarTuple, Context, TreeWalker) ->
  727. opttrans_variable_ast(resolve_variable_ast(VarTuple, Context, TreeWalker, 'find_value'), Context).
  728. resolve_indexvariable_ast(VarTuple, Context, TreeWalker) ->
  729. opttrans_variable_ast(resolve_variable_ast(VarTuple, Context, TreeWalker, 'fetch_value'), Context).
  730. opttrans_variable_ast({{Ast, VarName, Info}, TreeWalker}, Context) ->
  731. Ast1 = erl_syntax:application(
  732. erl_syntax:atom(z_trans),
  733. erl_syntax:atom(lookup_fallback),
  734. [
  735. Ast,
  736. z_context_ast(Context)
  737. ]),
  738. {{Ast1, VarName, Info}, TreeWalker}.
  739. resolve_variable_ast({index_value, Variable, Index}, Context, TreeWalker, FinderFunction) ->
  740. {{IndexAst,Info},TreeWalker2} = value_ast(Index, false, Context, TreeWalker),
  741. {{VarAst, VarName, Info2}, TreeWalker3} = resolve_variable_ast(Variable, Context, TreeWalker2, FinderFunction),
  742. Ast = erl_syntax:application(
  743. erl_syntax:atom(erlydtl_runtime),
  744. erl_syntax:atom(FinderFunction),
  745. [IndexAst, VarAst, z_context_ast(Context)]),
  746. {{Ast, VarName, merge_info(Info, Info2)}, TreeWalker3};
  747. resolve_variable_ast({attribute, {{identifier, _, Arg}, {variable, {identifier, _, "q"}}}}, Context, TreeWalker, _FinderFunction) ->
  748. Ast = erl_syntax:application(
  749. erl_syntax:atom(z_context),
  750. erl_syntax:atom(get_q),
  751. [erl_syntax:string(Arg), z_context_ast(Context)]),
  752. {{Ast, "q", #ast_info{}}, TreeWalker};
  753. resolve_variable_ast({attribute, {{identifier, _, Arg}, {variable, {identifier, _, "q_validated"}}}}, Context, TreeWalker, _FinderFunction) ->
  754. Ast = erl_syntax:application(
  755. erl_syntax:atom(z_context),
  756. erl_syntax:atom(get_q_validated),
  757. [erl_syntax:string(Arg), z_context_ast(Context)]),
  758. {{Ast, "q", #ast_info{}}, TreeWalker};
  759. resolve_variable_ast({attribute, {{identifier, _, Model}, {variable, {identifier, _, "m"}}}}, _Context, TreeWalker, _FinderFunction) ->
  760. Ast = erl_syntax:tuple([
  761. erl_syntax:atom(m),
  762. erl_syntax:atom("m_" ++ Model),
  763. erl_syntax:atom(undefined)
  764. ]),
  765. {{Ast, "m", #ast_info{}}, TreeWalker};
  766. resolve_variable_ast({attribute, {{identifier, _, AttrName}, Variable}}, Context, TreeWalker, FinderFunction) ->
  767. {{VarAst, VarName, Info}, TreeWalker2} = resolve_variable_ast(Variable, Context, TreeWalker, FinderFunction),
  768. Ast = erl_syntax:application(
  769. erl_syntax:atom(erlydtl_runtime),
  770. erl_syntax:atom(FinderFunction),
  771. [erl_syntax:atom(AttrName), VarAst, z_context_ast(Context)]),
  772. {{Ast, VarName, Info}, TreeWalker2};
  773. resolve_variable_ast({variable, {identifier, _, "now"}}, Context, TreeWalker, _FinderFunction) ->
  774. Ast = case resolve_scoped_variable_ast("now", Context) of
  775. undefined ->
  776. erl_syntax:application(
  777. erl_syntax:atom(erlang),
  778. erl_syntax:atom(localtime),
  779. []);
  780. Val ->
  781. Val
  782. end,
  783. {{Ast, "now", #ast_info{}}, TreeWalker};
  784. resolve_variable_ast({variable, {identifier, _, "z_language"}}, Context, TreeWalker, _FinderFunction) ->
  785. Ast = case resolve_scoped_variable_ast("z_language", Context) of
  786. undefined ->
  787. erl_syntax:application(
  788. erl_syntax:atom(z_context),
  789. erl_syntax:atom(language),
  790. [z_context_ast(Context)]);
  791. Val ->
  792. Val
  793. end,
  794. {{Ast, "z_language", #ast_info{}}, TreeWalker};
  795. resolve_variable_ast({variable, {identifier, _, VarName}}, Context, TreeWalker, FinderFunction) ->
  796. Ast = case resolve_scoped_variable_ast(VarName, Context) of
  797. undefined ->
  798. erl_syntax:application(
  799. erl_syntax:atom(erlydtl_runtime),
  800. erl_syntax:atom(FinderFunction),
  801. [erl_syntax:atom(VarName), erl_syntax:variable("Variables"), z_context_ast(Context)]);
  802. Val ->
  803. Val
  804. end,
  805. {{Ast, VarName, #ast_info{}}, TreeWalker};
  806. resolve_variable_ast({apply_filter, Variable, Filter}, Context, TreeWalker, FinderFunction) ->
  807. {{VarAst, VarName, Info}, TreeWalker2} = resolve_variable_ast(Variable, Context, TreeWalker, FinderFunction),
  808. ValueAst = erl_syntax:application(
  809. erl_syntax:atom(erlydtl_runtime),
  810. erl_syntax:atom(to_value),
  811. [VarAst, z_context_ast(Context)]
  812. ),
  813. {{VarValue, Info2}, TreeWalker3} = filter_ast1(Filter, ValueAst, Context, TreeWalker2),
  814. {{VarValue, VarName, merge_info(Info, Info2)}, TreeWalker3};
  815. resolve_variable_ast(ValueToken, Context, TreeWalker, _FinderFunction) ->
  816. {{Ast, Info}, TreeWalker1} = value_ast(ValueToken, false, Context, TreeWalker),
  817. {{Ast, "$value", Info}, TreeWalker1}.
  818. resolve_scoped_variable_ast(VarName, Context) ->
  819. lists:foldl(fun(Scope, Value) ->
  820. case Value of
  821. undefined -> proplists:get_value(list_to_atom(VarName), Scope);
  822. _ -> Value
  823. end
  824. end, undefined, Context#dtl_context.local_scopes).
  825. %% @doc Return the AST for the z_context var
  826. z_context_ast(Context) ->
  827. case resolve_scoped_variable_ast("ZpContext", Context) of
  828. undefined -> erl_syntax:variable("ZpContext");
  829. Ast -> Ast
  830. end.
  831. format(Ast, Context) ->
  832. auto_escape(stringify(Ast, Context), Context).
  833. stringify(Ast, Context) ->
  834. erl_syntax:application(erl_syntax:atom(filter_stringify), erl_syntax:atom(stringify),
  835. [Ast, z_context_ast(Context)]).
  836. auto_escape(Value, Context) ->
  837. case Context#dtl_context.auto_escape of
  838. on ->
  839. erl_syntax:application(erl_syntax:atom(filter_force_escape), erl_syntax:atom(force_escape),
  840. [Value, z_context_ast(Context)]);
  841. _ ->
  842. Value
  843. end.
  844. ifexpr_ast(Expression, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
  845. Info = merge_info(IfContentsInfo, ElseContentsInfo),
  846. {{Ast, ExpressionInfo}, TreeWalker1} = value_ast(Expression, false, Context, TreeWalker),
  847. {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(is_false), [Ast]),
  848. [erl_syntax:clause([erl_syntax:atom(true)], none,
  849. [ElseContentsAst]),
  850. erl_syntax:clause([erl_syntax:underscore()], none,
  851. [IfContentsAst])
  852. ]), merge_info(ExpressionInfo, Info)}, TreeWalker1}.
  853. ifequalelse_ast(Args, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
  854. Info = merge_info(IfContentsInfo, ElseContentsInfo),
  855. {[Arg1Ast, Arg2Ast], VarNames, Info1, TreeWalker1} = lists:foldl(fun
  856. (X, {Asts, AccVarNames, Inf, TW}) ->
  857. case X of
  858. {string_literal, _, Literal} ->
  859. {[erl_syntax:string(unescape_string_literal(Literal)) | Asts], AccVarNames, Inf, TW};
  860. {trans_literal, _, Literal} ->
  861. {[trans_literal_ast(Literal, Context) | Asts], AccVarNames, Inf, TW};
  862. {number_literal, _, Literal} ->
  863. {[erl_syntax:integer(list_to_integer(Literal)) | Asts], AccVarNames, Inf, TW};
  864. Variable ->
  865. {{Ast, VarName, VarInfo}, TW1} = resolve_ifvariable_ast(Variable, Context, TW),
  866. {[Ast | Asts], [VarName | AccVarNames], merge_info(Inf, VarInfo), TW1}
  867. end
  868. end,
  869. {[], Info#ast_info.var_names, #ast_info{}, TreeWalker},
  870. Args),
  871. Ast = erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(are_equal),
  872. [Arg1Ast, Arg2Ast]),
  873. [
  874. erl_syntax:clause([erl_syntax:atom(true)], none, [IfContentsAst]),
  875. erl_syntax:clause([erl_syntax:underscore()], none, [ElseContentsAst])
  876. ]),
  877. {{Ast, merge_info(Info1, Info#ast_info{var_names = VarNames})}, TreeWalker1}.
  878. %% With statement with only a single variable, easy & quick match.
  879. with_ast([Value], [{identifier, _, V}], Contents, Context, TreeWalker) ->
  880. Postfix = z_ids:identifier(),
  881. VarAst = erl_syntax:variable("With_" ++ V ++ [$_|Postfix]),
  882. {{ValueAst, ValueInfo}, TreeWalker1} = value_ast(Value, false, Context, TreeWalker),
  883. LocalScope = [ {list_to_atom(V), VarAst} ],
  884. {{InnerAst, InnerInfo}, TreeWalker2} = body_ast(
  885. Contents,
  886. Context#dtl_context{local_scopes=[LocalScope | Context#dtl_context.local_scopes]},
  887. TreeWalker1),
  888. WithAst = erl_syntax:block_expr([erl_syntax:match_expr(VarAst, ValueAst), InnerAst]),
  889. {{WithAst, merge_info(ValueInfo,InnerInfo)}, TreeWalker2};
  890. %% With statement with multiple vars, match against tuples and lists.
  891. with_ast([Value], Variables, Contents, Context, TreeWalker) ->
  892. Postfix = z_ids:identifier(),
  893. VarAsts = lists:map(fun({identifier, _, V}) ->
  894. erl_syntax:variable("With_" ++ V ++ [$_|Postfix])
  895. en