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 |