erlydtl_compiler.erl 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. %%%-------------------------------------------------------------------
  2. %%% File: erlydtl_compiler.erl
  3. %%% @author Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
  4. %%% @author Evan Miller <emmiller@gmail.com>
  5. %%% @copyright 2008 Roberto Saccon, Evan Miller
  6. %%% @doc
  7. %%% ErlyDTL template compiler
  8. %%% @end
  9. %%%
  10. %%% The MIT License
  11. %%%
  12. %%% Copyright (c) 2007 Roberto Saccon, Evan Miller
  13. %%%
  14. %%% Permission is hereby granted, free of charge, to any person obtaining a copy
  15. %%% of this software and associated documentation files (the "Software"), to deal
  16. %%% in the Software without restriction, including without limitation the rights
  17. %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  18. %%% copies of the Software, and to permit persons to whom the Software is
  19. %%% furnished to do so, subject to the following conditions:
  20. %%%
  21. %%% The above copyright notice and this permission notice shall be included in
  22. %%% all copies or substantial portions of the Software.
  23. %%%
  24. %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  25. %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  26. %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  27. %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  28. %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  29. %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  30. %%% THE SOFTWARE.
  31. %%%
  32. %%% @since 2007-12-16 by Roberto Saccon, Evan Miller
  33. %%%-------------------------------------------------------------------
  34. -module(erlydtl_compiler).
  35. -author('rsaccon@gmail.com').
  36. -author('emmiller@gmail.com').
  37. %% --------------------------------------------------------------------
  38. %% Definitions
  39. %% --------------------------------------------------------------------
  40. -export([compile/2, compile/3]).
  41. -record(dtl_context, {
  42. local_scopes = [],
  43. block_dict = dict:new(),
  44. auto_escape = off,
  45. doc_root = "",
  46. parse_trail = [],
  47. vars = [],
  48. custom_tags_dir = [],
  49. reader = {file, read_file},
  50. module = [],
  51. compiler_options = [verbose, report_errors],
  52. force_recompile = false}).
  53. -record(ast_info, {
  54. dependencies = [],
  55. var_names = [],
  56. pre_render_asts = []}).
  57. -record(treewalker, {
  58. counter = 0,
  59. custom_tags = []}).
  60. compile(Binary, Module) when is_binary(Binary) ->
  61. compile(Binary, Module, []);
  62. compile(File, Module) ->
  63. compile(File, Module, []).
  64. compile(Binary, Module, Options) when is_binary(Binary) ->
  65. File = "",
  66. CheckSum = "",
  67. case parse(Binary) of
  68. {ok, DjangoParseTree} ->
  69. case compile_to_binary(File, DjangoParseTree,
  70. init_dtl_context(File, Module, Options), CheckSum) of
  71. {ok, Module1, _} ->
  72. {ok, Module1};
  73. Err ->
  74. Err
  75. end;
  76. Err ->
  77. Err
  78. end;
  79. compile(File, Module, Options) ->
  80. crypto:start(),
  81. Context = init_dtl_context(File, Module, Options),
  82. case parse(File, Context) of
  83. ok ->
  84. ok;
  85. {ok, DjangoParseTree, CheckSum} ->
  86. case compile_to_binary(File, DjangoParseTree, Context, CheckSum) of
  87. {ok, Module1, Bin} ->
  88. OutDir = proplists:get_value(out_dir, Options, "ebin"),
  89. BeamFile = filename:join([OutDir, atom_to_list(Module1) ++ ".beam"]),
  90. case file:write_file(BeamFile, Bin) of
  91. ok ->
  92. ok;
  93. {error, Reason} ->
  94. {error, lists:concat(["beam generation failed (", Reason, "): ", BeamFile])}
  95. end;
  96. Err ->
  97. Err
  98. end;
  99. Err ->
  100. Err
  101. end.
  102. %%====================================================================
  103. %% Internal functions
  104. %%====================================================================
  105. compile_to_binary(File, DjangoParseTree, Context, CheckSum) ->
  106. try body_ast(DjangoParseTree, Context, #treewalker{}) of
  107. {{Ast, Info}, _} ->
  108. case compile:forms(forms(File, Context#dtl_context.module, Ast, Info, CheckSum),
  109. Context#dtl_context.compiler_options) of
  110. {ok, Module1, Bin} ->
  111. code:purge(Module1),
  112. case code:load_binary(Module1, atom_to_list(Module1) ++ ".erl", Bin) of
  113. {module, _} -> {ok, Module1, Bin};
  114. _ -> {error, lists:concat(["code reload failed: ", Module1])}
  115. end;
  116. error ->
  117. {error, lists:concat(["compilation failed: ", File])};
  118. OtherError ->
  119. OtherError
  120. end
  121. catch
  122. throw:Error -> Error
  123. end.
  124. init_dtl_context(File, Module, Options) when is_list(Module) ->
  125. init_dtl_context(File, list_to_atom(Module), Options);
  126. init_dtl_context(File, Module, Options) ->
  127. Ctx = #dtl_context{},
  128. #dtl_context{
  129. parse_trail = [File],
  130. module = Module,
  131. doc_root = proplists:get_value(doc_root, Options, filename:dirname(File)),
  132. custom_tags_dir = proplists:get_value(custom_tags_dir, Options, Ctx#dtl_context.custom_tags_dir),
  133. vars = proplists:get_value(vars, Options, Ctx#dtl_context.vars),
  134. reader = proplists:get_value(reader, Options, Ctx#dtl_context.reader),
  135. compiler_options = proplists:get_value(compiler_options, Options, Ctx#dtl_context.compiler_options),
  136. force_recompile = proplists:get_value(force_recompile, Options, Ctx#dtl_context.force_recompile)}.
  137. is_up_to_date(_, #dtl_context{force_recompile = true}) ->
  138. false;
  139. is_up_to_date(CheckSum, Context) ->
  140. Module = Context#dtl_context.module,
  141. {M, F} = Context#dtl_context.reader,
  142. case catch Module:source() of
  143. {_, CheckSum} ->
  144. case catch Module:dependencies() of
  145. L when is_list(L) ->
  146. RecompileList = lists:foldl(fun
  147. ({XFile, XCheckSum}, Acc) ->
  148. case catch M:F(XFile) of
  149. {ok, Data} ->
  150. case binary_to_list(crypto:sha(Data)) of
  151. XCheckSum ->
  152. Acc;
  153. _ ->
  154. [recompile | Acc]
  155. end;
  156. _ ->
  157. [recompile | Acc]
  158. end
  159. end, [], L),
  160. case RecompileList of
  161. [] -> true;
  162. _ -> false
  163. end;
  164. _ ->
  165. false
  166. end;
  167. _ ->
  168. false
  169. end.
  170. parse(File, Context) ->
  171. {M, F} = Context#dtl_context.reader,
  172. case catch M:F(File) of
  173. {ok, Data} ->
  174. CheckSum = binary_to_list(crypto:sha(Data)),
  175. parse(CheckSum, Data, Context);
  176. _ ->
  177. {error, "reading " ++ File ++ " failed "}
  178. end.
  179. parse(CheckSum, Data, Context) ->
  180. case is_up_to_date(CheckSum, Context) of
  181. true ->
  182. ok;
  183. _ ->
  184. case parse(Data) of
  185. {ok, Val} ->
  186. {ok, Val, CheckSum};
  187. Err ->
  188. Err
  189. end
  190. end.
  191. parse(Data) ->
  192. case erlydtl_scanner:scan(binary_to_list(Data)) of
  193. {ok, Tokens} ->
  194. erlydtl_parser:parse(Tokens);
  195. Err ->
  196. Err
  197. end.
  198. forms(File, Module, BodyAst, BodyInfo, CheckSum) ->
  199. Render0FunctionAst = erl_syntax:function(erl_syntax:atom(render),
  200. [erl_syntax:clause([], none, [erl_syntax:application(none,
  201. erl_syntax:atom(render), [erl_syntax:list([])])])]),
  202. Function2 = erl_syntax:application(none, erl_syntax:atom(render2),
  203. [erl_syntax:variable("Variables")]),
  204. ClauseOk = erl_syntax:clause([erl_syntax:variable("Val")], none,
  205. [erl_syntax:tuple([erl_syntax:atom(ok), erl_syntax:variable("Val")])]),
  206. ClauseCatch = erl_syntax:clause([erl_syntax:variable("Err")], none,
  207. [erl_syntax:tuple([erl_syntax:atom(error), erl_syntax:variable("Err")])]),
  208. Render1FunctionAst = erl_syntax:function(erl_syntax:atom(render),
  209. [erl_syntax:clause([erl_syntax:variable("Variables")], none,
  210. [erl_syntax:try_expr([Function2], [ClauseOk], [ClauseCatch])])]),
  211. SourceFunctionTuple = erl_syntax:tuple(
  212. [erl_syntax:string(File), erl_syntax:string(CheckSum)]),
  213. SourceFunctionAst = erl_syntax:function(
  214. erl_syntax:atom(source),
  215. [erl_syntax:clause([], none, [SourceFunctionTuple])]),
  216. DependenciesFunctionAst = erl_syntax:function(
  217. erl_syntax:atom(dependencies), [erl_syntax:clause([], none,
  218. [erl_syntax:list(lists:map(fun
  219. ({XFile, XCheckSum}) ->
  220. erl_syntax:tuple([erl_syntax:string(XFile), erl_syntax:string(XCheckSum)])
  221. end, BodyInfo#ast_info.dependencies))])]),
  222. RenderInternalFunctionAst = erl_syntax:function(
  223. erl_syntax:atom(render2),
  224. [erl_syntax:clause([erl_syntax:variable("Variables")], none,
  225. [BodyAst])]),
  226. ModuleAst = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
  227. ExportAst = erl_syntax:attribute(erl_syntax:atom(export),
  228. [erl_syntax:list([erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(0)),
  229. erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(1)),
  230. erl_syntax:arity_qualifier(erl_syntax:atom(source), erl_syntax:integer(0)),
  231. erl_syntax:arity_qualifier(erl_syntax:atom(dependencies), erl_syntax:integer(0))])]),
  232. [erl_syntax:revert(X) || X <- [ModuleAst, ExportAst, Render0FunctionAst,
  233. Render1FunctionAst, SourceFunctionAst, DependenciesFunctionAst, RenderInternalFunctionAst
  234. | BodyInfo#ast_info.pre_render_asts]].
  235. % child templates should only consist of blocks at the top level
  236. body_ast([{extends, {string_literal, _Pos, String}} | ThisParseTree], Context, TreeWalker) ->
  237. File = full_path(unescape_string_literal(String), Context#dtl_context.doc_root),
  238. case lists:member(File, Context#dtl_context.parse_trail) of
  239. true ->
  240. throw({error, "Circular file inclusion!"});
  241. _ ->
  242. case parse(File, Context) of
  243. {ok, ParentParseTree, CheckSum} ->
  244. BlockDict = lists:foldl(
  245. fun
  246. ({block, {identifier, _, Name}, Contents}, Dict) ->
  247. dict:store(Name, Contents, Dict);
  248. (_, Dict) ->
  249. Dict
  250. end, dict:new(), ThisParseTree),
  251. with_dependency({File, CheckSum}, body_ast(ParentParseTree, Context#dtl_context{
  252. block_dict = dict:merge(fun(_Key, _ParentVal, ChildVal) -> ChildVal end,
  253. BlockDict, Context#dtl_context.block_dict),
  254. parse_trail = [File | Context#dtl_context.parse_trail]}, TreeWalker));
  255. Err ->
  256. throw(Err)
  257. end
  258. end;
  259. body_ast(DjangoParseTree, Context, TreeWalker) ->
  260. {AstInfoList, TreeWalker2} = lists:mapfoldl(
  261. fun
  262. ({'block', {identifier, _, Name}, Contents}, TreeWalkerAcc) ->
  263. Block = case dict:find(Name, Context#dtl_context.block_dict) of
  264. {ok, ChildBlock} ->
  265. ChildBlock;
  266. _ ->
  267. Contents
  268. end,
  269. body_ast(Block, Context, TreeWalkerAcc);
  270. ({'comment', _Contents}, TreeWalkerAcc) ->
  271. empty_ast(TreeWalkerAcc);
  272. ({'autoescape', {identifier, _, OnOrOff}, Contents}, TreeWalkerAcc) ->
  273. body_ast(Contents, Context#dtl_context{auto_escape = list_to_atom(OnOrOff)},
  274. TreeWalkerAcc);
  275. ({'text', _Pos, String}, TreeWalkerAcc) ->
  276. string_ast(String, TreeWalkerAcc);
  277. ({'string_literal', _Pos, String}, TreeWalkerAcc) ->
  278. {{auto_escape(erl_syntax:string(unescape_string_literal(String)), Context),
  279. #ast_info{}}, TreeWalkerAcc};
  280. ({'number_literal', _Pos, Number}, TreeWalkerAcc) ->
  281. string_ast(Number, TreeWalkerAcc);
  282. ({'attribute', _} = Variable, TreeWalkerAcc) ->
  283. {Ast, VarName} = resolve_variable_ast(Variable, Context),
  284. {{format(Ast, Context), #ast_info{var_names = [VarName]}}, TreeWalkerAcc};
  285. ({'variable', _} = Variable, TreeWalkerAcc) ->
  286. {Ast, VarName} = resolve_variable_ast(Variable, Context),
  287. {{format(Ast, Context), #ast_info{var_names = [VarName]}}, TreeWalkerAcc};
  288. ({'include', {string_literal, _, File}}, TreeWalkerAcc) ->
  289. include_ast(unescape_string_literal(File), Context, TreeWalkerAcc);
  290. ({'if', {'not', Variable}, Contents}, TreeWalkerAcc) ->
  291. {IfAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
  292. {ElseAstInfo, TreeWalker2} = body_ast(Contents, Context, TreeWalker1),
  293. ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  294. ({'if', Variable, Contents}, TreeWalkerAcc) ->
  295. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  296. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  297. ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  298. ({'ifelse', {'not', Variable}, IfContents, ElseContents}, TreeWalkerAcc) ->
  299. {IfAstInfo, TreeWalker1} = body_ast(ElseContents, Context, TreeWalkerAcc),
  300. {ElseAstInfo, TreeWalker2} = body_ast(IfContents, Context, TreeWalker1),
  301. ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  302. ({'ifelse', Variable, IfContents, ElseContents}, TreeWalkerAcc) ->
  303. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  304. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
  305. ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  306. ({'ifequal', Args, Contents}, TreeWalkerAcc) ->
  307. {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
  308. {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
  309. ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  310. ({'ifequalelse', Args, IfContents, ElseContents}, TreeWalkerAcc) ->
  311. {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
  312. {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context,TreeWalker1),
  313. ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  314. ({'ifnotequal', Args, Contents}, TreeWalkerAcc) ->
  315. {IfAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
  316. {ElseAstInfo, TreeWalker2} = body_ast(Contents, Context, TreeWalker1),
  317. ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  318. ({'ifnotequalelse', Args, IfContents, ElseContents}, TreeWalkerAcc) ->
  319. {IfAstInfo, TreeWalker1} = body_ast(ElseContents, Context, TreeWalkerAcc),
  320. {ElseAstInfo, TreeWalker2} = body_ast(IfContents, Context, TreeWalker1),
  321. ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
  322. ({'apply_filter', Variable, Filter}, TreeWalkerAcc) ->
  323. filter_ast(Variable, Filter, Context, TreeWalkerAcc);
  324. ({'for', {'in', IteratorList, Variable}, Contents}, TreeWalkerAcc) ->
  325. for_loop_ast(IteratorList, Variable, Contents, Context, TreeWalkerAcc);
  326. ({'load', Names}, TreeWalkerAcc) ->
  327. load_ast(Names, Context, TreeWalkerAcc);
  328. ({'tag', {identifier, _, Name}, Args}, TreeWalkerAcc) ->
  329. tag_ast(Name, Args, Context, TreeWalkerAcc);
  330. ({'call', {'identifier', _, Name}}, TreeWalkerAcc) ->
  331. call_ast(Name, TreeWalkerAcc);
  332. ({'call', {'identifier', _, Name}, With}, TreeWalkerAcc) ->
  333. call_with_ast(Name, With, Context, TreeWalkerAcc)
  334. end, TreeWalker, DjangoParseTree),
  335. {AstList, {Info, TreeWalker3}} = lists:mapfoldl(
  336. fun({Ast, Info}, {InfoAcc, TreeWalkerAcc}) ->
  337. PresetVars = lists:foldl(fun
  338. (X, Acc) ->
  339. case proplists:lookup(list_to_atom(X), Context#dtl_context.vars) of
  340. none ->
  341. Acc;
  342. Val ->
  343. [erl_syntax:abstract(Val) | Acc]
  344. end
  345. end, [], Info#ast_info.var_names),
  346. case PresetVars of
  347. [] ->
  348. {Ast, {merge_info(Info, InfoAcc), TreeWalkerAcc}};
  349. _ ->
  350. Counter = TreeWalkerAcc#treewalker.counter,
  351. Name = lists:concat([pre_render, Counter]),
  352. Ast1 = erl_syntax:application(none, erl_syntax:atom(Name),
  353. [erl_syntax:list(PresetVars)]),
  354. PreRenderAst = erl_syntax:function(erl_syntax:atom(Name),
  355. [erl_syntax:clause([erl_syntax:variable("Variables")], none, [Ast])]),
  356. PreRenderAsts = Info#ast_info.pre_render_asts,
  357. Info1 = Info#ast_info{pre_render_asts = [PreRenderAst | PreRenderAsts]},
  358. {Ast1, {merge_info(Info1, InfoAcc), TreeWalkerAcc#treewalker{counter = Counter + 1}}}
  359. end
  360. end, {#ast_info{}, TreeWalker2}, AstInfoList),
  361. {{erl_syntax:list(AstList), Info}, TreeWalker3}.
  362. merge_info(Info1, Info2) ->
  363. #ast_info{dependencies =
  364. lists:merge(
  365. lists:sort(Info1#ast_info.dependencies),
  366. lists:sort(Info2#ast_info.dependencies)),
  367. var_names =
  368. lists:merge(
  369. lists:sort(Info1#ast_info.var_names),
  370. lists:sort(Info2#ast_info.var_names)),
  371. pre_render_asts =
  372. lists:merge(
  373. Info1#ast_info.pre_render_asts,
  374. Info2#ast_info.pre_render_asts)}.
  375. with_dependencies([], Args) ->
  376. Args;
  377. with_dependencies([H, T], Args) ->
  378. with_dependencies(T, with_dependency(H, Args)).
  379. with_dependency(FilePath, {{Ast, Info}, TreeWalker}) ->
  380. {{Ast, Info#ast_info{dependencies = [FilePath | Info#ast_info.dependencies]}}, TreeWalker}.
  381. empty_ast(TreeWalker) ->
  382. {{erl_syntax:list([]), #ast_info{}}, TreeWalker}.
  383. string_ast(String, TreeWalker) ->
  384. {{erl_syntax:string(String), #ast_info{}}, TreeWalker}. %% less verbose AST, better for development and debugging
  385. % {{erl_syntax:binary([erl_syntax:binary_field(erl_syntax:integer(X)) || X <- String]), #ast_info{}}, TreeWalker}.
  386. include_ast(File, Context, TreeWalker) ->
  387. FilePath = full_path(File, Context#dtl_context.doc_root),
  388. case parse(FilePath, Context) of
  389. {ok, InclusionParseTree, CheckSum} ->
  390. with_dependency({FilePath, CheckSum}, body_ast(InclusionParseTree, Context#dtl_context{
  391. parse_trail = [FilePath | Context#dtl_context.parse_trail]}, TreeWalker));
  392. Err ->
  393. throw(Err)
  394. end.
  395. filter_ast(Variable, Filter, Context, TreeWalker) ->
  396. % the escape filter is special; it is always applied last, so we have to go digging for it
  397. % AutoEscape = 'did' means we (will have) decided whether to escape the current variable,
  398. % so don't do any more escaping
  399. {{UnescapedAst, Info}, TreeWalker2} = filter_ast_noescape(Variable, Filter,
  400. Context#dtl_context{auto_escape = did}, TreeWalker),
  401. case search_for_escape_filter(Variable, Filter, Context) of
  402. on ->
  403. {{erl_syntax:application(
  404. erl_syntax:atom(erlydtl_filters),
  405. erl_syntax:atom(force_escape),
  406. [UnescapedAst]),
  407. Info}, TreeWalker2};
  408. _ ->
  409. {{UnescapedAst, Info}, TreeWalker2}
  410. end.
  411. filter_ast_noescape(Variable, [{identifier, _, "escape"}], Context, TreeWalker) ->
  412. body_ast([Variable], Context, TreeWalker);
  413. filter_ast_noescape(Variable, [{identifier, _, Name} | Arg], Context, TreeWalker) ->
  414. {{VariableAst, Info}, TreeWalker2} = body_ast([Variable], Context, TreeWalker),
  415. {{erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(Name),
  416. [VariableAst | case Arg of
  417. [{string_literal, _, ArgName}] ->
  418. [erl_syntax:string(unescape_string_literal(ArgName))];
  419. [{number_literal, _, ArgName}] ->
  420. [erl_syntax:integer(list_to_integer(ArgName))];
  421. _ ->
  422. []
  423. end]), Info}, TreeWalker2}.
  424. search_for_escape_filter(_, _, #dtl_context{auto_escape = on}) ->
  425. on;
  426. search_for_escape_filter(_, _, #dtl_context{auto_escape = did}) ->
  427. off;
  428. search_for_escape_filter(Variable, Filter, _) ->
  429. search_for_escape_filter(Variable, Filter).
  430. search_for_escape_filter(_, [{identifier, _, "escape"}]) ->
  431. on;
  432. search_for_escape_filter({apply_filter, Variable, Filter}, _) ->
  433. search_for_escape_filter(Variable, Filter);
  434. search_for_escape_filter(_Variable, _Filter) ->
  435. off.
  436. resolve_variable_ast(VarTuple, Context) ->
  437. resolve_variable_ast(VarTuple, Context, 'fetch_value').
  438. resolve_ifvariable_ast(VarTuple, Context) ->
  439. resolve_variable_ast(VarTuple, Context, 'find_value').
  440. resolve_variable_ast({attribute, {{identifier, _, AttrName}, Variable}}, Context, FinderFunction) ->
  441. {VarAst, VarName} = resolve_variable_ast(Variable, Context, FinderFunction),
  442. {erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(FinderFunction),
  443. [erl_syntax:atom(AttrName), VarAst]), VarName};
  444. resolve_variable_ast({variable, {identifier, _, VarName}}, Context, FinderFunction) ->
  445. VarValue = case resolve_scoped_variable_ast(VarName, Context) of
  446. undefined ->
  447. erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(FinderFunction),
  448. [erl_syntax:atom(VarName), erl_syntax:variable("Variables")]);
  449. Val ->
  450. Val
  451. end,
  452. {VarValue, VarName}.
  453. resolve_scoped_variable_ast(VarName, Context) ->
  454. lists:foldl(fun(Scope, Value) ->
  455. case Value of
  456. undefined -> proplists:get_value(list_to_atom(VarName), Scope);
  457. _ -> Value
  458. end
  459. end, undefined, Context#dtl_context.local_scopes).
  460. format(Ast, Context) ->
  461. auto_escape(format_integer_ast(Ast), Context).
  462. format_integer_ast(Ast) ->
  463. erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(format_integer),
  464. [Ast]).
  465. auto_escape(Value, Context) ->
  466. case Context#dtl_context.auto_escape of
  467. on ->
  468. erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(force_escape),
  469. [Value]);
  470. _ ->
  471. Value
  472. end.
  473. ifelse_ast(Variable, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
  474. Info = merge_info(IfContentsInfo, ElseContentsInfo),
  475. VarNames = Info#ast_info.var_names,
  476. {Ast, VarName} = resolve_ifvariable_ast(Variable, Context),
  477. {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(is_false), [Ast]),
  478. [erl_syntax:clause([erl_syntax:atom(true)], none,
  479. [ElseContentsAst]),
  480. erl_syntax:clause([erl_syntax:underscore()], none,
  481. [IfContentsAst])
  482. ]), Info#ast_info{var_names = [VarName | VarNames]}}, TreeWalker}.
  483. ifequalelse_ast(Args, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
  484. Info = merge_info(IfContentsInfo, ElseContentsInfo),
  485. {[Arg1Ast, Arg2Ast], VarNames} = lists:foldl(fun
  486. (X, {Asts, AccVarNames}) ->
  487. case X of
  488. {string_literal, _, Literal} ->
  489. {[erl_syntax:string(unescape_string_literal(Literal)) | Asts], AccVarNames};
  490. {number_literal, _, Literal} ->
  491. {[erl_syntax:integer(list_to_integer(Literal)) | Asts], AccVarNames};
  492. Variable ->
  493. {Ast, VarName} = resolve_ifvariable_ast(Variable, Context),
  494. {[Ast | Asts], [VarName | AccVarNames]}
  495. end
  496. end,
  497. {[], Info#ast_info.var_names},
  498. Args),
  499. Ast = erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(are_equal),
  500. [Arg1Ast, Arg2Ast]),
  501. [
  502. erl_syntax:clause([erl_syntax:atom(true)], none, [IfContentsAst]),
  503. erl_syntax:clause([erl_syntax:underscore()], none, [ElseContentsAst])
  504. ]),
  505. {{Ast, Info#ast_info{var_names = VarNames}}, TreeWalker}.
  506. for_loop_ast(IteratorList, Variable, Contents, Context, TreeWalker) ->
  507. Vars = lists:map(fun({identifier, _, Iterator}) ->
  508. erl_syntax:variable("Var_" ++ Iterator)
  509. end, IteratorList),
  510. {{InnerAst, Info}, TreeWalker2} = body_ast(Contents,
  511. Context#dtl_context{local_scopes = [
  512. [{'forloop', erl_syntax:variable("Counters")} | lists:map(
  513. fun({identifier, _, Iterator}) ->
  514. {list_to_atom(Iterator), erl_syntax:variable("Var_" ++ Iterator)}
  515. end, IteratorList)] | Context#dtl_context.local_scopes]}, TreeWalker),
  516. CounterAst = erl_syntax:application(erl_syntax:atom(erlydtl_runtime),
  517. erl_syntax:atom(increment_counter_stats), [erl_syntax:variable("Counters")]),
  518. {ListAst, VarName} = resolve_variable_ast(Variable, Context),
  519. CounterVars0 = case resolve_scoped_variable_ast("forloop", Context) of
  520. undefined ->
  521. erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(init_counter_stats), [ListAst]);
  522. Value ->
  523. erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(init_counter_stats), [ListAst, Value])
  524. end,
  525. {{erl_syntax:application(
  526. erl_syntax:atom('erlang'), erl_syntax:atom('element'),
  527. [erl_syntax:integer(1), erl_syntax:application(
  528. erl_syntax:atom('lists'), erl_syntax:atom('mapfoldl'),
  529. [erl_syntax:fun_expr([
  530. erl_syntax:clause([erl_syntax:tuple(Vars), erl_syntax:variable("Counters")], none,
  531. [erl_syntax:tuple([InnerAst, CounterAst])]),
  532. erl_syntax:clause(case Vars of [H] -> [H, erl_syntax:variable("Counters")];
  533. _ -> [erl_syntax:list(Vars), erl_syntax:variable("Counters")] end, none,
  534. [erl_syntax:tuple([InnerAst, CounterAst])])
  535. ]),
  536. CounterVars0, ListAst])]),
  537. Info#ast_info{var_names = [VarName]}}, TreeWalker2}.
  538. load_ast(Names, _Context, TreeWalker) ->
  539. CustomTags = lists:merge([X || {identifier, _ , X} <- Names], TreeWalker#treewalker.custom_tags),
  540. {{erl_syntax:list([]), #ast_info{}}, TreeWalker#treewalker{custom_tags = CustomTags}}.
  541. unescape_string_literal(String) ->
  542. unescape_string_literal(string:strip(String, both, 34), [], noslash).
  543. unescape_string_literal([], Acc, noslash) ->
  544. lists:reverse(Acc);
  545. unescape_string_literal([$\\ | Rest], Acc, noslash) ->
  546. unescape_string_literal(Rest, Acc, slash);
  547. unescape_string_literal([C | Rest], Acc, noslash) ->
  548. unescape_string_literal(Rest, [C | Acc], noslash);
  549. unescape_string_literal("n" ++ Rest, Acc, slash) ->
  550. unescape_string_literal(Rest, [$\n | Acc], noslash);
  551. unescape_string_literal("r" ++ Rest, Acc, slash) ->
  552. unescape_string_literal(Rest, [$\r | Acc], noslash);
  553. unescape_string_literal("t" ++ Rest, Acc, slash) ->
  554. unescape_string_literal(Rest, [$\t | Acc], noslash);
  555. unescape_string_literal([C | Rest], Acc, slash) ->
  556. unescape_string_literal(Rest, [C | Acc], noslash).
  557. full_path(File, DocRoot) ->
  558. filename:join([DocRoot, File]).
  559. %%-------------------------------------------------------------------
  560. %% Custom tags
  561. %%-------------------------------------------------------------------
  562. tag_ast(Name, Args, Context, TreeWalker) ->
  563. case lists:member(Name, TreeWalker#treewalker.custom_tags) of
  564. true ->
  565. InterpretedArgs = lists:map(fun
  566. ({{identifier, _, Key}, {string_literal, _, Value}}) ->
  567. {list_to_atom(Key), erl_syntax:string(unescape_string_literal(Value))};
  568. ({{identifier, _, Key}, Value}) ->
  569. {list_to_atom(Key), format(resolve_variable_ast(Value, Context), Context)}
  570. end, Args),
  571. DefaultFilePath = filename:join([erlydtl_deps:get_base_dir(), "priv", "custom_tags", Name]),
  572. case Context#dtl_context.custom_tags_dir of
  573. [] ->
  574. case parse(DefaultFilePath, Context) of
  575. {ok, TagParseTree, CheckSum} ->
  576. tag_ast2({DefaultFilePath, CheckSum}, TagParseTree, InterpretedArgs, Context, TreeWalker);
  577. _ ->
  578. Reason = lists:concat(["Loading tag source for '", Name, "' failed: ",
  579. DefaultFilePath]),
  580. throw({error, Reason})
  581. end;
  582. _ ->
  583. CustomFilePath = filename:join([Context#dtl_context.custom_tags_dir, Name]),
  584. case parse(CustomFilePath, Context) of
  585. {ok, TagParseTree, CheckSum} ->
  586. tag_ast2({CustomFilePath, CheckSum},TagParseTree, InterpretedArgs, Context, TreeWalker);
  587. _ ->
  588. case parse(DefaultFilePath, Context) of
  589. {ok, TagParseTree, CheckSum} ->
  590. tag_ast2({DefaultFilePath, CheckSum}, TagParseTree, InterpretedArgs, Context, TreeWalker);
  591. _ ->
  592. Reason = lists:concat(["Loading tag source for '", Name, "' failed: ",
  593. CustomFilePath, ", ", DefaultFilePath]),
  594. throw({error, Reason})
  595. end
  596. end
  597. end;
  598. _ ->
  599. throw({error, lists:concat(["Custom tag '", Name, "' not loaded"])})
  600. end.
  601. tag_ast2({Source, _} = Dep, TagParseTree, InterpretedArgs, Context, TreeWalker) ->
  602. with_dependency(Dep, body_ast(TagParseTree, Context#dtl_context{
  603. local_scopes = [ InterpretedArgs | Context#dtl_context.local_scopes ],
  604. parse_trail = [ Source | Context#dtl_context.parse_trail ]}, TreeWalker)).
  605. call_ast(Module, TreeWalkerAcc) ->
  606. call_ast(Module, erl_syntax:variable("Variables"), #ast_info{}, TreeWalkerAcc).
  607. call_with_ast(Module, Variable, Context, TreeWalker) ->
  608. {VarAst, VarName} = resolve_variable_ast(Variable, Context),
  609. call_ast(Module, VarAst, #ast_info{var_names=[VarName]}, TreeWalker).
  610. call_ast(Module, Variable, AstInfo, TreeWalker) ->
  611. AppAst = erl_syntax:application(
  612. erl_syntax:atom(Module),
  613. erl_syntax:atom(render),
  614. [Variable]),
  615. RenderedAst = erl_syntax:variable("Rendered"),
  616. OkAst = erl_syntax:clause(
  617. [erl_syntax:tuple([erl_syntax:atom(ok), RenderedAst])],
  618. none,
  619. [RenderedAst]),
  620. ReasonAst = erl_syntax:variable("Reason"),
  621. ErrStrAst = erl_syntax:application(
  622. erl_syntax:atom(io_lib),
  623. erl_syntax:atom(format),
  624. [erl_syntax:string("error: ~p"), erl_syntax:list([ReasonAst])]),
  625. ErrorAst = erl_syntax:clause(
  626. [erl_syntax:tuple([erl_syntax:atom(error), ReasonAst])],
  627. none,
  628. [ErrStrAst]),
  629. CallAst = erl_syntax:case_expr(AppAst, [OkAst, ErrorAst]),
  630. Module2 = list_to_atom(Module),
  631. with_dependencies(Module2:dependencies(), {{CallAst, AstInfo}, TreeWalker}).