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

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

Issue 137013002: Removed obsolete code (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Removed libraries not used Created 6 years, 11 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([String name = null]) {
112 if (TokenKind.isIdentifier(_peekToken.kind)) {
113 return (name != null) ? _peekToken.text == name : true;
114 }
115
116 return false;
117 }
118
119 bool _maybeEat(int kind) {
120 if (_peekToken.kind == kind) {
121 _previousToken = _peekToken;
122 if (kind == TokenKind.GREATER_THAN) {
123 _peekToken = tokenizer.next(false);
124 } else {
125 _peekToken = tokenizer.next();
126 }
127 return true;
128 } else {
129 return false;
130 }
131 }
132
133 void _eat(int kind) {
134 if (!_maybeEat(kind)) {
135 _errorExpected(TokenKind.kindToString(kind));
136 }
137 }
138
139 void _eatSemicolon() {
140 _eat(TokenKind.SEMICOLON);
141 }
142
143 void _errorExpected(String expected) {
144 var tok = _next();
145 var message;
146 try {
147 message = 'expected $expected, but found $tok';
148 } catch (e) {
149 message = 'parsing error expected $expected';
150 }
151 _error(message, tok.span);
152 }
153
154 void _error(String message, [SourceSpan location=null]) {
155 if (location == null) {
156 location = _peekToken.span;
157 }
158
159 if (printHandler == null) {
160 world.fatal(message, location); // syntax errors are fatal for now
161 } else {
162 // TODO(terry): Need common World view for css and template parser.
163 // For now this is how we return errors from CSS - ugh.
164 printHandler(message);
165 }
166 }
167
168 void _warning(String message, [SourceSpan location=null]) {
169 if (location == null) {
170 location = _peekToken.span;
171 }
172
173 if (printHandler == null) {
174 world.warning(message, location);
175 } else {
176 // TODO(terry): Need common World view for css and template parser.
177 // For now this is how we return errors from CSS - ugh.
178 printHandler(message);
179 }
180 }
181
182 SourceSpan _makeSpan(int start) {
183 return new SourceSpan(source, start, _previousToken.end);
184 }
185
186 ///////////////////////////////////////////////////////////////////
187 // Top level productions
188 ///////////////////////////////////////////////////////////////////
189
190 Template processTemplate() {
191 var template;
192
193 int start = _peekToken.start;
194
195 // Handle the template keyword followed by template signature.
196 _eat(TokenKind.TEMPLATE_KEYWORD);
197
198 if (_peekIdentifier()) {
199 final templateName = identifier();
200
201 List<Map<Identifier, Identifier>> params =
202 new List<Map<Identifier, Identifier>>();
203
204 _eat(TokenKind.LPAREN);
205
206 start = _peekToken.start;
207 while (true) {
208 // TODO(terry): Need robust Dart argument parser (e.g.,
209 // List<String> arg1, etc).
210 var type = processAsIdentifier();
211 var paramName = processAsIdentifier();
212 if (type != null && paramName != null) {
213 params.add({'type': type, 'name' : paramName});
214
215 if (!_maybeEat(TokenKind.COMMA)) {
216 break;
217 }
218 } else {
219 // No parameters we're done.
220 break;
221 }
222 }
223
224 _eat(TokenKind.RPAREN);
225
226 TemplateSignature sig =
227 new TemplateSignature(templateName.name, params, _makeSpan(start));
228
229 TemplateContent content = processTemplateContent();
230
231 template = new Template(sig, content, _makeSpan(start));
232 }
233
234 return template;
235 }
236
237 // All tokens are identifiers tokenizer is geared to HTML if identifiers are
238 // HTML element or attribute names we need them as an identifier. Used by
239 // template signatures and expressions in ${...}
240 Identifier processAsIdentifier() {
241 int start = _peekToken.start;
242
243 if (_peekIdentifier()) {
244 return identifier();
245 } else if (TokenKind.validTagName(_peek())) {
246 var tok = _next();
247 return new Identifier(TokenKind.tagNameFromTokenId(tok.kind),
248 _makeSpan(start));
249 }
250 }
251
252 css.Stylesheet processCSS() {
253 // Is there a CSS block?
254 if (_peekIdentifier('css')) {
255 _next();
256
257 int start = _peekToken.start;
258 _eat(TokenKind.LBRACE);
259
260 css.Stylesheet cssCtx = processCSSContent(source, tokenizer.startIndex);
261
262 // TODO(terry): Hack, restart template parser where CSS parser stopped.
263 tokenizer.index = lastCSSIndexParsed;
264 _next(false);
265
266 _eat(TokenKind.RBRACE); // close } of css block
267
268 return cssCtx;
269 }
270 }
271
272 // TODO(terry): get should be able to use all template control flow but return
273 // a string instead of a node. Maybe expose both html and get
274 // e.g.,
275 //
276 // A.)
277 // html {
278 // <div>...</div>
279 // }
280 //
281 // B.)
282 // html foo() {
283 // <div>..</div>
284 // }
285 //
286 // C.)
287 // get {
288 // <div>...</div>
289 // }
290 //
291 // D.)
292 // get foo {
293 // <div>..</div>
294 // }
295 //
296 // Only one default allower either A or C the constructor will
297 // generate a string or a node.
298 // Examples B and D would generate getters that either return
299 // a node for B or a String for D.
300 //
301 List<TemplateGetter> processGetters() {
302 List<TemplateGetter> getters = [];
303
304 while (true) {
305 if (_peekIdentifier('get')) {
306 _next();
307
308 int start = _peekToken.start;
309 if (_peekIdentifier()) {
310 String getterName = identifier().name;
311
312 List<Map<Identifier, Identifier>> params =
313 new List<Map<Identifier, Identifier>>();
314
315 _eat(TokenKind.LPAREN);
316
317 start = _peekToken.start;
318 while (true) {
319 // TODO(terry): Need robust Dart argument parser (e.g.,
320 // List<String> arg1, etc).
321 var type = processAsIdentifier();
322 var paramName = processAsIdentifier();
323 if (paramName == null && type != null) {
324 paramName = type;
325 type = "";
326 }
327 if (type != null && paramName != null) {
328 params.add({'type': type, 'name' : paramName});
329
330 if (!_maybeEat(TokenKind.COMMA)) {
331 break;
332 }
333 } else {
334 // No parameters we're done.
335 break;
336 }
337 }
338
339 _eat(TokenKind.RPAREN);
340
341 _eat(TokenKind.LBRACE);
342
343 var elems = new TemplateElement.fragment(_makeSpan(_peekToken.start));
344 var templateDoc = processHTML(elems);
345
346 _eat(TokenKind.RBRACE); // close } of get block
347
348 getters.add(new TemplateGetter(getterName, params, templateDoc,
349 _makeSpan(_peekToken.start)));
350 }
351 } else {
352 break;
353 }
354 }
355
356 return getters;
357
358 /*
359 get newTotal(value) {
360 <div class="alignleft">${value}</div>
361 }
362
363 String get HTML_newTotal(value) {
364 return '<div class="alignleft">${value}</div>
365 }
366
367 */
368 }
369
370 TemplateContent processTemplateContent() {
371 css.Stylesheet ss;
372
373 _eat(TokenKind.LBRACE);
374
375 int start = _peekToken.start;
376
377 ss = processCSS();
378
379 // TODO(terry): getters should be allowed anywhere not just after CSS.
380 List<TemplateGetter> getters = processGetters();
381
382 var elems = new TemplateElement.fragment(_makeSpan(_peekToken.start));
383 var templateDoc = processHTML(elems);
384
385 // TODO(terry): Should allow css { } to be at beginning or end of the
386 // template's content. Today css only allow at beginning
387 // because the css {...} is sucked in as a text node. We'll
388 // need a special escape for css maybe:
389 //
390 // ${#css}
391 // ${/css}
392 //
393 // uggggly!
394
395
396 _eat(TokenKind.RBRACE);
397
398 return new TemplateContent(ss, templateDoc, getters, _makeSpan(start));
399 }
400
401 int lastCSSIndexParsed; // TODO(terry): Hack, last good CSS parsed.
402
403 css.Stylesheet processCSSContent(var cssSource, int start) {
404 try {
405 css.Parser parser = new css.Parser(new SourceFile(
406 SourceFile.IN_MEMORY_FILE, cssSource.text), start);
407
408 css.Stylesheet stylesheet = parser.parse(false, new ErrorMsgRedirector());
409
410 var lastParsedChar = parser.tokenizer.startIndex;
411
412 lastCSSIndexParsed = lastParsedChar;
413
414 return stylesheet;
415 } catch (cssParseException) {
416 // TODO(terry): Need SourceSpan from CSS parser to pass onto _error.
417 _error("Unexcepted CSS error: ${cssParseException.toString()}");
418 }
419 }
420
421 /* TODO(terry): Assume template { }, single close curley as a text node
422 * inside of the template would need to be escaped maybe \}
423 */
424 processHTML(TemplateElement root) {
425 assert(root.isFragment);
426 TagStack stack = new TagStack(root);
427
428 int start = _peekToken.start;
429
430 bool done = false;
431 while (!done) {
432 if (_maybeEat(TokenKind.LESS_THAN)) {
433 // Open tag
434 start = _peekToken.start;
435
436 if (TokenKind.validTagName(_peek())) {
437 Token tagToken = _next();
438
439 Map<String, TemplateAttribute> attrs = processAttributes();
440
441 String varName;
442 if (attrs.containsKey('var')) {
443 varName = attrs['var'].value;
444 attrs.remove('var');
445 }
446
447 int scopeType; // 1 implies scoped, 2 implies non-scoped element.
448 if (_maybeEat(TokenKind.GREATER_THAN)) {
449 // Scoped unless the tag is explicitly known as an unscoped tag
450 // e.g., <br>.
451 scopeType = TokenKind.unscopedTag(tagToken.kind) ? 2 : 1;
452 } else if (_maybeEat(TokenKind.END_NO_SCOPE_TAG)) {
453 scopeType = 2;
454 }
455 if (scopeType > 0) {
456 var elem = new TemplateElement.attributes(tagToken.kind,
457 attrs.values.toList(), varName, _makeSpan(start));
458 stack.top().add(elem);
459
460 if (scopeType == 1) {
461 // Maybe more nested tags/text?
462 stack.push(elem);
463 }
464 }
465 } else {
466 // Close tag
467 _eat(TokenKind.SLASH);
468 if (TokenKind.validTagName(_peek())) {
469 Token tagToken = _next();
470
471 _eat(TokenKind.GREATER_THAN);
472
473 var elem = stack.pop();
474 if (elem is TemplateElement && !elem.isFragment) {
475 if (elem.tagTokenId != tagToken.kind) {
476 _error('Tag doesn\'t match expected </${elem.tagName}> got ' +
477 '</${TokenKind.tagNameFromTokenId(tagToken.kind)}>');
478 }
479 } else {
480 // Too many end tags.
481 _error('Too many end tags at ' +
482 '</${TokenKind.tagNameFromTokenId(tagToken.kind)}>');
483 }
484 }
485 }
486 } else if (_maybeEat(TokenKind.START_COMMAND)) {
487 Identifier commandName = processAsIdentifier();
488 if (commandName != null) {
489 switch (commandName.name) {
490 case "each":
491 case "with":
492 var listName = processAsIdentifier();
493 if (listName != null) {
494 var loopItem = processAsIdentifier();
495 // Is the optional item name specified?
496 // #each lists [item]
497 // #with expression [item]
498
499 _eat(TokenKind.RBRACE);
500
501 var frag = new TemplateElement.fragment(
502 _makeSpan(_peekToken.start));
503 TemplateDocument docFrag = processHTML(frag);
504
505 if (docFrag != null) {
506 var span = _makeSpan(start);
507 var cmd;
508 if (commandName.name == "each") {
509 cmd = new TemplateEachCommand(listName, loopItem, docFrag,
510 span);
511 } else if (commandName.name == "with") {
512 cmd = new TemplateWithCommand(listName, loopItem, docFrag,
513 span);
514 }
515
516 stack.top().add(cmd);
517 stack.push(cmd);
518 }
519
520 // Process ${/commandName}
521 _eat(TokenKind.END_COMMAND);
522
523 // Close command ${/commandName}
524 if (_peekIdentifier()) {
525 commandName = identifier();
526 switch (commandName.name) {
527 case "each":
528 case "with":
529 case "if":
530 case "else":
531 break;
532 default:
533 _error('Unknown command \${#${commandName}}');
534 }
535 var elem = stack.pop();
536 if (elem is TemplateEachCommand &&
537 commandName.name == "each") {
538
539 } else if (elem is TemplateWithCommand &&
540 commandName.name == "with") {
541
542 } /*else if (elem is TemplateIfCommand && commandName == "if") {
543
544 }
545 */else {
546 String expectedCmd;
547 if (elem is TemplateEachCommand) {
548 expectedCmd = "\${/each}";
549 } /* TODO(terry): else other commands as well */
550 _error('mismatched command expected ${expectedCmd} got...');
551 return;
552 }
553 _eat(TokenKind.RBRACE);
554 } else {
555 _error('Missing command name \${/commandName}');
556 }
557 } else {
558 _error("Missing listname for #each command");
559 }
560 break;
561 case "if":
562 break;
563 case "else":
564 break;
565 default:
566 // Calling another template.
567 int startPos = this._previousToken.end;
568 // Gobble up everything until we hit }
569 while (_peek() != TokenKind.RBRACE &&
570 _peek() != TokenKind.END_OF_FILE) {
571 _next(false);
572 }
573
574 if (_peek() == TokenKind.RBRACE) {
575 int endPos = this._previousToken.end;
576 TemplateCall callNode = new TemplateCall(commandName.name,
577 source.text.substring(startPos, endPos), _makeSpan(start));
578 stack.top().add(callNode);
579
580 _next(false);
581 } else {
582 _error("Unknown template command");
583 }
584 } // End of switch/case
585 }
586 } else if (_peekKind(TokenKind.END_COMMAND)) {
587 break;
588 } else {
589 // Any text or expression nodes?
590 var nodes = processTextNodes();
591 if (nodes.length > 0) {
592 assert(stack.top() != null);
593 for (var node in nodes) {
594 stack.top().add(node);
595 }
596 } else {
597 break;
598 }
599 }
600 }
601 /*
602 if (elems.children.length != 1) {
603 print("ERROR: No closing end-tag for elems ${elems[elems.length - 1]}");
604 }
605 */
606 var docChildren = new List<ASTNode>();
607 docChildren.add(stack.pop());
608 return new TemplateDocument(docChildren, _makeSpan(start));
609 }
610
611 /* Map is used so only last unique attribute name is remembered and to quickly
612 * find the var attribute.
613 */
614 Map<String, TemplateAttribute> processAttributes() {
615 Map<String, TemplateAttribute> attrs = new Map();
616
617 int start = _peekToken.start;
618 String elemName;
619 while (_peekIdentifier() ||
620 (elemName = TokenKind.tagNameFromTokenId(_peek())) != null) {
621 var attrName;
622 if (elemName == null) {
623 attrName = identifier();
624 } else {
625 attrName = new Identifier(elemName, _makeSpan(start));
626 _next();
627 }
628
629 var attrValue;
630
631 // Attribute value?
632 if (_peek() == TokenKind.ATTR_VALUE) {
633 var tok = _next();
634 attrValue = new StringValue(tok.value, _makeSpan(tok.start));
635 }
636
637 attrs[attrName.name] =
638 new TemplateAttribute(attrName, attrValue, _makeSpan(start));
639
640 start = _peekToken.start;
641 elemName = null;
642 }
643
644 return attrs;
645 }
646
647 identifier() {
648 var tok = _next();
649 if (!TokenKind.isIdentifier(tok.kind)) {
650 _error('expected identifier, but found $tok', tok.span);
651 }
652
653 return new Identifier(tok.text, _makeSpan(tok.start));
654 }
655
656 List<ASTNode> processTextNodes() {
657 // May contain TemplateText and TemplateExpression.
658 List<ASTNode> nodes = [];
659
660 int start = _peekToken.start;
661 bool inExpression = false;
662 StringBuffer stringValue = new StringBuffer();
663
664 // Any text chars between close of tag and text node?
665 if (_previousToken.kind == TokenKind.GREATER_THAN) {
666 // If the next token is } could be the close template token. If user
667 // needs } as token in text node use the entity &125;
668 // TODO(terry): Probably need a &RCURLY entity instead of 125.
669 if (_peek() == TokenKind.ERROR) {
670 // Backup, just past previous token, & rescan we're outside of the tag.
671 tokenizer.index = _previousToken.end;
672 _next(false);
673 } else if (_peek() != TokenKind.RBRACE) {
674 // Yes, grab the chars after the >
675 stringValue.write(_previousToken.source.text.substring(
676 this._previousToken.end, this._peekToken.start));
677 }
678 }
679
680 // Gobble up everything until we hit <
681 while (_peek() != TokenKind.LESS_THAN &&
682 _peek() != TokenKind.START_COMMAND &&
683 _peek() != TokenKind.END_COMMAND &&
684 (_peek() != TokenKind.RBRACE ||
685 (_peek() == TokenKind.RBRACE && inExpression)) &&
686 _peek() != TokenKind.END_OF_FILE) {
687
688 // Beginning of expression?
689 if (_peek() == TokenKind.START_EXPRESSION) {
690 if (stringValue.length > 0) {
691 // We have a real text node create the text node.
692 nodes.add(new TemplateText(stringValue.toString(), _makeSpan(start)));
693 stringValue = new StringBuffer();
694 start = _peekToken.start;
695 }
696 inExpression = true;
697 }
698
699 var tok = _next(false);
700 if (tok.kind == TokenKind.RBRACE && inExpression) {
701 // We have an expression create the expression node, don't save the }
702 inExpression = false;
703 nodes.add(new TemplateExpression(stringValue.toString(),
704 _makeSpan(start)));
705 stringValue = new StringBuffer();
706 start = _peekToken.start;
707 } else if (tok.kind != TokenKind.START_EXPRESSION) {
708 // Only save the the contents between ${ and }
709 stringValue.write(tok.text);
710 }
711 }
712
713 if (stringValue.length > 0) {
714 nodes.add(new TemplateText(stringValue.toString(), _makeSpan(start)));
715 }
716
717 return nodes;
718 }
719
720 }
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