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

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: Fix GIT mixup - ugh 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
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), _makeSpan(st art));
240 }
241 }
242
243 css.Stylesheet processCSS() {
244 // Is there a CSS block?
245 if (_peekIdentifier()) {
246 int start = _peekToken.start;
247 if (identifier().name == 'css') {
248 _eat(TokenKind.LBRACE);
249
250 css.Stylesheet cssCtx = processCSSContent(source, tokenizer.startIndex);
251
252 // TODO(terry): Hack, restart template parser where CSS parser stopped.
253 tokenizer.index = lastCSSIndexParsed;
254 _next(false);
255
256 _eat(TokenKind.RBRACE); // close } of css block
257
258 return cssCtx;
259 }
260 }
261 }
262 TemplateContent processTemplateContent() {
263 css.Stylesheet stylesheet;
264
265 _eat(TokenKind.LBRACE);
266
267 int start = _peekToken.start;
268
269 stylesheet = processCSS();
270
271 var elems = new TemplateElement.fragment(_makeSpan(_peekToken.start));
272 var templateDoc = processHTML(elems);
273
274 // TODO(terry): Should allow css { } to be at beginning or end of the
275 // template's content. Today css only allow at beginning
276 // because the css {...} is sucked in as a text node. We'll
277 // need a special escape for css maybe:
278 //
279 // ${#css}
280 // ${/css}
281 //
282 // uggggly!
283
284 _eat(TokenKind.RBRACE);
285
286 return new TemplateContent(stylesheet, templateDoc, _makeSpan(start));
287 }
288
289 int lastCSSIndexParsed; // TODO(terry): Hack, last good CSS parsed.
290
291 css.Stylesheet processCSSContent(var cssSource, int start) {
292 try {
293 css.Parser parser = new css.Parser(new SourceFile(
294 SourceFile.IN_MEMORY_FILE, cssSource.text), start);
295
296 css.Stylesheet stylesheet = parser.parse(false, new ErrorMsgRedirector());
297
298 var lastParsedChar = parser.tokenizer.startIndex;
299
300 lastCSSIndexParsed = lastParsedChar;
301
302 return stylesheet;
303 } catch (final cssParseException) {
304 // TODO(terry): Need SourceSpan from CSS parser to pass onto _error.
305 _error("Unexcepted CSS error: ${cssParseException.toString()}");
306 }
307 }
308
309 /* TODO(terry): Assume template { }, single close curley as a text node
310 * inside of the template would need to be escaped maybe \}
311 */
312 processHTML(TemplateElement root) {
313 assert(root.isFragment);
314 TagStack stack = new TagStack(root);
315
316 int start = _peekToken.start;
317
318 bool done = false;
319 while (!done) {
320 if (_maybeEat(TokenKind.LESS_THAN)) {
321 // Open tag
322 start = _peekToken.start;
323
324 if (TokenKind.validTagName(_peek())) {
325 Token tagToken = _next();
326
327 Map<String, TemplateAttribute> attrs = processAttributes();
328
329 String varName;
330 if (attrs.containsKey('var')) {
331 varName = attrs['var'].value;
332 attrs.remove('var');
333 }
334
335 int scopeType; // 1 implies scoped, 2 implies non-scoped element.
336 if (_maybeEat(TokenKind.GREATER_THAN)) {
337 scopeType = 1;
338 } else if (_maybeEat(TokenKind.END_NO_SCOPE_TAG)) {
339 scopeType = 2;
340 }
341 if (scopeType > 0) {
342 var elem = new TemplateElement.attributes(tagToken.kind,
343 attrs.getValues(), varName, _makeSpan(start));
344 stack.top().add(elem);
345
346 if (scopeType == 1) {
347 // Maybe more nested tags/text?
348 stack.push(elem);
349 }
350 }
351 } else {
352 // Close tag
353 _eat(TokenKind.SLASH);
354 if (TokenKind.validTagName(_peek())) {
355 Token tagToken = _next();
356
357 _eat(TokenKind.GREATER_THAN);
358
359 var elem = stack.pop();
360 if (elem is TemplateElement && !elem.isFragment) {
361 if (elem.tagTokenId != tagToken.kind) {
362 _error('Tag doesn\'t match expected </${elem.tagName}> got ' +
363 '</${TokenKind.tagNameFromTokenId(tagToken.kind)}>');
364 }
365 } else {
366 // Too many end tags.
367 _error('Too many end tags at ' +
368 '</${TokenKind.tagNameFromTokenId(tagToken.kind)}>');
369 }
370 }
371 }
372 } else if (_maybeEat(TokenKind.START_COMMAND)) {
373 if (_peekIdentifier()) {
374 var commandName = identifier();
375 switch (commandName.name) {
376 case "each":
377 case "with":
378 if (_peekIdentifier()) {
379 var listName = identifier();
380
381 _eat(TokenKind.RBRACE);
382
383 var frag = new TemplateElement.fragment(_makeSpan(_peekToken.sta rt));
384 TemplateDocument docFrag = processHTML(frag);
385
386 if (docFrag != null) {
387 var span = _makeSpan(start);
388 var cmd;
389 if (commandName.name == "each") {
390 cmd = new TemplateEachCommand(listName, docFrag, span);
391 } else if (commandName.name == "with") {
392 cmd = new TemplateWithCommand(listName, docFrag, span);
393 }
394
395 stack.top().add(cmd);
396 stack.push(cmd);
397 }
398
399 // Process ${/commandName}
400 _eat(TokenKind.END_COMMAND);
401
402 // Close command ${/commandName}
403 if (_peekIdentifier()) {
404 commandName = identifier();
405 switch (commandName.name) {
406 case "each":
407 case "with":
408 case "if":
409 case "else":
410 break;
411 default:
412 _error('Unknown command \${#${commandName}}');
413 }
414 var elem = stack.pop();
415 if (elem is TemplateEachCommand && commandName.name == "each") {
416
417 } else if (elem is TemplateWithCommand &&
418 commandName.name == "with") {
419
420 } /*else if (elem is TemplateIfCommand && commandName == "if") {
421
422 }
423 */else {
424 String expectedCmd;
425 if (elem is TemplateEachCommand) {
426 expectedCmd = "\${/each}";
427 } /* TODO(terry): else other commands as well */
428 _error('mismatched command expected ${expectedCmd} got...');
429 return;
430 }
431 _eat(TokenKind.RBRACE);
432 } else {
433 _error('Missing command name \${/commandName}');
434 }
435 } else {
436 _error("Missing listname for #each command");
437 }
438 break;
439 case "if":
440 break;
441 case "else":
442 break;
443 default:
444 _error("Unknown template command");
445 }
446 }
447 } else if (_peekKind(TokenKind.END_COMMAND)) {
448 break;
449 } else {
450 // Any text or expression nodes?
451 var nodes = processTextNodes();
452 if (nodes.length > 0) {
453 assert(stack.top() != null);
454 for (var node in nodes) {
455 stack.top().add(node);
456 }
457 } else {
458 break;
459 }
460 }
461 }
462 /*
463 if (elems.children.length != 1) {
464 print("ERROR: No closing end-tag for elems ${elems[elems.length - 1]}");
465 }
466 */
467 var docChildren = new List<ASTNode>();
468 docChildren.add(stack.pop());
469 return new TemplateDocument(docChildren, _makeSpan(start));
470 }
471
472 /* Map is used so only last unique attribute name is remembered and to quickly
473 * find the var attribute.
474 */
475 Map<String, TemplateAttribute> processAttributes() {
476 Map<String, TemplateAttribute> attrs = new Map();
477
478 int start = _peekToken.start;
479 String elemName;
480 while (_peekIdentifier() ||
481 (elemName = TokenKind.elementsToName(_peek())) != null) {
482 var attrName;
483 if (elemName == null) {
484 attrName = identifier();
485 } else {
486 attrName = new Identifier(elemName, _makeSpan(start));
487 _next();
488 }
489
490 var attrValue;
491
492 // Attribute value?
493 if (_peek() == TokenKind.ATTR_VALUE) {
494 var tok = _next();
495 attrValue = new StringValue(tok.value, _makeSpan(tok.start));
496 }
497
498 attrs[attrName.name] =
499 new TemplateAttribute(attrName, attrValue, _makeSpan(start));
500
501 start = _peekToken.start;
502 elemName = null;
503 }
504
505 return attrs;
506 }
507
508 identifier() {
509 var tok = _next();
510 if (!TokenKind.isIdentifier(tok.kind)) {
511 _error('expected identifier, but found $tok', tok.span);
512 }
513
514 return new Identifier(tok.text, _makeSpan(tok.start));
515 }
516
517 List<ASTNode> processTextNodes() {
518 // May contain TemplateText and TemplateExpression.
519 List<ASTNode> nodes = [];
520
521 int start = _peekToken.start;
522 bool inExpression = false;
523 StringBuffer stringValue = new StringBuffer();
524
525 // Gobble up everything until we hit <
526 int runningStart = _peekToken.start;
527 while (_peek() != TokenKind.LESS_THAN &&
528 (_peek() != TokenKind.RBRACE ||
529 (_peek() == TokenKind.RBRACE && inExpression)) &&
530 _peek() != TokenKind.END_OF_FILE) {
531
532 // Beginning of expression?
533 if (_peek() == TokenKind.START_EXPRESSION) {
534 if (stringValue.length > 0) {
535 // We have a real text node create the text node.
536 nodes.add(new TemplateText(stringValue.toString(), _makeSpan(start)));
537 stringValue = new StringBuffer();
538 start = _peekToken.start;
539 }
540 inExpression = true;
541 }
542
543 var tok = _next(false);
544 if (tok.kind == TokenKind.RBRACE && inExpression) {
545 // We have an expression create the expression node, don't save the }
546 inExpression = false;
547 nodes.add(new TemplateExpression(stringValue.toString(), _makeSpan(start )));
548 stringValue = new StringBuffer();
549 start = _peekToken.start;
550 } else if (tok.kind != TokenKind.START_EXPRESSION) {
551 // Only save the the contents between ${ and }
552 stringValue.add(tok.text);
553 }
554 }
555
556 if (stringValue.length > 0) {
557 nodes.add(new TemplateText(stringValue.toString(), _makeSpan(start)));
558 }
559
560 return nodes;
561 }
562
563 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698