erlydtl_compiler.erl 96 KB

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