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

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

Issue 137013002: Removed obsolete code (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Removed libraries not used Created 6 years, 11 months 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
« no previous file with comments | « utils/css/generate.dart ('k') | utils/css/source.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
4 /**
5 * A simple recursive descent parser for CSS.
6 */
7 class Parser {
8 Tokenizer tokenizer;
9
10 var _fs; // If non-null filesystem to read files.
11 String _basePath; // Base path of CSS file.
12
13 final SourceFile source;
14
15 Token _previousToken;
16 Token _peekToken;
17
18 // Communicating errors back to template parser.
19 // TODO(terry): Need a better mechanism (e.g., common World).
20 var _erroMsgRedirector;
21
22 Parser(this.source, [int start = 0, this._fs = null, this._basePath = null]) {
23 tokenizer = new Tokenizer(source, true, start);
24 _peekToken = tokenizer.next();
25 _previousToken = null;
26 }
27
28 // Main entry point for parsing an entire CSS file.
29 // If nestedCSS is true when we're back at processing directives from top and
30 // we encounter a } then stop we're inside of a template e.g.,
31 //
32 // template ... {
33 // css {
34 // .item {
35 // left: 10px;
36 // }
37 // }
38 // <div>...</div>
39 // }
40 //
41 Stylesheet parse([bool nestedCSS = false, var erroMsgRedirector = null]) {
42 // TODO(terry): Hack for migrating CSS errors back to template errors.
43 _erroMsgRedirector = erroMsgRedirector;
44
45 List<ASTNode> productions = [];
46
47 int start = _peekToken.start;
48 while (!_maybeEat(TokenKind.END_OF_FILE) &&
49 (!nestedCSS && !_peekKind(TokenKind.RBRACE))) {
50 // TODO(terry): Need to handle charset, import, media and page.
51 var directive = processDirective();
52 if (directive != null) {
53 productions.add(directive);
54 } else {
55 RuleSet ruleset = processRuleSet();
56 if (ruleset != null) {
57 productions.add(ruleset);
58 } else {
59 break;
60 }
61 }
62 }
63
64 return new Stylesheet(productions, _makeSpan(start));
65 }
66
67 /** Generate an error if [source] has not been completely consumed. */
68 void checkEndOfFile() {
69 _eat(TokenKind.END_OF_FILE);
70 }
71
72 /** Guard to break out of parser when an unexpected end of file is found. */
73 // TODO(jimhug): Failure to call this method can lead to inifinite parser
74 // loops. Consider embracing exceptions for more errors to reduce
75 // the danger here.
76 bool isPrematureEndOfFile() {
77 if (_maybeEat(TokenKind.END_OF_FILE)) {
78 _error('unexpected end of file', _peekToken.span);
79 return true;
80 } else {
81 return false;
82 }
83 }
84
85 ///////////////////////////////////////////////////////////////////
86 // Basic support methods
87 ///////////////////////////////////////////////////////////////////
88 int _peek() {
89 return _peekToken.kind;
90 }
91
92 Token _next() {
93 _previousToken = _peekToken;
94 _peekToken = tokenizer.next();
95 return _previousToken;
96 }
97
98 bool _peekKind(int kind) {
99 return _peekToken.kind == kind;
100 }
101
102 /* Is the next token a legal identifier? This includes pseudo-keywords. */
103 bool _peekIdentifier() {
104 return TokenKind.isIdentifier(_peekToken.kind);
105 }
106
107 bool _maybeEat(int kind) {
108 if (_peekToken.kind == kind) {
109 _previousToken = _peekToken;
110 _peekToken = tokenizer.next();
111 return true;
112 } else {
113 return false;
114 }
115 }
116
117 void _eat(int kind) {
118 if (!_maybeEat(kind)) {
119 _errorExpected(TokenKind.kindToString(kind));
120 }
121 }
122
123 void _eatSemicolon() {
124 _eat(TokenKind.SEMICOLON);
125 }
126
127 void _errorExpected(String expected) {
128 var tok = _next();
129 var message;
130 try {
131 message = 'expected $expected, but found $tok';
132 } catch (e) {
133 message = 'parsing error expected $expected';
134 }
135 _error(message, tok.span);
136 }
137
138 void _error(String message, [SourceSpan location=null]) {
139 if (location == null) {
140 location = _peekToken.span;
141 }
142
143 if (_erroMsgRedirector == null) {
144 world.fatal(message, location); // syntax errors are fatal for now
145 } else {
146 String text = "";
147 if (location != null) {
148 text = location.toMessageString("");
149 }
150 _erroMsgRedirector.displayError("CSS error: \r${text}\r${message}");
151 }
152 }
153
154 void _warning(String message, [SourceSpan location=null]) {
155 if (location == null) {
156 location = _peekToken.span;
157 }
158
159 world.warning(message, location);
160 }
161
162 SourceSpan _makeSpan(int start) {
163 return new SourceSpan(source, start, _previousToken.end);
164 }
165
166 ///////////////////////////////////////////////////////////////////
167 // Top level productions
168 ///////////////////////////////////////////////////////////////////
169
170 // Templates are @{selectors} single line nothing else.
171 SelectorGroup parseTemplate() {
172 SelectorGroup selectorGroup = null;
173 if (!isPrematureEndOfFile()) {
174 selectorGroup = templateExpression();
175 }
176
177 return selectorGroup;
178 }
179
180 /*
181 * Expect @{css_expression}
182 */
183 templateExpression() {
184 List<Selector> selectors = [];
185
186 int start = _peekToken.start;
187
188 _eat(TokenKind.AT);
189 _eat(TokenKind.LBRACE);
190
191 selectors.add(processSelector());
192 SelectorGroup group = new SelectorGroup(selectors, _makeSpan(start));
193
194 _eat(TokenKind.RBRACE);
195
196 return group;
197 }
198
199 ///////////////////////////////////////////////////////////////////
200 // Productions
201 ///////////////////////////////////////////////////////////////////
202
203 processMedia([bool oneRequired = false]) {
204 List<String> media = [];
205
206 while (_peekIdentifier()) {
207 // We have some media types.
208 var medium = identifier(); // Medium ident.
209 media.add(medium);
210 if (!_maybeEat(TokenKind.COMMA)) {
211 // No more media types exit now.
212 break;
213 }
214 }
215
216 if (oneRequired && media.length == 0) {
217 _error('at least one media type required', _peekToken.span);
218 }
219
220 return media;
221 }
222
223 // Directive grammar:
224 //
225 // import: '@import' [string | URI] media_list?
226 // media: '@media' media_list '{' ruleset '}'
227 // page: '@page' [':' IDENT]? '{' declarations '}'
228 // include: '@include' [string | URI]
229 // stylet: '@stylet' IDENT '{' ruleset '}'
230 // media_list: IDENT [',' IDENT]
231 // keyframes: '@-webkit-keyframes ...' (see grammar below).
232 // font_face: '@font-face' '{' declarations '}'
233 //
234 processDirective() {
235 int start = _peekToken.start;
236
237 if (_maybeEat(TokenKind.AT)) {
238 switch (_peek()) {
239 case TokenKind.DIRECTIVE_IMPORT:
240 _next();
241
242 String importStr;
243 if (_peekIdentifier()) {
244 var func = processFunction(identifier());
245 if (func is UriTerm) {
246 importStr = func.text;
247 }
248 } else {
249 importStr = processQuotedString(false);
250 }
251
252 // Any medias?
253 List<String> medias = processMedia();
254
255 if (importStr == null) {
256 _error('missing import string', _peekToken.span);
257 }
258 return new ImportDirective(importStr, medias, _makeSpan(start));
259 case TokenKind.DIRECTIVE_MEDIA:
260 _next();
261
262 // Any medias?
263 List<String> media = processMedia(true);
264 RuleSet ruleset;
265
266 if (_maybeEat(TokenKind.LBRACE)) {
267 ruleset = processRuleSet();
268 if (!_maybeEat(TokenKind.RBRACE)) {
269 _error('expected } after ruleset for @media', _peekToken.span);
270 }
271 } else {
272 _error('expected { after media before ruleset', _peekToken.span);
273 }
274 return new MediaDirective(media, ruleset, _makeSpan(start));
275 case TokenKind.DIRECTIVE_PAGE:
276 _next();
277
278 // Any pseudo page?
279 var pseudoPage;
280 if (_maybeEat(TokenKind.COLON)) {
281 if (_peekIdentifier()) {
282 pseudoPage = identifier();
283 }
284 }
285 return new PageDirective(pseudoPage, processDeclarations(),
286 _makeSpan(start));
287 case TokenKind.DIRECTIVE_KEYFRAMES:
288 /* Key frames grammar:
289 *
290 * @-webkit-keyframes [IDENT|STRING] '{' keyframes-blocks '}';
291 *
292 * keyframes-blocks:
293 * [keyframe-selectors '{' declarations '}']* ;
294 *
295 * keyframe-selectors:
296 * ['from'|'to'|PERCENTAGE] [',' ['from'|'to'|PERCENTAGE] ]* ;
297 */
298 _next();
299
300 var name;
301 if (_peekIdentifier()) {
302 name = identifier();
303 }
304
305 _eat(TokenKind.LBRACE);
306
307 KeyFrameDirective kf = new KeyFrameDirective(name, _makeSpan(start));
308
309 do {
310 Expressions selectors = new Expressions(_makeSpan(start));
311
312 do {
313 var term = processTerm();
314
315 // TODO(terry): Only allow from, to and PERCENTAGE ...
316
317 selectors.add(term);
318 } while (_maybeEat(TokenKind.COMMA));
319
320 kf.add(new KeyFrameBlock(selectors, processDeclarations(),
321 _makeSpan(start)));
322
323 } while (!_maybeEat(TokenKind.RBRACE));
324
325 return kf;
326 case TokenKind.DIRECTIVE_FONTFACE:
327 _next();
328
329 List<Declaration> decls = [];
330
331 // TODO(terry): To Be Implemented
332
333 return new FontFaceDirective(decls, _makeSpan(start));
334 case TokenKind.DIRECTIVE_INCLUDE:
335 _next();
336 String filename = processQuotedString(false);
337 if (_fs != null) {
338 // Does CSS file exist?
339 if (_fs.fileExists('${_basePath}${filename}')) {
340 String basePath = "";
341 int idx = filename.lastIndexOf('/');
342 if (idx >= 0) {
343 basePath = filename.substring(0, idx + 1);
344 }
345 basePath = '${_basePath}${basePath}';
346 // Yes, let's parse this file as well.
347 String fullFN = '${basePath}${filename}';
348 String contents = _fs.readAll(fullFN);
349 Parser parser = new Parser(new SourceFile(fullFN, contents), 0,
350 _fs, basePath);
351 Stylesheet stylesheet = parser.parse();
352 return new IncludeDirective(filename, stylesheet, _makeSpan(start));
353 }
354
355 _error('file doesn\'t exist ${filename}', _peekToken.span);
356 }
357
358 print("WARNING: @include doesn't work for uitest");
359 return new IncludeDirective(filename, null, _makeSpan(start));
360 case TokenKind.DIRECTIVE_STYLET:
361 /* Stylet grammar:
362 *
363 * @stylet IDENT '{'
364 * ruleset
365 * '}'
366 */
367 _next();
368
369 var name;
370 if (_peekIdentifier()) {
371 name = identifier();
372 }
373
374 _eat(TokenKind.LBRACE);
375
376 List<ASTNode> productions = [];
377
378 start = _peekToken.start;
379 while (!_maybeEat(TokenKind.END_OF_FILE)) {
380 RuleSet ruleset = processRuleSet();
381 if (ruleset == null) {
382 break;
383 }
384 productions.add(ruleset);
385 }
386
387 _eat(TokenKind.RBRACE);
388
389 return new StyletDirective(name, productions, _makeSpan(start));
390 default:
391 _error('unknown directive, found $_peekToken', _peekToken.span);
392 }
393 }
394 }
395
396 RuleSet processRuleSet() {
397 int start = _peekToken.start;
398
399 SelectorGroup selGroup = processSelectorGroup();
400 if (selGroup != null) {
401 return new RuleSet(selGroup, processDeclarations(), _makeSpan(start));
402 }
403 }
404
405 DeclarationGroup processDeclarations() {
406 int start = _peekToken.start;
407
408 _eat(TokenKind.LBRACE);
409
410 List<Declaration> decls = [];
411 do {
412 Declaration decl = processDeclaration();
413 if (decl != null) {
414 decls.add(decl);
415 }
416 } while (_maybeEat(TokenKind.SEMICOLON));
417
418 _eat(TokenKind.RBRACE);
419
420 return new DeclarationGroup(decls, _makeSpan(start));
421 }
422
423 SelectorGroup processSelectorGroup() {
424 List<Selector> selectors = [];
425 int start = _peekToken.start;
426 do {
427 Selector selector = processSelector();
428 if (selector != null) {
429 selectors.add(selector);
430 }
431 } while (_maybeEat(TokenKind.COMMA));
432
433 if (selectors.length > 0) {
434 return new SelectorGroup(selectors, _makeSpan(start));
435 }
436 }
437
438 /* Return list of selectors
439 *
440 */
441 processSelector() {
442 List<SimpleSelectorSequence> simpleSequences = [];
443 int start = _peekToken.start;
444 while (true) {
445 // First item is never descendant make sure it's COMBINATOR_NONE.
446 var selectorItem = simpleSelectorSequence(simpleSequences.length == 0);
447 if (selectorItem != null) {
448 simpleSequences.add(selectorItem);
449 } else {
450 break;
451 }
452 }
453
454 if (simpleSequences.length > 0) {
455 return new Selector(simpleSequences, _makeSpan(start));
456 }
457 }
458
459 simpleSelectorSequence(bool forceCombinatorNone) {
460 int start = _peekToken.start;
461 int combinatorType = TokenKind.COMBINATOR_NONE;
462
463 switch (_peek()) {
464 case TokenKind.PLUS:
465 _eat(TokenKind.PLUS);
466 combinatorType = TokenKind.COMBINATOR_PLUS;
467 break;
468 case TokenKind.GREATER:
469 _eat(TokenKind.GREATER);
470 combinatorType = TokenKind.COMBINATOR_GREATER;
471 break;
472 case TokenKind.TILDE:
473 _eat(TokenKind.TILDE);
474 combinatorType = TokenKind.COMBINATOR_TILDE;
475 break;
476 }
477
478 // Check if WHITESPACE existed between tokens if so we're descendent.
479 if (combinatorType == TokenKind.COMBINATOR_NONE && !forceCombinatorNone) {
480 if (this._previousToken != null &&
481 this._previousToken.end != this._peekToken.start) {
482 combinatorType = TokenKind.COMBINATOR_DESCENDANT;
483 }
484 }
485
486 var simpleSel = simpleSelector();
487 if (simpleSel != null) {
488 return new SimpleSelectorSequence(simpleSel, _makeSpan(start),
489 combinatorType);
490 }
491 }
492
493 /**
494 * Simple selector grammar:
495 *
496 * simple_selector_sequence
497 * : [ type_selector | universal ]
498 * [ HASH | class | attrib | pseudo | negation ]*
499 * | [ HASH | class | attrib | pseudo | negation ]+
500 * type_selector
501 * : [ namespace_prefix ]? element_name
502 * namespace_prefix
503 * : [ IDENT | '*' ]? '|'
504 * element_name
505 * : IDENT
506 * universal
507 * : [ namespace_prefix ]? '*'
508 * class
509 * : '.' IDENT
510 */
511 simpleSelector() {
512 // TODO(terry): Nathan makes a good point parsing of namespace and element
513 // are essentially the same (asterisk or identifier) other
514 // than the error message for element. Should consolidate the
515 // code.
516 // TODO(terry): Need to handle attribute namespace too.
517 var first;
518 int start = _peekToken.start;
519 switch (_peek()) {
520 case TokenKind.ASTERISK:
521 // Mark as universal namespace.
522 var tok = _next();
523 first = new Wildcard(_makeSpan(tok.start));
524 break;
525 case TokenKind.IDENTIFIER:
526 int startIdent = _peekToken.start;
527 first = identifier();
528 break;
529 }
530
531 if (_maybeEat(TokenKind.NAMESPACE)) {
532 var element;
533 switch (_peek()) {
534 case TokenKind.ASTERISK:
535 // Mark as universal element
536 var tok = _next();
537 element = new Wildcard(_makeSpan(tok.start));
538 break;
539 case TokenKind.IDENTIFIER:
540 element = identifier();
541 break;
542 default:
543 _error('expected element name or universal(*), but found $_peekToken',
544 _peekToken.span);
545 }
546
547 return new NamespaceSelector(first,
548 new ElementSelector(element, element.span), _makeSpan(start));
549 } else if (first != null) {
550 return new ElementSelector(first, _makeSpan(start));
551 } else {
552 // Check for HASH | class | attrib | pseudo | negation
553 return simpleSelectorTail();
554 }
555 }
556
557 simpleSelectorTail() {
558 // Check for HASH | class | attrib | pseudo | negation
559 int start = _peekToken.start;
560 switch (_peek()) {
561 case TokenKind.HASH:
562 _eat(TokenKind.HASH);
563 return new IdSelector(identifier(), _makeSpan(start));
564 case TokenKind.DOT:
565 _eat(TokenKind.DOT);
566 return new ClassSelector(identifier(), _makeSpan(start));
567 case TokenKind.COLON:
568 // :pseudo-class ::pseudo-element
569 // TODO(terry): '::' should be token.
570 _eat(TokenKind.COLON);
571 bool pseudoElement = _maybeEat(TokenKind.COLON);
572 var name = identifier();
573 // TODO(terry): Need to handle specific pseudo class/element name and
574 // backward compatible names that are : as well as :: as well as
575 // parameters.
576 return pseudoElement ?
577 new PseudoElementSelector(name, _makeSpan(start)) :
578 new PseudoClassSelector(name, _makeSpan(start));
579 case TokenKind.LBRACK:
580 return processAttribute();
581 }
582 }
583
584 // Attribute grammar:
585 //
586 // attributes :
587 // '[' S* IDENT S* [ ATTRIB_MATCHES S* [ IDENT | STRING ] S* ]? ']'
588 //
589 // ATTRIB_MATCHES :
590 // [ '=' | INCLUDES | DASHMATCH | PREFIXMATCH | SUFFIXMATCH | SUBSTRMATCH ]
591 //
592 // INCLUDES: '~='
593 //
594 // DASHMATCH: '|='
595 //
596 // PREFIXMATCH: '^='
597 //
598 // SUFFIXMATCH: '$='
599 //
600 // SUBSTRMATCH: '*='
601 //
602 //
603 processAttribute() {
604 int start = _peekToken.start;
605
606 if (_maybeEat(TokenKind.LBRACK)) {
607 var attrName = identifier();
608
609 int op = TokenKind.NO_MATCH;
610 switch (_peek()) {
611 case TokenKind.EQUALS:
612 case TokenKind.INCLUDES: // ~=
613 case TokenKind.DASH_MATCH: // |=
614 case TokenKind.PREFIX_MATCH: // ^=
615 case TokenKind.SUFFIX_MATCH: // $=
616 case TokenKind.SUBSTRING_MATCH: // *=
617 op = _peek();
618 _next();
619 break;
620 }
621
622 String value;
623 if (op != TokenKind.NO_MATCH) {
624 // Operator hit so we require a value too.
625 if (_peekIdentifier()) {
626 value = identifier();
627 } else {
628 value = processQuotedString(false);
629 }
630
631 if (value == null) {
632 _error('expected attribute value string or ident', _peekToken.span);
633 }
634 }
635
636 _eat(TokenKind.RBRACK);
637
638 return new AttributeSelector(attrName, op, value, _makeSpan(start));
639 }
640 }
641
642 // Declaration grammar:
643 //
644 // declaration: property ':' expr prio?
645 //
646 // property: IDENT
647 // prio: !important
648 // expr: (see processExpr)
649 //
650 processDeclaration() {
651 Declaration decl;
652
653 int start = _peekToken.start;
654
655 // IDENT ':' expr '!important'?
656 if (TokenKind.isIdentifier(_peekToken.kind)) {
657 var propertyIdent = identifier();
658 _eat(TokenKind.COLON);
659
660 decl = new Declaration(propertyIdent, processExpr(), _makeSpan(start));
661
662 // Handle !important (prio)
663 decl.important = _maybeEat(TokenKind.IMPORTANT);
664 }
665
666 return decl;
667 }
668
669 // Expression grammar:
670 //
671 // expression: term [ operator? term]*
672 //
673 // operator: '/' | ','
674 // term: (see processTerm)
675 //
676 processExpr() {
677 int start = _peekToken.start;
678 Expressions expressions = new Expressions(_makeSpan(start));
679
680 bool keepGoing = true;
681 var expr;
682 while (keepGoing && (expr = processTerm()) != null) {
683 var op;
684
685 int opStart = _peekToken.start;
686
687 switch (_peek()) {
688 case TokenKind.SLASH:
689 op = new OperatorSlash(_makeSpan(opStart));
690 break;
691 case TokenKind.COMMA:
692 op = new OperatorComma(_makeSpan(opStart));
693 break;
694 }
695
696 if (expr != null) {
697 expressions.add(expr);
698 } else {
699 keepGoing = false;
700 }
701
702 if (op != null) {
703 expressions.add(op);
704 _next();
705 }
706 }
707
708 return expressions;
709 }
710
711 // Term grammar:
712 //
713 // term:
714 // unary_operator?
715 // [ term_value ]
716 // | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
717 //
718 // term_value:
719 // NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
720 // TIME S* | FREQ S* | function
721 //
722 // NUMBER: {num}
723 // PERCENTAGE: {num}%
724 // LENGTH: {num}['px' | 'cm' | 'mm' | 'in' | 'pt' | 'pc']
725 // EMS: {num}'em'
726 // EXS: {num}'ex'
727 // ANGLE: {num}['deg' | 'rad' | 'grad']
728 // TIME: {num}['ms' | 's']
729 // FREQ: {num}['hz' | 'khz']
730 // function: IDENT '(' expr ')'
731 //
732 processTerm() {
733 int start = _peekToken.start;
734 Token t; // token for term's value
735 var value; // value of term (numeric values)
736
737 var unary = "";
738
739 switch (_peek()) {
740 case TokenKind.HASH:
741 this._eat(TokenKind.HASH);
742 String hexText;
743 if (_peekKind(TokenKind.INTEGER)) {
744 String hexText1 = _peekToken.text;
745 _next();
746 if (_peekIdentifier()) {
747 hexText = '${hexText1}${identifier().name}';
748 } else {
749 hexText = hexText1;
750 }
751 } else if (_peekIdentifier()) {
752 hexText = identifier().name;
753 } else {
754 _errorExpected("hex number");
755 }
756
757 try {
758 int hexValue = parseHex(hexText);
759 return new HexColorTerm(hexValue, hexText, _makeSpan(start));
760 } on HexNumberException catch (hne) {
761 _error('Bad hex number', _makeSpan(start));
762 }
763 break;
764 case TokenKind.INTEGER:
765 t = _next();
766 value = int.parse("${unary}${t.text}");
767 break;
768 case TokenKind.DOUBLE:
769 t = _next();
770 value = double.parse("${unary}${t.text}");
771 break;
772 case TokenKind.SINGLE_QUOTE:
773 case TokenKind.DOUBLE_QUOTE:
774 value = processQuotedString(false);
775 value = '"${value}"';
776 return new LiteralTerm(value, value, _makeSpan(start));
777 case TokenKind.LPAREN:
778 _next();
779
780 GroupTerm group = new GroupTerm(_makeSpan(start));
781
782 do {
783 var term = processTerm();
784 if (term != null && term is LiteralTerm) {
785 group.add(term);
786 }
787 } while (!_maybeEat(TokenKind.RPAREN));
788
789 return group;
790 case TokenKind.LBRACK:
791 _next();
792
793 var term = processTerm();
794 if (!(term is NumberTerm)) {
795 _error('Expecting a positive number', _makeSpan(start));
796 }
797
798 _eat(TokenKind.RBRACK);
799
800 return new ItemTerm(term.value, term.text, _makeSpan(start));
801 case TokenKind.IDENTIFIER:
802 var nameValue = identifier(); // Snarf up the ident we'll remap, maybe.
803
804 if (_maybeEat(TokenKind.LPAREN)) {
805 // FUNCTION
806 return processFunction(nameValue);
807 } else {
808 // TODO(terry): Need to have a list of known identifiers today only
809 // 'from' is special.
810 if (nameValue.name == 'from') {
811 return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start));
812 }
813
814 // What kind of identifier is it?
815 try {
816 // Named color?
817 int colorValue = TokenKind.matchColorName(nameValue.name);
818
819 // Yes, process the color as an RGB value.
820 String rgbColor = TokenKind.decimalToHex(colorValue, 3);
821 try {
822 colorValue = parseHex(rgbColor);
823 } on HexNumberException catch (hne) {
824 _error('Bad hex number', _makeSpan(start));
825 }
826 return new HexColorTerm(colorValue, rgbColor, _makeSpan(start));
827 } catch (error) {
828 if (error is NoColorMatchException) {
829 // TODO(terry): Other named things to match with validator?
830
831 // TODO(terry): Disable call to _warning need one World class for
832 // both CSS parser and other parser (e.g., template)
833 // so all warnings, errors, options, etc. are driven
834 // from the one World.
835 // _warning('Unknown property value ${error.name}', _makeSpan(start));
836 return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start));
837 }
838 }
839 }
840 }
841
842 var term;
843 var unitType = this._peek();
844
845 switch (unitType) {
846 case TokenKind.UNIT_EM:
847 term = new EmTerm(value, t.text, _makeSpan(start));
848 _next(); // Skip the unit
849 break;
850 case TokenKind.UNIT_EX:
851 term = new ExTerm(value, t.text, _makeSpan(start));
852 _next(); // Skip the unit
853 break;
854 case TokenKind.UNIT_LENGTH_PX:
855 case TokenKind.UNIT_LENGTH_CM:
856 case TokenKind.UNIT_LENGTH_MM:
857 case TokenKind.UNIT_LENGTH_IN:
858 case TokenKind.UNIT_LENGTH_PT:
859 case TokenKind.UNIT_LENGTH_PC:
860 term = new LengthTerm(value, t.text, _makeSpan(start), unitType);
861 _next(); // Skip the unit
862 break;
863 case TokenKind.UNIT_ANGLE_DEG:
864 case TokenKind.UNIT_ANGLE_RAD:
865 case TokenKind.UNIT_ANGLE_GRAD:
866 term = new AngleTerm(value, t.text, _makeSpan(start), unitType);
867 _next(); // Skip the unit
868 break;
869 case TokenKind.UNIT_TIME_MS:
870 case TokenKind.UNIT_TIME_S:
871 term = new TimeTerm(value, t.text, _makeSpan(start), unitType);
872 _next(); // Skip the unit
873 break;
874 case TokenKind.UNIT_FREQ_HZ:
875 case TokenKind.UNIT_FREQ_KHZ:
876 term = new FreqTerm(value, t.text, _makeSpan(start), unitType);
877 _next(); // Skip the unit
878 break;
879 case TokenKind.PERCENT:
880 term = new PercentageTerm(value, t.text, _makeSpan(start));
881 _next(); // Skip the %
882 break;
883 case TokenKind.UNIT_FRACTION:
884 term = new FractionTerm(value, t.text, _makeSpan(start));
885 _next(); // Skip the unit
886 break;
887 default:
888 if (value != null) {
889 term = new NumberTerm(value, t.text, _makeSpan(start));
890 }
891 }
892
893 return term;
894 }
895
896 processQuotedString([bool urlString = false]) {
897 int start = _peekToken.start;
898
899 // URI term sucks up everything inside of quotes(' or ") or between parens
900 int stopToken = urlString ? TokenKind.RPAREN : -1;
901 switch (_peek()) {
902 case TokenKind.SINGLE_QUOTE:
903 stopToken = TokenKind.SINGLE_QUOTE;
904 _next(); // Skip the SINGLE_QUOTE.
905 break;
906 case TokenKind.DOUBLE_QUOTE:
907 stopToken = TokenKind.DOUBLE_QUOTE;
908 _next(); // Skip the DOUBLE_QUOTE.
909 break;
910 default:
911 if (urlString) {
912 if (_peek() == TokenKind.LPAREN) {
913 _next(); // Skip the LPAREN.
914 }
915 stopToken = TokenKind.RPAREN;
916 } else {
917 _error('unexpected string', _makeSpan(start));
918 }
919 }
920
921 StringBuffer stringValue = new StringBuffer();
922
923 // Gobble up everything until we hit our stop token.
924 int runningStart = _peekToken.start;
925 while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) {
926 var tok = _next();
927 stringValue.write(tok.text);
928 }
929
930 if (stopToken != TokenKind.RPAREN) {
931 _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE;
932 }
933
934 return stringValue.toString();
935 }
936
937 // Function grammar:
938 //
939 // function: IDENT '(' expr ')'
940 //
941 processFunction(Identifier func) {
942 int start = _peekToken.start;
943
944 String name = func.name;
945
946 switch (name) {
947 case 'url':
948 // URI term sucks up everything inside of quotes(' or ") or between parens
949 String urlParam = processQuotedString(true);
950
951 // TODO(terry): Better error messge and checking for mismatched quotes.
952 if (_peek() == TokenKind.END_OF_FILE) {
953 _error("problem parsing URI", _peekToken.span);
954 }
955
956 if (_peek() == TokenKind.RPAREN) {
957 _next();
958 }
959
960 return new UriTerm(urlParam, _makeSpan(start));
961 case 'calc':
962 // TODO(terry): Implement expression handling...
963 break;
964 default:
965 var expr = processExpr();
966 if (!_maybeEat(TokenKind.RPAREN)) {
967 _error("problem parsing function expected ), ", _peekToken.span);
968 }
969
970 return new FunctionTerm(name, name, expr, _makeSpan(start));
971 }
972
973 return null;
974 }
975
976 identifier() {
977 var tok = _next();
978 if (!TokenKind.isIdentifier(tok.kind)) {
979 _error('expected identifier, but found $tok', tok.span);
980 }
981
982 return new Identifier(tok.text, _makeSpan(tok.start));
983 }
984
985 // TODO(terry): Move this to base <= 36 and into shared code.
986 static int _hexDigit(int c) {
987 if(c >= 48/*0*/ && c <= 57/*9*/) {
988 return c - 48;
989 } else if (c >= 97/*a*/ && c <= 102/*f*/) {
990 return c - 87;
991 } else if (c >= 65/*A*/ && c <= 70/*F*/) {
992 return c - 55;
993 } else {
994 return -1;
995 }
996 }
997
998 static int parseHex(String hex) {
999 var result = 0;
1000
1001 for (int i = 0; i < hex.length; i++) {
1002 var digit = _hexDigit(hex.codeUnitAt(i));
1003 if (digit < 0) {
1004 throw new HexNumberException();
1005 }
1006 result = (result << 4) + digit;
1007 }
1008
1009 return result;
1010 }
1011 }
1012
1013 /** Not a hex number. */
1014 class HexNumberException implements Exception {
1015 HexNumberException();
1016 }
1017
OLDNEW
« no previous file with comments | « utils/css/generate.dart ('k') | utils/css/source.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698