UNPKG

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