less 3.10.0-beta.2 → 3.10.3

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 (124) hide show
  1. package/.DS_Store +0 -0
  2. package/bin/lessc +6652 -5505
  3. package/dist/less.cjs.js +6593 -5448
  4. package/dist/less.js +10 -6
  5. package/dist/less.min.js +2 -2
  6. package/dist/less.min.js.map +1 -1
  7. package/lib/less/constants.js +13 -0
  8. package/lib/less/contexts.js +164 -0
  9. package/lib/less/data/colors.js +150 -0
  10. package/lib/less/data/index.js +4 -0
  11. package/lib/less/data/unit-conversions.js +21 -0
  12. package/lib/less/default-options.js +68 -0
  13. package/lib/less/environment/abstract-file-manager.js +127 -0
  14. package/lib/less/environment/abstract-plugin-loader.js +187 -0
  15. package/lib/less/environment/environment-api.js +25 -0
  16. package/lib/less/environment/environment.js +59 -0
  17. package/lib/less/environment/file-manager-api.js +103 -0
  18. package/lib/less/functions/boolean.js +13 -0
  19. package/lib/less/functions/color-blending.js +84 -0
  20. package/lib/less/functions/color.js +417 -0
  21. package/lib/less/functions/data-uri.js +74 -0
  22. package/lib/less/functions/default.js +25 -0
  23. package/lib/less/functions/function-caller.js +49 -0
  24. package/lib/less/functions/function-registry.js +35 -0
  25. package/lib/less/functions/index.js +33 -0
  26. package/lib/less/functions/list.js +143 -0
  27. package/lib/less/functions/math-helper.js +15 -0
  28. package/lib/less/functions/math.js +28 -0
  29. package/lib/less/functions/number.js +89 -0
  30. package/lib/less/functions/string.js +36 -0
  31. package/lib/less/functions/svg.js +87 -0
  32. package/lib/less/functions/types.js +70 -0
  33. package/lib/less/import-manager.js +169 -0
  34. package/lib/less/index.js +92 -0
  35. package/lib/less/less-error.js +140 -0
  36. package/lib/less/logger.js +34 -0
  37. package/lib/less/parse-tree.js +66 -0
  38. package/lib/less/parse.js +89 -0
  39. package/lib/less/parser/chunker.js +122 -0
  40. package/lib/less/parser/parser-input.js +390 -0
  41. package/lib/less/parser/parser.js +2418 -0
  42. package/lib/less/plugin-manager.js +168 -0
  43. package/lib/less/render.js +42 -0
  44. package/lib/less/source-map-builder.js +77 -0
  45. package/lib/less/source-map-output.js +149 -0
  46. package/lib/less/transform-tree.js +96 -0
  47. package/lib/less/tree/anonymous.js +37 -0
  48. package/lib/less/tree/assignment.js +33 -0
  49. package/lib/less/tree/atrule.js +165 -0
  50. package/lib/less/tree/attribute.js +34 -0
  51. package/lib/less/tree/call.js +105 -0
  52. package/lib/less/tree/color.js +246 -0
  53. package/lib/less/tree/combinator.js +29 -0
  54. package/lib/less/tree/comment.js +29 -0
  55. package/lib/less/tree/condition.js +43 -0
  56. package/lib/less/tree/debug-info.js +34 -0
  57. package/lib/less/tree/declaration.js +115 -0
  58. package/lib/less/tree/detached-ruleset.js +30 -0
  59. package/lib/less/tree/dimension.js +179 -0
  60. package/lib/less/tree/element.js +73 -0
  61. package/lib/less/tree/expression.js +74 -0
  62. package/lib/less/tree/extend.js +66 -0
  63. package/lib/less/tree/import.js +184 -0
  64. package/lib/less/tree/index.js +54 -0
  65. package/lib/less/tree/javascript.js +33 -0
  66. package/lib/less/tree/js-eval-node.js +58 -0
  67. package/lib/less/tree/keyword.js +21 -0
  68. package/lib/less/tree/media.js +154 -0
  69. package/lib/less/tree/mixin-call.js +215 -0
  70. package/lib/less/tree/mixin-definition.js +229 -0
  71. package/lib/less/tree/namespace-value.js +87 -0
  72. package/lib/less/tree/negative.js +26 -0
  73. package/lib/less/tree/node.js +178 -0
  74. package/lib/less/tree/operation.js +62 -0
  75. package/lib/less/tree/paren.js +22 -0
  76. package/lib/less/tree/property.js +77 -0
  77. package/lib/less/tree/quoted.js +70 -0
  78. package/lib/less/tree/ruleset.js +867 -0
  79. package/lib/less/tree/selector.js +146 -0
  80. package/lib/less/tree/unicode-descriptor.js +13 -0
  81. package/lib/less/tree/unit.js +141 -0
  82. package/lib/less/tree/url.js +65 -0
  83. package/lib/less/tree/value.js +44 -0
  84. package/lib/less/tree/variable-call.js +46 -0
  85. package/lib/less/tree/variable.js +67 -0
  86. package/lib/less/utils.js +122 -0
  87. package/lib/less/visitors/extend-visitor.js +505 -0
  88. package/lib/less/visitors/import-sequencer.js +58 -0
  89. package/lib/less/visitors/import-visitor.js +189 -0
  90. package/lib/less/visitors/index.js +15 -0
  91. package/lib/less/visitors/join-selector-visitor.js +57 -0
  92. package/lib/less/visitors/set-tree-visibility-visitor.js +45 -0
  93. package/lib/less/visitors/to-css-visitor.js +363 -0
  94. package/lib/less/visitors/visitor.js +163 -0
  95. package/lib/less-browser/add-default-options.js +49 -0
  96. package/lib/less-browser/bootstrap.js +69 -0
  97. package/lib/less-browser/browser.js +65 -0
  98. package/lib/less-browser/cache.js +43 -0
  99. package/lib/less-browser/error-reporting.js +170 -0
  100. package/lib/less-browser/file-manager.js +113 -0
  101. package/lib/less-browser/image-size.js +28 -0
  102. package/lib/less-browser/index.js +285 -0
  103. package/lib/less-browser/log-listener.js +42 -0
  104. package/lib/less-browser/plugin-loader.js +26 -0
  105. package/lib/less-browser/utils.js +24 -0
  106. package/lib/less-node/environment.js +16 -0
  107. package/lib/less-node/file-manager.js +177 -0
  108. package/lib/less-node/fs.js +10 -0
  109. package/lib/less-node/image-size.js +58 -0
  110. package/lib/less-node/index.js +27 -0
  111. package/lib/less-node/lessc-helper.js +89 -0
  112. package/lib/less-node/plugin-loader.js +53 -0
  113. package/lib/less-node/url-file-manager.js +49 -0
  114. package/lib/lessc.js +557 -0
  115. package/lib/source-map/source-map-0.1.31.js +1933 -0
  116. package/lib/source-map/source-map-footer.js +4 -0
  117. package/lib/source-map/source-map-header.js +3 -0
  118. package/package.json +1 -1
  119. package/test/.DS_Store +0 -0
  120. package/test/browser/less.min.js +11 -0
  121. package/test/browser/less.min.js.map +1 -0
  122. package/test/css/css-escapes.css +1 -0
  123. package/test/less/css-escapes.less +3 -1
  124. package/.git/index +0 -0
@@ -0,0 +1,2418 @@
1
+ import LessError from '../less-error';
2
+ import tree from '../tree';
3
+ import visitors from '../visitors';
4
+ import getParserInput from './parser-input';
5
+ import * as utils from '../utils';
6
+ import functionRegistry from '../functions/function-registry';
7
+
8
+ //
9
+ // less.js - parser
10
+ //
11
+ // A relatively straight-forward predictive parser.
12
+ // There is no tokenization/lexing stage, the input is parsed
13
+ // in one sweep.
14
+ //
15
+ // To make the parser fast enough to run in the browser, several
16
+ // optimization had to be made:
17
+ //
18
+ // - Matching and slicing on a huge input is often cause of slowdowns.
19
+ // The solution is to chunkify the input into smaller strings.
20
+ // The chunks are stored in the `chunks` var,
21
+ // `j` holds the current chunk index, and `currentPos` holds
22
+ // the index of the current chunk in relation to `input`.
23
+ // This gives us an almost 4x speed-up.
24
+ //
25
+ // - In many cases, we don't need to match individual tokens;
26
+ // for example, if a value doesn't hold any variables, operations
27
+ // or dynamic references, the parser can effectively 'skip' it,
28
+ // treating it as a literal.
29
+ // An example would be '1px solid #000' - which evaluates to itself,
30
+ // we don't need to know what the individual components are.
31
+ // The drawback, of course is that you don't get the benefits of
32
+ // syntax-checking on the CSS. This gives us a 50% speed-up in the parser,
33
+ // and a smaller speed-up in the code-gen.
34
+ //
35
+ //
36
+ // Token matching is done with the `$` function, which either takes
37
+ // a terminal string or regexp, or a non-terminal function to call.
38
+ // It also takes care of moving all the indices forwards.
39
+ //
40
+
41
+ const Parser = function Parser(context, imports, fileInfo) {
42
+ let parsers;
43
+ const parserInput = getParserInput();
44
+
45
+ function error(msg, type) {
46
+ throw new LessError(
47
+ {
48
+ index: parserInput.i,
49
+ filename: fileInfo.filename,
50
+ type: type || 'Syntax',
51
+ message: msg
52
+ },
53
+ imports
54
+ );
55
+ }
56
+
57
+ function expect(arg, msg) {
58
+ // some older browsers return typeof 'function' for RegExp
59
+ const result = (arg instanceof Function) ? arg.call(parsers) : parserInput.$re(arg);
60
+ if (result) {
61
+ return result;
62
+ }
63
+
64
+ error(msg || (typeof arg === 'string'
65
+ ? `expected '${arg}' got '${parserInput.currentChar()}'`
66
+ : 'unexpected token'));
67
+ }
68
+
69
+ // Specialization of expect()
70
+ function expectChar(arg, msg) {
71
+ if (parserInput.$char(arg)) {
72
+ return arg;
73
+ }
74
+ error(msg || `expected '${arg}' got '${parserInput.currentChar()}'`);
75
+ }
76
+
77
+ function getDebugInfo(index) {
78
+ const filename = fileInfo.filename;
79
+
80
+ return {
81
+ lineNumber: utils.getLocation(index, parserInput.getInput()).line + 1,
82
+ fileName: filename
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Used after initial parsing to create nodes on the fly
88
+ *
89
+ * @param {String} str - string to parse
90
+ * @param {Array} parseList - array of parsers to run input through e.g. ["value", "important"]
91
+ * @param {Number} currentIndex - start number to begin indexing
92
+ * @param {Object} fileInfo - fileInfo to attach to created nodes
93
+ */
94
+ function parseNode(str, parseList, currentIndex, fileInfo, callback) {
95
+ let result;
96
+ const returnNodes = [];
97
+ const parser = parserInput;
98
+
99
+ try {
100
+ parser.start(str, false, function fail(msg, index) {
101
+ callback({
102
+ message: msg,
103
+ index: index + currentIndex
104
+ });
105
+ });
106
+ for (let x = 0, p, i; (p = parseList[x]); x++) {
107
+ i = parser.i;
108
+ result = parsers[p]();
109
+ if (result) {
110
+ try {
111
+ result._index = i + currentIndex;
112
+ result._fileInfo = fileInfo;
113
+ } catch (e) {}
114
+ returnNodes.push(result);
115
+ }
116
+ else {
117
+ returnNodes.push(null);
118
+ }
119
+ }
120
+
121
+ const endInfo = parser.end();
122
+ if (endInfo.isFinished) {
123
+ callback(null, returnNodes);
124
+ }
125
+ else {
126
+ callback(true, null);
127
+ }
128
+ } catch (e) {
129
+ throw new LessError({
130
+ index: e.index + currentIndex,
131
+ message: e.message
132
+ }, imports, fileInfo.filename);
133
+ }
134
+ }
135
+
136
+ //
137
+ // The Parser
138
+ //
139
+ return {
140
+ parserInput,
141
+ imports,
142
+ fileInfo,
143
+ parseNode,
144
+ //
145
+ // Parse an input string into an abstract syntax tree,
146
+ // @param str A string containing 'less' markup
147
+ // @param callback call `callback` when done.
148
+ // @param [additionalData] An optional map which can contains vars - a map (key, value) of variables to apply
149
+ //
150
+ parse: function (str, callback, additionalData) {
151
+ let root;
152
+ let error = null;
153
+ let globalVars;
154
+ let modifyVars;
155
+ let ignored;
156
+ let preText = '';
157
+
158
+ globalVars = (additionalData && additionalData.globalVars) ? `${Parser.serializeVars(additionalData.globalVars)}\n` : '';
159
+ modifyVars = (additionalData && additionalData.modifyVars) ? `\n${Parser.serializeVars(additionalData.modifyVars)}` : '';
160
+
161
+ if (context.pluginManager) {
162
+ const preProcessors = context.pluginManager.getPreProcessors();
163
+ for (let i = 0; i < preProcessors.length; i++) {
164
+ str = preProcessors[i].process(str, { context, imports, fileInfo });
165
+ }
166
+ }
167
+
168
+ if (globalVars || (additionalData && additionalData.banner)) {
169
+ preText = ((additionalData && additionalData.banner) ? additionalData.banner : '') + globalVars;
170
+ ignored = imports.contentsIgnoredChars;
171
+ ignored[fileInfo.filename] = ignored[fileInfo.filename] || 0;
172
+ ignored[fileInfo.filename] += preText.length;
173
+ }
174
+
175
+ str = str.replace(/\r\n?/g, '\n');
176
+ // Remove potential UTF Byte Order Mark
177
+ str = preText + str.replace(/^\uFEFF/, '') + modifyVars;
178
+ imports.contents[fileInfo.filename] = str;
179
+
180
+ // Start with the primary rule.
181
+ // The whole syntax tree is held under a Ruleset node,
182
+ // with the `root` property set to true, so no `{}` are
183
+ // output. The callback is called when the input is parsed.
184
+ try {
185
+ parserInput.start(str, context.chunkInput, function fail(msg, index) {
186
+ throw new LessError({
187
+ index,
188
+ type: 'Parse',
189
+ message: msg,
190
+ filename: fileInfo.filename
191
+ }, imports);
192
+ });
193
+
194
+ tree.Node.prototype.parse = this;
195
+ root = new tree.Ruleset(null, this.parsers.primary());
196
+ tree.Node.prototype.rootNode = root;
197
+ root.root = true;
198
+ root.firstRoot = true;
199
+ root.functionRegistry = functionRegistry.inherit();
200
+
201
+ } catch (e) {
202
+ return callback(new LessError(e, imports, fileInfo.filename));
203
+ }
204
+
205
+ // If `i` is smaller than the `input.length - 1`,
206
+ // it means the parser wasn't able to parse the whole
207
+ // string, so we've got a parsing error.
208
+ //
209
+ // We try to extract a \n delimited string,
210
+ // showing the line where the parse error occurred.
211
+ // We split it up into two parts (the part which parsed,
212
+ // and the part which didn't), so we can color them differently.
213
+ const endInfo = parserInput.end();
214
+ if (!endInfo.isFinished) {
215
+
216
+ let message = endInfo.furthestPossibleErrorMessage;
217
+
218
+ if (!message) {
219
+ message = 'Unrecognised input';
220
+ if (endInfo.furthestChar === '}') {
221
+ message += '. Possibly missing opening \'{\'';
222
+ } else if (endInfo.furthestChar === ')') {
223
+ message += '. Possibly missing opening \'(\'';
224
+ } else if (endInfo.furthestReachedEnd) {
225
+ message += '. Possibly missing something';
226
+ }
227
+ }
228
+
229
+ error = new LessError({
230
+ type: 'Parse',
231
+ message,
232
+ index: endInfo.furthest,
233
+ filename: fileInfo.filename
234
+ }, imports);
235
+ }
236
+
237
+ const finish = e => {
238
+ e = error || e || imports.error;
239
+
240
+ if (e) {
241
+ if (!(e instanceof LessError)) {
242
+ e = new LessError(e, imports, fileInfo.filename);
243
+ }
244
+
245
+ return callback(e);
246
+ }
247
+ else {
248
+ return callback(null, root);
249
+ }
250
+ };
251
+
252
+ if (context.processImports !== false) {
253
+ new visitors.ImportVisitor(imports, finish)
254
+ .run(root);
255
+ } else {
256
+ return finish();
257
+ }
258
+ },
259
+
260
+ //
261
+ // Here in, the parsing rules/functions
262
+ //
263
+ // The basic structure of the syntax tree generated is as follows:
264
+ //
265
+ // Ruleset -> Declaration -> Value -> Expression -> Entity
266
+ //
267
+ // Here's some Less code:
268
+ //
269
+ // .class {
270
+ // color: #fff;
271
+ // border: 1px solid #000;
272
+ // width: @w + 4px;
273
+ // > .child {...}
274
+ // }
275
+ //
276
+ // And here's what the parse tree might look like:
277
+ //
278
+ // Ruleset (Selector '.class', [
279
+ // Declaration ("color", Value ([Expression [Color #fff]]))
280
+ // Declaration ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
281
+ // Declaration ("width", Value ([Expression [Operation " + " [Variable "@w"][Dimension 4px]]]))
282
+ // Ruleset (Selector [Element '>', '.child'], [...])
283
+ // ])
284
+ //
285
+ // In general, most rules will try to parse a token with the `$re()` function, and if the return
286
+ // value is truly, will return a new node, of the relevant type. Sometimes, we need to check
287
+ // first, before parsing, that's when we use `peek()`.
288
+ //
289
+ parsers: parsers = {
290
+ //
291
+ // The `primary` rule is the *entry* and *exit* point of the parser.
292
+ // The rules here can appear at any level of the parse tree.
293
+ //
294
+ // The recursive nature of the grammar is an interplay between the `block`
295
+ // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
296
+ // as represented by this simplified grammar:
297
+ //
298
+ // primary → (ruleset | declaration)+
299
+ // ruleset → selector+ block
300
+ // block → '{' primary '}'
301
+ //
302
+ // Only at one point is the primary rule not called from the
303
+ // block rule: at the root level.
304
+ //
305
+ primary: function () {
306
+ const mixin = this.mixin;
307
+ let root = [];
308
+ let node;
309
+
310
+ while (true) {
311
+ while (true) {
312
+ node = this.comment();
313
+ if (!node) { break; }
314
+ root.push(node);
315
+ }
316
+ // always process comments before deciding if finished
317
+ if (parserInput.finished) {
318
+ break;
319
+ }
320
+ if (parserInput.peek('}')) {
321
+ break;
322
+ }
323
+
324
+ node = this.extendRule();
325
+ if (node) {
326
+ root = root.concat(node);
327
+ continue;
328
+ }
329
+
330
+ node = mixin.definition() || this.declaration() || this.ruleset() ||
331
+ mixin.call(false, false) || this.variableCall() || this.entities.call() || this.atrule();
332
+ if (node) {
333
+ root.push(node);
334
+ } else {
335
+ let foundSemiColon = false;
336
+ while (parserInput.$char(';')) {
337
+ foundSemiColon = true;
338
+ }
339
+ if (!foundSemiColon) {
340
+ break;
341
+ }
342
+ }
343
+ }
344
+
345
+ return root;
346
+ },
347
+
348
+ // comments are collected by the main parsing mechanism and then assigned to nodes
349
+ // where the current structure allows it
350
+ comment: function () {
351
+ if (parserInput.commentStore.length) {
352
+ const comment = parserInput.commentStore.shift();
353
+ return new(tree.Comment)(comment.text, comment.isLineComment, comment.index, fileInfo);
354
+ }
355
+ },
356
+
357
+ //
358
+ // Entities are tokens which can be found inside an Expression
359
+ //
360
+ entities: {
361
+ mixinLookup: function() {
362
+ return parsers.mixin.call(true, true);
363
+ },
364
+ //
365
+ // A string, which supports escaping " and '
366
+ //
367
+ // "milky way" 'he\'s the one!'
368
+ //
369
+ quoted: function (forceEscaped) {
370
+ let str;
371
+ const index = parserInput.i;
372
+ let isEscaped = false;
373
+
374
+ parserInput.save();
375
+ if (parserInput.$char('~')) {
376
+ isEscaped = true;
377
+ } else if (forceEscaped) {
378
+ parserInput.restore();
379
+ return;
380
+ }
381
+
382
+ str = parserInput.$quoted();
383
+ if (!str) {
384
+ parserInput.restore();
385
+ return;
386
+ }
387
+ parserInput.forget();
388
+
389
+ return new(tree.Quoted)(str.charAt(0), str.substr(1, str.length - 2), isEscaped, index, fileInfo);
390
+ },
391
+
392
+ //
393
+ // A catch-all word, such as:
394
+ //
395
+ // black border-collapse
396
+ //
397
+ keyword: function () {
398
+ const k = parserInput.$char('%') || parserInput.$re(/^\[?(?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+\]?/);
399
+ if (k) {
400
+ return tree.Color.fromKeyword(k) || new(tree.Keyword)(k);
401
+ }
402
+ },
403
+
404
+ //
405
+ // A function call
406
+ //
407
+ // rgb(255, 0, 255)
408
+ //
409
+ // The arguments are parsed with the `entities.arguments` parser.
410
+ //
411
+ call: function () {
412
+ let name;
413
+ let args;
414
+ let func;
415
+ const index = parserInput.i;
416
+
417
+ // http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
418
+ if (parserInput.peek(/^url\(/i)) {
419
+ return;
420
+ }
421
+
422
+ parserInput.save();
423
+
424
+ name = parserInput.$re(/^([\w-]+|%|progid:[\w\.]+)\(/);
425
+ if (!name) {
426
+ parserInput.forget();
427
+ return;
428
+ }
429
+
430
+ name = name[1];
431
+ func = this.customFuncCall(name);
432
+ if (func) {
433
+ args = func.parse();
434
+ if (args && func.stop) {
435
+ parserInput.forget();
436
+ return args;
437
+ }
438
+ }
439
+
440
+ args = this.arguments(args);
441
+
442
+ if (!parserInput.$char(')')) {
443
+ parserInput.restore('Could not parse call arguments or missing \')\'');
444
+ return;
445
+ }
446
+
447
+ parserInput.forget();
448
+
449
+ return new(tree.Call)(name, args, index, fileInfo);
450
+ },
451
+
452
+ //
453
+ // Parsing rules for functions with non-standard args, e.g.:
454
+ //
455
+ // boolean(not(2 > 1))
456
+ //
457
+ // This is a quick prototype, to be modified/improved when
458
+ // more custom-parsed funcs come (e.g. `selector(...)`)
459
+ //
460
+
461
+ customFuncCall: function (name) {
462
+ /* Ideally the table is to be moved out of here for faster perf.,
463
+ but it's quite tricky since it relies on all these `parsers`
464
+ and `expect` available only here */
465
+ return {
466
+ alpha: f(parsers.ieAlpha, true),
467
+ boolean: f(condition),
468
+ 'if': f(condition)
469
+ }[name.toLowerCase()];
470
+
471
+ function f(parse, stop) {
472
+ return {
473
+ parse, // parsing function
474
+ stop // when true - stop after parse() and return its result,
475
+ // otherwise continue for plain args
476
+ };
477
+ }
478
+
479
+ function condition() {
480
+ return [expect(parsers.condition, 'expected condition')];
481
+ }
482
+ },
483
+
484
+ arguments: function (prevArgs) {
485
+ let argsComma = prevArgs || [];
486
+ const argsSemiColon = [];
487
+ let isSemiColonSeparated;
488
+ let value;
489
+
490
+ parserInput.save();
491
+
492
+ while (true) {
493
+ if (prevArgs) {
494
+ prevArgs = false;
495
+ } else {
496
+ value = parsers.detachedRuleset() || this.assignment() || parsers.expression();
497
+ if (!value) {
498
+ break;
499
+ }
500
+
501
+ if (value.value && value.value.length == 1) {
502
+ value = value.value[0];
503
+ }
504
+
505
+ argsComma.push(value);
506
+ }
507
+
508
+ if (parserInput.$char(',')) {
509
+ continue;
510
+ }
511
+
512
+ if (parserInput.$char(';') || isSemiColonSeparated) {
513
+ isSemiColonSeparated = true;
514
+ value = (argsComma.length < 1) ? argsComma[0]
515
+ : new tree.Value(argsComma);
516
+ argsSemiColon.push(value);
517
+ argsComma = [];
518
+ }
519
+ }
520
+
521
+ parserInput.forget();
522
+ return isSemiColonSeparated ? argsSemiColon : argsComma;
523
+ },
524
+ literal: function () {
525
+ return this.dimension() ||
526
+ this.color() ||
527
+ this.quoted() ||
528
+ this.unicodeDescriptor();
529
+ },
530
+
531
+ // Assignments are argument entities for calls.
532
+ // They are present in ie filter properties as shown below.
533
+ //
534
+ // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
535
+ //
536
+
537
+ assignment: function () {
538
+ let key;
539
+ let value;
540
+ parserInput.save();
541
+ key = parserInput.$re(/^\w+(?=\s?=)/i);
542
+ if (!key) {
543
+ parserInput.restore();
544
+ return;
545
+ }
546
+ if (!parserInput.$char('=')) {
547
+ parserInput.restore();
548
+ return;
549
+ }
550
+ value = parsers.entity();
551
+ if (value) {
552
+ parserInput.forget();
553
+ return new(tree.Assignment)(key, value);
554
+ } else {
555
+ parserInput.restore();
556
+ }
557
+ },
558
+
559
+ //
560
+ // Parse url() tokens
561
+ //
562
+ // We use a specific rule for urls, because they don't really behave like
563
+ // standard function calls. The difference is that the argument doesn't have
564
+ // to be enclosed within a string, so it can't be parsed as an Expression.
565
+ //
566
+ url: function () {
567
+ let value;
568
+ const index = parserInput.i;
569
+
570
+ parserInput.autoCommentAbsorb = false;
571
+
572
+ if (!parserInput.$str('url(')) {
573
+ parserInput.autoCommentAbsorb = true;
574
+ return;
575
+ }
576
+
577
+ value = this.quoted() || this.variable() || this.property() ||
578
+ parserInput.$re(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || '';
579
+
580
+ parserInput.autoCommentAbsorb = true;
581
+
582
+ expectChar(')');
583
+
584
+ return new(tree.URL)((value.value != null ||
585
+ value instanceof tree.Variable ||
586
+ value instanceof tree.Property) ?
587
+ value : new(tree.Anonymous)(value, index), index, fileInfo);
588
+ },
589
+
590
+ //
591
+ // A Variable entity, such as `@fink`, in
592
+ //
593
+ // width: @fink + 2px
594
+ //
595
+ // We use a different parser for variable definitions,
596
+ // see `parsers.variable`.
597
+ //
598
+ variable: function () {
599
+ let ch;
600
+ let name;
601
+ const index = parserInput.i;
602
+
603
+ parserInput.save();
604
+ if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^@@?[\w-]+/))) {
605
+ ch = parserInput.currentChar();
606
+ if (ch === '(' || ch === '[' && !parserInput.prevChar().match(/^\s/)) {
607
+ // this may be a VariableCall lookup
608
+ const result = parsers.variableCall(name);
609
+ if (result) {
610
+ parserInput.forget();
611
+ return result;
612
+ }
613
+ }
614
+ parserInput.forget();
615
+ return new(tree.Variable)(name, index, fileInfo);
616
+ }
617
+ parserInput.restore();
618
+ },
619
+
620
+ // A variable entity using the protective {} e.g. @{var}
621
+ variableCurly: function () {
622
+ let curly;
623
+ const index = parserInput.i;
624
+
625
+ if (parserInput.currentChar() === '@' && (curly = parserInput.$re(/^@\{([\w-]+)\}/))) {
626
+ return new(tree.Variable)(`@${curly[1]}`, index, fileInfo);
627
+ }
628
+ },
629
+ //
630
+ // A Property accessor, such as `$color`, in
631
+ //
632
+ // background-color: $color
633
+ //
634
+ property: function () {
635
+ let name;
636
+ const index = parserInput.i;
637
+
638
+ if (parserInput.currentChar() === '$' && (name = parserInput.$re(/^\$[\w-]+/))) {
639
+ return new(tree.Property)(name, index, fileInfo);
640
+ }
641
+ },
642
+
643
+ // A property entity useing the protective {} e.g. ${prop}
644
+ propertyCurly: function () {
645
+ let curly;
646
+ const index = parserInput.i;
647
+
648
+ if (parserInput.currentChar() === '$' && (curly = parserInput.$re(/^\$\{([\w-]+)\}/))) {
649
+ return new(tree.Property)(`$${curly[1]}`, index, fileInfo);
650
+ }
651
+ },
652
+ //
653
+ // A Hexadecimal color
654
+ //
655
+ // #4F3C2F
656
+ //
657
+ // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
658
+ //
659
+ color: function () {
660
+ let rgb;
661
+ parserInput.save();
662
+
663
+ if (parserInput.currentChar() === '#' && (rgb = parserInput.$re(/^#([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3,4})([\w.#\[])?/))) {
664
+ if (!rgb[2]) {
665
+ parserInput.forget();
666
+ return new(tree.Color)(rgb[1], undefined, rgb[0]);
667
+ }
668
+ }
669
+ parserInput.restore();
670
+ },
671
+
672
+ colorKeyword: function () {
673
+ parserInput.save();
674
+ const autoCommentAbsorb = parserInput.autoCommentAbsorb;
675
+ parserInput.autoCommentAbsorb = false;
676
+ const k = parserInput.$re(/^[_A-Za-z-][_A-Za-z0-9-]+/);
677
+ parserInput.autoCommentAbsorb = autoCommentAbsorb;
678
+ if (!k) {
679
+ parserInput.forget();
680
+ return;
681
+ }
682
+ parserInput.restore();
683
+ const color = tree.Color.fromKeyword(k);
684
+ if (color) {
685
+ parserInput.$str(k);
686
+ return color;
687
+ }
688
+ },
689
+
690
+ //
691
+ // A Dimension, that is, a number and a unit
692
+ //
693
+ // 0.5em 95%
694
+ //
695
+ dimension: function () {
696
+ if (parserInput.peekNotNumeric()) {
697
+ return;
698
+ }
699
+
700
+ const value = parserInput.$re(/^([+-]?\d*\.?\d+)(%|[a-z_]+)?/i);
701
+ if (value) {
702
+ return new(tree.Dimension)(value[1], value[2]);
703
+ }
704
+ },
705
+
706
+ //
707
+ // A unicode descriptor, as is used in unicode-range
708
+ //
709
+ // U+0?? or U+00A1-00A9
710
+ //
711
+ unicodeDescriptor: function () {
712
+ let ud;
713
+
714
+ ud = parserInput.$re(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/);
715
+ if (ud) {
716
+ return new(tree.UnicodeDescriptor)(ud[0]);
717
+ }
718
+ },
719
+
720
+ //
721
+ // JavaScript code to be evaluated
722
+ //
723
+ // `window.location.href`
724
+ //
725
+ javascript: function () {
726
+ let js;
727
+ const index = parserInput.i;
728
+
729
+ parserInput.save();
730
+
731
+ const escape = parserInput.$char('~');
732
+ const jsQuote = parserInput.$char('`');
733
+
734
+ if (!jsQuote) {
735
+ parserInput.restore();
736
+ return;
737
+ }
738
+
739
+ js = parserInput.$re(/^[^`]*`/);
740
+ if (js) {
741
+ parserInput.forget();
742
+ return new(tree.JavaScript)(js.substr(0, js.length - 1), Boolean(escape), index, fileInfo);
743
+ }
744
+ parserInput.restore('invalid javascript definition');
745
+ }
746
+ },
747
+
748
+ //
749
+ // The variable part of a variable definition. Used in the `rule` parser
750
+ //
751
+ // @fink:
752
+ //
753
+ variable: function () {
754
+ let name;
755
+
756
+ if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^(@[\w-]+)\s*:/))) { return name[1]; }
757
+ },
758
+
759
+ //
760
+ // Call a variable value to retrieve a detached ruleset
761
+ // or a value from a detached ruleset's rules.
762
+ //
763
+ // @fink();
764
+ // @fink;
765
+ // color: @fink[@color];
766
+ //
767
+ variableCall: function (parsedName) {
768
+ let lookups;
769
+ let important;
770
+ const i = parserInput.i;
771
+ const inValue = !!parsedName;
772
+ let name = parsedName;
773
+
774
+ parserInput.save();
775
+
776
+ if (name || (parserInput.currentChar() === '@'
777
+ && (name = parserInput.$re(/^(@[\w-]+)(\(\s*\))?/)))) {
778
+
779
+ lookups = this.mixin.ruleLookups();
780
+
781
+ if (!lookups && ((inValue && parserInput.$str('()') !== '()') || (name[2] !== '()'))) {
782
+ parserInput.restore('Missing \'[...]\' lookup in variable call');
783
+ return;
784
+ }
785
+
786
+ if (!inValue) {
787
+ name = name[1];
788
+ }
789
+
790
+ if (lookups && parsers.important()) {
791
+ important = true;
792
+ }
793
+
794
+ const call = new tree.VariableCall(name, i, fileInfo);
795
+ if (!inValue && parsers.end()) {
796
+ parserInput.forget();
797
+ return call;
798
+ }
799
+ else {
800
+ parserInput.forget();
801
+ return new tree.NamespaceValue(call, lookups, important, i, fileInfo);
802
+ }
803
+ }
804
+
805
+ parserInput.restore();
806
+ },
807
+
808
+ //
809
+ // extend syntax - used to extend selectors
810
+ //
811
+ extend: function(isRule) {
812
+ let elements;
813
+ let e;
814
+ const index = parserInput.i;
815
+ let option;
816
+ let extendList;
817
+ let extend;
818
+
819
+ if (!parserInput.$str(isRule ? '&:extend(' : ':extend(')) {
820
+ return;
821
+ }
822
+
823
+ do {
824
+ option = null;
825
+ elements = null;
826
+ while (!(option = parserInput.$re(/^(all)(?=\s*(\)|,))/))) {
827
+ e = this.element();
828
+ if (!e) {
829
+ break;
830
+ }
831
+ if (elements) {
832
+ elements.push(e);
833
+ } else {
834
+ elements = [ e ];
835
+ }
836
+ }
837
+
838
+ option = option && option[1];
839
+ if (!elements) {
840
+ error('Missing target selector for :extend().');
841
+ }
842
+ extend = new(tree.Extend)(new(tree.Selector)(elements), option, index, fileInfo);
843
+ if (extendList) {
844
+ extendList.push(extend);
845
+ } else {
846
+ extendList = [ extend ];
847
+ }
848
+ } while (parserInput.$char(','));
849
+
850
+ expect(/^\)/);
851
+
852
+ if (isRule) {
853
+ expect(/^;/);
854
+ }
855
+
856
+ return extendList;
857
+ },
858
+
859
+ //
860
+ // extendRule - used in a rule to extend all the parent selectors
861
+ //
862
+ extendRule: function() {
863
+ return this.extend(true);
864
+ },
865
+
866
+ //
867
+ // Mixins
868
+ //
869
+ mixin: {
870
+ //
871
+ // A Mixin call, with an optional argument list
872
+ //
873
+ // #mixins > .square(#fff);
874
+ // #mixins.square(#fff);
875
+ // .rounded(4px, black);
876
+ // .button;
877
+ //
878
+ // We can lookup / return a value using the lookup syntax:
879
+ //
880
+ // color: #mixin.square(#fff)[@color];
881
+ //
882
+ // The `while` loop is there because mixins can be
883
+ // namespaced, but we only support the child and descendant
884
+ // selector for now.
885
+ //
886
+ call: function (inValue, getLookup) {
887
+ const s = parserInput.currentChar();
888
+ let important = false;
889
+ let lookups;
890
+ const index = parserInput.i;
891
+ let elements;
892
+ let args;
893
+ let hasParens;
894
+
895
+ if (s !== '.' && s !== '#') { return; }
896
+
897
+ parserInput.save(); // stop us absorbing part of an invalid selector
898
+
899
+ elements = this.elements();
900
+
901
+ if (elements) {
902
+ if (parserInput.$char('(')) {
903
+ args = this.args(true).args;
904
+ expectChar(')');
905
+ hasParens = true;
906
+ }
907
+
908
+ if (getLookup !== false) {
909
+ lookups = this.ruleLookups();
910
+ }
911
+ if (getLookup === true && !lookups) {
912
+ parserInput.restore();
913
+ return;
914
+ }
915
+
916
+ if (inValue && !lookups && !hasParens) {
917
+ // This isn't a valid in-value mixin call
918
+ parserInput.restore();
919
+ return;
920
+ }
921
+
922
+ if (!inValue && parsers.important()) {
923
+ important = true;
924
+ }
925
+
926
+ if (inValue || parsers.end()) {
927
+ parserInput.forget();
928
+ const mixin = new(tree.mixin.Call)(elements, args, index, fileInfo, !lookups && important);
929
+ if (lookups) {
930
+ return new tree.NamespaceValue(mixin, lookups, important);
931
+ }
932
+ else {
933
+ return mixin;
934
+ }
935
+ }
936
+ }
937
+
938
+ parserInput.restore();
939
+ },
940
+ /**
941
+ * Matching elements for mixins
942
+ * (Start with . or # and can have > )
943
+ */
944
+ elements: function() {
945
+ let elements;
946
+ let e;
947
+ let c;
948
+ let elem;
949
+ let elemIndex;
950
+ const re = /^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/;
951
+ while (true) {
952
+ elemIndex = parserInput.i;
953
+ e = parserInput.$re(re);
954
+
955
+ if (!e) {
956
+ break;
957
+ }
958
+ elem = new(tree.Element)(c, e, false, elemIndex, fileInfo);
959
+ if (elements) {
960
+ elements.push(elem);
961
+ } else {
962
+ elements = [ elem ];
963
+ }
964
+ c = parserInput.$char('>');
965
+ }
966
+ return elements;
967
+ },
968
+ args: function (isCall) {
969
+ const entities = parsers.entities;
970
+ const returner = { args:null, variadic: false };
971
+ let expressions = [];
972
+ const argsSemiColon = [];
973
+ const argsComma = [];
974
+ let isSemiColonSeparated;
975
+ let expressionContainsNamed;
976
+ let name;
977
+ let nameLoop;
978
+ let value;
979
+ let arg;
980
+ let expand;
981
+ let hasSep = true;
982
+
983
+ parserInput.save();
984
+
985
+ while (true) {
986
+ if (isCall) {
987
+ arg = parsers.detachedRuleset() || parsers.expression();
988
+ } else {
989
+ parserInput.commentStore.length = 0;
990
+ if (parserInput.$str('...')) {
991
+ returner.variadic = true;
992
+ if (parserInput.$char(';') && !isSemiColonSeparated) {
993
+ isSemiColonSeparated = true;
994
+ }
995
+ (isSemiColonSeparated ? argsSemiColon : argsComma)
996
+ .push({ variadic: true });
997
+ break;
998
+ }
999
+ arg = entities.variable() || entities.property() || entities.literal() || entities.keyword() || this.call(true);
1000
+ }
1001
+
1002
+ if (!arg || !hasSep) {
1003
+ break;
1004
+ }
1005
+
1006
+ nameLoop = null;
1007
+ if (arg.throwAwayComments) {
1008
+ arg.throwAwayComments();
1009
+ }
1010
+ value = arg;
1011
+ let val = null;
1012
+
1013
+ if (isCall) {
1014
+ // Variable
1015
+ if (arg.value && arg.value.length == 1) {
1016
+ val = arg.value[0];
1017
+ }
1018
+ } else {
1019
+ val = arg;
1020
+ }
1021
+
1022
+ if (val && (val instanceof tree.Variable || val instanceof tree.Property)) {
1023
+ if (parserInput.$char(':')) {
1024
+ if (expressions.length > 0) {
1025
+ if (isSemiColonSeparated) {
1026
+ error('Cannot mix ; and , as delimiter types');
1027
+ }
1028
+ expressionContainsNamed = true;
1029
+ }
1030
+
1031
+ value = parsers.detachedRuleset() || parsers.expression();
1032
+
1033
+ if (!value) {
1034
+ if (isCall) {
1035
+ error('could not understand value for named argument');
1036
+ } else {
1037
+ parserInput.restore();
1038
+ returner.args = [];
1039
+ return returner;
1040
+ }
1041
+ }
1042
+ nameLoop = (name = val.name);
1043
+ } else if (parserInput.$str('...')) {
1044
+ if (!isCall) {
1045
+ returner.variadic = true;
1046
+ if (parserInput.$char(';') && !isSemiColonSeparated) {
1047
+ isSemiColonSeparated = true;
1048
+ }
1049
+ (isSemiColonSeparated ? argsSemiColon : argsComma)
1050
+ .push({ name: arg.name, variadic: true });
1051
+ break;
1052
+ } else {
1053
+ expand = true;
1054
+ }
1055
+ } else if (!isCall) {
1056
+ name = nameLoop = val.name;
1057
+ value = null;
1058
+ }
1059
+ }
1060
+
1061
+ if (value) {
1062
+ expressions.push(value);
1063
+ }
1064
+
1065
+ argsComma.push({ name:nameLoop, value, expand });
1066
+
1067
+ if (parserInput.$char(',')) {
1068
+ hasSep = true;
1069
+ continue;
1070
+ }
1071
+ hasSep = parserInput.$char(';') === ';';
1072
+
1073
+ if (hasSep || isSemiColonSeparated) {
1074
+
1075
+ if (expressionContainsNamed) {
1076
+ error('Cannot mix ; and , as delimiter types');
1077
+ }
1078
+
1079
+ isSemiColonSeparated = true;
1080
+
1081
+ if (expressions.length > 1) {
1082
+ value = new(tree.Value)(expressions);
1083
+ }
1084
+ argsSemiColon.push({ name, value, expand });
1085
+
1086
+ name = null;
1087
+ expressions = [];
1088
+ expressionContainsNamed = false;
1089
+ }
1090
+ }
1091
+
1092
+ parserInput.forget();
1093
+ returner.args = isSemiColonSeparated ? argsSemiColon : argsComma;
1094
+ return returner;
1095
+ },
1096
+ //
1097
+ // A Mixin definition, with a list of parameters
1098
+ //
1099
+ // .rounded (@radius: 2px, @color) {
1100
+ // ...
1101
+ // }
1102
+ //
1103
+ // Until we have a finer grained state-machine, we have to
1104
+ // do a look-ahead, to make sure we don't have a mixin call.
1105
+ // See the `rule` function for more information.
1106
+ //
1107
+ // We start by matching `.rounded (`, and then proceed on to
1108
+ // the argument list, which has optional default values.
1109
+ // We store the parameters in `params`, with a `value` key,
1110
+ // if there is a value, such as in the case of `@radius`.
1111
+ //
1112
+ // Once we've got our params list, and a closing `)`, we parse
1113
+ // the `{...}` block.
1114
+ //
1115
+ definition: function () {
1116
+ let name;
1117
+ let params = [];
1118
+ let match;
1119
+ let ruleset;
1120
+ let cond;
1121
+ let variadic = false;
1122
+ if ((parserInput.currentChar() !== '.' && parserInput.currentChar() !== '#') ||
1123
+ parserInput.peek(/^[^{]*\}/)) {
1124
+ return;
1125
+ }
1126
+
1127
+ parserInput.save();
1128
+
1129
+ match = parserInput.$re(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/);
1130
+ if (match) {
1131
+ name = match[1];
1132
+
1133
+ const argInfo = this.args(false);
1134
+ params = argInfo.args;
1135
+ variadic = argInfo.variadic;
1136
+
1137
+ // .mixincall("@{a}");
1138
+ // looks a bit like a mixin definition..
1139
+ // also
1140
+ // .mixincall(@a: {rule: set;});
1141
+ // so we have to be nice and restore
1142
+ if (!parserInput.$char(')')) {
1143
+ parserInput.restore('Missing closing \')\'');
1144
+ return;
1145
+ }
1146
+
1147
+ parserInput.commentStore.length = 0;
1148
+
1149
+ if (parserInput.$str('when')) { // Guard
1150
+ cond = expect(parsers.conditions, 'expected condition');
1151
+ }
1152
+
1153
+ ruleset = parsers.block();
1154
+
1155
+ if (ruleset) {
1156
+ parserInput.forget();
1157
+ return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
1158
+ } else {
1159
+ parserInput.restore();
1160
+ }
1161
+ } else {
1162
+ parserInput.forget();
1163
+ }
1164
+ },
1165
+
1166
+ ruleLookups: function() {
1167
+ let rule;
1168
+ let args;
1169
+ const lookups = [];
1170
+
1171
+ if (parserInput.currentChar() !== '[') {
1172
+ return;
1173
+ }
1174
+
1175
+ while (true) {
1176
+ parserInput.save();
1177
+ args = null;
1178
+ rule = this.lookupValue();
1179
+ if (!rule && rule !== '') {
1180
+ parserInput.restore();
1181
+ break;
1182
+ }
1183
+ lookups.push(rule);
1184
+ parserInput.forget();
1185
+ }
1186
+ if (lookups.length > 0) {
1187
+ return lookups;
1188
+ }
1189
+ },
1190
+
1191
+ lookupValue: function() {
1192
+ parserInput.save();
1193
+
1194
+ if (!parserInput.$char('[')) {
1195
+ parserInput.restore();
1196
+ return;
1197
+ }
1198
+
1199
+ const name = parserInput.$re(/^(?:[@$]{0,2})[_a-zA-Z0-9-]*/);
1200
+
1201
+ if (!parserInput.$char(']')) {
1202
+ parserInput.restore();
1203
+ return;
1204
+ }
1205
+
1206
+ if (name || name === '') {
1207
+ parserInput.forget();
1208
+ return name;
1209
+ }
1210
+
1211
+ parserInput.restore();
1212
+ }
1213
+ },
1214
+ //
1215
+ // Entities are the smallest recognized token,
1216
+ // and can be found inside a rule's value.
1217
+ //
1218
+ entity: function () {
1219
+ const entities = this.entities;
1220
+
1221
+ return this.comment() || entities.literal() || entities.variable() || entities.url() ||
1222
+ entities.property() || entities.call() || entities.keyword() || this.mixin.call(true) ||
1223
+ entities.javascript();
1224
+ },
1225
+
1226
+ //
1227
+ // A Declaration terminator. Note that we use `peek()` to check for '}',
1228
+ // because the `block` rule will be expecting it, but we still need to make sure
1229
+ // it's there, if ';' was omitted.
1230
+ //
1231
+ end: function () {
1232
+ return parserInput.$char(';') || parserInput.peek('}');
1233
+ },
1234
+
1235
+ //
1236
+ // IE's alpha function
1237
+ //
1238
+ // alpha(opacity=88)
1239
+ //
1240
+ ieAlpha: function () {
1241
+ let value;
1242
+
1243
+ // http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
1244
+ if (!parserInput.$re(/^opacity=/i)) { return; }
1245
+ value = parserInput.$re(/^\d+/);
1246
+ if (!value) {
1247
+ value = expect(parsers.entities.variable, 'Could not parse alpha');
1248
+ value = `@{${value.name.slice(1)}}`;
1249
+ }
1250
+ expectChar(')');
1251
+ return new tree.Quoted('', `alpha(opacity=${value})`);
1252
+ },
1253
+
1254
+ //
1255
+ // A Selector Element
1256
+ //
1257
+ // div
1258
+ // + h1
1259
+ // #socks
1260
+ // input[type="text"]
1261
+ //
1262
+ // Elements are the building blocks for Selectors,
1263
+ // they are made out of a `Combinator` (see combinator rule),
1264
+ // and an element name, such as a tag a class, or `*`.
1265
+ //
1266
+ element: function () {
1267
+ let e;
1268
+ let c;
1269
+ let v;
1270
+ const index = parserInput.i;
1271
+
1272
+ c = this.combinator();
1273
+
1274
+ e = parserInput.$re(/^(?:\d+\.\d+|\d+)%/) ||
1275
+ parserInput.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
1276
+ parserInput.$char('*') || parserInput.$char('&') || this.attribute() ||
1277
+ parserInput.$re(/^\([^&()@]+\)/) || parserInput.$re(/^[\.#:](?=@)/) ||
1278
+ this.entities.variableCurly();
1279
+
1280
+ if (!e) {
1281
+ parserInput.save();
1282
+ if (parserInput.$char('(')) {
1283
+ if ((v = this.selector(false)) && parserInput.$char(')')) {
1284
+ e = new(tree.Paren)(v);
1285
+ parserInput.forget();
1286
+ } else {
1287
+ parserInput.restore('Missing closing \')\'');
1288
+ }
1289
+ } else {
1290
+ parserInput.forget();
1291
+ }
1292
+ }
1293
+
1294
+ if (e) { return new(tree.Element)(c, e, e instanceof tree.Variable, index, fileInfo); }
1295
+ },
1296
+
1297
+ //
1298
+ // Combinators combine elements together, in a Selector.
1299
+ //
1300
+ // Because our parser isn't white-space sensitive, special care
1301
+ // has to be taken, when parsing the descendant combinator, ` `,
1302
+ // as it's an empty space. We have to check the previous character
1303
+ // in the input, to see if it's a ` ` character. More info on how
1304
+ // we deal with this in *combinator.js*.
1305
+ //
1306
+ combinator: function () {
1307
+ let c = parserInput.currentChar();
1308
+
1309
+ if (c === '/') {
1310
+ parserInput.save();
1311
+ const slashedCombinator = parserInput.$re(/^\/[a-z]+\//i);
1312
+ if (slashedCombinator) {
1313
+ parserInput.forget();
1314
+ return new(tree.Combinator)(slashedCombinator);
1315
+ }
1316
+ parserInput.restore();
1317
+ }
1318
+
1319
+ if (c === '>' || c === '+' || c === '~' || c === '|' || c === '^') {
1320
+ parserInput.i++;
1321
+ if (c === '^' && parserInput.currentChar() === '^') {
1322
+ c = '^^';
1323
+ parserInput.i++;
1324
+ }
1325
+ while (parserInput.isWhitespace()) { parserInput.i++; }
1326
+ return new(tree.Combinator)(c);
1327
+ } else if (parserInput.isWhitespace(-1)) {
1328
+ return new(tree.Combinator)(' ');
1329
+ } else {
1330
+ return new(tree.Combinator)(null);
1331
+ }
1332
+ },
1333
+ //
1334
+ // A CSS Selector
1335
+ // with less extensions e.g. the ability to extend and guard
1336
+ //
1337
+ // .class > div + h1
1338
+ // li a:hover
1339
+ //
1340
+ // Selectors are made out of one or more Elements, see above.
1341
+ //
1342
+ selector: function (isLess) {
1343
+ const index = parserInput.i;
1344
+ let elements;
1345
+ let extendList;
1346
+ let c;
1347
+ let e;
1348
+ let allExtends;
1349
+ let when;
1350
+ let condition;
1351
+ isLess = isLess !== false;
1352
+ while ((isLess && (extendList = this.extend())) || (isLess && (when = parserInput.$str('when'))) || (e = this.element())) {
1353
+ if (when) {
1354
+ condition = expect(this.conditions, 'expected condition');
1355
+ } else if (condition) {
1356
+ error('CSS guard can only be used at the end of selector');
1357
+ } else if (extendList) {
1358
+ if (allExtends) {
1359
+ allExtends = allExtends.concat(extendList);
1360
+ } else {
1361
+ allExtends = extendList;
1362
+ }
1363
+ } else {
1364
+ if (allExtends) { error('Extend can only be used at the end of selector'); }
1365
+ c = parserInput.currentChar();
1366
+ if (elements) {
1367
+ elements.push(e);
1368
+ } else {
1369
+ elements = [ e ];
1370
+ }
1371
+ e = null;
1372
+ }
1373
+ if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') {
1374
+ break;
1375
+ }
1376
+ }
1377
+
1378
+ if (elements) { return new(tree.Selector)(elements, allExtends, condition, index, fileInfo); }
1379
+ if (allExtends) { error('Extend must be used to extend a selector, it cannot be used on its own'); }
1380
+ },
1381
+ selectors: function () {
1382
+ let s;
1383
+ let selectors;
1384
+ while (true) {
1385
+ s = this.selector();
1386
+ if (!s) {
1387
+ break;
1388
+ }
1389
+ if (selectors) {
1390
+ selectors.push(s);
1391
+ } else {
1392
+ selectors = [ s ];
1393
+ }
1394
+ parserInput.commentStore.length = 0;
1395
+ if (s.condition && selectors.length > 1) {
1396
+ error("Guards are only currently allowed on a single selector.");
1397
+ }
1398
+ if (!parserInput.$char(',')) { break; }
1399
+ if (s.condition) {
1400
+ error("Guards are only currently allowed on a single selector.");
1401
+ }
1402
+ parserInput.commentStore.length = 0;
1403
+ }
1404
+ return selectors;
1405
+ },
1406
+ attribute: function () {
1407
+ if (!parserInput.$char('[')) { return; }
1408
+
1409
+ const entities = this.entities;
1410
+ let key;
1411
+ let val;
1412
+ let op;
1413
+
1414
+ if (!(key = entities.variableCurly())) {
1415
+ key = expect(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/);
1416
+ }
1417
+
1418
+ op = parserInput.$re(/^[|~*$^]?=/);
1419
+ if (op) {
1420
+ val = entities.quoted() || parserInput.$re(/^[0-9]+%/) || parserInput.$re(/^[\w-]+/) || entities.variableCurly();
1421
+ }
1422
+
1423
+ expectChar(']');
1424
+
1425
+ return new(tree.Attribute)(key, op, val);
1426
+ },
1427
+
1428
+ //
1429
+ // The `block` rule is used by `ruleset` and `mixin.definition`.
1430
+ // It's a wrapper around the `primary` rule, with added `{}`.
1431
+ //
1432
+ block: function () {
1433
+ let content;
1434
+ if (parserInput.$char('{') && (content = this.primary()) && parserInput.$char('}')) {
1435
+ return content;
1436
+ }
1437
+ },
1438
+
1439
+ blockRuleset: function() {
1440
+ let block = this.block();
1441
+
1442
+ if (block) {
1443
+ block = new tree.Ruleset(null, block);
1444
+ }
1445
+ return block;
1446
+ },
1447
+
1448
+ detachedRuleset: function() {
1449
+ let argInfo;
1450
+ let params;
1451
+ let variadic;
1452
+
1453
+ parserInput.save();
1454
+ if (parserInput.$re(/^[.#]\(/)) {
1455
+ /**
1456
+ * DR args currently only implemented for each() function, and not
1457
+ * yet settable as `@dr: #(@arg) {}`
1458
+ * This should be done when DRs are merged with mixins.
1459
+ * See: https://github.com/less/less-meta/issues/16
1460
+ */
1461
+ argInfo = this.mixin.args(false);
1462
+ params = argInfo.args;
1463
+ variadic = argInfo.variadic;
1464
+ if (!parserInput.$char(')')) {
1465
+ parserInput.restore();
1466
+ return;
1467
+ }
1468
+ }
1469
+ const blockRuleset = this.blockRuleset();
1470
+ if (blockRuleset) {
1471
+ parserInput.forget();
1472
+ if (params) {
1473
+ return new tree.mixin.Definition(null, params, blockRuleset, null, variadic);
1474
+ }
1475
+ return new tree.DetachedRuleset(blockRuleset);
1476
+ }
1477
+ parserInput.restore();
1478
+ },
1479
+
1480
+ //
1481
+ // div, .class, body > p {...}
1482
+ //
1483
+ ruleset: function () {
1484
+ let selectors;
1485
+ let rules;
1486
+ let debugInfo;
1487
+
1488
+ parserInput.save();
1489
+
1490
+ if (context.dumpLineNumbers) {
1491
+ debugInfo = getDebugInfo(parserInput.i);
1492
+ }
1493
+
1494
+ selectors = this.selectors();
1495
+
1496
+ if (selectors && (rules = this.block())) {
1497
+ parserInput.forget();
1498
+ const ruleset = new(tree.Ruleset)(selectors, rules, context.strictImports);
1499
+ if (context.dumpLineNumbers) {
1500
+ ruleset.debugInfo = debugInfo;
1501
+ }
1502
+ return ruleset;
1503
+ } else {
1504
+ parserInput.restore();
1505
+ }
1506
+ },
1507
+ declaration: function () {
1508
+ let name;
1509
+ let value;
1510
+ const index = parserInput.i;
1511
+ let hasDR;
1512
+ const c = parserInput.currentChar();
1513
+ let important;
1514
+ let merge;
1515
+ let isVariable;
1516
+
1517
+ if (c === '.' || c === '#' || c === '&' || c === ':') { return; }
1518
+
1519
+ parserInput.save();
1520
+
1521
+ name = this.variable() || this.ruleProperty();
1522
+ if (name) {
1523
+ isVariable = typeof name === 'string';
1524
+
1525
+ if (isVariable) {
1526
+ value = this.detachedRuleset();
1527
+ if (value) {
1528
+ hasDR = true;
1529
+ }
1530
+ }
1531
+
1532
+ parserInput.commentStore.length = 0;
1533
+ if (!value) {
1534
+ // a name returned by this.ruleProperty() is always an array of the form:
1535
+ // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
1536
+ // where each item is a tree.Keyword or tree.Variable
1537
+ merge = !isVariable && name.length > 1 && name.pop().value;
1538
+
1539
+ // Custom property values get permissive parsing
1540
+ if (name[0].value && name[0].value.slice(0, 2) === '--') {
1541
+ value = this.permissiveValue();
1542
+ }
1543
+ // Try to store values as anonymous
1544
+ // If we need the value later we'll re-parse it in ruleset.parseValue
1545
+ else {
1546
+ value = this.anonymousValue();
1547
+ }
1548
+ if (value) {
1549
+ parserInput.forget();
1550
+ // anonymous values absorb the end ';' which is required for them to work
1551
+ return new(tree.Declaration)(name, value, false, merge, index, fileInfo);
1552
+ }
1553
+
1554
+ if (!value) {
1555
+ value = this.value();
1556
+ }
1557
+
1558
+ if (value) {
1559
+ important = this.important();
1560
+ } else if (isVariable) {
1561
+ // As a last resort, try permissiveValue
1562
+ value = this.permissiveValue();
1563
+ }
1564
+ }
1565
+
1566
+ if (value && (this.end() || hasDR)) {
1567
+ parserInput.forget();
1568
+ return new(tree.Declaration)(name, value, important, merge, index, fileInfo);
1569
+ }
1570
+ else {
1571
+ parserInput.restore();
1572
+ }
1573
+ } else {
1574
+ parserInput.restore();
1575
+ }
1576
+ },
1577
+ anonymousValue: function () {
1578
+ const index = parserInput.i;
1579
+ const match = parserInput.$re(/^([^.#@\$+\/'"*`(;{}-]*);/);
1580
+ if (match) {
1581
+ return new(tree.Anonymous)(match[1], index);
1582
+ }
1583
+ },
1584
+ /**
1585
+ * Used for custom properties, at-rules, and variables (as fallback)
1586
+ * Parses almost anything inside of {} [] () "" blocks
1587
+ * until it reaches outer-most tokens.
1588
+ *
1589
+ * First, it will try to parse comments and entities to reach
1590
+ * the end. This is mostly like the Expression parser except no
1591
+ * math is allowed.
1592
+ */
1593
+ permissiveValue: function (untilTokens) {
1594
+ let i;
1595
+ let e;
1596
+ let done;
1597
+ let value;
1598
+ const tok = untilTokens || ';';
1599
+ const index = parserInput.i;
1600
+ const result = [];
1601
+
1602
+ function testCurrentChar() {
1603
+ const char = parserInput.currentChar();
1604
+ if (typeof tok === 'string') {
1605
+ return char === tok;
1606
+ } else {
1607
+ return tok.test(char);
1608
+ }
1609
+ }
1610
+ if (testCurrentChar()) {
1611
+ return;
1612
+ }
1613
+ value = [];
1614
+ do {
1615
+ e = this.comment();
1616
+ if (e) {
1617
+ value.push(e);
1618
+ continue;
1619
+ }
1620
+ e = this.entity();
1621
+ if (e) {
1622
+ value.push(e);
1623
+ }
1624
+ } while (e);
1625
+
1626
+ done = testCurrentChar();
1627
+
1628
+ if (value.length > 0) {
1629
+ value = new(tree.Expression)(value);
1630
+ if (done) {
1631
+ return value;
1632
+ }
1633
+ else {
1634
+ result.push(value);
1635
+ }
1636
+ // Preserve space before $parseUntil as it will not
1637
+ if (parserInput.prevChar() === ' ') {
1638
+ result.push(new tree.Anonymous(' ', index));
1639
+ }
1640
+ }
1641
+ parserInput.save();
1642
+
1643
+ value = parserInput.$parseUntil(tok);
1644
+
1645
+ if (value) {
1646
+ if (typeof value === 'string') {
1647
+ error(`Expected '${value}'`, 'Parse');
1648
+ }
1649
+ if (value.length === 1 && value[0] === ' ') {
1650
+ parserInput.forget();
1651
+ return new tree.Anonymous('', index);
1652
+ }
1653
+ let item;
1654
+ for (i = 0; i < value.length; i++) {
1655
+ item = value[i];
1656
+ if (Array.isArray(item)) {
1657
+ // Treat actual quotes as normal quoted values
1658
+ result.push(new tree.Quoted(item[0], item[1], true, index, fileInfo));
1659
+ }
1660
+ else {
1661
+ if (i === value.length - 1) {
1662
+ item = item.trim();
1663
+ }
1664
+ // Treat like quoted values, but replace vars like unquoted expressions
1665
+ const quote = new tree.Quoted('\'', item, true, index, fileInfo);
1666
+ quote.variableRegex = /@([\w-]+)/g;
1667
+ quote.propRegex = /\$([\w-]+)/g;
1668
+ result.push(quote);
1669
+ }
1670
+ }
1671
+ parserInput.forget();
1672
+ return new tree.Expression(result, true);
1673
+ }
1674
+ parserInput.restore();
1675
+ },
1676
+
1677
+ //
1678
+ // An @import atrule
1679
+ //
1680
+ // @import "lib";
1681
+ //
1682
+ // Depending on our environment, importing is done differently:
1683
+ // In the browser, it's an XHR request, in Node, it would be a
1684
+ // file-system operation. The function used for importing is
1685
+ // stored in `import`, which we pass to the Import constructor.
1686
+ //
1687
+ 'import': function () {
1688
+ let path;
1689
+ let features;
1690
+ const index = parserInput.i;
1691
+
1692
+ const dir = parserInput.$re(/^@import?\s+/);
1693
+
1694
+ if (dir) {
1695
+ const options = (dir ? this.importOptions() : null) || {};
1696
+
1697
+ if ((path = this.entities.quoted() || this.entities.url())) {
1698
+ features = this.mediaFeatures();
1699
+
1700
+ if (!parserInput.$char(';')) {
1701
+ parserInput.i = index;
1702
+ error('missing semi-colon or unrecognised media features on import');
1703
+ }
1704
+ features = features && new(tree.Value)(features);
1705
+ return new(tree.Import)(path, features, options, index, fileInfo);
1706
+ }
1707
+ else {
1708
+ parserInput.i = index;
1709
+ error('malformed import statement');
1710
+ }
1711
+ }
1712
+ },
1713
+
1714
+ importOptions: function() {
1715
+ let o;
1716
+ const options = {};
1717
+ let optionName;
1718
+ let value;
1719
+
1720
+ // list of options, surrounded by parens
1721
+ if (!parserInput.$char('(')) { return null; }
1722
+ do {
1723
+ o = this.importOption();
1724
+ if (o) {
1725
+ optionName = o;
1726
+ value = true;
1727
+ switch (optionName) {
1728
+ case 'css':
1729
+ optionName = 'less';
1730
+ value = false;
1731
+ break;
1732
+ case 'once':
1733
+ optionName = 'multiple';
1734
+ value = false;
1735
+ break;
1736
+ }
1737
+ options[optionName] = value;
1738
+ if (!parserInput.$char(',')) { break; }
1739
+ }
1740
+ } while (o);
1741
+ expectChar(')');
1742
+ return options;
1743
+ },
1744
+
1745
+ importOption: function() {
1746
+ const opt = parserInput.$re(/^(less|css|multiple|once|inline|reference|optional)/);
1747
+ if (opt) {
1748
+ return opt[1];
1749
+ }
1750
+ },
1751
+
1752
+ mediaFeature: function () {
1753
+ const entities = this.entities;
1754
+ const nodes = [];
1755
+ let e;
1756
+ let p;
1757
+ parserInput.save();
1758
+ do {
1759
+ e = entities.keyword() || entities.variable() || entities.mixinLookup();
1760
+ if (e) {
1761
+ nodes.push(e);
1762
+ } else if (parserInput.$char('(')) {
1763
+ p = this.property();
1764
+ e = this.value();
1765
+ if (parserInput.$char(')')) {
1766
+ if (p && e) {
1767
+ nodes.push(new(tree.Paren)(new(tree.Declaration)(p, e, null, null, parserInput.i, fileInfo, true)));
1768
+ } else if (e) {
1769
+ nodes.push(new(tree.Paren)(e));
1770
+ } else {
1771
+ error('badly formed media feature definition');
1772
+ }
1773
+ } else {
1774
+ error('Missing closing \')\'', 'Parse');
1775
+ }
1776
+ }
1777
+ } while (e);
1778
+
1779
+ parserInput.forget();
1780
+ if (nodes.length > 0) {
1781
+ return new(tree.Expression)(nodes);
1782
+ }
1783
+ },
1784
+
1785
+ mediaFeatures: function () {
1786
+ const entities = this.entities;
1787
+ const features = [];
1788
+ let e;
1789
+ do {
1790
+ e = this.mediaFeature();
1791
+ if (e) {
1792
+ features.push(e);
1793
+ if (!parserInput.$char(',')) { break; }
1794
+ } else {
1795
+ e = entities.variable() || entities.mixinLookup();
1796
+ if (e) {
1797
+ features.push(e);
1798
+ if (!parserInput.$char(',')) { break; }
1799
+ }
1800
+ }
1801
+ } while (e);
1802
+
1803
+ return features.length > 0 ? features : null;
1804
+ },
1805
+
1806
+ media: function () {
1807
+ let features;
1808
+ let rules;
1809
+ let media;
1810
+ let debugInfo;
1811
+ const index = parserInput.i;
1812
+
1813
+ if (context.dumpLineNumbers) {
1814
+ debugInfo = getDebugInfo(index);
1815
+ }
1816
+
1817
+ parserInput.save();
1818
+
1819
+ if (parserInput.$str('@media')) {
1820
+ features = this.mediaFeatures();
1821
+
1822
+ rules = this.block();
1823
+
1824
+ if (!rules) {
1825
+ error('media definitions require block statements after any features');
1826
+ }
1827
+
1828
+ parserInput.forget();
1829
+
1830
+ media = new(tree.Media)(rules, features, index, fileInfo);
1831
+ if (context.dumpLineNumbers) {
1832
+ media.debugInfo = debugInfo;
1833
+ }
1834
+
1835
+ return media;
1836
+ }
1837
+
1838
+ parserInput.restore();
1839
+ },
1840
+
1841
+ //
1842
+
1843
+ // A @plugin directive, used to import plugins dynamically.
1844
+ //
1845
+ // @plugin (args) "lib";
1846
+ //
1847
+ plugin: function () {
1848
+ let path;
1849
+ let args;
1850
+ let options;
1851
+ const index = parserInput.i;
1852
+ const dir = parserInput.$re(/^@plugin?\s+/);
1853
+
1854
+ if (dir) {
1855
+ args = this.pluginArgs();
1856
+
1857
+ if (args) {
1858
+ options = {
1859
+ pluginArgs: args,
1860
+ isPlugin: true
1861
+ };
1862
+ }
1863
+ else {
1864
+ options = { isPlugin: true };
1865
+ }
1866
+
1867
+ if ((path = this.entities.quoted() || this.entities.url())) {
1868
+
1869
+ if (!parserInput.$char(';')) {
1870
+ parserInput.i = index;
1871
+ error('missing semi-colon on @plugin');
1872
+ }
1873
+ return new(tree.Import)(path, null, options, index, fileInfo);
1874
+ }
1875
+ else {
1876
+ parserInput.i = index;
1877
+ error('malformed @plugin statement');
1878
+ }
1879
+ }
1880
+ },
1881
+
1882
+ pluginArgs: function() {
1883
+ // list of options, surrounded by parens
1884
+ parserInput.save();
1885
+ if (!parserInput.$char('(')) {
1886
+ parserInput.restore();
1887
+ return null;
1888
+ }
1889
+ const args = parserInput.$re(/^\s*([^\);]+)\)\s*/);
1890
+ if (args[1]) {
1891
+ parserInput.forget();
1892
+ return args[1].trim();
1893
+ }
1894
+ else {
1895
+ parserInput.restore();
1896
+ return null;
1897
+ }
1898
+ },
1899
+
1900
+ //
1901
+ // A CSS AtRule
1902
+ //
1903
+ // @charset "utf-8";
1904
+ //
1905
+ atrule: function () {
1906
+ const index = parserInput.i;
1907
+ let name;
1908
+ let value;
1909
+ let rules;
1910
+ let nonVendorSpecificName;
1911
+ let hasIdentifier;
1912
+ let hasExpression;
1913
+ let hasUnknown;
1914
+ let hasBlock = true;
1915
+ let isRooted = true;
1916
+
1917
+ if (parserInput.currentChar() !== '@') { return; }
1918
+
1919
+ value = this['import']() || this.plugin() || this.media();
1920
+ if (value) {
1921
+ return value;
1922
+ }
1923
+
1924
+ parserInput.save();
1925
+
1926
+ name = parserInput.$re(/^@[a-z-]+/);
1927
+
1928
+ if (!name) { return; }
1929
+
1930
+ nonVendorSpecificName = name;
1931
+ if (name.charAt(1) == '-' && name.indexOf('-', 2) > 0) {
1932
+ nonVendorSpecificName = `@${name.slice(name.indexOf('-', 2) + 1)}`;
1933
+ }
1934
+
1935
+ switch (nonVendorSpecificName) {
1936
+ case '@charset':
1937
+ hasIdentifier = true;
1938
+ hasBlock = false;
1939
+ break;
1940
+ case '@namespace':
1941
+ hasExpression = true;
1942
+ hasBlock = false;
1943
+ break;
1944
+ case '@keyframes':
1945
+ case '@counter-style':
1946
+ hasIdentifier = true;
1947
+ break;
1948
+ case '@document':
1949
+ case '@supports':
1950
+ hasUnknown = true;
1951
+ isRooted = false;
1952
+ break;
1953
+ default:
1954
+ hasUnknown = true;
1955
+ break;
1956
+ }
1957
+
1958
+ parserInput.commentStore.length = 0;
1959
+
1960
+ if (hasIdentifier) {
1961
+ value = this.entity();
1962
+ if (!value) {
1963
+ error(`expected ${name} identifier`);
1964
+ }
1965
+ } else if (hasExpression) {
1966
+ value = this.expression();
1967
+ if (!value) {
1968
+ error(`expected ${name} expression`);
1969
+ }
1970
+ } else if (hasUnknown) {
1971
+ value = this.permissiveValue(/^[{;]/);
1972
+ hasBlock = (parserInput.currentChar() === '{');
1973
+ if (!value) {
1974
+ if (!hasBlock && parserInput.currentChar() !== ';') {
1975
+ error(`${name} rule is missing block or ending semi-colon`);
1976
+ }
1977
+ }
1978
+ else if (!value.value) {
1979
+ value = null;
1980
+ }
1981
+ }
1982
+
1983
+ if (hasBlock) {
1984
+ rules = this.blockRuleset();
1985
+ }
1986
+
1987
+ if (rules || (!hasBlock && value && parserInput.$char(';'))) {
1988
+ parserInput.forget();
1989
+ return new(tree.AtRule)(name, value, rules, index, fileInfo,
1990
+ context.dumpLineNumbers ? getDebugInfo(index) : null,
1991
+ isRooted
1992
+ );
1993
+ }
1994
+
1995
+ parserInput.restore('at-rule options not recognised');
1996
+ },
1997
+
1998
+ //
1999
+ // A Value is a comma-delimited list of Expressions
2000
+ //
2001
+ // font-family: Baskerville, Georgia, serif;
2002
+ //
2003
+ // In a Rule, a Value represents everything after the `:`,
2004
+ // and before the `;`.
2005
+ //
2006
+ value: function () {
2007
+ let e;
2008
+ const expressions = [];
2009
+ const index = parserInput.i;
2010
+
2011
+ do {
2012
+ e = this.expression();
2013
+ if (e) {
2014
+ expressions.push(e);
2015
+ if (!parserInput.$char(',')) { break; }
2016
+ }
2017
+ } while (e);
2018
+
2019
+ if (expressions.length > 0) {
2020
+ return new(tree.Value)(expressions, index);
2021
+ }
2022
+ },
2023
+ important: function () {
2024
+ if (parserInput.currentChar() === '!') {
2025
+ return parserInput.$re(/^! *important/);
2026
+ }
2027
+ },
2028
+ sub: function () {
2029
+ let a;
2030
+ let e;
2031
+
2032
+ parserInput.save();
2033
+ if (parserInput.$char('(')) {
2034
+ a = this.addition();
2035
+ if (a && parserInput.$char(')')) {
2036
+ parserInput.forget();
2037
+ e = new(tree.Expression)([a]);
2038
+ e.parens = true;
2039
+ return e;
2040
+ }
2041
+ parserInput.restore('Expected \')\'');
2042
+ return;
2043
+ }
2044
+ parserInput.restore();
2045
+ },
2046
+ multiplication: function () {
2047
+ let m;
2048
+ let a;
2049
+ let op;
2050
+ let operation;
2051
+ let isSpaced;
2052
+ m = this.operand();
2053
+ if (m) {
2054
+ isSpaced = parserInput.isWhitespace(-1);
2055
+ while (true) {
2056
+ if (parserInput.peek(/^\/[*\/]/)) {
2057
+ break;
2058
+ }
2059
+
2060
+ parserInput.save();
2061
+
2062
+ op = parserInput.$char('/') || parserInput.$char('*') || parserInput.$str('./');
2063
+
2064
+ if (!op) { parserInput.forget(); break; }
2065
+
2066
+ a = this.operand();
2067
+
2068
+ if (!a) { parserInput.restore(); break; }
2069
+ parserInput.forget();
2070
+
2071
+ m.parensInOp = true;
2072
+ a.parensInOp = true;
2073
+ operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
2074
+ isSpaced = parserInput.isWhitespace(-1);
2075
+ }
2076
+ return operation || m;
2077
+ }
2078
+ },
2079
+ addition: function () {
2080
+ let m;
2081
+ let a;
2082
+ let op;
2083
+ let operation;
2084
+ let isSpaced;
2085
+ m = this.multiplication();
2086
+ if (m) {
2087
+ isSpaced = parserInput.isWhitespace(-1);
2088
+ while (true) {
2089
+ op = parserInput.$re(/^[-+]\s+/) || (!isSpaced && (parserInput.$char('+') || parserInput.$char('-')));
2090
+ if (!op) {
2091
+ break;
2092
+ }
2093
+ a = this.multiplication();
2094
+ if (!a) {
2095
+ break;
2096
+ }
2097
+
2098
+ m.parensInOp = true;
2099
+ a.parensInOp = true;
2100
+ operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
2101
+ isSpaced = parserInput.isWhitespace(-1);
2102
+ }
2103
+ return operation || m;
2104
+ }
2105
+ },
2106
+ conditions: function () {
2107
+ let a;
2108
+ let b;
2109
+ const index = parserInput.i;
2110
+ let condition;
2111
+
2112
+ a = this.condition(true);
2113
+ if (a) {
2114
+ while (true) {
2115
+ if (!parserInput.peek(/^,\s*(not\s*)?\(/) || !parserInput.$char(',')) {
2116
+ break;
2117
+ }
2118
+ b = this.condition(true);
2119
+ if (!b) {
2120
+ break;
2121
+ }
2122
+ condition = new(tree.Condition)('or', condition || a, b, index);
2123
+ }
2124
+ return condition || a;
2125
+ }
2126
+ },
2127
+ condition: function (needsParens) {
2128
+ let result;
2129
+ let logical;
2130
+ let next;
2131
+ function or() {
2132
+ return parserInput.$str('or');
2133
+ }
2134
+
2135
+ result = this.conditionAnd(needsParens);
2136
+ if (!result) {
2137
+ return ;
2138
+ }
2139
+ logical = or();
2140
+ if (logical) {
2141
+ next = this.condition(needsParens);
2142
+ if (next) {
2143
+ result = new(tree.Condition)(logical, result, next);
2144
+ } else {
2145
+ return ;
2146
+ }
2147
+ }
2148
+ return result;
2149
+ },
2150
+ conditionAnd: function (needsParens) {
2151
+ let result;
2152
+ let logical;
2153
+ let next;
2154
+ const self = this;
2155
+ function insideCondition() {
2156
+ const cond = self.negatedCondition(needsParens) || self.parenthesisCondition(needsParens);
2157
+ if (!cond && !needsParens) {
2158
+ return self.atomicCondition(needsParens);
2159
+ }
2160
+ return cond;
2161
+ }
2162
+ function and() {
2163
+ return parserInput.$str('and');
2164
+ }
2165
+
2166
+ result = insideCondition();
2167
+ if (!result) {
2168
+ return ;
2169
+ }
2170
+ logical = and();
2171
+ if (logical) {
2172
+ next = this.conditionAnd(needsParens);
2173
+ if (next) {
2174
+ result = new(tree.Condition)(logical, result, next);
2175
+ } else {
2176
+ return ;
2177
+ }
2178
+ }
2179
+ return result;
2180
+ },
2181
+ negatedCondition: function (needsParens) {
2182
+ if (parserInput.$str('not')) {
2183
+ const result = this.parenthesisCondition(needsParens);
2184
+ if (result) {
2185
+ result.negate = !result.negate;
2186
+ }
2187
+ return result;
2188
+ }
2189
+ },
2190
+ parenthesisCondition: function (needsParens) {
2191
+ function tryConditionFollowedByParenthesis(me) {
2192
+ let body;
2193
+ parserInput.save();
2194
+ body = me.condition(needsParens);
2195
+ if (!body) {
2196
+ parserInput.restore();
2197
+ return ;
2198
+ }
2199
+ if (!parserInput.$char(')')) {
2200
+ parserInput.restore();
2201
+ return ;
2202
+ }
2203
+ parserInput.forget();
2204
+ return body;
2205
+ }
2206
+
2207
+ let body;
2208
+ parserInput.save();
2209
+ if (!parserInput.$str('(')) {
2210
+ parserInput.restore();
2211
+ return ;
2212
+ }
2213
+ body = tryConditionFollowedByParenthesis(this);
2214
+ if (body) {
2215
+ parserInput.forget();
2216
+ return body;
2217
+ }
2218
+
2219
+ body = this.atomicCondition(needsParens);
2220
+ if (!body) {
2221
+ parserInput.restore();
2222
+ return ;
2223
+ }
2224
+ if (!parserInput.$char(')')) {
2225
+ parserInput.restore(`expected ')' got '${parserInput.currentChar()}'`);
2226
+ return ;
2227
+ }
2228
+ parserInput.forget();
2229
+ return body;
2230
+ },
2231
+ atomicCondition: function (needsParens) {
2232
+ const entities = this.entities;
2233
+ const index = parserInput.i;
2234
+ let a;
2235
+ let b;
2236
+ let c;
2237
+ let op;
2238
+
2239
+ function cond() {
2240
+ return this.addition() || entities.keyword() || entities.quoted() || entities.mixinLookup();
2241
+ }
2242
+ cond = cond.bind(this);
2243
+
2244
+ a = cond();
2245
+ if (a) {
2246
+ if (parserInput.$char('>')) {
2247
+ if (parserInput.$char('=')) {
2248
+ op = '>=';
2249
+ } else {
2250
+ op = '>';
2251
+ }
2252
+ } else
2253
+ if (parserInput.$char('<')) {
2254
+ if (parserInput.$char('=')) {
2255
+ op = '<=';
2256
+ } else {
2257
+ op = '<';
2258
+ }
2259
+ } else
2260
+ if (parserInput.$char('=')) {
2261
+ if (parserInput.$char('>')) {
2262
+ op = '=>';
2263
+ } else if (parserInput.$char('<')) {
2264
+ op = '=<';
2265
+ } else {
2266
+ op = '=';
2267
+ }
2268
+ }
2269
+ if (op) {
2270
+ b = cond();
2271
+ if (b) {
2272
+ c = new(tree.Condition)(op, a, b, index, false);
2273
+ } else {
2274
+ error('expected expression');
2275
+ }
2276
+ } else {
2277
+ c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, false);
2278
+ }
2279
+ return c;
2280
+ }
2281
+ },
2282
+
2283
+ //
2284
+ // An operand is anything that can be part of an operation,
2285
+ // such as a Color, or a Variable
2286
+ //
2287
+ operand: function () {
2288
+ const entities = this.entities;
2289
+ let negate;
2290
+
2291
+ if (parserInput.peek(/^-[@\$\(]/)) {
2292
+ negate = parserInput.$char('-');
2293
+ }
2294
+
2295
+ let o = this.sub() || entities.dimension() ||
2296
+ entities.color() || entities.variable() ||
2297
+ entities.property() || entities.call() ||
2298
+ entities.quoted(true) || entities.colorKeyword() ||
2299
+ entities.mixinLookup();
2300
+
2301
+ if (negate) {
2302
+ o.parensInOp = true;
2303
+ o = new(tree.Negative)(o);
2304
+ }
2305
+
2306
+ return o;
2307
+ },
2308
+
2309
+ //
2310
+ // Expressions either represent mathematical operations,
2311
+ // or white-space delimited Entities.
2312
+ //
2313
+ // 1px solid black
2314
+ // @var * 2
2315
+ //
2316
+ expression: function () {
2317
+ const entities = [];
2318
+ let e;
2319
+ let delim;
2320
+ const index = parserInput.i;
2321
+
2322
+ do {
2323
+ e = this.comment();
2324
+ if (e) {
2325
+ entities.push(e);
2326
+ continue;
2327
+ }
2328
+ e = this.addition() || this.entity();
2329
+ if (e) {
2330
+ entities.push(e);
2331
+ // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
2332
+ if (!parserInput.peek(/^\/[\/*]/)) {
2333
+ delim = parserInput.$char('/');
2334
+ if (delim) {
2335
+ entities.push(new(tree.Anonymous)(delim, index));
2336
+ }
2337
+ }
2338
+ }
2339
+ } while (e);
2340
+ if (entities.length > 0) {
2341
+ return new(tree.Expression)(entities);
2342
+ }
2343
+ },
2344
+ property: function () {
2345
+ const name = parserInput.$re(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/);
2346
+ if (name) {
2347
+ return name[1];
2348
+ }
2349
+ },
2350
+ ruleProperty: function () {
2351
+ let name = [];
2352
+ const index = [];
2353
+ let s;
2354
+ let k;
2355
+
2356
+ parserInput.save();
2357
+
2358
+ const simpleProperty = parserInput.$re(/^([_a-zA-Z0-9-]+)\s*:/);
2359
+ if (simpleProperty) {
2360
+ name = [new(tree.Keyword)(simpleProperty[1])];
2361
+ parserInput.forget();
2362
+ return name;
2363
+ }
2364
+
2365
+ function match(re) {
2366
+ const i = parserInput.i;
2367
+ const chunk = parserInput.$re(re);
2368
+ if (chunk) {
2369
+ index.push(i);
2370
+ return name.push(chunk[1]);
2371
+ }
2372
+ }
2373
+
2374
+ match(/^(\*?)/);
2375
+ while (true) {
2376
+ if (!match(/^((?:[\w-]+)|(?:[@\$]\{[\w-]+\}))/)) {
2377
+ break;
2378
+ }
2379
+ }
2380
+
2381
+ if ((name.length > 1) && match(/^((?:\+_|\+)?)\s*:/)) {
2382
+ parserInput.forget();
2383
+
2384
+ // at last, we have the complete match now. move forward,
2385
+ // convert name particles to tree objects and return:
2386
+ if (name[0] === '') {
2387
+ name.shift();
2388
+ index.shift();
2389
+ }
2390
+ for (k = 0; k < name.length; k++) {
2391
+ s = name[k];
2392
+ name[k] = (s.charAt(0) !== '@' && s.charAt(0) !== '$') ?
2393
+ new(tree.Keyword)(s) :
2394
+ (s.charAt(0) === '@' ?
2395
+ new(tree.Variable)(`@${s.slice(2, -1)}`, index[k], fileInfo) :
2396
+ new(tree.Property)(`$${s.slice(2, -1)}`, index[k], fileInfo));
2397
+ }
2398
+ return name;
2399
+ }
2400
+ parserInput.restore();
2401
+ }
2402
+ }
2403
+ };
2404
+ };
2405
+ Parser.serializeVars = vars => {
2406
+ let s = '';
2407
+
2408
+ for (const name in vars) {
2409
+ if (Object.hasOwnProperty.call(vars, name)) {
2410
+ const value = vars[name];
2411
+ s += `${((name[0] === '@') ? '' : '@') + name}: ${value}${(String(value).slice(-1) === ';') ? '' : ';'}`;
2412
+ }
2413
+ }
2414
+
2415
+ return s;
2416
+ };
2417
+
2418
+ export default Parser;