/*! * Stylus - Expression * Copyright (c) Automattic * MIT Licensed */ /** * Module dependencies. */ var Node = require('./node') , nodes = require('../nodes') , utils = require('../utils'); /** * Initialize a new `Expression`. * * @param {Boolean} isList * @api public */ var Expression = module.exports = function Expression(isList){ Node.call(this); this.nodes = []; this.isList = isList; }; /** * Check if the variable has a value. * * @return {Boolean} * @api public */ Expression.prototype.__defineGetter__('isEmpty', function(){ return !this.nodes.length; }); /** * Return the first node in this expression. * * @return {Node} * @api public */ Expression.prototype.__defineGetter__('first', function(){ return this.nodes[0] ? this.nodes[0].first : nodes.null; }); /** * Hash all the nodes in order. * * @return {String} * @api public */ Expression.prototype.__defineGetter__('hash', function(){ return this.nodes.map(function(node){ return node.hash; }).join('::'); }); /** * Inherit from `Node.prototype`. */ Expression.prototype.__proto__ = Node.prototype; /** * Return a clone of this node. * * @return {Node} * @api public */ Expression.prototype.clone = function(parent){ var clone = new this.constructor(this.isList); clone.preserve = this.preserve; clone.lineno = this.lineno; clone.column = this.column; clone.filename = this.filename; clone.nodes = this.nodes.map(function(node) { return node.clone(parent, clone); }); return clone; }; /** * Push the given `node`. * * @param {Node} node * @api public */ Expression.prototype.push = function(node){ this.nodes.push(node); }; /** * Operate on `right` with the given `op`. * * @param {String} op * @param {Node} right * @return {Node} * @api public */ Expression.prototype.operate = function(op, right, val){ switch (op) { case '[]=': var self = this , range = utils.unwrap(right).nodes , val = utils.unwrap(val) , len , node; range.forEach(function(unit){ len = self.nodes.length; if ('unit' == unit.nodeName) { var i = unit.val < 0 ? len + unit.val : unit.val , n = i; while (i-- > len) self.nodes[i] = nodes.null; self.nodes[n] = val; } else if (unit.string) { node = self.nodes[0]; if (node && 'object' == node.nodeName) node.set(unit.string, val.clone()); } }); return val; case '[]': var expr = new nodes.Expression , vals = utils.unwrap(this).nodes , range = utils.unwrap(right).nodes , node; range.forEach(function(unit){ if ('unit' == unit.nodeName) { node = vals[unit.val < 0 ? vals.length + unit.val : unit.val]; } else if ('object' == vals[0].nodeName) { node = vals[0].get(unit.string); } if (node) expr.push(node); }); return expr.isEmpty ? nodes.null : utils.unwrap(expr); case '||': return this.toBoolean().isTrue ? this : right; case 'in': return Node.prototype.operate.call(this, op, right); case '!=': return this.operate('==', right, val).negate(); case '==': var len = this.nodes.length , right = right.toExpression() , a , b; if (len != right.nodes.length) return nodes.false; for (var i = 0; i < len; ++i) { a = this.nodes[i]; b = right.nodes[i]; if (a.operate(op, b).isTrue) continue; return nodes.false; } return nodes.true; break; default: return this.first.operate(op, right, val); } }; /** * Expressions with length > 1 are truthy, * otherwise the first value's toBoolean() * method is invoked. * * @return {Boolean} * @api public */ Expression.prototype.toBoolean = function(){ if (this.nodes.length > 1) return nodes.true; return this.first.toBoolean(); }; /** * Return " " or ", , " if * the expression represents a list. * * @return {String} * @api public */ Expression.prototype.toString = function(){ return '(' + this.nodes.map(function(node){ return node.toString(); }).join(this.isList ? ', ' : ' ') + ')'; }; /** * Return a JSON representation of this node. * * @return {Object} * @api public */ Expression.prototype.toJSON = function(){ return { __type: 'Expression', isList: this.isList, preserve: this.preserve, lineno: this.lineno, column: this.column, filename: this.filename, nodes: this.nodes }; };