1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | var Visitor = require('./')
|
13 | , units = require('../units')
|
14 | , nodes = require('../nodes')
|
15 | , Stack = require('../stack')
|
16 | , Frame = require('../stack/frame')
|
17 | , utils = require('../utils')
|
18 | , bifs = require('../functions')
|
19 | , dirname = require('path').dirname
|
20 | , colors = require('../colors')
|
21 | , debug = require('debug')('stylus:evaluator')
|
22 | , fs = require('fs');
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | function importFile(node, file, literal) {
|
30 | var importStack = this.importStack
|
31 | , Parser = require('../parser')
|
32 | , stat;
|
33 |
|
34 |
|
35 | if (node.once) {
|
36 | if (this.requireHistory[file]) return nodes.null;
|
37 | this.requireHistory[file] = true;
|
38 |
|
39 | if (literal && !this.includeCSS) {
|
40 | return node;
|
41 | }
|
42 | }
|
43 |
|
44 |
|
45 | if (~importStack.indexOf(file))
|
46 | throw new Error('import loop has been found');
|
47 |
|
48 | var str = fs.readFileSync(file, 'utf8');
|
49 |
|
50 |
|
51 | if (!str.trim()) return nodes.null;
|
52 |
|
53 |
|
54 | node.path = file;
|
55 | node.dirname = dirname(file);
|
56 |
|
57 | stat = fs.statSync(file);
|
58 | node.mtime = stat.mtime;
|
59 | this.paths.push(node.dirname);
|
60 |
|
61 | if (this.options._imports) this.options._imports.push(node.clone());
|
62 |
|
63 |
|
64 | importStack.push(file);
|
65 | nodes.filename = file;
|
66 |
|
67 | if (literal) {
|
68 | literal = new nodes.Literal(str.replace(/\r\n?/g, '\n'));
|
69 | literal.lineno = literal.column = 1;
|
70 | if (!this.resolveURL) return literal;
|
71 | }
|
72 |
|
73 |
|
74 | var block = new nodes.Block
|
75 | , parser = new Parser(str, utils.merge({ root: block }, this.options));
|
76 |
|
77 | try {
|
78 | block = parser.parse();
|
79 | } catch (err) {
|
80 | var line = parser.lexer.lineno
|
81 | , column = parser.lexer.column;
|
82 |
|
83 | if (literal && this.includeCSS && this.resolveURL) {
|
84 | this.warn('ParseError: ' + file + ':' + line + ':' + column + '. This file included as-is');
|
85 | return literal;
|
86 | } else {
|
87 | err.filename = file;
|
88 | err.lineno = line;
|
89 | err.column = column;
|
90 | err.input = str;
|
91 | throw err;
|
92 | }
|
93 | }
|
94 |
|
95 |
|
96 | block = block.clone(this.currentBlock);
|
97 | block.parent = this.currentBlock;
|
98 | block.scope = false;
|
99 | var ret = this.visit(block);
|
100 | importStack.pop();
|
101 | if (!this.resolveURL || this.resolveURL.nocheck) this.paths.pop();
|
102 |
|
103 | return ret;
|
104 | }
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 | var Evaluator = module.exports = function Evaluator(root, options) {
|
120 | options = options || {};
|
121 | Visitor.call(this, root);
|
122 | var functions = this.functions = options.functions || {};
|
123 | this.stack = new Stack;
|
124 | this.imports = options.imports || [];
|
125 | this.globals = options.globals || {};
|
126 | this.paths = options.paths || [];
|
127 | this.prefix = options.prefix || '';
|
128 | this.filename = options.filename;
|
129 | this.includeCSS = options['include css'];
|
130 | this.resolveURL = functions.url
|
131 | && 'resolver' == functions.url.name
|
132 | && functions.url.options;
|
133 | this.paths.push(dirname(options.filename || '.'));
|
134 | this.stack.push(this.global = new Frame(root));
|
135 | this.warnings = options.warn;
|
136 | this.options = options;
|
137 | this.calling = [];
|
138 | this.importStack = [];
|
139 | this.requireHistory = {};
|
140 | this.return = 0;
|
141 | };
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 | Evaluator.prototype.__proto__ = Visitor.prototype;
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 | var visit = Visitor.prototype.visit;
|
158 | Evaluator.prototype.visit = function(node){
|
159 | try {
|
160 | return visit.call(this, node);
|
161 | } catch (err) {
|
162 | if (err.filename) throw err;
|
163 | err.lineno = node.lineno;
|
164 | err.column = node.column;
|
165 | err.filename = node.filename;
|
166 | err.stylusStack = this.stack.toString();
|
167 | try {
|
168 | err.input = fs.readFileSync(err.filename, 'utf8');
|
169 | } catch (err) {
|
170 |
|
171 | }
|
172 | throw err;
|
173 | }
|
174 | };
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 | Evaluator.prototype.setup = function(){
|
186 | var root = this.root;
|
187 | var imports = [];
|
188 |
|
189 | this.populateGlobalScope();
|
190 | this.imports.forEach(function(file){
|
191 | var expr = new nodes.Expression;
|
192 | expr.push(new nodes.String(file));
|
193 | imports.push(new nodes.Import(expr));
|
194 | }, this);
|
195 |
|
196 | root.nodes = imports.concat(root.nodes);
|
197 | };
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 | Evaluator.prototype.populateGlobalScope = function(){
|
209 | var scope = this.global.scope;
|
210 |
|
211 |
|
212 | Object.keys(colors).forEach(function(name){
|
213 | var color = colors[name]
|
214 | , rgba = new nodes.RGBA(color[0], color[1], color[2], color[3])
|
215 | , node = new nodes.Ident(name, rgba);
|
216 | rgba.name = name;
|
217 | scope.add(node);
|
218 | });
|
219 |
|
220 |
|
221 | scope.add(new nodes.Ident(
|
222 | 'embedurl',
|
223 | new nodes.Function('embedurl', require('../functions/url')({
|
224 | limit: false
|
225 | }))
|
226 | ));
|
227 |
|
228 |
|
229 | var globals = this.globals;
|
230 | Object.keys(globals).forEach(function(name){
|
231 | var val = globals[name];
|
232 | if (!val.nodeName) val = new nodes.Literal(val);
|
233 | scope.add(new nodes.Ident(name, val));
|
234 | });
|
235 | };
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 | Evaluator.prototype.evaluate = function(){
|
245 | debug('eval %s', this.filename);
|
246 | this.setup();
|
247 | return this.visit(this.root);
|
248 | };
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | Evaluator.prototype.visitGroup = function(group){
|
255 | group.nodes = group.nodes.map(function(selector){
|
256 | selector.val = this.interpolate(selector);
|
257 | debug('ruleset %s', selector.val);
|
258 | return selector;
|
259 | }, this);
|
260 |
|
261 | group.block = this.visit(group.block);
|
262 | return group;
|
263 | };
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 | Evaluator.prototype.visitReturn = function(ret){
|
270 | ret.expr = this.visit(ret.expr);
|
271 | throw ret;
|
272 | };
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 | Evaluator.prototype.visitMedia = function(media){
|
279 | media.block = this.visit(media.block);
|
280 | media.val = this.visit(media.val);
|
281 | return media;
|
282 | };
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 | Evaluator.prototype.visitQueryList = function(queries){
|
289 | var val, query;
|
290 | queries.nodes.forEach(this.visit, this);
|
291 |
|
292 | if (1 == queries.nodes.length) {
|
293 | query = queries.nodes[0];
|
294 | if (val = this.lookup(query.type)) {
|
295 | val = val.first.string;
|
296 | if (!val) return queries;
|
297 | var Parser = require('../parser')
|
298 | , parser = new Parser(val, this.options);
|
299 | queries = this.visit(parser.queries());
|
300 | }
|
301 | }
|
302 | return queries;
|
303 | };
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 | Evaluator.prototype.visitQuery = function(node){
|
310 | node.predicate = this.visit(node.predicate);
|
311 | node.type = this.visit(node.type);
|
312 | node.nodes.forEach(this.visit, this);
|
313 | return node;
|
314 | };
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 | Evaluator.prototype.visitFeature = function(node){
|
321 | node.name = this.interpolate(node);
|
322 | if (node.expr) {
|
323 | this.return++;
|
324 | node.expr = this.visit(node.expr);
|
325 | this.return--;
|
326 | }
|
327 | return node;
|
328 | };
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 | Evaluator.prototype.visitObject = function(obj){
|
335 | for (var key in obj.vals) {
|
336 | obj.vals[key] = this.visit(obj.vals[key]);
|
337 | }
|
338 | return obj;
|
339 | };
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 | Evaluator.prototype.visitMember = function(node){
|
346 | var left = node.left
|
347 | , right = node.right
|
348 | , obj = this.visit(left).first;
|
349 |
|
350 | if ('object' != obj.nodeName) {
|
351 | throw new Error(left.toString() + ' has no property .' + right);
|
352 | }
|
353 | if (node.val) {
|
354 | this.return++;
|
355 | obj.set(right.name, this.visit(node.val));
|
356 | this.return--;
|
357 | }
|
358 | return obj.get(right.name);
|
359 | };
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 | Evaluator.prototype.visitKeyframes = function(keyframes){
|
366 | var val;
|
367 | if (keyframes.fabricated) return keyframes;
|
368 | keyframes.val = this.interpolate(keyframes).trim();
|
369 | if (val = this.lookup(keyframes.val)) {
|
370 | keyframes.val = val.first.string || val.first.name;
|
371 | }
|
372 | keyframes.block = this.visit(keyframes.block);
|
373 |
|
374 | if ('official' != keyframes.prefix) return keyframes;
|
375 |
|
376 | this.vendors.forEach(function(prefix){
|
377 |
|
378 | if ('ms' == prefix) return;
|
379 | var node = keyframes.clone();
|
380 | node.val = keyframes.val;
|
381 | node.prefix = prefix;
|
382 | node.block = keyframes.block;
|
383 | node.fabricated = true;
|
384 | this.currentBlock.push(node);
|
385 | }, this);
|
386 |
|
387 | return nodes.null;
|
388 | };
|
389 |
|
390 |
|
391 |
|
392 |
|
393 |
|
394 | Evaluator.prototype.visitFunction = function(fn){
|
395 |
|
396 | var local = this.stack.currentFrame.scope.lookup(fn.name);
|
397 | if (local) this.warn('local ' + local.nodeName + ' "' + fn.name + '" previously defined in this scope');
|
398 |
|
399 |
|
400 | var user = this.functions[fn.name];
|
401 | if (user) this.warn('user-defined function "' + fn.name + '" is already defined');
|
402 |
|
403 |
|
404 | var bif = bifs[fn.name];
|
405 | if (bif) this.warn('built-in function "' + fn.name + '" is already defined');
|
406 |
|
407 | return fn;
|
408 | };
|
409 |
|
410 |
|
411 |
|
412 |
|
413 |
|
414 | Evaluator.prototype.visitEach = function(each){
|
415 | this.return++;
|
416 | var expr = utils.unwrap(this.visit(each.expr))
|
417 | , len = expr.nodes.length
|
418 | , val = new nodes.Ident(each.val)
|
419 | , key = new nodes.Ident(each.key || '__index__')
|
420 | , scope = this.currentScope
|
421 | , block = this.currentBlock
|
422 | , vals = []
|
423 | , self = this
|
424 | , body
|
425 | , obj;
|
426 | this.return--;
|
427 |
|
428 | each.block.scope = false;
|
429 |
|
430 | function visitBody(key, val) {
|
431 | scope.add(val);
|
432 | scope.add(key);
|
433 | body = self.visit(each.block.clone());
|
434 | vals = vals.concat(body.nodes);
|
435 | }
|
436 |
|
437 |
|
438 | if (1 == len && 'object' == expr.nodes[0].nodeName) {
|
439 | obj = expr.nodes[0];
|
440 | for (var prop in obj.vals) {
|
441 | val.val = new nodes.String(prop);
|
442 | key.val = obj.get(prop);
|
443 | visitBody(key, val);
|
444 | }
|
445 | } else {
|
446 | for (var i = 0; i < len; ++i) {
|
447 | val.val = expr.nodes[i];
|
448 | key.val = new nodes.Unit(i);
|
449 | visitBody(key, val);
|
450 | }
|
451 | }
|
452 |
|
453 | this.mixin(vals, block);
|
454 | return vals[vals.length - 1] || nodes.null;
|
455 | };
|
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 | Evaluator.prototype.visitCall = function(call){
|
462 | debug('call %s', call);
|
463 | var fn = this.lookup(call.name)
|
464 | , literal
|
465 | , ret;
|
466 |
|
467 |
|
468 | this.ignoreColors = 'url' == call.name;
|
469 |
|
470 |
|
471 | if (fn && 'expression' == fn.nodeName) {
|
472 | fn = fn.nodes[0];
|
473 | }
|
474 |
|
475 |
|
476 | if (fn && 'function' != fn.nodeName) {
|
477 | fn = this.lookupFunction(call.name);
|
478 | }
|
479 |
|
480 |
|
481 | if (!fn || fn.nodeName != 'function') {
|
482 | debug('%s is undefined', call);
|
483 |
|
484 | if ('calc' == this.unvendorize(call.name)) {
|
485 | literal = call.args.nodes && call.args.nodes[0];
|
486 | if (literal) ret = new nodes.Literal(call.name + literal);
|
487 | } else {
|
488 | ret = this.literalCall(call);
|
489 | }
|
490 | this.ignoreColors = false;
|
491 | return ret;
|
492 | }
|
493 |
|
494 | this.calling.push(call.name);
|
495 |
|
496 |
|
497 | if (this.calling.length > 200) {
|
498 | throw new RangeError('Maximum stylus call stack size exceeded');
|
499 | }
|
500 |
|
501 |
|
502 | if ('expression' == fn.nodeName) fn = fn.first;
|
503 |
|
504 |
|
505 | this.return++;
|
506 | var args = this.visit(call.args);
|
507 |
|
508 | for (var key in args.map) {
|
509 | args.map[key] = this.visit(args.map[key].clone());
|
510 | }
|
511 | this.return--;
|
512 |
|
513 |
|
514 | if (fn.fn) {
|
515 | debug('%s is built-in', call);
|
516 | ret = this.invokeBuiltin(fn.fn, args);
|
517 |
|
518 | } else if ('function' == fn.nodeName) {
|
519 | debug('%s is user-defined', call);
|
520 |
|
521 | if (call.block) call.block = this.visit(call.block);
|
522 | ret = this.invokeFunction(fn, args, call.block);
|
523 | }
|
524 |
|
525 | this.calling.pop();
|
526 | this.ignoreColors = false;
|
527 | return ret;
|
528 | };
|
529 |
|
530 |
|
531 |
|
532 |
|
533 |
|
534 | Evaluator.prototype.visitIdent = function(ident){
|
535 | var prop;
|
536 |
|
537 | if (ident.property) {
|
538 | if (prop = this.lookupProperty(ident.name)) {
|
539 | return this.visit(prop.expr.clone());
|
540 | }
|
541 | return nodes.null;
|
542 |
|
543 | } else if (ident.val.isNull) {
|
544 | var val = this.lookup(ident.name);
|
545 |
|
546 | if (val && ident.mixin) this.mixinNode(val);
|
547 | return val ? this.visit(val) : ident;
|
548 |
|
549 | } else {
|
550 | this.return++;
|
551 | ident.val = this.visit(ident.val);
|
552 | this.return--;
|
553 | this.currentScope.add(ident);
|
554 | return ident.val;
|
555 | }
|
556 | };
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 | Evaluator.prototype.visitBinOp = function(binop){
|
563 |
|
564 | if ('is defined' == binop.op) return this.isDefined(binop.left);
|
565 |
|
566 | this.return++;
|
567 |
|
568 | var op = binop.op
|
569 | , left = this.visit(binop.left)
|
570 | , right = ('||' == op || '&&' == op)
|
571 | ? binop.right : this.visit(binop.right);
|
572 |
|
573 |
|
574 | var val = binop.val
|
575 | ? this.visit(binop.val)
|
576 | : null;
|
577 | this.return--;
|
578 |
|
579 |
|
580 | try {
|
581 | return this.visit(left.operate(op, right, val));
|
582 | } catch (err) {
|
583 |
|
584 |
|
585 | if ('CoercionError' == err.name) {
|
586 | switch (op) {
|
587 | case '==':
|
588 | return nodes.false;
|
589 | case '!=':
|
590 | return nodes.true;
|
591 | }
|
592 | }
|
593 | throw err;
|
594 | }
|
595 | };
|
596 |
|
597 |
|
598 |
|
599 |
|
600 |
|
601 | Evaluator.prototype.visitUnaryOp = function(unary){
|
602 | var op = unary.op
|
603 | , node = this.visit(unary.expr);
|
604 |
|
605 | if ('!' != op) {
|
606 | node = node.first.clone();
|
607 | utils.assertType(node, 'unit');
|
608 | }
|
609 |
|
610 | switch (op) {
|
611 | case '-':
|
612 | node.val = -node.val;
|
613 | break;
|
614 | case '+':
|
615 | node.val = +node.val;
|
616 | break;
|
617 | case '~':
|
618 | node.val = ~node.val;
|
619 | break;
|
620 | case '!':
|
621 | return node.toBoolean().negate();
|
622 | }
|
623 |
|
624 | return node;
|
625 | };
|
626 |
|
627 |
|
628 |
|
629 |
|
630 |
|
631 | Evaluator.prototype.visitTernary = function(ternary){
|
632 | var ok = this.visit(ternary.cond).toBoolean();
|
633 | return ok.isTrue
|
634 | ? this.visit(ternary.trueExpr)
|
635 | : this.visit(ternary.falseExpr);
|
636 | };
|
637 |
|
638 |
|
639 |
|
640 |
|
641 |
|
642 | Evaluator.prototype.visitExpression = function(expr){
|
643 | for (var i = 0, len = expr.nodes.length; i < len; ++i) {
|
644 | expr.nodes[i] = this.visit(expr.nodes[i]);
|
645 | }
|
646 |
|
647 |
|
648 | if (this.castable(expr)) expr = this.cast(expr);
|
649 |
|
650 | return expr;
|
651 | };
|
652 |
|
653 |
|
654 |
|
655 |
|
656 |
|
657 | Evaluator.prototype.visitArguments = Evaluator.prototype.visitExpression;
|
658 |
|
659 |
|
660 |
|
661 |
|
662 |
|
663 | Evaluator.prototype.visitProperty = function(prop){
|
664 | var name = this.interpolate(prop)
|
665 | , fn = this.lookup(name)
|
666 | , call = fn && 'function' == fn.first.nodeName
|
667 | , literal = ~this.calling.indexOf(name)
|
668 | , _prop = this.property;
|
669 |
|
670 |
|
671 | if (call && !literal && !prop.literal) {
|
672 | var args = nodes.Arguments.fromExpression(utils.unwrap(prop.expr.clone()));
|
673 | prop.name = name;
|
674 | this.property = prop;
|
675 | this.return++;
|
676 | this.property.expr = this.visit(prop.expr);
|
677 | this.return--;
|
678 | var ret = this.visit(new nodes.Call(name, args));
|
679 | this.property = _prop;
|
680 | return ret;
|
681 |
|
682 | } else {
|
683 | this.return++;
|
684 | prop.name = name;
|
685 | prop.literal = true;
|
686 | this.property = prop;
|
687 | prop.expr = this.visit(prop.expr);
|
688 | this.property = _prop;
|
689 | this.return--;
|
690 | return prop;
|
691 | }
|
692 | };
|
693 |
|
694 |
|
695 |
|
696 |
|
697 |
|
698 | Evaluator.prototype.visitRoot = function(block){
|
699 |
|
700 | if (block != this.root) {
|
701 | block.constructor = nodes.Block;
|
702 | return this.visit(block);
|
703 | }
|
704 |
|
705 | for (var i = 0; i < block.nodes.length; ++i) {
|
706 | block.index = i;
|
707 | block.nodes[i] = this.visit(block.nodes[i]);
|
708 | }
|
709 | return block;
|
710 | };
|
711 |
|
712 |
|
713 |
|
714 |
|
715 |
|
716 | Evaluator.prototype.visitBlock = function(block){
|
717 | this.stack.push(new Frame(block));
|
718 | for (block.index = 0; block.index < block.nodes.length; ++block.index) {
|
719 | try {
|
720 | block.nodes[block.index] = this.visit(block.nodes[block.index]);
|
721 | } catch (err) {
|
722 | if ('return' == err.nodeName) {
|
723 | if (this.return) {
|
724 | this.stack.pop();
|
725 | throw err;
|
726 | } else {
|
727 | block.nodes[block.index] = err;
|
728 | break;
|
729 | }
|
730 | } else {
|
731 | throw err;
|
732 | }
|
733 | }
|
734 | }
|
735 | this.stack.pop();
|
736 | return block;
|
737 | };
|
738 |
|
739 |
|
740 |
|
741 |
|
742 |
|
743 | Evaluator.prototype.visitAtblock = function(atblock){
|
744 | atblock.block = this.visit(atblock.block);
|
745 | return atblock;
|
746 | };
|
747 |
|
748 |
|
749 |
|
750 |
|
751 |
|
752 | Evaluator.prototype.visitAtrule = function(atrule){
|
753 | atrule.val = this.interpolate(atrule);
|
754 | if (atrule.block) atrule.block = this.visit(atrule.block);
|
755 | return atrule;
|
756 | };
|
757 |
|
758 |
|
759 |
|
760 |
|
761 |
|
762 | Evaluator.prototype.visitSupports = function(node){
|
763 | var condition = node.condition
|
764 | , val;
|
765 |
|
766 | this.return++;
|
767 | node.condition = this.visit(condition);
|
768 | this.return--;
|
769 |
|
770 | val = condition.first;
|
771 | if (1 == condition.nodes.length
|
772 | && 'string' == val.nodeName) {
|
773 | node.condition = val.string;
|
774 | }
|
775 | node.block = this.visit(node.block);
|
776 | return node;
|
777 | };
|
778 |
|
779 |
|
780 |
|
781 |
|
782 |
|
783 | Evaluator.prototype.visitIf = function(node){
|
784 | var ret
|
785 | , block = this.currentBlock
|
786 | , negate = node.negate;
|
787 |
|
788 | this.return++;
|
789 | var ok = this.visit(node.cond).first.toBoolean();
|
790 | this.return--;
|
791 |
|
792 | node.block.scope = node.block.hasMedia;
|
793 |
|
794 |
|
795 | if (negate) {
|
796 |
|
797 | if (ok.isFalse) {
|
798 | ret = this.visit(node.block);
|
799 | }
|
800 | } else {
|
801 |
|
802 | if (ok.isTrue) {
|
803 | ret = this.visit(node.block);
|
804 |
|
805 | } else if (node.elses.length) {
|
806 | var elses = node.elses
|
807 | , len = elses.length
|
808 | , cond;
|
809 | for (var i = 0; i < len; ++i) {
|
810 |
|
811 | if (elses[i].cond) {
|
812 | elses[i].block.scope = elses[i].block.hasMedia;
|
813 | this.return++;
|
814 | cond = this.visit(elses[i].cond).first.toBoolean();
|
815 | this.return--;
|
816 | if (cond.isTrue) {
|
817 | ret = this.visit(elses[i].block);
|
818 | break;
|
819 | }
|
820 |
|
821 | } else {
|
822 | elses[i].scope = elses[i].hasMedia;
|
823 | ret = this.visit(elses[i]);
|
824 | }
|
825 | }
|
826 | }
|
827 | }
|
828 |
|
829 |
|
830 |
|
831 | if (ret && !node.postfix && block.node
|
832 | && ~['group'
|
833 | , 'atrule'
|
834 | , 'media'
|
835 | , 'supports'
|
836 | , 'keyframes'].indexOf(block.node.nodeName)) {
|
837 | this.mixin(ret.nodes, block);
|
838 | return nodes.null;
|
839 | }
|
840 |
|
841 | return ret || nodes.null;
|
842 | };
|
843 |
|
844 |
|
845 |
|
846 |
|
847 |
|
848 | Evaluator.prototype.visitExtend = function(extend){
|
849 | var block = this.currentBlock;
|
850 | if ('group' != block.node.nodeName) block = this.closestGroup;
|
851 | extend.selectors.forEach(function(selector){
|
852 | block.node.extends.push({
|
853 |
|
854 |
|
855 | selector: this.interpolate(selector.clone()).trim(),
|
856 | optional: selector.optional,
|
857 | lineno: selector.lineno,
|
858 | column: selector.column
|
859 | });
|
860 | }, this);
|
861 | return nodes.null;
|
862 | };
|
863 |
|
864 |
|
865 |
|
866 |
|
867 |
|
868 | Evaluator.prototype.visitImport = function(imported){
|
869 | this.return++;
|
870 |
|
871 | var path = this.visit(imported.path).first
|
872 | , nodeName = imported.once ? 'require' : 'import'
|
873 | , found
|
874 | , literal;
|
875 |
|
876 | this.return--;
|
877 | debug('import %s', path);
|
878 |
|
879 |
|
880 | if ('url' == path.name) {
|
881 | if (imported.once) throw new Error('You cannot @require a url');
|
882 |
|
883 | return imported;
|
884 | }
|
885 |
|
886 |
|
887 | if (!path.string) throw new Error('@' + nodeName + ' string expected');
|
888 |
|
889 | var name = path = path.string;
|
890 |
|
891 |
|
892 | if (/(?:url\s*\(\s*)?['"]?(?:#|(?:https?:)?\/\/)/i.test(path)) {
|
893 | if (imported.once) throw new Error('You cannot @require a url');
|
894 | return imported;
|
895 | }
|
896 |
|
897 |
|
898 | if (/\.css(?:"|$)/.test(path)) {
|
899 | literal = true;
|
900 | if (!imported.once && !this.includeCSS) {
|
901 | return imported;
|
902 | }
|
903 | }
|
904 |
|
905 |
|
906 | if (!literal && !/\.styl$/i.test(path)) path += '.styl';
|
907 |
|
908 |
|
909 | found = utils.find(path, this.paths, this.filename);
|
910 | if (!found) {
|
911 | found = utils.lookupIndex(name, this.paths, this.filename);
|
912 | }
|
913 |
|
914 |
|
915 | if (!found) throw new Error('failed to locate @' + nodeName + ' file ' + path);
|
916 |
|
917 | var block = new nodes.Block;
|
918 |
|
919 | for (var i = 0, len = found.length; i < len; ++i) {
|
920 | block.push(importFile.call(this, imported, found[i], literal));
|
921 | }
|
922 |
|
923 | return block;
|
924 | };
|
925 |
|
926 |
|
927 |
|
928 |
|
929 |
|
930 |
|
931 |
|
932 |
|
933 |
|
934 |
|
935 | Evaluator.prototype.invokeFunction = function(fn, args, content){
|
936 | var block = new nodes.Block(fn.block.parent);
|
937 |
|
938 |
|
939 |
|
940 | var body = fn.block.clone(block);
|
941 |
|
942 |
|
943 | var mixinBlock = this.stack.currentFrame.block;
|
944 |
|
945 |
|
946 | this.stack.push(new Frame(block));
|
947 | var scope = this.currentScope;
|
948 |
|
949 |
|
950 | if ('arguments' != args.nodeName) {
|
951 | var expr = new nodes.Expression;
|
952 | expr.push(args);
|
953 | args = nodes.Arguments.fromExpression(expr);
|
954 | }
|
955 |
|
956 |
|
957 | scope.add(new nodes.Ident('arguments', args));
|
958 |
|
959 |
|
960 | scope.add(new nodes.Ident('mixin', this.return
|
961 | ? nodes.false
|
962 | : new nodes.String(mixinBlock.nodeName)));
|
963 |
|
964 |
|
965 | if (this.property) {
|
966 | var prop = this.propertyExpression(this.property, fn.name);
|
967 | scope.add(new nodes.Ident('current-property', prop));
|
968 | } else {
|
969 | scope.add(new nodes.Ident('current-property', nodes.null));
|
970 | }
|
971 |
|
972 |
|
973 | var expr = new nodes.Expression;
|
974 | for (var i = this.calling.length - 1; i-- ; ) {
|
975 | expr.push(new nodes.Literal(this.calling[i]));
|
976 | };
|
977 | scope.add(new nodes.Ident('called-from', expr));
|
978 |
|
979 |
|
980 | var i = 0
|
981 | , len = args.nodes.length;
|
982 | fn.params.nodes.forEach(function(node){
|
983 |
|
984 | if (node.rest) {
|
985 | node.val = new nodes.Expression;
|
986 | for (; i < len; ++i) node.val.push(args.nodes[i]);
|
987 | node.val.preserve = true;
|
988 | node.val.isList = args.isList;
|
989 |
|
990 | } else {
|
991 | var arg = args.map[node.name] || args.nodes[i++];
|
992 | node = node.clone();
|
993 | if (arg) {
|
994 | arg.isEmpty ? args.nodes[i - 1] = this.visit(node) : node.val = arg;
|
995 | } else {
|
996 | args.push(node.val);
|
997 | }
|
998 |
|
999 |
|
1000 | if (node.val.isNull) {
|
1001 | throw new Error('argument "' + node + '" required for ' + fn);
|
1002 | }
|
1003 | }
|
1004 |
|
1005 | scope.add(node);
|
1006 | }, this);
|
1007 |
|
1008 |
|
1009 | if (content) scope.add(new nodes.Ident('block', content, true));
|
1010 |
|
1011 |
|
1012 | return this.invoke(body, true, fn.filename);
|
1013 | };
|
1014 |
|
1015 |
|
1016 |
|
1017 |
|
1018 |
|
1019 |
|
1020 |
|
1021 |
|
1022 |
|
1023 |
|
1024 | Evaluator.prototype.invokeBuiltin = function(fn, args){
|
1025 |
|
1026 |
|
1027 |
|
1028 |
|
1029 |
|
1030 | if (fn.raw) {
|
1031 | args = args.nodes;
|
1032 | } else {
|
1033 | args = utils.params(fn).reduce(function(ret, param){
|
1034 | var arg = args.map[param] || args.nodes.shift()
|
1035 | if (arg) {
|
1036 | arg = utils.unwrap(arg);
|
1037 | var len = arg.nodes.length;
|
1038 | if (len > 1) {
|
1039 | for (var i = 0; i < len; ++i) {
|
1040 | ret.push(utils.unwrap(arg.nodes[i].first));
|
1041 | }
|
1042 | } else {
|
1043 | ret.push(arg.first);
|
1044 | }
|
1045 | }
|
1046 | return ret;
|
1047 | }, []);
|
1048 | }
|
1049 |
|
1050 |
|
1051 | var body = utils.coerce(fn.apply(this, args));
|
1052 |
|
1053 |
|
1054 |
|
1055 |
|
1056 | var expr = new nodes.Expression;
|
1057 | expr.push(body);
|
1058 | body = expr;
|
1059 |
|
1060 |
|
1061 | return this.invoke(body);
|
1062 | };
|
1063 |
|
1064 |
|
1065 |
|
1066 |
|
1067 |
|
1068 |
|
1069 |
|
1070 |
|
1071 |
|
1072 | Evaluator.prototype.invoke = function(body, stack, filename){
|
1073 | var self = this
|
1074 | , ret;
|
1075 |
|
1076 | if (filename) this.paths.push(dirname(filename));
|
1077 |
|
1078 |
|
1079 | if (this.return) {
|
1080 | ret = this.eval(body.nodes);
|
1081 | if (stack) this.stack.pop();
|
1082 |
|
1083 | } else {
|
1084 | body = this.visit(body);
|
1085 | if (stack) this.stack.pop();
|
1086 | this.mixin(body.nodes, this.currentBlock);
|
1087 | ret = nodes.null;
|
1088 | }
|
1089 |
|
1090 | if (filename) this.paths.pop();
|
1091 |
|
1092 | return ret;
|
1093 | };
|
1094 |
|
1095 |
|
1096 |
|
1097 |
|
1098 |
|
1099 |
|
1100 |
|
1101 |
|
1102 |
|
1103 | Evaluator.prototype.mixin = function(nodes, block){
|
1104 | if (!nodes.length) return;
|
1105 | var len = block.nodes.length
|
1106 | , head = block.nodes.slice(0, block.index)
|
1107 | , tail = block.nodes.slice(block.index + 1, len);
|
1108 | this._mixin(nodes, head, block);
|
1109 | block.index = 0;
|
1110 | block.nodes = head.concat(tail);
|
1111 | };
|
1112 |
|
1113 |
|
1114 |
|
1115 |
|
1116 |
|
1117 |
|
1118 |
|
1119 |
|
1120 |
|
1121 |
|
1122 | Evaluator.prototype._mixin = function(items, dest, block){
|
1123 | var node
|
1124 | , len = items.length;
|
1125 | for (var i = 0; i < len; ++i) {
|
1126 | switch ((node = items[i]).nodeName) {
|
1127 | case 'return':
|
1128 | return;
|
1129 | case 'block':
|
1130 | this._mixin(node.nodes, dest, block);
|
1131 | break;
|
1132 | case 'media':
|
1133 |
|
1134 | var parentNode = node.block.parent.node;
|
1135 | if (parentNode && 'call' != parentNode.nodeName) {
|
1136 | node.block.parent = block;
|
1137 | }
|
1138 | case 'property':
|
1139 | var val = node.expr;
|
1140 |
|
1141 | if (node.literal && 'block' == val.first.name) {
|
1142 | val = utils.unwrap(val);
|
1143 | val.nodes[0] = new nodes.Literal('block');
|
1144 | }
|
1145 | default:
|
1146 | dest.push(node);
|
1147 | }
|
1148 | }
|
1149 | };
|
1150 |
|
1151 |
|
1152 |
|
1153 |
|
1154 |
|
1155 |
|
1156 |
|
1157 |
|
1158 | Evaluator.prototype.mixinNode = function(node){
|
1159 | node = this.visit(node.first);
|
1160 | switch (node.nodeName) {
|
1161 | case 'object':
|
1162 | this.mixinObject(node);
|
1163 | return nodes.null;
|
1164 | case 'block':
|
1165 | case 'atblock':
|
1166 | this.mixin(node.nodes, this.currentBlock);
|
1167 | return nodes.null;
|
1168 | }
|
1169 | };
|
1170 |
|
1171 |
|
1172 |
|
1173 |
|
1174 |
|
1175 |
|
1176 |
|
1177 |
|
1178 | Evaluator.prototype.mixinObject = function(object){
|
1179 | var Parser = require('../parser')
|
1180 | , root = this.root
|
1181 | , str = '$block ' + object.toBlock()
|
1182 | , parser = new Parser(str, utils.merge({ root: block }, this.options))
|
1183 | , block;
|
1184 |
|
1185 | try {
|
1186 | block = parser.parse();
|
1187 | } catch (err) {
|
1188 | err.filename = this.filename;
|
1189 | err.lineno = parser.lexer.lineno;
|
1190 | err.column = parser.lexer.column;
|
1191 | err.input = str;
|
1192 | throw err;
|
1193 | }
|
1194 |
|
1195 | block.parent = root;
|
1196 | block.scope = false;
|
1197 | var ret = this.visit(block)
|
1198 | , vals = ret.first.nodes;
|
1199 | for (var i = 0, len = vals.length; i < len; ++i) {
|
1200 | if (vals[i].block) {
|
1201 | this.mixin(vals[i].block.nodes, this.currentBlock);
|
1202 | break;
|
1203 | }
|
1204 | }
|
1205 | };
|
1206 |
|
1207 |
|
1208 |
|
1209 |
|
1210 |
|
1211 |
|
1212 |
|
1213 |
|
1214 |
|
1215 | Evaluator.prototype.eval = function(vals){
|
1216 | if (!vals) return nodes.null;
|
1217 | var len = vals.length
|
1218 | , node = nodes.null;
|
1219 |
|
1220 | try {
|
1221 | for (var i = 0; i < len; ++i) {
|
1222 | node = vals[i];
|
1223 | switch (node.nodeName) {
|
1224 | case 'if':
|
1225 | if ('block' != node.block.nodeName) {
|
1226 | node = this.visit(node);
|
1227 | break;
|
1228 | }
|
1229 | case 'each':
|
1230 | case 'block':
|
1231 | node = this.visit(node);
|
1232 | if (node.nodes) node = this.eval(node.nodes);
|
1233 | break;
|
1234 | default:
|
1235 | node = this.visit(node);
|
1236 | }
|
1237 | }
|
1238 | } catch (err) {
|
1239 | if ('return' == err.nodeName) {
|
1240 | return err.expr;
|
1241 | } else {
|
1242 | throw err;
|
1243 | }
|
1244 | }
|
1245 |
|
1246 | return node;
|
1247 | };
|
1248 |
|
1249 |
|
1250 |
|
1251 |
|
1252 |
|
1253 |
|
1254 |
|
1255 |
|
1256 |
|
1257 | Evaluator.prototype.literalCall = function(call){
|
1258 | call.args = this.visit(call.args);
|
1259 | return call;
|
1260 | };
|
1261 |
|
1262 |
|
1263 |
|
1264 |
|
1265 |
|
1266 |
|
1267 |
|
1268 |
|
1269 |
|
1270 | Evaluator.prototype.lookupProperty = function(name){
|
1271 | var i = this.stack.length
|
1272 | , index = this.currentBlock.index
|
1273 | , top = i
|
1274 | , nodes
|
1275 | , block
|
1276 | , len
|
1277 | , other;
|
1278 |
|
1279 | while (i--) {
|
1280 | block = this.stack[i].block;
|
1281 | if (!block.node) continue;
|
1282 | switch (block.node.nodeName) {
|
1283 | case 'group':
|
1284 | case 'function':
|
1285 | case 'if':
|
1286 | case 'each':
|
1287 | case 'atrule':
|
1288 | case 'media':
|
1289 | case 'atblock':
|
1290 | case 'call':
|
1291 | nodes = block.nodes;
|
1292 |
|
1293 | if (i + 1 == top) {
|
1294 | while (index--) {
|
1295 |
|
1296 | if (this.property == nodes[index]) continue;
|
1297 | other = this.interpolate(nodes[index]);
|
1298 | if (name == other) return nodes[index].clone();
|
1299 | }
|
1300 |
|
1301 | } else {
|
1302 | len = nodes.length;
|
1303 | while (len--) {
|
1304 | if ('property' != nodes[len].nodeName
|
1305 | || this.property == nodes[len]) continue;
|
1306 | other = this.interpolate(nodes[len]);
|
1307 | if (name == other) return nodes[len].clone();
|
1308 | }
|
1309 | }
|
1310 | break;
|
1311 | }
|
1312 | }
|
1313 |
|
1314 | return nodes.null;
|
1315 | };
|
1316 |
|
1317 |
|
1318 |
|
1319 |
|
1320 |
|
1321 |
|
1322 |
|
1323 |
|
1324 | Evaluator.prototype.__defineGetter__('closestBlock', function(){
|
1325 | var i = this.stack.length
|
1326 | , block;
|
1327 | while (i--) {
|
1328 | block = this.stack[i].block;
|
1329 | if (block.node) {
|
1330 | switch (block.node.nodeName) {
|
1331 | case 'group':
|
1332 | case 'keyframes':
|
1333 | case 'atrule':
|
1334 | case 'atblock':
|
1335 | case 'media':
|
1336 | case 'call':
|
1337 | return block;
|
1338 | }
|
1339 | }
|
1340 | }
|
1341 | });
|
1342 |
|
1343 |
|
1344 |
|
1345 |
|
1346 |
|
1347 |
|
1348 |
|
1349 |
|
1350 | Evaluator.prototype.__defineGetter__('closestGroup', function(){
|
1351 | var i = this.stack.length
|
1352 | , block;
|
1353 | while (i--) {
|
1354 | block = this.stack[i].block;
|
1355 | if (block.node && 'group' == block.node.nodeName) {
|
1356 | return block;
|
1357 | }
|
1358 | }
|
1359 | });
|
1360 |
|
1361 |
|
1362 |
|
1363 |
|
1364 |
|
1365 |
|
1366 |
|
1367 |
|
1368 | Evaluator.prototype.__defineGetter__('selectorStack', function(){
|
1369 | var block
|
1370 | , stack = [];
|
1371 | for (var i = 0, len = this.stack.length; i < len; ++i) {
|
1372 | block = this.stack[i].block;
|
1373 | if (block.node && 'group' == block.node.nodeName) {
|
1374 | block.node.nodes.forEach(function(selector) {
|
1375 | if (!selector.val) selector.val = this.interpolate(selector);
|
1376 | }, this);
|
1377 | stack.push(block.node.nodes);
|
1378 | }
|
1379 | }
|
1380 | return stack;
|
1381 | });
|
1382 |
|
1383 |
|
1384 |
|
1385 |
|
1386 |
|
1387 |
|
1388 |
|
1389 |
|
1390 |
|
1391 |
|
1392 | Evaluator.prototype.lookup = function(name){
|
1393 | var val;
|
1394 | if (this.ignoreColors && name in colors) return;
|
1395 | if (val = this.stack.lookup(name)) {
|
1396 | return utils.unwrap(val);
|
1397 | } else {
|
1398 | return this.lookupFunction(name);
|
1399 | }
|
1400 | };
|
1401 |
|
1402 |
|
1403 |
|
1404 |
|
1405 |
|
1406 |
|
1407 |
|
1408 |
|
1409 |
|
1410 | Evaluator.prototype.interpolate = function(node){
|
1411 | var self = this
|
1412 | , isSelector = ('selector' == node.nodeName);
|
1413 | function toString(node) {
|
1414 | switch (node.nodeName) {
|
1415 | case 'function':
|
1416 | case 'ident':
|
1417 | return node.name;
|
1418 | case 'literal':
|
1419 | case 'string':
|
1420 | if (self.prefix && !node.prefixed && !node.val.nodeName) {
|
1421 | node.val = node.val.replace(/\./g, '.' + self.prefix);
|
1422 | node.prefixed = true;
|
1423 | }
|
1424 | return node.val;
|
1425 | case 'unit':
|
1426 |
|
1427 | return '%' == node.type ? node.val + '%' : node.val;
|
1428 | case 'member':
|
1429 | return toString(self.visit(node));
|
1430 | case 'expression':
|
1431 |
|
1432 | if (self.calling && ~self.calling.indexOf('selector') && self._selector) return self._selector;
|
1433 | self.return++;
|
1434 | var ret = toString(self.visit(node).first);
|
1435 | self.return--;
|
1436 | if (isSelector) self._selector = ret;
|
1437 | return ret;
|
1438 | }
|
1439 | }
|
1440 |
|
1441 | if (node.segments) {
|
1442 | return node.segments.map(toString).join('');
|
1443 | } else {
|
1444 | return toString(node);
|
1445 | }
|
1446 | };
|
1447 |
|
1448 |
|
1449 |
|
1450 |
|
1451 |
|
1452 |
|
1453 |
|
1454 |
|
1455 |
|
1456 | Evaluator.prototype.lookupFunction = function(name){
|
1457 | var fn = this.functions[name] || bifs[name];
|
1458 | if (fn) return new nodes.Function(name, fn);
|
1459 | };
|
1460 |
|
1461 |
|
1462 |
|
1463 |
|
1464 |
|
1465 |
|
1466 |
|
1467 |
|
1468 |
|
1469 | Evaluator.prototype.isDefined = function(node){
|
1470 | if ('ident' == node.nodeName) {
|
1471 | return nodes.Boolean(this.lookup(node.name));
|
1472 | } else {
|
1473 | throw new Error('invalid "is defined" check on non-variable ' + node);
|
1474 | }
|
1475 | };
|
1476 |
|
1477 |
|
1478 |
|
1479 |
|
1480 |
|
1481 |
|
1482 |
|
1483 |
|
1484 |
|
1485 |
|
1486 |
|
1487 |
|
1488 | Evaluator.prototype.propertyExpression = function(prop, name){
|
1489 | var expr = new nodes.Expression
|
1490 | , val = prop.expr.clone();
|
1491 |
|
1492 |
|
1493 | expr.push(new nodes.String(prop.name));
|
1494 |
|
1495 |
|
1496 | function replace(node) {
|
1497 | if ('call' == node.nodeName && name == node.name) {
|
1498 | return new nodes.Literal('__CALL__');
|
1499 | }
|
1500 |
|
1501 | if (node.nodes) node.nodes = node.nodes.map(replace);
|
1502 | return node;
|
1503 | }
|
1504 |
|
1505 | replace(val);
|
1506 | expr.push(val);
|
1507 | return expr;
|
1508 | };
|
1509 |
|
1510 |
|
1511 |
|
1512 |
|
1513 |
|
1514 |
|
1515 |
|
1516 |
|
1517 |
|
1518 | Evaluator.prototype.cast = function(expr){
|
1519 | return new nodes.Unit(expr.first.val, expr.nodes[1].name);
|
1520 | };
|
1521 |
|
1522 |
|
1523 |
|
1524 |
|
1525 |
|
1526 |
|
1527 |
|
1528 |
|
1529 |
|
1530 | Evaluator.prototype.castable = function(expr){
|
1531 | return 2 == expr.nodes.length
|
1532 | && 'unit' == expr.first.nodeName
|
1533 | && ~units.indexOf(expr.nodes[1].name);
|
1534 | };
|
1535 |
|
1536 |
|
1537 |
|
1538 |
|
1539 |
|
1540 |
|
1541 |
|
1542 |
|
1543 | Evaluator.prototype.warn = function(msg){
|
1544 | if (!this.warnings) return;
|
1545 | console.warn('\u001b[33mWarning:\u001b[0m ' + msg);
|
1546 | };
|
1547 |
|
1548 |
|
1549 |
|
1550 |
|
1551 |
|
1552 |
|
1553 |
|
1554 |
|
1555 | Evaluator.prototype.__defineGetter__('currentBlock', function(){
|
1556 | return this.stack.currentFrame.block;
|
1557 | });
|
1558 |
|
1559 |
|
1560 |
|
1561 |
|
1562 |
|
1563 |
|
1564 |
|
1565 |
|
1566 | Evaluator.prototype.__defineGetter__('vendors', function(){
|
1567 | return this.lookup('vendors').nodes.map(function(node){
|
1568 | return node.string;
|
1569 | });
|
1570 | });
|
1571 |
|
1572 |
|
1573 |
|
1574 |
|
1575 |
|
1576 |
|
1577 |
|
1578 |
|
1579 |
|
1580 | Evaluator.prototype.unvendorize = function(prop){
|
1581 | for (var i = 0, len = this.vendors.length; i < len; i++) {
|
1582 | if ('official' != this.vendors[i]) {
|
1583 | var vendor = '-' + this.vendors[i] + '-';
|
1584 | if (~prop.indexOf(vendor)) return prop.replace(vendor, '');
|
1585 | }
|
1586 | }
|
1587 | return prop;
|
1588 | };
|
1589 |
|
1590 |
|
1591 |
|
1592 |
|
1593 |
|
1594 |
|
1595 |
|
1596 |
|
1597 | Evaluator.prototype.__defineGetter__('currentScope', function(){
|
1598 | return this.stack.currentFrame.scope;
|
1599 | });
|
1600 |
|
1601 |
|
1602 |
|
1603 |
|
1604 |
|
1605 |
|
1606 |
|
1607 |
|
1608 | Evaluator.prototype.__defineGetter__('currentFrame', function(){
|
1609 | return this.stack.currentFrame;
|
1610 | });
|