erlydtl_compiler.erl 97 KB

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