Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(17)

Side by Side Diff: utils/template/parser.dart

Issue 9695048: Template parser (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Siggi's comments Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « utils/template/htmltree.dart ('k') | utils/template/source.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3
4 class TagStack {
5 List<ASTNode> _stack;
6
7 TagStack(var elem) : _stack = [] {
8 _stack.add(elem);
9 }
10
11 void push(var elem) {
12 _stack.add(elem);
13 }
14
15 ASTNode pop() {
16 return _stack.removeLast();
17 }
18
19 top() {
20 return _stack.last();
21 }
22 }
23
24 // TODO(terry): Cleanup returning errors from CSS to common World error
25 // handler.
26 class ErrorMsgRedirector {
27 void displayError(String msg) {
28 if (world.printHandler != null) {
29 world.printHandler(msg);
30 } else {
31 print("Unhandler Error: ${msg}");
32 }
33 world.errors++;
34 }
35 }
36
37 /**
38 * A simple recursive descent parser for HTML.
39 */
40 class Parser {
41 Tokenizer tokenizer;
42
43 var _fs; // If non-null filesystem to read files.
44
45 final SourceFile source;
46
47 Token _previousToken;
48 Token _peekToken;
49
50 PrintHandler printHandler;
51
52 Parser(this.source, [int start = 0, this._fs = null]) {
53 tokenizer = new Tokenizer(source, true, start);
54 _peekToken = tokenizer.next();
55 _previousToken = null;
56 }
57
58 // Main entry point for parsing an entire HTML file.
59 List<Template> parse([PrintHandler handler = null]) {
60 printHandler = handler;
61
62 List<Template> productions = [];
63
64 int start = _peekToken.start;
65 while (!_maybeEat(TokenKind.END_OF_FILE)) {
66 Template template = processTemplate();
67 if (template != null) {
68 productions.add(template);
69 }
70 }
71
72 return productions;
73 }
74
75 /** Generate an error if [source] has not been completely consumed. */
76 void checkEndOfFile() {
77 _eat(TokenKind.END_OF_FILE);
78 }
79
80 /** Guard to break out of parser when an unexpected end of file is found. */
81 // TODO(jimhug): Failure to call this method can lead to inifinite parser
82 // loops. Consider embracing exceptions for more errors to reduce
83 // the danger here.
84 bool isPrematureEndOfFile() {
85 if (_maybeEat(TokenKind.END_OF_FILE)) {
86 _error('unexpected end of file', _peekToken.span);
87 return true;
88 } else {
89 return false;
90 }
91 }
92
93 ///////////////////////////////////////////////////////////////////
94 // Basic support methods
95 ///////////////////////////////////////////////////////////////////
96 int _peek() {
97 return _peekToken.kind;
98 }
99
100 Token _next([bool inTag = true]) {
101 _previousToken = _peekToken;
102 _peekToken = tokenizer.next(inTag);
103 return _previousToken;
104 }
105
106 bool _peekKind(int kind) {
107 return _peekToken.kind == kind;
108 }
109
110 /* Is the next token a legal identifier? This includes pseudo-keywords. */
111 bool _peekIdentifier() {
112 return TokenKind.isIdentifier(_peekToken.kind);
113 }
114
115 bool _maybeEat(int kind) {
116 if (_peekToken.kind == kind) {
117 _previousToken = _peekToken;
118 _peekToken = tokenizer.next();
119 return true;
120 } else {
121 return false;
122 }
123 }
124
125 void _eat(int kind) {
126 if (!_maybeEat(kind)) {
127 _errorExpected(TokenKind.kindToString(kind));
128 }
129 }
130
131 void _eatSemicolon() {
132 _eat(TokenKind.SEMICOLON);
133 }
134
135 void _errorExpected(String expected) {
136 var tok = _next();
137 var message;
138 try {
139 message = 'expected $expected, but found $tok';
140 } catch (final e) {
141 message = 'parsing error expected $expected';
142 }
143 _error(message, tok.span);
144 }
145
146 void _error(String message, [SourceSpan location=null]) {
147 if (location === null) {
148 location = _peekToken.span;
149 }
150
151 if (printHandler == null) {
152 world.fatal(message, location); // syntax errors are fatal for now
153 } else {
154 // TODO(terry): Need common World view for css and template parser.
155 // For now this is how we return errors from CSS - ugh.
156 printHandler(message);
157 }
158 }
159
160 void _warning(String message, [SourceSpan location=null]) {
161 if (location === null) {
162 location = _peekToken.span;
163 }
164
165 if (printHandler == null) {
166 world.warning(message, location);
167 } else {
168 // TODO(terry): Need common World view for css and template parser.
169 // For now this is how we return errors from CSS - ugh.
170 printHandler(message);
171 }
172 }
173
174 SourceSpan _makeSpan(int start) {
175 return new SourceSpan(source, start, _previousToken.end);
176 }
177
178 ///////////////////////////////////////////////////////////////////
179 // Top level productions
180 ///////////////////////////////////////////////////////////////////
181
182 Template processTemplate() {
183 var template;
184
185 int start = _peekToken.start;
186
187 // Handle the template keyword followed by template signature.
188 _eat(TokenKind.TEMPLATE_KEYWORD);
189
190 if (_peekIdentifier()) {
191 final templateName = identifier();
192
193 List<Map<Identifier, Identifier>> params =
194 new List<Map<Identifier, Identifier>>();
195
196 _eat(TokenKind.LPAREN);
197
198 start = _peekToken.start;
199 while (true) {
200 // TODO(terry): Need robust Dart argument parser (e.g.,
201 // List<String> arg1, etc).
202 var type = processAsIdentifier();
203 var paramName = processAsIdentifier();
204 if (type != null && paramName != null) {
205 params.add({'type': type, 'name' : paramName});
206
207 if (!_maybeEat(TokenKind.COMMA)) {
208 break;
209 }
210 } else {
211 _error("Template paramter missing type and name", _makeSpan(start));
212 break;
213 }
214 }
215
216 _eat(TokenKind.RPAREN);
217
218 TemplateSignature sig =
219 new TemplateSignature(templateName.name, params, _makeSpan(start));
220
221 TemplateContent content = processTemplateContent();
222
223 template = new Template(sig, content, _makeSpan(start));
224 }
225
226 return template;
227 }
228
229 // All tokens are identifiers tokenizer is geared to HTML if identifiers are
230 // HTML element or attribute names we need them as an identifier. Used by
231 // template signatures and expressions in ${...}
232 Identifier processAsIdentifier() {
233 int start = _peekToken.start;
234
235 if (_peekIdentifier()) {
236 return identifier();
237 } else if (TokenKind.validTagName(_peek())) {
238 var tok = _next();
239 return new Identifier(TokenKind.tagNameFromTokenId(tok.kind),
240 _makeSpan(start));
241 }
242 }
243
244 css.Stylesheet processCSS() {
245 // Is there a CSS block?
246 if (_peekIdentifier()) {
247 int start = _peekToken.start;
248 if (identifier().name == 'css') {
249 _eat(TokenKind.LBRACE);
250
251 css.Stylesheet cssCtx = processCSSContent(source, tokenizer.startIndex);
252
253 // TODO(terry): Hack, restart template parser where CSS parser stopped.
254 tokenizer.index = lastCSSIndexParsed;
255 _next(false);
256
257 _eat(TokenKind.RBRACE); // close } of css block
258
259 return cssCtx;
260 }
261 }
262 }
263 TemplateContent processTemplateContent() {
264 css.Stylesheet stylesheet;
265
266 _eat(TokenKind.LBRACE);
267
268 int start = _peekToken.start;
269
270 stylesheet = processCSS();
271
272 var elems = new TemplateElement.fragment(_makeSpan(_peekToken.start));
273 var templateDoc = processHTML(elems);
274
275 // TODO(terry): Should allow css { } to be at beginning or end of the
276 // template's content. Today css only allow at beginning
277 // because the css {...} is sucked in as a text node. We'll
278 // need a special escape for css maybe:
279 //
280 // ${#css}
281 // ${/css}
282 //
283 // uggggly!
284
285 _eat(TokenKind.RBRACE);
286
287 return new TemplateContent(stylesheet, templateDoc, _makeSpan(start));
288 }
289
290 int lastCSSIndexParsed; // TODO(terry): Hack, last good CSS parsed.
291
292 css.Stylesheet processCSSContent(var cssSource, int start) {
293 try {
294 css.Parser parser = new css.Parser(new SourceFile(
295 SourceFile.IN_MEMORY_FILE, cssSource.text), start);
296
297 css.Stylesheet stylesheet = parser.parse(false, new ErrorMsgRedirector());
298
299 var lastParsedChar = parser.tokenizer.startIndex;
300
301 lastCSSIndexParsed = lastParsedChar;
302
303 return stylesheet;
304 } catch (final cssParseException) {
305 // TODO(terry): Need SourceSpan from CSS parser to pass onto _error.
306 _error("Unexcepted CSS error: ${cssParseException.toString()}");
307 }
308 }
309
310 /* TODO(terry): Assume template { }, single close curley as a text node
311 * inside of the template would need to be escaped maybe \}
312 */
313 processHTML(TemplateElement root) {
314 assert(root.isFragment);
315 TagStack stack = new TagStack(root);
316
317 int start = _peekToken.start;
318
319 bool done = false;
320 while (!done) {
321 if (_maybeEat(TokenKind.LESS_THAN)) {
322 // Open tag
323 start = _peekToken.start;
324
325 if (TokenKind.validTagName(_peek())) {
326 Token tagToken = _next();
327
328 Map<String, TemplateAttribute> attrs = processAttributes();
329
330 String varName;
331 if (attrs.containsKey('var')) {
332 varName = attrs['var'].value;
333 attrs.remove('var');
334 }
335
336 int scopeType; // 1 implies scoped, 2 implies non-scoped element.
337 if (_maybeEat(TokenKind.GREATER_THAN)) {
338 scopeType = 1;
339 } else if (_maybeEat(TokenKind.END_NO_SCOPE_TAG)) {
340 scopeType = 2;
341 }
342 if (scopeType > 0) {
343 var elem = new TemplateElement.attributes(tagToken.kind,
344 attrs.getValues(), varName, _makeSpan(start));
345 stack.top().add(elem);
346
347 if (scopeType == 1) {
348 // Maybe more nested tags/text?
349 stack.push(elem);
350 }
351 }
352 } else {
353 // Close tag
354 _eat(TokenKind.SLASH);
355 if (TokenKind.validTagName(_peek())) {
356 Token tagToken = _next();
357
358 _eat(TokenKind.GREATER_THAN);
359
360 var elem = stack.pop();
361 if (elem is TemplateElement && !elem.isFragment) {
362 if (elem.tagTokenId != tagToken.kind) {
363 _error('Tag doesn\'t match expected </${elem.tagName}> got ' +
364 '</${TokenKind.tagNameFromTokenId(tagToken.kind)}>');
365 }
366 } else {
367 // Too many end tags.
368 _error('Too many end tags at ' +
369 '</${TokenKind.tagNameFromTokenId(tagToken.kind)}>');
370 }
371 }
372 }
373 } else if (_maybeEat(TokenKind.START_COMMAND)) {
374 if (_peekIdentifier()) {
375 var commandName = identifier();
376 switch (commandName.name) {
377 case "each":
378 case "with":
379 if (_peekIdentifier()) {
380 var listName = identifier();
381
382 _eat(TokenKind.RBRACE);
383
384 var frag = new TemplateElement.fragment(
385 _makeSpan(_peekToken.start));
386 TemplateDocument docFrag = processHTML(frag);
387
388 if (docFrag != null) {
389 var span = _makeSpan(start);
390 var cmd;
391 if (commandName.name == "each") {
392 cmd = new TemplateEachCommand(listName, docFrag, span);
393 } else if (commandName.name == "with") {
394 cmd = new TemplateWithCommand(listName, docFrag, span);
395 }
396
397 stack.top().add(cmd);
398 stack.push(cmd);
399 }
400
401 // Process ${/commandName}
402 _eat(TokenKind.END_COMMAND);
403
404 // Close command ${/commandName}
405 if (_peekIdentifier()) {
406 commandName = identifier();
407 switch (commandName.name) {
408 case "each":
409 case "with":
410 case "if":
411 case "else":
412 break;
413 default:
414 _error('Unknown command \${#${commandName}}');
415 }
416 var elem = stack.pop();
417 if (elem is TemplateEachCommand &&
418 commandName.name == "each") {
419
420 } else if (elem is TemplateWithCommand &&
421 commandName.name == "with") {
422
423 } /*else if (elem is TemplateIfCommand && commandName == "if") {
424
425 }
426 */else {
427 String expectedCmd;
428 if (elem is TemplateEachCommand) {
429 expectedCmd = "\${/each}";
430 } /* TODO(terry): else other commands as well */
431 _error('mismatched command expected ${expectedCmd} got...');
432 return;
433 }
434 _eat(TokenKind.RBRACE);
435 } else {
436 _error('Missing command name \${/commandName}');
437 }
438 } else {
439 _error("Missing listname for #each command");
440 }
441 break;
442 case "if":
443 break;
444 case "else":
445 break;
446 default:
447 _error("Unknown template command");
448 }
449 }
450 } else if (_peekKind(TokenKind.END_COMMAND)) {
451 break;
452 } else {
453 // Any text or expression nodes?
454 var nodes = processTextNodes();
455 if (nodes.length > 0) {
456 assert(stack.top() != null);
457 for (var node in nodes) {
458 stack.top().add(node);
459 }
460 } else {
461 break;
462 }
463 }
464 }
465 /*
466 if (elems.children.length != 1) {
467 print("ERROR: No closing end-tag for elems ${elems[elems.length - 1]}");
468 }
469 */
470 var docChildren = new List<ASTNode>();
471 docChildren.add(stack.pop());
472 return new TemplateDocument(docChildren, _makeSpan(start));
473 }
474
475 /* Map is used so only last unique attribute name is remembered and to quickly
476 * find the var attribute.
477 */
478 Map<String, TemplateAttribute> processAttributes() {
479 Map<String, TemplateAttribute> attrs = new Map();
480
481 int start = _peekToken.start;
482 String elemName;
483 while (_peekIdentifier() ||
484 (elemName = TokenKind.elementsToName(_peek())) != null) {
485 var attrName;
486 if (elemName == null) {
487 attrName = identifier();
488 } else {
489 attrName = new Identifier(elemName, _makeSpan(start));
490 _next();
491 }
492
493 var attrValue;
494
495 // Attribute value?
496 if (_peek() == TokenKind.ATTR_VALUE) {
497 var tok = _next();
498 attrValue = new StringValue(tok.value, _makeSpan(tok.start));
499 }
500
501 attrs[attrName.name] =
502 new TemplateAttribute(attrName, attrValue, _makeSpan(start));
503
504 start = _peekToken.start;
505 elemName = null;
506 }
507
508 return attrs;
509 }
510
511 identifier() {
512 var tok = _next();
513 if (!TokenKind.isIdentifier(tok.kind)) {
514 _error('expected identifier, but found $tok', tok.span);
515 }
516
517 return new Identifier(tok.text, _makeSpan(tok.start));
518 }
519
520 List<ASTNode> processTextNodes() {
521 // May contain TemplateText and TemplateExpression.
522 List<ASTNode> nodes = [];
523
524 int start = _peekToken.start;
525 bool inExpression = false;
526 StringBuffer stringValue = new StringBuffer();
527
528 // Gobble up everything until we hit <
529 int runningStart = _peekToken.start;
530 while (_peek() != TokenKind.LESS_THAN &&
531 (_peek() != TokenKind.RBRACE ||
532 (_peek() == TokenKind.RBRACE && inExpression)) &&
533 _peek() != TokenKind.END_OF_FILE) {
534
535 // Beginning of expression?
536 if (_peek() == TokenKind.START_EXPRESSION) {
537 if (stringValue.length > 0) {
538 // We have a real text node create the text node.
539 nodes.add(new TemplateText(stringValue.toString(), _makeSpan(start)));
540 stringValue = new StringBuffer();
541 start = _peekToken.start;
542 }
543 inExpression = true;
544 }
545
546 var tok = _next(false);
547 if (tok.kind == TokenKind.RBRACE && inExpression) {
548 // We have an expression create the expression node, don't save the }
549 inExpression = false;
550 nodes.add(new TemplateExpression(stringValue.toString(),
551 _makeSpan(start)));
552 stringValue = new StringBuffer();
553 start = _peekToken.start;
554 } else if (tok.kind != TokenKind.START_EXPRESSION) {
555 // Only save the the contents between ${ and }
556 stringValue.add(tok.text);
557 }
558 }
559
560 if (stringValue.length > 0) {
561 nodes.add(new TemplateText(stringValue.toString(), _makeSpan(start)));
562 }
563
564 return nodes;
565 }
566
567 }
OLDNEW
« no previous file with comments | « utils/template/htmltree.dart ('k') | utils/template/source.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698