erlydtl_scanner.erl 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. %%%-------------------------------------------------------------------
  2. %%% File: erlydtl_scanner.erl
  3. %%% @author Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
  4. %%% @author Evan Miller <emmiller@gmail.com>
  5. %%% @author Andreas Stenius <kaos@astekk.se>
  6. %%% @copyright 2008 Roberto Saccon, Evan Miller
  7. %%% @doc
  8. %%% Template language scanner
  9. %%% @end
  10. %%%
  11. %%% The MIT License
  12. %%%
  13. %%% Copyright (c) 2007 Roberto Saccon, Evan Miller
  14. %%%
  15. %%% Permission is hereby granted, free of charge, to any person obtaining a copy
  16. %%% of this software and associated documentation files (the "Software"), to deal
  17. %%% in the Software without restriction, including without limitation the rights
  18. %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  19. %%% copies of the Software, and to permit persons to whom the Software is
  20. %%% furnished to do so, subject to the following conditions:
  21. %%%
  22. %%% The above copyright notice and this permission notice shall be included in
  23. %%% all copies or substantial portions of the Software.
  24. %%%
  25. %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  26. %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  27. %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  28. %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  29. %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  30. %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  31. %%% THE SOFTWARE.
  32. %%%
  33. %%% @since 2007-11-11 by Roberto Saccon, Evan Miller
  34. %%%-------------------------------------------------------------------
  35. -module(erlydtl_scanner).
  36. -author('rsaccon@gmail.com').
  37. -author('emmiller@gmail.com').
  38. -author('Andreas Stenius <kaos@astekk.se>').
  39. -export([scan/1, resume/1]).
  40. -include("erlydtl_ext.hrl").
  41. %%====================================================================
  42. %% API
  43. %%====================================================================
  44. %%--------------------------------------------------------------------
  45. %% @spec scan(T::template()) -> {ok, S::tokens()} | {error, Reason}
  46. %% @type template() = string() | binary(). Template to parse
  47. %% @type tokens() = [tuple()].
  48. %% @doc Scan the template string T and return the a token list or
  49. %% an error.
  50. %% @end
  51. %%--------------------------------------------------------------------
  52. scan(Template) ->
  53. scan(Template, [], {1, 1}, in_text).
  54. resume(#scanner_state{ template=Template, scanned=Scanned,
  55. pos=Pos, state=State}) ->
  56. scan(Template, Scanned, Pos, State).
  57. scan([], Scanned, _, in_text) ->
  58. Tokens = lists:reverse(Scanned),
  59. FixedTokens = reverse_strings(Tokens),
  60. MarkedTokens = mark_keywords(FixedTokens),
  61. AtomizedTokens = atomize_identifiers(MarkedTokens),
  62. {ok, AtomizedTokens};
  63. scan([], _Scanned, _, {in_comment, _}) ->
  64. {error, "Reached end of file inside a comment."};
  65. scan([], _Scanned, _, _) ->
  66. {error, "Reached end of file inside a code block."};
  67. scan("<!--{{" ++ T, Scanned, {Row, Column}, in_text) ->
  68. scan(T, [{open_var, {Row, Column}, '<!--{{'} | Scanned], {Row, Column + length("<!--{{")}, {in_code, "}}-->"});
  69. scan("{{" ++ T, Scanned, {Row, Column}, in_text) ->
  70. scan(T, [{open_var, {Row, Column}, '{{'} | Scanned], {Row, Column + length("{{")}, {in_code, "}}"});
  71. scan("<!--{#" ++ T, Scanned, {Row, Column}, in_text) ->
  72. scan(T, Scanned, {Row, Column + length("<!--{#")}, {in_comment, "#}-->"});
  73. scan("{#" ++ T, Scanned, {Row, Column}, in_text) ->
  74. scan(T, Scanned, {Row, Column + length("{#")}, {in_comment, "#}"});
  75. scan("#}-->" ++ T, Scanned, {Row, Column}, {in_comment, "#}-->"}) ->
  76. scan(T, Scanned, {Row, Column + length("#}-->")}, in_text);
  77. scan("#}" ++ T, Scanned, {Row, Column}, {in_comment, "#}"}) ->
  78. scan(T, Scanned, {Row, Column + length("#}")}, in_text);
  79. scan("<!--{%" ++ T, Scanned, {Row, Column}, in_text) ->
  80. scan(T, [{open_tag, {Row, Column}, '<!--{%'} | Scanned],
  81. {Row, Column + length("<!--{%")}, {in_code, "%}-->"});
  82. scan("{%" ++ T, Scanned, {Row, Column}, in_text) ->
  83. scan(T, [{open_tag, {Row, Column}, '{%'} | Scanned],
  84. {Row, Column + length("{%")}, {in_code, "%}"});
  85. scan([_ | T], Scanned, {Row, Column}, {in_comment, Closer}) ->
  86. scan(T, Scanned, {Row, Column + 1}, {in_comment, Closer});
  87. scan("\n" ++ T, Scanned, {Row, Column}, in_text) ->
  88. scan(T, append_text_char(Scanned, {Row, Column}, $\n), {Row + 1, 1}, in_text);
  89. scan([H | T], Scanned, {Row, Column}, in_text) ->
  90. scan(T, append_text_char(Scanned, {Row, Column}, H), {Row, Column + 1}, in_text);
  91. scan("\"" ++ T, Scanned, {Row, Column}, {in_code, Closer}) ->
  92. scan(T, [{string_literal, {Row, Column}, "\""} | Scanned], {Row, Column + 1}, {in_double_quote, Closer});
  93. scan("\"" ++ T, Scanned, {Row, Column}, {in_identifier, Closer}) ->
  94. scan(T, [{string_literal, {Row, Column}, "\""} | Scanned], {Row, Column + 1}, {in_double_quote, Closer});
  95. scan("\'" ++ T, Scanned, {Row, Column}, {in_code, Closer}) ->
  96. scan(T, [{string_literal, {Row, Column}, "\""} | Scanned], {Row, Column + 1}, {in_single_quote, Closer});
  97. scan("\'" ++ T, Scanned, {Row, Column}, {in_identifier, Closer}) ->
  98. scan(T, [{string_literal, {Row, Column}, "\""} | Scanned], {Row, Column + 1}, {in_single_quote, Closer});
  99. scan([$\\ | T], Scanned, {Row, Column}, {in_double_quote, Closer}) ->
  100. scan(T, append_char(Scanned, $\\), {Row, Column + 1}, {in_double_quote_slash, Closer});
  101. scan([H | T], Scanned, {Row, Column}, {in_double_quote_slash, Closer}) ->
  102. scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_double_quote, Closer});
  103. scan([$\\ | T], Scanned, {Row, Column}, {in_single_quote, Closer}) ->
  104. scan(T, append_char(Scanned, $\\), {Row, Column + 1}, {in_single_quote_slash, Closer});
  105. scan([H | T], Scanned, {Row, Column}, {in_single_quote_slash, Closer}) ->
  106. scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_single_quote, Closer});
  107. % end quote
  108. scan("\"" ++ T, Scanned, {Row, Column}, {in_double_quote, Closer}) ->
  109. scan(T, append_char(Scanned, 34), {Row, Column + 1}, {in_code, Closer});
  110. % treat single quotes the same as double quotes
  111. scan("\'" ++ T, Scanned, {Row, Column}, {in_single_quote, Closer}) ->
  112. scan(T, append_char(Scanned, 34), {Row, Column + 1}, {in_code, Closer});
  113. scan([H | T], Scanned, {Row, Column}, {in_double_quote, Closer}) ->
  114. scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_double_quote, Closer});
  115. scan([H | T], Scanned, {Row, Column}, {in_single_quote, Closer}) ->
  116. scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_single_quote, Closer});
  117. scan("}}-->" ++ T, Scanned, {Row, Column}, {_, "}}-->"}) ->
  118. scan(T, [{close_var, {Row, Column}, '}}-->'} | Scanned],
  119. {Row, Column + length("}}-->")}, in_text);
  120. scan("}}" ++ T, Scanned, {Row, Column}, {_, "}}"}) ->
  121. scan(T, [{close_var, {Row, Column}, '}}'} | Scanned], {Row, Column + 2}, in_text);
  122. scan("%}-->" ++ T, Scanned, {Row, Column}, {_, "%}-->"}) ->
  123. scan(T, [{close_tag, {Row, Column}, '%}-->'} | Scanned],
  124. {Row, Column + length("%}-->")}, in_text);
  125. scan("%}" ++ T, [{identifier, _, "mitabrev"}, {open_tag, _, '{%'}|Scanned], {Row, Column}, {_, "%}"}) ->
  126. scan(T, [{string, {Row, Column + 2}, ""}|Scanned], {Row, Column + 2}, {in_verbatim, undefined});
  127. scan("%}" ++ T, [{identifier, _, ReversedTag}, {identifier, _, "mitabrev"}, {open_tag, _, '{%'}|Scanned],
  128. {Row, Column}, {_, "%}"}) ->
  129. scan(T, [{string, {Row, Column + 2}, ""}|Scanned], {Row, Column + 2}, {in_verbatim, ReversedTag});
  130. scan("%}" ++ T, Scanned, {Row, Column}, {_, "%}"}) ->
  131. scan(T, [{close_tag, {Row, Column}, '%}'} | Scanned],
  132. {Row, Column + 2}, in_text);
  133. scan("{%" ++ T, Scanned, {Row, Column}, {in_verbatim, Tag}) ->
  134. scan(T, Scanned, {Row, Column + 2}, {in_verbatim_code, lists:reverse("{%"), Tag});
  135. scan(" " ++ T, Scanned, {Row, Column}, {in_verbatim_code, BackTrack, Tag}) ->
  136. scan(T, Scanned, {Row, Column + 1}, {in_verbatim_code, [$\ |BackTrack], Tag});
  137. scan("endverbatim%}" ++ T, Scanned, {Row, Column}, {in_verbatim_code, _BackTrack, undefined}) ->
  138. scan(T, Scanned, {Row, Column + length("endverbatim%}")}, in_text);
  139. scan("endverbatim " ++ T, Scanned, {Row, Column}, {in_verbatim_code, BackTrack, Tag}) ->
  140. scan(T, Scanned, {Row, Column + length("endverbatim ")},
  141. {in_endverbatim_code, "", lists:reverse("endverbatim ", BackTrack), Tag});
  142. scan(" " ++ T, Scanned, {Row, Column}, {in_endverbatim_code, "", BackTrack, Tag}) ->
  143. scan(T, Scanned, {Row, Column + 1}, {in_endverbatim_code, "", [$\ |BackTrack], Tag});
  144. scan([H|T], Scanned, {Row, Column}, {in_endverbatim_code, EndTag, BackTrack, Tag}) when H >= $a, H =< $z; H >= $0, H =< $9; H =:= $_ ->
  145. scan(T, Scanned, {Row, Column + 1}, {in_endverbatim_code, [H|EndTag], [H|BackTrack], Tag});
  146. scan(" " ++ T, Scanned, {Row, Column}, {in_endverbatim_code, Tag, BackTrack, Tag}) ->
  147. scan(T, Scanned, {Row, Column + 1}, {in_endverbatim_code, Tag, [$\ |BackTrack], Tag});
  148. scan("%}" ++ T, Scanned, {Row, Column}, {in_endverbatim_code, Tag, _BackTrack, Tag}) ->
  149. scan(T, Scanned, {Row, Column + 2}, in_text);
  150. scan("%}" ++ T, Scanned, {Row, Column}, {in_endverbatim_code, "", _BackTrack, undefined}) ->
  151. scan(T, Scanned, {Row, Column + 2}, in_text);
  152. scan([H|T], [{string, Pos, Data}|Scanned], {Row, Column}, {in_endverbatim_code, _, BackTrack, Tag}) ->
  153. NewPos = case H of $\n -> {Row + 1, 1}; _ -> {Row, Column + 1} end,
  154. scan(T, [{string, Pos, [H|BackTrack] ++ Data}|Scanned], NewPos, {in_verbatim, Tag});
  155. scan([H|T], [{string, Pos, Data}|Scanned], {Row, Column}, {in_verbatim_code, BackTrack, Tag}) ->
  156. NewPos = case H of $\n -> {Row + 1, 1}; _ -> {Row, Column + 1} end,
  157. scan(T, [{string, Pos, [H|BackTrack] ++ Data}|Scanned], NewPos, {in_verbatim, Tag});
  158. scan([H|T], [{string, Pos, Data}|Scanned], {Row, Column}, {in_verbatim, Tag}) ->
  159. NewPos = case H of $\n -> {Row + 1, 1}; _ -> {Row, Column + 1} end,
  160. scan(T, [{string, Pos, [H|Data]}|Scanned], NewPos, {in_verbatim, Tag});
  161. scan("==" ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  162. scan(T, [{'==', {Row, Column}} | Scanned], {Row, Column + 2}, {in_code, Closer});
  163. scan("!=" ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  164. scan(T, [{'!=', {Row, Column}} | Scanned], {Row, Column + 2}, {in_code, Closer});
  165. scan(">=" ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  166. scan(T, [{'>=', {Row, Column}} | Scanned], {Row, Column + 2}, {in_code, Closer});
  167. scan("<=" ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  168. scan(T, [{'<=', {Row, Column}} | Scanned], {Row, Column + 2}, {in_code, Closer});
  169. scan("<" ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  170. scan(T, [{'<', {Row, Column}} | Scanned], {Row, Column + 1}, {in_code, Closer});
  171. scan(">" ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  172. scan(T, [{'>', {Row, Column}} | Scanned], {Row, Column + 1}, {in_code, Closer});
  173. scan("("++ T, Scanned, {Row, Column}, {_, Closer}) ->
  174. scan(T, [{'(', {Row, Column}} | Scanned], {Row, Column + 1}, {in_code, Closer});
  175. scan(")" ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  176. scan(T, [{')', {Row, Column}} | Scanned], {Row, Column + 1}, {in_code, Closer});
  177. scan("," ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  178. scan(T, [{',', {Row, Column}} | Scanned], {Row, Column + 1}, {in_code, Closer});
  179. scan("|" ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  180. scan(T, [{'|', {Row, Column}} | Scanned], {Row, Column + 1}, {in_code, Closer});
  181. scan("=" ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  182. scan(T, [{'=', {Row, Column}} | Scanned], {Row, Column + 1}, {in_code, Closer});
  183. scan(":" ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  184. scan(T, [{':', {Row, Column}} | Scanned], {Row, Column + 1}, {in_code, Closer});
  185. scan("." ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  186. scan(T, [{'.', {Row, Column}} | Scanned], {Row, Column + 1}, {in_code, Closer});
  187. scan("_(" ++ T, Scanned, {Row, Column}, {in_code, Closer}) ->
  188. scan(T, lists:reverse([{'_', {Row, Column}}, {'(', {Row, Column + 1}}], Scanned), {Row, Column + 2}, {in_code, Closer});
  189. scan(" " ++ T, Scanned, {Row, Column}, {_, Closer}) ->
  190. scan(T, Scanned, {Row, Column + 1}, {in_code, Closer});
  191. scan([H | T], Scanned, {Row, Column}, {in_code, Closer}) ->
  192. case char_type(H) of
  193. letter_underscore ->
  194. scan(T, [{identifier, {Row, Column}, [H]} | Scanned], {Row, Column + 1}, {in_identifier, Closer});
  195. hyphen_minus ->
  196. scan(T, [{number_literal, {Row, Column}, [H]} | Scanned], {Row, Column + 1}, {in_number, Closer});
  197. digit ->
  198. scan(T, [{number_literal, {Row, Column}, [H]} | Scanned], {Row, Column + 1}, {in_number, Closer});
  199. _ ->
  200. {error, {Row, ?MODULE, lists:concat(["Illegal character in column ", Column])},
  201. #scanner_state{ template=[H|T], scanned=Scanned, pos={Row, Column}, state={in_code, Closer}}}
  202. end;
  203. scan([H | T], Scanned, {Row, Column}, {in_number, Closer}) ->
  204. case char_type(H) of
  205. digit ->
  206. scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_number, Closer});
  207. _ ->
  208. {error, {Row, ?MODULE, lists:concat(["Illegal character in column ", Column])},
  209. #scanner_state{ template=[H|T], scanned=Scanned, pos={Row, Column}, state={in_code, Closer}}}
  210. end;
  211. scan([H | T], Scanned, {Row, Column}, {in_identifier, Closer}) ->
  212. case char_type(H) of
  213. letter_underscore ->
  214. scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_identifier, Closer});
  215. digit ->
  216. scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_identifier, Closer});
  217. _ ->
  218. {error, {Row, ?MODULE, lists:concat(["Illegal character in column ", Column])},
  219. #scanner_state{ template=[H|T], scanned=Scanned, pos={Row, Column}, state={in_code, Closer}}}
  220. end.
  221. % internal functions
  222. append_char([{Type, Pos, Chars}|Scanned], Char) ->
  223. [{Type, Pos, [Char | Chars]} | Scanned].
  224. append_text_char([], {Row, Column}, Char) ->
  225. [{string, {Row, Column}, [Char]}];
  226. append_text_char([{string, StrPos, Chars} |Scanned1], _, Char) ->
  227. [{string, StrPos, [Char | Chars]} | Scanned1];
  228. append_text_char(Scanned, {Row, Column}, Char) ->
  229. [{string, {Row, Column}, [Char]} | Scanned].
  230. char_type(C) when ((C >= $a) andalso (C =< $z)) orelse ((C >= $A) andalso (C =< $Z)) orelse (C == $_) ->
  231. letter_underscore;
  232. char_type(C) when ((C >= $0) andalso (C =< $9)) ->
  233. digit;
  234. char_type($-) ->
  235. hyphen_minus;
  236. char_type(_C) ->
  237. undefined.
  238. reverse_strings(Tokens) ->
  239. reverse_strings(Tokens, []).
  240. reverse_strings([], Acc) ->
  241. lists:reverse(Acc);
  242. reverse_strings([{Category, Pos, String}|T], Acc) when Category =:= string; Category =:= identifier;
  243. Category =:= string_literal; Category =:= number_literal ->
  244. reverse_strings(T, [{Category, Pos, lists:reverse(String)}|Acc]);
  245. reverse_strings([Other|T], Acc) ->
  246. reverse_strings(T, [Other|Acc]).
  247. mark_keywords(Tokens) ->
  248. mark_keywords(Tokens, []).
  249. mark_keywords([], Acc) ->
  250. lists:reverse(Acc);
  251. mark_keywords([{identifier, Pos, "in" = String}|T], Acc) ->
  252. mark_keywords(T, [{in_keyword, Pos, String}|Acc]);
  253. mark_keywords([{identifier, Pos, "not" = String}|T], Acc) ->
  254. mark_keywords(T, [{not_keyword, Pos, String}|Acc]);
  255. mark_keywords([{identifier, Pos, "or" = String}|T], Acc) ->
  256. mark_keywords(T, [{or_keyword, Pos, String}|Acc]);
  257. mark_keywords([{identifier, Pos, "and" = String}|T], Acc) ->
  258. mark_keywords(T, [{and_keyword, Pos, String}|Acc]);
  259. mark_keywords([{identifier, Pos, "as" = String}|T], Acc) ->
  260. mark_keywords(T, [{as_keyword, Pos, String}|Acc]);
  261. mark_keywords([{identifier, Pos, "by" = String}|T], Acc) ->
  262. mark_keywords(T, [{by_keyword, Pos, String}|Acc]);
  263. mark_keywords([{identifier, Pos, "with" = String}|T], Acc) ->
  264. mark_keywords(T, [{with_keyword, Pos, String}|Acc]);
  265. % These must be succeeded by a close_tag
  266. mark_keywords([{identifier, Pos, "only" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  267. mark_keywords(T, lists:reverse([{only_keyword, Pos, String}, CloseTag], Acc));
  268. mark_keywords([{identifier, Pos, "parsed" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  269. mark_keywords(T, lists:reverse([{parsed_keyword, Pos, String}, CloseTag], Acc));
  270. mark_keywords([{identifier, Pos, "noop" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  271. mark_keywords(T, lists:reverse([{noop_keyword, Pos, String}, CloseTag], Acc));
  272. mark_keywords([{identifier, Pos, "reversed" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  273. mark_keywords(T, lists:reverse([{reversed_keyword, Pos, String}, CloseTag], Acc));
  274. mark_keywords([{identifier, Pos, "openblock" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  275. mark_keywords(T, lists:reverse([{openblock_keyword, Pos, String}, CloseTag], Acc));
  276. mark_keywords([{identifier, Pos, "closeblock" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  277. mark_keywords(T, lists:reverse([{closeblock_keyword, Pos, String}, CloseTag], Acc));
  278. mark_keywords([{identifier, Pos, "openvariable" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  279. mark_keywords(T, lists:reverse([{openvariable_keyword, Pos, String}, CloseTag], Acc));
  280. mark_keywords([{identifier, Pos, "closevariable" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  281. mark_keywords(T, lists:reverse([{closevariable_keyword, Pos, String}, CloseTag], Acc));
  282. mark_keywords([{identifier, Pos, "openbrace" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  283. mark_keywords(T, lists:reverse([{openbrace_keyword, Pos, String}, CloseTag], Acc));
  284. mark_keywords([{identifier, Pos, "closebrace" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  285. mark_keywords(T, lists:reverse([{closebrace_keyword, Pos, String}, CloseTag], Acc));
  286. mark_keywords([{identifier, Pos, "opencomment" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  287. mark_keywords(T, lists:reverse([{opencomment_keyword, Pos, String}, CloseTag], Acc));
  288. mark_keywords([{identifier, Pos, "closecomment" = String}, {close_tag, _, _} = CloseTag|T], Acc) ->
  289. mark_keywords(T, lists:reverse([{closecomment_keyword, Pos, String}, CloseTag], Acc));
  290. % The rest must be preceded by an open_tag.
  291. % This allows variables to have the same names as tags.
  292. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "autoescape" = String}|T], Acc) ->
  293. mark_keywords(T, lists:reverse([OpenToken, {autoescape_keyword, Pos, String}], Acc));
  294. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endautoescape" = String}|T], Acc) ->
  295. mark_keywords(T, lists:reverse([OpenToken, {endautoescape_keyword, Pos, String}], Acc));
  296. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "block" = String}|T], Acc) ->
  297. mark_keywords(T, lists:reverse([OpenToken, {block_keyword, Pos, String}], Acc));
  298. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endblock" = String}|T], Acc) ->
  299. mark_keywords(T, lists:reverse([OpenToken, {endblock_keyword, Pos, String}], Acc));
  300. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "comment" = String}|T], Acc) ->
  301. mark_keywords(T, lists:reverse([OpenToken, {comment_keyword, Pos, String}], Acc));
  302. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endcomment" = String}|T], Acc) ->
  303. mark_keywords(T, lists:reverse([OpenToken, {endcomment_keyword, Pos, String}], Acc));
  304. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "cycle" = String}|T], Acc) ->
  305. mark_keywords(T, lists:reverse([OpenToken, {cycle_keyword, Pos, String}], Acc));
  306. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "extends" = String}|T], Acc) ->
  307. mark_keywords(T, lists:reverse([OpenToken, {extends_keyword, Pos, String}], Acc));
  308. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "filter" = String}|T], Acc) ->
  309. mark_keywords(T, lists:reverse([OpenToken, {filter_keyword, Pos, String}], Acc));
  310. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endfilter" = String}|T], Acc) ->
  311. mark_keywords(T, lists:reverse([OpenToken, {endfilter_keyword, Pos, String}], Acc));
  312. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "firstof" = String}|T], Acc) ->
  313. mark_keywords(T, lists:reverse([OpenToken, {firstof_keyword, Pos, String}], Acc));
  314. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "for" = String}|T], Acc) ->
  315. mark_keywords(T, lists:reverse([OpenToken, {for_keyword, Pos, String}], Acc));
  316. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "empty" = String}|T], Acc) ->
  317. mark_keywords(T, lists:reverse([OpenToken, {empty_keyword, Pos, String}], Acc));
  318. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endfor" = String}|T], Acc) ->
  319. mark_keywords(T, lists:reverse([OpenToken, {endfor_keyword, Pos, String}], Acc));
  320. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "if" = String}|T], Acc) ->
  321. mark_keywords(T, lists:reverse([OpenToken, {if_keyword, Pos, String}], Acc));
  322. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "elif" = String}|T], Acc) ->
  323. mark_keywords(T, lists:reverse([OpenToken, {elif_keyword, Pos, String}], Acc));
  324. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "else" = String}|T], Acc) ->
  325. mark_keywords(T, lists:reverse([OpenToken, {else_keyword, Pos, String}], Acc));
  326. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endif" = String}|T], Acc) ->
  327. mark_keywords(T, lists:reverse([OpenToken, {endif_keyword, Pos, String}], Acc));
  328. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "ifchanged" = String}|T], Acc) ->
  329. mark_keywords(T, lists:reverse([OpenToken, {ifchanged_keyword, Pos, String}], Acc));
  330. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endifchanged" = String}|T], Acc) ->
  331. mark_keywords(T, lists:reverse([OpenToken, {endifchanged_keyword, Pos, String}], Acc));
  332. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "ifequal" = String}|T], Acc) ->
  333. mark_keywords(T, lists:reverse([OpenToken, {ifequal_keyword, Pos, String}], Acc));
  334. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endifequal" = String}|T], Acc) ->
  335. mark_keywords(T, lists:reverse([OpenToken, {endifequal_keyword, Pos, String}], Acc));
  336. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "ifnotequal" = String}|T], Acc) ->
  337. mark_keywords(T, lists:reverse([OpenToken, {ifnotequal_keyword, Pos, String}], Acc));
  338. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endifnotequal" = String}|T], Acc) ->
  339. mark_keywords(T, lists:reverse([OpenToken, {endifnotequal_keyword, Pos, String}], Acc));
  340. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "include" = String}|T], Acc) ->
  341. mark_keywords(T, lists:reverse([OpenToken, {include_keyword, Pos, String}], Acc));
  342. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "now" = String}|T], Acc) ->
  343. mark_keywords(T, lists:reverse([OpenToken, {now_keyword, Pos, String}], Acc));
  344. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "regroup" = String}|T], Acc) ->
  345. mark_keywords(T, lists:reverse([OpenToken, {regroup_keyword, Pos, String}], Acc));
  346. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endregroup" = String}|T], Acc) ->
  347. mark_keywords(T, lists:reverse([OpenToken, {endregroup_keyword, Pos, String}], Acc));
  348. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "spaceless" = String}|T], Acc) ->
  349. mark_keywords(T, lists:reverse([OpenToken, {spaceless_keyword, Pos, String}], Acc));
  350. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endspaceless" = String}|T], Acc) ->
  351. mark_keywords(T, lists:reverse([OpenToken, {endspaceless_keyword, Pos, String}], Acc));
  352. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "ssi" = String}|T], Acc) ->
  353. mark_keywords(T, lists:reverse([OpenToken, {ssi_keyword, Pos, String}], Acc));
  354. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "templatetag" = String}|T], Acc) ->
  355. mark_keywords(T, lists:reverse([OpenToken, {templatetag_keyword, Pos, String}], Acc));
  356. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "widthratio" = String}|T], Acc) ->
  357. mark_keywords(T, lists:reverse([OpenToken, {widthratio_keyword, Pos, String}], Acc));
  358. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "call" = String}|T], Acc) ->
  359. mark_keywords(T, lists:reverse([OpenToken, {call_keyword, Pos, String}], Acc));
  360. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endwith" = String}|T], Acc) ->
  361. mark_keywords(T, lists:reverse([OpenToken, {endwith_keyword, Pos, String}], Acc));
  362. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "trans" = String}|T], Acc) ->
  363. mark_keywords(T, lists:reverse([OpenToken, {trans_keyword, Pos, String}], Acc));
  364. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "blocktrans" = String}|T], Acc) ->
  365. mark_keywords(T, lists:reverse([OpenToken, {blocktrans_keyword, Pos, String}], Acc));
  366. mark_keywords([{open_tag, _, _} = OpenToken, {identifier, Pos, "endblocktrans" = String}|T], Acc) ->
  367. mark_keywords(T, lists:reverse([OpenToken, {endblocktrans_keyword, Pos, String}], Acc));
  368. mark_keywords([H|T], Acc) ->
  369. mark_keywords(T, [H|Acc]).
  370. atomize_identifiers(Tokens) ->
  371. atomize_identifiers(Tokens, []).
  372. atomize_identifiers([], Acc) ->
  373. lists:reverse(Acc);
  374. atomize_identifiers([{identifier, Pos, String}|T], Acc) ->
  375. atomize_identifiers(T, [{identifier, Pos, list_to_atom(String)}|Acc]);
  376. atomize_identifiers([H|T], Acc) ->
  377. atomize_identifiers(T, [H|Acc]).