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 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 } | |
OLD | NEW |