| 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 |
| 11 import "visitor.dart"; | 11 import "visitor.dart"; |
| 12 import 'src/messages.dart'; | 12 import 'src/messages.dart'; |
| 13 import 'src/options.dart'; | 13 import 'src/options.dart'; |
| 14 | 14 |
| 15 part 'src/analyzer.dart'; | 15 part 'src/analyzer.dart'; |
| 16 part 'src/polyfill.dart'; | 16 part 'src/polyfill.dart'; |
| 17 part 'src/property.dart'; | 17 part 'src/property.dart'; |
| 18 part 'src/token.dart'; | 18 part 'src/token.dart'; |
| 19 part 'src/tokenizer_base.dart'; | 19 part 'src/tokenizer_base.dart'; |
| 20 part 'src/tokenizer.dart'; | 20 part 'src/tokenizer.dart'; |
| 21 part 'src/tokenkind.dart'; | 21 part 'src/tokenkind.dart'; |
| 22 | 22 |
| 23 | |
| 24 /** Used for parser lookup ahead (used for nested selectors Less support). */ | 23 /** Used for parser lookup ahead (used for nested selectors Less support). */ |
| 25 class ParserState extends TokenizerState { | 24 class ParserState extends TokenizerState { |
| 26 final Token peekToken; | 25 final Token peekToken; |
| 27 final Token previousToken; | 26 final Token previousToken; |
| 28 | 27 |
| 29 ParserState(this.peekToken, this.previousToken, Tokenizer tokenizer) | 28 ParserState(this.peekToken, this.previousToken, Tokenizer tokenizer) |
| 30 : super(tokenizer); | 29 : super(tokenizer); |
| 31 } | 30 } |
| 32 | 31 |
| 33 // TODO(jmesserly): this should not be global | 32 // TODO(jmesserly): this should not be global |
| 34 void _createMessages({List<Message> errors, List<String> options}) { | 33 void _createMessages({List<Message> errors, List<String> options}) { |
| 35 if (errors == null) errors = []; | 34 if (errors == null) errors = []; |
| 36 | 35 |
| 37 if (options == null) { | 36 if (options == null) { |
| 38 options = ['--no-colors', 'memory']; | 37 options = ['--no-colors', 'memory']; |
| 39 } | 38 } |
| 40 var opt = PreprocessorOptions.parse(options); | 39 var opt = PreprocessorOptions.parse(options); |
| 41 messages = new Messages(options: opt, printHandler: errors.add); | 40 messages = new Messages(options: opt, printHandler: errors.add); |
| 42 } | 41 } |
| 43 | 42 |
| 44 /** CSS checked mode enabled. */ | 43 /** CSS checked mode enabled. */ |
| 45 bool get isChecked => messages.options.checked; | 44 bool get isChecked => messages.options.checked; |
| 46 | 45 |
| 47 // TODO(terry): Remove nested name parameter. | 46 // TODO(terry): Remove nested name parameter. |
| 48 /** Parse and analyze the CSS file. */ | 47 /** Parse and analyze the CSS file. */ |
| 49 StyleSheet compile(input, {List<Message> errors, List<String> options, | 48 StyleSheet compile(input, {List<Message> errors, List<String> options, |
| 50 bool nested: true, | 49 bool nested: true, bool polyfill: false, List<StyleSheet> includes: null}) { |
| 51 bool polyfill: false, | |
| 52 List<StyleSheet> includes: null}) { | |
| 53 | |
| 54 if (includes == null) { | 50 if (includes == null) { |
| 55 includes = []; | 51 includes = []; |
| 56 } | 52 } |
| 57 | 53 |
| 58 var source = _inputAsString(input); | 54 var source = _inputAsString(input); |
| 59 | 55 |
| 60 _createMessages(errors: errors, options: options); | 56 _createMessages(errors: errors, options: options); |
| 61 | 57 |
| 62 var file = new SourceFile(source); | 58 var file = new SourceFile(source); |
| 63 | 59 |
| 64 var tree = new _Parser(file, source).parse(); | 60 var tree = new _Parser(file, source).parse(); |
| 65 | 61 |
| 66 analyze([tree], errors: errors, options: options); | 62 analyze([tree], errors: errors, options: options); |
| 67 | 63 |
| 68 if (polyfill) { | 64 if (polyfill) { |
| 69 var processCss = new PolyFill(messages, true); | 65 var processCss = new PolyFill(messages, true); |
| 70 processCss.process(tree, includes: includes); | 66 processCss.process(tree, includes: includes); |
| 71 } | 67 } |
| 72 | 68 |
| 73 return tree; | 69 return tree; |
| 74 } | 70 } |
| 75 | 71 |
| 76 /** Analyze the CSS file. */ | 72 /** Analyze the CSS file. */ |
| 77 void analyze(List<StyleSheet> styleSheets, | 73 void analyze(List<StyleSheet> styleSheets, |
| 78 {List<Message> errors, List<String> options}) { | 74 {List<Message> errors, List<String> options}) { |
| 79 | |
| 80 _createMessages(errors: errors, options: options); | 75 _createMessages(errors: errors, options: options); |
| 81 new Analyzer(styleSheets, messages).run(); | 76 new Analyzer(styleSheets, messages).run(); |
| 82 } | 77 } |
| 83 | 78 |
| 84 /** | 79 /** |
| 85 * Parse the [input] CSS stylesheet into a tree. The [input] can be a [String], | 80 * Parse the [input] CSS stylesheet into a tree. The [input] can be a [String], |
| 86 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional | 81 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional |
| 87 * [errors] list will contain each error/warning as a [Message]. | 82 * [errors] list will contain each error/warning as a [Message]. |
| 88 */ | 83 */ |
| 89 StyleSheet parse(input, {List<Message> errors, List<String> options}) { | 84 StyleSheet parse(input, {List<Message> errors, List<String> options}) { |
| (...skipping 10 matching lines...) Expand all Loading... |
| 100 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional | 95 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional |
| 101 * [errors] list will contain each error/warning as a [Message]. | 96 * [errors] list will contain each error/warning as a [Message]. |
| 102 */ | 97 */ |
| 103 // TODO(jmesserly): should rename "parseSelector" and return Selector | 98 // TODO(jmesserly): should rename "parseSelector" and return Selector |
| 104 StyleSheet selector(input, {List<Message> errors}) { | 99 StyleSheet selector(input, {List<Message> errors}) { |
| 105 var source = _inputAsString(input); | 100 var source = _inputAsString(input); |
| 106 | 101 |
| 107 _createMessages(errors: errors); | 102 _createMessages(errors: errors); |
| 108 | 103 |
| 109 var file = new SourceFile(source); | 104 var file = new SourceFile(source); |
| 110 return (new _Parser(file, source) | 105 return (new _Parser(file, source)..tokenizer.inSelector = true) |
| 111 ..tokenizer.inSelector = true) | |
| 112 .parseSelector(); | 106 .parseSelector(); |
| 113 } | 107 } |
| 114 | 108 |
| 115 SelectorGroup parseSelectorGroup(input, {List<Message> errors}) { | 109 SelectorGroup parseSelectorGroup(input, {List<Message> errors}) { |
| 116 var source = _inputAsString(input); | 110 var source = _inputAsString(input); |
| 117 | 111 |
| 118 _createMessages(errors: errors); | 112 _createMessages(errors: errors); |
| 119 | 113 |
| 120 var file = new SourceFile(source); | 114 var file = new SourceFile(source); |
| 121 return (new _Parser(file, source) | 115 return (new _Parser(file, source) |
| 122 // TODO(jmesserly): this fix should be applied to the parser. It's tricky | 116 // TODO(jmesserly): this fix should be applied to the parser. It's tricky |
| 123 // because by the time the flag is set one token has already been fetched. | 117 // because by the time the flag is set one token has already been fetched. |
| 124 ..tokenizer.inSelector = true) | 118 ..tokenizer.inSelector = true).processSelectorGroup(); |
| 125 .processSelectorGroup(); | |
| 126 } | 119 } |
| 127 | 120 |
| 128 String _inputAsString(input) { | 121 String _inputAsString(input) { |
| 129 String source; | 122 String source; |
| 130 | 123 |
| 131 if (input is String) { | 124 if (input is String) { |
| 132 source = input; | 125 source = input; |
| 133 } else if (input is List) { | 126 } else if (input is List) { |
| 134 // TODO(terry): The parse function needs an "encoding" argument and will | 127 // TODO(terry): The parse function needs an "encoding" argument and will |
| 135 // default to whatever encoding CSS defaults to. | 128 // default to whatever encoding CSS defaults to. |
| (...skipping 20 matching lines...) Expand all Loading... |
| 156 return source; | 149 return source; |
| 157 } | 150 } |
| 158 | 151 |
| 159 // TODO(terry): Consider removing this class when all usages can be eliminated | 152 // TODO(terry): Consider removing this class when all usages can be eliminated |
| 160 // or replaced with compile API. | 153 // or replaced with compile API. |
| 161 /** Public parsing interface for csslib. */ | 154 /** Public parsing interface for csslib. */ |
| 162 class Parser { | 155 class Parser { |
| 163 final _Parser _parser; | 156 final _Parser _parser; |
| 164 | 157 |
| 165 // TODO(jmesserly): having file and text is redundant. | 158 // TODO(jmesserly): having file and text is redundant. |
| 166 Parser(SourceFile file, String text, {int start: 0, String baseUrl}) : | 159 Parser(SourceFile file, String text, {int start: 0, String baseUrl}) |
| 167 _parser = new _Parser(file, text, start: start, baseUrl: baseUrl); | 160 : _parser = new _Parser(file, text, start: start, baseUrl: baseUrl); |
| 168 | 161 |
| 169 StyleSheet parse() => _parser.parse(); | 162 StyleSheet parse() => _parser.parse(); |
| 170 } | 163 } |
| 171 | 164 |
| 172 /** A simple recursive descent parser for CSS. */ | 165 /** A simple recursive descent parser for CSS. */ |
| 173 class _Parser { | 166 class _Parser { |
| 174 final Tokenizer tokenizer; | 167 final Tokenizer tokenizer; |
| 175 | 168 |
| 176 /** Base url of CSS file. */ | 169 /** Base url of CSS file. */ |
| 177 final String _baseUrl; | 170 final String _baseUrl; |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 256 } | 249 } |
| 257 } | 250 } |
| 258 | 251 |
| 259 /////////////////////////////////////////////////////////////////// | 252 /////////////////////////////////////////////////////////////////// |
| 260 // Basic support methods | 253 // Basic support methods |
| 261 /////////////////////////////////////////////////////////////////// | 254 /////////////////////////////////////////////////////////////////// |
| 262 int _peek() { | 255 int _peek() { |
| 263 return _peekToken.kind; | 256 return _peekToken.kind; |
| 264 } | 257 } |
| 265 | 258 |
| 266 Token _next({unicodeRange : false}) { | 259 Token _next({unicodeRange: false}) { |
| 267 _previousToken = _peekToken; | 260 _previousToken = _peekToken; |
| 268 _peekToken = tokenizer.next(unicodeRange: unicodeRange); | 261 _peekToken = tokenizer.next(unicodeRange: unicodeRange); |
| 269 return _previousToken; | 262 return _previousToken; |
| 270 } | 263 } |
| 271 | 264 |
| 272 bool _peekKind(int kind) { | 265 bool _peekKind(int kind) { |
| 273 return _peekToken.kind == kind; | 266 return _peekToken.kind == kind; |
| 274 } | 267 } |
| 275 | 268 |
| 276 /* Is the next token a legal identifier? This includes pseudo-keywords. */ | 269 /* Is the next token a legal identifier? This includes pseudo-keywords. */ |
| 277 bool _peekIdentifier() { | 270 bool _peekIdentifier() { |
| 278 return TokenKind.isIdentifier(_peekToken.kind); | 271 return TokenKind.isIdentifier(_peekToken.kind); |
| 279 } | 272 } |
| 280 | 273 |
| 281 /** Marks the parser/tokenizer look ahead to support Less nested selectors. */ | 274 /** Marks the parser/tokenizer look ahead to support Less nested selectors. */ |
| 282 ParserState get _mark => | 275 ParserState get _mark => |
| 283 new ParserState(_peekToken, _previousToken, tokenizer); | 276 new ParserState(_peekToken, _previousToken, tokenizer); |
| 284 | 277 |
| 285 /** Restores the parser/tokenizer state to state remembered by _mark. */ | 278 /** Restores the parser/tokenizer state to state remembered by _mark. */ |
| 286 void _restore(ParserState markedData) { | 279 void _restore(ParserState markedData) { |
| 287 tokenizer.restore(markedData); | 280 tokenizer.restore(markedData); |
| 288 _peekToken = markedData.peekToken; | 281 _peekToken = markedData.peekToken; |
| 289 _previousToken = markedData.previousToken; | 282 _previousToken = markedData.previousToken; |
| 290 } | 283 } |
| 291 | 284 |
| 292 bool _maybeEat(int kind, {unicodeRange : false}) { | 285 bool _maybeEat(int kind, {unicodeRange: false}) { |
| 293 if (_peekToken.kind == kind) { | 286 if (_peekToken.kind == kind) { |
| 294 _previousToken = _peekToken; | 287 _previousToken = _peekToken; |
| 295 _peekToken = tokenizer.next(unicodeRange: unicodeRange); | 288 _peekToken = tokenizer.next(unicodeRange: unicodeRange); |
| 296 return true; | 289 return true; |
| 297 } else { | 290 } else { |
| 298 return false; | 291 return false; |
| 299 } | 292 } |
| 300 } | 293 } |
| 301 | 294 |
| 302 void _eat(int kind, {unicodeRange : false}) { | 295 void _eat(int kind, {unicodeRange: false}) { |
| 303 if (!_maybeEat(kind, unicodeRange: unicodeRange)) { | 296 if (!_maybeEat(kind, unicodeRange: unicodeRange)) { |
| 304 _errorExpected(TokenKind.kindToString(kind)); | 297 _errorExpected(TokenKind.kindToString(kind)); |
| 305 } | 298 } |
| 306 } | 299 } |
| 307 | 300 |
| 308 void _eatSemicolon() { | 301 void _eatSemicolon() { |
| 309 _eat(TokenKind.SEMICOLON); | 302 _eat(TokenKind.SEMICOLON); |
| 310 } | 303 } |
| 311 | 304 |
| 312 void _errorExpected(String expected) { | 305 void _errorExpected(String expected) { |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 392 // [ AND S* MediaExpr ]* | MediaExpr [ AND S* MediaExpr ]* | 385 // [ AND S* MediaExpr ]* | MediaExpr [ AND S* MediaExpr ]* |
| 393 | 386 |
| 394 var start = _peekToken.span; | 387 var start = _peekToken.span; |
| 395 | 388 |
| 396 // Is it a unary media operator? | 389 // Is it a unary media operator? |
| 397 var op = _peekToken.text; | 390 var op = _peekToken.text; |
| 398 var opLen = op.length; | 391 var opLen = op.length; |
| 399 var unaryOp = TokenKind.matchMediaOperator(op, 0, opLen); | 392 var unaryOp = TokenKind.matchMediaOperator(op, 0, opLen); |
| 400 if (unaryOp != -1) { | 393 if (unaryOp != -1) { |
| 401 if (isChecked) { | 394 if (isChecked) { |
| 402 if (startQuery && | 395 if (startQuery && unaryOp != TokenKind.MEDIA_OP_NOT || |
| 403 unaryOp != TokenKind.MEDIA_OP_NOT || | |
| 404 unaryOp != TokenKind.MEDIA_OP_ONLY) { | 396 unaryOp != TokenKind.MEDIA_OP_ONLY) { |
| 405 _warning("Only the unary operators NOT and ONLY allowed", | 397 _warning("Only the unary operators NOT and ONLY allowed", |
| 406 _makeSpan(start)); | 398 _makeSpan(start)); |
| 407 } | 399 } |
| 408 if (!startQuery && unaryOp != TokenKind.MEDIA_OP_AND) { | 400 if (!startQuery && unaryOp != TokenKind.MEDIA_OP_AND) { |
| 409 _warning("Only the binary AND operator allowed", _makeSpan(start)); | 401 _warning("Only the binary AND operator allowed", _makeSpan(start)); |
| 410 } | 402 } |
| 411 } | 403 } |
| 412 _next(); | 404 _next(); |
| 413 start = _peekToken.span; | 405 start = _peekToken.span; |
| (...skipping 27 matching lines...) Expand all Loading... |
| 441 return new MediaQuery(unaryOp, type, exprs, _makeSpan(start)); | 433 return new MediaQuery(unaryOp, type, exprs, _makeSpan(start)); |
| 442 } | 434 } |
| 443 } | 435 } |
| 444 | 436 |
| 445 MediaExpression processMediaExpression([bool andOperator = false]) { | 437 MediaExpression processMediaExpression([bool andOperator = false]) { |
| 446 var start = _peekToken.span; | 438 var start = _peekToken.span; |
| 447 | 439 |
| 448 // Grammar: '(' S* media_feature S* [ ':' S* expr ]? ')' S* | 440 // Grammar: '(' S* media_feature S* [ ':' S* expr ]? ')' S* |
| 449 if (_maybeEat(TokenKind.LPAREN)) { | 441 if (_maybeEat(TokenKind.LPAREN)) { |
| 450 if (_peekIdentifier()) { | 442 if (_peekIdentifier()) { |
| 451 var feature = identifier(); // Media feature. | 443 var feature = identifier(); // Media feature. |
| 452 while (_maybeEat(TokenKind.COLON)) { | 444 while (_maybeEat(TokenKind.COLON)) { |
| 453 var startExpr = _peekToken.span; | 445 var startExpr = _peekToken.span; |
| 454 var exprs = processExpr(); | 446 var exprs = processExpr(); |
| 455 if (_maybeEat(TokenKind.RPAREN)) { | 447 if (_maybeEat(TokenKind.RPAREN)) { |
| 456 return new MediaExpression(andOperator, feature, exprs, | 448 return new MediaExpression( |
| 457 _makeSpan(startExpr)); | 449 andOperator, feature, exprs, _makeSpan(startExpr)); |
| 458 } else if (isChecked) { | 450 } else if (isChecked) { |
| 459 _warning("Missing parenthesis around media expression", | 451 _warning("Missing parenthesis around media expression", |
| 460 _makeSpan(start)); | 452 _makeSpan(start)); |
| 461 return null; | 453 return null; |
| 462 } | 454 } |
| 463 } | 455 } |
| 464 } else if (isChecked) { | 456 } else if (isChecked) { |
| 465 _warning("Missing media feature in media expression", _makeSpan(start)); | 457 _warning("Missing media feature in media expression", _makeSpan(start)); |
| 466 return null; | 458 return null; |
| 467 } | 459 } |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 582 } | 574 } |
| 583 | 575 |
| 584 // Any pseudo page? | 576 // Any pseudo page? |
| 585 var pseudoPage; | 577 var pseudoPage; |
| 586 if (_maybeEat(TokenKind.COLON)) { | 578 if (_maybeEat(TokenKind.COLON)) { |
| 587 if (_peekIdentifier()) { | 579 if (_peekIdentifier()) { |
| 588 pseudoPage = identifier(); | 580 pseudoPage = identifier(); |
| 589 // TODO(terry): Normalize pseudoPage to lowercase. | 581 // TODO(terry): Normalize pseudoPage to lowercase. |
| 590 if (isChecked && | 582 if (isChecked && |
| 591 !(pseudoPage.name == 'left' || | 583 !(pseudoPage.name == 'left' || |
| 592 pseudoPage.name == 'right' || | 584 pseudoPage.name == 'right' || |
| 593 pseudoPage.name == 'first')) { | 585 pseudoPage.name == 'first')) { |
| 594 _warning("Pseudo page must be left, top or first", | 586 _warning( |
| 595 pseudoPage.span); | 587 "Pseudo page must be left, top or first", pseudoPage.span); |
| 596 return null; | 588 return null; |
| 597 } | 589 } |
| 598 } | 590 } |
| 599 } | 591 } |
| 600 | 592 |
| 601 String pseudoName = pseudoPage is Identifier ? pseudoPage.name : ''; | 593 String pseudoName = pseudoPage is Identifier ? pseudoPage.name : ''; |
| 602 String ident = name is Identifier ? name.name : ''; | 594 String ident = name is Identifier ? name.name : ''; |
| 603 return new PageDirective(ident, pseudoName, | 595 return new PageDirective( |
| 604 processMarginsDeclarations(), _makeSpan(start)); | 596 ident, pseudoName, processMarginsDeclarations(), _makeSpan(start)); |
| 605 | 597 |
| 606 case TokenKind.DIRECTIVE_CHARSET: | 598 case TokenKind.DIRECTIVE_CHARSET: |
| 607 // @charset S* STRING S* ';' | 599 // @charset S* STRING S* ';' |
| 608 _next(); | 600 _next(); |
| 609 | 601 |
| 610 var charEncoding = processQuotedString(false); | 602 var charEncoding = processQuotedString(false); |
| 611 if (isChecked && charEncoding == null) { | 603 if (isChecked && charEncoding == null) { |
| 612 // Missing character encoding. | 604 // Missing character encoding. |
| 613 _warning('missing character encoding string', _makeSpan(start)); | 605 _warning('missing character encoding string', _makeSpan(start)); |
| 614 } | 606 } |
| (...skipping 18 matching lines...) Expand all Loading... |
| 633 */ | 625 */ |
| 634 case TokenKind.DIRECTIVE_KEYFRAMES: | 626 case TokenKind.DIRECTIVE_KEYFRAMES: |
| 635 case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES: | 627 case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES: |
| 636 case TokenKind.DIRECTIVE_MOZ_KEYFRAMES: | 628 case TokenKind.DIRECTIVE_MOZ_KEYFRAMES: |
| 637 case TokenKind.DIRECTIVE_O_KEYFRAMES: | 629 case TokenKind.DIRECTIVE_O_KEYFRAMES: |
| 638 // TODO(terry): Remove workaround when bug 8270 is fixed. | 630 // TODO(terry): Remove workaround when bug 8270 is fixed. |
| 639 case TokenKind.DIRECTIVE_MS_KEYFRAMES: | 631 case TokenKind.DIRECTIVE_MS_KEYFRAMES: |
| 640 if (tokId == TokenKind.DIRECTIVE_MS_KEYFRAMES && isChecked) { | 632 if (tokId == TokenKind.DIRECTIVE_MS_KEYFRAMES && isChecked) { |
| 641 _warning('@-ms-keyframes should be @keyframes', _makeSpan(start)); | 633 _warning('@-ms-keyframes should be @keyframes', _makeSpan(start)); |
| 642 } | 634 } |
| 643 // TODO(terry): End of workaround. | 635 // TODO(terry): End of workaround. |
| 644 | 636 |
| 645 /* Key frames grammar: | 637 /* Key frames grammar: |
| 646 * | 638 * |
| 647 * @[browser]? keyframes [IDENT|STRING] '{' keyframes-blocks '}'; | 639 * @[browser]? keyframes [IDENT|STRING] '{' keyframes-blocks '}'; |
| 648 * | 640 * |
| 649 * browser: [-webkit-, -moz-, -ms-, -o-] | 641 * browser: [-webkit-, -moz-, -ms-, -o-] |
| 650 * | 642 * |
| 651 * keyframes-blocks: | 643 * keyframes-blocks: |
| 652 * [keyframe-selectors '{' declarations '}']* ; | 644 * [keyframe-selectors '{' declarations '}']* ; |
| 653 * | 645 * |
| (...skipping 15 matching lines...) Expand all Loading... |
| 669 Expressions selectors = new Expressions(_makeSpan(start)); | 661 Expressions selectors = new Expressions(_makeSpan(start)); |
| 670 | 662 |
| 671 do { | 663 do { |
| 672 var term = processTerm(); | 664 var term = processTerm(); |
| 673 | 665 |
| 674 // TODO(terry): Only allow from, to and PERCENTAGE ... | 666 // TODO(terry): Only allow from, to and PERCENTAGE ... |
| 675 | 667 |
| 676 selectors.add(term); | 668 selectors.add(term); |
| 677 } while (_maybeEat(TokenKind.COMMA)); | 669 } while (_maybeEat(TokenKind.COMMA)); |
| 678 | 670 |
| 679 keyframe.add(new KeyFrameBlock(selectors, processDeclarations(), | 671 keyframe.add(new KeyFrameBlock( |
| 680 _makeSpan(start))); | 672 selectors, processDeclarations(), _makeSpan(start))); |
| 681 | |
| 682 } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile()); | 673 } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile()); |
| 683 | 674 |
| 684 return keyframe; | 675 return keyframe; |
| 685 | 676 |
| 686 case TokenKind.DIRECTIVE_FONTFACE: | 677 case TokenKind.DIRECTIVE_FONTFACE: |
| 687 _next(); | 678 _next(); |
| 688 return new FontFaceDirective(processDeclarations(), _makeSpan(start)); | 679 return new FontFaceDirective(processDeclarations(), _makeSpan(start)); |
| 689 | 680 |
| 690 case TokenKind.DIRECTIVE_STYLET: | 681 case TokenKind.DIRECTIVE_STYLET: |
| 691 /* Stylet grammar: | 682 /* Stylet grammar: |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 746 if (func is UriTerm) { | 737 if (func is UriTerm) { |
| 747 // @namespace url(""); | 738 // @namespace url(""); |
| 748 namespaceUri = func.text; | 739 namespaceUri = func.text; |
| 749 prefix = null; | 740 prefix = null; |
| 750 } | 741 } |
| 751 } else { | 742 } else { |
| 752 namespaceUri = processQuotedString(false); | 743 namespaceUri = processQuotedString(false); |
| 753 } | 744 } |
| 754 } | 745 } |
| 755 | 746 |
| 756 return new NamespaceDirective(prefix != null ? prefix.name : '', | 747 return new NamespaceDirective( |
| 757 namespaceUri, _makeSpan(start)); | 748 prefix != null ? prefix.name : '', namespaceUri, _makeSpan(start)); |
| 758 | 749 |
| 759 case TokenKind.DIRECTIVE_MIXIN: | 750 case TokenKind.DIRECTIVE_MIXIN: |
| 760 return processMixin(); | 751 return processMixin(); |
| 761 | 752 |
| 762 case TokenKind.DIRECTIVE_INCLUDE: | 753 case TokenKind.DIRECTIVE_INCLUDE: |
| 763 return processInclude( _makeSpan(start)); | 754 return processInclude(_makeSpan(start)); |
| 764 | 755 |
| 765 case TokenKind.DIRECTIVE_CONTENT: | 756 case TokenKind.DIRECTIVE_CONTENT: |
| 766 // TODO(terry): TBD | 757 // TODO(terry): TBD |
| 767 _warning("@content not implemented.", _makeSpan(start)); | 758 _warning("@content not implemented.", _makeSpan(start)); |
| 768 return null; | 759 return null; |
| 769 } | 760 } |
| 770 return null; | 761 return null; |
| 771 } | 762 } |
| 772 | 763 |
| 773 /** | 764 /** |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 816 while (!_maybeEat(TokenKind.END_OF_FILE)) { | 807 while (!_maybeEat(TokenKind.END_OF_FILE)) { |
| 817 var directive = processDirective(); | 808 var directive = processDirective(); |
| 818 if (directive != null) { | 809 if (directive != null) { |
| 819 productions.add(directive); | 810 productions.add(directive); |
| 820 continue; | 811 continue; |
| 821 } | 812 } |
| 822 | 813 |
| 823 var declGroup = processDeclarations(checkBrace: false); | 814 var declGroup = processDeclarations(checkBrace: false); |
| 824 var decls = []; | 815 var decls = []; |
| 825 if (declGroup.declarations.any((decl) { | 816 if (declGroup.declarations.any((decl) { |
| 826 return decl is Declaration && | 817 return decl is Declaration && decl is! IncludeMixinAtDeclaration; |
| 827 decl is! IncludeMixinAtDeclaration; | |
| 828 })) { | 818 })) { |
| 829 var newDecls = []; | 819 var newDecls = []; |
| 830 productions.forEach((include) { | 820 productions.forEach((include) { |
| 831 // If declGroup has items that are declarations then we assume | 821 // If declGroup has items that are declarations then we assume |
| 832 // this mixin is a declaration mixin not a top-level mixin. | 822 // this mixin is a declaration mixin not a top-level mixin. |
| 833 if (include is IncludeDirective) { | 823 if (include is IncludeDirective) { |
| 834 newDecls.add(new IncludeMixinAtDeclaration(include, | 824 newDecls.add(new IncludeMixinAtDeclaration(include, include.span)); |
| 835 include.span)); | |
| 836 } else { | 825 } else { |
| 837 _warning("Error mixing of top-level vs declarations mixins", | 826 _warning("Error mixing of top-level vs declarations mixins", |
| 838 _makeSpan(include.span)); | 827 _makeSpan(include.span)); |
| 839 } | 828 } |
| 840 }); | 829 }); |
| 841 declGroup.declarations.insertAll(0, newDecls); | 830 declGroup.declarations.insertAll(0, newDecls); |
| 842 productions = []; | 831 productions = []; |
| 843 } else { | 832 } else { |
| 844 // Declarations are just @includes make it a list of productions | 833 // Declarations are just @includes make it a list of productions |
| 845 // not a declaration group (anything else is a ruleset). Make it a | 834 // not a declaration group (anything else is a ruleset). Make it a |
| 846 // list of productions, not a declaration group. | 835 // list of productions, not a declaration group. |
| 847 for (var decl in declGroup.declarations) { | 836 for (var decl in declGroup.declarations) { |
| 848 productions.add(decl is IncludeMixinAtDeclaration ? | 837 productions |
| 849 decl.include : decl); | 838 .add(decl is IncludeMixinAtDeclaration ? decl.include : decl); |
| 850 }; | 839 } |
| 840 ; |
| 851 declGroup.declarations.clear(); | 841 declGroup.declarations.clear(); |
| 852 } | 842 } |
| 853 | 843 |
| 854 if (declGroup.declarations.isNotEmpty) { | 844 if (declGroup.declarations.isNotEmpty) { |
| 855 if (productions.isEmpty) { | 845 if (productions.isEmpty) { |
| 856 mixinDirective = new MixinDeclarationDirective(name.name, params, | 846 mixinDirective = new MixinDeclarationDirective( |
| 857 false, declGroup, _makeSpan(start)); | 847 name.name, params, false, declGroup, _makeSpan(start)); |
| 858 break; | 848 break; |
| 859 } else { | 849 } else { |
| 860 for (var decl in declGroup.declarations) { | 850 for (var decl in declGroup.declarations) { |
| 861 productions.add(decl is IncludeMixinAtDeclaration ? | 851 productions |
| 862 decl.include : decl); | 852 .add(decl is IncludeMixinAtDeclaration ? decl.include : decl); |
| 863 } | 853 } |
| 864 } | 854 } |
| 865 } else { | 855 } else { |
| 866 mixinDirective = new MixinRulesetDirective(name.name, params, | 856 mixinDirective = new MixinRulesetDirective( |
| 867 false, productions, _makeSpan(start)); | 857 name.name, params, false, productions, _makeSpan(start)); |
| 868 break; | 858 break; |
| 869 } | 859 } |
| 870 } | 860 } |
| 871 | 861 |
| 872 if (productions.isNotEmpty) { | 862 if (productions.isNotEmpty) { |
| 873 mixinDirective = new MixinRulesetDirective(name.name, params, | 863 mixinDirective = new MixinRulesetDirective( |
| 874 false, productions, _makeSpan(start)); | 864 name.name, params, false, productions, _makeSpan(start)); |
| 875 } | 865 } |
| 876 | 866 |
| 877 _eat(TokenKind.RBRACE); | 867 _eat(TokenKind.RBRACE); |
| 878 | 868 |
| 879 return mixinDirective; | 869 return mixinDirective; |
| 880 } | 870 } |
| 881 | 871 |
| 882 /** | 872 /** |
| 883 * Returns a VarDefinitionDirective or VarDefinition if a varaible otherwise | 873 * Returns a VarDefinitionDirective or VarDefinition if a varaible otherwise |
| 884 * return the token id of a directive or -1 if neither. | 874 * return the token id of a directive or -1 if neither. |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 987 } | 977 } |
| 988 | 978 |
| 989 return new IncludeDirective(name.name, params, span); | 979 return new IncludeDirective(name.name, params, span); |
| 990 } | 980 } |
| 991 | 981 |
| 992 RuleSet processRuleSet([SelectorGroup selectorGroup]) { | 982 RuleSet processRuleSet([SelectorGroup selectorGroup]) { |
| 993 if (selectorGroup == null) { | 983 if (selectorGroup == null) { |
| 994 selectorGroup = processSelectorGroup(); | 984 selectorGroup = processSelectorGroup(); |
| 995 } | 985 } |
| 996 if (selectorGroup != null) { | 986 if (selectorGroup != null) { |
| 997 return new RuleSet(selectorGroup, processDeclarations(), | 987 return new RuleSet( |
| 998 selectorGroup.span); | 988 selectorGroup, processDeclarations(), selectorGroup.span); |
| 999 } | 989 } |
| 1000 } | 990 } |
| 1001 | 991 |
| 1002 /** | 992 /** |
| 1003 * Look ahead to see if what should be a declaration is really a selector. | 993 * Look ahead to see if what should be a declaration is really a selector. |
| 1004 * If it's a selector than it's a nested selector. This support's Less' | 994 * If it's a selector than it's a nested selector. This support's Less' |
| 1005 * nested selector syntax (requires a look ahead). E.g., | 995 * nested selector syntax (requires a look ahead). E.g., |
| 1006 * | 996 * |
| 1007 * div { | 997 * div { |
| 1008 * width : 20px; | 998 * width : 20px; |
| (...skipping 15 matching lines...) Expand all Loading... |
| 1024 */ | 1014 */ |
| 1025 SelectorGroup _nestedSelector() { | 1015 SelectorGroup _nestedSelector() { |
| 1026 Messages oldMessages = messages; | 1016 Messages oldMessages = messages; |
| 1027 _createMessages(); | 1017 _createMessages(); |
| 1028 | 1018 |
| 1029 var markedData = _mark; | 1019 var markedData = _mark; |
| 1030 | 1020 |
| 1031 // Look a head do we have a nested selector instead of a declaration? | 1021 // Look a head do we have a nested selector instead of a declaration? |
| 1032 SelectorGroup selGroup = processSelectorGroup(); | 1022 SelectorGroup selGroup = processSelectorGroup(); |
| 1033 | 1023 |
| 1034 var nestedSelector = selGroup != null && _peekKind(TokenKind.LBRACE) && | 1024 var nestedSelector = selGroup != null && |
| 1025 _peekKind(TokenKind.LBRACE) && |
| 1035 messages.messages.isEmpty; | 1026 messages.messages.isEmpty; |
| 1036 | 1027 |
| 1037 if (!nestedSelector) { | 1028 if (!nestedSelector) { |
| 1038 // Not a selector so restore the world. | 1029 // Not a selector so restore the world. |
| 1039 _restore(markedData); | 1030 _restore(markedData); |
| 1040 messages = oldMessages; | 1031 messages = oldMessages; |
| 1041 return null; | 1032 return null; |
| 1042 } else { | 1033 } else { |
| 1043 // Remember any messages from look ahead. | 1034 // Remember any messages from look ahead. |
| 1044 oldMessages.mergeMessages(messages); | 1035 oldMessages.mergeMessages(messages); |
| 1045 messages = oldMessages; | 1036 messages = oldMessages; |
| 1046 return selGroup; | 1037 return selGroup; |
| 1047 } | 1038 } |
| 1048 } | 1039 } |
| 1049 | 1040 |
| 1050 DeclarationGroup processDeclarations({bool checkBrace: true}) { | 1041 DeclarationGroup processDeclarations({bool checkBrace: true}) { |
| 1051 var start = _peekToken.span; | 1042 var start = _peekToken.span; |
| 1052 | 1043 |
| 1053 if (checkBrace) _eat(TokenKind.LBRACE); | 1044 if (checkBrace) _eat(TokenKind.LBRACE); |
| 1054 | 1045 |
| 1055 List decls = []; | 1046 List decls = []; |
| 1056 List dartStyles = []; // List of latest styles exposed to Dart. | 1047 List dartStyles = []; // List of latest styles exposed to Dart. |
| 1057 | 1048 |
| 1058 do { | 1049 do { |
| 1059 var selectorGroup = _nestedSelector(); | 1050 var selectorGroup = _nestedSelector(); |
| 1060 while (selectorGroup != null) { | 1051 while (selectorGroup != null) { |
| 1061 // Nested selector so process as a ruleset. | 1052 // Nested selector so process as a ruleset. |
| 1062 var ruleset = processRuleSet(selectorGroup); | 1053 var ruleset = processRuleSet(selectorGroup); |
| 1063 decls.add(ruleset); | 1054 decls.add(ruleset); |
| 1064 selectorGroup = _nestedSelector(); | 1055 selectorGroup = _nestedSelector(); |
| 1065 } | 1056 } |
| 1066 | 1057 |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1104 } | 1095 } |
| 1105 | 1096 |
| 1106 List<DeclarationGroup> processMarginsDeclarations() { | 1097 List<DeclarationGroup> processMarginsDeclarations() { |
| 1107 List groups = []; | 1098 List groups = []; |
| 1108 | 1099 |
| 1109 var start = _peekToken.span; | 1100 var start = _peekToken.span; |
| 1110 | 1101 |
| 1111 _eat(TokenKind.LBRACE); | 1102 _eat(TokenKind.LBRACE); |
| 1112 | 1103 |
| 1113 List<Declaration> decls = []; | 1104 List<Declaration> decls = []; |
| 1114 List dartStyles = []; // List of latest styles exposed to Dart. | 1105 List dartStyles = []; // List of latest styles exposed to Dart. |
| 1115 | 1106 |
| 1116 do { | 1107 do { |
| 1117 switch (_peek()) { | 1108 switch (_peek()) { |
| 1118 case TokenKind.MARGIN_DIRECTIVE_TOPLEFTCORNER: | 1109 case TokenKind.MARGIN_DIRECTIVE_TOPLEFTCORNER: |
| 1119 case TokenKind.MARGIN_DIRECTIVE_TOPLEFT: | 1110 case TokenKind.MARGIN_DIRECTIVE_TOPLEFT: |
| 1120 case TokenKind.MARGIN_DIRECTIVE_TOPCENTER: | 1111 case TokenKind.MARGIN_DIRECTIVE_TOPCENTER: |
| 1121 case TokenKind.MARGIN_DIRECTIVE_TOPRIGHT: | 1112 case TokenKind.MARGIN_DIRECTIVE_TOPRIGHT: |
| 1122 case TokenKind.MARGIN_DIRECTIVE_TOPRIGHTCORNER: | 1113 case TokenKind.MARGIN_DIRECTIVE_TOPRIGHTCORNER: |
| 1123 case TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFTCORNER: | 1114 case TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFTCORNER: |
| 1124 case TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFT: | 1115 case TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFT: |
| (...skipping 10 matching lines...) Expand all Loading... |
| 1135 // margin : | 1126 // margin : |
| 1136 // margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* | 1127 // margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* |
| 1137 // | 1128 // |
| 1138 // margin_sym : @top-left-corner, @top-left, @bottom-left, etc. | 1129 // margin_sym : @top-left-corner, @top-left, @bottom-left, etc. |
| 1139 var marginSym = _peek(); | 1130 var marginSym = _peek(); |
| 1140 | 1131 |
| 1141 _next(); | 1132 _next(); |
| 1142 | 1133 |
| 1143 var declGroup = processDeclarations(); | 1134 var declGroup = processDeclarations(); |
| 1144 if (declGroup != null) { | 1135 if (declGroup != null) { |
| 1145 groups.add(new MarginGroup(marginSym, declGroup.declarations, | 1136 groups.add(new MarginGroup( |
| 1146 _makeSpan(start))); | 1137 marginSym, declGroup.declarations, _makeSpan(start))); |
| 1147 } | 1138 } |
| 1148 break; | 1139 break; |
| 1149 default: | 1140 default: |
| 1150 Declaration decl = processDeclaration(dartStyles); | 1141 Declaration decl = processDeclaration(dartStyles); |
| 1151 if (decl != null) { | 1142 if (decl != null) { |
| 1152 if (decl.hasDartStyle) { | 1143 if (decl.hasDartStyle) { |
| 1153 var newDartStyle = decl.dartStyle; | 1144 var newDartStyle = decl.dartStyle; |
| 1154 | 1145 |
| 1155 // Replace or add latest Dart style. | 1146 // Replace or add latest Dart style. |
| 1156 bool replaced = false; | 1147 bool replaced = false; |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1241 combinatorType = TokenKind.COMBINATOR_GREATER; | 1232 combinatorType = TokenKind.COMBINATOR_GREATER; |
| 1242 break; | 1233 break; |
| 1243 case TokenKind.TILDE: | 1234 case TokenKind.TILDE: |
| 1244 _eat(TokenKind.TILDE); | 1235 _eat(TokenKind.TILDE); |
| 1245 combinatorType = TokenKind.COMBINATOR_TILDE; | 1236 combinatorType = TokenKind.COMBINATOR_TILDE; |
| 1246 break; | 1237 break; |
| 1247 case TokenKind.AMPERSAND: | 1238 case TokenKind.AMPERSAND: |
| 1248 _eat(TokenKind.AMPERSAND); | 1239 _eat(TokenKind.AMPERSAND); |
| 1249 thisOperator = true; | 1240 thisOperator = true; |
| 1250 break; | 1241 break; |
| 1251 } | 1242 } |
| 1252 | 1243 |
| 1253 // Check if WHITESPACE existed between tokens if so we're descendent. | 1244 // Check if WHITESPACE existed between tokens if so we're descendent. |
| 1254 if (combinatorType == TokenKind.COMBINATOR_NONE && !forceCombinatorNone) { | 1245 if (combinatorType == TokenKind.COMBINATOR_NONE && !forceCombinatorNone) { |
| 1255 if (this._previousToken != null && | 1246 if (this._previousToken != null && |
| 1256 this._previousToken.end != this._peekToken.start) { | 1247 this._previousToken.end != this._peekToken.start) { |
| 1257 combinatorType = TokenKind.COMBINATOR_DESCENDANT; | 1248 combinatorType = TokenKind.COMBINATOR_DESCENDANT; |
| 1258 } | 1249 } |
| 1259 } | 1250 } |
| 1260 | 1251 |
| 1261 var span = _makeSpan(start); | 1252 var span = _makeSpan(start); |
| 1262 var simpleSel = thisOperator ? | 1253 var simpleSel = thisOperator |
| 1263 new ElementSelector(new ThisOperator(span), span) : simpleSelector(); | 1254 ? new ElementSelector(new ThisOperator(span), span) |
| 1255 : simpleSelector(); |
| 1264 if (simpleSel == null && | 1256 if (simpleSel == null && |
| 1265 (combinatorType == TokenKind.COMBINATOR_PLUS || | 1257 (combinatorType == TokenKind.COMBINATOR_PLUS || |
| 1266 combinatorType == TokenKind.COMBINATOR_GREATER || | 1258 combinatorType == TokenKind.COMBINATOR_GREATER || |
| 1267 combinatorType == TokenKind.COMBINATOR_TILDE)) { | 1259 combinatorType == TokenKind.COMBINATOR_TILDE)) { |
| 1268 // For "+ &", "~ &" or "> &" a selector sequence with no name is needed | 1260 // For "+ &", "~ &" or "> &" a selector sequence with no name is needed |
| 1269 // so that the & will have a combinator too. This is needed to | 1261 // so that the & will have a combinator too. This is needed to |
| 1270 // disambiguate selector expressions: | 1262 // disambiguate selector expressions: |
| 1271 // .foo&:hover combinator before & is NONE | 1263 // .foo&:hover combinator before & is NONE |
| 1272 // .foo & combinator before & is DESCDENDANT | 1264 // .foo & combinator before & is DESCDENDANT |
| 1273 // .foo > & combinator before & is GREATER | 1265 // .foo > & combinator before & is GREATER |
| 1274 simpleSel = new ElementSelector(new Identifier("", span), span); | 1266 simpleSel = new ElementSelector(new Identifier("", span), span); |
| 1275 } | 1267 } |
| 1276 if (simpleSel != null) { | 1268 if (simpleSel != null) { |
| 1277 return new SimpleSelectorSequence(simpleSel, span, combinatorType); | 1269 return new SimpleSelectorSequence(simpleSel, span, combinatorType); |
| 1278 } | 1270 } |
| 1279 } | 1271 } |
| 1280 | 1272 |
| 1281 /** | 1273 /** |
| 1282 * Simple selector grammar: | 1274 * Simple selector grammar: |
| 1283 * | 1275 * |
| 1284 * simple_selector_sequence | 1276 * simple_selector_sequence |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1335 break; | 1327 break; |
| 1336 case TokenKind.IDENTIFIER: | 1328 case TokenKind.IDENTIFIER: |
| 1337 element = identifier(); | 1329 element = identifier(); |
| 1338 break; | 1330 break; |
| 1339 default: | 1331 default: |
| 1340 _error('expected element name or universal(*), but found $_peekToken', | 1332 _error('expected element name or universal(*), but found $_peekToken', |
| 1341 _peekToken.span); | 1333 _peekToken.span); |
| 1342 break; | 1334 break; |
| 1343 } | 1335 } |
| 1344 | 1336 |
| 1345 return new NamespaceSelector(first, | 1337 return new NamespaceSelector( |
| 1346 new ElementSelector(element, element.span), _makeSpan(start)); | 1338 first, new ElementSelector(element, element.span), _makeSpan(start)); |
| 1347 } else if (first != null) { | 1339 } else if (first != null) { |
| 1348 return new ElementSelector(first, _makeSpan(start)); | 1340 return new ElementSelector(first, _makeSpan(start)); |
| 1349 } else { | 1341 } else { |
| 1350 // Check for HASH | class | attrib | pseudo | negation | 1342 // Check for HASH | class | attrib | pseudo | negation |
| 1351 return simpleSelectorTail(); | 1343 return simpleSelectorTail(); |
| 1352 } | 1344 } |
| 1353 } | 1345 } |
| 1354 | 1346 |
| 1355 bool _anyWhiteSpaceBeforePeekToken(int kind) { | 1347 bool _anyWhiteSpaceBeforePeekToken(int kind) { |
| 1356 if (_previousToken != null && _peekToken != null && | 1348 if (_previousToken != null && |
| 1349 _peekToken != null && |
| 1357 _previousToken.kind == kind) { | 1350 _previousToken.kind == kind) { |
| 1358 // If end of previous token isn't same as the start of peek token then | 1351 // If end of previous token isn't same as the start of peek token then |
| 1359 // there's something between these tokens probably whitespace. | 1352 // there's something between these tokens probably whitespace. |
| 1360 return _previousToken.end != _peekToken.start; | 1353 return _previousToken.end != _peekToken.start; |
| 1361 } | 1354 } |
| 1362 | 1355 |
| 1363 return false; | 1356 return false; |
| 1364 } | 1357 } |
| 1365 | 1358 |
| 1366 /** | 1359 /** |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1427 var pseudoName; | 1420 var pseudoName; |
| 1428 if (_peekIdentifier()) { | 1421 if (_peekIdentifier()) { |
| 1429 pseudoName = identifier(); | 1422 pseudoName = identifier(); |
| 1430 } else { | 1423 } else { |
| 1431 return null; | 1424 return null; |
| 1432 } | 1425 } |
| 1433 | 1426 |
| 1434 // Functional pseudo? | 1427 // Functional pseudo? |
| 1435 | 1428 |
| 1436 if (_peekToken.kind == TokenKind.LPAREN) { | 1429 if (_peekToken.kind == TokenKind.LPAREN) { |
| 1437 | |
| 1438 if (!pseudoElement && pseudoName.name.toLowerCase() == 'not') { | 1430 if (!pseudoElement && pseudoName.name.toLowerCase() == 'not') { |
| 1439 _eat(TokenKind.LPAREN); | 1431 _eat(TokenKind.LPAREN); |
| 1440 | 1432 |
| 1441 // Negation : ':NOT(' S* negation_arg S* ')' | 1433 // Negation : ':NOT(' S* negation_arg S* ')' |
| 1442 var negArg = simpleSelector(); | 1434 var negArg = simpleSelector(); |
| 1443 | 1435 |
| 1444 _eat(TokenKind.RPAREN); | 1436 _eat(TokenKind.RPAREN); |
| 1445 return new NegationSelector(negArg, _makeSpan(start)); | 1437 return new NegationSelector(negArg, _makeSpan(start)); |
| 1446 } else { | 1438 } else { |
| 1447 // Special parsing for expressions in pseudo functions. Minus is used | 1439 // Special parsing for expressions in pseudo functions. Minus is used |
| (...skipping 11 matching lines...) Expand all Loading... |
| 1459 tokenizer.inSelectorExpression = false; | 1451 tokenizer.inSelectorExpression = false; |
| 1460 | 1452 |
| 1461 // Used during selector look-a-head if not a SelectorExpression is | 1453 // Used during selector look-a-head if not a SelectorExpression is |
| 1462 // bad. | 1454 // bad. |
| 1463 if (expr is! SelectorExpression) { | 1455 if (expr is! SelectorExpression) { |
| 1464 _errorExpected("CSS expression"); | 1456 _errorExpected("CSS expression"); |
| 1465 return null; | 1457 return null; |
| 1466 } | 1458 } |
| 1467 | 1459 |
| 1468 _eat(TokenKind.RPAREN); | 1460 _eat(TokenKind.RPAREN); |
| 1469 return (pseudoElement) ? | 1461 return (pseudoElement) |
| 1470 new PseudoElementFunctionSelector(pseudoName, expr, span) : | 1462 ? new PseudoElementFunctionSelector(pseudoName, expr, span) |
| 1471 new PseudoClassFunctionSelector(pseudoName, expr, span); | 1463 : new PseudoClassFunctionSelector(pseudoName, expr, span); |
| 1472 } | 1464 } |
| 1473 } | 1465 } |
| 1474 | 1466 |
| 1475 // TODO(terry): Need to handle specific pseudo class/element name and | 1467 // TODO(terry): Need to handle specific pseudo class/element name and |
| 1476 // backward compatible names that are : as well as :: as well as | 1468 // backward compatible names that are : as well as :: as well as |
| 1477 // parameters. Current, spec uses :: for pseudo-element and : for | 1469 // parameters. Current, spec uses :: for pseudo-element and : for |
| 1478 // pseudo-class. However, CSS2.1 allows for : to specify old | 1470 // pseudo-class. However, CSS2.1 allows for : to specify old |
| 1479 // pseudo-elements (:first-line, :first-letter, :before and :after) any | 1471 // pseudo-elements (:first-line, :first-letter, :before and :after) any |
| 1480 // new pseudo-elements defined would require a ::. | 1472 // new pseudo-elements defined would require a ::. |
| 1481 return pseudoElement ? | 1473 return pseudoElement |
| 1482 new PseudoElementSelector(pseudoName, _makeSpan(start)) : | 1474 ? new PseudoElementSelector(pseudoName, _makeSpan(start)) |
| 1483 new PseudoClassSelector(pseudoName, _makeSpan(start)); | 1475 : new PseudoClassSelector(pseudoName, _makeSpan(start)); |
| 1484 } | 1476 } |
| 1485 | 1477 |
| 1486 /** | 1478 /** |
| 1487 * In CSS3, the expressions are identifiers, strings, or of the form "an+b". | 1479 * In CSS3, the expressions are identifiers, strings, or of the form "an+b". |
| 1488 * | 1480 * |
| 1489 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ | 1481 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ |
| 1490 * | 1482 * |
| 1491 * num [0-9]+|[0-9]*\.[0-9]+ | 1483 * num [0-9]+|[0-9]*\.[0-9]+ |
| 1492 * PLUS '+' | 1484 * PLUS '+' |
| 1493 * DIMENSION {num}{ident} | 1485 * DIMENSION {num}{ident} |
| (...skipping 30 matching lines...) Expand all Loading... |
| 1524 break; | 1516 break; |
| 1525 case TokenKind.SINGLE_QUOTE: | 1517 case TokenKind.SINGLE_QUOTE: |
| 1526 value = processQuotedString(false); | 1518 value = processQuotedString(false); |
| 1527 value = "'${_escapeString(value, single: true)}'"; | 1519 value = "'${_escapeString(value, single: true)}'"; |
| 1528 return new LiteralTerm(value, value, _makeSpan(start)); | 1520 return new LiteralTerm(value, value, _makeSpan(start)); |
| 1529 case TokenKind.DOUBLE_QUOTE: | 1521 case TokenKind.DOUBLE_QUOTE: |
| 1530 value = processQuotedString(false); | 1522 value = processQuotedString(false); |
| 1531 value = '"${_escapeString(value)}"'; | 1523 value = '"${_escapeString(value)}"'; |
| 1532 return new LiteralTerm(value, value, _makeSpan(start)); | 1524 return new LiteralTerm(value, value, _makeSpan(start)); |
| 1533 case TokenKind.IDENTIFIER: | 1525 case TokenKind.IDENTIFIER: |
| 1534 value = identifier(); // Snarf up the ident we'll remap, maybe. | 1526 value = identifier(); // Snarf up the ident we'll remap, maybe. |
| 1535 break; | 1527 break; |
| 1536 default: | 1528 default: |
| 1537 keepParsing = false; | 1529 keepParsing = false; |
| 1538 } | 1530 } |
| 1539 | 1531 |
| 1540 if (keepParsing && value != null) { | 1532 if (keepParsing && value != null) { |
| 1541 var unitTerm; | 1533 var unitTerm; |
| 1542 // Don't process the dimension if MINUS or PLUS is next. | 1534 // Don't process the dimension if MINUS or PLUS is next. |
| 1543 if (_peek() != TokenKind.MINUS && _peek() != TokenKind.PLUS) { | 1535 if (_peek() != TokenKind.MINUS && _peek() != TokenKind.PLUS) { |
| 1544 unitTerm = processDimension(termToken, value, _makeSpan(start)); | 1536 unitTerm = processDimension(termToken, value, _makeSpan(start)); |
| (...skipping 30 matching lines...) Expand all Loading... |
| 1575 // | 1567 // |
| 1576 // | 1568 // |
| 1577 AttributeSelector processAttribute() { | 1569 AttributeSelector processAttribute() { |
| 1578 var start = _peekToken.span; | 1570 var start = _peekToken.span; |
| 1579 | 1571 |
| 1580 if (_maybeEat(TokenKind.LBRACK)) { | 1572 if (_maybeEat(TokenKind.LBRACK)) { |
| 1581 var attrName = identifier(); | 1573 var attrName = identifier(); |
| 1582 | 1574 |
| 1583 int op; | 1575 int op; |
| 1584 switch (_peek()) { | 1576 switch (_peek()) { |
| 1585 case TokenKind.EQUALS: | 1577 case TokenKind.EQUALS: |
| 1586 case TokenKind.INCLUDES: // ~= | 1578 case TokenKind.INCLUDES: // ~= |
| 1587 case TokenKind.DASH_MATCH: // |= | 1579 case TokenKind.DASH_MATCH: // |= |
| 1588 case TokenKind.PREFIX_MATCH: // ^= | 1580 case TokenKind.PREFIX_MATCH: // ^= |
| 1589 case TokenKind.SUFFIX_MATCH: // $= | 1581 case TokenKind.SUFFIX_MATCH: // $= |
| 1590 case TokenKind.SUBSTRING_MATCH: // *= | 1582 case TokenKind.SUBSTRING_MATCH: // *= |
| 1591 op = _peek(); | 1583 op = _peek(); |
| 1592 _next(); | 1584 _next(); |
| 1593 break; | 1585 break; |
| 1594 default: | 1586 default: |
| 1595 op = TokenKind.NO_MATCH; | 1587 op = TokenKind.NO_MATCH; |
| 1596 } | 1588 } |
| 1597 | 1589 |
| 1598 var value; | 1590 var value; |
| 1599 if (op != TokenKind.NO_MATCH) { | 1591 if (op != TokenKind.NO_MATCH) { |
| 1600 // Operator hit so we require a value too. | 1592 // Operator hit so we require a value too. |
| 1601 if (_peekIdentifier()) { | 1593 if (_peekIdentifier()) { |
| 1602 value = identifier(); | 1594 value = identifier(); |
| 1603 } else { | 1595 } else { |
| 1604 value = processQuotedString(false); | 1596 value = processQuotedString(false); |
| 1605 } | 1597 } |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1647 | 1639 |
| 1648 _eat(TokenKind.COLON); | 1640 _eat(TokenKind.COLON); |
| 1649 | 1641 |
| 1650 Expressions exprs = processExpr(ieFilterProperty); | 1642 Expressions exprs = processExpr(ieFilterProperty); |
| 1651 | 1643 |
| 1652 var dartComposite = _styleForDart(propertyIdent, exprs, dartStyles); | 1644 var dartComposite = _styleForDart(propertyIdent, exprs, dartStyles); |
| 1653 | 1645 |
| 1654 // Handle !important (prio) | 1646 // Handle !important (prio) |
| 1655 var importantPriority = _maybeEat(TokenKind.IMPORTANT); | 1647 var importantPriority = _maybeEat(TokenKind.IMPORTANT); |
| 1656 | 1648 |
| 1657 decl = new Declaration(propertyIdent, exprs, dartComposite, | 1649 decl = new Declaration( |
| 1658 _makeSpan(start), important: importantPriority, ie7: ie7); | 1650 propertyIdent, exprs, dartComposite, _makeSpan(start), |
| 1651 important: importantPriority, ie7: ie7); |
| 1659 } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) { | 1652 } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) { |
| 1660 _next(); | 1653 _next(); |
| 1661 var definedName; | 1654 var definedName; |
| 1662 if (_peekIdentifier()) definedName = identifier(); | 1655 if (_peekIdentifier()) definedName = identifier(); |
| 1663 | 1656 |
| 1664 _eat(TokenKind.COLON); | 1657 _eat(TokenKind.COLON); |
| 1665 | 1658 |
| 1666 Expressions exprs = processExpr(); | 1659 Expressions exprs = processExpr(); |
| 1667 | 1660 |
| 1668 decl = new VarDefinition(definedName, exprs, _makeSpan(start)); | 1661 decl = new VarDefinition(definedName, exprs, _makeSpan(start)); |
| (...skipping 22 matching lines...) Expand all Loading... |
| 1691 _warning("not a valid selector", span); | 1684 _warning("not a valid selector", span); |
| 1692 } | 1685 } |
| 1693 } | 1686 } |
| 1694 decl = new ExtendDeclaration(simpleSequences, span); | 1687 decl = new ExtendDeclaration(simpleSequences, span); |
| 1695 } | 1688 } |
| 1696 | 1689 |
| 1697 return decl; | 1690 return decl; |
| 1698 } | 1691 } |
| 1699 | 1692 |
| 1700 /** List of styles exposed to the Dart UI framework. */ | 1693 /** List of styles exposed to the Dart UI framework. */ |
| 1701 static const int _fontPartFont= 0; | 1694 static const int _fontPartFont = 0; |
| 1702 static const int _fontPartVariant = 1; | 1695 static const int _fontPartVariant = 1; |
| 1703 static const int _fontPartWeight = 2; | 1696 static const int _fontPartWeight = 2; |
| 1704 static const int _fontPartSize = 3; | 1697 static const int _fontPartSize = 3; |
| 1705 static const int _fontPartFamily = 4; | 1698 static const int _fontPartFamily = 4; |
| 1706 static const int _fontPartStyle = 5; | 1699 static const int _fontPartStyle = 5; |
| 1707 static const int _marginPartMargin = 6; | 1700 static const int _marginPartMargin = 6; |
| 1708 static const int _marginPartLeft = 7; | 1701 static const int _marginPartLeft = 7; |
| 1709 static const int _marginPartTop = 8; | 1702 static const int _marginPartTop = 8; |
| 1710 static const int _marginPartRight = 9; | 1703 static const int _marginPartRight = 9; |
| 1711 static const int _marginPartBottom = 10; | 1704 static const int _marginPartBottom = 10; |
| (...skipping 10 matching lines...) Expand all Loading... |
| 1722 static const int _borderPartBottomWidth = 21; | 1715 static const int _borderPartBottomWidth = 21; |
| 1723 static const int _heightPart = 22; | 1716 static const int _heightPart = 22; |
| 1724 static const int _widthPart = 23; | 1717 static const int _widthPart = 23; |
| 1725 static const int _paddingPartPadding = 24; | 1718 static const int _paddingPartPadding = 24; |
| 1726 static const int _paddingPartLeft = 25; | 1719 static const int _paddingPartLeft = 25; |
| 1727 static const int _paddingPartTop = 26; | 1720 static const int _paddingPartTop = 26; |
| 1728 static const int _paddingPartRight = 27; | 1721 static const int _paddingPartRight = 27; |
| 1729 static const int _paddingPartBottom = 28; | 1722 static const int _paddingPartBottom = 28; |
| 1730 | 1723 |
| 1731 static const Map<String, int> _stylesToDart = const { | 1724 static const Map<String, int> _stylesToDart = const { |
| 1732 'font': _fontPartFont, | 1725 'font': _fontPartFont, |
| 1733 'font-family': _fontPartFamily, | 1726 'font-family': _fontPartFamily, |
| 1734 'font-size': _fontPartSize, | 1727 'font-size': _fontPartSize, |
| 1735 'font-style': _fontPartStyle, | 1728 'font-style': _fontPartStyle, |
| 1736 'font-variant': _fontPartVariant, | 1729 'font-variant': _fontPartVariant, |
| 1737 'font-weight': _fontPartWeight, | 1730 'font-weight': _fontPartWeight, |
| 1738 'line-height': _lineHeightPart, | 1731 'line-height': _lineHeightPart, |
| 1739 'margin': _marginPartMargin, | 1732 'margin': _marginPartMargin, |
| 1740 'margin-left': _marginPartLeft, | 1733 'margin-left': _marginPartLeft, |
| 1741 'margin-right': _marginPartRight, | 1734 'margin-right': _marginPartRight, |
| 1742 'margin-top': _marginPartTop, | 1735 'margin-top': _marginPartTop, |
| 1743 'margin-bottom': _marginPartBottom, | 1736 'margin-bottom': _marginPartBottom, |
| 1744 'border': _borderPartBorder, | 1737 'border': _borderPartBorder, |
| 1745 'border-left': _borderPartLeft, | 1738 'border-left': _borderPartLeft, |
| 1746 'border-right': _borderPartRight, | 1739 'border-right': _borderPartRight, |
| 1747 'border-top': _borderPartTop, | 1740 'border-top': _borderPartTop, |
| 1748 'border-bottom': _borderPartBottom, | 1741 'border-bottom': _borderPartBottom, |
| 1749 'border-width': _borderPartWidth, | 1742 'border-width': _borderPartWidth, |
| 1750 'border-left-width': _borderPartLeftWidth, | 1743 'border-left-width': _borderPartLeftWidth, |
| 1751 'border-top-width': _borderPartTopWidth, | 1744 'border-top-width': _borderPartTopWidth, |
| 1752 'border-right-width': _borderPartRightWidth, | 1745 'border-right-width': _borderPartRightWidth, |
| 1753 'border-bottom-width': _borderPartBottomWidth, | 1746 'border-bottom-width': _borderPartBottomWidth, |
| 1754 'height': _heightPart, | 1747 'height': _heightPart, |
| 1755 'width': _widthPart, | 1748 'width': _widthPart, |
| 1756 'padding': _paddingPartPadding, | 1749 'padding': _paddingPartPadding, |
| 1757 'padding-left': _paddingPartLeft, | 1750 'padding-left': _paddingPartLeft, |
| 1758 'padding-top': _paddingPartTop, | 1751 'padding-top': _paddingPartTop, |
| 1759 'padding-right': _paddingPartRight, | 1752 'padding-right': _paddingPartRight, |
| 1760 'padding-bottom': _paddingPartBottom | 1753 'padding-bottom': _paddingPartBottom |
| 1761 }; | 1754 }; |
| 1762 | 1755 |
| 1763 static const Map<String, int> _nameToFontWeight = const { | 1756 static const Map<String, int> _nameToFontWeight = const { |
| 1764 'bold' : FontWeight.bold, | 1757 'bold': FontWeight.bold, |
| 1765 'normal' : FontWeight.normal | 1758 'normal': FontWeight.normal |
| 1766 }; | 1759 }; |
| 1767 | 1760 |
| 1768 static int _findStyle(String styleName) => _stylesToDart[styleName]; | 1761 static int _findStyle(String styleName) => _stylesToDart[styleName]; |
| 1769 | 1762 |
| 1770 DartStyleExpression _styleForDart(Identifier property, Expressions exprs, | 1763 DartStyleExpression _styleForDart( |
| 1771 List dartStyles) { | 1764 Identifier property, Expressions exprs, List dartStyles) { |
| 1772 var styleType = _findStyle(property.name.toLowerCase()); | 1765 var styleType = _findStyle(property.name.toLowerCase()); |
| 1773 if (styleType != null) { | 1766 if (styleType != null) { |
| 1774 return buildDartStyleNode(styleType, exprs, dartStyles); | 1767 return buildDartStyleNode(styleType, exprs, dartStyles); |
| 1775 } | 1768 } |
| 1776 } | 1769 } |
| 1777 | 1770 |
| 1778 FontExpression _mergeFontStyles(FontExpression fontExpr, List dartStyles) { | 1771 FontExpression _mergeFontStyles(FontExpression fontExpr, List dartStyles) { |
| 1779 // Merge all font styles for this class selector. | 1772 // Merge all font styles for this class selector. |
| 1780 for (var dartStyle in dartStyles) { | 1773 for (var dartStyle in dartStyles) { |
| 1781 if (dartStyle.isFont) { | 1774 if (dartStyle.isFont) { |
| 1782 fontExpr = new FontExpression.merge(dartStyle, fontExpr); | 1775 fontExpr = new FontExpression.merge(dartStyle, fontExpr); |
| 1783 } | 1776 } |
| 1784 } | 1777 } |
| 1785 | 1778 |
| 1786 return fontExpr; | 1779 return fontExpr; |
| 1787 } | 1780 } |
| 1788 | 1781 |
| 1789 DartStyleExpression buildDartStyleNode(int styleType, Expressions exprs, | 1782 DartStyleExpression buildDartStyleNode( |
| 1790 List dartStyles) { | 1783 int styleType, Expressions exprs, List dartStyles) { |
| 1791 | |
| 1792 switch (styleType) { | 1784 switch (styleType) { |
| 1793 /* | 1785 /* |
| 1794 * Properties in order: | 1786 * Properties in order: |
| 1795 * | 1787 * |
| 1796 * font-style font-variant font-weight font-size/line-height font-family | 1788 * font-style font-variant font-weight font-size/line-height font-family |
| 1797 * | 1789 * |
| 1798 * The font-size and font-family values are required. If other values are | 1790 * The font-size and font-family values are required. If other values are |
| 1799 * missing; a default, if it exist, will be used. | 1791 * missing; a default, if it exist, will be used. |
| 1800 */ | 1792 */ |
| 1801 case _fontPartFont: | 1793 case _fontPartFont: |
| 1802 var processor = new ExpressionsProcessor(exprs); | 1794 var processor = new ExpressionsProcessor(exprs); |
| 1803 return _mergeFontStyles(processor.processFont(), dartStyles); | 1795 return _mergeFontStyles(processor.processFont(), dartStyles); |
| 1804 case _fontPartFamily: | 1796 case _fontPartFamily: |
| 1805 var processor = new ExpressionsProcessor(exprs); | 1797 var processor = new ExpressionsProcessor(exprs); |
| 1806 | 1798 |
| 1807 try { | 1799 try { |
| 1808 return _mergeFontStyles(processor.processFontFamily(), dartStyles); | 1800 return _mergeFontStyles(processor.processFontFamily(), dartStyles); |
| 1809 } catch (fontException) { | 1801 } catch (fontException) { |
| 1810 _error(fontException, _peekToken.span); | 1802 _error(fontException, _peekToken.span); |
| 1811 } | 1803 } |
| 1812 break; | 1804 break; |
| 1813 case _fontPartSize: | 1805 case _fontPartSize: |
| (...skipping 23 matching lines...) Expand all Loading... |
| 1837 * bolder | 1829 * bolder |
| 1838 * lighter | 1830 * lighter |
| 1839 * 100 - 900 | 1831 * 100 - 900 |
| 1840 * inherit | 1832 * inherit |
| 1841 */ | 1833 */ |
| 1842 // TODO(terry): Only 'normal', 'bold', or values of 100-900 supoorted | 1834 // TODO(terry): Only 'normal', 'bold', or values of 100-900 supoorted |
| 1843 // need to handle bolder, lighter, and inherit. See | 1835 // need to handle bolder, lighter, and inherit. See |
| 1844 // https://github.com/dart-lang/csslib/issues/1 | 1836 // https://github.com/dart-lang/csslib/issues/1 |
| 1845 var expr = exprs.expressions[0]; | 1837 var expr = exprs.expressions[0]; |
| 1846 if (expr is NumberTerm) { | 1838 if (expr is NumberTerm) { |
| 1847 var fontExpr = new FontExpression(expr.span, | 1839 var fontExpr = new FontExpression(expr.span, weight: expr.value); |
| 1848 weight: expr.value); | |
| 1849 return _mergeFontStyles(fontExpr, dartStyles); | 1840 return _mergeFontStyles(fontExpr, dartStyles); |
| 1850 } else if (expr is LiteralTerm) { | 1841 } else if (expr is LiteralTerm) { |
| 1851 int weight = _nameToFontWeight[expr.value.toString()]; | 1842 int weight = _nameToFontWeight[expr.value.toString()]; |
| 1852 if (weight != null) { | 1843 if (weight != null) { |
| 1853 var fontExpr = new FontExpression(expr.span, weight: weight); | 1844 var fontExpr = new FontExpression(expr.span, weight: weight); |
| 1854 return _mergeFontStyles(fontExpr, dartStyles); | 1845 return _mergeFontStyles(fontExpr, dartStyles); |
| 1855 } | 1846 } |
| 1856 } | 1847 } |
| 1857 break; | 1848 break; |
| 1858 case _lineHeightPart: | 1849 case _lineHeightPart: |
| 1859 num lineHeight; | 1850 num lineHeight; |
| 1860 if (exprs.expressions.length == 1) { | 1851 if (exprs.expressions.length == 1) { |
| 1861 var expr = exprs.expressions[0]; | 1852 var expr = exprs.expressions[0]; |
| 1862 if (expr is UnitTerm) { | 1853 if (expr is UnitTerm) { |
| 1863 UnitTerm unitTerm = expr; | 1854 UnitTerm unitTerm = expr; |
| 1864 // TODO(terry): Need to handle other units and LiteralTerm normal | 1855 // TODO(terry): Need to handle other units and LiteralTerm normal |
| 1865 // See https://github.com/dart-lang/csslib/issues/2. | 1856 // See https://github.com/dart-lang/csslib/issues/2. |
| 1866 if (unitTerm.unit == TokenKind.UNIT_LENGTH_PX || | 1857 if (unitTerm.unit == TokenKind.UNIT_LENGTH_PX || |
| 1867 unitTerm.unit == TokenKind.UNIT_LENGTH_PT) { | 1858 unitTerm.unit == TokenKind.UNIT_LENGTH_PT) { |
| 1868 var fontExpr = new FontExpression(expr.span, | 1859 var fontExpr = new FontExpression(expr.span, |
| 1869 lineHeight: new LineHeight(expr.value, inPixels: true)); | 1860 lineHeight: new LineHeight(expr.value, inPixels: true)); |
| 1870 return _mergeFontStyles(fontExpr, dartStyles); | 1861 return _mergeFontStyles(fontExpr, dartStyles); |
| 1871 } else if (isChecked) { | 1862 } else if (isChecked) { |
| 1872 _warning("Unexpected unit for line-height", expr.span); | 1863 _warning("Unexpected unit for line-height", expr.span); |
| 1873 } | 1864 } |
| 1874 } else if (expr is NumberTerm) { | 1865 } else if (expr is NumberTerm) { |
| 1875 var fontExpr = new FontExpression(expr.span, | 1866 var fontExpr = new FontExpression(expr.span, |
| 1876 lineHeight: new LineHeight(expr.value, inPixels: false)); | 1867 lineHeight: new LineHeight(expr.value, inPixels: false)); |
| 1877 return _mergeFontStyles(fontExpr, dartStyles); | 1868 return _mergeFontStyles(fontExpr, dartStyles); |
| (...skipping 14 matching lines...) Expand all Loading... |
| 1892 } | 1883 } |
| 1893 break; | 1884 break; |
| 1894 case _borderPartWidth: | 1885 case _borderPartWidth: |
| 1895 var v = marginValue(exprs.expressions[0]); | 1886 var v = marginValue(exprs.expressions[0]); |
| 1896 if (v != null) { | 1887 if (v != null) { |
| 1897 final box = new BoxEdge.uniform(v); | 1888 final box = new BoxEdge.uniform(v); |
| 1898 return new BorderExpression.boxEdge(exprs.span, box); | 1889 return new BorderExpression.boxEdge(exprs.span, box); |
| 1899 } | 1890 } |
| 1900 break; | 1891 break; |
| 1901 case _paddingPartPadding: | 1892 case _paddingPartPadding: |
| 1902 return new PaddingExpression.boxEdge(exprs.span, | 1893 return new PaddingExpression.boxEdge( |
| 1903 processFourNums(exprs)); | 1894 exprs.span, processFourNums(exprs)); |
| 1904 case _marginPartLeft: | 1895 case _marginPartLeft: |
| 1905 case _marginPartTop: | 1896 case _marginPartTop: |
| 1906 case _marginPartRight: | 1897 case _marginPartRight: |
| 1907 case _marginPartBottom: | 1898 case _marginPartBottom: |
| 1908 case _borderPartLeft: | 1899 case _borderPartLeft: |
| 1909 case _borderPartTop: | 1900 case _borderPartTop: |
| 1910 case _borderPartRight: | 1901 case _borderPartRight: |
| 1911 case _borderPartBottom: | 1902 case _borderPartBottom: |
| 1912 case _borderPartLeftWidth: | 1903 case _borderPartLeftWidth: |
| 1913 case _borderPartTopWidth: | 1904 case _borderPartTopWidth: |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1993 top = marginValue(exprs.expressions[0]); | 1984 top = marginValue(exprs.expressions[0]); |
| 1994 right = top; | 1985 right = top; |
| 1995 bottom = top; | 1986 bottom = top; |
| 1996 left = top; | 1987 left = top; |
| 1997 break; | 1988 break; |
| 1998 case 2: | 1989 case 2: |
| 1999 top = marginValue(exprs.expressions[0]); | 1990 top = marginValue(exprs.expressions[0]); |
| 2000 bottom = top; | 1991 bottom = top; |
| 2001 right = marginValue(exprs.expressions[1]); | 1992 right = marginValue(exprs.expressions[1]); |
| 2002 left = right; | 1993 left = right; |
| 2003 break; | 1994 break; |
| 2004 case 3: | 1995 case 3: |
| 2005 top = marginValue(exprs.expressions[0]); | 1996 top = marginValue(exprs.expressions[0]); |
| 2006 right = marginValue(exprs.expressions[1]); | 1997 right = marginValue(exprs.expressions[1]); |
| 2007 left = right; | 1998 left = right; |
| 2008 bottom = marginValue(exprs.expressions[2]); | 1999 bottom = marginValue(exprs.expressions[2]); |
| 2009 break; | 2000 break; |
| 2010 case 4: | 2001 case 4: |
| 2011 top = marginValue(exprs.expressions[0]); | 2002 top = marginValue(exprs.expressions[0]); |
| 2012 right = marginValue(exprs.expressions[1]); | 2003 right = marginValue(exprs.expressions[1]); |
| 2013 bottom = marginValue(exprs.expressions[2]); | 2004 bottom = marginValue(exprs.expressions[2]); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 2039 var expressions = new Expressions(_makeSpan(start)); | 2030 var expressions = new Expressions(_makeSpan(start)); |
| 2040 | 2031 |
| 2041 var keepGoing = true; | 2032 var keepGoing = true; |
| 2042 var expr; | 2033 var expr; |
| 2043 while (keepGoing && (expr = processTerm(ieFilter)) != null) { | 2034 while (keepGoing && (expr = processTerm(ieFilter)) != null) { |
| 2044 var op; | 2035 var op; |
| 2045 | 2036 |
| 2046 var opStart = _peekToken.span; | 2037 var opStart = _peekToken.span; |
| 2047 | 2038 |
| 2048 switch (_peek()) { | 2039 switch (_peek()) { |
| 2049 case TokenKind.SLASH: | 2040 case TokenKind.SLASH: |
| 2050 op = new OperatorSlash(_makeSpan(opStart)); | 2041 op = new OperatorSlash(_makeSpan(opStart)); |
| 2051 break; | 2042 break; |
| 2052 case TokenKind.COMMA: | 2043 case TokenKind.COMMA: |
| 2053 op = new OperatorComma(_makeSpan(opStart)); | 2044 op = new OperatorComma(_makeSpan(opStart)); |
| 2054 break; | 2045 break; |
| 2055 case TokenKind.BACKSLASH: | 2046 case TokenKind.BACKSLASH: |
| 2056 // Backslash outside of string; detected IE8 or older signaled by \9 at | 2047 // Backslash outside of string; detected IE8 or older signaled by \9 a
t |
| 2057 // end of an expression. | 2048 // end of an expression. |
| 2058 var ie8Start = _peekToken.span; | 2049 var ie8Start = _peekToken.span; |
| 2059 | 2050 |
| 2060 _next(); | 2051 _next(); |
| 2061 if (_peekKind(TokenKind.INTEGER)) { | 2052 if (_peekKind(TokenKind.INTEGER)) { |
| 2062 var numToken = _next(); | 2053 var numToken = _next(); |
| 2063 var value = int.parse(numToken.text); | 2054 var value = int.parse(numToken.text); |
| 2064 if (value == 9) { | 2055 if (value == 9) { |
| 2065 op = new IE8Term(_makeSpan(ie8Start)); | 2056 op = new IE8Term(_makeSpan(ie8Start)); |
| 2066 } else if (isChecked) { | 2057 } else if (isChecked) { |
| 2067 _warning("\$value is not valid in an expression", _makeSpan(start)); | 2058 _warning( |
| 2059 "\$value is not valid in an expression", _makeSpan(start)); |
| 2060 } |
| 2068 } | 2061 } |
| 2069 } | 2062 break; |
| 2070 break; | |
| 2071 } | 2063 } |
| 2072 | 2064 |
| 2073 if (expr != null) { | 2065 if (expr != null) { |
| 2074 if (expr is List) { | 2066 if (expr is List) { |
| 2075 expr.forEach((exprItem) { | 2067 expr.forEach((exprItem) { |
| 2076 expressions.add(exprItem); | 2068 expressions.add(exprItem); |
| 2077 }); | 2069 }); |
| 2078 } else { | 2070 } else { |
| 2079 expressions.add(expr); | 2071 expressions.add(expr); |
| 2080 } | 2072 } |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2113 // LENGTH: {num}['px' | 'cm' | 'mm' | 'in' | 'pt' | 'pc'] | 2105 // LENGTH: {num}['px' | 'cm' | 'mm' | 'in' | 'pt' | 'pc'] |
| 2114 // EMS: {num}'em' | 2106 // EMS: {num}'em' |
| 2115 // EXS: {num}'ex' | 2107 // EXS: {num}'ex' |
| 2116 // ANGLE: {num}['deg' | 'rad' | 'grad'] | 2108 // ANGLE: {num}['deg' | 'rad' | 'grad'] |
| 2117 // TIME: {num}['ms' | 's'] | 2109 // TIME: {num}['ms' | 's'] |
| 2118 // FREQ: {num}['hz' | 'khz'] | 2110 // FREQ: {num}['hz' | 'khz'] |
| 2119 // function: IDENT '(' expr ')' | 2111 // function: IDENT '(' expr ')' |
| 2120 // | 2112 // |
| 2121 processTerm([bool ieFilter = false]) { | 2113 processTerm([bool ieFilter = false]) { |
| 2122 var start = _peekToken.span; | 2114 var start = _peekToken.span; |
| 2123 Token t; // token for term's value | 2115 Token t; // token for term's value |
| 2124 var value; // value of term (numeric values) | 2116 var value; // value of term (numeric values) |
| 2125 | 2117 |
| 2126 var unary = ""; | 2118 var unary = ""; |
| 2127 switch (_peek()) { | 2119 switch (_peek()) { |
| 2128 case TokenKind.HASH: | 2120 case TokenKind.HASH: |
| 2129 this._eat(TokenKind.HASH); | 2121 this._eat(TokenKind.HASH); |
| 2130 if (!_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { | 2122 if (!_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { |
| 2131 String hexText; | 2123 String hexText; |
| 2132 if (_peekKind(TokenKind.INTEGER)) { | 2124 if (_peekKind(TokenKind.INTEGER)) { |
| 2133 String hexText1 = _peekToken.text; | 2125 String hexText1 = _peekToken.text; |
| 2134 _next(); | 2126 _next(); |
| 2135 if (_peekIdentifier()) { | 2127 if (_peekIdentifier()) { |
| 2136 hexText = '$hexText1${identifier().name}'; | 2128 hexText = '$hexText1${identifier().name}'; |
| 2129 } else { |
| 2130 hexText = hexText1; |
| 2131 } |
| 2132 } else if (_peekIdentifier()) { |
| 2133 hexText = identifier().name; |
| 2134 } |
| 2135 if (hexText != null) { |
| 2136 return _parseHex(hexText, _makeSpan(start)); |
| 2137 } |
| 2138 } |
| 2139 |
| 2140 if (isChecked) { |
| 2141 _warning("Expected hex number", _makeSpan(start)); |
| 2142 } |
| 2143 // Construct the bad hex value with a #<space>number. |
| 2144 return _parseHex(" ${processTerm().text}", _makeSpan(start)); |
| 2145 case TokenKind.INTEGER: |
| 2146 t = _next(); |
| 2147 value = int.parse("${unary}${t.text}"); |
| 2148 break; |
| 2149 case TokenKind.DOUBLE: |
| 2150 t = _next(); |
| 2151 value = double.parse("${unary}${t.text}"); |
| 2152 break; |
| 2153 case TokenKind.SINGLE_QUOTE: |
| 2154 value = processQuotedString(false); |
| 2155 value = "'${_escapeString(value, single: true)}'"; |
| 2156 return new LiteralTerm(value, value, _makeSpan(start)); |
| 2157 case TokenKind.DOUBLE_QUOTE: |
| 2158 value = processQuotedString(false); |
| 2159 value = '"${_escapeString(value)}"'; |
| 2160 return new LiteralTerm(value, value, _makeSpan(start)); |
| 2161 case TokenKind.LPAREN: |
| 2162 _next(); |
| 2163 |
| 2164 GroupTerm group = new GroupTerm(_makeSpan(start)); |
| 2165 |
| 2166 var term; |
| 2167 do { |
| 2168 term = processTerm(); |
| 2169 if (term != null && term is LiteralTerm) { |
| 2170 group.add(term); |
| 2171 } |
| 2172 } while (term != null && |
| 2173 !_maybeEat(TokenKind.RPAREN) && |
| 2174 !isPrematureEndOfFile()); |
| 2175 |
| 2176 return group; |
| 2177 case TokenKind.LBRACK: |
| 2178 _next(); |
| 2179 |
| 2180 var term = processTerm(); |
| 2181 if (!(term is NumberTerm)) { |
| 2182 _error('Expecting a positive number', _makeSpan(start)); |
| 2183 } |
| 2184 |
| 2185 _eat(TokenKind.RBRACK); |
| 2186 |
| 2187 return new ItemTerm(term.value, term.text, _makeSpan(start)); |
| 2188 case TokenKind.IDENTIFIER: |
| 2189 var nameValue = identifier(); // Snarf up the ident we'll remap, maybe. |
| 2190 |
| 2191 if (!ieFilter && _maybeEat(TokenKind.LPAREN)) { |
| 2192 // FUNCTION |
| 2193 return processFunction(nameValue); |
| 2194 } |
| 2195 if (ieFilter) { |
| 2196 if (_maybeEat(TokenKind.COLON) && |
| 2197 nameValue.name.toLowerCase() == 'progid') { |
| 2198 // IE filter:progid: |
| 2199 return processIEFilter(start); |
| 2137 } else { | 2200 } else { |
| 2138 hexText = hexText1; | 2201 // Handle filter:<name> where name is any filter e.g., alpha, chroma
, |
| 2202 // Wave, blur, etc. |
| 2203 return processIEFilter(start); |
| 2139 } | 2204 } |
| 2140 } else if (_peekIdentifier()) { | |
| 2141 hexText = identifier().name; | |
| 2142 } | 2205 } |
| 2143 if (hexText != null) { | 2206 |
| 2144 return _parseHex(hexText, _makeSpan(start)); | 2207 // TODO(terry): Need to have a list of known identifiers today only |
| 2208 // 'from' is special. |
| 2209 if (nameValue.name == 'from') { |
| 2210 return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start)); |
| 2145 } | 2211 } |
| 2146 } | |
| 2147 | 2212 |
| 2148 if (isChecked) { | 2213 // What kind of identifier is it, named color? |
| 2149 _warning("Expected hex number", _makeSpan(start)); | 2214 var colorEntry = TokenKind.matchColorName(nameValue.name); |
| 2150 } | 2215 if (colorEntry == null) { |
| 2151 // Construct the bad hex value with a #<space>number. | 2216 if (isChecked) { |
| 2152 return _parseHex(" ${processTerm().text}", _makeSpan(start)); | 2217 var propName = nameValue.name; |
| 2153 case TokenKind.INTEGER: | 2218 var errMsg = TokenKind.isPredefinedName(propName) |
| 2154 t = _next(); | 2219 ? "Improper use of property value ${propName}" |
| 2155 value = int.parse("${unary}${t.text}"); | 2220 : "Unknown property value ${propName}"; |
| 2156 break; | 2221 _warning(errMsg, _makeSpan(start)); |
| 2157 case TokenKind.DOUBLE: | 2222 } |
| 2158 t = _next(); | 2223 return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start)); |
| 2159 value = double.parse("${unary}${t.text}"); | 2224 } |
| 2160 break; | |
| 2161 case TokenKind.SINGLE_QUOTE: | |
| 2162 value = processQuotedString(false); | |
| 2163 value = "'${_escapeString(value, single: true)}'"; | |
| 2164 return new LiteralTerm(value, value, _makeSpan(start)); | |
| 2165 case TokenKind.DOUBLE_QUOTE: | |
| 2166 value = processQuotedString(false); | |
| 2167 value = '"${_escapeString(value)}"'; | |
| 2168 return new LiteralTerm(value, value, _makeSpan(start)); | |
| 2169 case TokenKind.LPAREN: | |
| 2170 _next(); | |
| 2171 | 2225 |
| 2172 GroupTerm group = new GroupTerm(_makeSpan(start)); | 2226 // Yes, process the color as an RGB value. |
| 2173 | 2227 var rgbColor = |
| 2174 var term; | 2228 TokenKind.decimalToHex(TokenKind.colorValue(colorEntry), 6); |
| 2175 do { | 2229 return _parseHex(rgbColor, _makeSpan(start)); |
| 2176 term = processTerm(); | 2230 case TokenKind.UNICODE_RANGE: |
| 2177 if (term != null && term is LiteralTerm) { | 2231 var first; |
| 2178 group.add(term); | 2232 var second; |
| 2179 } | 2233 var firstNumber; |
| 2180 } while (term != null && !_maybeEat(TokenKind.RPAREN) && | 2234 var secondNumber; |
| 2181 !isPrematureEndOfFile()); | 2235 _eat(TokenKind.UNICODE_RANGE, unicodeRange: true); |
| 2182 | 2236 if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { |
| 2183 return group; | 2237 first = _previousToken.text; |
| 2184 case TokenKind.LBRACK: | 2238 firstNumber = int.parse('0x$first'); |
| 2185 _next(); | 2239 if (firstNumber > MAX_UNICODE) { |
| 2186 | 2240 _error("unicode range must be less than 10FFFF", _makeSpan(start)); |
| 2187 var term = processTerm(); | 2241 } |
| 2188 if (!(term is NumberTerm)) { | 2242 if (_maybeEat(TokenKind.MINUS, unicodeRange: true)) { |
| 2189 _error('Expecting a positive number', _makeSpan(start)); | 2243 if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { |
| 2190 } | 2244 second = _previousToken.text; |
| 2191 | 2245 secondNumber = int.parse('0x$second'); |
| 2192 _eat(TokenKind.RBRACK); | 2246 if (secondNumber > MAX_UNICODE) { |
| 2193 | 2247 _error( |
| 2194 return new ItemTerm(term.value, term.text, _makeSpan(start)); | 2248 "unicode range must be less than 10FFFF", _makeSpan(start)); |
| 2195 case TokenKind.IDENTIFIER: | 2249 } |
| 2196 var nameValue = identifier(); // Snarf up the ident we'll remap, maybe. | 2250 if (firstNumber > secondNumber) { |
| 2197 | 2251 _error("unicode first range can not be greater than last", |
| 2198 if (!ieFilter && _maybeEat(TokenKind.LPAREN)) { | 2252 _makeSpan(start)); |
| 2199 // FUNCTION | 2253 } |
| 2200 return processFunction(nameValue); | |
| 2201 } if (ieFilter) { | |
| 2202 if (_maybeEat(TokenKind.COLON) && | |
| 2203 nameValue.name.toLowerCase() == 'progid') { | |
| 2204 // IE filter:progid: | |
| 2205 return processIEFilter(start); | |
| 2206 } else { | |
| 2207 // Handle filter:<name> where name is any filter e.g., alpha, chroma, | |
| 2208 // Wave, blur, etc. | |
| 2209 return processIEFilter(start); | |
| 2210 } | |
| 2211 } | |
| 2212 | |
| 2213 // TODO(terry): Need to have a list of known identifiers today only | |
| 2214 // 'from' is special. | |
| 2215 if (nameValue.name == 'from') { | |
| 2216 return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start)); | |
| 2217 } | |
| 2218 | |
| 2219 // What kind of identifier is it, named color? | |
| 2220 var colorEntry = TokenKind.matchColorName(nameValue.name); | |
| 2221 if (colorEntry == null) { | |
| 2222 if (isChecked) { | |
| 2223 var propName = nameValue.name; | |
| 2224 var errMsg = TokenKind.isPredefinedName(propName) ? | |
| 2225 "Improper use of property value ${propName}" : | |
| 2226 "Unknown property value ${propName}"; | |
| 2227 _warning(errMsg, _makeSpan(start)); | |
| 2228 } | |
| 2229 return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start)); | |
| 2230 } | |
| 2231 | |
| 2232 // Yes, process the color as an RGB value. | |
| 2233 var rgbColor = | |
| 2234 TokenKind.decimalToHex(TokenKind.colorValue(colorEntry), 6); | |
| 2235 return _parseHex(rgbColor, _makeSpan(start)); | |
| 2236 case TokenKind.UNICODE_RANGE: | |
| 2237 var first; | |
| 2238 var second; | |
| 2239 var firstNumber; | |
| 2240 var secondNumber; | |
| 2241 _eat(TokenKind.UNICODE_RANGE, unicodeRange: true); | |
| 2242 if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { | |
| 2243 first = _previousToken.text; | |
| 2244 firstNumber = int.parse('0x$first'); | |
| 2245 if (firstNumber > MAX_UNICODE) { | |
| 2246 _error("unicode range must be less than 10FFFF", _makeSpan(start)); | |
| 2247 } | |
| 2248 if (_maybeEat(TokenKind.MINUS, unicodeRange: true)) { | |
| 2249 if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { | |
| 2250 second = _previousToken.text; | |
| 2251 secondNumber = int.parse('0x$second'); | |
| 2252 if (secondNumber > MAX_UNICODE) { | |
| 2253 _error("unicode range must be less than 10FFFF", | |
| 2254 _makeSpan(start)); | |
| 2255 } | |
| 2256 if (firstNumber > secondNumber) { | |
| 2257 _error("unicode first range can not be greater than last", | |
| 2258 _makeSpan(start)); | |
| 2259 } | 2254 } |
| 2260 } | 2255 } |
| 2261 } | 2256 } else if (_maybeEat(TokenKind.HEX_RANGE, unicodeRange: true)) { |
| 2262 } else if (_maybeEat(TokenKind.HEX_RANGE, unicodeRange: true)) { | 2257 first = _previousToken.text; |
| 2263 first = _previousToken.text; | |
| 2264 } | |
| 2265 | |
| 2266 return new UnicodeRangeTerm(first, second, _makeSpan(start)); | |
| 2267 case TokenKind.AT: | |
| 2268 if (messages.options.lessSupport) { | |
| 2269 _next(); | |
| 2270 | |
| 2271 var expr = processExpr(); | |
| 2272 if (isChecked && expr.expressions.length > 1) { | |
| 2273 _error("only @name for Less syntax", _peekToken.span); | |
| 2274 } | 2258 } |
| 2275 | 2259 |
| 2276 var param = expr.expressions[0]; | 2260 return new UnicodeRangeTerm(first, second, _makeSpan(start)); |
| 2277 var varUsage = new VarUsage(param.text, [], _makeSpan(start)); | 2261 case TokenKind.AT: |
| 2278 expr.expressions[0] = varUsage; | 2262 if (messages.options.lessSupport) { |
| 2279 return expr.expressions; | 2263 _next(); |
| 2280 } | 2264 |
| 2281 break; | 2265 var expr = processExpr(); |
| 2266 if (isChecked && expr.expressions.length > 1) { |
| 2267 _error("only @name for Less syntax", _peekToken.span); |
| 2268 } |
| 2269 |
| 2270 var param = expr.expressions[0]; |
| 2271 var varUsage = new VarUsage(param.text, [], _makeSpan(start)); |
| 2272 expr.expressions[0] = varUsage; |
| 2273 return expr.expressions; |
| 2274 } |
| 2275 break; |
| 2282 } | 2276 } |
| 2283 | 2277 |
| 2284 return processDimension(t, value, _makeSpan(start)); | 2278 return processDimension(t, value, _makeSpan(start)); |
| 2285 } | 2279 } |
| 2286 | 2280 |
| 2287 /** Process all dimension units. */ | 2281 /** Process all dimension units. */ |
| 2288 LiteralTerm processDimension(Token t, var value, SourceSpan span) { | 2282 LiteralTerm processDimension(Token t, var value, SourceSpan span) { |
| 2289 LiteralTerm term; | 2283 LiteralTerm term; |
| 2290 var unitType = this._peek(); | 2284 var unitType = this._peek(); |
| 2291 | 2285 |
| 2292 switch (unitType) { | 2286 switch (unitType) { |
| 2293 case TokenKind.UNIT_EM: | 2287 case TokenKind.UNIT_EM: |
| 2294 term = new EmTerm(value, t.text, span); | 2288 term = new EmTerm(value, t.text, span); |
| 2295 _next(); // Skip the unit | 2289 _next(); // Skip the unit |
| 2296 break; | 2290 break; |
| 2297 case TokenKind.UNIT_EX: | 2291 case TokenKind.UNIT_EX: |
| 2298 term = new ExTerm(value, t.text, span); | 2292 term = new ExTerm(value, t.text, span); |
| 2299 _next(); // Skip the unit | 2293 _next(); // Skip the unit |
| 2300 break; | 2294 break; |
| 2301 case TokenKind.UNIT_LENGTH_PX: | 2295 case TokenKind.UNIT_LENGTH_PX: |
| 2302 case TokenKind.UNIT_LENGTH_CM: | 2296 case TokenKind.UNIT_LENGTH_CM: |
| 2303 case TokenKind.UNIT_LENGTH_MM: | 2297 case TokenKind.UNIT_LENGTH_MM: |
| 2304 case TokenKind.UNIT_LENGTH_IN: | 2298 case TokenKind.UNIT_LENGTH_IN: |
| 2305 case TokenKind.UNIT_LENGTH_PT: | 2299 case TokenKind.UNIT_LENGTH_PT: |
| 2306 case TokenKind.UNIT_LENGTH_PC: | 2300 case TokenKind.UNIT_LENGTH_PC: |
| 2307 term = new LengthTerm(value, t.text, span, unitType); | 2301 term = new LengthTerm(value, t.text, span, unitType); |
| 2308 _next(); // Skip the unit | 2302 _next(); // Skip the unit |
| 2309 break; | 2303 break; |
| 2310 case TokenKind.UNIT_ANGLE_DEG: | 2304 case TokenKind.UNIT_ANGLE_DEG: |
| 2311 case TokenKind.UNIT_ANGLE_RAD: | 2305 case TokenKind.UNIT_ANGLE_RAD: |
| 2312 case TokenKind.UNIT_ANGLE_GRAD: | 2306 case TokenKind.UNIT_ANGLE_GRAD: |
| 2313 case TokenKind.UNIT_ANGLE_TURN: | 2307 case TokenKind.UNIT_ANGLE_TURN: |
| 2314 term = new AngleTerm(value, t.text, span, unitType); | 2308 term = new AngleTerm(value, t.text, span, unitType); |
| 2315 _next(); // Skip the unit | 2309 _next(); // Skip the unit |
| 2316 break; | 2310 break; |
| 2317 case TokenKind.UNIT_TIME_MS: | 2311 case TokenKind.UNIT_TIME_MS: |
| 2318 case TokenKind.UNIT_TIME_S: | 2312 case TokenKind.UNIT_TIME_S: |
| 2319 term = new TimeTerm(value, t.text, span, unitType); | 2313 term = new TimeTerm(value, t.text, span, unitType); |
| 2320 _next(); // Skip the unit | 2314 _next(); // Skip the unit |
| 2321 break; | 2315 break; |
| 2322 case TokenKind.UNIT_FREQ_HZ: | 2316 case TokenKind.UNIT_FREQ_HZ: |
| 2323 case TokenKind.UNIT_FREQ_KHZ: | 2317 case TokenKind.UNIT_FREQ_KHZ: |
| 2324 term = new FreqTerm(value, t.text, span, unitType); | 2318 term = new FreqTerm(value, t.text, span, unitType); |
| 2325 _next(); // Skip the unit | 2319 _next(); // Skip the unit |
| 2326 break; | 2320 break; |
| 2327 case TokenKind.PERCENT: | 2321 case TokenKind.PERCENT: |
| 2328 term = new PercentageTerm(value, t.text, span); | 2322 term = new PercentageTerm(value, t.text, span); |
| 2329 _next(); // Skip the % | 2323 _next(); // Skip the % |
| 2330 break; | 2324 break; |
| 2331 case TokenKind.UNIT_FRACTION: | 2325 case TokenKind.UNIT_FRACTION: |
| 2332 term = new FractionTerm(value, t.text, span); | 2326 term = new FractionTerm(value, t.text, span); |
| 2333 _next(); // Skip the unit | 2327 _next(); // Skip the unit |
| 2334 break; | 2328 break; |
| 2335 case TokenKind.UNIT_RESOLUTION_DPI: | 2329 case TokenKind.UNIT_RESOLUTION_DPI: |
| 2336 case TokenKind.UNIT_RESOLUTION_DPCM: | 2330 case TokenKind.UNIT_RESOLUTION_DPCM: |
| 2337 case TokenKind.UNIT_RESOLUTION_DPPX: | 2331 case TokenKind.UNIT_RESOLUTION_DPPX: |
| 2338 term = new ResolutionTerm(value, t.text, span, unitType); | 2332 term = new ResolutionTerm(value, t.text, span, unitType); |
| 2339 _next(); // Skip the unit | 2333 _next(); // Skip the unit |
| 2340 break; | 2334 break; |
| 2341 case TokenKind.UNIT_CH: | 2335 case TokenKind.UNIT_CH: |
| 2342 term = new ChTerm(value, t.text, span, unitType); | 2336 term = new ChTerm(value, t.text, span, unitType); |
| 2343 _next(); // Skip the unit | 2337 _next(); // Skip the unit |
| 2344 break; | 2338 break; |
| 2345 case TokenKind.UNIT_REM: | 2339 case TokenKind.UNIT_REM: |
| 2346 term = new RemTerm(value, t.text, span, unitType); | 2340 term = new RemTerm(value, t.text, span, unitType); |
| 2347 _next(); // Skip the unit | 2341 _next(); // Skip the unit |
| 2348 break; | 2342 break; |
| 2349 case TokenKind.UNIT_VIEWPORT_VW: | 2343 case TokenKind.UNIT_VIEWPORT_VW: |
| 2350 case TokenKind.UNIT_VIEWPORT_VH: | 2344 case TokenKind.UNIT_VIEWPORT_VH: |
| 2351 case TokenKind.UNIT_VIEWPORT_VMIN: | 2345 case TokenKind.UNIT_VIEWPORT_VMIN: |
| 2352 case TokenKind.UNIT_VIEWPORT_VMAX: | 2346 case TokenKind.UNIT_VIEWPORT_VMAX: |
| 2353 term = new ViewportTerm(value, t.text, span, unitType); | 2347 term = new ViewportTerm(value, t.text, span, unitType); |
| 2354 _next(); // Skip the unit | 2348 _next(); // Skip the unit |
| 2355 break; | 2349 break; |
| 2356 default: | 2350 default: |
| 2357 if (value != null && t != null) { | 2351 if (value != null && t != null) { |
| 2358 term = (value is Identifier) | 2352 term = (value is Identifier) |
| 2359 ? new LiteralTerm(value, value.name, span) | 2353 ? new LiteralTerm(value, value.name, span) |
| 2360 : new NumberTerm(value, t.text, span); | 2354 : new NumberTerm(value, t.text, span); |
| 2361 } | 2355 } |
| 2362 break; | 2356 break; |
| 2363 } | 2357 } |
| 2364 | 2358 |
| 2365 return term; | 2359 return term; |
| 2366 } | 2360 } |
| 2367 | 2361 |
| 2368 String processQuotedString([bool urlString = false]) { | 2362 String processQuotedString([bool urlString = false]) { |
| 2369 var start = _peekToken.span; | 2363 var start = _peekToken.span; |
| 2370 | 2364 |
| 2371 // URI term sucks up everything inside of quotes(' or ") or between parens | 2365 // URI term sucks up everything inside of quotes(' or ") or between parens |
| 2372 var stopToken = urlString ? TokenKind.RPAREN : -1; | 2366 var stopToken = urlString ? TokenKind.RPAREN : -1; |
| 2373 | 2367 |
| 2374 // Note: disable skipping whitespace tokens inside a string. | 2368 // Note: disable skipping whitespace tokens inside a string. |
| 2375 // TODO(jmesserly): the layering here feels wrong. | 2369 // TODO(jmesserly): the layering here feels wrong. |
| 2376 var skipWhitespace = tokenizer._skipWhitespace; | 2370 var skipWhitespace = tokenizer._skipWhitespace; |
| 2377 tokenizer._skipWhitespace = false; | 2371 tokenizer._skipWhitespace = false; |
| 2378 | 2372 |
| 2379 switch (_peek()) { | 2373 switch (_peek()) { |
| 2380 case TokenKind.SINGLE_QUOTE: | 2374 case TokenKind.SINGLE_QUOTE: |
| 2381 stopToken = TokenKind.SINGLE_QUOTE; | 2375 stopToken = TokenKind.SINGLE_QUOTE; |
| 2382 _next(); // Skip the SINGLE_QUOTE. | 2376 _next(); // Skip the SINGLE_QUOTE. |
| 2383 start = _peekToken.span; | 2377 start = _peekToken.span; |
| 2384 break; | 2378 break; |
| 2385 case TokenKind.DOUBLE_QUOTE: | 2379 case TokenKind.DOUBLE_QUOTE: |
| 2386 stopToken = TokenKind.DOUBLE_QUOTE; | 2380 stopToken = TokenKind.DOUBLE_QUOTE; |
| 2387 _next(); // Skip the DOUBLE_QUOTE. | 2381 _next(); // Skip the DOUBLE_QUOTE. |
| 2388 start = _peekToken.span; | 2382 start = _peekToken.span; |
| 2389 break; | 2383 break; |
| 2390 default: | 2384 default: |
| 2391 if (urlString) { | 2385 if (urlString) { |
| 2392 if (_peek() == TokenKind.LPAREN) { | 2386 if (_peek() == TokenKind.LPAREN) { |
| 2393 _next(); // Skip the LPAREN. | 2387 _next(); // Skip the LPAREN. |
| 2394 start = _peekToken.span; | 2388 start = _peekToken.span; |
| 2389 } |
| 2390 stopToken = TokenKind.RPAREN; |
| 2391 } else { |
| 2392 _error('unexpected string', _makeSpan(start)); |
| 2395 } | 2393 } |
| 2396 stopToken = TokenKind.RPAREN; | 2394 break; |
| 2397 } else { | |
| 2398 _error('unexpected string', _makeSpan(start)); | |
| 2399 } | |
| 2400 break; | |
| 2401 } | 2395 } |
| 2402 | 2396 |
| 2403 // Gobble up everything until we hit our stop token. | 2397 // Gobble up everything until we hit our stop token. |
| 2404 var stringValue = new StringBuffer(); | 2398 var stringValue = new StringBuffer(); |
| 2405 while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) { | 2399 while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) { |
| 2406 stringValue.write(_next().text); | 2400 stringValue.write(_next().text); |
| 2407 } | 2401 } |
| 2408 | 2402 |
| 2409 tokenizer._skipWhitespace = skipWhitespace; | 2403 tokenizer._skipWhitespace = skipWhitespace; |
| 2410 | 2404 |
| 2411 // All characters between quotes is the string. | 2405 // All characters between quotes is the string. |
| 2412 if (stopToken != TokenKind.RPAREN) { | 2406 if (stopToken != TokenKind.RPAREN) { |
| 2413 _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE; | 2407 _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE; |
| 2414 } | 2408 } |
| 2415 | 2409 |
| 2416 return stringValue.toString(); | 2410 return stringValue.toString(); |
| 2417 } | 2411 } |
| 2418 | 2412 |
| 2419 // TODO(terry): Should probably understand IE's non-standard filter syntax to | 2413 // TODO(terry): Should probably understand IE's non-standard filter syntax to |
| 2420 // fully support calc, var(), etc. | 2414 // fully support calc, var(), etc. |
| 2421 /** | 2415 /** |
| 2422 * IE's filter property breaks CSS value parsing. IE's format can be: | 2416 * IE's filter property breaks CSS value parsing. IE's format can be: |
| 2423 * | 2417 * |
| 2424 * filter: progid:DXImageTransform.MS.gradient(Type=0, Color='#9d8b83'); | 2418 * filter: progid:DXImageTransform.MS.gradient(Type=0, Color='#9d8b83'); |
| 2425 * | 2419 * |
| 2426 * We'll just parse everything after the 'progid:' look for the left paren | 2420 * We'll just parse everything after the 'progid:' look for the left paren |
| 2427 * then parse to the right paren ignoring everything in between. | 2421 * then parse to the right paren ignoring everything in between. |
| 2428 */ | 2422 */ |
| 2429 processIEFilter(FileSpan startAfterProgidColon) { | 2423 processIEFilter(FileSpan startAfterProgidColon) { |
| 2430 var parens = 0; | 2424 var parens = 0; |
| 2431 | 2425 |
| 2432 while (_peek() != TokenKind.END_OF_FILE) { | 2426 while (_peek() != TokenKind.END_OF_FILE) { |
| 2433 switch (_peek()) { | 2427 switch (_peek()) { |
| 2434 case TokenKind.LPAREN: | 2428 case TokenKind.LPAREN: |
| 2435 _eat(TokenKind.LPAREN); | 2429 _eat(TokenKind.LPAREN); |
| 2436 parens++; | 2430 parens++; |
| 2437 break; | 2431 break; |
| 2438 case TokenKind.RPAREN: | 2432 case TokenKind.RPAREN: |
| 2439 _eat(TokenKind.RPAREN); | 2433 _eat(TokenKind.RPAREN); |
| 2440 if (--parens == 0) { | 2434 if (--parens == 0) { |
| 2441 var tok = tokenizer.makeIEFilter(startAfterProgidColon.start.offset, | 2435 var tok = tokenizer.makeIEFilter( |
| 2442 _peekToken.start); | 2436 startAfterProgidColon.start.offset, _peekToken.start); |
| 2443 return new LiteralTerm(tok.text, tok.text, tok.span); | 2437 return new LiteralTerm(tok.text, tok.text, tok.span); |
| 2444 } | 2438 } |
| 2445 break; | 2439 break; |
| 2446 default: | 2440 default: |
| 2447 _eat(_peek()); | 2441 _eat(_peek()); |
| 2448 } | 2442 } |
| 2449 } | 2443 } |
| 2450 } | 2444 } |
| 2451 | 2445 |
| 2452 // Function grammar: | 2446 // Function grammar: |
| 2453 // | 2447 // |
| 2454 // function: IDENT '(' expr ')' | 2448 // function: IDENT '(' expr ')' |
| 2455 // | 2449 // |
| 2456 processFunction(Identifier func) { | 2450 processFunction(Identifier func) { |
| 2457 var start = _peekToken.span; | 2451 var start = _peekToken.span; |
| 2458 | 2452 |
| 2459 var name = func.name; | 2453 var name = func.name; |
| 2460 | 2454 |
| 2461 switch (name) { | 2455 switch (name) { |
| 2462 case 'url': | 2456 case 'url': |
| 2463 // URI term sucks up everything inside of quotes(' or ") or between parens | 2457 // URI term sucks up everything inside of quotes(' or ") or between pare
ns |
| 2464 var urlParam = processQuotedString(true); | 2458 var urlParam = processQuotedString(true); |
| 2465 | 2459 |
| 2466 // TODO(terry): Better error messge and checking for mismatched quotes. | 2460 // TODO(terry): Better error messge and checking for mismatched quotes. |
| 2467 if (_peek() == TokenKind.END_OF_FILE) { | 2461 if (_peek() == TokenKind.END_OF_FILE) { |
| 2468 _error("problem parsing URI", _peekToken.span); | 2462 _error("problem parsing URI", _peekToken.span); |
| 2469 } | 2463 } |
| 2470 | 2464 |
| 2471 if (_peek() == TokenKind.RPAREN) { | 2465 if (_peek() == TokenKind.RPAREN) { |
| 2472 _next(); | 2466 _next(); |
| 2473 } | 2467 } |
| 2474 | 2468 |
| 2475 return new UriTerm(urlParam, _makeSpan(start)); | 2469 return new UriTerm(urlParam, _makeSpan(start)); |
| 2476 case 'calc': | 2470 case 'calc': |
| 2477 // TODO(terry): Implement expression handling... | 2471 // TODO(terry): Implement expression handling... |
| 2478 break; | 2472 break; |
| 2479 case 'var': | 2473 case 'var': |
| 2480 // TODO(terry): Consider handling var in IE specific filter/progid. This | 2474 // TODO(terry): Consider handling var in IE specific filter/progid. Thi
s |
| 2481 // will require parsing entire IE specific syntax e.g., | 2475 // will require parsing entire IE specific syntax e.g., |
| 2482 // param = value or progid:com_id, etc. for example: | 2476 // param = value or progid:com_id, etc. for example: |
| 2483 // | 2477 // |
| 2484 // var-blur: Blur(Add = 0, Direction = 225, Strength = 10); | 2478 // var-blur: Blur(Add = 0, Direction = 225, Strength = 10); |
| 2485 // var-gradient: progid:DXImageTransform.Microsoft.gradient" | 2479 // var-gradient: progid:DXImageTransform.Microsoft.gradient" |
| 2486 // (GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); | 2480 // (GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); |
| 2487 var expr = processExpr(); | 2481 var expr = processExpr(); |
| 2488 if (!_maybeEat(TokenKind.RPAREN)) { | 2482 if (!_maybeEat(TokenKind.RPAREN)) { |
| 2489 _error("problem parsing var expected ), ", _peekToken.span); | 2483 _error("problem parsing var expected ), ", _peekToken.span); |
| 2490 } | 2484 } |
| 2491 if (isChecked && | 2485 if (isChecked && |
| 2492 expr.expressions.where((e) => e is OperatorComma).length > 1) { | 2486 expr.expressions.where((e) => e is OperatorComma).length > 1) { |
| 2493 _error("too many parameters to var()", _peekToken.span); | 2487 _error("too many parameters to var()", _peekToken.span); |
| 2494 } | 2488 } |
| 2495 | 2489 |
| 2496 var paramName = expr.expressions[0].text; | 2490 var paramName = expr.expressions[0].text; |
| 2497 | 2491 |
| 2498 // [0] - var name, [1] - OperatorComma, [2] - default value. | 2492 // [0] - var name, [1] - OperatorComma, [2] - default value. |
| 2499 var defaultValues = expr.expressions.length >= 3 | 2493 var defaultValues = |
| 2500 ? expr.expressions.sublist(2) : []; | 2494 expr.expressions.length >= 3 ? expr.expressions.sublist(2) : []; |
| 2501 return new VarUsage(paramName, defaultValues, _makeSpan(start)); | 2495 return new VarUsage(paramName, defaultValues, _makeSpan(start)); |
| 2502 default: | 2496 default: |
| 2503 var expr = processExpr(); | 2497 var expr = processExpr(); |
| 2504 if (!_maybeEat(TokenKind.RPAREN)) { | 2498 if (!_maybeEat(TokenKind.RPAREN)) { |
| 2505 _error("problem parsing function expected ), ", _peekToken.span); | 2499 _error("problem parsing function expected ), ", _peekToken.span); |
| 2506 } | 2500 } |
| 2507 | 2501 |
| 2508 return new FunctionTerm(name, name, expr, _makeSpan(start)); | 2502 return new FunctionTerm(name, name, expr, _makeSpan(start)); |
| 2509 } | 2503 } |
| 2510 | 2504 |
| 2511 return null; | 2505 return null; |
| 2512 } | 2506 } |
| 2513 | 2507 |
| 2514 Identifier identifier() { | 2508 Identifier identifier() { |
| 2515 var tok = _next(); | 2509 var tok = _next(); |
| 2516 | 2510 |
| 2517 if (!TokenKind.isIdentifier(tok.kind) && | 2511 if (!TokenKind.isIdentifier(tok.kind) && |
| 2518 !TokenKind.isKindIdentifier(tok.kind)) { | 2512 !TokenKind.isKindIdentifier(tok.kind)) { |
| 2519 if (isChecked) { | 2513 if (isChecked) { |
| 2520 _warning('expected identifier, but found $tok', tok.span); | 2514 _warning('expected identifier, but found $tok', tok.span); |
| 2521 } | 2515 } |
| 2522 return new Identifier("", _makeSpan(tok.span)); | 2516 return new Identifier("", _makeSpan(tok.span)); |
| 2523 } | 2517 } |
| 2524 | 2518 |
| 2525 return new Identifier(tok.text, _makeSpan(tok.span)); | 2519 return new Identifier(tok.text, _makeSpan(tok.span)); |
| 2526 } | 2520 } |
| 2527 | 2521 |
| 2528 // TODO(terry): Move this to base <= 36 and into shared code. | 2522 // TODO(terry): Move this to base <= 36 and into shared code. |
| 2529 static int _hexDigit(int c) { | 2523 static int _hexDigit(int c) { |
| 2530 if (c >= 48/*0*/ && c <= 57/*9*/) { | 2524 if (c >= 48 /*0*/ && c <= 57 /*9*/) { |
| 2531 return c - 48; | 2525 return c - 48; |
| 2532 } else if (c >= 97/*a*/ && c <= 102/*f*/) { | 2526 } else if (c >= 97 /*a*/ && c <= 102 /*f*/) { |
| 2533 return c - 87; | 2527 return c - 87; |
| 2534 } else if (c >= 65/*A*/ && c <= 70/*F*/) { | 2528 } else if (c >= 65 /*A*/ && c <= 70 /*F*/) { |
| 2535 return c - 55; | 2529 return c - 55; |
| 2536 } else { | 2530 } else { |
| 2537 return -1; | 2531 return -1; |
| 2538 } | 2532 } |
| 2539 } | 2533 } |
| 2540 | 2534 |
| 2541 HexColorTerm _parseHex(String hexText, SourceSpan span) { | 2535 HexColorTerm _parseHex(String hexText, SourceSpan span) { |
| 2542 var hexValue = 0; | 2536 var hexValue = 0; |
| 2543 | 2537 |
| 2544 for (var i = 0; i < hexText.length; i++) { | 2538 for (var i = 0; i < hexText.length; i++) { |
| 2545 var digit = _hexDigit(hexText.codeUnitAt(i)); | 2539 var digit = _hexDigit(hexText.codeUnitAt(i)); |
| 2546 if (digit < 0) { | 2540 if (digit < 0) { |
| 2547 _warning('Bad hex number', span); | 2541 _warning('Bad hex number', span); |
| 2548 return new HexColorTerm(new BAD_HEX_VALUE(), hexText, span); | 2542 return new HexColorTerm(new BAD_HEX_VALUE(), hexText, span); |
| 2549 } | 2543 } |
| 2550 hexValue = (hexValue << 4) + digit; | 2544 hexValue = (hexValue << 4) + digit; |
| 2551 } | 2545 } |
| 2552 | 2546 |
| 2553 // Make 3 character hex value #RRGGBB => #RGB iff: | 2547 // Make 3 character hex value #RRGGBB => #RGB iff: |
| 2554 // high/low nibble of RR is the same, high/low nibble of GG is the same and | 2548 // high/low nibble of RR is the same, high/low nibble of GG is the same and |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2682 * Escapes [text] for use in a CSS string. | 2676 * Escapes [text] for use in a CSS string. |
| 2683 * [single] specifies single quote `'` vs double quote `"`. | 2677 * [single] specifies single quote `'` vs double quote `"`. |
| 2684 */ | 2678 */ |
| 2685 String _escapeString(String text, {bool single: false}) { | 2679 String _escapeString(String text, {bool single: false}) { |
| 2686 StringBuffer result = null; | 2680 StringBuffer result = null; |
| 2687 | 2681 |
| 2688 for (int i = 0; i < text.length; i++) { | 2682 for (int i = 0; i < text.length; i++) { |
| 2689 var code = text.codeUnitAt(i); | 2683 var code = text.codeUnitAt(i); |
| 2690 String replace = null; | 2684 String replace = null; |
| 2691 switch (code) { | 2685 switch (code) { |
| 2692 case 34/*'"'*/: if (!single) replace = r'\"'; break; | 2686 case 34 /*'"'*/ : |
| 2693 case 39/*"'"*/: if (single) replace = r"\'"; break; | 2687 if (!single) replace = r'\"'; |
| 2688 break; |
| 2689 case 39 /*"'"*/ : |
| 2690 if (single) replace = r"\'"; |
| 2691 break; |
| 2694 } | 2692 } |
| 2695 | 2693 |
| 2696 if (replace != null && result == null) { | 2694 if (replace != null && result == null) { |
| 2697 result = new StringBuffer(text.substring(0, i)); | 2695 result = new StringBuffer(text.substring(0, i)); |
| 2698 } | 2696 } |
| 2699 | 2697 |
| 2700 if (result != null) result.write(replace != null ? replace : text[i]); | 2698 if (result != null) result.write(replace != null ? replace : text[i]); |
| 2701 } | 2699 } |
| 2702 | 2700 |
| 2703 return result == null ? text : result.toString(); | 2701 return result == null ? text : result.toString(); |
| 2704 } | 2702 } |
| OLD | NEW |