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

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: Added Jim's CR comments and updated tests. 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 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> expression() {
110 List<SelectorGroup> groups = [];
111 while (!_maybeEat(TokenKind.END_OF_FILE)) {
112 do {
113 int start = _peekToken.start;
114 groups.add(new SelectorGroup(selectorExpression(), _makeSpan(start)));
115 } while (_maybeEat(TokenKind.COMMA));
116 }
117
118 return groups;
119 }
120
121 // Templates are @{selectors} single line nothing else.
122 SelectorGroup template() {
123 SelectorGroup selectorGroup;
124 if (!_maybeEat(TokenKind.END_OF_FILE)) {
125 selectorGroup = templateExpression();
126 if (!_maybeEat(TokenKind.END_OF_FILE)) {
127 // TODO(terry): Error should be done.
128 }
129 }
130
131 return selectorGroup;
132 }
133
134 /*
135 * Expect @{css_expression}
136 */
137 templateExpression() {
138 int start = _peekToken.start;
139
140 _eat(TokenKind.AT);
141 _eat(TokenKind.LBRACE);
142
143 SelectorGroup group = new SelectorGroup(selectorExpression(),
144 _makeSpan(start));
145
146 _eat(TokenKind.RBRACE);
147
148 return group;
149 }
150
151 int classNameCheck(var selector, int matches) {
152 if (selector.isCombinatorNone()) {
153 if (matches < 0) {
154 String tooMany = selector.toString();
155 throw new CssSelectorException(
156 'Can not mix Id selector with class selector(s). Id ' +
157 'selector must be singleton too many starting at $tooMany',
158 selector.span);
159 }
160
161 return ++matches;
162 } else {
163 String error = selector.toString();
164 throw new CssSelectorException(
165 'Selectors can not have combinators (>, +, or ~) before $error',
166 selector.span);
167 }
168 }
169
170 int elementIdCheck(var selector, int matches) {
171 if (selector.isCombinatorNone()) {
172 if (matches != 0) {
173 String tooMany = selector.toString();
174 throw new CssSelectorException(
175 'Use of Id selector must be singleton starting at $tooMany',
176 selector.span);
177 }
178 return --matches;
179 } else {
180 String error = selector.toString();
181 throw new CssSelectorException(
182 'Selectors can not have combinators (>, +, or ~) before $error',
183 selector.span);
184 }
185 }
186
187 // Validate the @{css expression} only .class and #elementId are valid inside
188 // of @{...}.
189 validateTemplate(List<lang.Node> selectors, CssWorld cssWorld) {
190 var errorSelector;
191 bool found = false;
192
193 int matches = 0; // < 0 IdSelectors, > 0 ClassSelector
194 for (selector in selectors) {
195 found = false;
196 if (selector is ClassSelector) {
197 // Any class name starting with an underscore is a private class name
198 // that doesn't have to match the world of known classes.
199 if (!selector.name.startsWith('_')) {
200 for (className in cssWorld.classes) {
201 if (selector.name == className) {
202 matches = classNameCheck(selector, matches);
203 found = true;
204 break;
205 }
206 }
207 } else {
208 // Don't check any class name that is prefixed with an underscore.
209 // However, signal as found and bump up matches; it's a valid class
210 // name.
211 matches = classNameCheck(selector, matches);
212 found = true;
213 }
214 } else if (selector is IdSelector) {
215 // Any element id starting with an underscore is a private element id
216 // that doesn't have to match the world of known elemtn ids.
217 if (!selector.name.startsWith('_')) {
218 for (id in cssWorld.ids) {
219 if (selector.name == id) {
220 matches = elementIdCheck(selector, matches);
221 found = true;
222 break;
223 }
224 }
225 } else {
226 // Don't check any element ID that is prefixed with an underscore.
227 // However, signal as found and bump up matches; it's a valid element
228 // ID.
229 matches = elementIdCheck(selector, matches);
230 found = true;
231 }
232 } else {
233 String badSelector = selector.toString();
234 throw new CssSelectorException(
235 'Invalid selector $badSelector', selector.span);
236 }
237
238 if (!found) {
239 errorSelector = selector; // Flag the problem selector.
240 break;
241 }
242 }
243
244 assert(matches >= 0 || matches == -1);
245
246 if (!found && errorSelector != null) {
247 String unknownName = errorSelector.toString();
248 throw new CssSelectorException('Unknown selector name $unknownName',
249 errorSelector.span);
250 }
251 }
252
253 ///////////////////////////////////////////////////////////////////
254 // Productions
255 ///////////////////////////////////////////////////////////////////
256
257 selectorExpression() {
258 return simpleSelectorSequence();
259 }
260
261 simpleSelectorSequence() {
262 List<SimpleSelector> simpleSelectors = [];
263 while (true) {
264 var selectorItem = combinator();
265 if (selectorItem != null) {
266 simpleSelectors.add(selectorItem);
267 } else {
268 break;
269 }
270 }
271
272 return simpleSelectors;
273 }
274
275 combinator() {
276 int combinatorType = TokenKind.COMBINATOR_NONE;
277 switch (_peek()) {
278 case TokenKind.COMBINATOR_PLUS:
279 _eat(TokenKind.COMBINATOR_PLUS);
280 combinatorType = TokenKind.COMBINATOR_PLUS;
281 break;
282 case TokenKind.COMBINATOR_GREATER:
283 _eat(TokenKind.COMBINATOR_GREATER);
284 combinatorType = TokenKind.COMBINATOR_GREATER;
285 break;
286 case TokenKind.COMBINATOR_TILDE:
287 _eat(TokenKind.COMBINATOR_TILDE);
288 combinatorType = TokenKind.COMBINATOR_TILDE;
289 break;
290 }
291
292 return namespaceElementUniversal(combinatorType);
293 }
294
295 /**
296 * Simple selector grammar:
297 * simple_selector_sequence
298 * : [ type_selector | universal ]
299 * [ HASH | class | attrib | pseudo | negation ]*
300 * | [ HASH | class | attrib | pseudo | negation ]+
301 * type_selector
302 * : [ namespace_prefix ]? element_name
303 * namespace_prefix
304 * : [ IDENT | '*' ]? '|'
305 * element_name
306 * : IDENT
307 * universal
308 * : [ namespace_prefix ]? '*'
309 * class
310 * : '.' IDENT
311 */
312 namespaceElementUniversal(int combinator) {
313 String first;
314 switch (_peek()) {
315 case TokenKind.ASTERISK:
316 first = '*'; // Mark as universal namespace.
317 _next();
318 break;
319 case TokenKind.IDENTIFIER:
320 int startIdent = _peekToken.start;
321 first = identifier();
322 break;
323 }
324
325 if (first != null) {
326 // Could be a namespace?
327 var isNamespace = _maybeEat(TokenKind.NAMESPACE);
328 if (isNamespace) {
329 var element;
330 switch (_peek()) {
331 case TokenKind.ASTERISK:
332 element = '*'; // Mark as universal.
333 _next();
334 break;
335 case TokenKind.IDENTIFIER:
336 int startIdent = _peekToken.start;
337 element = identifier();
338 break;
339 default:
340 _error('expected element name or universal, but found $_peekToken',
341 _peekToken.span);
342 }
343
344 return new NamespaceSelector(first, new ElementSelector(element),
345 combinator);
346 } else {
347 return new ElementSelector(first, combinator);
348 }
349 } else {
350 // Check for HASH | class | attrib | pseudo | negation
351 return selectorNameType(combinator);
352 }
353 }
354
355 selectorNameType(int combinator) {
356 // Check for HASH | class | attrib | pseudo | negation
357 switch (_peek()) {
358 case TokenKind.HASH:
359 int startHash = _peekToken.start;
360 _eat(TokenKind.HASH);
361 var name = identifier();
362 return new IdSelector(name, combinator);
363 case TokenKind.DOT:
364 _eat(TokenKind.DOT);
365 var name = identifier();
366 return new ClassSelector(name, combinator);
367 case TokenKind.PSEUDO:
368 // :pseudo-class ::pseudo-element
369 _eat(TokenKind.PSEUDO);
370 bool pseudoClass = _peek() != TokenKind.PSEUDO;
371 var name = identifier();
372 // TODO(terry): Need to handle specific pseudo class/element name and
373 // backward compatible names that are : as well as ::.
374 return pseudoClass ?
375 new PseudoClassSelector(name, combinator) :
376 new PseudoElementSelector(name, combinator);
377
378 // TODO(terry): attrib, negation.
379 }
380 }
381
382 identifier() {
383 var tok = _next();
384 if (!TokenKind.isIdentifier(tok.kind)) {
385 try {
386 _error('expected identifier, but found $tok', tok.span);
387 } catch (var e) {
388 _error('expected identifier', tok.span);
389 }
390 }
391
392 return new Identifier(tok.text, _makeSpan(tok.start));
393 }
394 }
OLDNEW
« no previous file with comments | « utils/css/cssworld.dart ('k') | utils/css/test.dart » ('j') | utils/css/test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698