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

Side by Side Diff: frog/css/parser_css.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: 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 the dart language.
jimhug 2011/11/09 16:04:03 Old comment.
terry 2011/11/09 21:56:06 Done.
7 */
8 class Parser {
9 // If true throw CSSParserExceptions for any tokenizing/parsing problems.
10 bool _errorsAsException;
11 Tokenizer tokenizer;
12
13 final SourceFile source;
14
15 // TODO(jimhug): 1. Try to kill initializers, if fails, clean this up.
16 bool _inInitializers;
nweiz 2011/11/10 00:04:39 Unused variable.
terry 2011/11/16 14:00:22 Done.
17
18 Token _previousToken;
19 Token _peekToken;
20
21 Parser(this.source, [int startOffset = 0]) {
22 tokenizer = new Tokenizer(source, true, startOffset);
23 _peekToken = tokenizer.next();
24 _previousToken = null;
25 _inInitializers = false;
26 _errorsAsException = false;
27 }
28
29 /** Generate an error if [source] has not been completely consumed. */
30 void checkEndOfFile() {
31 _eat(TokenKind.END_OF_FILE);
32 }
33
34 /** Guard to break out of parser when an unexpected end of file is found. */
35 // TODO(jimhug): Failure to call this method can lead to inifinite parser
36 // loops. Consider embracing exceptions for more errors to reduce
37 // the danger here.
38 bool isPrematureEndOfFile() {
39 if (_maybeEat(TokenKind.END_OF_FILE)) {
40 _error('unexpected end of file', _peekToken.span);
41 return true;
42 } else {
43 return false;
44 }
45 }
46
47 ///////////////////////////////////////////////////////////////////
48 // Basic support methods
49 ///////////////////////////////////////////////////////////////////
50 int _peek() {
51 return _peekToken.kind;
52 }
53
54 Token _next() {
55 _previousToken = _peekToken;
56 _peekToken = tokenizer.next();
57 return _previousToken;
58 }
59
60 bool _peekKind(int kind) {
61 return _peekToken.kind == kind;
62 }
63
64 /* Is the next token a legal identifier? This includes pseudo-keywords. */
65 bool _peekIdentifier() {
66 return TokenKind.isIdentifier(_peekToken.kind);
nweiz 2011/11/10 00:04:39 Why can't you use _peekKind(TokenKind.IDENTIFIER)?
terry 2011/11/16 14:00:22 This is mimicked after frog's parser. On 2011/11/
nweiz 2011/11/23 00:59:44 Frog's parser needs it because there are multiple
67 }
68
69 bool _maybeEat(int kind) {
70 if (_peekToken.kind == kind) {
71 _previousToken = _peekToken;
72 _peekToken = tokenizer.next();
73 return true;
74 } else {
75 return false;
76 }
77 }
78
79 void _eat(int kind) {
80 if (!_maybeEat(kind)) {
81 _errorExpected(TokenKind.kindToString(kind));
82 }
83 }
84
85 void _eatSemicolon() {
nweiz 2011/11/10 00:04:39 Unused method.
terry 2011/11/16 14:00:22 Unused now will use later. On 2011/11/10 00:04:39
nweiz 2011/11/23 00:59:44 I don't think it'll be useful, actually... there a
86 _eat(TokenKind.SEMICOLON);
87 }
88
89 void _errorExpected(String expected) {
90 var tok = _next();
91 var message;
92 try {
93 message = 'expected $expected, but found $tok';
94 } catch (var e) {
95 message = 'parsing error expected $expected';
96 }
97 _error(message, tok.span);
98 }
99
100 void _error(String message, [SourceSpan location=null]) {
101 if (location === null) {
102 location = _peekToken.span;
103 }
104
105 // TODO(terry): Should use below world.fatal:
106 // world.fatal(message, location); // syntax errors are fatal for now
107
108 // TODO(terry): Beginning of temp code until world.fatal enabled.
109 var text = message;
110 if (location != null) {
111 text = location.toMessageString(message);
112 }
113 print("FATAL: $text");
114
115 // Any parsing problem throw exception too.
116 if (this._errorsAsException) {
117 throw new CssParserException(message, location);
118 }
119 // TODO(terry): End of temp code until world.fatal enabled.
120 }
121
122 SourceSpan _makeSpan(int start) {
123 return new SourceSpan(source, start, _previousToken.end);
124 }
125
126 ///////////////////////////////////////////////////////////////////
127 // Top level productions
128 ///////////////////////////////////////////////////////////////////
129
130 List<SelectorGroup> expression([bool throwErrors = false]) {
nweiz 2011/11/10 00:04:39 I think your terminology is confusing here. "expre
terry 2011/11/16 14:00:22 I see your point, expression was probably a bad na
131 this._errorsAsException = throwErrors;
132 List<SelectorGroup> groups = [];
133 while (!_maybeEat(TokenKind.END_OF_FILE)) {
134 do {
135 int start = _peekToken.start;
136 groups.add(new SelectorGroup(selectorExpression(), start));
137 } while (_maybeEat(TokenKind.COMMA));
138 }
139
140 return groups;
141 }
142
143 // Templates are @{selectors} single line nothing else.
144 SelectorGroup template([bool throwErrors = false]) {
145 this._errorsAsException = throwErrors;
146 SelectorGroup selectorGroup;
147 if (!_maybeEat(TokenKind.END_OF_FILE)) {
148 selectorGroup = templateExpression();
149 if (!_maybeEat(TokenKind.END_OF_FILE)) {
nweiz 2011/11/10 00:04:39 Can you just call isPrematureEndOfFile?
terry 2011/11/22 16:40:47 Done.
150 // TODO(terry): Error should be done.
151 }
152 }
153
154 return selectorGroup;
155 }
156
157 /*
158 * Expect @{css_expression}
159 */
160 templateExpression() {
161 SelectorGroup selectorGroup = null;
162 int start = _peekToken.start;
163
164 if (_peek() == TokenKind.AT) {
nweiz 2011/11/10 00:04:39 Is there any reason to do all these if checks, rat
terry 2011/11/16 14:00:22 Main reason was more exact error. However, I thin
165 _eat(TokenKind.AT);
166 if (_peek() == TokenKind.LBRACE) {
167 _eat(TokenKind.LBRACE);
168 List<SimpleSelector> selectors= selectorExpression();
169 SelectorGroup exprResult = new SelectorGroup(selectors, start);
170
171 if (_peek() == TokenKind.RBRACE) {
172 _eat(TokenKind.RBRACE);
173 selectorGroup = exprResult;
174 }
175 }
176 }
177
178 if (selectorGroup == null) {
179 _errorExpected('css template @{css selector}');
180 } else {
181 return selectorGroup;
182 }
183 }
184
185 int classNameCheck(var selector, int matches) {
186 if (selector.isCombinatorNone()) {
187 if (matches < 0) {
188 String tooMany = selector.toString();
189 throw new CssSelectorException(
190 'Can not mix Id selector with class selector(s). Id ' +
nweiz 2011/11/10 00:04:39 This isn't actually true. "#foo.bar" is a valid se
terry 2011/11/16 14:00:22 Of course, however this is used by the validator f
nweiz 2011/11/23 00:59:44 Why aren't we allowing arbitrary selectors in sele
191 'selector must be singleton too many starting at $tooMany',
192 selector.span);
193 }
194
195 return ++matches;
nweiz 2011/11/10 00:04:39 Style nit: matches + 1, since you aren't actually
terry 2011/11/22 16:40:47 Done.
196 } else {
197 String error = selector.toString();
198 throw new CssSelectorException(
199 'Selectors can not have combinators (>, +, or ~) before $error',
200 selector.span);
201 }
202 }
203
204 int elementIdCheck(var selector, int matches) {
205 if (selector.isCombinatorNone()) {
206 if (matches != 0) {
207 String tooMany = selector.toString();
208 throw new CssSelectorException(
209 'Use of Id selector must be singleton starting at $tooMany',
210 selector.span);
211 }
212 return --matches;
nweiz 2011/11/10 00:04:39 Style nit: matches - 1
terry 2011/11/22 16:40:47 Done.
213 } else {
214 String error = selector.toString();
215 throw new CssSelectorException(
216 'Selectors can not have combinators (>, +, or ~) before $error',
217 selector.span);
218 }
219 }
220
221 // Validate the @{css expression} only .class and #elementId are valid inside
222 // of @{...}.
223 validateTemplate(List<Node> selectors, CssWorld cssWorld) {
nweiz 2011/11/10 00:04:39 Does this really belong in the parser? It's not ac
terry 2011/11/22 16:40:47 Will be moving this out in the next checkin. On 2
224 var errorSelector;
225 bool found = false;
226
227 bool matches = 0; // < 0 IdSelectors, > 0 ClassSelector
nweiz 2011/11/10 00:04:39 You're declaring an int as a bool here. Also, it s
terry 2011/11/22 16:40:47 Thanks, caught this earlier when I merged with the
228 for (selector in selectors) {
229 found = false;
230 if (selector is ClassSelector) {
231 // Any class name starting with an underscore is a private class name
232 // that doesn't have to match the world of known classes.
233 if (!selector.name.startsWith('_')) {
234 for (className in cssWorld.classes) {
nweiz 2011/11/10 00:04:39 Perhaps cssWorld.classes should be a hash table so
terry 2011/11/22 16:40:47 You're probably right but I've err'd on the side o
235 if (selector.name == className) {
236 matches = classNameCheck(selector, matches);
237 found = true;
238 break;
nweiz 2011/11/10 00:04:39 If you break after you find a single valid selecto
terry 2011/11/22 16:40:47 No each selector is validated if any selector isn'
239 }
240 }
241 } else {
242 // Don't check any class name that is prefixed with an underscore.
243 // However, signal as found and bump up matches; it's a valid class
244 // name.
245 matches = classNameCheck(selector, matches);
246 found = true;
247 }
248 } else if (selector is IdSelector) {
249 // Any element id starting with an underscore is a private element id
250 // that doesn't have to match the world of known elemtn ids.
251 if (!selector.name.startsWith('_')) {
252 for (id in cssWorld.ids) {
253 if (selector.name == id) {
254 matches = elementIdCheck(selector, matches);
255 found = true;
256 break;
257 }
258 }
259 } else {
260 // Don't check any element ID that is prefixed with an underscore.
261 // However, signal as found and bump up matches; it's a valid element
262 // ID.
263 matches = elementIdCheck(selector, matches);
264 found = true;
265 }
266 } else {
267 String badSelector = selector.toString();
268 throw new CssSelectorException(
269 'Invalid selector $badSelector', selector.span);
nweiz 2011/11/10 00:04:39 This is a confusing error message; it implies that
terry 2011/11/22 16:40:47 Good point I'll change the error. Done.
270 }
271
272 if (!found) {
273 errorSelector = selector; // Flag the problem selector.
nweiz 2011/11/10 00:04:39 If this is the only place you set errorSelector, w
terry 2011/11/22 16:40:47 Done.
274 break;
275 }
276 }
277
278 assert(matches >= 0 || matches == -1);
279
280 if (!found && errorSelector != null) {
281 String unknownName = errorSelector.toString();
282 throw new CssSelectorException('Unknown selector name $unknownName',
283 errorSelector.span);
284 }
285 }
286
287 ///////////////////////////////////////////////////////////////////
288 // Productions
289 ///////////////////////////////////////////////////////////////////
290
291 selectorExpression() {
nweiz 2011/11/10 00:04:39 This method doesn't seem to do anything.
terry 2011/11/22 16:40:47 It's gone. On 2011/11/10 00:04:39, nweiz wrote:
292 return simpleSelectorSequence();
293 }
294
295 simpleSelectorSequence() {
nweiz 2011/11/10 00:04:39 selector()
terry 2011/11/22 16:40:47 Done.
296 List<SimpleSelector> simpleSelectors = [];
297 while (true) {
298 var selectorItem = combinator();
299 if (selectorItem != null) {
300 simpleSelectors.add(selectorItem);
301 } else {
302 break;
303 }
304 }
305
306 return simpleSelectors;
307 }
308
309 combinator() {
nweiz 2011/11/10 00:04:39 simpleSelectorSequence()
terry 2011/11/22 16:40:47 Done.
310 int combinatorType = TokenKind.COMBINATOR_NONE;
311 switch (_peek()) {
312 case TokenKind.COMBINATOR_PLUS:
313 _eat(TokenKind.COMBINATOR_PLUS);
314 combinatorType = TokenKind.COMBINATOR_PLUS;
315 break;
316 case TokenKind.COMBINATOR_GREATER:
317 _eat(TokenKind.COMBINATOR_GREATER);
318 combinatorType = TokenKind.COMBINATOR_GREATER;
319 break;
320 case TokenKind.COMBINATOR_TILDE:
321 _eat(TokenKind.COMBINATOR_TILDE);
322 combinatorType = TokenKind.COMBINATOR_TILDE;
323 break;
324 }
nweiz 2011/11/10 00:04:39 You never seem to be parsing a sequence of simple
terry 2011/11/22 16:40:47 I had't completed the code but have now so sequenc
325
326 return namespaceElementUniversal(combinatorType);
327 }
328
329 /**
330 * Simple selector grammar:
331 * simple_selector_sequence
332 * : [ type_selector | universal ]
333 * [ HASH | class | attrib | pseudo | negation ]*
334 * | [ HASH | class | attrib | pseudo | negation ]+
335 * type_selector
336 * : [ namespace_prefix ]? element_name
337 * namespace_prefix
338 * : [ IDENT | '*' ]? '|'
339 * element_name
340 * : IDENT
341 * universal
342 * : [ namespace_prefix ]? '*'
343 * class
344 * : '.' IDENT
345 */
346 namespaceElementUniversal(Token combinator) {
nweiz 2011/11/10 00:04:39 simpleSelector()
terry 2011/11/22 16:40:47 Done.
347 String first;
348 switch (_peek()) {
349 case TokenKind.ASTERISK:
350 first = '*'; // Mark as universal namespace.
351 _next();
352 break;
353 case TokenKind.IDENTIFIER:
354 int startIdent = _peekToken.start;
355 first = identifier();
356 break;
357 }
358
359 if (first != null) {
nweiz 2011/11/10 00:04:39 Would be cleaner to return immediately if first ==
terry 2011/11/22 16:40:47 Done.
360 // Could be a namespace?
361 var isNamespace = _maybeEat(TokenKind.NAMESPACE);
362 if (isNamespace) {
363 var element;
364 switch (_peek()) {
nweiz 2011/11/10 00:04:39 I don't like this duplicated logic. Could you chec
terry 2011/11/22 16:40:47 For now I like the element vs/ namespace parsing I
365 case TokenKind.ASTERISK:
366 element = '*'; // Mark as universal.
367 _next();
368 break;
369 case TokenKind.IDENTIFIER:
370 int startIdent = _peekToken.start;
371 element = identifier();
372 break;
373 default:
374 _error('expected element name or universal, but found $_peekToken', _peekToken.span);
jimhug 2011/11/09 16:04:03 Nit: too long line.
terry 2011/11/09 21:56:06 Done.
375 }
376
377 return new NamespaceSelector(first, new ElementSelector(element),
378 combinator);
379 } else {
380 return new ElementSelector(first, combinator);
381 }
382 } else {
383 // Check for HASH | class | attrib | pseudo | negation
384 return selectorNameType(combinator);
385 }
386 }
387
388 selectorNameType(Token combinator) {
nweiz 2011/11/10 00:04:39 simpleSelectorTail()?
terry 2011/11/22 16:40:47 Done.
389 // Check for HASH | class | attrib | pseudo | negation
390 switch (_peek()) {
391 case TokenKind.HASH:
392 int startHash = _peekToken.start;
nweiz 2011/11/10 00:04:39 Unused variable.
terry 2011/11/22 16:40:47 Done.
393 _eat(TokenKind.HASH);
394 var name = identifier();
395 return new IdSelector(name, combinator);
396 case TokenKind.DOT:
397 _eat(TokenKind.DOT);
398 var name = identifier();
399 return new ClassSelector(name, combinator);
400 case TokenKind.PSEUDO:
401 // :pseudo-class ::pseudo-element
402 _eat(TokenKind.PSEUDO);
403 bool pseudoClass = _peek() != TokenKind.PSEUDO;
nweiz 2011/11/10 00:04:39 Seems like parsing : vs :: is a job for the tokeni
terry 2011/11/22 16:40:47 Your right. I'll fix in the next checkin.
404 var name = identifier();
405 // TODO(terry): Need to handle specific pseudo class/element name and
406 // backward compatible names that are : as well as ::.
407 return pseudoClass ?
408 new PseudoClassSelector(name, combinator) :
409 new PseudoElementSelector(name, combinator);
nweiz 2011/11/10 00:04:39 TODO: Some pseudo-class and -element selectors hav
terry 2011/11/22 16:40:47 Yep, I haven't finished this yet I'll add a TODO.
410
411 // TODO(terry): attrib, negation.
412 }
413 }
414
415 identifier() {
416 var tok = _next();
417 if (!TokenKind.isIdentifier(tok.kind)) {
nweiz 2011/11/10 00:04:39 Why aren't you just using _eat here?
terry 2011/11/22 16:40:47 Why for better error recovery? Currently, error r
418 try {
419 _error('expected identifier, but found $tok', tok.span);
420 } catch (var e) {
421 _error('expected identifier', tok.span);
422 }
423 }
424
425 return new Identifier(tok.text, _makeSpan(tok.start));
426 }
427 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698