UNPKG

45.1 kBJavaScriptView Raw
1/*!
2 * Stylus - Parser
3 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
4 * MIT Licensed
5 */
6
7/**
8 * Module dependencies.
9 */
10
11var Lexer = require('./lexer')
12 , nodes = require('./nodes')
13 , Token = require('./token')
14 , units = require('./units')
15 , errors = require('./errors')
16 , cache = require('./cache');
17
18// debuggers
19
20var debug = {
21 lexer: require('debug')('stylus:lexer')
22 , selector: require('debug')('stylus:parser:selector')
23};
24
25/**
26 * Selector composite tokens.
27 */
28
29var selectorTokens = [
30 'ident'
31 , 'string'
32 , 'selector'
33 , 'function'
34 , 'comment'
35 , 'boolean'
36 , 'space'
37 , 'color'
38 , 'unit'
39 , 'for'
40 , 'in'
41 , '['
42 , ']'
43 , '('
44 , ')'
45 , '+'
46 , '-'
47 , '*'
48 , '*='
49 , '<'
50 , '>'
51 , '='
52 , ':'
53 , '&'
54 , '~'
55 , '{'
56 , '}'
57 , '.'
58 , '/'
59];
60
61/**
62 * CSS pseudo-classes and pseudo-elements.
63 * See http://dev.w3.org/csswg/selectors4/
64 */
65
66var pseudoSelectors = [
67 // Logical Combinations
68 'matches'
69 , 'not'
70
71 // Linguistic Pseudo-classes
72 , 'dir'
73 , 'lang'
74
75 // Location Pseudo-classes
76 , 'any-link'
77 , 'link'
78 , 'visited'
79 , 'local-link'
80 , 'target'
81 , 'scope'
82
83 // User Action Pseudo-classes
84 , 'hover'
85 , 'active'
86 , 'focus'
87 , 'drop'
88
89 // Time-dimensional Pseudo-classes
90 , 'current'
91 , 'past'
92 , 'future'
93
94 // The Input Pseudo-classes
95 , 'enabled'
96 , 'disabled'
97 , 'read-only'
98 , 'read-write'
99 , 'placeholder-shown'
100 , 'checked'
101 , 'indeterminate'
102 , 'valid'
103 , 'invalid'
104 , 'in-range'
105 , 'out-of-range'
106 , 'required'
107 , 'optional'
108 , 'user-error'
109
110 // Tree-Structural pseudo-classes
111 , 'root'
112 , 'empty'
113 , 'blank'
114 , 'nth-child'
115 , 'nth-last-child'
116 , 'first-child'
117 , 'last-child'
118 , 'only-child'
119 , 'nth-of-type'
120 , 'nth-last-of-type'
121 , 'first-of-type'
122 , 'last-of-type'
123 , 'only-of-type'
124 , 'nth-match'
125 , 'nth-last-match'
126
127 // Grid-Structural Selectors
128 , 'nth-column'
129 , 'nth-last-column'
130
131 // Pseudo-elements
132 , 'first-line'
133 , 'first-letter'
134 , 'before'
135 , 'after'
136
137 // Non-standard
138 , 'selection'
139];
140
141/**
142 * Initialize a new `Parser` with the given `str` and `options`.
143 *
144 * @param {String} str
145 * @param {Object} options
146 * @api private
147 */
148
149var Parser = module.exports = function Parser(str, options) {
150 var self = this;
151 options = options || {};
152 Parser.cache = Parser.cache || Parser.getCache(options);
153 this.hash = Parser.cache.key(str, options);
154 this.lexer = {};
155 if (!Parser.cache.has(this.hash)) {
156 this.lexer = new Lexer(str, options);
157 }
158 this.prefix = options.prefix || '';
159 this.root = options.root || new nodes.Root;
160 this.state = ['root'];
161 this.stash = [];
162 this.parens = 0;
163 this.css = 0;
164 this.state.pop = function(){
165 self.prevState = [].pop.call(this);
166 };
167};
168
169/**
170 * Get cache instance.
171 *
172 * @param {Object} options
173 * @return {Object}
174 * @api private
175 */
176
177Parser.getCache = function(options) {
178 return false === options.cache
179 ? cache(false)
180 : cache(options.cache || 'memory', options);
181};
182
183/**
184 * Parser prototype.
185 */
186
187Parser.prototype = {
188
189 /**
190 * Constructor.
191 */
192
193 constructor: Parser,
194
195 /**
196 * Return current state.
197 *
198 * @return {String}
199 * @api private
200 */
201
202 currentState: function() {
203 return this.state[this.state.length - 1];
204 },
205
206 /**
207 * Return previous state.
208 *
209 * @return {String}
210 * @api private
211 */
212
213 previousState: function() {
214 return this.state[this.state.length - 2];
215 },
216
217 /**
218 * Parse the input, then return the root node.
219 *
220 * @return {Node}
221 * @api private
222 */
223
224 parse: function(){
225 var block = this.parent = this.root;
226 if (Parser.cache.has(this.hash)) {
227 block = Parser.cache.get(this.hash);
228 // normalize cached imports
229 if ('block' == block.nodeName) block.constructor = nodes.Root;
230 } else {
231 while ('eos' != this.peek().type) {
232 this.skipWhitespace();
233 if ('eos' == this.peek().type) break;
234 var stmt = this.statement();
235 this.accept(';');
236 if (!stmt) this.error('unexpected token {peek}, not allowed at the root level');
237 block.push(stmt);
238 }
239 Parser.cache.set(this.hash, block);
240 }
241 return block;
242 },
243
244 /**
245 * Throw an `Error` with the given `msg`.
246 *
247 * @param {String} msg
248 * @api private
249 */
250
251 error: function(msg){
252 var type = this.peek().type
253 , val = undefined == this.peek().val
254 ? ''
255 : ' ' + this.peek().toString();
256 if (val.trim() == type.trim()) val = '';
257 throw new errors.ParseError(msg.replace('{peek}', '"' + type + val + '"'));
258 },
259
260 /**
261 * Accept the given token `type`, and return it,
262 * otherwise return `undefined`.
263 *
264 * @param {String} type
265 * @return {Token}
266 * @api private
267 */
268
269 accept: function(type){
270 if (type == this.peek().type) {
271 return this.next();
272 }
273 },
274
275 /**
276 * Expect token `type` and return it, throw otherwise.
277 *
278 * @param {String} type
279 * @return {Token}
280 * @api private
281 */
282
283 expect: function(type){
284 if (type != this.peek().type) {
285 this.error('expected "' + type + '", got {peek}');
286 }
287 return this.next();
288 },
289
290 /**
291 * Get the next token.
292 *
293 * @return {Token}
294 * @api private
295 */
296
297 next: function() {
298 var tok = this.stash.length
299 ? this.stash.pop()
300 : this.lexer.next()
301 , line = tok.lineno
302 , column = tok.column || 1;
303
304 if (tok.val && tok.val.nodeName) {
305 tok.val.lineno = line;
306 tok.val.column = column;
307 }
308 nodes.lineno = line;
309 nodes.column = column;
310 debug.lexer('%s %s', tok.type, tok.val || '');
311 return tok;
312 },
313
314 /**
315 * Peek with lookahead(1).
316 *
317 * @return {Token}
318 * @api private
319 */
320
321 peek: function() {
322 return this.lexer.peek();
323 },
324
325 /**
326 * Lookahead `n` tokens.
327 *
328 * @param {Number} n
329 * @return {Token}
330 * @api private
331 */
332
333 lookahead: function(n){
334 return this.lexer.lookahead(n);
335 },
336
337 /**
338 * Check if the token at `n` is a valid selector token.
339 *
340 * @param {Number} n
341 * @return {Boolean}
342 * @api private
343 */
344
345 isSelectorToken: function(n) {
346 var la = this.lookahead(n).type;
347 switch (la) {
348 case 'for':
349 return this.bracketed;
350 case '[':
351 this.bracketed = true;
352 return true;
353 case ']':
354 this.bracketed = false;
355 return true;
356 default:
357 return ~selectorTokens.indexOf(la);
358 }
359 },
360
361 /**
362 * Check if the token at `n` is a pseudo selector.
363 *
364 * @param {Number} n
365 * @return {Boolean}
366 * @api private
367 */
368
369 isPseudoSelector: function(n){
370 var val = this.lookahead(n).val;
371 return val && ~pseudoSelectors.indexOf(val.name);
372 },
373
374 /**
375 * Check if the current line contains `type`.
376 *
377 * @param {String} type
378 * @return {Boolean}
379 * @api private
380 */
381
382 lineContains: function(type){
383 var i = 1
384 , la;
385
386 while (la = this.lookahead(i++)) {
387 if (~['indent', 'outdent', 'newline', 'eos'].indexOf(la.type)) return;
388 if (type == la.type) return true;
389 }
390 },
391
392 /**
393 * Valid selector tokens.
394 */
395
396 selectorToken: function() {
397 if (this.isSelectorToken(1)) {
398 if ('{' == this.peek().type) {
399 // unclosed, must be a block
400 if (!this.lineContains('}')) return;
401 // check if ':' is within the braces.
402 // though not required by Stylus, chances
403 // are if someone is using {} they will
404 // use CSS-style props, helping us with
405 // the ambiguity in this case
406 var i = 0
407 , la;
408 while (la = this.lookahead(++i)) {
409 if ('}' == la.type) {
410 // Check empty block.
411 if (i == 2 || (i == 3 && this.lookahead(i - 1).type == 'space'))
412 return;
413 break;
414 }
415 if (':' == la.type) return;
416 }
417 }
418 return this.next();
419 }
420 },
421
422 /**
423 * Skip the given `tokens`.
424 *
425 * @param {Array} tokens
426 * @api private
427 */
428
429 skip: function(tokens) {
430 while (~tokens.indexOf(this.peek().type))
431 this.next();
432 },
433
434 /**
435 * Consume whitespace.
436 */
437
438 skipWhitespace: function() {
439 this.skip(['space', 'indent', 'outdent', 'newline']);
440 },
441
442 /**
443 * Consume newlines.
444 */
445
446 skipNewlines: function() {
447 while ('newline' == this.peek().type)
448 this.next();
449 },
450
451 /**
452 * Consume spaces.
453 */
454
455 skipSpaces: function() {
456 while ('space' == this.peek().type)
457 this.next();
458 },
459
460 /**
461 * Consume spaces and comments.
462 */
463
464 skipSpacesAndComments: function() {
465 while ('space' == this.peek().type
466 || 'comment' == this.peek().type)
467 this.next();
468 },
469
470 /**
471 * Check if the following sequence of tokens
472 * forms a function definition, ie trailing
473 * `{` or indentation.
474 */
475
476 looksLikeFunctionDefinition: function(i) {
477 return 'indent' == this.lookahead(i).type
478 || '{' == this.lookahead(i).type;
479 },
480
481 /**
482 * Check if the following sequence of tokens
483 * forms a selector.
484 *
485 * @param {Boolean} [fromProperty]
486 * @return {Boolean}
487 * @api private
488 */
489
490 looksLikeSelector: function(fromProperty) {
491 var i = 1
492 , brace;
493
494 // Real property
495 if (fromProperty && ':' == this.lookahead(i + 1).type
496 && (this.lookahead(i + 1).space || 'indent' == this.lookahead(i + 2).type))
497 return false;
498
499 // Assume selector when an ident is
500 // followed by a selector
501 while ('ident' == this.lookahead(i).type
502 && 'newline' == this.lookahead(i + 1).type) i += 2;
503
504 while (this.isSelectorToken(i)
505 || ',' == this.lookahead(i).type) {
506
507 if ('selector' == this.lookahead(i).type)
508 return true;
509
510 if ('.' == this.lookahead(i).type && 'ident' == this.lookahead(i + 1).type)
511 return true;
512
513 if ('*' == this.lookahead(i).type && 'newline' == this.lookahead(i + 1).type)
514 return true;
515
516 if (':' == this.lookahead(i).type
517 && ':' == this.lookahead(i + 1).type)
518 return true;
519
520 if (this.looksLikeAttributeSelector(i))
521 return true;
522
523 if (('=' == this.lookahead(i).type || 'function' == this.lookahead(i).type)
524 && '{' == this.lookahead(i + 1).type)
525 return false;
526
527 if (':' == this.lookahead(i).type
528 && !this.isPseudoSelector(i + 1)
529 && this.lineContains('.'))
530 return false;
531
532 // the ':' token within braces signifies
533 // a selector. ex: "foo{bar:'baz'}"
534 if ('{' == this.lookahead(i).type) brace = true;
535 else if ('}' == this.lookahead(i).type) brace = false;
536 if (brace && ':' == this.lookahead(i).type) return true;
537
538 // '}' preceded by a space is considered a selector.
539 // for example "foo{bar}{baz}" may be a property,
540 // however "foo{bar} {baz}" is a selector
541 if ('space' == this.lookahead(i).type
542 && '{' == this.lookahead(i + 1).type)
543 return true;
544
545 // Assume pseudo selectors are NOT properties
546 // as 'td:th-child(1)' may look like a property
547 // and function call to the parser otherwise
548 if (':' == this.lookahead(i++).type
549 && !this.lookahead(i-1).space
550 && this.isPseudoSelector(i))
551 return true;
552
553 if (',' == this.lookahead(i).type
554 && 'newline' == this.lookahead(i + 1).type)
555 return true;
556 }
557
558 // Trailing comma
559 if (',' == this.lookahead(i).type
560 && 'newline' == this.lookahead(i + 1).type)
561 return true;
562
563 // Trailing brace
564 if ('{' == this.lookahead(i).type
565 && 'newline' == this.lookahead(i + 1).type)
566 return true;
567
568 // css-style mode, false on ; }
569 if (this.css) {
570 if (';' == this.lookahead(i).type ||
571 '}' == this.lookahead(i - 1).type)
572 return false;
573 }
574
575 // Trailing separators
576 while (!~[
577 'indent'
578 , 'outdent'
579 , 'newline'
580 , 'for'
581 , 'if'
582 , ';'
583 , '}'
584 , 'eos'].indexOf(this.lookahead(i).type))
585 ++i;
586
587 if ('indent' == this.lookahead(i).type)
588 return true;
589 },
590
591 /**
592 * Check if the following sequence of tokens
593 * forms an attribute selector.
594 */
595
596 looksLikeAttributeSelector: function(n) {
597 var type = this.lookahead(n).type;
598 if ('=' == type && this.bracketed) return true;
599 return ('ident' == type || 'string' == type)
600 && ']' == this.lookahead(n + 1).type
601 && ('newline' == this.lookahead(n + 2).type || this.isSelectorToken(n + 2))
602 && !this.lineContains(':')
603 && !this.lineContains('=');
604 },
605
606 /**
607 * Check if the following sequence of tokens
608 * forms a keyframe block.
609 */
610
611 looksLikeKeyframe: function() {
612 var i = 2
613 , type;
614 switch (this.lookahead(i).type) {
615 case '{':
616 case 'indent':
617 case ',':
618 return true;
619 case 'newline':
620 while ('unit' == this.lookahead(++i).type
621 || 'newline' == this.lookahead(i).type) ;
622 type = this.lookahead(i).type;
623 return 'indent' == type || '{' == type;
624 }
625 },
626
627 /**
628 * Check if the current state supports selectors.
629 */
630
631 stateAllowsSelector: function() {
632 switch (this.currentState()) {
633 case 'root':
634 case 'atblock':
635 case 'selector':
636 case 'conditional':
637 case 'function':
638 case 'media':
639 case 'atrule':
640 case 'for':
641 return true;
642 }
643 },
644
645 /**
646 * Try to assign @block to the node.
647 *
648 * @param {Expression} expr
649 * @private
650 */
651
652 assignAtblock: function(expr) {
653 try {
654 expr.push(this.atblock(expr));
655 } catch(err) {
656 this.error('invalid right-hand side operand in assignment, got {peek}');
657 }
658 },
659
660 /**
661 * statement
662 * | statement 'if' expression
663 * | statement 'unless' expression
664 */
665
666 statement: function() {
667 var stmt = this.stmt()
668 , state = this.prevState
669 , block
670 , op;
671
672 // special-case statements since it
673 // is not an expression. We could
674 // implement postfix conditionals at
675 // the expression level, however they
676 // would then fail to enclose properties
677 if (this.allowPostfix) {
678 this.allowPostfix = false;
679 state = 'expression';
680 }
681
682 switch (state) {
683 case 'assignment':
684 case 'expression':
685 case 'function arguments':
686 while (op =
687 this.accept('if')
688 || this.accept('unless')
689 || this.accept('for')) {
690 switch (op.type) {
691 case 'if':
692 case 'unless':
693 stmt = new nodes.If(this.expression(), stmt);
694 stmt.postfix = true;
695 stmt.negate = 'unless' == op.type;
696 this.accept(';');
697 break;
698 case 'for':
699 var key
700 , val = this.id().name;
701 if (this.accept(',')) key = this.id().name;
702 this.expect('in');
703 var each = new nodes.Each(val, key, this.expression());
704 block = new nodes.Block;
705 block.push(stmt);
706 each.block = block;
707 stmt = each;
708 }
709 }
710 }
711
712 return stmt;
713 },
714
715 /**
716 * ident
717 * | selector
718 * | literal
719 * | charset
720 * | namespace
721 * | import
722 * | require
723 * | media
724 * | atrule
725 * | scope
726 * | keyframes
727 * | mozdocument
728 * | for
729 * | if
730 * | unless
731 * | comment
732 * | expression
733 * | 'return' expression
734 */
735
736 stmt: function() {
737 var type = this.peek().type;
738 switch (type) {
739 case 'keyframes':
740 return this.keyframes();
741 case '-moz-document':
742 return this.mozdocument();
743 case 'comment':
744 case 'selector':
745 case 'literal':
746 case 'charset':
747 case 'namespace':
748 case 'import':
749 case 'require':
750 case 'extend':
751 case 'media':
752 case 'atrule':
753 case 'ident':
754 case 'scope':
755 case 'unless':
756 case 'function':
757 case 'for':
758 case 'if':
759 return this[type]();
760 case 'return':
761 return this.return();
762 case '{':
763 return this.property();
764 default:
765 // Contextual selectors
766 if (this.stateAllowsSelector()) {
767 switch (type) {
768 case 'color':
769 case '~':
770 case '>':
771 case '<':
772 case ':':
773 case '&':
774 case '[':
775 case '.':
776 case '/':
777 return this.selector();
778 case '+':
779 return 'function' == this.lookahead(2).type
780 ? this.functionCall()
781 : this.selector();
782 case '*':
783 return this.property();
784 // keyframe blocks (10%, 20% { ... })
785 case 'unit':
786 if (this.looksLikeKeyframe()) return this.selector();
787 case '-':
788 if ('{' == this.lookahead(2).type)
789 return this.property();
790 }
791 }
792
793 // Expression fallback
794 var expr = this.expression();
795 if (expr.isEmpty) this.error('unexpected {peek}');
796 return expr;
797 }
798 },
799
800 /**
801 * indent (!outdent)+ outdent
802 */
803
804 block: function(node, scope) {
805 var delim
806 , stmt
807 , block = this.parent = new nodes.Block(this.parent, node);
808
809 if (false === scope) block.scope = false;
810
811 this.accept('newline');
812
813 // css-style
814 if (this.accept('{')) {
815 this.css++;
816 delim = '}';
817 this.skipWhitespace();
818 } else {
819 delim = 'outdent';
820 this.expect('indent');
821 }
822
823 while (delim != this.peek().type) {
824 // css-style
825 if (this.css) {
826 if (this.accept('newline') || this.accept('indent')) continue;
827 stmt = this.statement();
828 this.accept(';');
829 this.skipWhitespace();
830 } else {
831 if (this.accept('newline') || this.accept('indent')) continue;
832 stmt = this.statement();
833 this.accept(';');
834 }
835 if (!stmt) this.error('unexpected token {peek} in block');
836 block.push(stmt);
837 }
838
839 // css-style
840 if (this.css) {
841 this.skipWhitespace();
842 this.expect('}');
843 this.skipSpaces();
844 this.css--;
845 } else {
846 this.expect('outdent');
847 }
848
849 this.parent = block.parent;
850 return block;
851 },
852
853 /**
854 * comment space*
855 */
856
857 comment: function(){
858 var node = this.next().val;
859 this.skipSpaces();
860 return node;
861 },
862
863 /**
864 * for val (',' key) in expr
865 */
866
867 for: function() {
868 this.expect('for');
869 var key
870 , val = this.id().name;
871 if (this.accept(',')) key = this.id().name;
872 this.expect('in');
873 this.state.push('for');
874 this.cond = true;
875 var each = new nodes.Each(val, key, this.expression());
876 this.cond = false;
877 each.block = this.block(each, false);
878 this.state.pop();
879 return each;
880 },
881
882 /**
883 * return expression
884 */
885
886 return: function() {
887 this.expect('return');
888 var expr = this.expression();
889 return expr.isEmpty
890 ? new nodes.Return
891 : new nodes.Return(expr);
892 },
893
894 /**
895 * unless expression block
896 */
897
898 unless: function() {
899 this.expect('unless');
900 this.state.push('conditional');
901 this.cond = true;
902 var node = new nodes.If(this.expression(), true);
903 this.cond = false;
904 node.block = this.block(node, false);
905 this.state.pop();
906 return node;
907 },
908
909 /**
910 * if expression block (else block)?
911 */
912
913 if: function() {
914 this.expect('if');
915 this.state.push('conditional');
916 this.cond = true;
917 var node = new nodes.If(this.expression())
918 , cond
919 , block;
920 this.cond = false;
921 node.block = this.block(node, false);
922 // ignore newlines and comments
923 while ('newline' == this.peek().type
924 || 'comment' == this.peek().type) this.next();
925 while (this.accept('else')) {
926 if (this.accept('if')) {
927 this.cond = true;
928 cond = this.expression();
929 this.cond = false;
930 block = this.block(node, false);
931 node.elses.push(new nodes.If(cond, block));
932 } else {
933 node.elses.push(this.block(node, false));
934 break;
935 }
936 }
937 this.state.pop();
938 return node;
939 },
940
941 /**
942 * @block
943 *
944 * @param {Expression} [node]
945 */
946
947 atblock: function(node){
948 if (!node) this.expect('atblock');
949 node = new nodes.Atblock;
950 this.state.push('atblock');
951 node.block = this.block(node, false);
952 this.state.pop();
953 return node;
954 },
955
956 /**
957 * atrule selector? block
958 */
959
960 atrule: function(){
961 var type = this.expect('atrule').val
962 , node = new nodes.Atrule(type);
963 this.skipSpacesAndComments();
964 node.segments = this.selectorParts();
965 this.skipSpacesAndComments();
966 this.state.push('atrule');
967 node.block = this.block(node);
968 this.state.pop();
969 return node;
970 },
971
972 /**
973 * scope
974 */
975
976 scope: function(){
977 this.expect('scope');
978 var selector = this.selectorParts()
979 .map(function(selector) { return selector.val; })
980 .join('');
981 this.selectorScope = selector.trim();
982 return nodes.null;
983 },
984
985 /**
986 * extend
987 */
988
989 extend: function(){
990 var tok = this.expect('extend')
991 , selectors = []
992 , node
993 , arr;
994
995 do {
996 arr = this.selectorParts();
997 if (arr.length) selectors.push(new nodes.Selector(arr));
998 } while(this.accept(','));
999
1000 node = new nodes.Extend(selectors);
1001 node.lineno = tok.lineno;
1002 node.column = tok.column;
1003 return node;
1004 },
1005
1006 /**
1007 * media queries
1008 */
1009
1010 media: function() {
1011 this.expect('media');
1012 this.state.push('media');
1013 var media = new nodes.Media(this.queries());
1014 media.block = this.block(media);
1015 this.state.pop();
1016 return media;
1017 },
1018
1019 /**
1020 * query (',' query)*
1021 */
1022
1023 queries: function() {
1024 var queries = new nodes.QueryList
1025 , skip = ['comment', 'newline', 'space'];
1026
1027 do {
1028 this.skip(skip);
1029 queries.push(this.query());
1030 this.skip(skip);
1031 } while (this.accept(','));
1032 return queries;
1033 },
1034
1035 /**
1036 * expression
1037 * | (ident | 'not')? ident ('and' queryExpr)*
1038 * | queryExpr ('and' queryExpr)*
1039 */
1040
1041 query: function() {
1042 var query = new nodes.Query
1043 , expr
1044 , pred
1045 , id;
1046
1047 // hash values support
1048 if ('ident' == this.peek().type
1049 && ('.' == this.lookahead(2).type
1050 || '[' == this.lookahead(2).type)) {
1051 this.cond = true;
1052 expr = this.expression();
1053 this.cond = false;
1054 query.push(new nodes.QueryExpr(expr.nodes));
1055 return query;
1056 }
1057
1058 if (pred = this.accept('ident') || this.accept('not')) {
1059 pred = new nodes.Literal(pred.val.string || pred.val);
1060
1061 this.skipSpacesAndComments();
1062 if (id = this.accept('ident')) {
1063 query.type = id.val;
1064 query.predicate = pred;
1065 } else {
1066 query.type = pred;
1067 }
1068 this.skipSpacesAndComments();
1069
1070 if (!this.accept('&&')) return query;
1071 }
1072
1073 do {
1074 query.push(this.queryExpr());
1075 } while (this.accept('&&'));
1076
1077 return query;
1078 },
1079
1080 /**
1081 * '(' ident ( ':'? expression )? ')'
1082 */
1083
1084 queryExpr: function() {
1085 this.skipSpacesAndComments();
1086 this.expect('(');
1087 this.skipSpacesAndComments();
1088 var node = new nodes.QueryExpr(this.interpolate());
1089 this.skipSpacesAndComments();
1090 this.accept(':')
1091 this.skipSpacesAndComments();
1092 this.inProperty = true;
1093 node.expr = this.expression();
1094 this.inProperty = false;
1095 this.skipSpacesAndComments();
1096 this.expect(')');
1097 this.skipSpacesAndComments();
1098 return node;
1099 },
1100
1101 /**
1102 * @-moz-document call (',' call)* block
1103 */
1104
1105 mozdocument: function(){
1106 this.expect('-moz-document');
1107 var mozdocument = new nodes.Atrule('-moz-document')
1108 , calls = [];
1109 do {
1110 this.skipSpacesAndComments();
1111 calls.push(this.functionCall());
1112 this.skipSpacesAndComments();
1113 } while (this.accept(','));
1114 mozdocument.segments = [new nodes.Literal(calls.join(', '))];
1115 this.state.push('atrule');
1116 mozdocument.block = this.block(mozdocument, false);
1117 this.state.pop();
1118 return mozdocument;
1119 },
1120
1121 /**
1122 * import expression
1123 */
1124
1125 import: function() {
1126 this.expect('import');
1127 this.allowPostfix = true;
1128 return new nodes.Import(this.expression(), false);
1129 },
1130
1131 /**
1132 * require expression
1133 */
1134
1135 require: function() {
1136 this.expect('require');
1137 this.allowPostfix = true;
1138 return new nodes.Import(this.expression(), true);
1139 },
1140
1141 /**
1142 * charset string
1143 */
1144
1145 charset: function() {
1146 this.expect('charset');
1147 var str = this.expect('string').val;
1148 this.allowPostfix = true;
1149 return new nodes.Charset(str);
1150 },
1151
1152 /**
1153 * namespace ident? string
1154 */
1155
1156 namespace: function() {
1157 var str
1158 , prefix;
1159 this.expect('namespace');
1160
1161 this.skipSpacesAndComments();
1162 if (prefix = this.accept('ident')) {
1163 prefix = prefix.val;
1164 }
1165 this.skipSpacesAndComments();
1166
1167 str = this.expect('string').val;
1168 this.allowPostfix = true;
1169 return new nodes.Namespace(str, prefix);
1170 },
1171
1172 /**
1173 * keyframes name block
1174 */
1175
1176 keyframes: function() {
1177 var tok = this.expect('keyframes')
1178 , keyframes;
1179
1180 this.skipSpacesAndComments();
1181 keyframes = new nodes.Keyframes(this.interpolate(), tok.val);
1182 this.skipSpacesAndComments();
1183
1184 // block
1185 this.state.push('atrule');
1186 keyframes.block = this.block(keyframes);
1187 this.state.pop();
1188
1189 return keyframes;
1190 },
1191
1192 /**
1193 * literal
1194 */
1195
1196 literal: function() {
1197 return this.expect('literal').val;
1198 },
1199
1200 /**
1201 * ident space?
1202 */
1203
1204 id: function() {
1205 var tok = this.expect('ident');
1206 this.accept('space');
1207 return tok.val;
1208 },
1209
1210 /**
1211 * ident
1212 * | assignment
1213 * | property
1214 * | selector
1215 */
1216
1217 ident: function() {
1218 var i = 2
1219 , la = this.lookahead(i).type;
1220
1221 while ('space' == la) la = this.lookahead(++i).type;
1222
1223 switch (la) {
1224 // Assignment
1225 case '=':
1226 case '?=':
1227 case '-=':
1228 case '+=':
1229 case '*=':
1230 case '/=':
1231 case '%=':
1232 return this.assignment();
1233 // Member
1234 case '.':
1235 if ('space' == this.lookahead(i - 1).type) return this.selector();
1236 if (this._ident == this.peek()) return this.id();
1237 while ('=' != this.lookahead(++i).type
1238 && !~['[', ',', 'newline', 'indent', 'eos'].indexOf(this.lookahead(i).type)) ;
1239 if ('=' == this.lookahead(i).type) {
1240 this._ident = this.peek();
1241 return this.expression();
1242 } else if (this.looksLikeSelector() && this.stateAllowsSelector()) {
1243 return this.selector();
1244 }
1245 // Assignment []=
1246 case '[':
1247 if (this._ident == this.peek()) return this.id();
1248 while (']' != this.lookahead(i++).type
1249 && 'selector' != this.lookahead(i).type
1250 && 'eos' != this.lookahead(i).type) ;
1251 if ('=' == this.lookahead(i).type) {
1252 this._ident = this.peek();
1253 return this.expression();
1254 } else if (this.looksLikeSelector() && this.stateAllowsSelector()) {
1255 return this.selector();
1256 }
1257 // Operation
1258 case '-':
1259 case '+':
1260 case '/':
1261 case '*':
1262 case '%':
1263 case '**':
1264 case 'and':
1265 case 'or':
1266 case '&&':
1267 case '||':
1268 case '>':
1269 case '<':
1270 case '>=':
1271 case '<=':
1272 case '!=':
1273 case '==':
1274 case '?':
1275 case 'in':
1276 case 'is a':
1277 case 'is defined':
1278 // Prevent cyclic .ident, return literal
1279 if (this._ident == this.peek()) {
1280 return this.id();
1281 } else {
1282 this._ident = this.peek();
1283 switch (this.currentState()) {
1284 // unary op or selector in property / for
1285 case 'for':
1286 case 'selector':
1287 return this.property();
1288 // Part of a selector
1289 case 'root':
1290 case 'media':
1291 case 'atblock':
1292 case 'atrule':
1293 return '[' == la
1294 ? this.subscript()
1295 : this.selector();
1296 case 'function':
1297 case 'conditional':
1298 return this.looksLikeSelector()
1299 ? this.selector()
1300 : this.expression();
1301 // Do not disrupt the ident when an operand
1302 default:
1303 return this.operand
1304 ? this.id()
1305 : this.expression();
1306 }
1307 }
1308 // Selector or property
1309 default:
1310 switch (this.currentState()) {
1311 case 'root':
1312 return this.selector();
1313 case 'for':
1314 case 'media':
1315 case 'selector':
1316 case 'function':
1317 case 'conditional':
1318 case 'atblock':
1319 case 'atrule':
1320 return this.property();
1321 default:
1322 var id = this.id();
1323 if ('interpolation' == this.previousState()) id.mixin = true;
1324 return id;
1325 }
1326 }
1327 },
1328
1329 /**
1330 * '*'? (ident | '{' expression '}')+
1331 */
1332
1333 interpolate: function() {
1334 var node
1335 , segs = []
1336 , star;
1337
1338 star = this.accept('*');
1339 if (star) segs.push(new nodes.Literal('*'));
1340
1341 while (true) {
1342 if (this.accept('{')) {
1343 this.state.push('interpolation');
1344 segs.push(this.expression());
1345 this.expect('}');
1346 this.state.pop();
1347 } else if (node = this.accept('-')){
1348 segs.push(new nodes.Literal('-'));
1349 } else if (node = this.accept('ident')){
1350 segs.push(node.val);
1351 } else {
1352 break;
1353 }
1354 }
1355 if (!segs.length) this.expect('ident');
1356 return segs;
1357 },
1358
1359 /**
1360 * property ':'? expression
1361 * | ident
1362 */
1363
1364 property: function() {
1365 if (this.looksLikeSelector(true)) return this.selector();
1366
1367 // property
1368 var ident = this.interpolate()
1369 , prop = new nodes.Property(ident)
1370 , ret = prop;
1371
1372 // optional ':'
1373 this.accept('space');
1374 if (this.accept(':')) this.accept('space');
1375
1376 this.state.push('property');
1377 this.inProperty = true;
1378 prop.expr = this.list();
1379 if (prop.expr.isEmpty) ret = ident[0];
1380 this.inProperty = false;
1381 this.allowPostfix = true;
1382 this.state.pop();
1383
1384 // optional ';'
1385 this.accept(';');
1386
1387 return ret;
1388 },
1389
1390 /**
1391 * selector ',' selector
1392 * | selector newline selector
1393 * | selector block
1394 */
1395
1396 selector: function() {
1397 var arr
1398 , group = new nodes.Group
1399 , scope = this.selectorScope
1400 , isRoot = 'root' == this.currentState()
1401 , selector;
1402
1403 do {
1404 // Clobber newline after ,
1405 this.accept('newline');
1406
1407 arr = this.selectorParts();
1408
1409 // Push the selector
1410 if (isRoot && scope) arr.unshift(new nodes.Literal(scope + ' '));
1411 if (arr.length) {
1412 selector = new nodes.Selector(arr);
1413 selector.lineno = arr[0].lineno;
1414 selector.column = arr[0].column;
1415 group.push(selector);
1416 }
1417 } while (this.accept(',') || this.accept('newline'));
1418
1419 this.state.push('selector');
1420 group.block = this.block(group);
1421 this.state.pop();
1422
1423 return group;
1424 },
1425
1426 selectorParts: function(){
1427 var tok
1428 , arr = [];
1429
1430 // Selector candidates,
1431 // stitched together to
1432 // form a selector.
1433 while (tok = this.selectorToken()) {
1434 debug.selector('%s', tok);
1435 // Selector component
1436 switch (tok.type) {
1437 case '{':
1438 this.skipSpaces();
1439 var expr = this.expression();
1440 this.skipSpaces();
1441 this.expect('}');
1442 arr.push(expr);
1443 break;
1444 case this.prefix && '.':
1445 var literal = new nodes.Literal(tok.val + this.prefix);
1446 literal.prefixed = true;
1447 arr.push(literal);
1448 break;
1449 case 'comment':
1450 // ignore comments
1451 break;
1452 case 'color':
1453 case 'unit':
1454 arr.push(new nodes.Literal(tok.val.raw));
1455 break;
1456 case 'space':
1457 arr.push(new nodes.Literal(' '));
1458 break;
1459 case 'function':
1460 arr.push(new nodes.Literal(tok.val.name + '('));
1461 break;
1462 case 'ident':
1463 arr.push(new nodes.Literal(tok.val.name || tok.val.string));
1464 break;
1465 default:
1466 arr.push(new nodes.Literal(tok.val));
1467 if (tok.space) arr.push(new nodes.Literal(' '));
1468 }
1469 }
1470
1471 return arr;
1472 },
1473
1474 /**
1475 * ident ('=' | '?=') expression
1476 */
1477
1478 assignment: function() {
1479 var op
1480 , node
1481 , name = this.id().name;
1482
1483 if (op =
1484 this.accept('=')
1485 || this.accept('?=')
1486 || this.accept('+=')
1487 || this.accept('-=')
1488 || this.accept('*=')
1489 || this.accept('/=')
1490 || this.accept('%=')) {
1491 this.state.push('assignment');
1492 var expr = this.list();
1493 // @block support
1494 if (expr.isEmpty) this.assignAtblock(expr);
1495 node = new nodes.Ident(name, expr);
1496 this.state.pop();
1497
1498 switch (op.type) {
1499 case '?=':
1500 var defined = new nodes.BinOp('is defined', node)
1501 , lookup = new nodes.Ident(name);
1502 node = new nodes.Ternary(defined, lookup, node);
1503 break;
1504 case '+=':
1505 case '-=':
1506 case '*=':
1507 case '/=':
1508 case '%=':
1509 node.val = new nodes.BinOp(op.type[0], new nodes.Ident(name), expr);
1510 break;
1511 }
1512 }
1513
1514 return node;
1515 },
1516
1517 /**
1518 * definition
1519 * | call
1520 */
1521
1522 function: function() {
1523 var parens = 1
1524 , i = 2
1525 , tok;
1526
1527 // Lookahead and determine if we are dealing
1528 // with a function call or definition. Here
1529 // we pair parens to prevent false negatives
1530 out:
1531 while (tok = this.lookahead(i++)) {
1532 switch (tok.type) {
1533 case 'function':
1534 case '(':
1535 ++parens;
1536 break;
1537 case ')':
1538 if (!--parens) break out;
1539 break;
1540 case 'eos':
1541 this.error('failed to find closing paren ")"');
1542 }
1543 }
1544
1545 // Definition or call
1546 switch (this.currentState()) {
1547 case 'expression':
1548 return this.functionCall();
1549 default:
1550 return this.looksLikeFunctionDefinition(i)
1551 ? this.functionDefinition()
1552 : this.expression();
1553 }
1554 },
1555
1556 /**
1557 * url '(' (expression | urlchars)+ ')'
1558 */
1559
1560 url: function() {
1561 this.expect('function');
1562 this.state.push('function arguments');
1563 var args = this.args();
1564 this.expect(')');
1565 this.state.pop();
1566 return new nodes.Call('url', args);
1567 },
1568
1569 /**
1570 * '+'? ident '(' expression ')' block?
1571 */
1572
1573 functionCall: function() {
1574 var withBlock = this.accept('+');
1575 if ('url' == this.peek().val.name) return this.url();
1576 var name = this.expect('function').val.name;
1577 this.state.push('function arguments');
1578 this.parens++;
1579 var args = this.args();
1580 this.expect(')');
1581 this.parens--;
1582 this.state.pop();
1583 var call = new nodes.Call(name, args);
1584 if (withBlock) {
1585 this.state.push('function');
1586 call.block = this.block(call);
1587 this.state.pop();
1588 }
1589 return call;
1590 },
1591
1592 /**
1593 * ident '(' params ')' block
1594 */
1595
1596 functionDefinition: function() {
1597 var name = this.expect('function').val.name;
1598
1599 // params
1600 this.state.push('function params');
1601 this.skipWhitespace();
1602 var params = this.params();
1603 this.skipWhitespace();
1604 this.expect(')');
1605 this.state.pop();
1606
1607 // Body
1608 this.state.push('function');
1609 var fn = new nodes.Function(name, params);
1610 fn.block = this.block(fn);
1611 this.state.pop();
1612 return new nodes.Ident(name, fn);
1613 },
1614
1615 /**
1616 * ident
1617 * | ident '...'
1618 * | ident '=' expression
1619 * | ident ',' ident
1620 */
1621
1622 params: function() {
1623 var tok
1624 , node
1625 , params = new nodes.Params;
1626 while (tok = this.accept('ident')) {
1627 this.accept('space');
1628 params.push(node = tok.val);
1629 if (this.accept('...')) {
1630 node.rest = true;
1631 } else if (this.accept('=')) {
1632 node.val = this.expression();
1633 }
1634 this.skipWhitespace();
1635 this.accept(',');
1636 this.skipWhitespace();
1637 }
1638 return params;
1639 },
1640
1641 /**
1642 * (ident ':')? expression (',' (ident ':')? expression)*
1643 */
1644
1645 args: function() {
1646 var args = new nodes.Arguments
1647 , keyword;
1648
1649 do {
1650 // keyword
1651 if ('ident' == this.peek().type && ':' == this.lookahead(2).type) {
1652 keyword = this.next().val.string;
1653 this.expect(':');
1654 args.map[keyword] = this.expression();
1655 // arg
1656 } else {
1657 args.push(this.expression());
1658 }
1659 } while (this.accept(','));
1660
1661 return args;
1662 },
1663
1664 /**
1665 * expression (',' expression)*
1666 */
1667
1668 list: function() {
1669 var node = this.expression();
1670
1671 while (this.accept(',')) {
1672 if (node.isList) {
1673 list.push(this.expression());
1674 } else {
1675 var list = new nodes.Expression(true);
1676 list.push(node);
1677 list.push(this.expression());
1678 node = list;
1679 }
1680 }
1681 return node;
1682 },
1683
1684 /**
1685 * negation+
1686 */
1687
1688 expression: function() {
1689 var node
1690 , expr = new nodes.Expression;
1691 this.state.push('expression');
1692 while (node = this.negation()) {
1693 if (!node) this.error('unexpected token {peek} in expression');
1694 expr.push(node);
1695 }
1696 this.state.pop();
1697 if (expr.nodes.length) {
1698 expr.lineno = expr.nodes[0].lineno;
1699 expr.column = expr.nodes[0].column;
1700 }
1701 return expr;
1702 },
1703
1704 /**
1705 * 'not' ternary
1706 * | ternary
1707 */
1708
1709 negation: function() {
1710 if (this.accept('not')) {
1711 return new nodes.UnaryOp('!', this.negation());
1712 }
1713 return this.ternary();
1714 },
1715
1716 /**
1717 * logical ('?' expression ':' expression)?
1718 */
1719
1720 ternary: function() {
1721 var node = this.logical();
1722 if (this.accept('?')) {
1723 var trueExpr = this.expression();
1724 this.expect(':');
1725 var falseExpr = this.expression();
1726 node = new nodes.Ternary(node, trueExpr, falseExpr);
1727 }
1728 return node;
1729 },
1730
1731 /**
1732 * typecheck (('&&' | '||') typecheck)*
1733 */
1734
1735 logical: function() {
1736 var op
1737 , node = this.typecheck();
1738 while (op = this.accept('&&') || this.accept('||')) {
1739 node = new nodes.BinOp(op.type, node, this.typecheck());
1740 }
1741 return node;
1742 },
1743
1744 /**
1745 * equality ('is a' equality)*
1746 */
1747
1748 typecheck: function() {
1749 var op
1750 , node = this.equality();
1751 while (op = this.accept('is a')) {
1752 this.operand = true;
1753 if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
1754 node = new nodes.BinOp(op.type, node, this.equality());
1755 this.operand = false;
1756 }
1757 return node;
1758 },
1759
1760 /**
1761 * in (('==' | '!=') in)*
1762 */
1763
1764 equality: function() {
1765 var op
1766 , node = this.in();
1767 while (op = this.accept('==') || this.accept('!=')) {
1768 this.operand = true;
1769 if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
1770 node = new nodes.BinOp(op.type, node, this.in());
1771 this.operand = false;
1772 }
1773 return node;
1774 },
1775
1776 /**
1777 * relational ('in' relational)*
1778 */
1779
1780 in: function() {
1781 var node = this.relational();
1782 while (this.accept('in')) {
1783 this.operand = true;
1784 if (!node) this.error('illegal unary "in", missing left-hand operand');
1785 node = new nodes.BinOp('in', node, this.relational());
1786 this.operand = false;
1787 }
1788 return node;
1789 },
1790
1791 /**
1792 * range (('>=' | '<=' | '>' | '<') range)*
1793 */
1794
1795 relational: function() {
1796 var op
1797 , node = this.range();
1798 while (op =
1799 this.accept('>=')
1800 || this.accept('<=')
1801 || this.accept('<')
1802 || this.accept('>')
1803 ) {
1804 this.operand = true;
1805 if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
1806 node = new nodes.BinOp(op.type, node, this.range());
1807 this.operand = false;
1808 }
1809 return node;
1810 },
1811
1812 /**
1813 * additive (('..' | '...') additive)*
1814 */
1815
1816 range: function() {
1817 var op
1818 , node = this.additive();
1819 if (op = this.accept('...') || this.accept('..')) {
1820 this.operand = true;
1821 if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
1822 node = new nodes.BinOp(op.val, node, this.additive());
1823 this.operand = false;
1824 }
1825 return node;
1826 },
1827
1828 /**
1829 * multiplicative (('+' | '-') multiplicative)*
1830 */
1831
1832 additive: function() {
1833 var op
1834 , node = this.multiplicative();
1835 while (op = this.accept('+') || this.accept('-')) {
1836 this.operand = true;
1837 node = new nodes.BinOp(op.type, node, this.multiplicative());
1838 this.operand = false;
1839 }
1840 return node;
1841 },
1842
1843 /**
1844 * defined (('**' | '*' | '/' | '%') defined)*
1845 */
1846
1847 multiplicative: function() {
1848 var op
1849 , node = this.defined();
1850 while (op =
1851 this.accept('**')
1852 || this.accept('*')
1853 || this.accept('/')
1854 || this.accept('%')) {
1855 this.operand = true;
1856 if ('/' == op && this.inProperty && !this.parens) {
1857 this.stash.push(new Token('literal', new nodes.Literal('/')));
1858 this.operand = false;
1859 return node;
1860 } else {
1861 if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
1862 node = new nodes.BinOp(op.type, node, this.defined());
1863 this.operand = false;
1864 }
1865 }
1866 return node;
1867 },
1868
1869 /**
1870 * unary 'is defined'
1871 * | unary
1872 */
1873
1874 defined: function() {
1875 var node = this.unary();
1876 if (this.accept('is defined')) {
1877 if (!node) this.error('illegal unary "is defined", missing left-hand operand');
1878 node = new nodes.BinOp('is defined', node);
1879 }
1880 return node;
1881 },
1882
1883 /**
1884 * ('!' | '~' | '+' | '-') unary
1885 * | subscript
1886 */
1887
1888 unary: function() {
1889 var op
1890 , node;
1891 if (op =
1892 this.accept('!')
1893 || this.accept('~')
1894 || this.accept('+')
1895 || this.accept('-')) {
1896 this.operand = true;
1897 node = new nodes.UnaryOp(op.type, this.unary());
1898 this.operand = false;
1899 return node;
1900 }
1901 return this.subscript();
1902 },
1903
1904 /**
1905 * member ('[' expression ']' ('.' id)? '='?)+
1906 * | member
1907 */
1908
1909 subscript: function() {
1910 var node = this.member()
1911 , id;
1912 while (this.accept('[')) {
1913 node = new nodes.BinOp('[]', node, this.expression());
1914 this.expect(']');
1915 if (this.accept('.')) {
1916 id = new nodes.Ident(this.expect('ident').val.string);
1917 node = new nodes.Member(node, id);
1918 }
1919 // TODO: TernaryOp :)
1920 if (this.accept('=')) {
1921 node.op += '=';
1922 node.val = this.expression();
1923 // @block support
1924 if (node.val.isEmpty) this.assignAtblock(node.val);
1925 }
1926 }
1927 return node;
1928 },
1929
1930 /**
1931 * primary ('.' id)+ '='?
1932 * | primary
1933 */
1934
1935 member: function() {
1936 var node = this.primary();
1937 if (node) {
1938 while (this.accept('.')) {
1939 var id = new nodes.Ident(this.expect('ident').val.string);
1940 node = new nodes.Member(node, id);
1941 }
1942 this.skipSpaces();
1943 if (this.accept('=')) {
1944 node.val = this.expression();
1945 // @block support
1946 if (node.val.isEmpty) this.assignAtblock(node.val);
1947 }
1948 }
1949 return node;
1950 },
1951
1952 /**
1953 * '{' '}'
1954 * | '{' pair (ws pair)* '}'
1955 */
1956
1957 object: function(){
1958 var obj = new nodes.Object
1959 , id, val, comma;
1960 this.expect('{');
1961 this.skipWhitespace();
1962
1963 while (!this.accept('}')) {
1964 if (this.accept('comment')
1965 || this.accept('newline')) continue;
1966
1967 if (!comma) this.accept(',');
1968 id = this.accept('ident') || this.accept('string');
1969 if (!id) this.error('expected "ident" or "string", got {peek}');
1970 id = id.val.hash;
1971 this.skipSpacesAndComments();
1972 this.expect(':');
1973 val = this.expression();
1974 obj.set(id, val);
1975 comma = this.accept(',');
1976 this.skipWhitespace();
1977 }
1978
1979 return obj;
1980 },
1981
1982 /**
1983 * unit
1984 * | null
1985 * | color
1986 * | string
1987 * | ident
1988 * | boolean
1989 * | literal
1990 * | object
1991 * | atblock
1992 * | atrule
1993 * | '(' expression ')' '%'?
1994 */
1995
1996 primary: function() {
1997 var tok;
1998 this.skipSpacesAndComments();
1999
2000 // Parenthesis
2001 if (this.accept('(')) {
2002 ++this.parens;
2003 var expr = this.expression()
2004 , paren = this.expect(')');
2005 --this.parens;
2006 if (this.accept('%')) expr.push(new nodes.Ident('%'));
2007 tok = this.peek();
2008 // (1 + 2)px, (1 + 2)em, etc.
2009 if (!paren.space
2010 && 'ident' == tok.type
2011 && ~units.indexOf(tok.val.string)) {
2012 expr.push(new nodes.Ident(tok.val.string));
2013 this.next();
2014 }
2015 return expr;
2016 }
2017
2018 tok = this.peek();
2019
2020 // Primitive
2021 switch (tok.type) {
2022 case 'null':
2023 case 'unit':
2024 case 'color':
2025 case 'string':
2026 case 'literal':
2027 case 'boolean':
2028 return this.next().val;
2029 case !this.cond && '{':
2030 return this.object();
2031 case 'atblock':
2032 return this.atblock();
2033 // property lookup
2034 case 'atrule':
2035 var id = new nodes.Ident(this.next().val);
2036 id.property = true;
2037 return id;
2038 case 'ident':
2039 return this.ident();
2040 case 'function':
2041 return tok.anonymous
2042 ? this.functionDefinition()
2043 : this.functionCall();
2044 }
2045 }
2046};