stylus-source 0.15.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.DS_Store +0 -0
- data/README.md +3 -0
- data/lib/.DS_Store +0 -0
- data/lib/node_modules/cssom/.idea/CSSOM.iml +9 -0
- data/lib/node_modules/cssom/.idea/dictionaries/nv.xml +3 -0
- data/lib/node_modules/cssom/.idea/encodings.xml +5 -0
- data/lib/node_modules/cssom/.idea/misc.xml +17 -0
- data/lib/node_modules/cssom/.idea/modules.xml +9 -0
- data/lib/node_modules/cssom/.idea/projectCodeStyle.xml +82 -0
- data/lib/node_modules/cssom/.idea/vcs.xml +8 -0
- data/lib/node_modules/cssom/.idea/workspace.xml +467 -0
- data/lib/node_modules/cssom/.livereload +19 -0
- data/lib/node_modules/cssom/Jakefile +37 -0
- data/lib/node_modules/cssom/README.mdown +33 -0
- data/lib/node_modules/cssom/Rakefile +23 -0
- data/lib/node_modules/cssom/docs/.livereload +19 -0
- data/lib/node_modules/cssom/docs/bar.css +3 -0
- data/lib/node_modules/cssom/docs/demo.css +0 -0
- data/lib/node_modules/cssom/docs/foo.css +4 -0
- data/lib/node_modules/cssom/docs/parse.html +170 -0
- data/lib/node_modules/cssom/docs/parse2.html +431 -0
- data/lib/node_modules/cssom/index.html +100 -0
- data/lib/node_modules/cssom/lib/CSSImportRule.js +34 -0
- data/lib/node_modules/cssom/lib/CSSMediaRule.js +38 -0
- data/lib/node_modules/cssom/lib/CSSOM.js +3 -0
- data/lib/node_modules/cssom/lib/CSSRule.js +38 -0
- data/lib/node_modules/cssom/lib/CSSStyleDeclaration.js +130 -0
- data/lib/node_modules/cssom/lib/CSSStyleRule.js +187 -0
- data/lib/node_modules/cssom/lib/CSSStyleSheet.js +85 -0
- data/lib/node_modules/cssom/lib/MediaList.js +61 -0
- data/lib/node_modules/cssom/lib/StyleSheet.js +15 -0
- data/lib/node_modules/cssom/lib/clone.js +69 -0
- data/lib/node_modules/cssom/lib/index.js +10 -0
- data/lib/node_modules/cssom/lib/parse.js +195 -0
- data/lib/node_modules/cssom/media.html +17 -0
- data/lib/node_modules/cssom/package.json +30 -0
- data/lib/node_modules/cssom/plugins/toHTML.js +32 -0
- data/lib/node_modules/cssom/server/index.html +22 -0
- data/lib/node_modules/cssom/server/index.js +21 -0
- data/lib/node_modules/cssom/shorthands.html +21 -0
- data/lib/node_modules/cssom/test/CSSStyleDeclaration.test.js +35 -0
- data/lib/node_modules/cssom/test/CSSStyleRule.test.js +12 -0
- data/lib/node_modules/cssom/test/CSSStyleSheet.test.js +16 -0
- data/lib/node_modules/cssom/test/MediaList.test.js +21 -0
- data/lib/node_modules/cssom/test/clone.test.js +38 -0
- data/lib/node_modules/cssom/test/fixtures/dummy.css +3 -0
- data/lib/node_modules/cssom/test/helper.js +97 -0
- data/lib/node_modules/cssom/test/index.html +42 -0
- data/lib/node_modules/cssom/test/parse.test.js +346 -0
- data/lib/node_modules/cssom/test/vendor/qunit.css +189 -0
- data/lib/node_modules/cssom/test/vendor/qunit.js +1341 -0
- data/lib/node_modules/growl/History.md +16 -0
- data/lib/node_modules/growl/Readme.md +74 -0
- data/lib/node_modules/growl/lib/growl.js +82 -0
- data/lib/node_modules/growl/package.json +6 -0
- data/lib/node_modules/growl/test.js +17 -0
- data/lib/stylus/colors.js +156 -0
- data/lib/stylus/convert/css.js +130 -0
- data/lib/stylus/errors.js +58 -0
- data/lib/stylus/functions/image.js +120 -0
- data/lib/stylus/functions/index.js +722 -0
- data/lib/stylus/functions/index.styl +123 -0
- data/lib/stylus/functions/url.js +98 -0
- data/lib/stylus/lexer.js +728 -0
- data/lib/stylus/middleware.js +223 -0
- data/lib/stylus/nodes/arguments.js +65 -0
- data/lib/stylus/nodes/binop.js +54 -0
- data/lib/stylus/nodes/block.js +99 -0
- data/lib/stylus/nodes/boolean.js +103 -0
- data/lib/stylus/nodes/call.js +57 -0
- data/lib/stylus/nodes/charset.js +42 -0
- data/lib/stylus/nodes/comment.js +32 -0
- data/lib/stylus/nodes/each.js +56 -0
- data/lib/stylus/nodes/expression.js +168 -0
- data/lib/stylus/nodes/fontface.js +55 -0
- data/lib/stylus/nodes/function.js +104 -0
- data/lib/stylus/nodes/group.js +79 -0
- data/lib/stylus/nodes/hsla.js +256 -0
- data/lib/stylus/nodes/ident.js +127 -0
- data/lib/stylus/nodes/if.js +55 -0
- data/lib/stylus/nodes/import.js +30 -0
- data/lib/stylus/nodes/index.js +52 -0
- data/lib/stylus/nodes/jsliteral.js +32 -0
- data/lib/stylus/nodes/keyframes.js +78 -0
- data/lib/stylus/nodes/literal.js +92 -0
- data/lib/stylus/nodes/media.js +42 -0
- data/lib/stylus/nodes/node.js +209 -0
- data/lib/stylus/nodes/null.js +72 -0
- data/lib/stylus/nodes/page.js +43 -0
- data/lib/stylus/nodes/params.js +72 -0
- data/lib/stylus/nodes/property.js +72 -0
- data/lib/stylus/nodes/return.js +44 -0
- data/lib/stylus/nodes/rgba.js +335 -0
- data/lib/stylus/nodes/root.js +50 -0
- data/lib/stylus/nodes/selector.js +57 -0
- data/lib/stylus/nodes/string.js +120 -0
- data/lib/stylus/nodes/ternary.js +51 -0
- data/lib/stylus/nodes/unaryop.js +46 -0
- data/lib/stylus/nodes/unit.js +207 -0
- data/lib/stylus/parser.js +1514 -0
- data/lib/stylus/renderer.js +157 -0
- data/lib/stylus/source.rb +7 -0
- data/lib/stylus/stack/frame.js +66 -0
- data/lib/stylus/stack/index.js +146 -0
- data/lib/stylus/stack/scope.js +53 -0
- data/lib/stylus/stylus.js +102 -0
- data/lib/stylus/token.js +53 -0
- data/lib/stylus/utils.js +237 -0
- data/lib/stylus/visitor/compiler.js +472 -0
- data/lib/stylus/visitor/evaluator.js +1070 -0
- data/lib/stylus/visitor/index.js +31 -0
- data/stylus-source.gemspec +15 -0
- metadata +158 -0
|
@@ -0,0 +1,1514 @@
|
|
|
1
|
+
|
|
2
|
+
/*!
|
|
3
|
+
* Stylus - Parser
|
|
4
|
+
* Copyright(c) 2010 LearnBoost <dev@learnboost.com>
|
|
5
|
+
* MIT Licensed
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Module dependencies.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
var Lexer = require('./lexer')
|
|
13
|
+
, nodes = require('./nodes')
|
|
14
|
+
, Token = require('./token')
|
|
15
|
+
, inspect = require('sys').inspect
|
|
16
|
+
, errors = require('./errors');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Selector composite tokens.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
var selectorTokens = [
|
|
23
|
+
'ident'
|
|
24
|
+
, 'string'
|
|
25
|
+
, 'selector'
|
|
26
|
+
, 'function'
|
|
27
|
+
, 'comment'
|
|
28
|
+
, 'boolean'
|
|
29
|
+
, 'space'
|
|
30
|
+
, 'color'
|
|
31
|
+
, 'unit'
|
|
32
|
+
, 'for'
|
|
33
|
+
, '['
|
|
34
|
+
, ']'
|
|
35
|
+
, '('
|
|
36
|
+
, ')'
|
|
37
|
+
, '+'
|
|
38
|
+
, '-'
|
|
39
|
+
, '*'
|
|
40
|
+
, '*='
|
|
41
|
+
, '<'
|
|
42
|
+
, '>'
|
|
43
|
+
, '='
|
|
44
|
+
, ':'
|
|
45
|
+
, '&'
|
|
46
|
+
, '~'
|
|
47
|
+
, '{'
|
|
48
|
+
, '}'
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* CSS3 pseudo-selectors.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
var pseudoSelectors = [
|
|
56
|
+
'root'
|
|
57
|
+
, 'nth-child'
|
|
58
|
+
, 'nth-last-child'
|
|
59
|
+
, 'nth-of-type'
|
|
60
|
+
, 'nth-last-of-type'
|
|
61
|
+
, 'first-child'
|
|
62
|
+
, 'last-child'
|
|
63
|
+
, 'first-of-type'
|
|
64
|
+
, 'last-of-type'
|
|
65
|
+
, 'only-child'
|
|
66
|
+
, 'only-of-type'
|
|
67
|
+
, 'empty'
|
|
68
|
+
, 'link'
|
|
69
|
+
, 'visited'
|
|
70
|
+
, 'active'
|
|
71
|
+
, 'hover'
|
|
72
|
+
, 'focus'
|
|
73
|
+
, 'target'
|
|
74
|
+
, 'lang'
|
|
75
|
+
, 'enabled'
|
|
76
|
+
, 'disabled'
|
|
77
|
+
, 'checked'
|
|
78
|
+
, 'not'
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Initialize a new `Parser` with the given `str` and `options`.
|
|
83
|
+
*
|
|
84
|
+
* @param {String} str
|
|
85
|
+
* @param {Object} options
|
|
86
|
+
* @api private
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
var Parser = module.exports = function Parser(str, options) {
|
|
90
|
+
var self = this;
|
|
91
|
+
options = options || {};
|
|
92
|
+
this.lexer = new Lexer(str, options);
|
|
93
|
+
this.root = options.root || new nodes.Root;
|
|
94
|
+
this.state = ['root'];
|
|
95
|
+
this.stash = [];
|
|
96
|
+
this.parens = 0;
|
|
97
|
+
this.state.pop = function(){
|
|
98
|
+
self.prevState = [].pop.call(this);
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parser prototype.
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
Parser.prototype = {
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Constructor.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
constructor: Parser,
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Return current state.
|
|
116
|
+
*
|
|
117
|
+
* @return {String}
|
|
118
|
+
* @api private
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
currentState: function() {
|
|
122
|
+
return this.state[this.state.length - 1];
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Parse the input, then return the root node.
|
|
127
|
+
*
|
|
128
|
+
* @return {Node}
|
|
129
|
+
* @api private
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
parse: function(){
|
|
133
|
+
var block = this.parent = this.root;
|
|
134
|
+
while ('eos' != this.peek().type) {
|
|
135
|
+
if (this.accept('newline')) continue;
|
|
136
|
+
var stmt = this.statement();
|
|
137
|
+
this.accept(';');
|
|
138
|
+
if (!stmt) this.error('unexpected token {peek}, not allowed at the root level');
|
|
139
|
+
block.push(stmt);
|
|
140
|
+
}
|
|
141
|
+
return block;
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Throw an `Error` with the given `msg`.
|
|
146
|
+
*
|
|
147
|
+
* @param {String} msg
|
|
148
|
+
* @api private
|
|
149
|
+
*/
|
|
150
|
+
|
|
151
|
+
error: function(msg){
|
|
152
|
+
var type = this.peek().type
|
|
153
|
+
, val = undefined == this.peek().val
|
|
154
|
+
? ''
|
|
155
|
+
: ' ' + this.peek().toString();
|
|
156
|
+
if (val.trim() == type.trim()) val = '';
|
|
157
|
+
throw new errors.ParseError(msg.replace('{peek}', '"' + type + val + '"'));
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Accept the given token `type`, and return it,
|
|
162
|
+
* otherwise return `undefined`.
|
|
163
|
+
*
|
|
164
|
+
* @param {String} type
|
|
165
|
+
* @return {Token}
|
|
166
|
+
* @api private
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
accept: function(type){
|
|
170
|
+
if (type == this.peek().type) {
|
|
171
|
+
return this.next();
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Expect token `type` and return it, throw otherwise.
|
|
177
|
+
*
|
|
178
|
+
* @param {String} type
|
|
179
|
+
* @return {Token}
|
|
180
|
+
* @api private
|
|
181
|
+
*/
|
|
182
|
+
|
|
183
|
+
expect: function(type){
|
|
184
|
+
if (type != this.peek().type) {
|
|
185
|
+
this.error('expected "' + type + '", got {peek}');
|
|
186
|
+
}
|
|
187
|
+
return this.next();
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get the next token.
|
|
192
|
+
*
|
|
193
|
+
* @return {Token}
|
|
194
|
+
* @api private
|
|
195
|
+
*/
|
|
196
|
+
|
|
197
|
+
next: function() {
|
|
198
|
+
var tok = this.stash.length
|
|
199
|
+
? this.stash.pop()
|
|
200
|
+
: this.lexer.next();
|
|
201
|
+
nodes.lineno = tok.lineno;
|
|
202
|
+
return tok;
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Peek with lookahead(1).
|
|
207
|
+
*
|
|
208
|
+
* @return {Token}
|
|
209
|
+
* @api private
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
peek: function() {
|
|
213
|
+
return this.lexer.peek();
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Lookahead `n` tokens.
|
|
218
|
+
*
|
|
219
|
+
* @param {Number} n
|
|
220
|
+
* @return {Token}
|
|
221
|
+
* @api private
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
lookahead: function(n){
|
|
225
|
+
return this.lexer.lookahead(n);
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Check if the token at `n` is a valid selector token.
|
|
230
|
+
*
|
|
231
|
+
* @param {Number} n
|
|
232
|
+
* @return {Boolean}
|
|
233
|
+
* @api private
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
isSelectorToken: function(n) {
|
|
237
|
+
var la = this.lookahead(n).type;
|
|
238
|
+
switch (la) {
|
|
239
|
+
case 'for':
|
|
240
|
+
return this.bracketed;
|
|
241
|
+
case '[':
|
|
242
|
+
this.bracketed = true;
|
|
243
|
+
return true;
|
|
244
|
+
case ']':
|
|
245
|
+
this.bracketed = false;
|
|
246
|
+
return true;
|
|
247
|
+
default:
|
|
248
|
+
return ~selectorTokens.indexOf(la);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Check if the token at `n` is a pseudo selector.
|
|
254
|
+
*
|
|
255
|
+
* @param {Number} n
|
|
256
|
+
* @return {Boolean}
|
|
257
|
+
* @api private
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
isPseudoSelector: function(n){
|
|
261
|
+
return ~pseudoSelectors.indexOf(this.lookahead(n).val.name);
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Check if the current line contains `type`.
|
|
266
|
+
*
|
|
267
|
+
* @param {String} type
|
|
268
|
+
* @return {Boolean}
|
|
269
|
+
* @api private
|
|
270
|
+
*/
|
|
271
|
+
|
|
272
|
+
lineContains: function(type){
|
|
273
|
+
var i = 1
|
|
274
|
+
, la;
|
|
275
|
+
|
|
276
|
+
while (la = this.lookahead(i++)) {
|
|
277
|
+
if (~['indent', 'outdent', 'newline'].indexOf(la.type)) return;
|
|
278
|
+
if (type == la.type) return true;
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Valid selector tokens.
|
|
284
|
+
*/
|
|
285
|
+
|
|
286
|
+
selectorToken: function() {
|
|
287
|
+
if (this.isSelectorToken(1)) {
|
|
288
|
+
if ('{' == this.peek().type) {
|
|
289
|
+
// unclosed, must be a block
|
|
290
|
+
if (!this.lineContains('}')) return;
|
|
291
|
+
// check if ':' is within the braces.
|
|
292
|
+
// though not required by stylus, chances
|
|
293
|
+
// are if someone is using {} they will
|
|
294
|
+
// use css-style props, helping us with
|
|
295
|
+
// the ambiguity in this case
|
|
296
|
+
var i = 0
|
|
297
|
+
, la;
|
|
298
|
+
while (la = this.lookahead(++i)) {
|
|
299
|
+
if ('}' == la.type) break;
|
|
300
|
+
if (':' == la.type) return;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return this.next();
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Consume whitespace.
|
|
309
|
+
*/
|
|
310
|
+
|
|
311
|
+
skipWhitespace: function() {
|
|
312
|
+
while (~['space', 'indent', 'outdent', 'newline'].indexOf(this.peek().type))
|
|
313
|
+
this.next();
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Consume spaces.
|
|
318
|
+
*/
|
|
319
|
+
|
|
320
|
+
skipSpaces: function() {
|
|
321
|
+
while ('space' == this.peek().type)
|
|
322
|
+
this.next();
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Check if the following sequence of tokens
|
|
327
|
+
* forms a function definition, ie trailing
|
|
328
|
+
* `{` or indentation.
|
|
329
|
+
*/
|
|
330
|
+
|
|
331
|
+
looksLikeFunctionDefinition: function(i) {
|
|
332
|
+
return 'indent' == this.lookahead(i).type
|
|
333
|
+
|| '{' == this.lookahead(i).type;
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Check if the following sequence of tokens
|
|
338
|
+
* forms a selector.
|
|
339
|
+
*/
|
|
340
|
+
|
|
341
|
+
looksLikeSelector: function() {
|
|
342
|
+
var i = 1
|
|
343
|
+
, brace;
|
|
344
|
+
|
|
345
|
+
// Assume selector when an ident is
|
|
346
|
+
// followed by a selector
|
|
347
|
+
while ('ident' == this.lookahead(i).type
|
|
348
|
+
&& 'newline' == this.lookahead(i + 1).type) i += 2;
|
|
349
|
+
|
|
350
|
+
while (this.isSelectorToken(i)
|
|
351
|
+
|| ',' == this.lookahead(i).type) {
|
|
352
|
+
|
|
353
|
+
if ('selector' == this.lookahead(i).type)
|
|
354
|
+
return true;
|
|
355
|
+
|
|
356
|
+
// the ':' token within braces signifies
|
|
357
|
+
// a selector. ex: "foo{bar:'baz'}"
|
|
358
|
+
if ('{' == this.lookahead(i).type) brace = true;
|
|
359
|
+
else if ('}' == this.lookahead(i).type) brace = false;
|
|
360
|
+
if (brace && ':' == this.lookahead(i).type) return true;
|
|
361
|
+
|
|
362
|
+
// '}' preceded by a space is considered a selector.
|
|
363
|
+
// for example "foo{bar}{baz}" may be a property,
|
|
364
|
+
// however "foo{bar} {baz}" is a selector
|
|
365
|
+
if ('space' == this.lookahead(i).type
|
|
366
|
+
&& '{' == this.lookahead(i + 1).type)
|
|
367
|
+
return true;
|
|
368
|
+
|
|
369
|
+
// Assume pseudo selectors are NOT properties
|
|
370
|
+
// as 'td:th-child(1)' may look like a property
|
|
371
|
+
// and function call to the parser otherwise
|
|
372
|
+
if (':' == this.lookahead(i++).type
|
|
373
|
+
&& this.isPseudoSelector(i))
|
|
374
|
+
return true;
|
|
375
|
+
|
|
376
|
+
if (',' == this.lookahead(i).type
|
|
377
|
+
&& 'newline' == this.lookahead(i + 1).type)
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Trailing comma
|
|
382
|
+
if (',' == this.lookahead(i).type
|
|
383
|
+
&& 'newline' == this.lookahead(i + 1).type)
|
|
384
|
+
return true;
|
|
385
|
+
|
|
386
|
+
// Trailing brace
|
|
387
|
+
if ('{' == this.lookahead(i).type
|
|
388
|
+
&& 'newline' == this.lookahead(i + 1).type)
|
|
389
|
+
return true;
|
|
390
|
+
|
|
391
|
+
// css-style mode, false on ; }
|
|
392
|
+
if (this.css) {
|
|
393
|
+
if (';' == this.lookahead(i) ||
|
|
394
|
+
'}' == this.lookahead(i))
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Trailing separators
|
|
399
|
+
while (!~[
|
|
400
|
+
'indent'
|
|
401
|
+
, 'outdent'
|
|
402
|
+
, 'newline'
|
|
403
|
+
, 'for'
|
|
404
|
+
, 'if'
|
|
405
|
+
, ';'
|
|
406
|
+
, '}'].indexOf(this.lookahead(i).type))
|
|
407
|
+
++i;
|
|
408
|
+
|
|
409
|
+
if ('indent' == this.lookahead(i).type)
|
|
410
|
+
return true;
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Check if the current state supports selectors.
|
|
415
|
+
*/
|
|
416
|
+
|
|
417
|
+
stateAllowsSelector: function() {
|
|
418
|
+
switch (this.currentState()) {
|
|
419
|
+
case 'root':
|
|
420
|
+
case 'selector':
|
|
421
|
+
case 'conditional':
|
|
422
|
+
case 'keyframe':
|
|
423
|
+
case 'function':
|
|
424
|
+
case 'font-face':
|
|
425
|
+
case 'media':
|
|
426
|
+
case 'for':
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* statement
|
|
433
|
+
* | statement 'if' expression
|
|
434
|
+
* | statement 'unless' expression
|
|
435
|
+
*/
|
|
436
|
+
|
|
437
|
+
statement: function() {
|
|
438
|
+
var stmt = this.stmt()
|
|
439
|
+
, state = this.prevState
|
|
440
|
+
, block
|
|
441
|
+
, op;
|
|
442
|
+
|
|
443
|
+
// special-case statements since it
|
|
444
|
+
// is not an expression. We could
|
|
445
|
+
// implement postfix conditionals at
|
|
446
|
+
// the expression level, however they
|
|
447
|
+
// would then fail to enclose properties
|
|
448
|
+
if (this.allowPostfix) {
|
|
449
|
+
delete this.allowPostfix;
|
|
450
|
+
state = 'expression';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
switch (state) {
|
|
454
|
+
case 'assignment':
|
|
455
|
+
case 'expression':
|
|
456
|
+
case 'function arguments':
|
|
457
|
+
while (op =
|
|
458
|
+
this.accept('if')
|
|
459
|
+
|| this.accept('unless')
|
|
460
|
+
|| this.accept('for')) {
|
|
461
|
+
switch (op.type) {
|
|
462
|
+
case 'if':
|
|
463
|
+
case 'unless':
|
|
464
|
+
stmt = new nodes.If(this.expression(), stmt);
|
|
465
|
+
stmt.postfix = true;
|
|
466
|
+
stmt.negate = 'unless' == op.type;
|
|
467
|
+
this.accept(';');
|
|
468
|
+
break;
|
|
469
|
+
case 'for':
|
|
470
|
+
var key
|
|
471
|
+
, val = this.id().name;
|
|
472
|
+
if (this.accept(',')) key = this.id().name;
|
|
473
|
+
this.expect('in');
|
|
474
|
+
var each = new nodes.Each(val, key, this.expression());
|
|
475
|
+
block = new nodes.Block;
|
|
476
|
+
block.push(stmt);
|
|
477
|
+
each.block = block;
|
|
478
|
+
stmt = each;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return stmt;
|
|
484
|
+
},
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* ident
|
|
488
|
+
* | selector
|
|
489
|
+
* | literal
|
|
490
|
+
* | charset
|
|
491
|
+
* | import
|
|
492
|
+
* | media
|
|
493
|
+
* | keyframes
|
|
494
|
+
* | page
|
|
495
|
+
* | for
|
|
496
|
+
* | if
|
|
497
|
+
* | unless
|
|
498
|
+
* | comment
|
|
499
|
+
* | expression
|
|
500
|
+
* | 'return' expression
|
|
501
|
+
*/
|
|
502
|
+
|
|
503
|
+
stmt: function() {
|
|
504
|
+
var type = this.peek().type;
|
|
505
|
+
switch (type) {
|
|
506
|
+
case '-webkit-keyframes':
|
|
507
|
+
case 'keyframes':
|
|
508
|
+
return this.keyframes();
|
|
509
|
+
case 'font-face':
|
|
510
|
+
return this.fontface();
|
|
511
|
+
case 'comment':
|
|
512
|
+
case 'selector':
|
|
513
|
+
case 'literal':
|
|
514
|
+
case 'charset':
|
|
515
|
+
case 'import':
|
|
516
|
+
case 'media':
|
|
517
|
+
case 'page':
|
|
518
|
+
case 'ident':
|
|
519
|
+
case 'unless':
|
|
520
|
+
case 'function':
|
|
521
|
+
case 'for':
|
|
522
|
+
case 'if':
|
|
523
|
+
return this[type]();
|
|
524
|
+
case 'return':
|
|
525
|
+
return this.return();
|
|
526
|
+
case '{':
|
|
527
|
+
return this.property();
|
|
528
|
+
default:
|
|
529
|
+
// Contextual selectors
|
|
530
|
+
if (this.stateAllowsSelector()) {
|
|
531
|
+
switch (type) {
|
|
532
|
+
case 'color':
|
|
533
|
+
case '~':
|
|
534
|
+
case '+':
|
|
535
|
+
case '>':
|
|
536
|
+
case '<':
|
|
537
|
+
case ':':
|
|
538
|
+
case '&':
|
|
539
|
+
case '[':
|
|
540
|
+
return this.selector();
|
|
541
|
+
case '*':
|
|
542
|
+
return this.property();
|
|
543
|
+
case '-':
|
|
544
|
+
if ('{' == this.lookahead(2).type)
|
|
545
|
+
return this.property();
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Expression fallback
|
|
550
|
+
var expr = this.expression();
|
|
551
|
+
if (expr.isEmpty) this.error('unexpected {peek}');
|
|
552
|
+
return expr;
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* indent (!outdent)+ outdent
|
|
558
|
+
*/
|
|
559
|
+
|
|
560
|
+
block: function(node, scope) {
|
|
561
|
+
var delim
|
|
562
|
+
, stmt
|
|
563
|
+
, _ = this.css
|
|
564
|
+
, block = this.parent = new nodes.Block(this.parent, node);
|
|
565
|
+
|
|
566
|
+
if (false === scope) block.scope = false;
|
|
567
|
+
|
|
568
|
+
// css-style
|
|
569
|
+
if (this.css = this.accept('{')) {
|
|
570
|
+
delim = '}';
|
|
571
|
+
this.skipWhitespace();
|
|
572
|
+
} else {
|
|
573
|
+
delim = 'outdent';
|
|
574
|
+
this.expect('indent');
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
while (delim != this.peek().type) {
|
|
578
|
+
// css-style
|
|
579
|
+
if (this.css) {
|
|
580
|
+
if (this.accept('newline')) continue;
|
|
581
|
+
stmt = this.statement();
|
|
582
|
+
this.accept(';');
|
|
583
|
+
this.skipWhitespace();
|
|
584
|
+
} else {
|
|
585
|
+
if (this.accept('newline')) continue;
|
|
586
|
+
stmt = this.statement();
|
|
587
|
+
this.accept(';');
|
|
588
|
+
}
|
|
589
|
+
if (!stmt) this.error('unexpected token {peek} in block');
|
|
590
|
+
block.push(stmt);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// css-style
|
|
594
|
+
if (this.css) {
|
|
595
|
+
this.skipWhitespace();
|
|
596
|
+
this.expect('}');
|
|
597
|
+
this.skipSpaces();
|
|
598
|
+
this.css = _;
|
|
599
|
+
} else {
|
|
600
|
+
this.expect('outdent');
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
this.parent = block.parent;
|
|
604
|
+
return block;
|
|
605
|
+
},
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* comment space*
|
|
609
|
+
*/
|
|
610
|
+
|
|
611
|
+
comment: function(){
|
|
612
|
+
var node = this.next().val;
|
|
613
|
+
this.skipSpaces();
|
|
614
|
+
return node;
|
|
615
|
+
},
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* for val (',' key) in expr
|
|
619
|
+
*/
|
|
620
|
+
|
|
621
|
+
for: function() {
|
|
622
|
+
this.expect('for');
|
|
623
|
+
var key
|
|
624
|
+
, val = this.id().name;
|
|
625
|
+
if (this.accept(',')) key = this.id().name;
|
|
626
|
+
this.expect('in');
|
|
627
|
+
var each = new nodes.Each(val, key, this.expression());
|
|
628
|
+
this.state.push('for');
|
|
629
|
+
each.block = this.block(each, false);
|
|
630
|
+
this.state.pop();
|
|
631
|
+
return each;
|
|
632
|
+
},
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* return expression
|
|
636
|
+
*/
|
|
637
|
+
|
|
638
|
+
return: function() {
|
|
639
|
+
this.expect('return');
|
|
640
|
+
var expr = this.expression();
|
|
641
|
+
return expr.isEmpty
|
|
642
|
+
? new nodes.Return
|
|
643
|
+
: new nodes.Return(expr);
|
|
644
|
+
},
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* unless expression block
|
|
648
|
+
*/
|
|
649
|
+
|
|
650
|
+
unless: function() {
|
|
651
|
+
this.expect('unless');
|
|
652
|
+
var node = new nodes.If(this.expression(), true);
|
|
653
|
+
this.state.push('conditional');
|
|
654
|
+
node.block = this.block(node, false);
|
|
655
|
+
this.state.pop();
|
|
656
|
+
return node;
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* if expression block (else block)?
|
|
661
|
+
*/
|
|
662
|
+
|
|
663
|
+
if: function() {
|
|
664
|
+
this.expect('if');
|
|
665
|
+
var node = new nodes.If(this.expression());
|
|
666
|
+
this.state.push('conditional');
|
|
667
|
+
node.block = this.block(node, false);
|
|
668
|
+
while (this.accept('else')) {
|
|
669
|
+
if (this.accept('if')) {
|
|
670
|
+
var cond = this.expression()
|
|
671
|
+
, block = this.block(node, false);
|
|
672
|
+
node.elses.push(new nodes.If(cond, block));
|
|
673
|
+
} else {
|
|
674
|
+
node.elses.push(this.block(node, false));
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
this.state.pop();
|
|
679
|
+
return node;
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* media
|
|
684
|
+
*/
|
|
685
|
+
|
|
686
|
+
media: function() {
|
|
687
|
+
var val = this.expect('media').val
|
|
688
|
+
, media = new nodes.Media(val);
|
|
689
|
+
this.state.push('media');
|
|
690
|
+
media.block = this.block(media);
|
|
691
|
+
this.state.pop();
|
|
692
|
+
return media;
|
|
693
|
+
},
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* fontface
|
|
697
|
+
*/
|
|
698
|
+
|
|
699
|
+
fontface: function() {
|
|
700
|
+
this.expect('font-face');
|
|
701
|
+
var node = new nodes.FontFace;
|
|
702
|
+
this.state.push('font-face');
|
|
703
|
+
node.block = this.block(node);
|
|
704
|
+
this.state.pop();
|
|
705
|
+
return node;
|
|
706
|
+
},
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* import expression
|
|
710
|
+
*/
|
|
711
|
+
|
|
712
|
+
import: function() {
|
|
713
|
+
this.expect('import');
|
|
714
|
+
this.allowPostfix = true;
|
|
715
|
+
return new nodes.Import(this.expression());
|
|
716
|
+
},
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* charset string
|
|
720
|
+
*/
|
|
721
|
+
|
|
722
|
+
charset: function() {
|
|
723
|
+
this.expect('charset');
|
|
724
|
+
var str = this.expect('string').val;
|
|
725
|
+
this.allowPostfix = true;
|
|
726
|
+
return new nodes.Charset(str);
|
|
727
|
+
},
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* page selector? block
|
|
731
|
+
*/
|
|
732
|
+
|
|
733
|
+
page: function() {
|
|
734
|
+
var selector;
|
|
735
|
+
this.expect('page');
|
|
736
|
+
if (this.accept(':')) {
|
|
737
|
+
var str = this.expect('ident').val.name;
|
|
738
|
+
selector = new nodes.Literal(':' + str);
|
|
739
|
+
}
|
|
740
|
+
var page = new nodes.Page(selector);
|
|
741
|
+
this.state.push('page');
|
|
742
|
+
page.block = this.block(page);
|
|
743
|
+
this.state.pop();
|
|
744
|
+
return page;
|
|
745
|
+
},
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* keyframes name ((unit | from | to) block)+
|
|
749
|
+
*/
|
|
750
|
+
|
|
751
|
+
keyframes: function() {
|
|
752
|
+
var pos
|
|
753
|
+
, _ = this.css
|
|
754
|
+
, tok = this.expect('keyframes')
|
|
755
|
+
, keyframes = new nodes.Keyframes(this.id(), tok.val);
|
|
756
|
+
|
|
757
|
+
// css-style
|
|
758
|
+
if (this.css = this.accept('{')) {
|
|
759
|
+
this.skipWhitespace();
|
|
760
|
+
} else {
|
|
761
|
+
this.expect('indent');
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
while (pos = this.accept('unit') || this.accept('ident')) {
|
|
765
|
+
// from | to
|
|
766
|
+
if ('ident' == pos.type) {
|
|
767
|
+
this.accept('space');
|
|
768
|
+
switch (pos.val.name) {
|
|
769
|
+
case 'from':
|
|
770
|
+
pos = new nodes.Unit(0, '%');
|
|
771
|
+
break;
|
|
772
|
+
case 'to':
|
|
773
|
+
pos = new nodes.Unit(100, '%');
|
|
774
|
+
break;
|
|
775
|
+
default:
|
|
776
|
+
this.error('"' + pos.val.name + '" is invalid, use "from" or "to"');
|
|
777
|
+
}
|
|
778
|
+
} else {
|
|
779
|
+
pos = pos.val;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// block
|
|
783
|
+
this.state.push('keyframe');
|
|
784
|
+
var block = this.block(keyframes);
|
|
785
|
+
keyframes.push(pos, block);
|
|
786
|
+
this.state.pop();
|
|
787
|
+
if (this.css) this.skipWhitespace();
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// css-style
|
|
791
|
+
if (this.css) {
|
|
792
|
+
this.skipWhitespace();
|
|
793
|
+
this.expect('}');
|
|
794
|
+
this.css = _;
|
|
795
|
+
} else {
|
|
796
|
+
this.expect('outdent');
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return keyframes;
|
|
800
|
+
},
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* literal
|
|
804
|
+
*/
|
|
805
|
+
|
|
806
|
+
literal: function() {
|
|
807
|
+
return this.expect('literal').val;
|
|
808
|
+
},
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* ident space?
|
|
812
|
+
*/
|
|
813
|
+
|
|
814
|
+
id: function() {
|
|
815
|
+
var tok = this.expect('ident');
|
|
816
|
+
this.accept('space');
|
|
817
|
+
return tok.val;
|
|
818
|
+
},
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* ident
|
|
822
|
+
* | assignment
|
|
823
|
+
* | property
|
|
824
|
+
* | selector
|
|
825
|
+
*/
|
|
826
|
+
|
|
827
|
+
ident: function() {
|
|
828
|
+
var i = 2
|
|
829
|
+
, la = this.lookahead(i).type;
|
|
830
|
+
|
|
831
|
+
while ('space' == la) la = this.lookahead(++i).type;
|
|
832
|
+
|
|
833
|
+
switch (la) {
|
|
834
|
+
// Assignment
|
|
835
|
+
case '=':
|
|
836
|
+
case '?=':
|
|
837
|
+
case '-=':
|
|
838
|
+
case '+=':
|
|
839
|
+
case '*=':
|
|
840
|
+
case '/=':
|
|
841
|
+
case '%=':
|
|
842
|
+
return this.assignment();
|
|
843
|
+
// Assignment []=
|
|
844
|
+
case '[':
|
|
845
|
+
if (this._ident == this.peek()) return this.id();
|
|
846
|
+
while (']' != this.lookahead(i++).type
|
|
847
|
+
&& 'selector' != this.lookahead(i).type) ;
|
|
848
|
+
if ('=' == this.lookahead(i).type) {
|
|
849
|
+
this._ident = this.peek();
|
|
850
|
+
return this.expression();
|
|
851
|
+
} else if (this.looksLikeSelector() && this.stateAllowsSelector()) {
|
|
852
|
+
return this.selector();
|
|
853
|
+
}
|
|
854
|
+
// Operation
|
|
855
|
+
case '-':
|
|
856
|
+
case '+':
|
|
857
|
+
case '/':
|
|
858
|
+
case '*':
|
|
859
|
+
case '%':
|
|
860
|
+
case '**':
|
|
861
|
+
case 'and':
|
|
862
|
+
case 'or':
|
|
863
|
+
case '&&':
|
|
864
|
+
case '||':
|
|
865
|
+
case '>':
|
|
866
|
+
case '<':
|
|
867
|
+
case '>=':
|
|
868
|
+
case '<=':
|
|
869
|
+
case '!=':
|
|
870
|
+
case '==':
|
|
871
|
+
case '?':
|
|
872
|
+
case 'in':
|
|
873
|
+
case 'is a':
|
|
874
|
+
case 'is defined':
|
|
875
|
+
// Prevent cyclic .ident, return literal
|
|
876
|
+
if (this._ident == this.peek()) {
|
|
877
|
+
return this.id();
|
|
878
|
+
} else {
|
|
879
|
+
this._ident = this.peek();
|
|
880
|
+
switch (this.currentState()) {
|
|
881
|
+
// unary op or selector in property / for
|
|
882
|
+
case 'for':
|
|
883
|
+
case 'selector':
|
|
884
|
+
return this.property();
|
|
885
|
+
// Part of a selector
|
|
886
|
+
case 'root':
|
|
887
|
+
case 'media':
|
|
888
|
+
case 'font-face':
|
|
889
|
+
return this.selector();
|
|
890
|
+
// Do not disrupt the ident when an operand
|
|
891
|
+
default:
|
|
892
|
+
return this.operand
|
|
893
|
+
? this.id()
|
|
894
|
+
: this.expression();
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
// Selector or property
|
|
898
|
+
default:
|
|
899
|
+
switch (this.currentState()) {
|
|
900
|
+
case 'root':
|
|
901
|
+
return this.selector();
|
|
902
|
+
case 'for':
|
|
903
|
+
case 'page':
|
|
904
|
+
case 'media':
|
|
905
|
+
case 'font-face':
|
|
906
|
+
case 'selector':
|
|
907
|
+
case 'function':
|
|
908
|
+
case 'keyframe':
|
|
909
|
+
case 'conditional':
|
|
910
|
+
return this.property();
|
|
911
|
+
default:
|
|
912
|
+
return this.id();
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* '*'? (ident | '{' expression '}')+
|
|
919
|
+
*/
|
|
920
|
+
|
|
921
|
+
interpolate: function() {
|
|
922
|
+
var node
|
|
923
|
+
, segs = []
|
|
924
|
+
, star;
|
|
925
|
+
|
|
926
|
+
star = this.accept('*');
|
|
927
|
+
if (star) segs.push(new nodes.Literal('*'));
|
|
928
|
+
|
|
929
|
+
while (true) {
|
|
930
|
+
if (this.accept('{')) {
|
|
931
|
+
this.state.push('interpolation');
|
|
932
|
+
segs.push(this.expression());
|
|
933
|
+
this.expect('}');
|
|
934
|
+
this.state.pop();
|
|
935
|
+
} else if (node = this.accept('-')){
|
|
936
|
+
segs.push(new nodes.Literal('-'));
|
|
937
|
+
} else if (node = this.accept('ident')){
|
|
938
|
+
segs.push(node.val);
|
|
939
|
+
} else {
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (!segs.length) this.expect('ident');
|
|
944
|
+
return segs;
|
|
945
|
+
},
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* property ':'? expression
|
|
949
|
+
* | ident
|
|
950
|
+
*/
|
|
951
|
+
|
|
952
|
+
property: function() {
|
|
953
|
+
if (this.looksLikeSelector()) return this.selector();
|
|
954
|
+
|
|
955
|
+
// property
|
|
956
|
+
var ident = this.interpolate()
|
|
957
|
+
, ret = prop = new nodes.Property(ident);
|
|
958
|
+
|
|
959
|
+
// optional ':'
|
|
960
|
+
this.accept('space');
|
|
961
|
+
if (this.accept(':')) this.accept('space');
|
|
962
|
+
|
|
963
|
+
this.state.push('property');
|
|
964
|
+
this.inProperty = true;
|
|
965
|
+
prop.expr = this.list();
|
|
966
|
+
if (prop.expr.isEmpty) ret = ident[0];
|
|
967
|
+
this.inProperty = false;
|
|
968
|
+
this.allowPostfix = true;
|
|
969
|
+
this.state.pop();
|
|
970
|
+
|
|
971
|
+
// optional ';'
|
|
972
|
+
this.accept(';');
|
|
973
|
+
|
|
974
|
+
return ret;
|
|
975
|
+
},
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* selector ',' selector
|
|
979
|
+
* | selector newline selector
|
|
980
|
+
* | selector block
|
|
981
|
+
*/
|
|
982
|
+
|
|
983
|
+
selector: function() {
|
|
984
|
+
var tok
|
|
985
|
+
, arr
|
|
986
|
+
, group = new nodes.Group;
|
|
987
|
+
|
|
988
|
+
do {
|
|
989
|
+
arr = [];
|
|
990
|
+
|
|
991
|
+
// Clobber newline after ,
|
|
992
|
+
this.accept('newline');
|
|
993
|
+
|
|
994
|
+
// Selector candidates,
|
|
995
|
+
// stitched together to
|
|
996
|
+
// form a selector.
|
|
997
|
+
while (tok = this.selectorToken()) {
|
|
998
|
+
// Selector component
|
|
999
|
+
switch (tok.type) {
|
|
1000
|
+
case '{':
|
|
1001
|
+
this.skipSpaces();
|
|
1002
|
+
var expr = this.expression();
|
|
1003
|
+
this.skipSpaces();
|
|
1004
|
+
this.expect('}');
|
|
1005
|
+
arr.push(expr);
|
|
1006
|
+
break;
|
|
1007
|
+
case 'comment':
|
|
1008
|
+
arr.push(new nodes.Literal(tok.val.str));
|
|
1009
|
+
break;
|
|
1010
|
+
case 'color':
|
|
1011
|
+
arr.push(new nodes.Literal(tok.val.raw));
|
|
1012
|
+
break;
|
|
1013
|
+
case 'space':
|
|
1014
|
+
arr.push(new nodes.Literal(' '));
|
|
1015
|
+
break;
|
|
1016
|
+
case 'function':
|
|
1017
|
+
arr.push(new nodes.Literal(tok.val.name + '('));
|
|
1018
|
+
break;
|
|
1019
|
+
case 'ident':
|
|
1020
|
+
arr.push(new nodes.Literal(tok.val.name));
|
|
1021
|
+
break;
|
|
1022
|
+
default:
|
|
1023
|
+
arr.push(new nodes.Literal(tok.val));
|
|
1024
|
+
if (tok.space) arr.push(new nodes.Literal(' '));
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Push the selector
|
|
1029
|
+
group.push(new nodes.Selector(arr));
|
|
1030
|
+
} while (this.accept(',') || this.accept('newline'));
|
|
1031
|
+
|
|
1032
|
+
this.lexer.allowComments = false;
|
|
1033
|
+
this.state.push('selector');
|
|
1034
|
+
group.block = this.block(group);
|
|
1035
|
+
this.state.pop();
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
return group;
|
|
1039
|
+
},
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* ident ('=' | '?=') expression
|
|
1043
|
+
*/
|
|
1044
|
+
|
|
1045
|
+
assignment: function() {
|
|
1046
|
+
var op
|
|
1047
|
+
, node
|
|
1048
|
+
, name = this.id().name;
|
|
1049
|
+
|
|
1050
|
+
if (op =
|
|
1051
|
+
this.accept('=')
|
|
1052
|
+
|| this.accept('?=')
|
|
1053
|
+
|| this.accept('+=')
|
|
1054
|
+
|| this.accept('-=')
|
|
1055
|
+
|| this.accept('*=')
|
|
1056
|
+
|| this.accept('/=')
|
|
1057
|
+
|| this.accept('%=')) {
|
|
1058
|
+
this.state.push('assignment');
|
|
1059
|
+
var expr = this.list();
|
|
1060
|
+
if (expr.isEmpty) this.error('invalid right-hand side operand in assignment, got {peek}')
|
|
1061
|
+
node = new nodes.Ident(name, expr);
|
|
1062
|
+
this.state.pop();
|
|
1063
|
+
|
|
1064
|
+
switch (op.type) {
|
|
1065
|
+
case '?=':
|
|
1066
|
+
var defined = new nodes.BinOp('is defined', node)
|
|
1067
|
+
, lookup = new nodes.Ident(name);
|
|
1068
|
+
node = new nodes.Ternary(defined, lookup, node);
|
|
1069
|
+
break;
|
|
1070
|
+
case '+=':
|
|
1071
|
+
case '-=':
|
|
1072
|
+
case '*=':
|
|
1073
|
+
case '/=':
|
|
1074
|
+
case '%=':
|
|
1075
|
+
node.val = new nodes.BinOp(op.type[0], new nodes.Ident(name), expr);
|
|
1076
|
+
break;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
return node;
|
|
1081
|
+
},
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* definition
|
|
1085
|
+
* | call
|
|
1086
|
+
*/
|
|
1087
|
+
|
|
1088
|
+
function: function() {
|
|
1089
|
+
var parens = 1
|
|
1090
|
+
, i = 2
|
|
1091
|
+
, tok;
|
|
1092
|
+
|
|
1093
|
+
// Lookahead and determine if we are dealing
|
|
1094
|
+
// with a function call or definition. Here
|
|
1095
|
+
// we pair parens to prevent false negatives
|
|
1096
|
+
out:
|
|
1097
|
+
while (tok = this.lookahead(i++)) {
|
|
1098
|
+
switch (tok.type) {
|
|
1099
|
+
case 'function':
|
|
1100
|
+
case '(':
|
|
1101
|
+
++parens;
|
|
1102
|
+
break;
|
|
1103
|
+
case ')':
|
|
1104
|
+
if (!--parens) break out;
|
|
1105
|
+
break;
|
|
1106
|
+
case 'eos':
|
|
1107
|
+
this.error('failed to find closing paren ")"');
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// Definition or call
|
|
1112
|
+
switch (this.currentState()) {
|
|
1113
|
+
case 'expression':
|
|
1114
|
+
return this.functionCall();
|
|
1115
|
+
default:
|
|
1116
|
+
return this.looksLikeFunctionDefinition(i)
|
|
1117
|
+
? this.functionDefinition()
|
|
1118
|
+
: this.expression();
|
|
1119
|
+
}
|
|
1120
|
+
},
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* url '(' (expression | urlchars)+ ')'
|
|
1124
|
+
*/
|
|
1125
|
+
|
|
1126
|
+
url: function() {
|
|
1127
|
+
this.expect('function');
|
|
1128
|
+
this.state.push('function arguments');
|
|
1129
|
+
var args = this.args();
|
|
1130
|
+
this.expect(')');
|
|
1131
|
+
this.state.pop();
|
|
1132
|
+
return new nodes.Call('url', args);
|
|
1133
|
+
},
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* ident '(' expression ')'
|
|
1137
|
+
*/
|
|
1138
|
+
|
|
1139
|
+
functionCall: function() {
|
|
1140
|
+
if ('url' == this.peek().val.name) return this.url();
|
|
1141
|
+
var name = this.expect('function').val.name;
|
|
1142
|
+
this.state.push('function arguments');
|
|
1143
|
+
var args = this.args();
|
|
1144
|
+
this.expect(')');
|
|
1145
|
+
this.state.pop();
|
|
1146
|
+
return new nodes.Call(name, args);
|
|
1147
|
+
},
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* ident '(' params ')' block
|
|
1151
|
+
*/
|
|
1152
|
+
|
|
1153
|
+
functionDefinition: function() {
|
|
1154
|
+
var name = this.expect('function').val.name;
|
|
1155
|
+
|
|
1156
|
+
// params
|
|
1157
|
+
this.state.push('function params');
|
|
1158
|
+
this.skipWhitespace();
|
|
1159
|
+
var params = this.params();
|
|
1160
|
+
this.skipWhitespace();
|
|
1161
|
+
this.expect(')');
|
|
1162
|
+
this.state.pop();
|
|
1163
|
+
|
|
1164
|
+
// Body
|
|
1165
|
+
this.state.push('function');
|
|
1166
|
+
var fn = new nodes.Function(name, params);
|
|
1167
|
+
fn.block = this.block(fn);
|
|
1168
|
+
this.state.pop();
|
|
1169
|
+
return new nodes.Ident(name, fn);
|
|
1170
|
+
},
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* ident
|
|
1174
|
+
* | ident '...'
|
|
1175
|
+
* | ident '=' expression
|
|
1176
|
+
* | ident ',' ident
|
|
1177
|
+
*/
|
|
1178
|
+
|
|
1179
|
+
params: function() {
|
|
1180
|
+
var tok
|
|
1181
|
+
, node
|
|
1182
|
+
, params = new nodes.Params;
|
|
1183
|
+
while (tok = this.accept('ident')) {
|
|
1184
|
+
this.accept('space');
|
|
1185
|
+
params.push(node = tok.val);
|
|
1186
|
+
if (this.accept('...')) {
|
|
1187
|
+
node.rest = true;
|
|
1188
|
+
} else if (this.accept('=')) {
|
|
1189
|
+
node.val = this.expression();
|
|
1190
|
+
}
|
|
1191
|
+
this.skipWhitespace();
|
|
1192
|
+
this.accept(',');
|
|
1193
|
+
this.skipWhitespace();
|
|
1194
|
+
}
|
|
1195
|
+
return params;
|
|
1196
|
+
},
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* (ident ':')? expression (',' (ident ':')? expression)*
|
|
1200
|
+
*/
|
|
1201
|
+
|
|
1202
|
+
args: function() {
|
|
1203
|
+
var args = new nodes.Arguments
|
|
1204
|
+
, keyword;
|
|
1205
|
+
|
|
1206
|
+
do {
|
|
1207
|
+
// keyword
|
|
1208
|
+
if ('ident' == this.peek().type && ':' == this.lookahead(2).type) {
|
|
1209
|
+
keyword = this.next().val.string;
|
|
1210
|
+
this.expect(':');
|
|
1211
|
+
args.map[keyword] = this.expression();
|
|
1212
|
+
// arg
|
|
1213
|
+
} else {
|
|
1214
|
+
args.push(this.expression());
|
|
1215
|
+
}
|
|
1216
|
+
} while (this.accept(','));
|
|
1217
|
+
|
|
1218
|
+
return args;
|
|
1219
|
+
},
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* expression (',' expression)*
|
|
1223
|
+
*/
|
|
1224
|
+
|
|
1225
|
+
list: function() {
|
|
1226
|
+
var node = this.expression();
|
|
1227
|
+
while (this.accept(',')) {
|
|
1228
|
+
if (node.isList) {
|
|
1229
|
+
list.push(this.expression());
|
|
1230
|
+
} else {
|
|
1231
|
+
var list = new nodes.Expression(true);
|
|
1232
|
+
list.push(node);
|
|
1233
|
+
list.push(this.expression());
|
|
1234
|
+
node = list;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return node;
|
|
1238
|
+
},
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* negation+
|
|
1242
|
+
*/
|
|
1243
|
+
|
|
1244
|
+
expression: function() {
|
|
1245
|
+
var node
|
|
1246
|
+
, expr = new nodes.Expression;
|
|
1247
|
+
this.state.push('expression');
|
|
1248
|
+
while (node = this.negation()) {
|
|
1249
|
+
if (!node) this.error('unexpected token {peek} in expression');
|
|
1250
|
+
expr.push(node);
|
|
1251
|
+
}
|
|
1252
|
+
this.state.pop();
|
|
1253
|
+
return expr;
|
|
1254
|
+
},
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* 'not' ternary
|
|
1258
|
+
* | ternary
|
|
1259
|
+
*/
|
|
1260
|
+
|
|
1261
|
+
negation: function() {
|
|
1262
|
+
if (this.accept('not')) {
|
|
1263
|
+
return new nodes.UnaryOp('!', this.negation());
|
|
1264
|
+
}
|
|
1265
|
+
return this.ternary();
|
|
1266
|
+
},
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* logical ('?' expression ':' expression)?
|
|
1270
|
+
*/
|
|
1271
|
+
|
|
1272
|
+
ternary: function() {
|
|
1273
|
+
var node = this.logical();
|
|
1274
|
+
if (this.accept('?')) {
|
|
1275
|
+
var trueExpr = this.expression();
|
|
1276
|
+
this.expect(':');
|
|
1277
|
+
var falseExpr = this.expression();
|
|
1278
|
+
node = new nodes.Ternary(node, trueExpr, falseExpr);
|
|
1279
|
+
}
|
|
1280
|
+
return node;
|
|
1281
|
+
},
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* typecheck (('&&' | '||') typecheck)*
|
|
1285
|
+
*/
|
|
1286
|
+
|
|
1287
|
+
logical: function() {
|
|
1288
|
+
var op
|
|
1289
|
+
, node = this.typecheck();
|
|
1290
|
+
while (op = this.accept('&&') || this.accept('||')) {
|
|
1291
|
+
node = new nodes.BinOp(op.type, node, this.typecheck());
|
|
1292
|
+
}
|
|
1293
|
+
return node;
|
|
1294
|
+
},
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* equality ('is a' equality)*
|
|
1298
|
+
*/
|
|
1299
|
+
|
|
1300
|
+
typecheck: function() {
|
|
1301
|
+
var op
|
|
1302
|
+
, node = this.equality();
|
|
1303
|
+
while (op = this.accept('is a')) {
|
|
1304
|
+
this.operand = true;
|
|
1305
|
+
if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
|
|
1306
|
+
node = new nodes.BinOp(op.type, node, this.equality());
|
|
1307
|
+
this.operand = false;
|
|
1308
|
+
}
|
|
1309
|
+
return node;
|
|
1310
|
+
},
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* in (('==' | '!=') in)*
|
|
1314
|
+
*/
|
|
1315
|
+
|
|
1316
|
+
equality: function() {
|
|
1317
|
+
var op
|
|
1318
|
+
, node = this.in();
|
|
1319
|
+
while (op = this.accept('==') || this.accept('!=')) {
|
|
1320
|
+
this.operand = true;
|
|
1321
|
+
if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
|
|
1322
|
+
node = new nodes.BinOp(op.type, node, this.in());
|
|
1323
|
+
this.operand = false;
|
|
1324
|
+
}
|
|
1325
|
+
return node;
|
|
1326
|
+
},
|
|
1327
|
+
|
|
1328
|
+
/**
|
|
1329
|
+
* relational ('in' relational)*
|
|
1330
|
+
*/
|
|
1331
|
+
|
|
1332
|
+
in: function() {
|
|
1333
|
+
var node = this.relational();
|
|
1334
|
+
while (this.accept('in')) {
|
|
1335
|
+
this.operand = true;
|
|
1336
|
+
if (!node) this.error('illegal unary "in", missing left-hand operand');
|
|
1337
|
+
node = new nodes.BinOp('in', node, this.relational());
|
|
1338
|
+
this.operand = false;
|
|
1339
|
+
}
|
|
1340
|
+
return node;
|
|
1341
|
+
},
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* range (('>=' | '<=' | '>' | '<') range)*
|
|
1345
|
+
*/
|
|
1346
|
+
|
|
1347
|
+
relational: function() {
|
|
1348
|
+
var op
|
|
1349
|
+
, node = this.range();
|
|
1350
|
+
while (op =
|
|
1351
|
+
this.accept('>=')
|
|
1352
|
+
|| this.accept('<=')
|
|
1353
|
+
|| this.accept('<')
|
|
1354
|
+
|| this.accept('>')
|
|
1355
|
+
) {
|
|
1356
|
+
this.operand = true;
|
|
1357
|
+
if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
|
|
1358
|
+
node = new nodes.BinOp(op.type, node, this.range());
|
|
1359
|
+
this.operand = false;
|
|
1360
|
+
}
|
|
1361
|
+
return node;
|
|
1362
|
+
},
|
|
1363
|
+
|
|
1364
|
+
/**
|
|
1365
|
+
* additive (('..' | '...') additive)*
|
|
1366
|
+
*/
|
|
1367
|
+
|
|
1368
|
+
range: function() {
|
|
1369
|
+
var op
|
|
1370
|
+
, node = this.additive();
|
|
1371
|
+
if (op = this.accept('...') || this.accept('..')) {
|
|
1372
|
+
this.operand = true;
|
|
1373
|
+
if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
|
|
1374
|
+
node = new nodes.BinOp(op.val, node, this.additive());
|
|
1375
|
+
this.operand = false;
|
|
1376
|
+
}
|
|
1377
|
+
return node;
|
|
1378
|
+
},
|
|
1379
|
+
|
|
1380
|
+
/**
|
|
1381
|
+
* multiplicative (('+' | '-') multiplicative)*
|
|
1382
|
+
*/
|
|
1383
|
+
|
|
1384
|
+
additive: function() {
|
|
1385
|
+
var op
|
|
1386
|
+
, node = this.multiplicative();
|
|
1387
|
+
while (op = this.accept('+') || this.accept('-')) {
|
|
1388
|
+
this.operand = true;
|
|
1389
|
+
node = new nodes.BinOp(op.type, node, this.multiplicative());
|
|
1390
|
+
this.operand = false;
|
|
1391
|
+
}
|
|
1392
|
+
return node;
|
|
1393
|
+
},
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* defined (('**' | '*' | '/' | '%') defined)*
|
|
1397
|
+
*/
|
|
1398
|
+
|
|
1399
|
+
multiplicative: function() {
|
|
1400
|
+
var op
|
|
1401
|
+
, node = this.defined();
|
|
1402
|
+
while (op =
|
|
1403
|
+
this.accept('**')
|
|
1404
|
+
|| this.accept('*')
|
|
1405
|
+
|| this.accept('/')
|
|
1406
|
+
|| this.accept('%')) {
|
|
1407
|
+
this.operand = true;
|
|
1408
|
+
if ('/' == op && this.inProperty && !this.parens) {
|
|
1409
|
+
this.stash.push(new Token('literal', new nodes.Literal('/')));
|
|
1410
|
+
this.operand = false;
|
|
1411
|
+
return node;
|
|
1412
|
+
} else {
|
|
1413
|
+
if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
|
|
1414
|
+
node = new nodes.BinOp(op.type, node, this.defined());
|
|
1415
|
+
this.operand = false;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return node;
|
|
1419
|
+
},
|
|
1420
|
+
|
|
1421
|
+
/**
|
|
1422
|
+
* unary 'is defined'
|
|
1423
|
+
* | unary
|
|
1424
|
+
*/
|
|
1425
|
+
|
|
1426
|
+
defined: function() {
|
|
1427
|
+
var node = this.unary();
|
|
1428
|
+
if (this.accept('is defined')) {
|
|
1429
|
+
if (!node) this.error('illegal unary "is defined", missing left-hand operand');
|
|
1430
|
+
node = new nodes.BinOp('is defined', node);
|
|
1431
|
+
}
|
|
1432
|
+
return node;
|
|
1433
|
+
},
|
|
1434
|
+
|
|
1435
|
+
/**
|
|
1436
|
+
* ('!' | '~' | '+' | '-') unary
|
|
1437
|
+
* | subscript
|
|
1438
|
+
*/
|
|
1439
|
+
|
|
1440
|
+
unary: function() {
|
|
1441
|
+
var op
|
|
1442
|
+
, node;
|
|
1443
|
+
if (op =
|
|
1444
|
+
this.accept('!')
|
|
1445
|
+
|| this.accept('~')
|
|
1446
|
+
|| this.accept('+')
|
|
1447
|
+
|| this.accept('-')) {
|
|
1448
|
+
this.operand = true;
|
|
1449
|
+
node = new nodes.UnaryOp(op.type, this.unary());
|
|
1450
|
+
this.operand = false;
|
|
1451
|
+
return node;
|
|
1452
|
+
}
|
|
1453
|
+
return this.subscript();
|
|
1454
|
+
},
|
|
1455
|
+
|
|
1456
|
+
/**
|
|
1457
|
+
* primary ('[' expression ']' '='?)+
|
|
1458
|
+
* | primary
|
|
1459
|
+
*/
|
|
1460
|
+
|
|
1461
|
+
subscript: function() {
|
|
1462
|
+
var node = this.primary();
|
|
1463
|
+
while (this.accept('[')) {
|
|
1464
|
+
node = new nodes.BinOp('[]', node, this.expression());
|
|
1465
|
+
this.expect(']');
|
|
1466
|
+
// TODO: TernaryOp :)
|
|
1467
|
+
if (this.accept('=')) {
|
|
1468
|
+
node.op += '=';
|
|
1469
|
+
node.val = this.expression();
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
return node;
|
|
1473
|
+
},
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* unit
|
|
1477
|
+
* | null
|
|
1478
|
+
* | color
|
|
1479
|
+
* | string
|
|
1480
|
+
* | ident
|
|
1481
|
+
* | boolean
|
|
1482
|
+
* | literal
|
|
1483
|
+
* | '(' expression ')'
|
|
1484
|
+
*/
|
|
1485
|
+
|
|
1486
|
+
primary: function() {
|
|
1487
|
+
var op
|
|
1488
|
+
, node;
|
|
1489
|
+
|
|
1490
|
+
// Parenthesis
|
|
1491
|
+
if (this.accept('(')) {
|
|
1492
|
+
++this.parens;
|
|
1493
|
+
var expr = this.expression();
|
|
1494
|
+
this.expect(')');
|
|
1495
|
+
--this.parens;
|
|
1496
|
+
return expr;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// Primitive
|
|
1500
|
+
switch (this.peek().type) {
|
|
1501
|
+
case 'null':
|
|
1502
|
+
case 'unit':
|
|
1503
|
+
case 'color':
|
|
1504
|
+
case 'string':
|
|
1505
|
+
case 'literal':
|
|
1506
|
+
case 'boolean':
|
|
1507
|
+
return this.next().val;
|
|
1508
|
+
case 'ident':
|
|
1509
|
+
return this.ident();
|
|
1510
|
+
case 'function':
|
|
1511
|
+
return this.functionCall();
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
};
|