OLD | NEW |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 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 | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 | 3 |
4 /** | 4 /** |
5 * A simple recursive descent parser for CSS. | 5 * A simple recursive descent parser for CSS. |
6 */ | 6 */ |
7 class Parser { | 7 class Parser { |
8 Tokenizer tokenizer; | 8 Tokenizer tokenizer; |
9 | 9 |
10 var _fs; // If non-null filesystem to read files. | 10 var _fs; // If non-null filesystem to read files. |
11 String _basePath; // Base path of CSS file. | 11 String _basePath; // Base path of CSS file. |
12 | 12 |
13 final lang.SourceFile source; | 13 final SourceFile source; |
14 | 14 |
15 lang.Token _previousToken; | 15 Token _previousToken; |
16 lang.Token _peekToken; | 16 Token _peekToken; |
17 | 17 |
| 18 // Communicating errors back to template parser. |
| 19 // TODO(terry): Need a better mechanism (e.g., common World). |
| 20 var _erroMsgRedirector; |
| 21 |
18 Parser(this.source, [int start = 0, this._fs = null, this._basePath = null]) { | 22 Parser(this.source, [int start = 0, this._fs = null, this._basePath = null]) { |
19 tokenizer = new Tokenizer(source, true, start); | 23 tokenizer = new Tokenizer(source, true, start); |
20 _peekToken = tokenizer.next(); | 24 _peekToken = tokenizer.next(); |
21 _previousToken = null; | 25 _previousToken = null; |
22 } | 26 } |
23 | 27 |
24 // Main entry point for parsing an entire CSS file. | 28 // Main entry point for parsing an entire CSS file. |
25 Stylesheet parse() { | 29 // If nestedCSS is true when we're back at processing directives from top and |
26 List<lang.Node> productions = []; | 30 // we encounter a } then stop we're inside of a template e.g., |
| 31 // |
| 32 // template ... { |
| 33 // css { |
| 34 // .item { |
| 35 // left: 10px; |
| 36 // } |
| 37 // } |
| 38 // <div>...</div> |
| 39 // } |
| 40 // |
| 41 Stylesheet parse([bool nestedCSS = false, var erroMsgRedirector = null]) { |
| 42 // TODO(terry): Hack for migrating CSS errors back to template errors. |
| 43 _erroMsgRedirector = erroMsgRedirector; |
| 44 |
| 45 List<ASTNode> productions = []; |
27 | 46 |
28 int start = _peekToken.start; | 47 int start = _peekToken.start; |
29 while (!_maybeEat(TokenKind.END_OF_FILE)) { | 48 while (!_maybeEat(TokenKind.END_OF_FILE) && |
| 49 (!nestedCSS && !_peekKind(TokenKind.RBRACE))) { |
30 // TODO(terry): Need to handle charset, import, media and page. | 50 // TODO(terry): Need to handle charset, import, media and page. |
31 var directive = processDirective(); | 51 var directive = processDirective(); |
32 if (directive != null) { | 52 if (directive != null) { |
33 productions.add(directive); | 53 productions.add(directive); |
34 } else { | 54 } else { |
35 productions.add(processRuleSet()); | 55 RuleSet ruleset = processRuleSet(); |
| 56 if (ruleset != null) { |
| 57 productions.add(ruleset); |
| 58 } else { |
| 59 break; |
| 60 } |
36 } | 61 } |
37 } | 62 } |
38 | 63 |
39 return new Stylesheet(productions, _makeSpan(start)); | 64 return new Stylesheet(productions, _makeSpan(start)); |
40 } | 65 } |
41 | 66 |
42 /** Generate an error if [source] has not been completely consumed. */ | 67 /** Generate an error if [source] has not been completely consumed. */ |
43 void checkEndOfFile() { | 68 void checkEndOfFile() { |
44 _eat(TokenKind.END_OF_FILE); | 69 _eat(TokenKind.END_OF_FILE); |
45 } | 70 } |
(...skipping 11 matching lines...) Expand all Loading... |
57 } | 82 } |
58 } | 83 } |
59 | 84 |
60 /////////////////////////////////////////////////////////////////// | 85 /////////////////////////////////////////////////////////////////// |
61 // Basic support methods | 86 // Basic support methods |
62 /////////////////////////////////////////////////////////////////// | 87 /////////////////////////////////////////////////////////////////// |
63 int _peek() { | 88 int _peek() { |
64 return _peekToken.kind; | 89 return _peekToken.kind; |
65 } | 90 } |
66 | 91 |
67 lang.Token _next() { | 92 Token _next() { |
68 _previousToken = _peekToken; | 93 _previousToken = _peekToken; |
69 _peekToken = tokenizer.next(); | 94 _peekToken = tokenizer.next(); |
70 return _previousToken; | 95 return _previousToken; |
71 } | 96 } |
72 | 97 |
73 bool _peekKind(int kind) { | 98 bool _peekKind(int kind) { |
74 return _peekToken.kind == kind; | 99 return _peekToken.kind == kind; |
75 } | 100 } |
76 | 101 |
77 /* Is the next token a legal identifier? This includes pseudo-keywords. */ | 102 /* Is the next token a legal identifier? This includes pseudo-keywords. */ |
(...skipping 25 matching lines...) Expand all Loading... |
103 var tok = _next(); | 128 var tok = _next(); |
104 var message; | 129 var message; |
105 try { | 130 try { |
106 message = 'expected $expected, but found $tok'; | 131 message = 'expected $expected, but found $tok'; |
107 } catch (final e) { | 132 } catch (final e) { |
108 message = 'parsing error expected $expected'; | 133 message = 'parsing error expected $expected'; |
109 } | 134 } |
110 _error(message, tok.span); | 135 _error(message, tok.span); |
111 } | 136 } |
112 | 137 |
113 void _error(String message, [lang.SourceSpan location=null]) { | 138 void _error(String message, [SourceSpan location=null]) { |
114 if (location === null) { | 139 if (location === null) { |
115 location = _peekToken.span; | 140 location = _peekToken.span; |
116 } | 141 } |
117 | 142 |
118 lang.world.fatal(message, location); // syntax errors are fatal for now | 143 if (_erroMsgRedirector == null) { |
| 144 world.fatal(message, location); // syntax errors are fatal for now |
| 145 } else { |
| 146 String text = ""; |
| 147 if (location != null) { |
| 148 text = location.toMessageString(""); |
| 149 } |
| 150 _erroMsgRedirector.displayError("CSS error: \r${text}\r${message}"); |
| 151 } |
119 } | 152 } |
120 | 153 |
121 void _warning(String message, [lang.SourceSpan location=null]) { | 154 void _warning(String message, [SourceSpan location=null]) { |
122 if (location === null) { | 155 if (location === null) { |
123 location = _peekToken.span; | 156 location = _peekToken.span; |
124 } | 157 } |
125 | 158 |
126 lang.world.warning(message, location); | 159 world.warning(message, location); |
127 } | 160 } |
128 | 161 |
129 lang.SourceSpan _makeSpan(int start) { | 162 SourceSpan _makeSpan(int start) { |
130 return new lang.SourceSpan(source, start, _previousToken.end); | 163 return new SourceSpan(source, start, _previousToken.end); |
131 } | 164 } |
132 | 165 |
133 /////////////////////////////////////////////////////////////////// | 166 /////////////////////////////////////////////////////////////////// |
134 // Top level productions | 167 // Top level productions |
135 /////////////////////////////////////////////////////////////////// | 168 /////////////////////////////////////////////////////////////////// |
136 | 169 |
137 // Templates are @{selectors} single line nothing else. | 170 // Templates are @{selectors} single line nothing else. |
138 SelectorGroup parseTemplate() { | 171 SelectorGroup parseTemplate() { |
139 SelectorGroup selectorGroup = null; | 172 SelectorGroup selectorGroup = null; |
140 if (!isPrematureEndOfFile()) { | 173 if (!isPrematureEndOfFile()) { |
(...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
306 if (_fs.fileExists('${_basePath}${filename}')) { | 339 if (_fs.fileExists('${_basePath}${filename}')) { |
307 String basePath = ""; | 340 String basePath = ""; |
308 int idx = filename.lastIndexOf('/'); | 341 int idx = filename.lastIndexOf('/'); |
309 if (idx >= 0) { | 342 if (idx >= 0) { |
310 basePath = filename.substring(0, idx + 1); | 343 basePath = filename.substring(0, idx + 1); |
311 } | 344 } |
312 basePath = '${_basePath}${basePath}'; | 345 basePath = '${_basePath}${basePath}'; |
313 // Yes, let's parse this file as well. | 346 // Yes, let's parse this file as well. |
314 String fullFN = '${basePath}${filename}'; | 347 String fullFN = '${basePath}${filename}'; |
315 String contents = _fs.readAll(fullFN); | 348 String contents = _fs.readAll(fullFN); |
316 Parser parser = new Parser(new lang.SourceFile(fullFN, contents), 0, | 349 Parser parser = new Parser(new SourceFile(fullFN, contents), 0, |
317 _fs, basePath); | 350 _fs, basePath); |
318 Stylesheet stylesheet = parser.parse(); | 351 Stylesheet stylesheet = parser.parse(); |
319 return new IncludeDirective(filename, stylesheet, _makeSpan(start)); | 352 return new IncludeDirective(filename, stylesheet, _makeSpan(start)); |
320 } | 353 } |
321 | 354 |
322 _error('file doesn\'t exist ${filename}', _peekToken.span); | 355 _error('file doesn\'t exist ${filename}', _peekToken.span); |
323 } | 356 } |
324 | 357 |
325 print("WARNING: @include doesn't work for uitest"); | 358 print("WARNING: @include doesn't work for uitest"); |
326 return new IncludeDirective(filename, null, _makeSpan(start)); | 359 return new IncludeDirective(filename, null, _makeSpan(start)); |
327 case TokenKind.DIRECTIVE_STYLET: | 360 case TokenKind.DIRECTIVE_STYLET: |
328 /* Stylet grammar: | 361 /* Stylet grammar: |
329 * | 362 * |
330 * @stylet IDENT '{' | 363 * @stylet IDENT '{' |
331 * ruleset | 364 * ruleset |
332 * '}' | 365 * '}' |
333 */ | 366 */ |
334 _next(); | 367 _next(); |
335 | 368 |
336 var name; | 369 var name; |
337 if (_peekIdentifier()) { | 370 if (_peekIdentifier()) { |
338 name = identifier(); | 371 name = identifier(); |
339 } | 372 } |
340 | 373 |
341 _eat(TokenKind.LBRACE); | 374 _eat(TokenKind.LBRACE); |
342 | 375 |
343 List<lang.Node> productions = []; | 376 List<ASTNode> productions = []; |
344 | 377 |
345 int start = _peekToken.start; | 378 start = _peekToken.start; |
346 while (!_maybeEat(TokenKind.END_OF_FILE)) { | 379 while (!_maybeEat(TokenKind.END_OF_FILE)) { |
347 RuleSet ruleset = processRuleSet(); | 380 RuleSet ruleset = processRuleSet(); |
348 if (ruleset == null) { | 381 if (ruleset == null) { |
349 break; | 382 break; |
350 } | 383 } |
351 productions.add(ruleset); | 384 productions.add(ruleset); |
352 } | 385 } |
353 | 386 |
354 _eat(TokenKind.RBRACE); | 387 _eat(TokenKind.RBRACE); |
355 | 388 |
356 return new StyletDirective(name, productions, _makeSpan(start)); | 389 return new StyletDirective(name, productions, _makeSpan(start)); |
357 default: | 390 default: |
358 _error('unknown directive, found $_peekToken', _peekToken.span); | 391 _error('unknown directive, found $_peekToken', _peekToken.span); |
359 } | 392 } |
360 } | 393 } |
361 } | 394 } |
362 | 395 |
363 processRuleSet() { | 396 RuleSet processRuleSet() { |
364 int start = _peekToken.start; | 397 int start = _peekToken.start; |
365 | 398 |
366 SelectorGroup selGroup = processSelectorGroup(); | 399 SelectorGroup selGroup = processSelectorGroup(); |
367 if (selGroup != null) { | 400 if (selGroup != null) { |
368 return new RuleSet(selGroup, processDeclarations(), _makeSpan(start)); | 401 return new RuleSet(selGroup, processDeclarations(), _makeSpan(start)); |
369 } | 402 } |
370 } | 403 } |
371 | 404 |
372 DeclarationGroup processDeclarations() { | 405 DeclarationGroup processDeclarations() { |
373 int start = _peekToken.start; | 406 int start = _peekToken.start; |
(...skipping 317 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
691 // LENGTH: {num}['px' | 'cm' | 'mm' | 'in' | 'pt' | 'pc'] | 724 // LENGTH: {num}['px' | 'cm' | 'mm' | 'in' | 'pt' | 'pc'] |
692 // EMS: {num}'em' | 725 // EMS: {num}'em' |
693 // EXS: {num}'ex' | 726 // EXS: {num}'ex' |
694 // ANGLE: {num}['deg' | 'rad' | 'grad'] | 727 // ANGLE: {num}['deg' | 'rad' | 'grad'] |
695 // TIME: {num}['ms' | 's'] | 728 // TIME: {num}['ms' | 's'] |
696 // FREQ: {num}['hz' | 'khz'] | 729 // FREQ: {num}['hz' | 'khz'] |
697 // function: IDENT '(' expr ')' | 730 // function: IDENT '(' expr ')' |
698 // | 731 // |
699 processTerm() { | 732 processTerm() { |
700 int start = _peekToken.start; | 733 int start = _peekToken.start; |
701 lang.Token t; // token for term's value | 734 Token t; // token for term's value |
702 var value; // value of term (numeric values) | 735 var value; // value of term (numeric values) |
703 | 736 |
704 var unary = ""; | 737 var unary = ""; |
705 | 738 |
706 switch (_peek()) { | 739 switch (_peek()) { |
707 case TokenKind.HASH: | 740 case TokenKind.HASH: |
708 this._eat(TokenKind.HASH); | 741 this._eat(TokenKind.HASH); |
709 String hexText; | 742 String hexText; |
710 if (_peekKind(TokenKind.INTEGER)) { | 743 if (_peekKind(TokenKind.INTEGER)) { |
711 String hexText1 = _peekToken.text; | 744 String hexText1 = _peekToken.text; |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
771 // FUNCTION | 804 // FUNCTION |
772 return processFunction(nameValue); | 805 return processFunction(nameValue); |
773 } else { | 806 } else { |
774 // TODO(terry): Need to have a list of known identifiers today only | 807 // TODO(terry): Need to have a list of known identifiers today only |
775 // 'from' is special. | 808 // 'from' is special. |
776 if (nameValue.name == 'from') { | 809 if (nameValue.name == 'from') { |
777 return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start)); | 810 return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start)); |
778 } | 811 } |
779 | 812 |
780 // What kind of identifier is it? | 813 // What kind of identifier is it? |
781 int value; | |
782 try { | 814 try { |
783 // Named color? | 815 // Named color? |
784 value = TokenKind.matchColorName(nameValue.name); | 816 int colorValue = TokenKind.matchColorName(nameValue.name); |
785 | 817 |
786 // Yes, process the color as an RGB value. | 818 // Yes, process the color as an RGB value. |
787 String rgbColor = TokenKind.decimalToHex(value); | 819 String rgbColor = TokenKind.decimalToHex(colorValue); |
788 int value; | |
789 try { | 820 try { |
790 value = parseHex(rgbColor); | 821 colorValue = parseHex(rgbColor); |
791 } catch (HexNumberException hne) { | 822 } catch (HexNumberException hne) { |
792 _error('Bad hex number', _makeSpan(start)); | 823 _error('Bad hex number', _makeSpan(start)); |
793 } | 824 } |
794 return new HexColorTerm(value, rgbColor, _makeSpan(start)); | 825 return new HexColorTerm(colorValue, rgbColor, _makeSpan(start)); |
795 } catch (final error) { | 826 } catch (final error) { |
796 if (error is NoColorMatchException) { | 827 if (error is NoColorMatchException) { |
797 // TODO(terry): Other named things to match with validator? | 828 // TODO(terry): Other named things to match with validator? |
798 _warning('Unknown property value ${error.name}', _makeSpan(start)); | 829 |
| 830 // TODO(terry): Disable call to _warning need one World class for |
| 831 // both CSS parser and other parser (e.g., template) |
| 832 // so all warnings, errors, options, etc. are driven |
| 833 // from the one World. |
| 834 // _warning('Unknown property value ${error.name}', _makeSpan(start)); |
799 return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start)); | 835 return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start)); |
800 } | 836 } |
801 } | 837 } |
802 } | 838 } |
803 } | 839 } |
804 | 840 |
805 var term; | 841 var term; |
806 var unitType = this._peek(); | 842 var unitType = this._peek(); |
807 | 843 |
808 switch (unitType) { | 844 switch (unitType) { |
(...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
971 | 1007 |
972 return result; | 1008 return result; |
973 } | 1009 } |
974 } | 1010 } |
975 | 1011 |
976 /** Not a hex number. */ | 1012 /** Not a hex number. */ |
977 class HexNumberException implements Exception { | 1013 class HexNumberException implements Exception { |
978 HexNumberException(); | 1014 HexNumberException(); |
979 } | 1015 } |
980 | 1016 |
OLD | NEW |