UNPKG

26.8 kBJavaScriptView Raw
1
2/*!
3 * Stylus - Parser
4 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
12var Lexer = require('./lexer')
13 , nodes = require('./nodes')
14 , inspect = require('sys').inspect;
15
16/**
17 * Selector composite tokens.
18 */
19
20var selectorTokens = [
21 'ident'
22 , 'string'
23 , 'selector'
24 , 'function'
25 , 'comment'
26 , 'space'
27 , 'color'
28 , 'unit'
29 , 'for'
30 , '['
31 , ']'
32 , '('
33 , ')'
34 , '+'
35 , '-'
36 , '*'
37 , '<'
38 , '>'
39 , '='
40 , ':'
41 , '&'
42 , '~'
43];
44
45/**
46 * CSS3 pseudo-selectors.
47 */
48
49var pseudoSelectors = [
50 'root'
51 , 'nth-child'
52 , 'nth-last-child'
53 , 'nth-of-type'
54 , 'nth-last-of-type'
55 , 'first-child'
56 , 'last-child'
57 , 'first-of-type'
58 , 'last-of-type'
59 , 'only-child'
60 , 'only-of-type'
61 , 'empty'
62 , 'link'
63 , 'visited'
64 , 'active'
65 , 'hover'
66 , 'focus'
67 , 'target'
68 , 'lang'
69 , 'enabled'
70 , 'disabled'
71 , 'checked'
72 , 'not'
73];
74
75/**
76 * Initialize a new `Parser` with the given `str` and `options`.
77 *
78 * @param {String} str
79 * @param {Object} options
80 * @api private
81 */
82
83var Parser = module.exports = function Parser(str, options) {
84 var self = this;
85 options = options || {};
86 this.str = nodes.source = str;
87 this.lexer = new Lexer(str, options);
88 this.root = options.root || new nodes.Root;
89 this.state = ['root'];
90 this.state.pop = function(){
91 self.prevState = [].pop.call(this);
92 };
93};
94
95/**
96 * Parser prototype.
97 */
98
99Parser.prototype = {
100
101 /**
102 * Constructor.
103 */
104
105 constructor: Parser,
106
107 /**
108 * Return current state.
109 *
110 * @return {String}
111 * @api private
112 */
113
114 get currentState() {
115 return this.state[this.state.length - 1];
116 },
117
118 /**
119 * Parse the input, then return the root node.
120 *
121 * @return {Node}
122 * @api private
123 */
124
125 parse: function(){
126 var block = this.parent = this.root;
127 while ('eos' != this.peek.type) {
128 if (this.accept('newline')) continue;
129 var stmt = this.statement;
130 this.accept(';');
131 if (!stmt) this.error('unexpected token {peek}, not allowed at the root level');
132 block.push(stmt);
133 }
134 return block;
135 },
136
137 /**
138 * Throw an `Error` with the given `msg`.
139 *
140 * @param {String} msg
141 * @api private
142 */
143
144 error: function(msg){
145 var type = this.peek.type
146 , val = undefined == this.peek.val
147 ? ''
148 : ' ' + this.peek.toString();
149 if (val.trim() == type.trim()) val = '';
150 throw new Error(msg.replace('{peek}', type + val));
151 },
152
153 /**
154 * Accept the given token `type`, and return it,
155 * otherwise return `undefined`.
156 *
157 * @param {String} type
158 * @return {Token}
159 * @api private
160 */
161
162 accept: function(type){
163 if (type == this.peek.type) {
164 return this.next;
165 }
166 },
167
168 /**
169 * Expect token `type` and return it, throw otherwise.
170 *
171 * @param {String} type
172 * @return {Token}
173 * @api private
174 */
175
176 expect: function(type){
177 if (type != this.peek.type) {
178 throw new Error('expected ' + type + ', got ' + this.peek);
179 }
180 return this.next;
181 },
182
183 /**
184 * Get the next token.
185 *
186 * @return {Token}
187 * @api private
188 */
189
190 get next() {
191 var tok = this.lexer.next;
192 nodes.lineno = tok.lineno;
193 return tok;
194 },
195
196 /**
197 * Peek with lookahead(1).
198 *
199 * @return {Token}
200 * @api private
201 */
202
203 get peek() {
204 return this.lexer.peek;
205 },
206
207 /**
208 * Lookahead `n` tokens.
209 *
210 * @param {Number} n
211 * @return {Token}
212 * @api private
213 */
214
215 lookahead: function(n){
216 return this.lexer.lookahead(n);
217 },
218
219 /**
220 * Check if the token at `n` is a valid selector token.
221 *
222 * @param {Number} n
223 * @return {Boolean}
224 * @api private
225 */
226
227 isSelectorToken: function(n) {
228 var la = this.lookahead(n).type;
229 switch (la) {
230 case 'for':
231 return this.bracketed;
232 case '[':
233 this.bracketed = true;
234 return true;
235 case ']':
236 this.bracketed = false;
237 return true;
238 default:
239 return ~selectorTokens.indexOf(la);
240 }
241 },
242
243 /**
244 * Check if the token at `n` is a pseudo selector.
245 *
246 * @param {Number} n
247 * @return {Boolean}
248 * @api private
249 */
250
251 isPseudoSelector: function(n){
252 return ~pseudoSelectors.indexOf(this.lookahead(n).val.name);
253 },
254
255 /**
256 * Valid selector tokens.
257 */
258
259 get selectorToken() {
260 if (this.isSelectorToken(1)) return this.next;
261 },
262
263 /**
264 * Consume whitespace.
265 */
266
267 get skipWhitespace() {
268 while (~['space', 'indent', 'outdent', 'newline'].indexOf(this.peek.type))
269 this.next;
270 },
271
272 /**
273 * Consume spaces.
274 */
275
276 get skipSpaces() {
277 while ('space' == this.peek.type)
278 this.next;
279 },
280
281 /**
282 * Check if the following sequence of tokens
283 * forms a function definition, ie trailing
284 * `{` or indentation.
285 */
286
287 looksLikeFunctionDefinition: function(i) {
288 return 'indent' == this.lookahead(i).type
289 || '{' == this.lookahead(i).type;
290 },
291
292 /**
293 * Check if the following sequence of tokens
294 * forms a selector.
295 */
296
297 get looksLikeSelector() {
298 var i = 1;
299
300 // Assume selector when an ident is
301 // followed by a selector
302 while ('ident' == this.lookahead(i).type
303 && 'newline' == this.lookahead(i + 1).type) i += 2;
304
305 // Assume pseudo selectors are NOT properties
306 // as 'td:th-child(1)' may look like a property
307 // and function call to the parser otherwise
308 while (this.isSelectorToken(i)) {
309 if (':' == this.lookahead(i++).type
310 && this.isPseudoSelector(i))
311 return true;
312 }
313
314 // Trailing comma
315 if (',' == this.lookahead(i).type
316 && 'newline' == this.lookahead(i + 1).type)
317 return true;
318
319 // Trailing brace
320 if ('{' == this.lookahead(i).type
321 && 'newline' == this.lookahead(i + 1).type)
322 return true;
323
324 // css-style mode, false on ; }
325 if (this.css) {
326 if (';' == this.lookahead(i) ||
327 '}' == this.lookahead(i))
328 return false;
329 }
330
331 // Trailing separators
332 while (!~[
333 'newline'
334 , 'indent'
335 , 'outdent'
336 , 'for'
337 , 'if'
338 , ';'
339 , '}'].indexOf(this.lookahead(i).type))
340 ++i;
341
342 if ('indent' == this.lookahead(i).type)
343 return true;
344 },
345
346 /**
347 * statement
348 * | statement 'if' expression
349 * | statement 'unless' expression
350 */
351
352 get statement() {
353 var op
354 , stmt = this.stmt
355 , state = this.prevState;
356
357 // special-case statements since it
358 // is not an expression. We could
359 // implement postfix conditionals at
360 // the expression level, however they
361 // would then fail to enclose properties
362 if (this.allowPostfix) {
363 delete this.allowPostfix;
364 state = 'expression';
365 }
366
367 switch (state) {
368 case 'assignment':
369 case 'expression':
370 case 'function arguments':
371 if (op = this.accept('if') || this.accept('unless')) {
372 stmt = new nodes.If(this.expression, stmt);
373 stmt.negate = 'unless' == op.type;
374 this.accept(';');
375 }
376 }
377
378 return stmt;
379 },
380
381 /**
382 * ident
383 * | selector
384 * | literal
385 * | charset
386 * | import
387 * | media
388 * | keyframes
389 * | page
390 * | for
391 * | if
392 * | unless
393 * | expression
394 * | 'return' expression
395 */
396
397 get stmt() {
398 var type = this.peek.type;
399 switch (type) {
400 case 'selector':
401 case 'literal':
402 case 'keyframes':
403 case 'charset':
404 case 'import':
405 case 'media':
406 case 'page':
407 case 'ident':
408 case 'unless':
409 case 'function':
410 case 'for':
411 case 'if':
412 return this[type];
413 case 'return':
414 return this.return;
415 case '{':
416 return this.property;
417 default:
418 // Contextual selectors
419 switch (this.currentState) {
420 case 'root':
421 case 'selector':
422 case 'conditional':
423 case 'keyframe':
424 case 'function':
425 case 'media':
426 case 'for':
427 switch (type) {
428 case 'color':
429 case '~':
430 case '+':
431 case '>':
432 case '<':
433 case '*':
434 case ':':
435 case '&':
436 case '[':
437 return this.selector;
438 }
439 }
440
441 // Expression fallback
442 var expr = this.expression;
443 if (expr.isEmpty) this.error('unexpected {peek}');
444 return expr;
445 }
446 },
447
448 /**
449 * indent (!outdent)+ outdent
450 */
451
452 block: function(node, scope) {
453 var delim
454 , stmt
455 , _ = this.css
456 , block = this.parent = new nodes.Block(this.parent, node);
457
458 if (false === scope) block.scope = false;
459
460 // css-style
461 if (this.css = this.accept('{')) {
462 delim = '}';
463 this.skipWhitespace;
464 } else {
465 delim = 'outdent';
466 this.expect('indent');
467 }
468
469 while (delim != this.peek.type) {
470 // css-style
471 if (this.css) {
472 if (this.accept('newline')) continue;
473 stmt = this.statement;
474 this.accept(';');
475 this.skipWhitespace;
476 } else {
477 if (this.accept('newline')) continue;
478 stmt = this.statement;
479 }
480 if (!stmt) this.error('unexpected token {peek} in block');
481 block.push(stmt);
482 }
483
484 // css-style
485 if (this.css) {
486 this.skipWhitespace;
487 this.expect('}');
488 this.skipSpaces;
489 this.css = _;
490 } else {
491 this.expect('outdent');
492 }
493
494 this.parent = block.parent;
495 return block;
496 },
497
498 /**
499 * for val (',' key) in expr
500 */
501
502 get for() {
503 this.expect('for');
504 var key
505 , val = this.id.name;
506 if (this.accept(',')) key = this.id.name;
507 this.expect('in');
508 var each = new nodes.Each(val, key, this.expression);
509 this.state.push('for');
510 each.block = this.block(each);
511 this.state.pop();
512 return each;
513 },
514
515 /**
516 * return expression
517 */
518
519 get return() {
520 this.expect('return');
521 var expr = this.expression;
522 return expr.isEmpty
523 ? new nodes.Return
524 : new nodes.Return(expr);
525 },
526
527 /**
528 * unless expression block
529 */
530
531 get unless() {
532 this.expect('unless');
533 var node = new nodes.If(this.expression, true);
534 this.state.push('conditional');
535 node.block = this.block(node, false);
536 this.state.pop();
537 return node;
538 },
539
540 /**
541 * if expression block (else block)?
542 */
543
544 get if() {
545 this.expect('if');
546 var node = new nodes.If(this.expression);
547 this.state.push('conditional');
548 node.block = this.block(node, false);
549 while (this.accept('else')) {
550 if (this.accept('if')) {
551 var cond = this.expression
552 , block = this.block(node, false);
553 node.elses.push(new nodes.If(cond, block));
554 } else {
555 node.elses.push(this.block(node, false));
556 break;
557 }
558 }
559 this.state.pop();
560 return node;
561 },
562
563 /**
564 * media
565 */
566
567 get media() {
568 var val = this.expect('media').val
569 , media = new nodes.Media(val);
570 this.state.push('media');
571 media.block = this.block(media);
572 this.state.pop();
573 return media;
574 },
575
576 /**
577 * import string
578 */
579
580 get import() {
581 this.expect('import');
582 this.allowPostfix = true;
583 return new nodes.Import(this.expect('string').val.val);
584 },
585
586 /**
587 * charset string
588 */
589
590 get charset() {
591 this.expect('charset');
592 var str = this.expect('string').val;
593 this.allowPostfix = true;
594 return new nodes.Charset(str);
595 },
596
597 /**
598 * page selector? block
599 */
600
601 get page() {
602 var selector;
603 this.expect('page');
604 if (this.accept(':')) {
605 var str = this.expect('ident').val.name;
606 selector = new nodes.Literal(':' + str);
607 }
608 var page = new nodes.Page(selector);
609 this.state.push('page');
610 page.block = this.block(page);
611 this.state.pop();
612 return page;
613 },
614
615 /**
616 * keyframes name ((unit | from | to) block)+
617 */
618
619 get keyframes() {
620 this.expect('keyframes');
621 var pos
622 , _ = this.css
623 , keyframes = new nodes.Keyframes(this.id);
624
625 // css-sty;e
626 if (this.css = this.accept('{')) {
627 this.skipWhitespace;
628 } else {
629 this.expect('indent');
630 }
631
632 while (pos = this.accept('unit') || this.accept('ident')) {
633 // from | to
634 if ('ident' == pos.type) {
635 this.accept('space');
636 switch (pos.val.name) {
637 case 'from':
638 pos = new nodes.Unit(0, '%');
639 break;
640 case 'to':
641 pos = new nodes.Unit(100, '%');
642 break;
643 default:
644 throw new Error('invalid ident "' + pos.val.name + '" in selector');
645 }
646 } else {
647 pos = pos.val;
648 }
649
650 // block
651 this.state.push('keyframe');
652 var block = this.block(keyframes);
653 keyframes.push(pos, block);
654 this.state.pop();
655 if (this.css) this.skipWhitespace;
656 }
657
658 // css-style
659 if (this.css) {
660 this.skipWhitespace;
661 this.expect('}');
662 this.css = _;
663 } else {
664 this.expect('outdent');
665 }
666
667 return keyframes;
668 },
669
670 /**
671 * literal
672 */
673
674 get literal() {
675 return this.expect('literal').val;
676 },
677
678 /**
679 * ident space?
680 */
681
682 get id() {
683 var tok = this.expect('ident');
684 this.accept('space');
685 return tok.val;
686 },
687
688 /**
689 * ident
690 * | assignment
691 * | property
692 * | selector
693 */
694
695 get ident() {
696 var i = 2
697 , la = this.lookahead(i).type;
698
699 while ('space' == la) la = this.lookahead(++i).type;
700
701 switch (la) {
702 // Assignment
703 case '=':
704 case '?=':
705 case '-=':
706 case '+=':
707 case '*=':
708 case '/=':
709 case '%=':
710 return this.assignment
711 // Operation
712 case '-':
713 case '+':
714 case '/':
715 case '*':
716 case '%':
717 case '**':
718 case 'and':
719 case 'or':
720 case '&&':
721 case '||':
722 case '>':
723 case '<':
724 case '>=':
725 case '<=':
726 case '!=':
727 case '==':
728 case '[':
729 case '?':
730 case 'in':
731 case 'is a':
732 case 'is defined':
733 // Prevent cyclic .ident, return literal
734 if (this._ident == this.peek) {
735 return this.id;
736 } else {
737 this._ident = this.peek;
738 switch (this.currentState) {
739 // unary op or selector in property / for
740 case 'for':
741 case 'selector':
742 return this.property;
743 // Part of a selector
744 case 'root':
745 return this.selector;
746 // Do not disrupt the ident when an operand
747 default:
748 return this.operand
749 ? this.id
750 : this.expression;
751 }
752 }
753 // Selector or property
754 default:
755 switch (this.currentState) {
756 case 'root':
757 return this.selector;
758 case 'for':
759 case 'page':
760 case 'media':
761 case 'selector':
762 case 'function':
763 case 'keyframe':
764 case 'conditional':
765 return this.property;
766 default:
767 return this.id;
768 }
769 }
770 },
771
772 /**
773 * (ident | '{' expression '}')+
774 */
775
776 get interpolate() {
777 var node
778 , segs = [];
779 while (true) {
780 if (this.accept('{')) {
781 this.state.push('interpolation');
782 segs.push(this.expression);
783 this.expect('}');
784 this.state.pop();
785 } else if (node = this.accept('ident')){
786 segs.push(node.val);
787 } else {
788 break;
789 }
790 }
791 if (!segs.length) this.expect('ident');
792 return segs;
793 },
794
795 /**
796 * property ':'? expression
797 * | ident
798 */
799
800 get property() {
801 if (this.looksLikeSelector) return this.selector;
802
803 // property
804 var ident = this.interpolate
805 , ret = prop = new nodes.Property(ident);
806
807 // optional ':'
808 this.accept('space');
809 if (this.accept(':')) this.accept('space');
810
811 this.state.push('property');
812 this.inProperty = true;
813 prop.expr = this.list;
814 if (prop.expr.isEmpty) ret = ident[0];
815 this.inProperty = false;
816 this.allowPostfix = true;
817 this.state.pop();
818
819 // optional ';'
820 this.accept(';');
821
822 return ret;
823 },
824
825 /**
826 * selector ',' selector
827 * | selector newline selector
828 * | selector block
829 */
830
831 get selector() {
832 var tok
833 , arr
834 , val
835 , prev
836 , parent
837 , group = new nodes.Group;
838
839 // Allow comments in selectors
840 // for hacks
841 this.lexer.allowComments = true;
842
843 do {
844 val = prev = null;
845 arr = [];
846
847 // Clobber newline after ,
848 this.accept('newline');
849
850 // Selector candidates,
851 // stitched together to
852 // form a selector.
853 while (tok = this.selectorToken) {
854 // Selector component
855 switch (tok.type) {
856 case 'unit': val = tok.val.val; break;
857 case 'ident': val = tok.val.name; break;
858 case 'function': val = tok.val.name + '('; break;
859 case 'string': val = tok.val.toString(); break;
860 case 'color': val = tok.val.raw; break;
861 case 'space': val = ' '; break;
862 default: val = tok.val;
863 }
864
865 // Whitespace support
866 if (!prev || prev.space) {
867 arr.push(val);
868 } else {
869 arr[arr.length-1] += val;
870 }
871 prev = tok;
872 }
873
874 // Push the selector
875 group.push(new nodes.Selector(arr.join(' '), parent));
876 } while (this.accept(',') || this.accept('newline'));
877
878 this.lexer.allowComments = false;
879 this.state.push('selector');
880 group.block = this.block(group);
881 this.state.pop();
882
883
884 return group;
885 },
886
887 /**
888 * ident ('=' | '?=') expression
889 */
890
891 get assignment() {
892 var op
893 , node
894 , name = this.id.name;
895
896 if (op =
897 this.accept('=')
898 || this.accept('?=')
899 || this.accept('+=')
900 || this.accept('-=')
901 || this.accept('*=')
902 || this.accept('/=')
903 || this.accept('%=')) {
904 this.state.push('assignment');
905 var expr = this.list;
906 if (expr.isEmpty) this.error('invalid right-hand side operand in assignment, got {peek}')
907 node = new nodes.Ident(name, expr);
908 this.state.pop();
909
910 switch (op.type) {
911 case '?=':
912 var defined = new nodes.BinOp('is defined', node)
913 , lookup = new nodes.Ident(name);
914 node = new nodes.Ternary(defined, lookup, node);
915 break;
916 case '+=':
917 case '-=':
918 case '*=':
919 case '/=':
920 case '%=':
921 node.val = new nodes.BinOp(op.type[0], new nodes.Ident(name), expr);
922 break;
923 }
924 }
925
926 return node;
927 },
928
929 /**
930 * definition
931 * | call
932 */
933
934 get function() {
935 var parens = 1
936 , i = 2
937 , tok;
938
939 // Lookahead and determine if we are dealing
940 // with a function call or definition. Here
941 // we pair parens to prevent false negatives
942 out:
943 while (tok = this.lookahead(i++)) {
944 switch (tok.type) {
945 case 'function': case '(': ++parens; break;
946 case ')': if (!--parens) break out;
947 }
948 }
949
950 // Definition or call
951 switch (this.currentState) {
952 case 'expression':
953 return this.functionCall;
954 default:
955 return this.looksLikeFunctionDefinition(i)
956 ? this.functionDefinition
957 : this.expression;
958 }
959 },
960
961 /**
962 * ident '(' expression ')'
963 */
964
965 get functionCall() {
966 var name = this.expect('function').val.name;
967 this.state.push('function arguments');
968 var args = this.args;
969 this.expect(')');
970 this.state.pop();
971 return new nodes.Call(name, args);
972 },
973
974 /**
975 * ident '(' params ')' block
976 */
977
978 get functionDefinition() {
979 var name = this.expect('function').val.name;
980
981 // params
982 this.state.push('function params');
983 var params = this.params;
984 this.expect(')');
985 this.state.pop();
986
987 // Body
988 this.state.push('function');
989 var fn = new nodes.Function(name, params);
990 fn.block = this.block(fn);
991 this.state.pop();
992 return new nodes.Ident(name, fn);
993 },
994
995 /**
996 * ident
997 * | ident '...'
998 * | ident '=' expression
999 * | ident ',' ident
1000 */
1001
1002 get params() {
1003 var tok
1004 , node
1005 , params = new nodes.Params;
1006 while (tok = this.accept('ident')) {
1007 this.accept('space');
1008 params.push(node = tok.val);
1009 if (this.accept('...')) {
1010 node.rest = true;
1011 } else if (this.accept('=')) {
1012 node.val = this.expression;
1013 }
1014 this.accept(',');
1015 }
1016 return params;
1017 },
1018
1019 /**
1020 * expression (',' expression)*
1021 */
1022
1023 get args() {
1024 var args = new nodes.Expression;
1025 do {
1026 args.push(this.expression);
1027 } while (this.accept(','));
1028 return args;
1029 },
1030
1031 /**
1032 * expression (',' expression)*
1033 */
1034
1035 get list() {
1036 var node = this.expression;
1037 while (this.accept(',')) {
1038 if (node.isList) {
1039 list.push(this.expression);
1040 } else {
1041 var list = new nodes.Expression(true);
1042 list.push(node);
1043 list.push(this.expression);
1044 node = list;
1045 }
1046 }
1047 return node;
1048 },
1049
1050 /**
1051 * negation+
1052 */
1053
1054 get expression() {
1055 var node
1056 , expr = new nodes.Expression;
1057 this.state.push('expression');
1058 while (node = this.negation) {
1059 if (!node) this.error('unexpected token {peek} in expression');
1060 expr.push(node);
1061 }
1062 this.state.pop();
1063 return expr;
1064 },
1065
1066 /**
1067 * 'not' ternary
1068 * | ternary
1069 */
1070
1071 get negation() {
1072 if (this.accept('not')) {
1073 return new nodes.UnaryOp('!', this.negation);
1074 }
1075 return this.ternary;
1076 },
1077
1078 /**
1079 * logical ('?' expression ':' expression)?
1080 */
1081
1082 get ternary() {
1083 var node = this.logical;
1084 if (this.accept('?')) {
1085 var trueExpr = this.expression;
1086 this.expect(':');
1087 var falseExpr = this.expression;
1088 node = new nodes.Ternary(node, trueExpr, falseExpr);
1089 }
1090 return node;
1091 },
1092
1093 /**
1094 * typecheck (('&&' | '||') typecheck)*
1095 */
1096
1097 get logical() {
1098 var op
1099 , node = this.typecheck;
1100 while (op = this.accept('&&') || this.accept('||')) {
1101 node = new nodes.BinOp(op.type, node, this.typecheck);
1102 }
1103 return node;
1104 },
1105
1106 /**
1107 * equality ('is a' equality)*
1108 */
1109
1110 get typecheck() {
1111 var op
1112 , node = this.equality;
1113 while (op = this.accept('is a')) {
1114 this.operand = true;
1115 if (!node) throw new Error('illegal unary ' + op);
1116 node = new nodes.BinOp(op.type, node, this.equality);
1117 this.operand = false;
1118 }
1119 return node;
1120 },
1121
1122 /**
1123 * in (('==' | '!=') in)*
1124 */
1125
1126 get equality() {
1127 var op
1128 , node = this.in;
1129 while (op = this.accept('==') || this.accept('!=')) {
1130 this.operand = true;
1131 if (!node) throw new Error('illegal unary ' + op);
1132 node = new nodes.BinOp(op.type, node, this.in);
1133 this.operand = false;
1134 }
1135 return node;
1136 },
1137
1138 /**
1139 * relational ('in' relational)*
1140 */
1141
1142 get in() {
1143 var node = this.relational;
1144 while (this.accept('in')) {
1145 this.operand = true;
1146 if (!node) throw new Error('illegal unary in');
1147 node = new nodes.BinOp('in', node, this.relational);
1148 this.operand = false;
1149 }
1150 return node;
1151 },
1152
1153 /**
1154 * range (('>=' | '<=' | '>' | '<') range)*
1155 */
1156
1157 get relational() {
1158 var op
1159 , node = this.range;
1160 while (op =
1161 this.accept('>=')
1162 || this.accept('<=')
1163 || this.accept('<')
1164 || this.accept('>')
1165 ) {
1166 this.operand = true;
1167 if (!node) throw new Error('illegal unary ' + op);
1168 node = new nodes.BinOp(op.type, node, this.range);
1169 this.operand = false;
1170 }
1171 return node;
1172 },
1173
1174 /**
1175 * additive (('..' | '...') additive)*
1176 */
1177
1178 get range() {
1179 var op
1180 , node = this.additive;
1181 if (op = this.accept('...') || this.accept('..')) {
1182 this.operand = true;
1183 if (!node) throw new Error('illegal unary ' + op);
1184 node = new nodes.BinOp(op.val, node, this.additive);
1185 this.operand = false;
1186 }
1187 return node;
1188 },
1189
1190 /**
1191 * multiplicative (('+' | '-') multiplicative)*
1192 */
1193
1194 get additive() {
1195 var op
1196 , node = this.multiplicative;
1197 while (op = this.accept('+') || this.accept('-')) {
1198 this.operand = true;
1199 node = new nodes.BinOp(op.type, node, this.multiplicative);
1200 this.operand = false;
1201 }
1202 return node;
1203 },
1204
1205 /**
1206 * defined (('**' | '*' | '/' | '%') defined)*
1207 */
1208
1209 get multiplicative() {
1210 var op
1211 , node = this.defined;
1212 while (op =
1213 this.accept('**')
1214 || this.accept('*')
1215 || this.accept('/')
1216 || this.accept('%')) {
1217 this.operand = true;
1218 if ('/' == op && this.inProperty && !this.parens) {
1219 var expr = new nodes.Expression;
1220 expr.push(node);
1221 expr.push(new nodes.Literal('/'));
1222 return expr;
1223 } else {
1224 if (!node) throw new Error('illegal unary ' + op);
1225 node = new nodes.BinOp(op.type, node, this.defined);
1226 this.operand = false;
1227 }
1228 }
1229 return node;
1230 },
1231
1232 /**
1233 * unary 'is defined'
1234 * | unary
1235 */
1236
1237 get defined() {
1238 var node = this.unary;
1239 if (this.accept('is defined')) {
1240 if (!node) throw new Error('illegal use of "is defined"');
1241 node = new nodes.BinOp('is defined', node);
1242 }
1243 return node;
1244 },
1245
1246 /**
1247 * ('!' | '~' | '+' | '-') unary
1248 * | subscript
1249 */
1250
1251 get unary() {
1252 var op
1253 , node;
1254 if (op =
1255 this.accept('!')
1256 || this.accept('~')
1257 || this.accept('+')
1258 || this.accept('-')) {
1259 this.operand = true;
1260 node = new nodes.UnaryOp(op.type, this.unary);
1261 this.operand = false;
1262 return node;
1263 }
1264 return this.subscript;
1265 },
1266
1267 /**
1268 * primary ('[' expression ']')+
1269 * | primary
1270 */
1271
1272 get subscript() {
1273 var node = this.primary;
1274 while (this.accept('[')) {
1275 node = new nodes.BinOp('[]', node, this.expression);
1276 this.expect(']');
1277 }
1278 return node;
1279 },
1280
1281 /**
1282 * unit
1283 * | null
1284 * | color
1285 * | string
1286 * | ident
1287 * | boolean
1288 * | literal
1289 * | '(' expression ')'
1290 */
1291
1292 get primary() {
1293 var op
1294 , node;
1295
1296 // Parenthesis
1297 if (this.accept('(')) {
1298 this.parens = true;
1299 var expr = this.expression;
1300 this.expect(')');
1301 this.parens = false;
1302 return expr;
1303 }
1304
1305 // Primitive
1306 switch (this.peek.type) {
1307 case 'null':
1308 case 'unit':
1309 case 'color':
1310 case 'string':
1311 case 'literal':
1312 case 'boolean':
1313 return this.next.val;
1314 case 'ident':
1315 return this.ident;
1316 case 'function':
1317 return this.functionCall;
1318 }
1319 }
1320};