 Chromium Code Reviews
 Chromium Code Reviews Issue 751453004:
  Improve the speed and memory efficiency of csslib parsing.  (Closed) 
  Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
    
  
    Issue 751453004:
  Improve the speed and memory efficiency of csslib parsing.  (Closed) 
  Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart| OLD | NEW | 
|---|---|
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. | 
| 4 | 4 | 
| 5 library csslib.parser; | 5 library csslib.parser; | 
| 6 | 6 | 
| 7 import 'dart:math' as math; | 7 import 'dart:math' as math; | 
| 8 | 8 | 
| 9 import 'package:source_span/source_span.dart'; | 9 import 'package:source_span/source_span.dart'; | 
| 10 | 10 | 
| (...skipping 178 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 189 : this.file = file, | 189 : this.file = file, | 
| 190 _baseUrl = baseUrl, | 190 _baseUrl = baseUrl, | 
| 191 tokenizer = new Tokenizer(file, text, true, start) { | 191 tokenizer = new Tokenizer(file, text, true, start) { | 
| 192 _peekToken = tokenizer.next(); | 192 _peekToken = tokenizer.next(); | 
| 193 } | 193 } | 
| 194 | 194 | 
| 195 /** Main entry point for parsing an entire CSS file. */ | 195 /** Main entry point for parsing an entire CSS file. */ | 
| 196 StyleSheet parse() { | 196 StyleSheet parse() { | 
| 197 List<TreeNode> productions = []; | 197 List<TreeNode> productions = []; | 
| 198 | 198 | 
| 199 int start = _peekToken.start; | 199 var start = _peekToken.span; | 
| 200 while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) { | 200 while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) { | 
| 201 // TODO(terry): Need to handle charset. | 201 // TODO(terry): Need to handle charset. | 
| 202 var directive = processDirective(); | 202 var directive = processDirective(); | 
| 203 if (directive != null) { | 203 if (directive != null) { | 
| 204 productions.add(directive); | 204 productions.add(directive); | 
| 205 _maybeEat(TokenKind.SEMICOLON); | 205 _maybeEat(TokenKind.SEMICOLON); | 
| 206 } else { | 206 } else { | 
| 207 RuleSet ruleset = processRuleSet(); | 207 RuleSet ruleset = processRuleSet(); | 
| 208 if (ruleset != null) { | 208 if (ruleset != null) { | 
| 209 productions.add(ruleset); | 209 productions.add(ruleset); | 
| 210 } else { | 210 } else { | 
| 211 break; | 211 break; | 
| 212 } | 212 } | 
| 213 } | 213 } | 
| 214 } | 214 } | 
| 215 | 215 | 
| 216 checkEndOfFile(); | 216 checkEndOfFile(); | 
| 217 | 217 | 
| 218 return new StyleSheet(productions, _makeSpan(start)); | 218 return new StyleSheet(productions, _makeSpan(start)); | 
| 219 } | 219 } | 
| 220 | 220 | 
| 221 /** Main entry point for parsing a simple selector sequence. */ | 221 /** Main entry point for parsing a simple selector sequence. */ | 
| 222 StyleSheet parseSelector() { | 222 StyleSheet parseSelector() { | 
| 223 List<TreeNode> productions = []; | 223 List<TreeNode> productions = []; | 
| 224 | 224 | 
| 225 int start = _peekToken.start; | 225 var start = _peekToken.span; | 
| 226 while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) { | 226 while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) { | 
| 227 var selector = processSelector(); | 227 var selector = processSelector(); | 
| 228 if (selector != null) { | 228 if (selector != null) { | 
| 229 productions.add(selector); | 229 productions.add(selector); | 
| 230 } | 230 } | 
| 231 } | 231 } | 
| 232 | 232 | 
| 233 checkEndOfFile(); | 233 checkEndOfFile(); | 
| 234 | 234 | 
| 235 return new StyleSheet.selector(productions, _makeSpan(start)); | 235 return new StyleSheet.selector(productions, _makeSpan(start)); | 
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 327 messages.error(message, location); | 327 messages.error(message, location); | 
| 328 } | 328 } | 
| 329 | 329 | 
| 330 void _warning(String message, SourceSpan location) { | 330 void _warning(String message, SourceSpan location) { | 
| 331 if (location == null) { | 331 if (location == null) { | 
| 332 location = _peekToken.span; | 332 location = _peekToken.span; | 
| 333 } | 333 } | 
| 334 messages.warning(message, location); | 334 messages.warning(message, location); | 
| 335 } | 335 } | 
| 336 | 336 | 
| 337 SourceSpan _makeSpan(int start) { | 337 SourceSpan _makeSpan(FileSpan start) { | 
| 338 // TODO(terry): there are places where we are creating spans before we eat | 338 // TODO(terry): there are places where we are creating spans before we eat | 
| 339 // the tokens, so using _previousToken.end is not always valid. | 339 // the tokens, so using _previousToken is not always valid. | 
| 340 var end = _previousToken != null && _previousToken.end >= start | 340 // TODO(nweiz): use < rather than compareTo when SourceSpan supports it. | 
| 341 ? _previousToken.end : _peekToken.end; | 341 if (_previousToken == null || _previousToken.span.compareTo(start) < 0) { | 
| 342 return file.span(start, end); | 342 return start; | 
| 343 } | |
| 344 return start.expand(_previousToken.span); | |
| 343 } | 345 } | 
| 344 | 346 | 
| 345 /////////////////////////////////////////////////////////////////// | 347 /////////////////////////////////////////////////////////////////// | 
| 346 // Top level productions | 348 // Top level productions | 
| 347 /////////////////////////////////////////////////////////////////// | 349 /////////////////////////////////////////////////////////////////// | 
| 348 | 350 | 
| 349 /** | 351 /** | 
| 350 * The media_query_list production below replaces the media_list production | 352 * The media_query_list production below replaces the media_list production | 
| 351 * from CSS2 the new grammar is: | 353 * from CSS2 the new grammar is: | 
| 352 * | 354 * | 
| (...skipping 29 matching lines...) Expand all Loading... | |
| 382 firstTime = true; | 384 firstTime = true; | 
| 383 } while ((!firstTime && mediaQuery != null) || firstTime); | 385 } while ((!firstTime && mediaQuery != null) || firstTime); | 
| 384 | 386 | 
| 385 return mediaQueries; | 387 return mediaQueries; | 
| 386 } | 388 } | 
| 387 | 389 | 
| 388 MediaQuery processMediaQuery([bool startQuery = true]) { | 390 MediaQuery processMediaQuery([bool startQuery = true]) { | 
| 389 // Grammar: [ONLY | NOT]? S* media_type S* | 391 // Grammar: [ONLY | NOT]? S* media_type S* | 
| 390 // [ AND S* MediaExpr ]* | MediaExpr [ AND S* MediaExpr ]* | 392 // [ AND S* MediaExpr ]* | MediaExpr [ AND S* MediaExpr ]* | 
| 391 | 393 | 
| 392 int start = _peekToken.start; | 394 var start = _peekToken.span; | 
| 393 | 395 | 
| 394 // Is it a unary media operator? | 396 // Is it a unary media operator? | 
| 395 var op = _peekToken.text; | 397 var op = _peekToken.text; | 
| 396 var opLen = op.length; | 398 var opLen = op.length; | 
| 397 var unaryOp = TokenKind.matchMediaOperator(op, 0, opLen); | 399 var unaryOp = TokenKind.matchMediaOperator(op, 0, opLen); | 
| 398 if (unaryOp != -1) { | 400 if (unaryOp != -1) { | 
| 399 if (isChecked) { | 401 if (isChecked) { | 
| 400 if (startQuery && | 402 if (startQuery && | 
| 401 unaryOp != TokenKind.MEDIA_OP_NOT || | 403 unaryOp != TokenKind.MEDIA_OP_NOT || | 
| 402 unaryOp != TokenKind.MEDIA_OP_ONLY) { | 404 unaryOp != TokenKind.MEDIA_OP_ONLY) { | 
| 403 _warning("Only the unary operators NOT and ONLY allowed", | 405 _warning("Only the unary operators NOT and ONLY allowed", | 
| 404 _makeSpan(start)); | 406 _makeSpan(start)); | 
| 405 } | 407 } | 
| 406 if (!startQuery && unaryOp != TokenKind.MEDIA_OP_AND) { | 408 if (!startQuery && unaryOp != TokenKind.MEDIA_OP_AND) { | 
| 407 _warning("Only the binary AND operator allowed", _makeSpan(start)); | 409 _warning("Only the binary AND operator allowed", _makeSpan(start)); | 
| 408 } | 410 } | 
| 409 } | 411 } | 
| 410 _next(); | 412 _next(); | 
| 411 start = _peekToken.start; | 413 start = _peekToken.span; | 
| 412 } | 414 } | 
| 413 | 415 | 
| 414 var type; | 416 var type; | 
| 415 if (startQuery && unaryOp != TokenKind.MEDIA_OP_AND) { | 417 if (startQuery && unaryOp != TokenKind.MEDIA_OP_AND) { | 
| 416 // Get the media type. | 418 // Get the media type. | 
| 417 if (_peekIdentifier()) type = identifier(); | 419 if (_peekIdentifier()) type = identifier(); | 
| 418 } | 420 } | 
| 419 | 421 | 
| 420 var exprs = []; | 422 var exprs = []; | 
| 421 | 423 | 
| (...skipping 12 matching lines...) Expand all Loading... | |
| 434 _next(); | 436 _next(); | 
| 435 } | 437 } | 
| 436 } | 438 } | 
| 437 | 439 | 
| 438 if (unaryOp != -1 || type != null || exprs.length > 0) { | 440 if (unaryOp != -1 || type != null || exprs.length > 0) { | 
| 439 return new MediaQuery(unaryOp, type, exprs, _makeSpan(start)); | 441 return new MediaQuery(unaryOp, type, exprs, _makeSpan(start)); | 
| 440 } | 442 } | 
| 441 } | 443 } | 
| 442 | 444 | 
| 443 MediaExpression processMediaExpression([bool andOperator = false]) { | 445 MediaExpression processMediaExpression([bool andOperator = false]) { | 
| 444 int start = _peekToken.start; | 446 var start = _peekToken.span; | 
| 445 | 447 | 
| 446 // Grammar: '(' S* media_feature S* [ ':' S* expr ]? ')' S* | 448 // Grammar: '(' S* media_feature S* [ ':' S* expr ]? ')' S* | 
| 447 if (_maybeEat(TokenKind.LPAREN)) { | 449 if (_maybeEat(TokenKind.LPAREN)) { | 
| 448 if (_peekIdentifier()) { | 450 if (_peekIdentifier()) { | 
| 449 var feature = identifier(); // Media feature. | 451 var feature = identifier(); // Media feature. | 
| 450 while (_maybeEat(TokenKind.COLON)) { | 452 while (_maybeEat(TokenKind.COLON)) { | 
| 451 int startExpr = _peekToken.start; | 453 var startExpr = _peekToken.span; | 
| 452 var exprs = processExpr(); | 454 var exprs = processExpr(); | 
| 453 if (_maybeEat(TokenKind.RPAREN)) { | 455 if (_maybeEat(TokenKind.RPAREN)) { | 
| 454 return new MediaExpression(andOperator, feature, exprs, | 456 return new MediaExpression(andOperator, feature, exprs, | 
| 455 _makeSpan(startExpr)); | 457 _makeSpan(startExpr)); | 
| 456 } else if (isChecked) { | 458 } else if (isChecked) { | 
| 457 _warning("Missing parenthesis around media expression", | 459 _warning("Missing parenthesis around media expression", | 
| 458 _makeSpan(start)); | 460 _makeSpan(start)); | 
| 459 return null; | 461 return null; | 
| 460 } | 462 } | 
| 461 } | 463 } | 
| (...skipping 15 matching lines...) Expand all Loading... | |
| 477 * keyframes: '@-webkit-keyframes ...' (see grammar below). | 479 * keyframes: '@-webkit-keyframes ...' (see grammar below). | 
| 478 * font_face: '@font-face' '{' declarations '}' | 480 * font_face: '@font-face' '{' declarations '}' | 
| 479 * namespace: '@namespace name url("xmlns") | 481 * namespace: '@namespace name url("xmlns") | 
| 480 * host: '@host '{' ruleset '}' | 482 * host: '@host '{' ruleset '}' | 
| 481 * mixin: '@mixin name [(args,...)] '{' declarations/ruleset '}' | 483 * mixin: '@mixin name [(args,...)] '{' declarations/ruleset '}' | 
| 482 * include: '@include name [(@arg,@arg1)] | 484 * include: '@include name [(@arg,@arg1)] | 
| 483 * '@include name [(@arg...)] | 485 * '@include name [(@arg...)] | 
| 484 * content '@content' | 486 * content '@content' | 
| 485 */ | 487 */ | 
| 486 processDirective() { | 488 processDirective() { | 
| 487 int start = _peekToken.start; | 489 var start = _peekToken.span; | 
| 488 | 490 | 
| 489 var tokId = processVariableOrDirective(); | 491 var tokId = processVariableOrDirective(); | 
| 490 if (tokId is VarDefinitionDirective) return tokId; | 492 if (tokId is VarDefinitionDirective) return tokId; | 
| 491 switch (tokId) { | 493 switch (tokId) { | 
| 492 case TokenKind.DIRECTIVE_IMPORT: | 494 case TokenKind.DIRECTIVE_IMPORT: | 
| 493 _next(); | 495 _next(); | 
| 494 | 496 | 
| 495 // @import "uri_string" or @import url("uri_string") are identical; only | 497 // @import "uri_string" or @import url("uri_string") are identical; only | 
| 496 // a url can follow an @import. | 498 // a url can follow an @import. | 
| 497 String importStr; | 499 String importStr; | 
| (...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 696 | 698 | 
| 697 var name; | 699 var name; | 
| 698 if (_peekIdentifier()) { | 700 if (_peekIdentifier()) { | 
| 699 name = identifier(); | 701 name = identifier(); | 
| 700 } | 702 } | 
| 701 | 703 | 
| 702 _eat(TokenKind.LBRACE); | 704 _eat(TokenKind.LBRACE); | 
| 703 | 705 | 
| 704 List<TreeNode> productions = []; | 706 List<TreeNode> productions = []; | 
| 705 | 707 | 
| 706 start = _peekToken.start; | 708 start = _peekToken.span; | 
| 707 while (!_maybeEat(TokenKind.END_OF_FILE)) { | 709 while (!_maybeEat(TokenKind.END_OF_FILE)) { | 
| 708 RuleSet ruleset = processRuleSet(); | 710 RuleSet ruleset = processRuleSet(); | 
| 709 if (ruleset == null) { | 711 if (ruleset == null) { | 
| 710 break; | 712 break; | 
| 711 } | 713 } | 
| 712 productions.add(ruleset); | 714 productions.add(ruleset); | 
| 713 } | 715 } | 
| 714 | 716 | 
| 715 _eat(TokenKind.RBRACE); | 717 _eat(TokenKind.RBRACE); | 
| 716 | 718 | 
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 748 } | 750 } | 
| 749 } else { | 751 } else { | 
| 750 namespaceUri = processQuotedString(false); | 752 namespaceUri = processQuotedString(false); | 
| 751 } | 753 } | 
| 752 } | 754 } | 
| 753 | 755 | 
| 754 return new NamespaceDirective(prefix != null ? prefix.name : '', | 756 return new NamespaceDirective(prefix != null ? prefix.name : '', | 
| 755 namespaceUri, _makeSpan(start)); | 757 namespaceUri, _makeSpan(start)); | 
| 756 | 758 | 
| 757 case TokenKind.DIRECTIVE_MIXIN: | 759 case TokenKind.DIRECTIVE_MIXIN: | 
| 758 return processMixin(start); | 760 return processMixin(); | 
| 759 | 761 | 
| 760 case TokenKind.DIRECTIVE_INCLUDE: | 762 case TokenKind.DIRECTIVE_INCLUDE: | 
| 761 return processInclude( _makeSpan(start)); | 763 return processInclude( _makeSpan(start)); | 
| 762 | 764 | 
| 763 case TokenKind.DIRECTIVE_CONTENT: | 765 case TokenKind.DIRECTIVE_CONTENT: | 
| 764 // TODO(terry): TBD | 766 // TODO(terry): TBD | 
| 765 _warning("@content not implemented.", _makeSpan(start)); | 767 _warning("@content not implemented.", _makeSpan(start)); | 
| 766 return null; | 768 return null; | 
| 767 } | 769 } | 
| 768 return null; | 770 return null; | 
| 769 } | 771 } | 
| 770 | 772 | 
| 771 /** | 773 /** | 
| 772 * Parse the mixin beginning token offset [start]. Returns a [MixinDefinition] | 774 * Parse the mixin beginning token offset [start]. Returns a [MixinDefinition] | 
| 773 * node. | 775 * node. | 
| 774 * | 776 * | 
| 775 * Mixin grammar: | 777 * Mixin grammar: | 
| 776 * | 778 * | 
| 777 * @mixin IDENT [(args,...)] '{' | 779 * @mixin IDENT [(args,...)] '{' | 
| 778 * [ruleset | property | directive]* | 780 * [ruleset | property | directive]* | 
| 779 * '}' | 781 * '}' | 
| 780 */ | 782 */ | 
| 781 MixinDefinition processMixin(int start) { | 783 MixinDefinition processMixin() { | 
| 782 _next(); | 784 _next(); | 
| 783 | 785 | 
| 784 var name = identifier(); | 786 var name = identifier(); | 
| 785 | 787 | 
| 786 List<VarDefinitionDirective> params = []; | 788 List<VarDefinitionDirective> params = []; | 
| 787 // Any parameters? | 789 // Any parameters? | 
| 788 if (_maybeEat(TokenKind.LPAREN)) { | 790 if (_maybeEat(TokenKind.LPAREN)) { | 
| 789 var mustHaveParam = false; | 791 var mustHaveParam = false; | 
| 790 var keepGoing = true; | 792 var keepGoing = true; | 
| 791 while (keepGoing) { | 793 while (keepGoing) { | 
| 792 var varDef = processVariableOrDirective(mixinParameter: true); | 794 var varDef = processVariableOrDirective(mixinParameter: true); | 
| 793 if (varDef is VarDefinitionDirective || varDef is VarDefinition) { | 795 if (varDef is VarDefinitionDirective || varDef is VarDefinition) { | 
| 794 params.add(varDef); | 796 params.add(varDef); | 
| 795 } else if (mustHaveParam) { | 797 } else if (mustHaveParam) { | 
| 796 _warning("Expecting parameter", _makeSpan(_peekToken.start)); | 798 _warning("Expecting parameter", _makeSpan(_peekToken.span)); | 
| 797 keepGoing = false; | 799 keepGoing = false; | 
| 798 } | 800 } | 
| 799 if (_maybeEat(TokenKind.COMMA)) { | 801 if (_maybeEat(TokenKind.COMMA)) { | 
| 800 mustHaveParam = true; | 802 mustHaveParam = true; | 
| 801 continue; | 803 continue; | 
| 802 } | 804 } | 
| 803 keepGoing = !_maybeEat(TokenKind.RPAREN); | 805 keepGoing = !_maybeEat(TokenKind.RPAREN); | 
| 804 } | 806 } | 
| 805 } | 807 } | 
| 806 | 808 | 
| 807 _eat(TokenKind.LBRACE); | 809 _eat(TokenKind.LBRACE); | 
| 808 | 810 | 
| 809 List<TreeNode> productions = []; | 811 List<TreeNode> productions = []; | 
| 810 List<TreeNode> declarations = []; | 812 List<TreeNode> declarations = []; | 
| 811 var mixinDirective; | 813 var mixinDirective; | 
| 812 | 814 | 
| 813 start = _peekToken.start; | 815 var start = _peekToken.span; | 
| 814 while (!_maybeEat(TokenKind.END_OF_FILE)) { | 816 while (!_maybeEat(TokenKind.END_OF_FILE)) { | 
| 815 var directive = processDirective(); | 817 var directive = processDirective(); | 
| 816 if (directive != null) { | 818 if (directive != null) { | 
| 817 productions.add(directive); | 819 productions.add(directive); | 
| 818 continue; | 820 continue; | 
| 819 } | 821 } | 
| 820 | 822 | 
| 821 var declGroup = processDeclarations(checkBrace: false); | 823 var declGroup = processDeclarations(checkBrace: false); | 
| 822 var decls = []; | 824 var decls = []; | 
| 823 if (declGroup.declarations.any((decl) { | 825 if (declGroup.declarations.any((decl) { | 
| 824 return decl is Declaration && | 826 return decl is Declaration && | 
| 825 decl is! IncludeMixinAtDeclaration; | 827 decl is! IncludeMixinAtDeclaration; | 
| 826 })) { | 828 })) { | 
| 827 var newDecls = []; | 829 var newDecls = []; | 
| 828 productions.forEach((include) { | 830 productions.forEach((include) { | 
| 829 // If declGroup has items that are declarations then we assume | 831 // If declGroup has items that are declarations then we assume | 
| 830 // this mixin is a declaration mixin not a top-level mixin. | 832 // this mixin is a declaration mixin not a top-level mixin. | 
| 831 if (include is IncludeDirective) { | 833 if (include is IncludeDirective) { | 
| 832 newDecls.add(new IncludeMixinAtDeclaration(include, | 834 newDecls.add(new IncludeMixinAtDeclaration(include, | 
| 833 include.span)); | 835 include.span)); | 
| 834 } else { | 836 } else { | 
| 835 _warning("Error mixing of top-level vs declarations mixins", | 837 _warning("Error mixing of top-level vs declarations mixins", | 
| 836 _makeSpan(include)); | 838 _makeSpan(include.span)); | 
| 837 } | 839 } | 
| 838 }); | 840 }); | 
| 839 declGroup.declarations.insertAll(0, newDecls); | 841 declGroup.declarations.insertAll(0, newDecls); | 
| 840 productions = []; | 842 productions = []; | 
| 841 } else { | 843 } else { | 
| 842 // Declarations are just @includes make it a list of productions | 844 // Declarations are just @includes make it a list of productions | 
| 843 // not a declaration group (anything else is a ruleset). Make it a | 845 // not a declaration group (anything else is a ruleset). Make it a | 
| 844 // list of productions, not a declaration group. | 846 // list of productions, not a declaration group. | 
| 845 for (var decl in declGroup.declarations) { | 847 for (var decl in declGroup.declarations) { | 
| 846 productions.add(decl is IncludeMixinAtDeclaration ? | 848 productions.add(decl is IncludeMixinAtDeclaration ? | 
| (...skipping 28 matching lines...) Expand all Loading... | |
| 875 _eat(TokenKind.RBRACE); | 877 _eat(TokenKind.RBRACE); | 
| 876 | 878 | 
| 877 return mixinDirective; | 879 return mixinDirective; | 
| 878 } | 880 } | 
| 879 | 881 | 
| 880 /** | 882 /** | 
| 881 * Returns a VarDefinitionDirective or VarDefinition if a varaible otherwise | 883 * Returns a VarDefinitionDirective or VarDefinition if a varaible otherwise | 
| 882 * return the token id of a directive or -1 if neither. | 884 * return the token id of a directive or -1 if neither. | 
| 883 */ | 885 */ | 
| 884 processVariableOrDirective({bool mixinParameter: false}) { | 886 processVariableOrDirective({bool mixinParameter: false}) { | 
| 885 int start = _peekToken.start; | 887 var start = _peekToken.span; | 
| 886 | 888 | 
| 887 var tokId = _peek(); | 889 var tokId = _peek(); | 
| 888 // Handle case for @ directive (where there's a whitespace between the @ | 890 // Handle case for @ directive (where there's a whitespace between the @ | 
| 889 // sign and the directive name. Technically, it's not valid grammar but | 891 // sign and the directive name. Technically, it's not valid grammar but | 
| 890 // a number of CSS tests test for whitespace between @ and name. | 892 // a number of CSS tests test for whitespace between @ and name. | 
| 891 if (tokId == TokenKind.AT) { | 893 if (tokId == TokenKind.AT) { | 
| 892 Token tok = _next(); | 894 Token tok = _next(); | 
| 893 tokId = _peek(); | 895 tokId = _peek(); | 
| 894 if (_peekIdentifier()) { | 896 if (_peekIdentifier()) { | 
| 895 // Is it a directive? | 897 // Is it a directive? | 
| (...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1039 return null; | 1041 return null; | 
| 1040 } else { | 1042 } else { | 
| 1041 // Remember any messages from look ahead. | 1043 // Remember any messages from look ahead. | 
| 1042 oldMessages.mergeMessages(messages); | 1044 oldMessages.mergeMessages(messages); | 
| 1043 messages = oldMessages; | 1045 messages = oldMessages; | 
| 1044 return selGroup; | 1046 return selGroup; | 
| 1045 } | 1047 } | 
| 1046 } | 1048 } | 
| 1047 | 1049 | 
| 1048 DeclarationGroup processDeclarations({bool checkBrace: true}) { | 1050 DeclarationGroup processDeclarations({bool checkBrace: true}) { | 
| 1049 int start = _peekToken.start; | 1051 var start = _peekToken.span; | 
| 1050 | 1052 | 
| 1051 if (checkBrace) _eat(TokenKind.LBRACE); | 1053 if (checkBrace) _eat(TokenKind.LBRACE); | 
| 1052 | 1054 | 
| 1053 List decls = []; | 1055 List decls = []; | 
| 1054 List dartStyles = []; // List of latest styles exposed to Dart. | 1056 List dartStyles = []; // List of latest styles exposed to Dart. | 
| 1055 | 1057 | 
| 1056 do { | 1058 do { | 
| 1057 var selectorGroup = _nestedSelector(); | 1059 var selectorGroup = _nestedSelector(); | 
| 1058 while (selectorGroup != null) { | 1060 while (selectorGroup != null) { | 
| 1059 // Nested selector so process as a ruleset. | 1061 // Nested selector so process as a ruleset. | 
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1097 } | 1099 } | 
| 1098 } | 1100 } | 
| 1099 } | 1101 } | 
| 1100 | 1102 | 
| 1101 return new DeclarationGroup(decls, _makeSpan(start)); | 1103 return new DeclarationGroup(decls, _makeSpan(start)); | 
| 1102 } | 1104 } | 
| 1103 | 1105 | 
| 1104 List<DeclarationGroup> processMarginsDeclarations() { | 1106 List<DeclarationGroup> processMarginsDeclarations() { | 
| 1105 List groups = []; | 1107 List groups = []; | 
| 1106 | 1108 | 
| 1107 int start = _peekToken.start; | 1109 var start = _peekToken.span; | 
| 1108 | 1110 | 
| 1109 _eat(TokenKind.LBRACE); | 1111 _eat(TokenKind.LBRACE); | 
| 1110 | 1112 | 
| 1111 List<Declaration> decls = []; | 1113 List<Declaration> decls = []; | 
| 1112 List dartStyles = []; // List of latest styles exposed to Dart. | 1114 List dartStyles = []; // List of latest styles exposed to Dart. | 
| 1113 | 1115 | 
| 1114 do { | 1116 do { | 
| 1115 switch (_peek()) { | 1117 switch (_peek()) { | 
| 1116 case TokenKind.MARGIN_DIRECTIVE_TOPLEFTCORNER: | 1118 case TokenKind.MARGIN_DIRECTIVE_TOPLEFTCORNER: | 
| 1117 case TokenKind.MARGIN_DIRECTIVE_TOPLEFT: | 1119 case TokenKind.MARGIN_DIRECTIVE_TOPLEFT: | 
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1182 | 1184 | 
| 1183 if (decls.length > 0) { | 1185 if (decls.length > 0) { | 
| 1184 groups.add(new DeclarationGroup(decls, _makeSpan(start))); | 1186 groups.add(new DeclarationGroup(decls, _makeSpan(start))); | 
| 1185 } | 1187 } | 
| 1186 | 1188 | 
| 1187 return groups; | 1189 return groups; | 
| 1188 } | 1190 } | 
| 1189 | 1191 | 
| 1190 SelectorGroup processSelectorGroup() { | 1192 SelectorGroup processSelectorGroup() { | 
| 1191 List<Selector> selectors = []; | 1193 List<Selector> selectors = []; | 
| 1192 int start = _peekToken.start; | 1194 var start = _peekToken.span; | 
| 1193 | 1195 | 
| 1194 do { | 1196 do { | 
| 1195 Selector selector = processSelector(); | 1197 Selector selector = processSelector(); | 
| 1196 if (selector != null) { | 1198 if (selector != null) { | 
| 1197 selectors.add(selector); | 1199 selectors.add(selector); | 
| 1198 } | 1200 } | 
| 1199 } while (_maybeEat(TokenKind.COMMA)); | 1201 } while (_maybeEat(TokenKind.COMMA)); | 
| 1200 | 1202 | 
| 1201 if (selectors.length > 0) { | 1203 if (selectors.length > 0) { | 
| 1202 return new SelectorGroup(selectors, _makeSpan(start)); | 1204 return new SelectorGroup(selectors, _makeSpan(start)); | 
| 1203 } | 1205 } | 
| 1204 } | 1206 } | 
| 1205 | 1207 | 
| 1206 /** | 1208 /** | 
| 1207 * Return list of selectors | 1209 * Return list of selectors | 
| 1208 */ | 1210 */ | 
| 1209 Selector processSelector() { | 1211 Selector processSelector() { | 
| 1210 var simpleSequences = <SimpleSelectorSequence>[]; | 1212 var simpleSequences = <SimpleSelectorSequence>[]; | 
| 1211 var start = _peekToken.start; | 1213 var start = _peekToken.span; | 
| 1212 while (true) { | 1214 while (true) { | 
| 1213 // First item is never descendant make sure it's COMBINATOR_NONE. | 1215 // First item is never descendant make sure it's COMBINATOR_NONE. | 
| 1214 var selectorItem = simpleSelectorSequence(simpleSequences.length == 0); | 1216 var selectorItem = simpleSelectorSequence(simpleSequences.length == 0); | 
| 1215 if (selectorItem != null) { | 1217 if (selectorItem != null) { | 
| 1216 simpleSequences.add(selectorItem); | 1218 simpleSequences.add(selectorItem); | 
| 1217 } else { | 1219 } else { | 
| 1218 break; | 1220 break; | 
| 1219 } | 1221 } | 
| 1220 } | 1222 } | 
| 1221 | 1223 | 
| 1222 if (simpleSequences.length > 0) { | 1224 if (simpleSequences.length > 0) { | 
| 1223 return new Selector(simpleSequences, _makeSpan(start)); | 1225 return new Selector(simpleSequences, _makeSpan(start)); | 
| 1224 } | 1226 } | 
| 1225 } | 1227 } | 
| 1226 | 1228 | 
| 1227 simpleSelectorSequence(bool forceCombinatorNone) { | 1229 simpleSelectorSequence(bool forceCombinatorNone) { | 
| 1228 var start = _peekToken.start; | 1230 var start = _peekToken.span; | 
| 1229 var combinatorType = TokenKind.COMBINATOR_NONE; | 1231 var combinatorType = TokenKind.COMBINATOR_NONE; | 
| 1230 var thisOperator = false; | 1232 var thisOperator = false; | 
| 1231 | 1233 | 
| 1232 switch (_peek()) { | 1234 switch (_peek()) { | 
| 1233 case TokenKind.PLUS: | 1235 case TokenKind.PLUS: | 
| 1234 _eat(TokenKind.PLUS); | 1236 _eat(TokenKind.PLUS); | 
| 1235 combinatorType = TokenKind.COMBINATOR_PLUS; | 1237 combinatorType = TokenKind.COMBINATOR_PLUS; | 
| 1236 break; | 1238 break; | 
| 1237 case TokenKind.GREATER: | 1239 case TokenKind.GREATER: | 
| 1238 _eat(TokenKind.GREATER); | 1240 _eat(TokenKind.GREATER); | 
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1294 * class | 1296 * class | 
| 1295 * : '.' IDENT | 1297 * : '.' IDENT | 
| 1296 */ | 1298 */ | 
| 1297 simpleSelector() { | 1299 simpleSelector() { | 
| 1298 // TODO(terry): Natalie makes a good point parsing of namespace and element | 1300 // TODO(terry): Natalie makes a good point parsing of namespace and element | 
| 1299 // are essentially the same (asterisk or identifier) other | 1301 // are essentially the same (asterisk or identifier) other | 
| 1300 // than the error message for element. Should consolidate the | 1302 // than the error message for element. Should consolidate the | 
| 1301 // code. | 1303 // code. | 
| 1302 // TODO(terry): Need to handle attribute namespace too. | 1304 // TODO(terry): Need to handle attribute namespace too. | 
| 1303 var first; | 1305 var first; | 
| 1304 int start = _peekToken.start; | 1306 var start = _peekToken.span; | 
| 1305 switch (_peek()) { | 1307 switch (_peek()) { | 
| 1306 case TokenKind.ASTERISK: | 1308 case TokenKind.ASTERISK: | 
| 1307 // Mark as universal namespace. | 1309 // Mark as universal namespace. | 
| 1308 var tok = _next(); | 1310 var tok = _next(); | 
| 1309 first = new Wildcard(_makeSpan(tok.start)); | 1311 first = new Wildcard(_makeSpan(tok.span)); | 
| 1310 break; | 1312 break; | 
| 1311 case TokenKind.IDENTIFIER: | 1313 case TokenKind.IDENTIFIER: | 
| 1312 first = identifier(); | 1314 first = identifier(); | 
| 1313 break; | 1315 break; | 
| 1314 default: | 1316 default: | 
| 1315 // Expecting simple selector. | 1317 // Expecting simple selector. | 
| 1316 // TODO(terry): Could be a synthesized token like value, etc. | 1318 // TODO(terry): Could be a synthesized token like value, etc. | 
| 1317 if (TokenKind.isKindIdentifier(_peek())) { | 1319 if (TokenKind.isKindIdentifier(_peek())) { | 
| 1318 first = identifier(); | 1320 first = identifier(); | 
| 1319 } else if (_peekKind(TokenKind.SEMICOLON)) { | 1321 } else if (_peekKind(TokenKind.SEMICOLON)) { | 
| 1320 // Can't be a selector if we found a semi-colon. | 1322 // Can't be a selector if we found a semi-colon. | 
| 1321 return null; | 1323 return null; | 
| 1322 } | 1324 } | 
| 1323 break; | 1325 break; | 
| 1324 } | 1326 } | 
| 1325 | 1327 | 
| 1326 if (_maybeEat(TokenKind.NAMESPACE)) { | 1328 if (_maybeEat(TokenKind.NAMESPACE)) { | 
| 1327 var element; | 1329 var element; | 
| 1328 switch (_peek()) { | 1330 switch (_peek()) { | 
| 1329 case TokenKind.ASTERISK: | 1331 case TokenKind.ASTERISK: | 
| 1330 // Mark as universal element | 1332 // Mark as universal element | 
| 1331 var tok = _next(); | 1333 var tok = _next(); | 
| 1332 element = new Wildcard(_makeSpan(tok.start)); | 1334 element = new Wildcard(_makeSpan(tok.span)); | 
| 1333 break; | 1335 break; | 
| 1334 case TokenKind.IDENTIFIER: | 1336 case TokenKind.IDENTIFIER: | 
| 1335 element = identifier(); | 1337 element = identifier(); | 
| 1336 break; | 1338 break; | 
| 1337 default: | 1339 default: | 
| 1338 _error('expected element name or universal(*), but found $_peekToken', | 1340 _error('expected element name or universal(*), but found $_peekToken', | 
| 1339 _peekToken.span); | 1341 _peekToken.span); | 
| 1340 break; | 1342 break; | 
| 1341 } | 1343 } | 
| 1342 | 1344 | 
| (...skipping 16 matching lines...) Expand all Loading... | |
| 1359 } | 1361 } | 
| 1360 | 1362 | 
| 1361 return false; | 1363 return false; | 
| 1362 } | 1364 } | 
| 1363 | 1365 | 
| 1364 /** | 1366 /** | 
| 1365 * type_selector | universal | HASH | class | attrib | pseudo | 1367 * type_selector | universal | HASH | class | attrib | pseudo | 
| 1366 */ | 1368 */ | 
| 1367 simpleSelectorTail() { | 1369 simpleSelectorTail() { | 
| 1368 // Check for HASH | class | attrib | pseudo | negation | 1370 // Check for HASH | class | attrib | pseudo | negation | 
| 1369 var start = _peekToken.start; | 1371 var start = _peekToken.span; | 
| 1370 switch (_peek()) { | 1372 switch (_peek()) { | 
| 1371 case TokenKind.HASH: | 1373 case TokenKind.HASH: | 
| 1372 _eat(TokenKind.HASH); | 1374 _eat(TokenKind.HASH); | 
| 1373 | 1375 | 
| 1374 var hasWhiteSpace = false; | 1376 var hasWhiteSpace = false; | 
| 1375 if (_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { | 1377 if (_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { | 
| 1376 _warning("Not a valid ID selector expected #id", _makeSpan(start)); | 1378 _warning("Not a valid ID selector expected #id", _makeSpan(start)); | 
| 1377 hasWhiteSpace = true; | 1379 hasWhiteSpace = true; | 
| 1378 } | 1380 } | 
| 1379 if (_peekIdentifier()) { | 1381 if (_peekIdentifier()) { | 
| (...skipping 26 matching lines...) Expand all Loading... | |
| 1406 case TokenKind.LBRACK: | 1408 case TokenKind.LBRACK: | 
| 1407 return processAttribute(); | 1409 return processAttribute(); | 
| 1408 case TokenKind.DOUBLE: | 1410 case TokenKind.DOUBLE: | 
| 1409 _error('name must start with a alpha character, but found a number', | 1411 _error('name must start with a alpha character, but found a number', | 
| 1410 _peekToken.span); | 1412 _peekToken.span); | 
| 1411 _next(); | 1413 _next(); | 
| 1412 break; | 1414 break; | 
| 1413 } | 1415 } | 
| 1414 } | 1416 } | 
| 1415 | 1417 | 
| 1416 processPseudoSelector(int start) { | 1418 processPseudoSelector(FileSpan start) { | 
| 1417 // :pseudo-class ::pseudo-element | 1419 // :pseudo-class ::pseudo-element | 
| 1418 // TODO(terry): '::' should be token. | 1420 // TODO(terry): '::' should be token. | 
| 1419 _eat(TokenKind.COLON); | 1421 _eat(TokenKind.COLON); | 
| 1420 var pseudoElement = _maybeEat(TokenKind.COLON); | 1422 var pseudoElement = _maybeEat(TokenKind.COLON); | 
| 1421 | 1423 | 
| 1422 // TODO(terry): If no identifier specified consider optimizing out the | 1424 // TODO(terry): If no identifier specified consider optimizing out the | 
| 1423 // : or :: and making this a normal selector. For now, | 1425 // : or :: and making this a normal selector. For now, | 
| 1424 // create an empty pseudoName. | 1426 // create an empty pseudoName. | 
| 1425 var pseudoName; | 1427 var pseudoName; | 
| 1426 if (_peekIdentifier()) { | 1428 if (_peekIdentifier()) { | 
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1485 * In CSS3, the expressions are identifiers, strings, or of the form "an+b". | 1487 * In CSS3, the expressions are identifiers, strings, or of the form "an+b". | 
| 1486 * | 1488 * | 
| 1487 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ | 1489 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ | 
| 1488 * | 1490 * | 
| 1489 * num [0-9]+|[0-9]*\.[0-9]+ | 1491 * num [0-9]+|[0-9]*\.[0-9]+ | 
| 1490 * PLUS '+' | 1492 * PLUS '+' | 
| 1491 * DIMENSION {num}{ident} | 1493 * DIMENSION {num}{ident} | 
| 1492 * NUMBER {num} | 1494 * NUMBER {num} | 
| 1493 */ | 1495 */ | 
| 1494 processSelectorExpression() { | 1496 processSelectorExpression() { | 
| 1495 var start = _peekToken.start; | 1497 var start = _peekToken.span; | 
| 1496 | 1498 | 
| 1497 var expressions = []; | 1499 var expressions = []; | 
| 1498 | 1500 | 
| 1499 Token termToken; | 1501 Token termToken; | 
| 1500 var value; | 1502 var value; | 
| 1501 | 1503 | 
| 1502 var keepParsing = true; | 1504 var keepParsing = true; | 
| 1503 while (keepParsing) { | 1505 while (keepParsing) { | 
| 1504 switch (_peek()) { | 1506 switch (_peek()) { | 
| 1505 case TokenKind.PLUS: | 1507 case TokenKind.PLUS: | 
| 1506 start = _peekToken.start; | 1508 start = _peekToken.span; | 
| 1507 termToken = _next(); | 1509 termToken = _next(); | 
| 1508 expressions.add(new OperatorPlus(_makeSpan(start))); | 1510 expressions.add(new OperatorPlus(_makeSpan(start))); | 
| 1509 break; | 1511 break; | 
| 1510 case TokenKind.MINUS: | 1512 case TokenKind.MINUS: | 
| 1511 start = _peekToken.start; | 1513 start = _peekToken.span; | 
| 1512 termToken = _next(); | 1514 termToken = _next(); | 
| 1513 expressions.add(new OperatorMinus(_makeSpan(start))); | 1515 expressions.add(new OperatorMinus(_makeSpan(start))); | 
| 1514 break; | 1516 break; | 
| 1515 case TokenKind.INTEGER: | 1517 case TokenKind.INTEGER: | 
| 1516 termToken = _next(); | 1518 termToken = _next(); | 
| 1517 value = int.parse(termToken.text); | 1519 value = int.parse(termToken.text); | 
| 1518 break; | 1520 break; | 
| 1519 case TokenKind.DOUBLE: | 1521 case TokenKind.DOUBLE: | 
| 1520 termToken = _next(); | 1522 termToken = _next(); | 
| 1521 value = double.parse(termToken.text); | 1523 value = double.parse(termToken.text); | 
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1566 // DASHMATCH: '|=' | 1568 // DASHMATCH: '|=' | 
| 1567 // | 1569 // | 
| 1568 // PREFIXMATCH: '^=' | 1570 // PREFIXMATCH: '^=' | 
| 1569 // | 1571 // | 
| 1570 // SUFFIXMATCH: '$=' | 1572 // SUFFIXMATCH: '$=' | 
| 1571 // | 1573 // | 
| 1572 // SUBSTRMATCH: '*=' | 1574 // SUBSTRMATCH: '*=' | 
| 1573 // | 1575 // | 
| 1574 // | 1576 // | 
| 1575 AttributeSelector processAttribute() { | 1577 AttributeSelector processAttribute() { | 
| 1576 var start = _peekToken.start; | 1578 var start = _peekToken.span; | 
| 1577 | 1579 | 
| 1578 if (_maybeEat(TokenKind.LBRACK)) { | 1580 if (_maybeEat(TokenKind.LBRACK)) { | 
| 1579 var attrName = identifier(); | 1581 var attrName = identifier(); | 
| 1580 | 1582 | 
| 1581 int op; | 1583 int op; | 
| 1582 switch (_peek()) { | 1584 switch (_peek()) { | 
| 1583 case TokenKind.EQUALS: | 1585 case TokenKind.EQUALS: | 
| 1584 case TokenKind.INCLUDES: // ~= | 1586 case TokenKind.INCLUDES: // ~= | 
| 1585 case TokenKind.DASH_MATCH: // |= | 1587 case TokenKind.DASH_MATCH: // |= | 
| 1586 case TokenKind.PREFIX_MATCH: // ^= | 1588 case TokenKind.PREFIX_MATCH: // ^= | 
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1622 // expr: (see processExpr) | 1624 // expr: (see processExpr) | 
| 1623 // | 1625 // | 
| 1624 // Here are the ugly IE hacks we need to support: | 1626 // Here are the ugly IE hacks we need to support: | 
| 1625 // property: expr prio? \9; - IE8 and below property, /9 before semi-colon | 1627 // property: expr prio? \9; - IE8 and below property, /9 before semi-colon | 
| 1626 // *IDENT - IE7 or below | 1628 // *IDENT - IE7 or below | 
| 1627 // _IDENT - IE6 property (automatically a valid ident) | 1629 // _IDENT - IE6 property (automatically a valid ident) | 
| 1628 // | 1630 // | 
| 1629 Declaration processDeclaration(List dartStyles) { | 1631 Declaration processDeclaration(List dartStyles) { | 
| 1630 Declaration decl; | 1632 Declaration decl; | 
| 1631 | 1633 | 
| 1632 int start = _peekToken.start; | 1634 var start = _peekToken.span; | 
| 1633 | 1635 | 
| 1634 // IE7 hack of * before property name if so the property is IE7 or below. | 1636 // IE7 hack of * before property name if so the property is IE7 or below. | 
| 1635 var ie7 = _peekKind(TokenKind.ASTERISK); | 1637 var ie7 = _peekKind(TokenKind.ASTERISK); | 
| 1636 if (ie7) { | 1638 if (ie7) { | 
| 1637 _next(); | 1639 _next(); | 
| 1638 } | 1640 } | 
| 1639 | 1641 | 
| 1640 // IDENT ':' expr '!important'? | 1642 // IDENT ':' expr '!important'? | 
| 1641 if (TokenKind.isIdentifier(_peekToken.kind)) { | 1643 if (TokenKind.isIdentifier(_peekToken.kind)) { | 
| 1642 var propertyIdent = identifier(); | 1644 var propertyIdent = identifier(); | 
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1674 | 1676 | 
| 1675 _next(); | 1677 _next(); | 
| 1676 var span = _makeSpan(start); | 1678 var span = _makeSpan(start); | 
| 1677 var selector = simpleSelector(); | 1679 var selector = simpleSelector(); | 
| 1678 if (selector == null) { | 1680 if (selector == null) { | 
| 1679 _warning("@extends expecting simple selector name", span); | 1681 _warning("@extends expecting simple selector name", span); | 
| 1680 } else { | 1682 } else { | 
| 1681 simpleSequences.add(selector); | 1683 simpleSequences.add(selector); | 
| 1682 } | 1684 } | 
| 1683 if (_peekKind(TokenKind.COLON)) { | 1685 if (_peekKind(TokenKind.COLON)) { | 
| 1684 var pseudoSelector = processPseudoSelector(_peekToken.start); | 1686 var pseudoSelector = processPseudoSelector(_peekToken.span); | 
| 1685 if (pseudoSelector is PseudoElementSelector || | 1687 if (pseudoSelector is PseudoElementSelector || | 
| 1686 pseudoSelector is PseudoClassSelector) { | 1688 pseudoSelector is PseudoClassSelector) { | 
| 1687 simpleSequences.add(pseudoSelector); | 1689 simpleSequences.add(pseudoSelector); | 
| 1688 } else { | 1690 } else { | 
| 1689 _warning("not a valid selector", span); | 1691 _warning("not a valid selector", span); | 
| 1690 } | 1692 } | 
| 1691 } | 1693 } | 
| 1692 decl = new ExtendDeclaration(simpleSequences, span); | 1694 decl = new ExtendDeclaration(simpleSequences, span); | 
| 1693 } | 1695 } | 
| 1694 | 1696 | 
| (...skipping 331 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2026 } | 2028 } | 
| 2027 | 2029 | 
| 2028 // Expression grammar: | 2030 // Expression grammar: | 
| 2029 // | 2031 // | 
| 2030 // expression: term [ operator? term]* | 2032 // expression: term [ operator? term]* | 
| 2031 // | 2033 // | 
| 2032 // operator: '/' | ',' | 2034 // operator: '/' | ',' | 
| 2033 // term: (see processTerm) | 2035 // term: (see processTerm) | 
| 2034 // | 2036 // | 
| 2035 Expressions processExpr([bool ieFilter = false]) { | 2037 Expressions processExpr([bool ieFilter = false]) { | 
| 2036 var start = _peekToken.start; | 2038 var start = _peekToken.span; | 
| 2037 var expressions = new Expressions(_makeSpan(start)); | 2039 var expressions = new Expressions(_makeSpan(start)); | 
| 2038 | 2040 | 
| 2039 var keepGoing = true; | 2041 var keepGoing = true; | 
| 2040 var expr; | 2042 var expr; | 
| 2041 while (keepGoing && (expr = processTerm(ieFilter)) != null) { | 2043 while (keepGoing && (expr = processTerm(ieFilter)) != null) { | 
| 2042 var op; | 2044 var op; | 
| 2043 | 2045 | 
| 2044 var opStart = _peekToken.start; | 2046 var opStart = _peekToken.span; | 
| 2045 | 2047 | 
| 2046 switch (_peek()) { | 2048 switch (_peek()) { | 
| 2047 case TokenKind.SLASH: | 2049 case TokenKind.SLASH: | 
| 2048 op = new OperatorSlash(_makeSpan(opStart)); | 2050 op = new OperatorSlash(_makeSpan(opStart)); | 
| 2049 break; | 2051 break; | 
| 2050 case TokenKind.COMMA: | 2052 case TokenKind.COMMA: | 
| 2051 op = new OperatorComma(_makeSpan(opStart)); | 2053 op = new OperatorComma(_makeSpan(opStart)); | 
| 2052 break; | 2054 break; | 
| 2053 case TokenKind.BACKSLASH: | 2055 case TokenKind.BACKSLASH: | 
| 2054 // Backslash outside of string; detected IE8 or older signaled by \9 at | 2056 // Backslash outside of string; detected IE8 or older signaled by \9 at | 
| 2055 // end of an expression. | 2057 // end of an expression. | 
| 2056 var ie8Start = _peekToken.start; | 2058 var ie8Start = _peekToken.span; | 
| 2057 | 2059 | 
| 2058 _next(); | 2060 _next(); | 
| 2059 if (_peekKind(TokenKind.INTEGER)) { | 2061 if (_peekKind(TokenKind.INTEGER)) { | 
| 2060 var numToken = _next(); | 2062 var numToken = _next(); | 
| 2061 var value = int.parse(numToken.text); | 2063 var value = int.parse(numToken.text); | 
| 2062 if (value == 9) { | 2064 if (value == 9) { | 
| 2063 op = new IE8Term(_makeSpan(ie8Start)); | 2065 op = new IE8Term(_makeSpan(ie8Start)); | 
| 2064 } else if (isChecked) { | 2066 } else if (isChecked) { | 
| 2065 _warning("\$value is not valid in an expression", _makeSpan(start)); | 2067 _warning("\$value is not valid in an expression", _makeSpan(start)); | 
| 2066 } | 2068 } | 
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2110 // PERCENTAGE: {num}% | 2112 // PERCENTAGE: {num}% | 
| 2111 // LENGTH: {num}['px' | 'cm' | 'mm' | 'in' | 'pt' | 'pc'] | 2113 // LENGTH: {num}['px' | 'cm' | 'mm' | 'in' | 'pt' | 'pc'] | 
| 2112 // EMS: {num}'em' | 2114 // EMS: {num}'em' | 
| 2113 // EXS: {num}'ex' | 2115 // EXS: {num}'ex' | 
| 2114 // ANGLE: {num}['deg' | 'rad' | 'grad'] | 2116 // ANGLE: {num}['deg' | 'rad' | 'grad'] | 
| 2115 // TIME: {num}['ms' | 's'] | 2117 // TIME: {num}['ms' | 's'] | 
| 2116 // FREQ: {num}['hz' | 'khz'] | 2118 // FREQ: {num}['hz' | 'khz'] | 
| 2117 // function: IDENT '(' expr ')' | 2119 // function: IDENT '(' expr ')' | 
| 2118 // | 2120 // | 
| 2119 processTerm([bool ieFilter = false]) { | 2121 processTerm([bool ieFilter = false]) { | 
| 2120 var start = _peekToken.start; | 2122 var start = _peekToken.span; | 
| 2121 Token t; // token for term's value | 2123 Token t; // token for term's value | 
| 2122 var value; // value of term (numeric values) | 2124 var value; // value of term (numeric values) | 
| 2123 | 2125 | 
| 2124 var unary = ""; | 2126 var unary = ""; | 
| 2125 switch (_peek()) { | 2127 switch (_peek()) { | 
| 2126 case TokenKind.HASH: | 2128 case TokenKind.HASH: | 
| 2127 this._eat(TokenKind.HASH); | 2129 this._eat(TokenKind.HASH); | 
| 2128 if (!_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { | 2130 if (!_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { | 
| 2129 String hexText; | 2131 String hexText; | 
| 2130 if (_peekKind(TokenKind.INTEGER)) { | 2132 if (_peekKind(TokenKind.INTEGER)) { | 
| (...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2357 ? new LiteralTerm(value, value.name, span) | 2359 ? new LiteralTerm(value, value.name, span) | 
| 2358 : new NumberTerm(value, t.text, span); | 2360 : new NumberTerm(value, t.text, span); | 
| 2359 } | 2361 } | 
| 2360 break; | 2362 break; | 
| 2361 } | 2363 } | 
| 2362 | 2364 | 
| 2363 return term; | 2365 return term; | 
| 2364 } | 2366 } | 
| 2365 | 2367 | 
| 2366 String processQuotedString([bool urlString = false]) { | 2368 String processQuotedString([bool urlString = false]) { | 
| 2367 var start = _peekToken.start; | 2369 var start = _peekToken.span; | 
| 2368 | 2370 | 
| 2369 // URI term sucks up everything inside of quotes(' or ") or between parens | 2371 // URI term sucks up everything inside of quotes(' or ") or between parens | 
| 2370 var stopToken = urlString ? TokenKind.RPAREN : -1; | 2372 var stopToken = urlString ? TokenKind.RPAREN : -1; | 
| 2371 | 2373 | 
| 2372 // Note: disable skipping whitespace tokens inside a string. | 2374 // Note: disable skipping whitespace tokens inside a string. | 
| 2373 // TODO(jmesserly): the layering here feels wrong. | 2375 // TODO(jmesserly): the layering here feels wrong. | 
| 2374 var skipWhitespace = tokenizer._skipWhitespace; | 2376 var skipWhitespace = tokenizer._skipWhitespace; | 
| 2375 tokenizer._skipWhitespace = false; | 2377 tokenizer._skipWhitespace = false; | 
| 2376 | 2378 | 
| 2377 switch (_peek()) { | 2379 switch (_peek()) { | 
| 2378 case TokenKind.SINGLE_QUOTE: | 2380 case TokenKind.SINGLE_QUOTE: | 
| 2379 stopToken = TokenKind.SINGLE_QUOTE; | 2381 stopToken = TokenKind.SINGLE_QUOTE; | 
| 2380 start = _peekToken.start + 1; // Skip the quote might have whitespace. | 2382 _next(); // Skip the SINGLE_QUOTE. | 
| 2381 _next(); // Skip the SINGLE_QUOTE. | 2383 start = _peekToken.span; | 
| 
sra1
2014/12/02 23:43:45
Will the _peekToken always have a span starting im
 | |
| 2382 break; | 2384 break; | 
| 2383 case TokenKind.DOUBLE_QUOTE: | 2385 case TokenKind.DOUBLE_QUOTE: | 
| 2384 stopToken = TokenKind.DOUBLE_QUOTE; | 2386 stopToken = TokenKind.DOUBLE_QUOTE; | 
| 2385 start = _peekToken.start + 1; // Skip the quote might have whitespace. | 2387 _next(); // Skip the DOUBLE_QUOTE. | 
| 2386 _next(); // Skip the DOUBLE_QUOTE. | 2388 start = _peekToken.span; | 
| 2387 break; | 2389 break; | 
| 2388 default: | 2390 default: | 
| 2389 if (urlString) { | 2391 if (urlString) { | 
| 2390 if (_peek() == TokenKind.LPAREN) { | 2392 if (_peek() == TokenKind.LPAREN) { | 
| 2391 _next(); // Skip the LPAREN. | 2393 _next(); // Skip the LPAREN. | 
| 2392 start = _peekToken.start; | 2394 start = _peekToken.span; | 
| 2393 } | 2395 } | 
| 2394 stopToken = TokenKind.RPAREN; | 2396 stopToken = TokenKind.RPAREN; | 
| 2395 } else { | 2397 } else { | 
| 2396 _error('unexpected string', _makeSpan(start)); | 2398 _error('unexpected string', _makeSpan(start)); | 
| 2397 } | 2399 } | 
| 2398 break; | 2400 break; | 
| 2399 } | 2401 } | 
| 2400 | 2402 | 
| 2401 // Gobble up everything until we hit our stop token. | 2403 // Gobble up everything until we hit our stop token. | 
| 2402 var runningStart = _peekToken.start; | |
| 2403 | |
| 2404 var stringValue = new StringBuffer(); | 2404 var stringValue = new StringBuffer(); | 
| 2405 while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) { | 2405 while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) { | 
| 2406 stringValue.write(_next().text); | 2406 stringValue.write(_next().text); | 
| 2407 } | 2407 } | 
| 2408 | 2408 | 
| 2409 tokenizer._skipWhitespace = skipWhitespace; | 2409 tokenizer._skipWhitespace = skipWhitespace; | 
| 2410 | 2410 | 
| 2411 // All characters between quotes is the string. | 2411 // All characters between quotes is the string. | 
| 2412 if (stopToken != TokenKind.RPAREN) { | 2412 if (stopToken != TokenKind.RPAREN) { | 
| 2413 _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE; | 2413 _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE; | 
| 2414 } | 2414 } | 
| 2415 | 2415 | 
| 2416 return stringValue.toString(); | 2416 return stringValue.toString(); | 
| 2417 } | 2417 } | 
| 2418 | 2418 | 
| 2419 // TODO(terry): Should probably understand IE's non-standard filter syntax to | 2419 // TODO(terry): Should probably understand IE's non-standard filter syntax to | 
| 2420 // fully support calc, var(), etc. | 2420 // fully support calc, var(), etc. | 
| 2421 /** | 2421 /** | 
| 2422 * IE's filter property breaks CSS value parsing. IE's format can be: | 2422 * IE's filter property breaks CSS value parsing. IE's format can be: | 
| 2423 * | 2423 * | 
| 2424 * filter: progid:DXImageTransform.MS.gradient(Type=0, Color='#9d8b83'); | 2424 * filter: progid:DXImageTransform.MS.gradient(Type=0, Color='#9d8b83'); | 
| 2425 * | 2425 * | 
| 2426 * We'll just parse everything after the 'progid:' look for the left paren | 2426 * We'll just parse everything after the 'progid:' look for the left paren | 
| 2427 * then parse to the right paren ignoring everything in between. | 2427 * then parse to the right paren ignoring everything in between. | 
| 2428 */ | 2428 */ | 
| 2429 processIEFilter(int startAfterProgidColon) { | 2429 processIEFilter(FileSpan startAfterProgidColon) { | 
| 2430 var parens = 0; | 2430 var parens = 0; | 
| 2431 | 2431 | 
| 2432 while (_peek() != TokenKind.END_OF_FILE) { | 2432 while (_peek() != TokenKind.END_OF_FILE) { | 
| 2433 switch (_peek()) { | 2433 switch (_peek()) { | 
| 2434 case TokenKind.LPAREN: | 2434 case TokenKind.LPAREN: | 
| 2435 _eat(TokenKind.LPAREN); | 2435 _eat(TokenKind.LPAREN); | 
| 2436 parens++; | 2436 parens++; | 
| 2437 break; | 2437 break; | 
| 2438 case TokenKind.RPAREN: | 2438 case TokenKind.RPAREN: | 
| 2439 _eat(TokenKind.RPAREN); | 2439 _eat(TokenKind.RPAREN); | 
| 2440 if (--parens == 0) { | 2440 if (--parens == 0) { | 
| 2441 var tok = tokenizer.makeIEFilter(startAfterProgidColon, | 2441 var tok = tokenizer.makeIEFilter(startAfterProgidColon.start.offset, | 
| 2442 _peekToken.start); | 2442 _peekToken.start); | 
| 2443 return new LiteralTerm(tok.text, tok.text, tok.span); | 2443 return new LiteralTerm(tok.text, tok.text, tok.span); | 
| 2444 } | 2444 } | 
| 2445 break; | 2445 break; | 
| 2446 default: | 2446 default: | 
| 2447 _eat(_peek()); | 2447 _eat(_peek()); | 
| 2448 } | 2448 } | 
| 2449 } | 2449 } | 
| 2450 } | 2450 } | 
| 2451 | 2451 | 
| 2452 // Function grammar: | 2452 // Function grammar: | 
| 2453 // | 2453 // | 
| 2454 // function: IDENT '(' expr ')' | 2454 // function: IDENT '(' expr ')' | 
| 2455 // | 2455 // | 
| 2456 processFunction(Identifier func) { | 2456 processFunction(Identifier func) { | 
| 2457 var start = _peekToken.start; | 2457 var start = _peekToken.span; | 
| 2458 | 2458 | 
| 2459 var name = func.name; | 2459 var name = func.name; | 
| 2460 | 2460 | 
| 2461 switch (name) { | 2461 switch (name) { | 
| 2462 case 'url': | 2462 case 'url': | 
| 2463 // URI term sucks up everything inside of quotes(' or ") or between parens | 2463 // URI term sucks up everything inside of quotes(' or ") or between parens | 
| 2464 var urlParam = processQuotedString(true); | 2464 var urlParam = processQuotedString(true); | 
| 2465 | 2465 | 
| 2466 // TODO(terry): Better error messge and checking for mismatched quotes. | 2466 // TODO(terry): Better error messge and checking for mismatched quotes. | 
| 2467 if (_peek() == TokenKind.END_OF_FILE) { | 2467 if (_peek() == TokenKind.END_OF_FILE) { | 
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2512 } | 2512 } | 
| 2513 | 2513 | 
| 2514 Identifier identifier() { | 2514 Identifier identifier() { | 
| 2515 var tok = _next(); | 2515 var tok = _next(); | 
| 2516 | 2516 | 
| 2517 if (!TokenKind.isIdentifier(tok.kind) && | 2517 if (!TokenKind.isIdentifier(tok.kind) && | 
| 2518 !TokenKind.isKindIdentifier(tok.kind)) { | 2518 !TokenKind.isKindIdentifier(tok.kind)) { | 
| 2519 if (isChecked) { | 2519 if (isChecked) { | 
| 2520 _warning('expected identifier, but found $tok', tok.span); | 2520 _warning('expected identifier, but found $tok', tok.span); | 
| 2521 } | 2521 } | 
| 2522 return new Identifier("", _makeSpan(tok.start)); | 2522 return new Identifier("", _makeSpan(tok.span)); | 
| 2523 } | 2523 } | 
| 2524 | 2524 | 
| 2525 return new Identifier(tok.text, _makeSpan(tok.start)); | 2525 return new Identifier(tok.text, _makeSpan(tok.span)); | 
| 2526 } | 2526 } | 
| 2527 | 2527 | 
| 2528 // TODO(terry): Move this to base <= 36 and into shared code. | 2528 // TODO(terry): Move this to base <= 36 and into shared code. | 
| 2529 static int _hexDigit(int c) { | 2529 static int _hexDigit(int c) { | 
| 2530 if (c >= 48/*0*/ && c <= 57/*9*/) { | 2530 if (c >= 48/*0*/ && c <= 57/*9*/) { | 
| 2531 return c - 48; | 2531 return c - 48; | 
| 2532 } else if (c >= 97/*a*/ && c <= 102/*f*/) { | 2532 } else if (c >= 97/*a*/ && c <= 102/*f*/) { | 
| 2533 return c - 87; | 2533 return c - 87; | 
| 2534 } else if (c >= 65/*A*/ && c <= 70/*F*/) { | 2534 } else if (c >= 65/*A*/ && c <= 70/*F*/) { | 
| 2535 return c - 55; | 2535 return c - 55; | 
| (...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2695 | 2695 | 
| 2696 if (replace != null && result == null) { | 2696 if (replace != null && result == null) { | 
| 2697 result = new StringBuffer(text.substring(0, i)); | 2697 result = new StringBuffer(text.substring(0, i)); | 
| 2698 } | 2698 } | 
| 2699 | 2699 | 
| 2700 if (result != null) result.write(replace != null ? replace : text[i]); | 2700 if (result != null) result.write(replace != null ? replace : text[i]); | 
| 2701 } | 2701 } | 
| 2702 | 2702 | 
| 2703 return result == null ? text : result.toString(); | 2703 return result == null ? text : result.toString(); | 
| 2704 } | 2704 } | 
| OLD | NEW |