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

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

Issue 268623002: [html5lib] implement querySelector/querySelectorAll (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 7 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 | « no previous file | pkg/csslib/lib/src/token.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 12 matching lines...) Expand all
23 23
24 /** Used for parser lookup ahead (used for nested selectors Less support). */ 24 /** Used for parser lookup ahead (used for nested selectors Less support). */
25 class ParserState extends TokenizerState { 25 class ParserState extends TokenizerState {
26 final Token peekToken; 26 final Token peekToken;
27 final Token previousToken; 27 final Token previousToken;
28 28
29 ParserState(this.peekToken, this.previousToken, Tokenizer tokenizer) 29 ParserState(this.peekToken, this.previousToken, Tokenizer tokenizer)
30 : super(tokenizer); 30 : super(tokenizer);
31 } 31 }
32 32
33 // TODO(jmesserly): this should not be global
33 void _createMessages({List<Message> errors, List<String> options}) { 34 void _createMessages({List<Message> errors, List<String> options}) {
34 if (errors == null) errors = []; 35 if (errors == null) errors = [];
35 36
36 if (options == null) { 37 if (options == null) {
37 options = ['--no-colors', 'memory']; 38 options = ['--no-colors', 'memory'];
38 } 39 }
39 var opt = PreprocessorOptions.parse(options); 40 var opt = PreprocessorOptions.parse(options);
40 messages = new Messages(options: opt, printHandler: errors.add); 41 messages = new Messages(options: opt, printHandler: errors.add);
41 } 42 }
42 43
43 /** CSS checked mode enabled. */ 44 /** CSS checked mode enabled. */
44 bool get isChecked => messages.options.checked; 45 bool get isChecked => messages.options.checked;
45 46
46 // TODO(terry): Remove nested name parameter. 47 // TODO(terry): Remove nested name parameter.
47 /** Parse and analyze the CSS file. */ 48 /** Parse and analyze the CSS file. */
48 StyleSheet compile(var input, {List<Message> errors, List<String> options, 49 StyleSheet compile(input, {List<Message> errors, List<String> options,
49 bool nested: true, 50 bool nested: true,
50 bool polyfill: false, 51 bool polyfill: false,
51 List<StyleSheet> includes: null}) { 52 List<StyleSheet> includes: null}) {
52 53
53 if (includes == null) { 54 if (includes == null) {
54 includes = []; 55 includes = [];
55 } 56 }
56 57
57 var source = _inputAsString(input); 58 var source = _inputAsString(input);
58 59
(...skipping 19 matching lines...) Expand all
78 79
79 _createMessages(errors: errors, options: options); 80 _createMessages(errors: errors, options: options);
80 new Analyzer(styleSheets, messages).run(); 81 new Analyzer(styleSheets, messages).run();
81 } 82 }
82 83
83 /** 84 /**
84 * Parse the [input] CSS stylesheet into a tree. The [input] can be a [String], 85 * Parse the [input] CSS stylesheet into a tree. The [input] can be a [String],
85 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional 86 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional
86 * [errors] list will contain each error/warning as a [Message]. 87 * [errors] list will contain each error/warning as a [Message].
87 */ 88 */
88 StyleSheet parse(var input, {List<Message> errors, List<String> options}) { 89 StyleSheet parse(input, {List<Message> errors, List<String> options}) {
89 var source = _inputAsString(input); 90 var source = _inputAsString(input);
90 91
91 _createMessages(errors: errors, options: options); 92 _createMessages(errors: errors, options: options);
92 93
93 var file = new SourceFile.text(null, source); 94 var file = new SourceFile.text(null, source);
94
95 return new _Parser(file, source).parse(); 95 return new _Parser(file, source).parse();
96 } 96 }
97 97
98 /** 98 /**
99 * Parse the [input] CSS selector into a tree. The [input] can be a [String], 99 * Parse the [input] CSS selector into a tree. The [input] can be a [String],
100 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional 100 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional
101 * [errors] list will contain each error/warning as a [Message]. 101 * [errors] list will contain each error/warning as a [Message].
102 */ 102 */
103 StyleSheet selector(var input, {List<Message> errors}) { 103 // TODO(jmesserly): should rename "parseSelector" and return Selector
104 StyleSheet selector(input, {List<Message> errors}) {
104 var source = _inputAsString(input); 105 var source = _inputAsString(input);
105 106
106 _createMessages(errors: errors); 107 _createMessages(errors: errors);
107 108
108 var file = new SourceFile.text(null, source); 109 var file = new SourceFile.text(null, source);
109 110 return (new _Parser(file, source)
110 return new _Parser(file, source).parseSelector(); 111 ..tokenizer.inSelector = true)
112 .parseSelector();
111 } 113 }
112 114
113 String _inputAsString(var input) { 115 SelectorGroup parseSelectorGroup(input, {List<Message> errors}) {
116 var source = _inputAsString(input);
117
118 _createMessages(errors: errors);
119
120 var file = new SourceFile.text(null, source);
121 return (new _Parser(file, source)
122 // 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.
124 ..tokenizer.inSelector = true)
125 .processSelectorGroup();
126 }
127
128 String _inputAsString(input) {
114 String source; 129 String source;
115 130
116 if (input is String) { 131 if (input is String) {
117 source = input; 132 source = input;
118 } else if (input is List<int>) { 133 } else if (input is List<int>) {
119 // TODO(terry): The parse function needs an "encoding" argument and will 134 // TODO(terry): The parse function needs an "encoding" argument and will
120 // default to whatever encoding CSS defaults to. 135 // default to whatever encoding CSS defaults to.
121 // 136 //
122 // Here's some info about CSS encodings: 137 // Here's some info about CSS encodings:
123 // http://www.w3.org/International/questions/qa-css-charset.en.php 138 // http://www.w3.org/International/questions/qa-css-charset.en.php
(...skipping 16 matching lines...) Expand all
140 155
141 return source; 156 return source;
142 } 157 }
143 158
144 // TODO(terry): Consider removing this class when all usages can be eliminated 159 // TODO(terry): Consider removing this class when all usages can be eliminated
145 // or replaced with compile API. 160 // or replaced with compile API.
146 /** Public parsing interface for csslib. */ 161 /** Public parsing interface for csslib. */
147 class Parser { 162 class Parser {
148 final _Parser _parser; 163 final _Parser _parser;
149 164
165 // TODO(jmesserly): having file and text is redundant.
150 Parser(SourceFile file, String text, {int start: 0, String baseUrl}) : 166 Parser(SourceFile file, String text, {int start: 0, String baseUrl}) :
151 _parser = new _Parser(file, text, start: start, baseUrl: baseUrl); 167 _parser = new _Parser(file, text, start: start, baseUrl: baseUrl);
152 168
153 StyleSheet parse() => _parser.parse(); 169 StyleSheet parse() => _parser.parse();
154 } 170 }
155 171
156 /** A simple recursive descent parser for CSS. */ 172 /** A simple recursive descent parser for CSS. */
157 class _Parser { 173 class _Parser {
158 final Tokenizer tokenizer; 174 final Tokenizer tokenizer;
159 175
(...skipping 1007 matching lines...) Expand 10 before | Expand all | Expand 10 after
1167 if (decls.length > 0) { 1183 if (decls.length > 0) {
1168 groups.add(new DeclarationGroup(decls, _makeSpan(start))); 1184 groups.add(new DeclarationGroup(decls, _makeSpan(start)));
1169 } 1185 }
1170 1186
1171 return groups; 1187 return groups;
1172 } 1188 }
1173 1189
1174 SelectorGroup processSelectorGroup() { 1190 SelectorGroup processSelectorGroup() {
1175 List<Selector> selectors = []; 1191 List<Selector> selectors = [];
1176 int start = _peekToken.start; 1192 int start = _peekToken.start;
1193
1177 do { 1194 do {
1178 Selector selector = processSelector(); 1195 Selector selector = processSelector();
1179 if (selector != null) { 1196 if (selector != null) {
1180 selectors.add(selector); 1197 selectors.add(selector);
1181 } 1198 }
1182 } while (_maybeEat(TokenKind.COMMA)); 1199 } while (_maybeEat(TokenKind.COMMA));
1183 1200
1184 if (selectors.length > 0) { 1201 if (selectors.length > 0) {
1185 return new SelectorGroup(selectors, _makeSpan(start)); 1202 return new SelectorGroup(selectors, _makeSpan(start));
1186 } 1203 }
(...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after
1406 // : or :: and making this a normal selector. For now, 1423 // : or :: and making this a normal selector. For now,
1407 // create an empty pseudoName. 1424 // create an empty pseudoName.
1408 var pseudoName; 1425 var pseudoName;
1409 if (_peekIdentifier()) { 1426 if (_peekIdentifier()) {
1410 pseudoName = identifier(); 1427 pseudoName = identifier();
1411 } else { 1428 } else {
1412 return null; 1429 return null;
1413 } 1430 }
1414 1431
1415 // Functional pseudo? 1432 // Functional pseudo?
1416 if (_maybeEat(TokenKind.LPAREN)) { 1433
1434 if (_peekToken.kind == TokenKind.LPAREN) {
1435
1417 if (!pseudoElement && pseudoName.name.toLowerCase() == 'not') { 1436 if (!pseudoElement && pseudoName.name.toLowerCase() == 'not') {
1437 _eat(TokenKind.LPAREN);
1438
1418 // Negation : ':NOT(' S* negation_arg S* ')' 1439 // Negation : ':NOT(' S* negation_arg S* ')'
1419 var negArg = simpleSelector(); 1440 var negArg = simpleSelector();
1420 1441
1421 _eat(TokenKind.RPAREN); 1442 _eat(TokenKind.RPAREN);
1422 return new NegationSelector(negArg, _makeSpan(start)); 1443 return new NegationSelector(negArg, _makeSpan(start));
1423 } else { 1444 } else {
1445 // Special parsing for expressions in pseudo functions. Minus is used
1446 // as operator not identifier.
1447 // TODO(jmesserly): we need to flip this before we eat the "(" as the
1448 // next token will be fetched when we do that. I think we should try to
1449 // refactor so we don't need this boolean; it seems fragile.
1450 tokenizer.inSelectorExpression = true;
1451 _eat(TokenKind.LPAREN);
1452
1424 // Handle function expression. 1453 // Handle function expression.
1425 var span = _makeSpan(start); 1454 var span = _makeSpan(start);
1426 var expr = processSelectorExpression(); 1455 var expr = processSelectorExpression();
1427 1456
1457 tokenizer.inSelectorExpression = false;
1458
1428 // Used during selector look-a-head if not a SelectorExpression is 1459 // Used during selector look-a-head if not a SelectorExpression is
1429 // bad. 1460 // bad.
1430 if (expr is! SelectorExpression) { 1461 if (expr is! SelectorExpression) {
1431 _errorExpected("CSS expression"); 1462 _errorExpected("CSS expression");
1432 return null; 1463 return null;
1433 } 1464 }
1434 1465
1435 _eat(TokenKind.RPAREN); 1466 _eat(TokenKind.RPAREN);
1436 return (pseudoElement) ? 1467 return (pseudoElement) ?
1437 new PseudoElementFunctionSelector(pseudoName, expr, span) : 1468 new PseudoElementFunctionSelector(pseudoName, expr, span) :
(...skipping 18 matching lines...) Expand all
1456 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ 1487 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
1457 * 1488 *
1458 * num [0-9]+|[0-9]*\.[0-9]+ 1489 * num [0-9]+|[0-9]*\.[0-9]+
1459 * PLUS '+' 1490 * PLUS '+'
1460 * DIMENSION {num}{ident} 1491 * DIMENSION {num}{ident}
1461 * NUMBER {num} 1492 * NUMBER {num}
1462 */ 1493 */
1463 processSelectorExpression() { 1494 processSelectorExpression() {
1464 var start = _peekToken.start; 1495 var start = _peekToken.start;
1465 1496
1466 var expression = new SelectorExpression(_makeSpan(start)); 1497 var expressions = [];
1467 1498
1468 Token termToken; 1499 Token termToken;
1469 var value; 1500 var value;
1470 1501
1471 // Special parsing for expressions in pseudo functions. Minus is used as
1472 // operator not identifier.
1473 tokenizer.selectorExpression = true;
1474
1475 var keepParsing = true; 1502 var keepParsing = true;
1476 while (keepParsing) { 1503 while (keepParsing) {
1477 switch (_peek()) { 1504 switch (_peek()) {
1478 case TokenKind.PLUS: 1505 case TokenKind.PLUS:
1479 start = _peekToken.start; 1506 start = _peekToken.start;
1480 termToken = _next(); 1507 termToken = _next();
1481 expression.add(new OperatorPlus(_makeSpan(start))); 1508 expressions.add(new OperatorPlus(_makeSpan(start)));
1482 break; 1509 break;
1483 case TokenKind.MINUS: 1510 case TokenKind.MINUS:
1484 start = _peekToken.start; 1511 start = _peekToken.start;
1485 termToken = _next(); 1512 termToken = _next();
1486 expression.add(new OperatorMinus(_makeSpan(start))); 1513 expressions.add(new OperatorMinus(_makeSpan(start)));
1487 break; 1514 break;
1488 case TokenKind.INTEGER: 1515 case TokenKind.INTEGER:
1489 termToken = _next(); 1516 termToken = _next();
1490 value = int.parse(termToken.text); 1517 value = int.parse(termToken.text);
1491 break; 1518 break;
1492 case TokenKind.DOUBLE: 1519 case TokenKind.DOUBLE:
1493 termToken = _next(); 1520 termToken = _next();
1494 value = double.parse(termToken.text); 1521 value = double.parse(termToken.text);
1495 break; 1522 break;
1496 case TokenKind.SINGLE_QUOTE: 1523 case TokenKind.SINGLE_QUOTE:
(...skipping 13 matching lines...) Expand all
1510 1537
1511 if (keepParsing && value != null) { 1538 if (keepParsing && value != null) {
1512 var unitTerm; 1539 var unitTerm;
1513 // Don't process the dimension if MINUS or PLUS is next. 1540 // Don't process the dimension if MINUS or PLUS is next.
1514 if (_peek() != TokenKind.MINUS && _peek() != TokenKind.PLUS) { 1541 if (_peek() != TokenKind.MINUS && _peek() != TokenKind.PLUS) {
1515 unitTerm = processDimension(termToken, value, _makeSpan(start)); 1542 unitTerm = processDimension(termToken, value, _makeSpan(start));
1516 } 1543 }
1517 if (unitTerm == null) { 1544 if (unitTerm == null) {
1518 unitTerm = new LiteralTerm(value, value.name, _makeSpan(start)); 1545 unitTerm = new LiteralTerm(value, value.name, _makeSpan(start));
1519 } 1546 }
1520 expression.add(unitTerm); 1547 expressions.add(unitTerm);
1521 1548
1522 value = null; 1549 value = null;
1523 } 1550 }
1524 } 1551 }
1525 1552
1526 tokenizer.selectorExpression = false; 1553 return new SelectorExpression(expressions, _makeSpan(start));
1527
1528 return expression;
1529 } 1554 }
1530 1555
1531 // Attribute grammar: 1556 // Attribute grammar:
1532 // 1557 //
1533 // attributes : 1558 // attributes :
1534 // '[' S* IDENT S* [ ATTRIB_MATCHES S* [ IDENT | STRING ] S* ]? ']' 1559 // '[' S* IDENT S* [ ATTRIB_MATCHES S* [ IDENT | STRING ] S* ]? ']'
1535 // 1560 //
1536 // ATTRIB_MATCHES : 1561 // ATTRIB_MATCHES :
1537 // [ '=' | INCLUDES | DASHMATCH | PREFIXMATCH | SUFFIXMATCH | SUBSTRMATCH ] 1562 // [ '=' | INCLUDES | DASHMATCH | PREFIXMATCH | SUFFIXMATCH | SUBSTRMATCH ]
1538 // 1563 //
(...skipping 797 matching lines...) Expand 10 before | Expand all | Expand 10 after
2336 } 2361 }
2337 2362
2338 return term; 2363 return term;
2339 } 2364 }
2340 2365
2341 String processQuotedString([bool urlString = false]) { 2366 String processQuotedString([bool urlString = false]) {
2342 var start = _peekToken.start; 2367 var start = _peekToken.start;
2343 2368
2344 // URI term sucks up everything inside of quotes(' or ") or between parens 2369 // URI term sucks up everything inside of quotes(' or ") or between parens
2345 var stopToken = urlString ? TokenKind.RPAREN : -1; 2370 var stopToken = urlString ? TokenKind.RPAREN : -1;
2371
2372 // Note: disable skipping whitespace tokens inside a string.
2373 // TODO(jmesserly): the layering here feels wrong.
2374 var skipWhitespace = tokenizer._skipWhitespace;
2375 tokenizer._skipWhitespace = false;
2376
2346 switch (_peek()) { 2377 switch (_peek()) {
2347 case TokenKind.SINGLE_QUOTE: 2378 case TokenKind.SINGLE_QUOTE:
2348 stopToken = TokenKind.SINGLE_QUOTE; 2379 stopToken = TokenKind.SINGLE_QUOTE;
2349 start = _peekToken.start + 1; // Skip the quote might have whitespace. 2380 start = _peekToken.start + 1; // Skip the quote might have whitespace.
2350 _next(); // Skip the SINGLE_QUOTE. 2381 _next(); // Skip the SINGLE_QUOTE.
2351 break; 2382 break;
2352 case TokenKind.DOUBLE_QUOTE: 2383 case TokenKind.DOUBLE_QUOTE:
2353 stopToken = TokenKind.DOUBLE_QUOTE; 2384 stopToken = TokenKind.DOUBLE_QUOTE;
2354 start = _peekToken.start + 1; // Skip the quote might have whitespace. 2385 start = _peekToken.start + 1; // Skip the quote might have whitespace.
2355 _next(); // Skip the DOUBLE_QUOTE. 2386 _next(); // Skip the DOUBLE_QUOTE.
2356 break; 2387 break;
2357 default: 2388 default:
2358 if (urlString) { 2389 if (urlString) {
2359 if (_peek() == TokenKind.LPAREN) { 2390 if (_peek() == TokenKind.LPAREN) {
2360 _next(); // Skip the LPAREN. 2391 _next(); // Skip the LPAREN.
2361 start = _peekToken.start; 2392 start = _peekToken.start;
2362 } 2393 }
2363 stopToken = TokenKind.RPAREN; 2394 stopToken = TokenKind.RPAREN;
2364 } else { 2395 } else {
2365 _error('unexpected string', _makeSpan(start)); 2396 _error('unexpected string', _makeSpan(start));
2366 } 2397 }
2367 break; 2398 break;
2368 } 2399 }
2369 2400
2370 // Gobble up everything until we hit our stop token. 2401 // Gobble up everything until we hit our stop token.
2371 var runningStart = _peekToken.start; 2402 var runningStart = _peekToken.start;
2403
2404 var stringValue = new StringBuffer();
2372 while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) { 2405 while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) {
2373 var tok = _next(); 2406 stringValue.write(_next().text);
2374 } 2407 }
2375 2408
2409 tokenizer._skipWhitespace = skipWhitespace;
2410
2376 // All characters between quotes is the string. 2411 // All characters between quotes is the string.
2377 var end = _peekToken.end;
2378 var stringValue = (_peekToken.span as FileSpan).file.getText(start,
2379 end - 1);
2380
2381 if (stopToken != TokenKind.RPAREN) { 2412 if (stopToken != TokenKind.RPAREN) {
2382 _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE; 2413 _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE;
2383 } 2414 }
2384 2415
2385 return stringValue; 2416 return stringValue.toString();
2386 } 2417 }
2387 2418
2388 // TODO(terry): Should probably understand IE's non-standard filter syntax to 2419 // TODO(terry): Should probably understand IE's non-standard filter syntax to
2389 // fully support calc, var(), etc. 2420 // fully support calc, var(), etc.
2390 /** 2421 /**
2391 * IE's filter property breaks CSS value parsing. IE's format can be: 2422 * IE's filter property breaks CSS value parsing. IE's format can be:
2392 * 2423 *
2393 * filter: progid:DXImageTransform.MS.gradient(Type=0, Color='#9d8b83'); 2424 * filter: progid:DXImageTransform.MS.gradient(Type=0, Color='#9d8b83');
2394 * 2425 *
2395 * We'll just parse everything after the 'progid:' look for the left paren 2426 * We'll just parse everything after the 'progid:' look for the left paren
(...skipping 268 matching lines...) Expand 10 before | Expand all | Expand 10 after
2664 2695
2665 if (replace != null && result == null) { 2696 if (replace != null && result == null) {
2666 result = new StringBuffer(text.substring(0, i)); 2697 result = new StringBuffer(text.substring(0, i));
2667 } 2698 }
2668 2699
2669 if (result != null) result.write(replace != null ? replace : text[i]); 2700 if (result != null) result.write(replace != null ? replace : text[i]);
2670 } 2701 }
2671 2702
2672 return result == null ? text : result.toString(); 2703 return result == null ? text : result.toString();
2673 } 2704 }
OLDNEW
« no previous file with comments | « no previous file | pkg/csslib/lib/src/token.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698