/src/Manos/Manos.Template/TemplateParser.cs

http://github.com/jacksonh/manos · C# · 585 lines · 238 code · 77 blank · 270 comment · 45 complexity · 045b05ddf225dae1c38ddb42c89b6e87 MD5 · raw file

  1. //
  2. // Copyright (C) 2010 Jackson Harper (jackson@manosdemono.com)
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining
  5. // a copy of this software and associated documentation files (the
  6. // "Software"), to deal in the Software without restriction, including
  7. // without limitation the rights to use, copy, modify, merge, publish,
  8. // distribute, sublicense, and/or sell copies of the Software, and to
  9. // permit persons to whom the Software is furnished to do so, subject to
  10. // the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be
  13. // included in all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. //
  23. //
  24. using System;
  25. using System.IO;
  26. using System.Text;
  27. using System.Collections.Generic;
  28. namespace Manos.Templates {
  29. public class TemplateParser {
  30. private ITemplateCodegen codegen;
  31. private TemplateEnvironment environment;
  32. public TemplateParser (TemplateEnvironment environment, ITemplateCodegen codegen)
  33. {
  34. this.codegen = codegen;
  35. this.environment = environment;
  36. }
  37. public void ParsePage (string name, TextReader reader)
  38. {
  39. Console.WriteLine ("parsing page: {0}", name);
  40. TemplateTokenizer tk = new TemplateTokenizer (environment, reader);
  41. codegen.BeginPage (name);
  42. Token tok = null;
  43. StringBuilder data = new StringBuilder ();
  44. while (true) {
  45. tok = tk.GetNextToken ();
  46. switch (tok.Type) {
  47. case TokenType.TOKEN_VARIABLE_BEGIN:
  48. FlushData (data);
  49. ParseVariable (tk);
  50. break;
  51. case TokenType.TOKEN_COMMENT_BEGIN:
  52. FlushData (data);
  53. ParseComment (tk);
  54. break;
  55. case TokenType.TOKEN_BLOCK_BEGIN:
  56. FlushData (data);
  57. ParseControlBlock (tk);
  58. break;
  59. case TokenType.TOKEN_EOF:
  60. FlushData (data);
  61. codegen.EndPage ();
  62. return;
  63. default:
  64. data.Append (tok.Value);
  65. break;
  66. }
  67. }
  68. }
  69. public void FlushData (StringBuilder data)
  70. {
  71. codegen.AddData (data.ToString ());
  72. data.Length = 0;
  73. }
  74. public void ParseVariable (TemplateTokenizer tk)
  75. {
  76. Expression exp = ParseExpression (tk, TokenType.TOKEN_BLOCK_END);
  77. if (tk.Current.Type != TokenType.TOKEN_VARIABLE_END)
  78. RaiseFailure (tk, String.Format ("Invalid variable statement found, '{0}' token found when a {1} was expected.",
  79. tk.Current.Value, environment.VariableEndString));
  80. codegen.EmitSinglePrint (exp);
  81. }
  82. public void ParseComment (TemplateTokenizer tk)
  83. {
  84. Token tok;
  85. StringBuilder builder = new StringBuilder ();
  86. do {
  87. tok = tk.GetNextToken ();
  88. if (tok.Type == TokenType.TOKEN_COMMENT_END) {
  89. return;
  90. }
  91. builder.Append (tok.Value);
  92. } while (tok.Type != TokenType.TOKEN_EOF);
  93. // FAIL
  94. }
  95. public void ParseControlBlock (TemplateTokenizer tk)
  96. {
  97. Token tok;
  98. StringBuilder builder = new StringBuilder ();
  99. ParseStatement (tk);
  100. tok = tk.Current;
  101. do {
  102. if (tok.Type == TokenType.TOKEN_BLOCK_END) {
  103. return;
  104. }
  105. builder.Append (tok.Value);
  106. tok = tk.GetNextToken ();
  107. } while (tok.Type != TokenType.TOKEN_EOF);
  108. }
  109. public void ParseStatement (TemplateTokenizer tk)
  110. {
  111. Token tok = NextNonWhiteSpaceToken (tk);
  112. if (tok.Type != TokenType.TOKEN_NAME) {
  113. // fail
  114. Console.WriteLine ("INVALID BLOCK TOKEN TYPE");
  115. }
  116. switch (tok.Value) {
  117. // case "print":
  118. // ParsePrint (tk);
  119. // break;
  120. case "foreach":
  121. ParseForeachLoop (tk);
  122. break;
  123. case "endforeach":
  124. ParseEndForeachLoop (tk);
  125. break;
  126. case "if":
  127. ParseIf (tk);
  128. break;
  129. case "else":
  130. ParseElse (tk);
  131. break;
  132. case "endif":
  133. ParseEndIf (tk);
  134. break;
  135. case "block":
  136. ParseBlock (tk);
  137. break;
  138. case "endblock":
  139. ParseEndBlock (tk);
  140. break;
  141. case "extends":
  142. ParseExtends (tk);
  143. break;
  144. // case "macro":
  145. // ParseMacro (tk);
  146. // break;
  147. // case "endmacro":
  148. // ParseEndMacro (tk);
  149. // break;
  150. // case "set":
  151. // ParseSet (tk);
  152. // break;
  153. case "include":
  154. case "from":
  155. case "import":
  156. case "call":
  157. case "filter":
  158. default:
  159. throw new InvalidOperationException (String.Format ("Unsupported operation {0}", tok.Value));
  160. break;
  161. }
  162. }
  163. /*
  164. public void ParsePrint (TemplateTokenizer tk)
  165. {
  166. Token tok;
  167. bool first = true;
  168. List<Expression> expressions = new List<Expression> ();
  169. do {
  170. Expression exp = ParseExpression (tk, TokenType.TOKEN_BLOCK_END);
  171. if (exp != null)
  172. expressions.Add (exp);
  173. tok = tk.Current;
  174. if (tok.Type == TokenType.TOKEN_BLOCK_END)
  175. break;
  176. if (!first && tok.Type != TokenType.TOKEN_COMMA)
  177. RaiseFailure (tk, "Invalid token found in print statement '{0}'");
  178. } while (tok.Type != TokenType.TOKEN_EOF);
  179. if (tok.Type == TokenType.TOKEN_EOF)
  180. RaiseFailure (tk, "Unexpected end of file.");
  181. codegen.EmitPrint (expressions);
  182. }
  183. */
  184. /*
  185. public void ParseSet (TemplateTokenizer tk)
  186. {
  187. Token tok = NextNonWhiteSpaceToken (tk);
  188. if (tok.Type != TokenType.TOKEN_NAME)
  189. RaiseFailure (tk, String.Format ("Invalid token found in set statement, expected a name got a {0}", tok.Value));
  190. NamedTarget target = new NamedTarget (tok.Value);
  191. tok = NextNonWhiteSpaceToken (tk);
  192. if (tok.Type != TokenType.TOKEN_ASSIGN)
  193. RaiseFailure (tk, String.Format ("Invalid token found in set statement, expected an = got a {0}", tok.Value));
  194. Expression expression = ParseExpression (tk, TokenType.TOKEN_BLOCK_END);
  195. codegen.EmitSet (target, expression);
  196. }
  197. */
  198. public void ParseIf (TemplateTokenizer tk)
  199. {
  200. Expression expression = ParseExpression (tk, TokenType.TOKEN_BLOCK_END);
  201. codegen.EmitIf (expression);
  202. }
  203. public void ParseElse (TemplateTokenizer tk)
  204. {
  205. Expression condition = null;
  206. Token tok = NextNonWhiteSpaceToken (tk);
  207. if (tok.Type != TokenType.TOKEN_BLOCK_END)
  208. condition = ParseExpression (tk, TokenType.TOKEN_BLOCK_END);
  209. codegen.EmitElse (null);
  210. }
  211. public void ParseEndIf (TemplateTokenizer tk)
  212. {
  213. Token tok = NextNonWhiteSpaceToken (tk);
  214. while (tok.Type != TokenType.TOKEN_BLOCK_END) {
  215. tok = NextNonWhiteSpaceToken (tk);
  216. }
  217. codegen.EmitEndIf ();
  218. }
  219. public void ParseBlock (TemplateTokenizer tk)
  220. {
  221. Token tok = NextNonWhiteSpaceToken (tk);
  222. if (tok.Type != TokenType.TOKEN_NAME)
  223. RaiseFailure (tk, String.Format ("Invalid '{0}' token found in block statement.", tok.Value));
  224. string name = tok.Value;
  225. /*
  226. List<ArgumentDefinition> args = null;
  227. tok = NextNonWhiteSpaceToken (tk);
  228. if (tok.Type == TokenType.TOKEN_LPAREN) {
  229. args = ParseArgumentDefinitions (tk);
  230. tok = NextNonWhiteSpaceToken (tk);
  231. }
  232. */
  233. codegen.BeginBlock (name);
  234. tok = NextNonWhiteSpaceToken (tk);
  235. if (tok.Type != TokenType.TOKEN_BLOCK_END)
  236. RaiseFailure (tk, String.Format ("Invalid '{0}' token found in block statement.", tok.Value));
  237. }
  238. public void ParseEndBlock (TemplateTokenizer tk)
  239. {
  240. Token tok = NextNonWhiteSpaceToken (tk);
  241. string name = null;
  242. if (tok.Type == TokenType.TOKEN_NAME) {
  243. name = tok.Value;
  244. tok = NextNonWhiteSpaceToken (tk);
  245. }
  246. // Name matching is optional, we pass null if no name is supplied
  247. codegen.EndBlock (name);
  248. if (tok.Type != TokenType.TOKEN_BLOCK_END)
  249. RaiseFailure (tk, String.Format ("Invalid '{0}' token found in endblock statement.", tok.Value));
  250. }
  251. public void ParseExtends (TemplateTokenizer tk)
  252. {
  253. Token tok = NextNonWhiteSpaceToken (tk);
  254. if (tok.Type != TokenType.TOKEN_QUOTED_STRING)
  255. RaiseFailure (tk, String.Format ("Invalid '{0}' token found in extends statement.", tok.Value));
  256. codegen.EmitExtends (ValueOfQuotedString (tok.Value));
  257. tok = NextNonWhiteSpaceToken (tk);
  258. if (tok.Type != TokenType.TOKEN_BLOCK_END)
  259. RaiseFailure (tk, String.Format ("Invalid '{0}' token found in extends statement.", tok.Value));
  260. }
  261. /*
  262. public void ParseMacro (TemplateTokenizer tk)
  263. {
  264. Token tok = NextNonWhiteSpaceToken (tk);
  265. if (tok.Type != TokenType.TOKEN_NAME)
  266. RaiseFailure (tk, String.Format ("Invalid macro definition, expected name got a '{0}'.", tok.Value));
  267. string name = tok.Value;
  268. List<ArgumentDefinition> args = null;
  269. tok = NextNonWhiteSpaceToken (tk);
  270. if (tok.Type == TokenType.TOKEN_LPAREN) {
  271. args = ParseArgumentDefinitions (tk);
  272. tok = NextNonWhiteSpaceToken (tk);
  273. }
  274. codegen.BeginMacro (name, args);
  275. if (tok.Type != TokenType.TOKEN_BLOCK_END)
  276. RaiseFailure (tk, String.Format ("Invalid macro definition, expect block end got a '{0}'.", tok.Value));
  277. }
  278. public void ParseEndMacro (TemplateTokenizer tk)
  279. {
  280. string name = null;
  281. Token tok = NextNonWhiteSpaceToken (tk);
  282. if (tok.Type == TokenType.TOKEN_NAME) {
  283. name = tok.Value;
  284. tok = NextNonWhiteSpaceToken (tk);
  285. }
  286. if (tok.Type != TokenType.TOKEN_BLOCK_END)
  287. RaiseFailure (tk, String.Format ("Invalid endmacro definition, expected a block end got a '{0}'.", tok.Value));
  288. current_page.EndMacro (name);
  289. }
  290. */
  291. public Expression ParseExpression (TemplateTokenizer tk, TokenType end_token_type, bool allow_conditionals=true)
  292. {
  293. Expression expression = null;
  294. /*
  295. Value target_value = ParseRValue (tk);
  296. Token tok = NextNonWhiteSpaceToken (tk);
  297. if (tok.Type == TokenType.TOKEN_DOT) {
  298. tok = NextNonWhiteSpaceToken (tk);
  299. if (tok.Type != TokenType.TOKEN_NAME)
  300. RaiseFailure (tk, String.Format ("Invalid expression, token '{0}' found where a name was expected.", tok.Value));
  301. expression = new Expression (new PropertyAccessValue (target_value, tok.Value));
  302. NextNonWhiteSpaceToken (tk);
  303. } else if (tok.Type == TokenType.TOKEN_LBRACKET) {
  304. string prop_name = ParseSubscript (tk);
  305. expression = new Expression (new PropertyAccessValue (target_value, prop_name));
  306. NextNonWhiteSpaceToken (tk);
  307. } else if (tok.Type == TokenType.TOKEN_LPAREN) {
  308. VariableValue target = target_value as VariableValue;
  309. if (target == null)
  310. RaiseFailure (tk, String.Format ("Invalid invoke expression, expected a name got a {0}", target_value));
  311. List<Expression> args = ParseArguments (tk);
  312. if (tk.Current.Type != TokenType.TOKEN_RPAREN)
  313. RaiseFailure (tk, String.Format ("Invalid invoke expression, token '{0}' where a ) was expected.", tk.Current.Value));
  314. expression = new Expression (new InvokeValue (target.Name.Name, args));
  315. NextNonWhiteSpaceToken (tk);
  316. } else
  317. expression = new Expression (target_value);
  318. while (tok.Type != end_token_type && tok.Type != TokenType.TOKEN_EOF) {
  319. if (tok.Type == TokenType.TOKEN_PIPE) {
  320. TemplateFilter filter = ParseFilter (tk);
  321. expression.AddFilter (filter);
  322. } else if (allow_conditionals && IsConditionalToken (tok)) {
  323. CompareOperator compare = CompareOperatorFromToken (tok);
  324. // expression = new ConditionalExpression (expression, compare, ParseExpression (tk, end_token_type));
  325. } else
  326. break;
  327. tok = tk.Current;
  328. }
  329. if (tok.Type == TokenType.TOKEN_EOF)
  330. RaiseFailure (tk, "Unexpected eof of file found while parsing expression.");
  331. */
  332. return expression;
  333. }
  334. public void ParseForeachLoop (TemplateTokenizer tk)
  335. {
  336. Token tok = NextNonWhiteSpaceToken (tk);
  337. if (tok.Type != TokenType.TOKEN_NAME)
  338. RaiseFailure (tk, String.Format ("Invalid for loop, expected a name got token '{0}'", tok.Value));
  339. string variable_name = tok.Value;
  340. Expect (tk, TokenType.TOKEN_NAME, "in", "Invalid for loop, no 'in' statement found. '{0}' found instead.");
  341. Expression iter = ParseExpression (tk, TokenType.TOKEN_BLOCK_END);
  342. codegen.BeginForeachLoop (variable_name, iter);
  343. }
  344. public void ParseEndForeachLoop (TemplateTokenizer tk)
  345. {
  346. Token tok = NextNonWhiteSpaceToken (tk);
  347. while (tok.Type != TokenType.TOKEN_BLOCK_END) {
  348. tok = NextNonWhiteSpaceToken (tk);
  349. }
  350. codegen.EndForeachLoop ();
  351. }
  352. public string ParseSubscript (TemplateTokenizer tk)
  353. {
  354. Token tok = NextNonWhiteSpaceToken (tk);
  355. if (tok.Type != TokenType.TOKEN_QUOTED_STRING)
  356. RaiseFailure (tk, "Invalid subscript expression, token '{0}' found where a quoted string was expected.");
  357. string value = ValueOfQuotedString (tok.Value);
  358. tok = NextNonWhiteSpaceToken (tk);
  359. if (tok.Type != TokenType.TOKEN_RBRACKET)
  360. RaiseFailure (tk, "Invalid subscript expression, token '{0}' found where a ] was expected.");
  361. return value;
  362. }
  363. /*
  364. public TemplateFilter ParseFilter (TemplateTokenizer tk)
  365. {
  366. Token tok = NextNonWhiteSpaceToken (tk);
  367. if (tok.Type != TokenType.TOKEN_NAME)
  368. RaiseFailure (tk, String.Format ("Invalid filter expression, token '{0}' found where a name was expected.", tok.Value));
  369. string name = tok.Value;
  370. List<Expression> args = new List<Expression> ();
  371. tok = NextNonWhiteSpaceToken (tk);
  372. if (tok.Type == TokenType.TOKEN_LPAREN) {
  373. args = ParseArguments (tk);
  374. if (tk.Current.Type != TokenType.TOKEN_RPAREN)
  375. RaiseFailure (tk, String.Format ("Invalid filter expression, token '{0}' where a ) was expected.", tk.Current.Value));
  376. // Advance pass the RPAREN
  377. NextNonWhiteSpaceToken (tk);
  378. }
  379. return new TemplateFilter (name, args);
  380. }
  381. */
  382. /*
  383. public List<Expression> ParseArguments (TemplateTokenizer tk)
  384. {
  385. List<Expression> expressions = new List<Expression> ();
  386. Token tok = null;
  387. do {
  388. Expression expression = ParseExpression (tk, TokenType.TOKEN_COMMA);
  389. expressions.Add (expression);
  390. tok = tk.Current;
  391. if (tok.Type == TokenType.TOKEN_RPAREN)
  392. break;
  393. if (tok.Type != TokenType.TOKEN_COMMA)
  394. RaiseFailure (tk, String.Format ("Invalid argument list, expected comma got a {0}", tk.Current.Value));
  395. } while (tok.Type != TokenType.TOKEN_EOF);
  396. if (tk.Current.Type == TokenType.TOKEN_EOF)
  397. RaiseFailure (tk, String.Format ("Unexpected end of file."));
  398. return expressions;
  399. }
  400. public List<ArgumentDefinition> ParseArgumentDefinitions (TemplateTokenizer tk)
  401. {
  402. Token tok = NextNonWhiteSpaceToken (tk);
  403. List<ArgumentDefinition> args = new List<ArgumentDefinition> ();
  404. do {
  405. if (tok.Type == TokenType.TOKEN_RPAREN)
  406. break;
  407. if (tok.Type != TokenType.TOKEN_NAME)
  408. RaiseFailure (tk, String.Format ("Invalid argument definition, expected a name got a '{0}'", tok.Value));
  409. string name = tok.Value;
  410. ConstantValue default_value = null;
  411. tok = NextNonWhiteSpaceToken (tk);
  412. if (tok.Type == TokenType.TOKEN_ASSIGN) {
  413. default_value = ParseConstantValue (tk);
  414. tok = NextNonWhiteSpaceToken (tk);
  415. }
  416. args.Add (new ArgumentDefinition (name, default_value));
  417. if (tok.Type == TokenType.TOKEN_RPAREN)
  418. break;
  419. if (tok.Type != TokenType.TOKEN_COMMA)
  420. RaiseFailure (tk, String.Format ("Invalid argument list, expected comma got a {0}", tk.Current.Type));
  421. tok = NextNonWhiteSpaceToken (tk);
  422. } while (tok.Type != TokenType.TOKEN_EOF);
  423. if (tk.Current.Type == TokenType.TOKEN_EOF)
  424. RaiseFailure (tk, String.Format ("Unexpected end of file."));
  425. return args;
  426. }
  427. */
  428. public Token NextNonWhiteSpaceToken (TemplateTokenizer tk)
  429. {
  430. Token tok;
  431. do {
  432. tok = tk.GetNextToken ();
  433. } while (tok.Type == TokenType.TOKEN_WHITESPACE);
  434. return tok;
  435. }
  436. private static string ValueOfQuotedString (string str)
  437. {
  438. string res = str.Substring (1, str.Length - 2);
  439. return res;
  440. }
  441. private void Expect (TemplateTokenizer tk, TokenType type, string value, string error="Expected symbol {0} not found.")
  442. {
  443. Token tok = NextNonWhiteSpaceToken (tk);
  444. if (tok.Type != type || tok.Value != value)
  445. RaiseFailure (tk, String.Format (error, value));
  446. }
  447. private void RaiseFailure (TemplateTokenizer tk, string error)
  448. {
  449. throw new Exception (String.Format ("({0}:{1}) FAILURE: {2}", tk.Line, tk.Column, error));
  450. }
  451. }
  452. }