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

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: Minor cleanup. Created 9 years 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> 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 }
OLDNEW
« no previous file with comments | « utils/css/cssworld.dart ('k') | utils/css/test.dart » ('j') | utils/css/tree.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698