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