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

Side by Side Diff: utils/css/parser.dart

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

Powered by Google App Engine
This is Rietveld 408576698