node.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. /*!
  2. * Stylus - Node
  3. * Copyright (c) Automattic <developer.wordpress.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var Evaluator = require('../visitor/evaluator')
  10. , utils = require('../utils')
  11. , nodes = require('./');
  12. /**
  13. * Initialize a new `CoercionError` with the given `msg`.
  14. *
  15. * @param {String} msg
  16. * @api private
  17. */
  18. function CoercionError(msg) {
  19. this.name = 'CoercionError'
  20. this.message = msg
  21. Error.captureStackTrace(this, CoercionError);
  22. }
  23. /**
  24. * Inherit from `Error.prototype`.
  25. */
  26. CoercionError.prototype.__proto__ = Error.prototype;
  27. /**
  28. * Node constructor.
  29. *
  30. * @api public
  31. */
  32. var Node = module.exports = function Node(){
  33. this.lineno = nodes.lineno || 1;
  34. this.column = nodes.column || 1;
  35. this.filename = nodes.filename;
  36. };
  37. Node.prototype = {
  38. constructor: Node,
  39. /**
  40. * Return this node.
  41. *
  42. * @return {Node}
  43. * @api public
  44. */
  45. get first() {
  46. return this;
  47. },
  48. /**
  49. * Return hash.
  50. *
  51. * @return {String}
  52. * @api public
  53. */
  54. get hash() {
  55. return this.val;
  56. },
  57. /**
  58. * Return node name.
  59. *
  60. * @return {String}
  61. * @api public
  62. */
  63. get nodeName() {
  64. return this.constructor.name.toLowerCase();
  65. },
  66. /**
  67. * Return this node.
  68. *
  69. * @return {Node}
  70. * @api public
  71. */
  72. clone: function(){
  73. return this;
  74. },
  75. /**
  76. * Return a JSON representation of this node.
  77. *
  78. * @return {Object}
  79. * @api public
  80. */
  81. toJSON: function(){
  82. return {
  83. lineno: this.lineno,
  84. column: this.column,
  85. filename: this.filename
  86. };
  87. },
  88. /**
  89. * Nodes by default evaluate to themselves.
  90. *
  91. * @return {Node}
  92. * @api public
  93. */
  94. eval: function(){
  95. return new Evaluator(this).evaluate();
  96. },
  97. /**
  98. * Return true.
  99. *
  100. * @return {Boolean}
  101. * @api public
  102. */
  103. toBoolean: function(){
  104. return nodes.true;
  105. },
  106. /**
  107. * Return the expression, or wrap this node in an expression.
  108. *
  109. * @return {Expression}
  110. * @api public
  111. */
  112. toExpression: function(){
  113. if ('expression' == this.nodeName) return this;
  114. var expr = new nodes.Expression;
  115. expr.push(this);
  116. return expr;
  117. },
  118. /**
  119. * Return false if `op` is generally not coerced.
  120. *
  121. * @param {String} op
  122. * @return {Boolean}
  123. * @api private
  124. */
  125. shouldCoerce: function(op){
  126. switch (op) {
  127. case 'is a':
  128. case 'in':
  129. case '||':
  130. case '&&':
  131. return false;
  132. default:
  133. return true;
  134. }
  135. },
  136. /**
  137. * Operate on `right` with the given `op`.
  138. *
  139. * @param {String} op
  140. * @param {Node} right
  141. * @return {Node}
  142. * @api public
  143. */
  144. operate: function(op, right){
  145. switch (op) {
  146. case 'is a':
  147. if ('string' == right.first.nodeName) {
  148. return nodes.Boolean(this.nodeName == right.val);
  149. } else {
  150. throw new Error('"is a" expects a string, got ' + right.toString());
  151. }
  152. case '==':
  153. return nodes.Boolean(this.hash == right.hash);
  154. case '!=':
  155. return nodes.Boolean(this.hash != right.hash);
  156. case '>=':
  157. return nodes.Boolean(this.hash >= right.hash);
  158. case '<=':
  159. return nodes.Boolean(this.hash <= right.hash);
  160. case '>':
  161. return nodes.Boolean(this.hash > right.hash);
  162. case '<':
  163. return nodes.Boolean(this.hash < right.hash);
  164. case '||':
  165. return this.toBoolean().isTrue
  166. ? this
  167. : right;
  168. case 'in':
  169. var vals = utils.unwrap(right).nodes
  170. , len = vals && vals.length
  171. , hash = this.hash;
  172. if (!vals) throw new Error('"in" given invalid right-hand operand, expecting an expression');
  173. // 'prop' in obj
  174. if (1 == len && 'object' == vals[0].nodeName) {
  175. return nodes.Boolean(vals[0].has(this.hash));
  176. }
  177. for (var i = 0; i < len; ++i) {
  178. if (hash == vals[i].hash) {
  179. return nodes.true;
  180. }
  181. }
  182. return nodes.false;
  183. case '&&':
  184. var a = this.toBoolean()
  185. , b = right.toBoolean();
  186. return a.isTrue && b.isTrue
  187. ? right
  188. : a.isFalse
  189. ? this
  190. : right;
  191. default:
  192. if ('[]' == op) {
  193. var msg = 'cannot perform '
  194. + this
  195. + '[' + right + ']';
  196. } else {
  197. var msg = 'cannot perform'
  198. + ' ' + this
  199. + ' ' + op
  200. + ' ' + right;
  201. }
  202. throw new Error(msg);
  203. }
  204. },
  205. /**
  206. * Default coercion throws.
  207. *
  208. * @param {Node} other
  209. * @return {Node}
  210. * @api public
  211. */
  212. coerce: function(other){
  213. if (other.nodeName == this.nodeName) return other;
  214. throw new CoercionError('cannot coerce ' + other + ' to ' + this.nodeName);
  215. }
  216. };