Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(36)

Side by Side Diff: pkg/csslib/lib/parser.dart

Issue 23819036: Support for @mixin, @include and @extend (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: All changes ready to commit Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/csslib/lib/css.dart ('k') | pkg/csslib/lib/src/analyzer.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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_maps/span.dart' show SourceFile, Span, FileSpan; 9 import 'package:source_maps/span.dart' show SourceFile, Span, FileSpan;
10 10
(...skipping 27 matching lines...) Expand all
38 } 38 }
39 var opt = PreprocessorOptions.parse(options); 39 var opt = PreprocessorOptions.parse(options);
40 messages = new Messages(options: opt, printHandler: errors.add); 40 messages = new Messages(options: opt, printHandler: errors.add);
41 } 41 }
42 42
43 /** CSS checked mode enabled. */ 43 /** CSS checked mode enabled. */
44 bool get isChecked => messages.options.checked; 44 bool get isChecked => messages.options.checked;
45 45
46 // TODO(terry): Remove nested name parameter. 46 // TODO(terry): Remove nested name parameter.
47 /** Parse and analyze the CSS file. */ 47 /** Parse and analyze the CSS file. */
48 StyleSheet compile(var input, 48 StyleSheet compile(var input, {List errors, List options, bool nested: true,
49 {List errors, List options, bool nested: true, bool polyfill: false}) { 49 bool polyfill: false, List<StyleSheet> includes: null}) {
50 if (includes == null) {
51 includes = [];
52 }
53
50 var source = _inputAsString(input); 54 var source = _inputAsString(input);
51 55
52 _createMessages(errors: errors, options: options); 56 _createMessages(errors: errors, options: options);
53 57
54 var file = new SourceFile.text(null, source); 58 var file = new SourceFile.text(null, source);
55 59
56 var tree = new Parser(file, source).parse(); 60 var tree = new _Parser(file, source).parse();
57 61
58 analyze([tree], errors: errors, options: options); 62 analyze([tree], errors: errors, options: options);
59 63
60 if (polyfill) { 64 if (polyfill) {
61 var processCss = new PolyFill(messages, true); 65 var processCss = new PolyFill(messages, true);
62 processCss.process(tree); 66 processCss.process(tree, includes: includes);
63 } 67 }
64 68
65 return tree; 69 return tree;
66 } 70 }
67 71
68 /** Analyze the CSS file. */ 72 /** Analyze the CSS file. */
69 void analyze(List<StyleSheet> styleSheets, {List errors, List options}) { 73 void analyze(List<StyleSheet> styleSheets, {List errors, List options}) {
70 _createMessages(errors: errors, options: options); 74 _createMessages(errors: errors, options: options);
71 new Analyzer(styleSheets, messages).run(); 75 new Analyzer(styleSheets, messages).run();
72 } 76 }
73 77
74 /** 78 /**
75 * Parse the [input] CSS stylesheet into a tree. The [input] can be a [String], 79 * Parse the [input] CSS stylesheet into a tree. The [input] can be a [String],
76 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional 80 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional
77 * [errors] list will contain each error/warning as a [Message]. 81 * [errors] list will contain each error/warning as a [Message].
78 */ 82 */
79 StyleSheet parse(var input, {List errors, List options}) { 83 StyleSheet parse(var input, {List errors, List options}) {
80 var source = _inputAsString(input); 84 var source = _inputAsString(input);
81 85
82 _createMessages(errors: errors, options: options); 86 _createMessages(errors: errors, options: options);
83 87
84 var file = new SourceFile.text(null, source); 88 var file = new SourceFile.text(null, source);
85 89
86 return new Parser(file, source).parse(); 90 return new _Parser(file, source).parse();
87 } 91 }
88 92
89 /** 93 /**
90 * Parse the [input] CSS selector into a tree. The [input] can be a [String], 94 * Parse the [input] CSS selector into a tree. The [input] can be a [String],
91 * 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
92 * [errors] list will contain each error/warning as a [Message]. 96 * [errors] list will contain each error/warning as a [Message].
93 */ 97 */
94 StyleSheet selector(var input, {List errors}) { 98 StyleSheet selector(var input, {List errors}) {
95 var source = _inputAsString(input); 99 var source = _inputAsString(input);
96 100
97 _createMessages(errors: errors); 101 _createMessages(errors: errors);
98 102
99 var file = new SourceFile.text(null, source); 103 var file = new SourceFile.text(null, source);
100 104
101 return new Parser(file, source).parseSelector(); 105 return new _Parser(file, source).parseSelector();
102 } 106 }
103 107
104 String _inputAsString(var input) { 108 String _inputAsString(var input) {
105 String source; 109 String source;
106 110
107 if (input is String) { 111 if (input is String) {
108 source = input; 112 source = input;
109 } else if (input is List<int>) { 113 } else if (input is List<int>) {
110 // TODO(terry): The parse function needs an "encoding" argument and will 114 // TODO(terry): The parse function needs an "encoding" argument and will
111 // default to whatever encoding CSS defaults to. 115 // default to whatever encoding CSS defaults to.
(...skipping 13 matching lines...) Expand all
125 } else { 129 } else {
126 // TODO(terry): Support RandomAccessFile using console. 130 // TODO(terry): Support RandomAccessFile using console.
127 throw new ArgumentError("'source' must be a String or " 131 throw new ArgumentError("'source' must be a String or "
128 "List<int> (of bytes). RandomAccessFile not supported from this " 132 "List<int> (of bytes). RandomAccessFile not supported from this "
129 "simple interface"); 133 "simple interface");
130 } 134 }
131 135
132 return source; 136 return source;
133 } 137 }
134 138
139 // TODO(terry): Consider removing this class when all usages can be eliminated
140 // or replaced with compile API.
141 /** Public parsing interface for csslib. */
142 class Parser {
143 final _Parser _parser;
144
145 Parser(SourceFile file, String text, {int start: 0, String baseUrl}) :
146 _parser = new _Parser(file, text, start: start, baseUrl: baseUrl);
147
148 StyleSheet parse() => _parser.parse();
149 }
150
135 /** A simple recursive descent parser for CSS. */ 151 /** A simple recursive descent parser for CSS. */
136 class Parser { 152 class _Parser {
137 Tokenizer tokenizer; 153 Tokenizer tokenizer;
138 154
139 /** Base url of CSS file. */ 155 /** Base url of CSS file. */
140 final String _baseUrl; 156 final String _baseUrl;
141 157
142 /** 158 /**
143 * File containing the source being parsed, used to report errors with 159 * File containing the source being parsed, used to report errors with
144 * source-span locations. 160 * source-span locations.
145 */ 161 */
146 final SourceFile file; 162 final SourceFile file;
147 163
148 Token _previousToken; 164 Token _previousToken;
149 Token _peekToken; 165 Token _peekToken;
150 166
151 Parser(SourceFile file, String text, {int start: 0, String baseUrl}) 167 _Parser(SourceFile file, String text, {int start: 0, String baseUrl})
152 : this.file = file, 168 : this.file = file,
153 _baseUrl = baseUrl, 169 _baseUrl = baseUrl,
154 tokenizer = new Tokenizer(file, text, true, start) { 170 tokenizer = new Tokenizer(file, text, true, start) {
155 _peekToken = tokenizer.next(); 171 _peekToken = tokenizer.next();
156 } 172 }
157 173
158 /** Main entry point for parsing an entire CSS file. */ 174 /** Main entry point for parsing an entire CSS file. */
159 StyleSheet parse() { 175 StyleSheet parse() {
160 List<TreeNode> productions = []; 176 List<TreeNode> productions = [];
161 177
(...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after
422 return null; 438 return null;
423 } 439 }
424 } 440 }
425 } else if (isChecked) { 441 } else if (isChecked) {
426 _warning("Missing media feature in media expression", _makeSpan(start)); 442 _warning("Missing media feature in media expression", _makeSpan(start));
427 return null; 443 return null;
428 } 444 }
429 } 445 }
430 } 446 }
431 447
432 // Directive grammar: 448 /**
433 // 449 * Directive grammar:
434 // import: '@import' [string | URI] media_list? 450 *
435 // media: '@media' media_query_list '{' ruleset '}' 451 * import: '@import' [string | URI] media_list?
436 // page: '@page' [':' IDENT]? '{' declarations '}' 452 * media: '@media' media_query_list '{' ruleset '}'
437 // stylet: '@stylet' IDENT '{' ruleset '}' 453 * page: '@page' [':' IDENT]? '{' declarations '}'
438 // media_query_list: IDENT [',' IDENT] 454 * stylet: '@stylet' IDENT '{' ruleset '}'
439 // keyframes: '@-webkit-keyframes ...' (see grammar below). 455 * media_query_list: IDENT [',' IDENT]
440 // font_face: '@font-face' '{' declarations '}' 456 * keyframes: '@-webkit-keyframes ...' (see grammar below).
441 // namespace: '@namespace name url("xmlns") 457 * font_face: '@font-face' '{' declarations '}'
442 // host: '@host '{' ruleset '}' 458 * namespace: '@namespace name url("xmlns")
459 * host: '@host '{' ruleset '}'
460 * mixin: '@mixin name [(args,...)] '{' declarations/ruleset '}'
461 * include: '@include name [(@arg,@arg1)]
462 * '@include name [(@arg...)]
463 * content '@content'
464 */
443 processDirective() { 465 processDirective() {
444 int start = _peekToken.start; 466 int start = _peekToken.start;
445 467
446 var tokId = _peek(); 468 var tokId = processVariableOrDirective();
447 // Handle case for @ directive (where there's a whitespace between the @ 469 if (tokId is VarDefinitionDirective) return tokId;
448 // sign and the directive name. Technically, it's not valid grammar but
449 // a number of CSS tests test for whitespace between @ and name.
450 if (tokId == TokenKind.AT) {
451 Token tok = _next();
452 tokId = _peek();
453 if (_peekIdentifier()) {
454 // Is it a directive?
455 var directive = _peekToken.text;
456 var directiveLen = directive.length;
457 tokId = TokenKind.matchDirectives(directive, 0, directiveLen);
458 if (tokId == -1) {
459 tokId = TokenKind.matchMarginDirectives(directive, 0, directiveLen);
460 }
461 }
462
463 if (tokId == -1) {
464 if (messages.options.lessSupport) {
465 // Less compatibility:
466 // @name: value; => var-name: value; (VarDefinition)
467 // property: @name; => property: var(name); (VarUsage)
468 var name;
469 if (_peekIdentifier()) {
470 name = identifier();
471 }
472
473 _eat(TokenKind.COLON);
474
475 Expressions exprs = processExpr();
476
477 var span = _makeSpan(start);
478 return new VarDefinitionDirective(
479 new VarDefinition(name, exprs, span), span);
480 } else if (isChecked) {
481 _error('unexpected directive @$_peekToken', _peekToken.span);
482 }
483 }
484 }
485
486 switch (tokId) { 470 switch (tokId) {
487 case TokenKind.DIRECTIVE_IMPORT: 471 case TokenKind.DIRECTIVE_IMPORT:
488 _next(); 472 _next();
489 473
490 // @import "uri_string" or @import url("uri_string") are identical; only 474 // @import "uri_string" or @import url("uri_string") are identical; only
491 // a url can follow an @import. 475 // a url can follow an @import.
492 String importStr; 476 String importStr;
493 if (_peekIdentifier()) { 477 if (_peekIdentifier()) {
494 var func = processFunction(identifier()); 478 var func = processFunction(identifier());
495 if (func is UriTerm) { 479 if (func is UriTerm) {
(...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after
741 namespaceUri = func.text; 725 namespaceUri = func.text;
742 prefix = null; 726 prefix = null;
743 } 727 }
744 } else { 728 } else {
745 namespaceUri = processQuotedString(false); 729 namespaceUri = processQuotedString(false);
746 } 730 }
747 } 731 }
748 732
749 return new NamespaceDirective(prefix != null ? prefix.name : '', 733 return new NamespaceDirective(prefix != null ? prefix.name : '',
750 namespaceUri, _makeSpan(start)); 734 namespaceUri, _makeSpan(start));
751 } 735
736 case TokenKind.DIRECTIVE_MIXIN:
737 return processMixin(start);
738
739 case TokenKind.DIRECTIVE_INCLUDE:
740 return processInclude( _makeSpan(start));
741
742 case TokenKind.DIRECTIVE_CONTENT:
743 // TODO(terry): TBD
744 _warning("@content not implemented.", _makeSpan(start));
745 return;
746 }
747 }
748
749 /**
750 * Parse the mixin beginning token offset [start]. Returns a [MixinDefinition]
751 * node.
752 *
753 * Mixin grammar:
754 *
755 * @mixin IDENT [(args,...)] '{'
756 * [ruleset | property | directive]*
757 * '}'
758 */
759 MixinDefinition processMixin(int start) {
760 _next();
761
762 var name = identifier();
763
764 List<VarDefinitionDirective> params = [];
765 // Any parameters?
766 if (_maybeEat(TokenKind.LPAREN)) {
767 var mustHaveParam = false;
768 var keepGoing = true;
769 while (keepGoing) {
770 var varDef = processVariableOrDirective(mixinParameter: true);
771 if (varDef is VarDefinitionDirective || varDef is VarDefinition) {
772 params.add(varDef);
773 } else if (mustHaveParam) {
774 _warning("Expecting parameter", _makeSpan(_peekToken.start));
775 keepGoing = false;
776 }
777 if (_maybeEat(TokenKind.COMMA)) {
778 mustHaveParam = true;
779 continue;
780 }
781 keepGoing = !_maybeEat(TokenKind.RPAREN);
782 }
783 }
784
785 _eat(TokenKind.LBRACE);
786
787 List<TreeNode> productions = [];
788 List<TreeNode> declarations = [];
789 var mixinDirective;
790
791 start = _peekToken.start;
792 while (!_maybeEat(TokenKind.END_OF_FILE)) {
793 var directive = processDirective();
794 if (directive != null) {
795 productions.add(directive);
796 continue;
797 }
798
799 var declGroup = processDeclarations(checkBrace: false);
800 var decls = [];
801 if (declGroup.declarations.any((decl) {
802 return decl is Declaration &&
803 decl is! IncludeMixinAtDeclaration;
804 })) {
805 var newDecls = [];
806 productions.forEach((include) {
807 // If declGroup has items that are declarations then we assume
808 // this mixin is a declaration mixin not a top-level mixin.
809 if (include is IncludeDirective) {
810 newDecls.add(new IncludeMixinAtDeclaration(include,
811 include.span));
812 } else {
813 _warning("Error mixing of top-level vs declarations mixins",
814 _makeSpan(include));
815 }
816 });
817 declGroup.declarations.insertAll(0, newDecls);
818 productions = [];
819 } else {
820 // Declarations are just @includes make it a list of productions
821 // not a declaration group (anything else is a ruleset). Make it a
822 // list of productions, not a declaration group.
823 for (var decl in declGroup.declarations) {
824 productions.add(decl is IncludeMixinAtDeclaration ?
825 decl.include : decl);
826 };
827 declGroup.declarations.clear();
828 }
829
830 if (declGroup.declarations.isNotEmpty) {
831 if (productions.isEmpty) {
832 mixinDirective = new MixinDeclarationDirective(name.name, params,
833 false, declGroup, _makeSpan(start));
834 break;
835 } else {
836 for (var decl in declGroup.declarations) {
837 productions.add(decl is IncludeMixinAtDeclaration ?
838 decl.include : decl);
839 }
840 }
841 } else {
842 mixinDirective = new MixinRulesetDirective(name.name, params,
843 false, productions, _makeSpan(start));
844 break;
845 }
846 }
847
848 if (productions.isNotEmpty) {
849 mixinDirective = new MixinRulesetDirective(name.name, params,
850 false, productions, _makeSpan(start));
851 }
852
853 _eat(TokenKind.RBRACE);
854
855 return mixinDirective;
856 }
857
858 /**
859 * Returns a VarDefinitionDirective or VarDefinition if a varaible otherwise
860 * return the token id of a directive or -1 if neither.
861 */
862 processVariableOrDirective({bool mixinParameter: false}) {
863 int start = _peekToken.start;
864
865 var tokId = _peek();
866 // Handle case for @ directive (where there's a whitespace between the @
867 // sign and the directive name. Technically, it's not valid grammar but
868 // a number of CSS tests test for whitespace between @ and name.
869 if (tokId == TokenKind.AT) {
870 Token tok = _next();
871 tokId = _peek();
872 if (_peekIdentifier()) {
873 // Is it a directive?
874 var directive = _peekToken.text;
875 var directiveLen = directive.length;
876 tokId = TokenKind.matchDirectives(directive, 0, directiveLen);
877 if (tokId == -1) {
878 tokId = TokenKind.matchMarginDirectives(directive, 0, directiveLen);
879 }
880 }
881
882 if (tokId == -1) {
883 if (messages.options.lessSupport) {
884 // Less compatibility:
885 // @name: value; => var-name: value; (VarDefinition)
886 // property: @name; => property: var(name); (VarUsage)
887 var name;
888 if (_peekIdentifier()) {
889 name = identifier();
890 }
891
892 Expressions exprs;
893 if (mixinParameter && _maybeEat(TokenKind.COLON)) {
894 exprs = processExpr();
895 } else if (!mixinParameter) {
896 _eat(TokenKind.COLON);
897 exprs = processExpr();
898 }
899
900 var span = _makeSpan(start);
901 return new VarDefinitionDirective(
902 new VarDefinition(name, exprs, span), span);
903 } else if (isChecked) {
904 _error('unexpected directive @$_peekToken', _peekToken.span);
905 }
906 }
907 } else if (mixinParameter && _peekToken.kind == TokenKind.VAR_DEFINITION) {
908 _next();
909 var definedName;
910 if (_peekIdentifier()) definedName = identifier();
911
912 Expressions exprs;
913 if (_maybeEat(TokenKind.COLON)) {
914 exprs = processExpr();
915 }
916
917 return new VarDefinition(definedName, exprs, _makeSpan(start));
918 }
919
920 return tokId;
921 }
922
923 IncludeDirective processInclude(Span span, {bool eatSemiColon: true}) {
924 /* Stylet grammar:
925 *
926 * @include IDENT [(args,...)];
927 */
928 _next();
929
930 var name;
931 if (_peekIdentifier()) {
932 name = identifier();
933 }
934
935 var params = [];
936
937 // Any parameters? Parameters can be multiple terms per argument e.g.,
938 // 3px solid yellow, green is two parameters:
939 // 1. 3px solid yellow
940 // 2. green
941 // the first has 3 terms and the second has 1 term.
942 if (_maybeEat(TokenKind.LPAREN)) {
943 var terms = [];
944 var expr;
945 var keepGoing = true;
946 while (keepGoing && (expr = processTerm()) != null) {
947 // VarUsage is returns as a list
948 terms.add(expr is List ? expr[0] : expr);
949 keepGoing = !_peekKind(TokenKind.RPAREN);
950 if (keepGoing) {
951 if (_maybeEat(TokenKind.COMMA)) {
952 params.add(terms);
953 terms = [];
954 }
955 }
956 }
957 params.add(terms);
958 _maybeEat(TokenKind.RPAREN);
959 }
960
961 if (eatSemiColon) {
962 _eat(TokenKind.SEMICOLON);
963 }
964
965 return new IncludeDirective(name.name, params, span);
752 } 966 }
753 967
754 RuleSet processRuleSet([SelectorGroup selectorGroup]) { 968 RuleSet processRuleSet([SelectorGroup selectorGroup]) {
755 if (selectorGroup == null) { 969 if (selectorGroup == null) {
756 selectorGroup = processSelectorGroup(); 970 selectorGroup = processSelectorGroup();
757 } 971 }
758 if (selectorGroup != null) { 972 if (selectorGroup != null) {
759 return new RuleSet(selectorGroup, processDeclarations(), 973 return new RuleSet(selectorGroup, processDeclarations(),
760 selectorGroup.span); 974 selectorGroup.span);
761 } 975 }
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
802 messages = oldMessages; 1016 messages = oldMessages;
803 return null; 1017 return null;
804 } else { 1018 } else {
805 // Remember any messages from look ahead. 1019 // Remember any messages from look ahead.
806 oldMessages.mergeMessages(messages); 1020 oldMessages.mergeMessages(messages);
807 messages = oldMessages; 1021 messages = oldMessages;
808 return selGroup; 1022 return selGroup;
809 } 1023 }
810 } 1024 }
811 1025
812 processDeclarations({bool checkBrace: true}) { 1026 DeclarationGroup processDeclarations({bool checkBrace: true}) {
813 int start = _peekToken.start; 1027 int start = _peekToken.start;
814 1028
815 if (checkBrace) _eat(TokenKind.LBRACE); 1029 if (checkBrace) _eat(TokenKind.LBRACE);
816 1030
817 List decls = []; 1031 List decls = [];
818 List dartStyles = []; // List of latest styles exposed to Dart. 1032 List dartStyles = []; // List of latest styles exposed to Dart.
819 1033
820 do { 1034 do {
821 var selectorGroup = _nestedSelector(); 1035 var selectorGroup = _nestedSelector();
822 while (selectorGroup != null) { 1036 while (selectorGroup != null) {
(...skipping 335 matching lines...) Expand 10 before | Expand all | Expand 10 after
1158 hasWhiteSpace = true; 1372 hasWhiteSpace = true;
1159 } 1373 }
1160 var id = identifier(); 1374 var id = identifier();
1161 if (hasWhiteSpace) { 1375 if (hasWhiteSpace) {
1162 // Generate bad selector class (normalized). 1376 // Generate bad selector class (normalized).
1163 id.name = " ${id.name}"; 1377 id.name = " ${id.name}";
1164 } 1378 }
1165 return new ClassSelector(id, _makeSpan(start)); 1379 return new ClassSelector(id, _makeSpan(start));
1166 case TokenKind.COLON: 1380 case TokenKind.COLON:
1167 // :pseudo-class ::pseudo-element 1381 // :pseudo-class ::pseudo-element
1168 // TODO(terry): '::' should be token. 1382 return processPseudoSelector(start);
1169 _eat(TokenKind.COLON);
1170 bool pseudoElement = _maybeEat(TokenKind.COLON);
1171
1172 // TODO(terry): If no identifier specified consider optimizing out the
1173 // : or :: and making this a normal selector. For now,
1174 // create an empty pseudoName.
1175 var pseudoName;
1176 if (_peekIdentifier()) {
1177 pseudoName = identifier();
1178 } else {
1179 return null;
1180 }
1181
1182 // Functional pseudo?
1183 if (_maybeEat(TokenKind.LPAREN)) {
1184 if (!pseudoElement && pseudoName.name.toLowerCase() == 'not') {
1185 // Negation : ':NOT(' S* negation_arg S* ')'
1186 var negArg = simpleSelector();
1187
1188 _eat(TokenKind.RPAREN);
1189 return new NegationSelector(negArg, _makeSpan(start));
1190 } else {
1191 // Handle function expression.
1192 var span = _makeSpan(start);
1193 var expr = processSelectorExpression();
1194
1195 // Used during selector look-a-head if not a SelectorExpression is
1196 // bad.
1197 if (expr is! SelectorExpression) {
1198 _errorExpected("CSS expression");
1199 return null;
1200 }
1201
1202 _eat(TokenKind.RPAREN);
1203 return (pseudoElement) ?
1204 new PseudoElementFunctionSelector(pseudoName, expr, span) :
1205 new PseudoClassFunctionSelector(pseudoName, expr, span);
1206 }
1207 }
1208
1209 // TODO(terry): Need to handle specific pseudo class/element name and
1210 // backward compatible names that are : as well as :: as well as
1211 // parameters. Current, spec uses :: for pseudo-element and : for
1212 // pseudo-class. However, CSS2.1 allows for : to specify old
1213 // pseudo-elements (:first-line, :first-letter, :before and :after) any
1214 // new pseudo-elements defined would require a ::.
1215 return pseudoElement ?
1216 new PseudoElementSelector(pseudoName, _makeSpan(start)) :
1217 new PseudoClassSelector(pseudoName, _makeSpan(start));
1218 case TokenKind.LBRACK: 1383 case TokenKind.LBRACK:
1219 return processAttribute(); 1384 return processAttribute();
1220 case TokenKind.DOUBLE: 1385 case TokenKind.DOUBLE:
1221 _error('name must start with a alpha character, but found a number', 1386 _error('name must start with a alpha character, but found a number',
1222 _peekToken.span); 1387 _peekToken.span);
1223 _next(); 1388 _next();
1224 break; 1389 break;
1225 } 1390 }
1226 } 1391 }
1227 1392
1393 processPseudoSelector(int start) {
1394 // :pseudo-class ::pseudo-element
1395 // TODO(terry): '::' should be token.
1396 _eat(TokenKind.COLON);
1397 bool pseudoElement = _maybeEat(TokenKind.COLON);
1398
1399 // TODO(terry): If no identifier specified consider optimizing out the
1400 // : or :: and making this a normal selector. For now,
1401 // create an empty pseudoName.
1402 var pseudoName;
1403 if (_peekIdentifier()) {
1404 pseudoName = identifier();
1405 } else {
1406 return null;
1407 }
1408
1409 // Functional pseudo?
1410 if (_maybeEat(TokenKind.LPAREN)) {
1411 if (!pseudoElement && pseudoName.name.toLowerCase() == 'not') {
1412 // Negation : ':NOT(' S* negation_arg S* ')'
1413 var negArg = simpleSelector();
1414
1415 _eat(TokenKind.RPAREN);
1416 return new NegationSelector(negArg, _makeSpan(start));
1417 } else {
1418 // Handle function expression.
1419 var span = _makeSpan(start);
1420 var expr = processSelectorExpression();
1421
1422 // Used during selector look-a-head if not a SelectorExpression is
1423 // bad.
1424 if (expr is! SelectorExpression) {
1425 _errorExpected("CSS expression");
1426 return null;
1427 }
1428
1429 _eat(TokenKind.RPAREN);
1430 return (pseudoElement) ?
1431 new PseudoElementFunctionSelector(pseudoName, expr, span) :
1432 new PseudoClassFunctionSelector(pseudoName, expr, span);
1433 }
1434 }
1435
1436 // TODO(terry): Need to handle specific pseudo class/element name and
1437 // backward compatible names that are : as well as :: as well as
1438 // parameters. Current, spec uses :: for pseudo-element and : for
1439 // pseudo-class. However, CSS2.1 allows for : to specify old
1440 // pseudo-elements (:first-line, :first-letter, :before and :after) any
1441 // new pseudo-elements defined would require a ::.
1442 return pseudoElement ?
1443 new PseudoElementSelector(pseudoName, _makeSpan(start)) :
1444 new PseudoClassSelector(pseudoName, _makeSpan(start));
1445 }
1446
1228 /** 1447 /**
1229 * In CSS3, the expressions are identifiers, strings, or of the form "an+b". 1448 * In CSS3, the expressions are identifiers, strings, or of the form "an+b".
1230 * 1449 *
1231 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ 1450 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
1232 * 1451 *
1233 * num [0-9]+|[0-9]*\.[0-9]+ 1452 * num [0-9]+|[0-9]*\.[0-9]+
1234 * PLUS '+' 1453 * PLUS '+'
1235 * DIMENSION {num}{ident} 1454 * DIMENSION {num}{ident}
1236 * NUMBER {num} 1455 * NUMBER {num}
1237 */ 1456 */
(...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after
1404 } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) { 1623 } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) {
1405 _next(); 1624 _next();
1406 var definedName; 1625 var definedName;
1407 if (_peekIdentifier()) definedName = identifier(); 1626 if (_peekIdentifier()) definedName = identifier();
1408 1627
1409 _eat(TokenKind.COLON); 1628 _eat(TokenKind.COLON);
1410 1629
1411 Expressions exprs = processExpr(); 1630 Expressions exprs = processExpr();
1412 1631
1413 decl = new VarDefinition(definedName, exprs, _makeSpan(start)); 1632 decl = new VarDefinition(definedName, exprs, _makeSpan(start));
1633 } else if (_peekToken.kind == TokenKind.DIRECTIVE_INCLUDE) {
1634 // @include mixinName in the declaration area.
1635 var span = _makeSpan(start);
1636 var include = processInclude(span, eatSemiColon: false);
1637 decl = new IncludeMixinAtDeclaration(include, span);
1638 } else if (_peekToken.kind == TokenKind.DIRECTIVE_EXTEND) {
1639 List<SimpleSelectorSequence> simpleSequences = [];
1640
1641 _next();
1642 var span = _makeSpan(start);
1643 var selector = simpleSelector();
1644 if (selector == null) {
1645 _warning("@extends expecting simple selector name", span);
1646 } else {
1647 simpleSequences.add(selector);
1648 }
1649 if (_peekKind(TokenKind.COLON)) {
1650 var pseudoSelector = processPseudoSelector(_peekToken.start);
1651 if (pseudoSelector is PseudoElementSelector ||
1652 pseudoSelector is PseudoClassSelector) {
1653 simpleSequences.add(pseudoSelector);
1654 } else {
1655 _warning("not a valid selector", span);
1656 }
1657 }
1658 decl = new ExtendDeclaration(simpleSequences, span);
1414 } 1659 }
1415 1660
1416 return decl; 1661 return decl;
1417 } 1662 }
1418 1663
1419 /** List of styles exposed to the Dart UI framework. */ 1664 /** List of styles exposed to the Dart UI framework. */
1420 static const int _fontPartFont= 0; 1665 static const int _fontPartFont= 0;
1421 static const int _fontPartVariant = 1; 1666 static const int _fontPartVariant = 1;
1422 static const int _fontPartWeight = 2; 1667 static const int _fontPartWeight = 2;
1423 static const int _fontPartSize = 3; 1668 static const int _fontPartSize = 3;
(...skipping 360 matching lines...) Expand 10 before | Expand all | Expand 10 after
1784 if (value == 9) { 2029 if (value == 9) {
1785 op = new IE8Term(_makeSpan(ie8Start)); 2030 op = new IE8Term(_makeSpan(ie8Start));
1786 } else if (isChecked) { 2031 } else if (isChecked) {
1787 _warning("\$value is not valid in an expression", _makeSpan(start)); 2032 _warning("\$value is not valid in an expression", _makeSpan(start));
1788 } 2033 }
1789 } 2034 }
1790 break; 2035 break;
1791 } 2036 }
1792 2037
1793 if (expr != null) { 2038 if (expr != null) {
1794 expressions.add(expr); 2039 if (expr is List) {
2040 expr.forEach((exprItem) {
2041 expressions.add(exprItem);
2042 });
2043 } else {
2044 expressions.add(expr);
2045 }
1795 } else { 2046 } else {
1796 keepGoing = false; 2047 keepGoing = false;
1797 } 2048 }
1798 2049
1799 if (op != null) { 2050 if (op != null) {
1800 expressions.add(op); 2051 expressions.add(op);
1801 if (op is IE8Term) { 2052 if (op is IE8Term) {
1802 keepGoing = false; 2053 keepGoing = false;
1803 } else { 2054 } else {
1804 _next(); 2055 _next();
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after
1978 case TokenKind.AT: 2229 case TokenKind.AT:
1979 if (messages.options.lessSupport) { 2230 if (messages.options.lessSupport) {
1980 _next(); 2231 _next();
1981 2232
1982 var expr = processExpr(); 2233 var expr = processExpr();
1983 if (isChecked && expr.expressions.length > 1) { 2234 if (isChecked && expr.expressions.length > 1) {
1984 _error("only @name for Less syntax", _peekToken.span); 2235 _error("only @name for Less syntax", _peekToken.span);
1985 } 2236 }
1986 2237
1987 var param = expr.expressions[0]; 2238 var param = expr.expressions[0];
1988 return new VarUsage(param.text, [], _makeSpan(start)); 2239 var varUsage = new VarUsage(param.text, [], _makeSpan(start));
2240 expr.expressions[0] = varUsage;
2241 return expr.expressions;
1989 } 2242 }
1990 break; 2243 break;
1991 } 2244 }
1992 2245
1993 return processDimension(t, value, _makeSpan(start)); 2246 return processDimension(t, value, _makeSpan(start));
1994 } 2247 }
1995 2248
1996 /** Process all dimension units. */ 2249 /** Process all dimension units. */
1997 processDimension(Token t, var value, Span span) { 2250 processDimension(Token t, var value, Span span) {
1998 var term; 2251 var term;
(...skipping 401 matching lines...) Expand 10 before | Expand all | Expand 10 after
2400 2653
2401 if (replace != null && result == null) { 2654 if (replace != null && result == null) {
2402 result = new StringBuffer(text.substring(0, i)); 2655 result = new StringBuffer(text.substring(0, i));
2403 } 2656 }
2404 2657
2405 if (result != null) result.write(replace != null ? replace : text[i]); 2658 if (result != null) result.write(replace != null ? replace : text[i]);
2406 } 2659 }
2407 2660
2408 return result == null ? text : result.toString(); 2661 return result == null ? text : result.toString();
2409 } 2662 }
OLDNEW
« no previous file with comments | « pkg/csslib/lib/css.dart ('k') | pkg/csslib/lib/src/analyzer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698