PageRenderTime 54ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/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
Possible License(s): Apache-2.0, MIT, LGPL-2.1, BSD-3-Clause
  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. end, Variables),
  896. {{ValueAst, ValueInfo}, TreeWalker1} = value_ast(Value, false, Context, TreeWalker),
  897. LocalScope = lists:map( fun({identifier, _, V}) ->
  898. {list_to_atom(V), erl_syntax:variable("With_" ++ V ++ [$_|Postfix]) }
  899. end, Variables),
  900. {{InnerAst, InnerInfo}, TreeWalker2} = body_ast(
  901. Contents,
  902. Context#dtl_context{local_scopes=[LocalScope | Context#dtl_context.local_scopes]},
  903. TreeWalker1),
  904. ListClauseAst = erl_syntax:clause([erl_syntax:list(VarAsts)], none, [InnerAst]),
  905. TupleClauseAst = erl_syntax:clause([erl_syntax:tuple(VarAsts)], none, [InnerAst]),
  906. WithAst = erl_syntax:case_expr(ValueAst, [ListClauseAst, TupleClauseAst]),
  907. {{WithAst, merge_info(ValueInfo,InnerInfo)}, TreeWalker2};
  908. %% With statement with multiple expressions and multiple vars
  909. with_ast(ValueList, Variables, Contents, Context, TreeWalker) ->
  910. Postfix = z_ids:identifier(),
  911. VarAsts = lists:map(fun({identifier, _, V}) ->
  912. erl_syntax:variable("With_" ++ V ++ [$_|Postfix])
  913. end, Variables),
  914. {{ValueAsts, ValueInfo}, TreeWalker1} = lists:foldr(
  915. fun (V,{{Vs,Inf},TW}) ->
  916. {{VAst, VInfo}, TW1} = value_ast(V, false, Context, TW),
  917. {{[VAst|Vs], merge_info(VInfo,Inf)}, TW1}
  918. end,
  919. {{[],#ast_info{}}, TreeWalker},
  920. ValueList),
  921. LocalScope = lists:map( fun({identifier, _, V}) ->
  922. {list_to_atom(V), erl_syntax:variable("With_" ++ V ++ [$_|Postfix]) }
  923. end, Variables),
  924. {{InnerAst, InnerInfo}, TreeWalker2} = body_ast(
  925. Contents,
  926. Context#dtl_context{local_scopes=[LocalScope | Context#dtl_context.local_scopes]},
  927. TreeWalker1),
  928. Assignments = [ erl_syntax:match_expr(Var,Val) || {Var,Val} <- lists:zip(VarAsts,ValueAsts) ],
  929. WithAst = erl_syntax:block_expr(Assignments ++ [InnerAst]),
  930. {{WithAst, merge_info(ValueInfo,InnerInfo)}, TreeWalker2}.
  931. for_loop_ast(IteratorList, LoopValue, Contents, EmptyPartContents, Context, TreeWalker) ->
  932. PostFix = z_ids:identifier(),
  933. Vars = lists:map(fun({identifier, _, Iterator}) ->
  934. erl_syntax:variable("Var_" ++ Iterator ++ PostFix)
  935. end, IteratorList),
  936. {{InnerAst, Info}, TreeWalker2} = body_ast(Contents,
  937. Context#dtl_context{local_scopes = [
  938. [{'forloop', erl_syntax:variable("Counters")} | lists:map(
  939. fun({identifier, _, Iterator}) ->
  940. {list_to_atom(Iterator), erl_syntax:variable("Var_" ++ Iterator ++ PostFix)}
  941. end, IteratorList)] | Context#dtl_context.local_scopes]}, TreeWalker),
  942. CounterAst = erl_syntax:application(erl_syntax:atom(erlydtl_runtime),
  943. erl_syntax:atom(increment_counter_stats), [erl_syntax:variable("Counters")]),
  944. {{LoopValueAst, LoopValueInfo}, TreeWalker3} = value_ast(LoopValue, false, Context, TreeWalker2),
  945. ListAst = erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(to_list), [LoopValueAst, z_context_ast(Context)]),
  946. ListVarAst = erl_syntax:variable("LoopVar_"++z_ids:identifier()),
  947. CounterVars0 = case resolve_scoped_variable_ast("forloop", Context) of
  948. undefined ->
  949. erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(init_counter_stats), [ListVarAst]);
  950. ForLoopValue ->
  951. erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(init_counter_stats), [ListVarAst, ForLoopValue])
  952. end,
  953. ForLoopF = fun(BaseListAst) -> erl_syntax:application(
  954. erl_syntax:atom('erlang'), erl_syntax:atom('element'),
  955. [erl_syntax:integer(1), erl_syntax:application(
  956. erl_syntax:atom('lists'), erl_syntax:atom('mapfoldl'),
  957. [erl_syntax:fun_expr([
  958. erl_syntax:clause([erl_syntax:tuple(Vars), erl_syntax:variable("Counters")], none,
  959. [erl_syntax:tuple([InnerAst, CounterAst])]),
  960. erl_syntax:clause(case Vars of [H] -> [H, erl_syntax:variable("Counters")];
  961. _ -> [erl_syntax:list(Vars), erl_syntax:variable("Counters")] end, none,
  962. [erl_syntax:tuple([InnerAst, CounterAst])])
  963. ]),
  964. CounterVars0, BaseListAst])])
  965. end,
  966. {CompleteForLoopAst, Info2, TreeWalker4} = case EmptyPartContents of
  967. none ->
  968. {ForLoopF(ListVarAst), Info, TreeWalker3};
  969. _ ->
  970. {{EmptyPartAst, EmptyPartInfo}, EmptyWalker} = body_ast(EmptyPartContents, Context, TreeWalker3),
  971. LAst = erl_syntax:variable("L_"++z_ids:identifier()),
  972. EmptyClauseAst = erl_syntax:clause(
  973. [erl_syntax:list([])],
  974. none,
  975. [EmptyPartAst]),
  976. LoopClauseAst = erl_syntax:clause(
  977. [LAst],
  978. none,
  979. [ForLoopF(LAst)]),
  980. {erl_syntax:case_expr(ListVarAst, [EmptyClauseAst, LoopClauseAst]), merge_info(Info,EmptyPartInfo), EmptyWalker}
  981. end,
  982. ForBlockAst = erl_syntax:block_expr([
  983. erl_syntax:match_expr(ListVarAst, ListAst),
  984. CompleteForLoopAst]),
  985. {{ForBlockAst, merge_info(LoopValueInfo, Info2)}, TreeWalker4}.
  986. load_ast(Names, _Context, TreeWalker) ->
  987. CustomTags = lists:merge([X || {identifier, _ , X} <- Names], TreeWalker#treewalker.custom_tags),
  988. {{erl_syntax:list([]), #ast_info{}}, TreeWalker#treewalker{custom_tags = CustomTags}}.
  989. cycle_ast(Names, Context, TreeWalker) ->
  990. {NamesTuple, TreeWalker1} = lists:foldr(
  991. fun
  992. ({string_literal, _, Str}, {Acc,TW}) ->
  993. {[ erl_syntax:string(unescape_string_literal(Str)) | Acc], TW};
  994. ({trans_literal, _, Str}, {Acc,TW}) ->
  995. {[ trans_literal_ast(Str, Context) | Acc ], TW};
  996. ({atom_literal, _, Str}, {Acc,TW}) ->
  997. {[ erl_syntax:atom(to_atom(unescape_string_literal(Str))) | Acc], TW};
  998. ({number_literal, _, Num}, {Acc,TW}) ->
  999. V = format(erl_syntax:integer(Num), Context),
  1000. {[ V | Acc ], TW};
  1001. ({variable, _}=Var, {Acc,TW}) ->
  1002. {{V, _VarName, _VarInfo},TW2} = resolve_variable_ast(Var, Context, TW),
  1003. {[ V | Acc ], TW2};
  1004. ({auto_id, Name}, {Acc,TW}) ->
  1005. {{V, _}, TW1} = auto_id_ast(Name, Context, TW),
  1006. {[ V |Acc ], TW1};
  1007. (_, {Acc,TW}) ->
  1008. {[ erl_syntax:atom(undefined) | Acc ], TW}
  1009. end,
  1010. {[],TreeWalker},
  1011. Names),
  1012. {{erl_syntax:application(
  1013. erl_syntax:atom('erlydtl_runtime'), erl_syntax:atom('cycle'),
  1014. [erl_syntax:tuple(NamesTuple), erl_syntax:variable("Counters"), z_context_ast(Context)]), #ast_info{}}, TreeWalker1}.
  1015. %% Older Django templates treat cycle with comma-delimited elements as strings
  1016. cycle_compat_ast(Names, Context, TreeWalker) ->
  1017. NamesTuple = [erl_syntax:string(X) || {identifier, _, X} <- Names],
  1018. {{erl_syntax:application(
  1019. erl_syntax:atom('erlydtl_runtime'), erl_syntax:atom('cycle'),
  1020. [erl_syntax:tuple(NamesTuple), erl_syntax:variable("Counters"), z_context_ast(Context)]), #ast_info{}}, TreeWalker}.
  1021. %% @doc Output the trans record with the translation call to z_trans.
  1022. %% author: Marc Worrell
  1023. trans_ast(TransLiteral, Context, TreeWalker) ->
  1024. % Remove the first and the last character, these were separating the string from the {_ and _} tokens
  1025. {{trans_ast1(z_string:trim(TransLiteral), Context), #ast_info{}}, TreeWalker}.
  1026. trans_ext_ast(String, Args, Context, TreeWalker) ->
  1027. Lit = unescape_string_literal(String, [], noslash),
  1028. ArgsTrans = [ trans_arg(A) || A <- Args ],
  1029. {{trans_ast1({trans, [{en,Lit}|ArgsTrans]}, Context), #ast_info{}}, TreeWalker}.
  1030. trans_arg({{identifier,_,Lang}, {string_literal,_,String}}) ->
  1031. {list_to_atom(Lang), String}.
  1032. trans_literal_ast(String, Context) ->
  1033. Lit = unescape_string_literal(String),
  1034. trans_ast1(Lit, Context).
  1035. %% @doc Fetch the translations and put them into the compiled template. We will need to
  1036. %% re-compile templates when translations are changed.
  1037. trans_ast1(Arg, Context) ->
  1038. case z_trans:translations(Arg, Context#dtl_context.z_context) of
  1039. {trans, Tr} ->
  1040. Tr1 = [ {z_convert:to_atom(Lang), z_convert:to_binary(S)} || {Lang,S} <- Tr ],
  1041. erl_syntax:application(
  1042. erl_syntax:atom(z_trans),
  1043. erl_syntax:atom(trans),
  1044. [
  1045. erl_syntax:abstract({trans, Tr1}),
  1046. z_context_ast(Context)
  1047. ]);
  1048. S when is_binary(S) ->
  1049. erl_syntax:abstract(S);
  1050. L when is_list(L) ->
  1051. erl_syntax:abstract(list_to_binary(L))
  1052. end.
  1053. now_ast(FormatString, Context, TreeWalker) ->
  1054. % Note: we can't use unescape_string_literal here
  1055. % because we want to allow escaping in the format string.
  1056. {{erl_syntax:application(
  1057. erl_syntax:atom(erlydtl_dateformat),
  1058. erl_syntax:atom(format),
  1059. [erl_syntax:string(FormatString), z_context_ast(Context)]),
  1060. #ast_info{}}, TreeWalker}.
  1061. unescape_string_literal(String) ->
  1062. unescape_string_literal(String, [], noslash).
  1063. unescape_string_literal([], Acc, noslash) ->
  1064. lists:reverse(Acc);
  1065. unescape_string_literal([$\\ | Rest], Acc, noslash) ->
  1066. unescape_string_literal(Rest, Acc, slash);
  1067. unescape_string_literal([C | Rest], Acc, noslash) ->
  1068. unescape_string_literal(Rest, [C | Acc], noslash);
  1069. unescape_string_literal("n" ++ Rest, Acc, slash) ->
  1070. unescape_string_literal(Rest, [$\n | Acc], noslash);
  1071. unescape_string_literal("r" ++ Rest, Acc, slash) ->
  1072. unescape_string_literal(Rest, [$\r | Acc], noslash);
  1073. unescape_string_literal("t" ++ Rest, Acc, slash) ->
  1074. unescape_string_literal(Rest, [$\t | Acc], noslash);
  1075. unescape_string_literal([C | Rest], Acc, slash) ->
  1076. unescape_string_literal(Rest, [C | Acc], noslash).
  1077. to_atom(B) when is_binary(B) ->
  1078. list_to_atom(binary_to_list(B));
  1079. to_atom(L) when is_list(L) ->
  1080. list_to_atom(L);
  1081. to_atom(A) when is_atom(A) ->
  1082. A.
  1083. full_path(File, Context) ->
  1084. case full_path(File, false, Context) of
  1085. [Filename] -> {ok, Filename};
  1086. [] -> {error, enoent}
  1087. end.
  1088. full_path(File, All, Context) ->
  1089. case Context#dtl_context.finder of
  1090. undefined ->
  1091. case Context#dtl_context.z_context of
  1092. undefined -> [];
  1093. ZContext -> z_template:find_template(File, All, ZContext)
  1094. end;
  1095. FinderFun ->
  1096. FinderFun(File, All)
  1097. end.
  1098. %%-------------------------------------------------------------------
  1099. %% Custom tags
  1100. %%-------------------------------------------------------------------
  1101. tag_ast(Name, Args, All, Context, TreeWalker) ->
  1102. case lists:member(Name, TreeWalker#treewalker.custom_tags) of
  1103. true ->
  1104. {InterpretedArgs, TreeWalker1} = interpreted_args(Args, Context, TreeWalker),
  1105. DefaultFilePath = filename:join([erlydtl_deps:get_base_dir(), "priv", "custom_tags", Name]),
  1106. case Context#dtl_context.custom_tags_dir of
  1107. [] ->
  1108. case parse(DefaultFilePath, Context) of
  1109. {ok, TagParseTree} ->
  1110. tag_ast2(DefaultFilePath, TagParseTree, InterpretedArgs, Context, TreeWalker1);
  1111. _ ->
  1112. Reason = lists:concat(["Loading tag source for '", Name, "' failed: ",
  1113. DefaultFilePath]),
  1114. throw({error, Reason})
  1115. end;
  1116. _ ->
  1117. CustomFilePath = filename:join([Context#dtl_context.custom_tags_dir, Name]),
  1118. case parse(CustomFilePath, Context) of
  1119. {ok, TagParseTree} ->
  1120. tag_ast2(CustomFilePath,TagParseTree, InterpretedArgs, Context, TreeWalker1);
  1121. _ ->
  1122. case parse(DefaultFilePath, Context) of
  1123. {ok, TagParseTree} ->
  1124. tag_ast2(DefaultFilePath, TagParseTree, InterpretedArgs, Context, TreeWalker1);
  1125. _ ->
  1126. Reason = lists:concat(["Loading tag source for '", Name, "' failed: ",
  1127. CustomFilePath, ", ", DefaultFilePath]),
  1128. throw({error, Reason})
  1129. end
  1130. end
  1131. end;
  1132. _ ->
  1133. % Dynamic scomp call
  1134. % throw({error, lists:concat(["Custom tag '", Name, "' not loaded"])})
  1135. scomp_ast(Name, Args, All, Context, TreeWalker)
  1136. end.
  1137. tag_ast2(Source, TagParseTree, InterpretedArgs, Context, TreeWalker) ->
  1138. with_dependency(Source, body_ast(TagParseTree, Context#dtl_context{
  1139. local_scopes = [ InterpretedArgs | Context#dtl_context.local_scopes ],
  1140. parse_trail = [ Source | Context#dtl_context.parse_trail ]}, TreeWalker)).
  1141. call_ast(Module, Args, Context, TreeWalker) ->
  1142. {ArgsAst, TreeWalker1} = scomp_ast_list_args(Args, Context, TreeWalker),
  1143. call_ast(Module, ArgsAst, #ast_info{}, Context, TreeWalker1).
  1144. call_with_ast(Module, Variable, Context, TreeWalker) ->
  1145. {{VarAst, VarName, VarInfo}, TreeWalker1} = resolve_variable_ast(Variable, Context, TreeWalker),
  1146. call_ast(Module, VarAst, merge_info(VarInfo, #ast_info{var_names=[VarName]}), Context, TreeWalker1).
  1147. call_ast(Module, ArgAst, AstInfo, Context, TreeWalker) ->
  1148. AppAst = erl_syntax:application(
  1149. erl_syntax:atom(Module),
  1150. erl_syntax:atom(render),
  1151. [ ArgAst,
  1152. erl_syntax:variable("Variables"),
  1153. z_context_ast(Context)
  1154. ]),
  1155. RenderedAst = erl_syntax:variable("Rendered"),
  1156. OkAst = erl_syntax:clause(
  1157. [erl_syntax:tuple([erl_syntax:atom(ok), RenderedAst])],
  1158. none,
  1159. [RenderedAst]),
  1160. ReasonAst = erl_syntax:variable("Reason"),
  1161. ErrStrAst = erl_syntax:application(
  1162. erl_syntax:atom(io_lib),
  1163. erl_syntax:atom(format),
  1164. [erl_syntax:string("error: ~p"), erl_syntax:list([ReasonAst])]),
  1165. ErrorAst = erl_syntax:clause(
  1166. [erl_syntax:tuple([erl_syntax:atom(error), ReasonAst])],
  1167. none,
  1168. [ErrStrAst]),
  1169. CallAst = erl_syntax:case_expr(AppAst, [OkAst, ErrorAst]),
  1170. {{CallAst, AstInfo}, TreeWalker}.
  1171. %% @doc Generate html to show the media tag. This is different in that the image tag can only
  1172. %% display images using the <img /> tag. This can also generate complete media viewers.
  1173. %% Author: Marc Worrell
  1174. %% @todo Optimization for the situation where all parameters are constants
  1175. media_ast(FilenameValue, Args, Context, TreeWalker) ->
  1176. FilenameAst = resolve_value_ast(FilenameValue, Context, TreeWalker),
  1177. {ArgsAst, TreeWalker1} = scomp_ast_list_args(Args, Context, TreeWalker),
  1178. AppAst = erl_syntax:application(
  1179. erl_syntax:atom(z_media_tag),
  1180. erl_syntax:atom(viewer),
  1181. [ FilenameAst,
  1182. ArgsAst,
  1183. z_context_ast(Context)
  1184. ]
  1185. ),
  1186. RenderedAst = erl_syntax:variable("Rendered"),
  1187. OkAst = erl_syntax:clause(
  1188. [erl_syntax:tuple([erl_syntax:atom(ok), RenderedAst])],
  1189. none,
  1190. [RenderedAst]),
  1191. ReasonAst = erl_syntax:variable("Reason"),
  1192. ErrStrAst = erl_syntax:application(
  1193. erl_syntax:atom(io_lib),
  1194. erl_syntax:atom(format),
  1195. [erl_syntax:string("error: ~p"), erl_syntax:list([ReasonAst])]),
  1196. ErrorAst = erl_syntax:clause(
  1197. [erl_syntax:tuple([erl_syntax:atom(error), ReasonAst])],
  1198. none,
  1199. [ErrStrAst]),
  1200. CallAst = erl_syntax:case_expr(AppAst, [OkAst, ErrorAst]),
  1201. {{CallAst, #ast_info{}}, TreeWalker1}.
  1202. %% @doc Generate an image tag based on the image name and the arguments
  1203. %% Author: Marc Worrell
  1204. %% @todo Optimization for the situation where all parameters are constants
  1205. image_ast(FilenameValue, Args, Context, TreeWalker) ->
  1206. FilenameAst = resolve_value_ast(FilenameValue, Context, TreeWalker),
  1207. {ArgsAst, TreeWalker1} = scomp_ast_list_args(Args, Context, TreeWalker),
  1208. AppAst = erl_syntax:application(
  1209. erl_syntax:atom(z_media_tag),
  1210. erl_syntax:atom(tag),
  1211. [ FilenameAst,
  1212. ArgsAst,
  1213. z_context_ast(Context)
  1214. ]
  1215. ),
  1216. RenderedAst = erl_syntax:variable("Rendered"),
  1217. OkAst = erl_syntax:clause(
  1218. [erl_syntax:tuple([erl_syntax:atom(ok), RenderedAst])],
  1219. none,
  1220. [RenderedAst]),
  1221. ReasonAst = erl_syntax:variable("Reason"),
  1222. ErrStrAst = erl_syntax:application(
  1223. erl_syntax:atom(io_lib),
  1224. erl_syntax:atom(format),
  1225. [erl_syntax:string("error: ~p"), erl_syntax:list([ReasonAst])]),
  1226. ErrorAst = erl_syntax:clause(
  1227. [erl_syntax:tuple([erl_syntax:atom(error), ReasonAst])],
  1228. none,
  1229. [ErrStrAst]),
  1230. CallAst = erl_syntax:case_expr(AppAst, [OkAst, ErrorAst]),
  1231. {{CallAst, #ast_info{}}, TreeWalker1}.
  1232. %% @doc Generate an image url based on the image name and the arguments
  1233. %% Author: Marc Worrell
  1234. %% @todo Optimization for the situation where all parameters are constants
  1235. image_url_ast(FilenameValue, Args, Context, TreeWalker) ->
  1236. FilenameAst = resolve_value_ast(FilenameValue, Context, TreeWalker),
  1237. {ArgsAst, TreeWalker1} = scomp_ast_list_args(Args, Context, TreeWalker),
  1238. AppAst = erl_syntax:application(
  1239. erl_syntax:atom(z_media_tag),
  1240. erl_syntax:atom(url),
  1241. [ FilenameAst,
  1242. ArgsAst,
  1243. z_context_ast(Context)
  1244. ]
  1245. ),
  1246. RenderedAst = erl_syntax:variable("Rendered"),
  1247. OkAst = erl_syntax:clause(
  1248. [erl_syntax:tuple([erl_syntax:atom(ok), RenderedAst])],
  1249. none,
  1250. [RenderedAst]),
  1251. ReasonAst = erl_syntax:variable("Reason"),
  1252. ErrStrAst = erl_syntax:application(
  1253. erl_syntax:atom(io_lib),
  1254. erl_syntax:atom(format),
  1255. [erl_syntax:string("error: ~p"), erl_syntax:list([ReasonAst])]),
  1256. ErrorAst = erl_syntax:clause(
  1257. [erl_syntax:tuple([erl_syntax:atom(error), ReasonAst])],
  1258. none,
  1259. [ErrStrAst]),
  1260. CallAst = erl_syntax:case_expr(AppAst, [OkAst, ErrorAst]),
  1261. {{CallAst, #ast_info{}}, TreeWalker1}.
  1262. %% Added by Marc Worrell - handle url generation using the url patterns
  1263. url_ast(Name, Args, Context, TreeWalker) ->
  1264. % Check if the 'escape' argument is there
  1265. {ArgsAst, TreeWalker1} = scomp_ast_list_args(Args, Context, TreeWalker),
  1266. AppAst = erl_syntax:application(
  1267. erl_syntax:atom(z_dispatcher),
  1268. erl_syntax:atom(url_for),
  1269. [ erl_syntax:atom(Name),
  1270. ArgsAst,
  1271. z_context_ast(Context)
  1272. ]
  1273. ),
  1274. {{AppAst, #ast_info{}}, TreeWalker1}.
  1275. print_ast(Value, Context, TreeWalker) ->
  1276. ValueAst = resolve_value_ast(Value, Context, TreeWalker),
  1277. PrintAst = erl_syntax:application(
  1278. erl_syntax:atom(io_lib),
  1279. erl_syntax:atom(format),
  1280. [ erl_syntax:string("~p"),
  1281. erl_syntax:list([ValueAst])
  1282. ]
  1283. ),
  1284. FlattenAst = erl_syntax:application(
  1285. erl_syntax:atom(lists),
  1286. erl_syntax:atom(flatten),
  1287. [PrintAst]
  1288. ),
  1289. EscapeAst = erl_syntax:application(
  1290. erl_syntax:atom(mochiweb_html),
  1291. erl_syntax:atom(escape),
  1292. [FlattenAst]
  1293. ),
  1294. PreAst = erl_syntax:list([
  1295. erl_syntax:string("<pre>"),
  1296. EscapeAst,
  1297. erl_syntax:string("</pre>")
  1298. ]),
  1299. {{PreAst, #ast_info{}}, TreeWalker}.
  1300. lib_ast(LibList, Args, Context, TreeWalker) ->
  1301. Libs = [ unescape_string_literal(V) || {string_literal, _, V} <- LibList ],
  1302. LibsAst = erl_syntax:list([ erl_syntax:string(L) || L <- Libs ]),
  1303. {ArgList, TreeWalker1} = scomp_ast_list_args(Args, Context, TreeWalker),
  1304. Ast = erl_syntax:application(
  1305. erl_syntax:atom(z_lib_include),
  1306. erl_syntax:atom(tag),
  1307. [ LibsAst,
  1308. ArgList,
  1309. z_context_ast(Context)
  1310. ]),
  1311. {{Ast, #ast_info{}}, TreeWalker1}.
  1312. cache_ast(MaxAge, Args, Body, Context, TreeWalker) ->
  1313. {Name, Args1} = case Args of
  1314. [{{identifier, _, Ident}, true}|RestArgs] when Ident =/= "if_anonymous" -> {Ident, RestArgs};
  1315. _ -> {z_ids:id(), Args}
  1316. end,
  1317. MaxAge1 = case MaxAge of
  1318. {number_literal, _, Value} -> list_to_integer(Value);
  1319. undefined -> 0
  1320. end,
  1321. {ArgsAst, ArgsTreeWalker} = scomp_ast_list_args(Args1, Context, TreeWalker),
  1322. ContextVarAst = erl_syntax:variable("Ctx_" ++ z_ids:identifier(10)),
  1323. {{BodyAst, BodyInfo}, BodyTreeWalker} = body_ast(
  1324. Body,
  1325. Context#dtl_context{local_scopes = [ [{'ZpContext', ContextVarAst}] | Context#dtl_context.local_scopes ]},
  1326. ArgsTreeWalker),
  1327. FuncAst = erl_syntax:fun_expr([
  1328. erl_syntax:clause(
  1329. [ ContextVarAst ],
  1330. none,
  1331. [ BodyAst ]
  1332. )
  1333. ]),
  1334. CacheAst = erl_syntax:application(
  1335. erl_syntax:atom(erlydtl_runtime),
  1336. erl_syntax:atom(cache),
  1337. [ erl_syntax:integer(MaxAge1),
  1338. erl_syntax:atom(list_to_atom("$tpl$" ++ Name)),
  1339. ArgsAst,
  1340. FuncAst,
  1341. z_context_ast(Context)]
  1342. ),
  1343. {{CacheAst, BodyInfo}, BodyTreeWalker}.
  1344. resolve_value_ast(Value, Context, TreeWalker) ->
  1345. {{Ast,_Info},_TreeWalker} = value_ast(Value, false, Context, TreeWalker),
  1346. Ast.
  1347. %% Added by Marc Worrell - handle evaluation of scomps by z_scomp
  1348. scomp_ast(ScompName, Args, false = _All, Context, TreeWalker) ->
  1349. {ArgsAst, TreeWalker1} = scomp_ast_list_args(Args, Context, TreeWalker),
  1350. AppAst = erl_syntax:application(
  1351. erl_syntax:atom(z_scomp),
  1352. erl_syntax:atom(render),
  1353. [ erl_syntax:atom(ScompName),
  1354. ArgsAst,
  1355. erl_syntax:variable("Variables"),
  1356. z_context_ast(Context)
  1357. ]
  1358. ),
  1359. RenderedAst = erl_syntax:variable("Rendered"),
  1360. CleanedAst = erl_syntax:application(
  1361. erl_syntax:atom(z_context),
  1362. erl_syntax:atom(prune_for_template),
  1363. [RenderedAst]
  1364. ),
  1365. OkAst = erl_syntax:clause(
  1366. [erl_syntax:tuple([erl_syntax:atom(ok), RenderedAst])],
  1367. none,
  1368. [CleanedAst]),
  1369. ReasonAst = erl_syntax:variable("Reason"),
  1370. ErrStrAst = erl_syntax:application(
  1371. erl_syntax:atom(io_lib),
  1372. erl_syntax:atom(format),
  1373. [erl_syntax:string("error: ~p"), erl_syntax:list([ReasonAst])]),
  1374. ErrorAst = erl_syntax:clause(
  1375. [erl_syntax:tuple([erl_syntax:atom(error), ReasonAst])],
  1376. none,
  1377. [ErrStrAst]),
  1378. CallAst = erl_syntax:case_expr(AppAst, [OkAst, ErrorAst]),
  1379. {{CallAst, #ast_info{}}, TreeWalker1};
  1380. scomp_ast(ScompName, Args, true, Context, TreeWalker) ->
  1381. {ArgsAst, TreeWalker1} = scomp_ast_list_args(Args, Context, TreeWalker),
  1382. AppAst = erl_syntax:application(
  1383. erl_syntax:atom(z_scomp),
  1384. erl_syntax:atom(render_all),
  1385. [ erl_syntax:atom(ScompName),
  1386. ArgsAst,
  1387. erl_syntax:variable("Variables"),
  1388. z_context_ast(Context)
  1389. ]
  1390. ),
  1391. {{AppAst, #ast_info{}}, TreeWalker1}.
  1392. scomp_ast_list_args(Args, Context, TreeWalker) ->
  1393. {ArgsAst, TreeWalker1}= interpreted_args(Args, Context, TreeWalker),
  1394. PropListAst = [ erl_syntax:tuple([erl_syntax:atom(A), B]) || {A,B} <- ArgsAst ],
  1395. { erl_syntax:list(PropListAst), TreeWalker1}.
  1396. %% lists:append(AutoId,"-Name")
  1397. auto_id_ast({identifier, _, Name}, Context, TreeWalker) ->
  1398. {{ erl_syntax:application(
  1399. erl_syntax:atom(lists), erl_syntax:atom(append),
  1400. [resolve_scoped_variable_ast("$autoid", Context), erl_syntax:string([$-|Name])]),
  1401. #ast_info{}
  1402. }, TreeWalker#treewalker{has_auto_id=true}};
  1403. auto_id_ast({{identifier, _, Name}, {identifier, _, _} = Var}, Context, TreeWalker) ->
  1404. {{V, _, VarInfo}, TreeWalker1} = resolve_variable_ast({variable, Var}, Context, TreeWalker),
  1405. {{ erl_syntax:application(
  1406. erl_syntax:atom(lists), erl_syntax:atom(append),
  1407. [
  1408. erl_syntax:list([
  1409. resolve_scoped_variable_ast("$autoid", Context),
  1410. erl_syntax:string([$-|Name]++"-"),
  1411. erl_syntax:application(
  1412. erl_syntax:atom(z_convert),
  1413. erl_syntax:atom(to_list),
  1414. [V])
  1415. ])
  1416. ]),
  1417. VarInfo
  1418. }, TreeWalker1#treewalker{has_auto_id=true}}.
  1419. interpreted_args(Args, Context, TreeWalker) ->
  1420. lists:foldr(
  1421. fun
  1422. ({{identifier, _, "postback"}, {Literal, _, Value}}, {Acc, TW}) when Literal == string_literal; Literal == trans_literal ->
  1423. % string postbacks are always translated to atoms
  1424. { [ {list_to_atom("postback"), erl_syntax:atom(unescape_string_literal(Value))} | Acc ], TW };
  1425. ({{identifier, _, Key}, Value}, {Acc, TW}) ->
  1426. % a normal key=value argument
  1427. {Ast, TW1} = interpreted_argval(Value, Context, TW),
  1428. { [ {list_to_atom(Key), Ast} | Acc ], TW1 }
  1429. end,
  1430. {[], TreeWalker},
  1431. Args).
  1432. interpreted_argval({number_literal, _, Value}, _Context, TreeWalker) ->
  1433. {erl_syntax:integer(list_to_integer(Value)), TreeWalker};
  1434. interpreted_argval({string_literal, _, Value}, _Context, TreeWalker) ->
  1435. {erl_syntax:string(unescape_string_literal(Value)), TreeWalker};
  1436. interpreted_argval({atom_literal, _, Value}, _Context, TreeWalker) ->
  1437. {erl_syntax:atom(to_atom(unescape_string_literal(Value))), TreeWalker};
  1438. interpreted_argval({trans_literal, _, Value}, Context, TreeWalker) ->
  1439. {trans_literal_ast(Value, Context), TreeWalker};
  1440. interpreted_argval({auto_id, Name}, Context, TreeWalker) ->
  1441. {{V, _}, TreeWalker1} = auto_id_ast(Name, Context, TreeWalker),
  1442. {V, TreeWalker1};
  1443. interpreted_argval({tuple_value, {identifier, _, TupleName}, TupleArgs}, Context, TreeWalker) ->
  1444. {ArgList, TreeWalker1} = scomp_ast_list_args(TupleArgs, Context, TreeWalker),
  1445. {erl_syntax:tuple([erl_syntax:atom(TupleName), ArgList]), TreeWalker1};
  1446. interpreted_argval({value_list, Values}, Context, TreeWalker) ->
  1447. {List, TreeWalker1} = lists:foldr(
  1448. fun(V, {Acc, TW}) ->
  1449. {VAst, TW1} = interpreted_argval(V, Context, TW),
  1450. {[VAst|Acc], TW1}
  1451. end,
  1452. {[], TreeWalker},
  1453. Values
  1454. ),
  1455. {erl_syntax:list(List), TreeWalker1};
  1456. interpreted_argval(true, _Context, TreeWalker) ->
  1457. {erl_syntax:atom(true), TreeWalker};
  1458. interpreted_argval(Value, Context, TreeWalker) ->
  1459. {{Ast, _VarName, _VarInfo}, TreeWalker1} = resolve_variable_ast(Value, Context, TreeWalker),
  1460. {Ast, TreeWalker1}.
  1461. notify(_Msg, #context{host=test}) ->
  1462. nop;
  1463. notify(Msg, ZContext) ->
  1464. z_notifier:notify(Msg, ZContext).