expression.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /*!
  2. * Stylus - Expression
  3. * Copyright (c) Automattic <developer.wordpress.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var Node = require('./node')
  10. , nodes = require('../nodes')
  11. , utils = require('../utils');
  12. /**
  13. * Initialize a new `Expression`.
  14. *
  15. * @param {Boolean} isList
  16. * @api public
  17. */
  18. var Expression = module.exports = function Expression(isList){
  19. Node.call(this);
  20. this.nodes = [];
  21. this.isList = isList;
  22. };
  23. /**
  24. * Check if the variable has a value.
  25. *
  26. * @return {Boolean}
  27. * @api public
  28. */
  29. Expression.prototype.__defineGetter__('isEmpty', function(){
  30. return !this.nodes.length;
  31. });
  32. /**
  33. * Return the first node in this expression.
  34. *
  35. * @return {Node}
  36. * @api public
  37. */
  38. Expression.prototype.__defineGetter__('first', function(){
  39. return this.nodes[0]
  40. ? this.nodes[0].first
  41. : nodes.null;
  42. });
  43. /**
  44. * Hash all the nodes in order.
  45. *
  46. * @return {String}
  47. * @api public
  48. */
  49. Expression.prototype.__defineGetter__('hash', function(){
  50. return this.nodes.map(function(node){
  51. return node.hash;
  52. }).join('::');
  53. });
  54. /**
  55. * Inherit from `Node.prototype`.
  56. */
  57. Expression.prototype.__proto__ = Node.prototype;
  58. /**
  59. * Return a clone of this node.
  60. *
  61. * @return {Node}
  62. * @api public
  63. */
  64. Expression.prototype.clone = function(parent){
  65. var clone = new this.constructor(this.isList);
  66. clone.preserve = this.preserve;
  67. clone.lineno = this.lineno;
  68. clone.column = this.column;
  69. clone.filename = this.filename;
  70. clone.nodes = this.nodes.map(function(node) {
  71. return node.clone(parent, clone);
  72. });
  73. return clone;
  74. };
  75. /**
  76. * Push the given `node`.
  77. *
  78. * @param {Node} node
  79. * @api public
  80. */
  81. Expression.prototype.push = function(node){
  82. this.nodes.push(node);
  83. };
  84. /**
  85. * Operate on `right` with the given `op`.
  86. *
  87. * @param {String} op
  88. * @param {Node} right
  89. * @return {Node}
  90. * @api public
  91. */
  92. Expression.prototype.operate = function(op, right, val){
  93. switch (op) {
  94. case '[]=':
  95. var self = this
  96. , range = utils.unwrap(right).nodes
  97. , val = utils.unwrap(val)
  98. , len
  99. , node;
  100. range.forEach(function(unit){
  101. len = self.nodes.length;
  102. if ('unit' == unit.nodeName) {
  103. var i = unit.val < 0 ? len + unit.val : unit.val
  104. , n = i;
  105. while (i-- > len) self.nodes[i] = nodes.null;
  106. self.nodes[n] = val;
  107. } else if (unit.string) {
  108. node = self.nodes[0];
  109. if (node && 'object' == node.nodeName) node.set(unit.string, val.clone());
  110. }
  111. });
  112. return val;
  113. case '[]':
  114. var expr = new nodes.Expression
  115. , vals = utils.unwrap(this).nodes
  116. , range = utils.unwrap(right).nodes
  117. , node;
  118. range.forEach(function(unit){
  119. if ('unit' == unit.nodeName) {
  120. node = vals[unit.val < 0 ? vals.length + unit.val : unit.val];
  121. } else if ('object' == vals[0].nodeName) {
  122. node = vals[0].get(unit.string);
  123. }
  124. if (node) expr.push(node);
  125. });
  126. return expr.isEmpty
  127. ? nodes.null
  128. : utils.unwrap(expr);
  129. case '||':
  130. return this.toBoolean().isTrue
  131. ? this
  132. : right;
  133. case 'in':
  134. return Node.prototype.operate.call(this, op, right);
  135. case '!=':
  136. return this.operate('==', right, val).negate();
  137. case '==':
  138. var len = this.nodes.length
  139. , right = right.toExpression()
  140. , a
  141. , b;
  142. if (len != right.nodes.length) return nodes.false;
  143. for (var i = 0; i < len; ++i) {
  144. a = this.nodes[i];
  145. b = right.nodes[i];
  146. if (a.operate(op, b).isTrue) continue;
  147. return nodes.false;
  148. }
  149. return nodes.true;
  150. break;
  151. default:
  152. return this.first.operate(op, right, val);
  153. }
  154. };
  155. /**
  156. * Expressions with length > 1 are truthy,
  157. * otherwise the first value's toBoolean()
  158. * method is invoked.
  159. *
  160. * @return {Boolean}
  161. * @api public
  162. */
  163. Expression.prototype.toBoolean = function(){
  164. if (this.nodes.length > 1) return nodes.true;
  165. return this.first.toBoolean();
  166. };
  167. /**
  168. * Return "<a> <b> <c>" or "<a>, <b>, <c>" if
  169. * the expression represents a list.
  170. *
  171. * @return {String}
  172. * @api public
  173. */
  174. Expression.prototype.toString = function(){
  175. return '(' + this.nodes.map(function(node){
  176. return node.toString();
  177. }).join(this.isList ? ', ' : ' ') + ')';
  178. };
  179. /**
  180. * Return a JSON representation of this node.
  181. *
  182. * @return {Object}
  183. * @api public
  184. */
  185. Expression.prototype.toJSON = function(){
  186. return {
  187. __type: 'Expression',
  188. isList: this.isList,
  189. preserve: this.preserve,
  190. lineno: this.lineno,
  191. column: this.column,
  192. filename: this.filename,
  193. nodes: this.nodes
  194. };
  195. };