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