UNPKG

27.1 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 expression
578 */
579
580 get import() {
581 this.expect('import');
582 this.allowPostfix = true;
583 return new nodes.Import(this.expression);
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 * url '(' (expression | urlchars)+ ')'
963 */
964
965 get url() {
966 this.expect('function');
967 this.state.push('function arguments');
968 var args = this.args;
969 this.expect(')');
970 this.state.pop();
971 return new nodes.Call('url', args);
972 },
973
974 /**
975 * ident '(' expression ')'
976 */
977
978 get functionCall() {
979 if ('url' == this.peek.val.name) return this.url;
980 var name = this.expect('function').val.name;
981 this.state.push('function arguments');
982 var args = this.args;
983 this.expect(')');
984 this.state.pop();
985 return new nodes.Call(name, args);
986 },
987
988 /**
989 * ident '(' params ')' block
990 */
991
992 get functionDefinition() {
993 var name = this.expect('function').val.name;
994
995 // params
996 this.state.push('function params');
997 var params = this.params;
998 this.expect(')');
999 this.state.pop();
1000
1001 // Body
1002 this.state.push('function');
1003 var fn = new nodes.Function(name, params);
1004 fn.block = this.block(fn);
1005 this.state.pop();
1006 return new nodes.Ident(name, fn);
1007 },
1008
1009 /**
1010 * ident
1011 * | ident '...'
1012 * | ident '=' expression
1013 * | ident ',' ident
1014 */
1015
1016 get params() {
1017 var tok
1018 , node
1019 , params = new nodes.Params;
1020 while (tok = this.accept('ident')) {
1021 this.accept('space');
1022 params.push(node = tok.val);
1023 if (this.accept('...')) {
1024 node.rest = true;
1025 } else if (this.accept('=')) {
1026 node.val = this.expression;
1027 }
1028 this.accept(',');
1029 }
1030 return params;
1031 },
1032
1033 /**
1034 * expression (',' expression)*
1035 */
1036
1037 get args() {
1038 var args = new nodes.Expression;
1039 do {
1040 args.push(this.expression);
1041 } while (this.accept(','));
1042 return args;
1043 },
1044
1045 /**
1046 * expression (',' expression)*
1047 */
1048
1049 get list() {
1050 var node = this.expression;
1051 while (this.accept(',')) {
1052 if (node.isList) {
1053 list.push(this.expression);
1054 } else {
1055 var list = new nodes.Expression(true);
1056 list.push(node);
1057 list.push(this.expression);
1058 node = list;
1059 }
1060 }
1061 return node;
1062 },
1063
1064 /**
1065 * negation+
1066 */
1067
1068 get expression() {
1069 var node
1070 , expr = new nodes.Expression;
1071 this.state.push('expression');
1072 while (node = this.negation) {
1073 if (!node) this.error('unexpected token {peek} in expression');
1074 expr.push(node);
1075 }
1076 this.state.pop();
1077 return expr;
1078 },
1079
1080 /**
1081 * 'not' ternary
1082 * | ternary
1083 */
1084
1085 get negation() {
1086 if (this.accept('not')) {
1087 return new nodes.UnaryOp('!', this.negation);
1088 }
1089 return this.ternary;
1090 },
1091
1092 /**
1093 * logical ('?' expression ':' expression)?
1094 */
1095
1096 get ternary() {
1097 var node = this.logical;
1098 if (this.accept('?')) {
1099 var trueExpr = this.expression;
1100 this.expect(':');
1101 var falseExpr = this.expression;
1102 node = new nodes.Ternary(node, trueExpr, falseExpr);
1103 }
1104 return node;
1105 },
1106
1107 /**
1108 * typecheck (('&&' | '||') typecheck)*
1109 */
1110
1111 get logical() {
1112 var op
1113 , node = this.typecheck;
1114 while (op = this.accept('&&') || this.accept('||')) {
1115 node = new nodes.BinOp(op.type, node, this.typecheck);
1116 }
1117 return node;
1118 },
1119
1120 /**
1121 * equality ('is a' equality)*
1122 */
1123
1124 get typecheck() {
1125 var op
1126 , node = this.equality;
1127 while (op = this.accept('is a')) {
1128 this.operand = true;
1129 if (!node) throw new Error('illegal unary ' + op);
1130 node = new nodes.BinOp(op.type, node, this.equality);
1131 this.operand = false;
1132 }
1133 return node;
1134 },
1135
1136 /**
1137 * in (('==' | '!=') in)*
1138 */
1139
1140 get equality() {
1141 var op
1142 , node = this.in;
1143 while (op = this.accept('==') || this.accept('!=')) {
1144 this.operand = true;
1145 if (!node) throw new Error('illegal unary ' + op);
1146 node = new nodes.BinOp(op.type, node, this.in);
1147 this.operand = false;
1148 }
1149 return node;
1150 },
1151
1152 /**
1153 * relational ('in' relational)*
1154 */
1155
1156 get in() {
1157 var node = this.relational;
1158 while (this.accept('in')) {
1159 this.operand = true;
1160 if (!node) throw new Error('illegal unary in');
1161 node = new nodes.BinOp('in', node, this.relational);
1162 this.operand = false;
1163 }
1164 return node;
1165 },
1166
1167 /**
1168 * range (('>=' | '<=' | '>' | '<') range)*
1169 */
1170
1171 get relational() {
1172 var op
1173 , node = this.range;
1174 while (op =
1175 this.accept('>=')
1176 || this.accept('<=')
1177 || this.accept('<')
1178 || this.accept('>')
1179 ) {
1180 this.operand = true;
1181 if (!node) throw new Error('illegal unary ' + op);
1182 node = new nodes.BinOp(op.type, node, this.range);
1183 this.operand = false;
1184 }
1185 return node;
1186 },
1187
1188 /**
1189 * additive (('..' | '...') additive)*
1190 */
1191
1192 get range() {
1193 var op
1194 , node = this.additive;
1195 if (op = this.accept('...') || this.accept('..')) {
1196 this.operand = true;
1197 if (!node) throw new Error('illegal unary ' + op);
1198 node = new nodes.BinOp(op.val, node, this.additive);
1199 this.operand = false;
1200 }
1201 return node;
1202 },
1203
1204 /**
1205 * multiplicative (('+' | '-') multiplicative)*
1206 */
1207
1208 get additive() {
1209 var op
1210 , node = this.multiplicative;
1211 while (op = this.accept('+') || this.accept('-')) {
1212 this.operand = true;
1213 node = new nodes.BinOp(op.type, node, this.multiplicative);
1214 this.operand = false;
1215 }
1216 return node;
1217 },
1218
1219 /**
1220 * defined (('**' | '*' | '/' | '%') defined)*
1221 */
1222
1223 get multiplicative() {
1224 var op
1225 , node = this.defined;
1226 while (op =
1227 this.accept('**')
1228 || this.accept('*')
1229 || this.accept('/')
1230 || this.accept('%')) {
1231 this.operand = true;
1232 if ('/' == op && this.inProperty && !this.parens) {
1233 var expr = new nodes.Expression;
1234 expr.push(node);
1235 expr.push(new nodes.Literal('/'));
1236 return expr;
1237 } else {
1238 if (!node) throw new Error('illegal unary ' + op);
1239 node = new nodes.BinOp(op.type, node, this.defined);
1240 this.operand = false;
1241 }
1242 }
1243 return node;
1244 },
1245
1246 /**
1247 * unary 'is defined'
1248 * | unary
1249 */
1250
1251 get defined() {
1252 var node = this.unary;
1253 if (this.accept('is defined')) {
1254 if (!node) throw new Error('illegal use of "is defined"');
1255 node = new nodes.BinOp('is defined', node);
1256 }
1257 return node;
1258 },
1259
1260 /**
1261 * ('!' | '~' | '+' | '-') unary
1262 * | subscript
1263 */
1264
1265 get unary() {
1266 var op
1267 , node;
1268 if (op =
1269 this.accept('!')
1270 || this.accept('~')
1271 || this.accept('+')
1272 || this.accept('-')) {
1273 this.operand = true;
1274 node = new nodes.UnaryOp(op.type, this.unary);
1275 this.operand = false;
1276 return node;
1277 }
1278 return this.subscript;
1279 },
1280
1281 /**
1282 * primary ('[' expression ']')+
1283 * | primary
1284 */
1285
1286 get subscript() {
1287 var node = this.primary;
1288 while (this.accept('[')) {
1289 node = new nodes.BinOp('[]', node, this.expression);
1290 this.expect(']');
1291 }
1292 return node;
1293 },
1294
1295 /**
1296 * unit
1297 * | null
1298 * | color
1299 * | string
1300 * | ident
1301 * | boolean
1302 * | literal
1303 * | '(' expression ')'
1304 */
1305
1306 get primary() {
1307 var op
1308 , node;
1309
1310 // Parenthesis
1311 if (this.accept('(')) {
1312 this.parens = true;
1313 var expr = this.expression;
1314 this.expect(')');
1315 this.parens = false;
1316 return expr;
1317 }
1318
1319 // Primitive
1320 switch (this.peek.type) {
1321 case 'null':
1322 case 'unit':
1323 case 'color':
1324 case 'string':
1325 case 'literal':
1326 case 'boolean':
1327 return this.next.val;
1328 case 'ident':
1329 return this.ident;
1330 case 'function':
1331 return this.functionCall;
1332 }
1333 }
1334};