erlydtl_compiler.erl 90 KB

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