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.
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
+ };