| 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 |