You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
4.6 KiB
220 lines
4.6 KiB
|
|
/*! |
|
* Stylus - Expression |
|
* Copyright (c) Automattic <developer.wordpress.com> |
|
* 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 "<a> <b> <c>" or "<a>, <b>, <c>" 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 |
|
}; |
|
};
|
|
|