stylus-source 0.15.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/.DS_Store +0 -0
  2. data/README.md +3 -0
  3. data/lib/.DS_Store +0 -0
  4. data/lib/node_modules/cssom/.idea/CSSOM.iml +9 -0
  5. data/lib/node_modules/cssom/.idea/dictionaries/nv.xml +3 -0
  6. data/lib/node_modules/cssom/.idea/encodings.xml +5 -0
  7. data/lib/node_modules/cssom/.idea/misc.xml +17 -0
  8. data/lib/node_modules/cssom/.idea/modules.xml +9 -0
  9. data/lib/node_modules/cssom/.idea/projectCodeStyle.xml +82 -0
  10. data/lib/node_modules/cssom/.idea/vcs.xml +8 -0
  11. data/lib/node_modules/cssom/.idea/workspace.xml +467 -0
  12. data/lib/node_modules/cssom/.livereload +19 -0
  13. data/lib/node_modules/cssom/Jakefile +37 -0
  14. data/lib/node_modules/cssom/README.mdown +33 -0
  15. data/lib/node_modules/cssom/Rakefile +23 -0
  16. data/lib/node_modules/cssom/docs/.livereload +19 -0
  17. data/lib/node_modules/cssom/docs/bar.css +3 -0
  18. data/lib/node_modules/cssom/docs/demo.css +0 -0
  19. data/lib/node_modules/cssom/docs/foo.css +4 -0
  20. data/lib/node_modules/cssom/docs/parse.html +170 -0
  21. data/lib/node_modules/cssom/docs/parse2.html +431 -0
  22. data/lib/node_modules/cssom/index.html +100 -0
  23. data/lib/node_modules/cssom/lib/CSSImportRule.js +34 -0
  24. data/lib/node_modules/cssom/lib/CSSMediaRule.js +38 -0
  25. data/lib/node_modules/cssom/lib/CSSOM.js +3 -0
  26. data/lib/node_modules/cssom/lib/CSSRule.js +38 -0
  27. data/lib/node_modules/cssom/lib/CSSStyleDeclaration.js +130 -0
  28. data/lib/node_modules/cssom/lib/CSSStyleRule.js +187 -0
  29. data/lib/node_modules/cssom/lib/CSSStyleSheet.js +85 -0
  30. data/lib/node_modules/cssom/lib/MediaList.js +61 -0
  31. data/lib/node_modules/cssom/lib/StyleSheet.js +15 -0
  32. data/lib/node_modules/cssom/lib/clone.js +69 -0
  33. data/lib/node_modules/cssom/lib/index.js +10 -0
  34. data/lib/node_modules/cssom/lib/parse.js +195 -0
  35. data/lib/node_modules/cssom/media.html +17 -0
  36. data/lib/node_modules/cssom/package.json +30 -0
  37. data/lib/node_modules/cssom/plugins/toHTML.js +32 -0
  38. data/lib/node_modules/cssom/server/index.html +22 -0
  39. data/lib/node_modules/cssom/server/index.js +21 -0
  40. data/lib/node_modules/cssom/shorthands.html +21 -0
  41. data/lib/node_modules/cssom/test/CSSStyleDeclaration.test.js +35 -0
  42. data/lib/node_modules/cssom/test/CSSStyleRule.test.js +12 -0
  43. data/lib/node_modules/cssom/test/CSSStyleSheet.test.js +16 -0
  44. data/lib/node_modules/cssom/test/MediaList.test.js +21 -0
  45. data/lib/node_modules/cssom/test/clone.test.js +38 -0
  46. data/lib/node_modules/cssom/test/fixtures/dummy.css +3 -0
  47. data/lib/node_modules/cssom/test/helper.js +97 -0
  48. data/lib/node_modules/cssom/test/index.html +42 -0
  49. data/lib/node_modules/cssom/test/parse.test.js +346 -0
  50. data/lib/node_modules/cssom/test/vendor/qunit.css +189 -0
  51. data/lib/node_modules/cssom/test/vendor/qunit.js +1341 -0
  52. data/lib/node_modules/growl/History.md +16 -0
  53. data/lib/node_modules/growl/Readme.md +74 -0
  54. data/lib/node_modules/growl/lib/growl.js +82 -0
  55. data/lib/node_modules/growl/package.json +6 -0
  56. data/lib/node_modules/growl/test.js +17 -0
  57. data/lib/stylus/colors.js +156 -0
  58. data/lib/stylus/convert/css.js +130 -0
  59. data/lib/stylus/errors.js +58 -0
  60. data/lib/stylus/functions/image.js +120 -0
  61. data/lib/stylus/functions/index.js +722 -0
  62. data/lib/stylus/functions/index.styl +123 -0
  63. data/lib/stylus/functions/url.js +98 -0
  64. data/lib/stylus/lexer.js +728 -0
  65. data/lib/stylus/middleware.js +223 -0
  66. data/lib/stylus/nodes/arguments.js +65 -0
  67. data/lib/stylus/nodes/binop.js +54 -0
  68. data/lib/stylus/nodes/block.js +99 -0
  69. data/lib/stylus/nodes/boolean.js +103 -0
  70. data/lib/stylus/nodes/call.js +57 -0
  71. data/lib/stylus/nodes/charset.js +42 -0
  72. data/lib/stylus/nodes/comment.js +32 -0
  73. data/lib/stylus/nodes/each.js +56 -0
  74. data/lib/stylus/nodes/expression.js +168 -0
  75. data/lib/stylus/nodes/fontface.js +55 -0
  76. data/lib/stylus/nodes/function.js +104 -0
  77. data/lib/stylus/nodes/group.js +79 -0
  78. data/lib/stylus/nodes/hsla.js +256 -0
  79. data/lib/stylus/nodes/ident.js +127 -0
  80. data/lib/stylus/nodes/if.js +55 -0
  81. data/lib/stylus/nodes/import.js +30 -0
  82. data/lib/stylus/nodes/index.js +52 -0
  83. data/lib/stylus/nodes/jsliteral.js +32 -0
  84. data/lib/stylus/nodes/keyframes.js +78 -0
  85. data/lib/stylus/nodes/literal.js +92 -0
  86. data/lib/stylus/nodes/media.js +42 -0
  87. data/lib/stylus/nodes/node.js +209 -0
  88. data/lib/stylus/nodes/null.js +72 -0
  89. data/lib/stylus/nodes/page.js +43 -0
  90. data/lib/stylus/nodes/params.js +72 -0
  91. data/lib/stylus/nodes/property.js +72 -0
  92. data/lib/stylus/nodes/return.js +44 -0
  93. data/lib/stylus/nodes/rgba.js +335 -0
  94. data/lib/stylus/nodes/root.js +50 -0
  95. data/lib/stylus/nodes/selector.js +57 -0
  96. data/lib/stylus/nodes/string.js +120 -0
  97. data/lib/stylus/nodes/ternary.js +51 -0
  98. data/lib/stylus/nodes/unaryop.js +46 -0
  99. data/lib/stylus/nodes/unit.js +207 -0
  100. data/lib/stylus/parser.js +1514 -0
  101. data/lib/stylus/renderer.js +157 -0
  102. data/lib/stylus/source.rb +7 -0
  103. data/lib/stylus/stack/frame.js +66 -0
  104. data/lib/stylus/stack/index.js +146 -0
  105. data/lib/stylus/stack/scope.js +53 -0
  106. data/lib/stylus/stylus.js +102 -0
  107. data/lib/stylus/token.js +53 -0
  108. data/lib/stylus/utils.js +237 -0
  109. data/lib/stylus/visitor/compiler.js +472 -0
  110. data/lib/stylus/visitor/evaluator.js +1070 -0
  111. data/lib/stylus/visitor/index.js +31 -0
  112. data/stylus-source.gemspec +15 -0
  113. 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
+ };