Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
| 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. | |
| 4 | |
| 5 /** | |
| 6 * A simple recursive descent parser for CSS. | |
| 7 */ | |
| 8 class Parser { | |
| 9 Tokenizer tokenizer; | |
| 10 | |
| 11 final lang.SourceFile source; | |
| 12 | |
| 13 lang.Token _previousToken; | |
| 14 lang.Token _peekToken; | |
| 15 | |
| 16 Parser(this.source, [int startOffset = 0]) { | |
| 17 tokenizer = new Tokenizer(source, true, startOffset); | |
| 18 _peekToken = tokenizer.next(); | |
| 19 _previousToken = null; | |
| 20 } | |
| 21 | |
| 22 /** Generate an error if [source] has not been completely consumed. */ | |
| 23 void checkEndOfFile() { | |
| 24 _eat(TokenKind.END_OF_FILE); | |
| 25 } | |
| 26 | |
| 27 /** Guard to break out of parser when an unexpected end of file is found. */ | |
| 28 // TODO(jimhug): Failure to call this method can lead to inifinite parser | |
| 29 // loops. Consider embracing exceptions for more errors to reduce | |
| 30 // the danger here. | |
| 31 bool isPrematureEndOfFile() { | |
| 32 if (_maybeEat(TokenKind.END_OF_FILE)) { | |
| 33 _error('unexpected end of file', _peekToken.span); | |
| 34 return true; | |
| 35 } else { | |
| 36 return false; | |
| 37 } | |
| 38 } | |
| 39 | |
| 40 /////////////////////////////////////////////////////////////////// | |
| 41 // Basic support methods | |
| 42 /////////////////////////////////////////////////////////////////// | |
| 43 int _peek() { | |
| 44 return _peekToken.kind; | |
| 45 } | |
| 46 | |
| 47 lang.Token _next() { | |
| 48 _previousToken = _peekToken; | |
| 49 _peekToken = tokenizer.next(); | |
| 50 return _previousToken; | |
| 51 } | |
| 52 | |
| 53 bool _peekKind(int kind) { | |
| 54 return _peekToken.kind == kind; | |
| 55 } | |
| 56 | |
| 57 /* Is the next token a legal identifier? This includes pseudo-keywords. */ | |
| 58 bool _peekIdentifier() { | |
| 59 return TokenKind.isIdentifier(_peekToken.kind); | |
| 60 } | |
| 61 | |
| 62 bool _maybeEat(int kind) { | |
| 63 if (_peekToken.kind == kind) { | |
| 64 _previousToken = _peekToken; | |
| 65 _peekToken = tokenizer.next(); | |
| 66 return true; | |
| 67 } else { | |
| 68 return false; | |
| 69 } | |
| 70 } | |
| 71 | |
| 72 void _eat(int kind) { | |
| 73 if (!_maybeEat(kind)) { | |
| 74 _errorExpected(TokenKind.kindToString(kind)); | |
| 75 } | |
| 76 } | |
| 77 | |
| 78 void _eatSemicolon() { | |
| 79 _eat(TokenKind.SEMICOLON); | |
| 80 } | |
| 81 | |
| 82 void _errorExpected(String expected) { | |
| 83 var tok = _next(); | |
| 84 var message; | |
| 85 try { | |
| 86 message = 'expected $expected, but found $tok'; | |
| 87 } catch (var e) { | |
| 88 message = 'parsing error expected $expected'; | |
| 89 } | |
| 90 _error(message, tok.span); | |
| 91 } | |
| 92 | |
| 93 void _error(String message, [lang.SourceSpan location=null]) { | |
| 94 if (location === null) { | |
| 95 location = _peekToken.span; | |
| 96 } | |
| 97 | |
| 98 lang.world.fatal(message, location); // syntax errors are fatal for now | |
| 99 } | |
| 100 | |
| 101 lang.SourceSpan _makeSpan(int start) { | |
| 102 return new lang.SourceSpan(source, start, _previousToken.end); | |
| 103 } | |
| 104 | |
| 105 /////////////////////////////////////////////////////////////////// | |
| 106 // Top level productions | |
| 107 /////////////////////////////////////////////////////////////////// | |
| 108 | |
| 109 List<SelectorGroup> preprocess() { | |
|
nweiz
2011/11/23 00:59:44
I don't like "preprocess" here either. This method
| |
| 110 List<SelectorGroup> groups = []; | |
| 111 while (!_maybeEat(TokenKind.END_OF_FILE)) { | |
| 112 do { | |
| 113 int start = _peekToken.start; | |
| 114 groups.add(new SelectorGroup(selector(), | |
| 115 _makeSpan(start))); | |
| 116 } while (_maybeEat(TokenKind.COMMA)); | |
| 117 } | |
| 118 | |
| 119 return groups; | |
| 120 } | |
| 121 | |
| 122 // Templates are @{selectors} single line nothing else. | |
| 123 SelectorGroup template() { | |
| 124 SelectorGroup selectorGroup = null; | |
| 125 if (!isPrematureEndOfFile()) { | |
| 126 selectorGroup = templateExpression(); | |
| 127 } | |
| 128 | |
| 129 return selectorGroup; | |
| 130 } | |
| 131 | |
| 132 /* | |
| 133 * Expect @{css_expression} | |
| 134 */ | |
| 135 templateExpression() { | |
| 136 int start = _peekToken.start; | |
| 137 | |
| 138 _eat(TokenKind.AT); | |
| 139 _eat(TokenKind.LBRACE); | |
| 140 | |
| 141 SelectorGroup group = new SelectorGroup(selector(), | |
| 142 _makeSpan(start)); | |
| 143 | |
| 144 _eat(TokenKind.RBRACE); | |
| 145 | |
| 146 return group; | |
| 147 } | |
| 148 | |
| 149 int classNameCheck(var selector, int matches) { | |
| 150 if (selector.isCombinatorDescendant() || | |
| 151 (selector.isCombinatorNone() && matches == 0)) { | |
| 152 if (matches < 0) { | |
| 153 String tooMany = selector.toString(); | |
| 154 throw new CssSelectorException( | |
| 155 'Can not mix Id selector with class selector(s). Id ' + | |
| 156 'selector must be singleton too many starting at $tooMany'); | |
| 157 } | |
| 158 | |
| 159 return matches + 1; | |
| 160 } else { | |
| 161 String error = selector.toString(); | |
| 162 throw new CssSelectorException( | |
| 163 'Selectors can not have combinators (>, +, or ~) before $error'); | |
| 164 } | |
| 165 } | |
| 166 | |
| 167 int elementIdCheck(var selector, int matches) { | |
| 168 if (selector.isCombinatorNone() && matches == 0) { | |
| 169 // Perfect just one element id returns matches of -1. | |
| 170 return -1; | |
| 171 } else if (selector.isCombinatorDescendant()) { | |
| 172 String tooMany = selector.toString(); | |
| 173 throw new CssSelectorException( | |
| 174 'Use of Id selector must be singleton starting at $tooMany'); | |
| 175 } else { | |
| 176 String error = selector.toString(); | |
| 177 throw new CssSelectorException( | |
| 178 'Selectors can not have combinators (>, +, or ~) before $error'); | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 // Validate the @{css expression} only .class and #elementId are valid inside | |
| 183 // of @{...}. | |
| 184 validateTemplate(List<lang.Node> selectors, CssWorld cssWorld) { | |
| 185 var errorSelector; // signal which selector didn't match. | |
|
nweiz
2011/11/23 00:59:44
Unused variable.
| |
| 186 bool found = false; // signal if a selector is matched. | |
| 187 | |
| 188 int matches = 0; // < 0 IdSelectors, > 0 ClassSelector | |
| 189 for (selector in selectors) { | |
| 190 found = false; | |
| 191 if (selector is ClassSelector) { | |
| 192 // Any class name starting with an underscore is a private class name | |
| 193 // that doesn't have to match the world of known classes. | |
| 194 if (!selector.name.startsWith('_')) { | |
| 195 // TODO(terry): For now iterate through all classes look for faster | |
| 196 // mechanism hash map, etc. | |
| 197 for (className in cssWorld.classes) { | |
| 198 if (selector.name == className) { | |
| 199 matches = classNameCheck(selector, matches); | |
| 200 found = true; // .class found. | |
| 201 break; | |
| 202 } | |
| 203 } | |
| 204 } else { | |
| 205 // Don't check any class name that is prefixed with an underscore. | |
| 206 // However, signal as found and bump up matches; it's a valid class | |
| 207 // name. | |
| 208 matches = classNameCheck(selector, matches); | |
| 209 found = true; // ._class are always okay. | |
| 210 } | |
| 211 } else if (selector is IdSelector) { | |
| 212 // Any element id starting with an underscore is a private element id | |
| 213 // that doesn't have to match the world of known elemtn ids. | |
| 214 if (!selector.name.startsWith('_')) { | |
| 215 for (id in cssWorld.ids) { | |
| 216 if (selector.name == id) { | |
| 217 matches = elementIdCheck(selector, matches); | |
| 218 found = true; // #id found. | |
| 219 break; | |
| 220 } | |
| 221 } | |
| 222 } else { | |
| 223 // Don't check any element ID that is prefixed with an underscore. | |
| 224 // However, signal as found and bump up matches; it's a valid element | |
| 225 // ID. | |
| 226 matches = elementIdCheck(selector, matches); | |
| 227 found = true; // #_id are always okay | |
| 228 } | |
| 229 } else { | |
| 230 String badSelector = selector.toString(); | |
| 231 throw new CssSelectorException( | |
| 232 'Invalid template selector $badSelector'); | |
| 233 } | |
| 234 | |
| 235 if (!found) { | |
| 236 String unknownName = selector.toString(); | |
| 237 throw new CssSelectorException('Unknown selector name $unknownName'); | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 // Every selector must match. | |
| 242 assert((matches >= 0 ? matches : -matches) == selectors.length); | |
| 243 } | |
| 244 | |
| 245 /////////////////////////////////////////////////////////////////// | |
| 246 // Productions | |
| 247 /////////////////////////////////////////////////////////////////// | |
| 248 | |
| 249 selector() { | |
| 250 List<SimpleSelector> simpleSelectors = []; | |
| 251 while (true) { | |
| 252 // First item is never descendant make sure it's COMBINATOR_NONE. | |
| 253 var selectorItem = simpleSelectorSequence(simpleSelectors.length == 0); | |
| 254 if (selectorItem != null) { | |
| 255 simpleSelectors.add(selectorItem); | |
| 256 } else { | |
| 257 break; | |
| 258 } | |
| 259 } | |
| 260 | |
| 261 return simpleSelectors; | |
| 262 } | |
| 263 | |
| 264 simpleSelectorSequence(bool forceCombinatorNone) { | |
|
nweiz
2011/11/23 00:59:44
While this technically works by treating sequences
| |
| 265 int combinatorType = TokenKind.COMBINATOR_NONE; | |
| 266 switch (_peek()) { | |
| 267 case TokenKind.COMBINATOR_PLUS: | |
| 268 _eat(TokenKind.COMBINATOR_PLUS); | |
| 269 combinatorType = TokenKind.COMBINATOR_PLUS; | |
| 270 break; | |
| 271 case TokenKind.COMBINATOR_GREATER: | |
| 272 _eat(TokenKind.COMBINATOR_GREATER); | |
| 273 combinatorType = TokenKind.COMBINATOR_GREATER; | |
| 274 break; | |
| 275 case TokenKind.COMBINATOR_TILDE: | |
| 276 _eat(TokenKind.COMBINATOR_TILDE); | |
| 277 combinatorType = TokenKind.COMBINATOR_TILDE; | |
| 278 break; | |
| 279 } | |
| 280 | |
| 281 // Check if WHITESPACE existed between tokens if so we're descendent. | |
| 282 if (combinatorType == TokenKind.COMBINATOR_NONE && !forceCombinatorNone) { | |
| 283 if (this._previousToken != null && | |
| 284 this._previousToken.end != this._peekToken.start) { | |
| 285 combinatorType = TokenKind.COMBINATOR_DESCENDANT; | |
| 286 } | |
| 287 } | |
| 288 | |
| 289 return simpleSelector(combinatorType); | |
| 290 } | |
| 291 | |
| 292 /** | |
| 293 * Simple selector grammar: | |
| 294 * simple_selector_sequence | |
| 295 * : [ type_selector | universal ] | |
| 296 * [ HASH | class | attrib | pseudo | negation ]* | |
| 297 * | [ HASH | class | attrib | pseudo | negation ]+ | |
| 298 * type_selector | |
| 299 * : [ namespace_prefix ]? element_name | |
| 300 * namespace_prefix | |
| 301 * : [ IDENT | '*' ]? '|' | |
| 302 * element_name | |
| 303 * : IDENT | |
| 304 * universal | |
| 305 * : [ namespace_prefix ]? '*' | |
| 306 * class | |
| 307 * : '.' IDENT | |
| 308 */ | |
| 309 simpleSelector(int combinator) { | |
| 310 // TODO(terry): Nathan makes a good point parsing of namespace and element | |
| 311 // are essentially the same (asterisk or identifier) other | |
| 312 // than the error message for element. Should consolidate the | |
| 313 // code. | |
| 314 var first; | |
| 315 int start = _peekToken.start; | |
| 316 switch (_peek()) { | |
| 317 case TokenKind.ASTERISK: | |
| 318 // Mark as universal namespace. | |
| 319 var tok = _next(); | |
| 320 first = new Wildcard(_makeSpan(tok.start)); | |
| 321 break; | |
| 322 case TokenKind.IDENTIFIER: | |
| 323 int startIdent = _peekToken.start; | |
| 324 first = identifier(); | |
| 325 break; | |
| 326 } | |
| 327 | |
| 328 if (first == null) { | |
| 329 // Check for HASH | class | attrib | pseudo | negation | |
| 330 return simpleSelectorTail(combinator); | |
| 331 } | |
| 332 | |
| 333 // Could be a namespace? | |
| 334 var isNamespace = _maybeEat(TokenKind.NAMESPACE); | |
| 335 if (isNamespace) { | |
| 336 var element; | |
| 337 switch (_peek()) { | |
| 338 case TokenKind.ASTERISK: | |
| 339 // Mark as universal element | |
| 340 var tok = _next(); | |
| 341 element = new Wildcard(_makeSpan(tok.start)); | |
| 342 break; | |
| 343 case TokenKind.IDENTIFIER: | |
| 344 element = identifier(); | |
| 345 break; | |
| 346 default: | |
| 347 _error('expected element name or universal(*), but found $_peekToken', | |
| 348 _peekToken.span); | |
| 349 } | |
| 350 | |
| 351 return new NamespaceSelector(first, | |
| 352 new ElementSelector(element, element.span), | |
| 353 _makeSpan(start), combinator); | |
| 354 } else { | |
| 355 return new ElementSelector(first, _makeSpan(start), combinator); | |
| 356 } | |
| 357 } | |
| 358 | |
| 359 simpleSelectorTail(int combinator) { | |
| 360 // Check for HASH | class | attrib | pseudo | negation | |
| 361 int start = _peekToken.start; | |
| 362 switch (_peek()) { | |
| 363 case TokenKind.HASH: | |
| 364 _eat(TokenKind.HASH); | |
| 365 return new IdSelector(identifier(), _makeSpan(start), combinator); | |
| 366 case TokenKind.DOT: | |
| 367 _eat(TokenKind.DOT); | |
| 368 return new ClassSelector(identifier(), _makeSpan(start), combinator); | |
| 369 case TokenKind.PSEUDO: | |
| 370 // :pseudo-class ::pseudo-element | |
| 371 // TODO(terry): '::' should be token. | |
| 372 _eat(TokenKind.PSEUDO); | |
| 373 bool pseudoClass = _peek() != TokenKind.PSEUDO; | |
| 374 var name = identifier(); | |
| 375 // TODO(terry): Need to handle specific pseudo class/element name and | |
| 376 // backward compatible names that are : as well as :: as well as | |
| 377 // parameters. | |
| 378 return pseudoClass ? | |
| 379 new PseudoClassSelector(name, _makeSpan(start), combinator) : | |
| 380 new PseudoElementSelector(name, _makeSpan(start), combinator); | |
| 381 | |
| 382 // TODO(terry): attrib, negation. | |
| 383 } | |
| 384 } | |
| 385 | |
| 386 identifier() { | |
| 387 var tok = _next(); | |
| 388 if (!TokenKind.isIdentifier(tok.kind)) { | |
| 389 _error('expected identifier, but found $tok', tok.span); | |
| 390 } | |
| 391 | |
| 392 return new Identifier(tok.text, _makeSpan(tok.start)); | |
| 393 } | |
| 394 } | |
| OLD | NEW |