less 2.0.8 → 2.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/lib/less/js/Makefile +8 -2
  2. data/lib/less/js/benchmark/less-benchmark.js +1 -1
  3. data/lib/less/js/bin/lessc +17 -11
  4. data/lib/less/js/build/amd.js +6 -0
  5. data/lib/less/js/dist/less-1.1.5.min.js +1 -8
  6. data/lib/less/js/dist/less-1.1.6.js +3004 -0
  7. data/lib/less/js/dist/less-1.1.6.min.js +9 -0
  8. data/lib/less/js/dist/less-1.2.0.js +3293 -0
  9. data/lib/less/js/dist/less-1.2.0.min.js +9 -0
  10. data/lib/less/js/dist/less-1.2.1.js +3318 -0
  11. data/lib/less/js/dist/less-1.2.1.min.js +9 -0
  12. data/lib/less/js/lib/less/browser.js +33 -28
  13. data/lib/less/js/lib/less/colors.js +151 -0
  14. data/lib/less/js/lib/less/cssmin.js +355 -0
  15. data/lib/less/js/lib/less/functions.js +49 -6
  16. data/lib/less/js/lib/less/index.js +21 -18
  17. data/lib/less/js/lib/less/parser.js +230 -92
  18. data/lib/less/js/lib/less/rhino.js +3 -1
  19. data/lib/less/js/lib/less/tree.js +6 -2
  20. data/lib/less/js/lib/less/tree/call.js +6 -3
  21. data/lib/less/js/lib/less/tree/condition.js +42 -0
  22. data/lib/less/js/lib/less/tree/dimension.js +15 -0
  23. data/lib/less/js/lib/less/tree/directive.js +8 -2
  24. data/lib/less/js/lib/less/tree/element.js +14 -2
  25. data/lib/less/js/lib/less/tree/expression.js +1 -1
  26. data/lib/less/js/lib/less/tree/import.js +13 -11
  27. data/lib/less/js/lib/less/tree/keyword.js +11 -1
  28. data/lib/less/js/lib/less/tree/mixin.js +31 -13
  29. data/lib/less/js/lib/less/tree/paren.js +16 -0
  30. data/lib/less/js/lib/less/tree/quoted.js +1 -1
  31. data/lib/less/js/lib/less/tree/rule.js +7 -3
  32. data/lib/less/js/lib/less/tree/ruleset.js +7 -4
  33. data/lib/less/js/lib/less/tree/selector.js +5 -0
  34. data/lib/less/js/lib/less/tree/variable.js +4 -2
  35. data/lib/less/js/package.json +1 -1
  36. data/lib/less/js/test/css/colors.css +10 -0
  37. data/lib/less/js/test/css/comments.css +9 -5
  38. data/lib/less/js/test/css/css-3.css +4 -2
  39. data/lib/less/js/test/css/css-escapes.css +1 -1
  40. data/lib/less/js/test/css/css.css +8 -4
  41. data/lib/less/js/test/css/functions.css +13 -0
  42. data/lib/less/js/test/css/import.css +10 -3
  43. data/lib/less/js/test/css/media.css +8 -2
  44. data/lib/less/js/test/css/mixins-args.css +5 -2
  45. data/lib/less/js/test/css/mixins-guards.css +58 -0
  46. data/lib/less/js/test/css/mixins-important.css +17 -0
  47. data/lib/less/js/test/css/mixins.css +2 -1
  48. data/lib/less/js/test/css/operations.css +3 -0
  49. data/lib/less/js/test/css/parens.css +1 -1
  50. data/lib/less/js/test/css/rulesets.css +6 -2
  51. data/lib/less/js/test/css/scope.css +6 -6
  52. data/lib/less/js/test/css/selectors.css +8 -4
  53. data/lib/less/js/test/css/strings.css +6 -4
  54. data/lib/less/js/test/css/variables.css +3 -0
  55. data/lib/less/js/test/css/whitespace.css +5 -3
  56. data/lib/less/js/test/less-test.js +1 -1
  57. data/lib/less/js/test/less/colors.less +13 -0
  58. data/lib/less/js/test/less/comments.less +8 -6
  59. data/lib/less/js/test/less/functions.less +15 -1
  60. data/lib/less/js/test/less/import.less +3 -1
  61. data/lib/less/js/test/less/import/import-test-e.less +2 -0
  62. data/lib/less/js/test/less/media.less +6 -0
  63. data/lib/less/js/test/less/mixins-args.less +6 -0
  64. data/lib/less/js/test/less/mixins-guards.less +94 -0
  65. data/lib/less/js/test/less/mixins-important.less +18 -0
  66. data/lib/less/js/test/less/operations.less +4 -0
  67. data/lib/less/js/test/less/strings.less +2 -0
  68. data/lib/less/js/test/less/variables.less +4 -0
  69. data/lib/less/version.rb +1 -1
  70. metadata +28 -12
@@ -144,20 +144,63 @@ tree.functions = {
144
144
  return new(tree.Quoted)('"' + str + '"', str);
145
145
  },
146
146
  round: function (n) {
147
+ return this._math('round', n);
148
+ },
149
+ ceil: function (n) {
150
+ return this._math('ceil', n);
151
+ },
152
+ floor: function (n) {
153
+ return this._math('floor', n);
154
+ },
155
+ _math: function (fn, n) {
147
156
  if (n instanceof tree.Dimension) {
148
- return new(tree.Dimension)(Math.round(number(n)), n.unit);
157
+ return new(tree.Dimension)(Math[fn](number(n)), n.unit);
149
158
  } else if (typeof(n) === 'number') {
150
- return Math.round(n);
159
+ return Math[fn](n);
151
160
  } else {
152
- throw {
153
- error: "RuntimeError",
154
- message: "math functions take numbers as parameters"
155
- };
161
+ throw { type: "Argument", message: "argument must be a number" };
156
162
  }
157
163
  },
158
164
  argb: function (color) {
159
165
  return new(tree.Anonymous)(color.toARGB());
160
166
 
167
+ },
168
+ percentage: function (n) {
169
+ return new(tree.Dimension)(n.value * 100, '%');
170
+ },
171
+ color: function (n) {
172
+ if (n instanceof tree.Quoted) {
173
+ return new(tree.Color)(n.value.slice(1));
174
+ } else {
175
+ throw { type: "Argument", message: "argument must be a string" };
176
+ }
177
+ },
178
+ iscolor: function (n) {
179
+ return this._isa(n, tree.Color);
180
+ },
181
+ isnumber: function (n) {
182
+ return this._isa(n, tree.Dimension);
183
+ },
184
+ isstring: function (n) {
185
+ return this._isa(n, tree.Quoted);
186
+ },
187
+ iskeyword: function (n) {
188
+ return this._isa(n, tree.Keyword);
189
+ },
190
+ isurl: function (n) {
191
+ return this._isa(n, tree.URL);
192
+ },
193
+ ispixel: function (n) {
194
+ return (n instanceof tree.Dimension) && n.unit === 'px' ? tree.True : tree.False;
195
+ },
196
+ ispercentage: function (n) {
197
+ return (n instanceof tree.Dimension) && n.unit === '%' ? tree.True : tree.False;
198
+ },
199
+ isem: function (n) {
200
+ return (n instanceof tree.Dimension) && n.unit === 'em' ? tree.True : tree.False;
201
+ },
202
+ _isa: function (n, Type) {
203
+ return (n instanceof Type) ? tree.True : tree.False;
161
204
  }
162
205
  };
163
206
 
@@ -3,7 +3,7 @@ var path = require('path'),
3
3
  fs = require('fs');
4
4
 
5
5
  var less = {
6
- version: [1, 1, 5],
6
+ version: [1, 2, 1],
7
7
  Parser: require('./parser').Parser,
8
8
  importer: require('./parser').importer,
9
9
  tree: require('./tree'),
@@ -43,7 +43,9 @@ var less = {
43
43
 
44
44
  if (options.silent) { return }
45
45
 
46
- if (!ctx.index) {
46
+ if (ctx.stack) { return sys.error(stylize(ctx.stack, 'red')) }
47
+
48
+ if (!ctx.hasOwnProperty('index')) {
47
49
  return sys.error(ctx.stack || ctx.message);
48
50
  }
49
51
 
@@ -51,17 +53,20 @@ var less = {
51
53
  error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey'));
52
54
  }
53
55
 
54
- error.push(ctx.line + ' ' + extract[1].slice(0, ctx.column)
55
- + stylize(stylize(extract[1][ctx.column], 'bold')
56
- + extract[1].slice(ctx.column + 1), 'yellow'));
56
+ if (extract[1]) {
57
+ error.push(ctx.line + ' ' + extract[1].slice(0, ctx.column)
58
+ + stylize(stylize(stylize(extract[1][ctx.column], 'bold')
59
+ + extract[1].slice(ctx.column + 1), 'red'), 'inverse'));
60
+ }
57
61
 
58
62
  if (typeof(extract[2]) === 'string') {
59
63
  error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
60
64
  }
61
65
  error = error.join('\n') + '\033[0m\n';
62
66
 
63
- message += stylize(ctx.message, 'red');
64
- ctx.filename && (message += stylize(' in ', 'red') + ctx.filename);
67
+ message += stylize(ctx.type + 'Error: ' + ctx.message, 'red');
68
+ ctx.filename && (message += stylize(' in ', 'red') + ctx.filename +
69
+ stylize(':' + ctx.line + ':' + ctx.column, 'grey'));
65
70
 
66
71
  sys.error(message, error);
67
72
 
@@ -69,16 +74,15 @@ var less = {
69
74
  sys.error(stylize('from ', 'red') + (ctx.filename || ''));
70
75
  sys.error(stylize(ctx.callLine, 'grey') + ' ' + ctx.callExtract);
71
76
  }
72
- if (ctx.stack) { sys.error(stylize(ctx.stack, 'red')) }
73
77
  }
74
78
  };
75
79
 
76
- ['color', 'directive', 'operation', 'dimension',
77
- 'keyword', 'variable', 'ruleset', 'element',
78
- 'selector', 'quoted', 'expression', 'rule',
79
- 'call', 'url', 'alpha', 'import',
80
- 'mixin', 'comment', 'anonymous', 'value',
81
- 'javascript', 'assignment'
80
+ ['color', 'directive', 'operation', 'dimension',
81
+ 'keyword', 'variable', 'ruleset', 'element',
82
+ 'selector', 'quoted', 'expression', 'rule',
83
+ 'call', 'url', 'alpha', 'import',
84
+ 'mixin', 'comment', 'anonymous', 'value',
85
+ 'javascript', 'assignment', 'condition', 'paren'
82
86
  ].forEach(function (n) {
83
87
  require('./tree/' + n);
84
88
  });
@@ -106,17 +110,16 @@ less.Parser.importer = function (file, paths, callback) {
106
110
  paths: [path.dirname(pathname)].concat(paths),
107
111
  filename: pathname
108
112
  }).parse(data, function (e, root) {
109
- if (e) less.writeError(e);
110
- callback(root);
113
+ callback(e, root, data);
111
114
  });
112
115
  });
113
116
  } else {
114
- sys.error("file '" + file + "' wasn't found.\n");
115
- process.exit(1);
117
+ callback({ type: 'File', message: "'" + file + "' wasn't found.\n" });
116
118
  }
117
119
  }
118
120
 
119
121
  require('./functions');
122
+ require('./colors');
120
123
 
121
124
  for (var k in less) { exports[k] = less[k] }
122
125
 
@@ -3,7 +3,8 @@ var less, tree;
3
3
  if (typeof environment === "object" && ({}).toString.call(environment) === "[object Environment]") {
4
4
  // Rhino
5
5
  // Details on how to detect Rhino: https://github.com/ringo/ringojs/issues/88
6
- less = {};
6
+ if (typeof(window) === 'undefined') { less = {} }
7
+ else { less = window.less = {} }
7
8
  tree = less.tree = {};
8
9
  less.mode = 'rhino';
9
10
  } else if (typeof(window) === 'undefined') {
@@ -72,7 +73,9 @@ less.Parser = function Parser(env) {
72
73
  paths: env && env.paths || [], // Search paths, when importing
73
74
  queue: [], // Files which haven't been imported yet
74
75
  files: {}, // Holds the imported parse trees
76
+ contents: {}, // Holds the imported file contents
75
77
  mime: env && env.mime, // MIME type of .less files
78
+ error: null, // Error in parsing/evaluating an import
76
79
  push: function (path, callback) {
77
80
  var that = this;
78
81
  this.queue.push(path);
@@ -80,11 +83,13 @@ less.Parser = function Parser(env) {
80
83
  //
81
84
  // Import a file asynchronously
82
85
  //
83
- less.Parser.importer(path, this.paths, function (root) {
86
+ less.Parser.importer(path, this.paths, function (e, root, contents) {
84
87
  that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue
85
88
  that.files[path] = root; // Store the root
89
+ that.contents[path] = contents;
86
90
 
87
- callback(root);
91
+ if (e && !that.error) { that.error = e }
92
+ callback(e, root);
88
93
 
89
94
  if (that.queue.length === 0) { finish() } // Call `finish` if we're done importing
90
95
  }, env);
@@ -158,6 +163,20 @@ less.Parser = function Parser(env) {
158
163
  }
159
164
  }
160
165
 
166
+ function expect(arg, msg) {
167
+ var result = $(arg);
168
+ if (! result) {
169
+ error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'"
170
+ : "unexpected token"));
171
+ } else {
172
+ return result;
173
+ }
174
+ }
175
+
176
+ function error(msg, type) {
177
+ throw { index: i, type: type || 'Syntax', message: msg };
178
+ }
179
+
161
180
  // Same as $(), but don't change the state of the parser,
162
181
  // just return the match.
163
182
  function peek(tok) {
@@ -172,6 +191,46 @@ less.Parser = function Parser(env) {
172
191
  }
173
192
  }
174
193
 
194
+ function getInput(e, env) {
195
+ if (e.filename && env.filename && (e.filename !== env.filename)) {
196
+ return parser.imports.contents[e.filename];
197
+ } else {
198
+ return input;
199
+ }
200
+ }
201
+
202
+ function getLocation(index, input) {
203
+ for (var n = index, column = -1;
204
+ n >= 0 && input.charAt(n) !== '\n';
205
+ n--) { column++ }
206
+
207
+ return { line: typeof(index) === 'number' ? (input.slice(0, index).match(/\n/g) || "").length : null,
208
+ column: column };
209
+ }
210
+
211
+ function LessError(e, env) {
212
+ var input = getInput(e, env),
213
+ loc = getLocation(e.index, input),
214
+ line = loc.line,
215
+ col = loc.column,
216
+ lines = input.split('\n');
217
+
218
+ this.type = e.type || 'Syntax';
219
+ this.message = e.message;
220
+ this.filename = e.filename || env.filename;
221
+ this.index = e.index;
222
+ this.line = typeof(line) === 'number' ? line + 1 : null;
223
+ this.callLine = e.call && (getLocation(e.call, input) + 1);
224
+ this.callExtract = lines[getLocation(e.call, input)];
225
+ this.stack = e.stack;
226
+ this.column = col;
227
+ this.extract = [
228
+ lines[line - 1],
229
+ lines[line],
230
+ lines[line + 1]
231
+ ];
232
+ }
233
+
175
234
  this.env = env = env || {};
176
235
 
177
236
  // The optimization level dictates the thoroughness of the parser,
@@ -196,19 +255,18 @@ less.Parser = function Parser(env) {
196
255
  var root, start, end, zone, line, lines, buff = [], c, error = null;
197
256
 
198
257
  i = j = current = furthest = 0;
199
- chunks = [];
200
258
  input = str.replace(/\r\n/g, '\n');
201
259
 
202
260
  // Split the input into chunks.
203
261
  chunks = (function (chunks) {
204
262
  var j = 0,
205
- skip = /[^"'`\{\}\/\(\)]+/g,
263
+ skip = /[^"'`\{\}\/\(\)\\]+/g,
206
264
  comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
265
+ string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`\\\r\n]|\\.)*)`/g,
207
266
  level = 0,
208
267
  match,
209
268
  chunk = chunks[0],
210
- inParam,
211
- inString;
269
+ inParam;
212
270
 
213
271
  for (var i = 0, c, cc; i < input.length; i++) {
214
272
  skip.lastIndex = i;
@@ -219,9 +277,17 @@ less.Parser = function Parser(env) {
219
277
  }
220
278
  }
221
279
  c = input.charAt(i);
222
- comment.lastIndex = i;
280
+ comment.lastIndex = string.lastIndex = i;
281
+
282
+ if (match = string.exec(input)) {
283
+ if (match.index === i) {
284
+ i += match[0].length;
285
+ chunk.push(match[0]);
286
+ c = input.charAt(i);
287
+ }
288
+ }
223
289
 
224
- if (!inString && !inParam && c === '/') {
290
+ if (!inParam && c === '/') {
225
291
  cc = input.charAt(i + 1);
226
292
  if (cc === '/' || cc === '*') {
227
293
  if (match = comment.exec(input)) {
@@ -234,34 +300,21 @@ less.Parser = function Parser(env) {
234
300
  }
235
301
  }
236
302
 
237
- if (c === '{' && !inString && !inParam) { level ++;
238
- chunk.push(c);
239
- } else if (c === '}' && !inString && !inParam) { level --;
240
- chunk.push(c);
241
- chunks[++j] = chunk = [];
242
- } else if (c === '(' && !inString && !inParam) {
243
- chunk.push(c);
244
- inParam = true;
245
- } else if (c === ')' && !inString && inParam) {
246
- chunk.push(c);
247
- inParam = false;
248
- } else {
249
- if (c === '"' || c === "'" || c === '`') {
250
- if (! inString) {
251
- inString = c;
252
- } else {
253
- inString = inString === c ? false : inString;
254
- }
255
- }
256
- chunk.push(c);
303
+ switch (c) {
304
+ case '{': if (! inParam) { level ++; chunk.push(c); break }
305
+ case '}': if (! inParam) { level --; chunk.push(c); chunks[++j] = chunk = []; break }
306
+ case '(': if (! inParam) { inParam = true; chunk.push(c); break }
307
+ case ')': if ( inParam) { inParam = false; chunk.push(c); break }
308
+ default: chunk.push(c);
257
309
  }
258
310
  }
259
311
  if (level > 0) {
260
- throw {
261
- type: 'Syntax',
262
- message: "Missing closing `}`",
312
+ return callback(new(LessError)({
313
+ index: i,
314
+ type: 'Parse',
315
+ message: "missing closing `}`",
263
316
  filename: env.filename
264
- };
317
+ }, env));
265
318
  }
266
319
 
267
320
  return chunks.map(function (c) { return c.join('') });;
@@ -271,14 +324,18 @@ less.Parser = function Parser(env) {
271
324
  // The whole syntax tree is held under a Ruleset node,
272
325
  // with the `root` property set to true, so no `{}` are
273
326
  // output. The callback is called when the input is parsed.
274
- root = new(tree.Ruleset)([], $(this.parsers.primary));
275
- root.root = true;
327
+ try {
328
+ root = new(tree.Ruleset)([], $(this.parsers.primary));
329
+ root.root = true;
330
+ } catch (e) {
331
+ return callback(new(LessError)(e, env));
332
+ }
276
333
 
277
334
  root.toCSS = (function (evaluate) {
278
335
  var line, lines, column;
279
336
 
280
337
  return function (options, variables) {
281
- var frames = [];
338
+ var frames = [], importError;
282
339
 
283
340
  options = options || {};
284
341
  //
@@ -313,39 +370,21 @@ less.Parser = function Parser(env) {
313
370
  var css = evaluate.call(this, { frames: frames })
314
371
  .toCSS([], { compress: options.compress || false });
315
372
  } catch (e) {
316
- lines = input.split('\n');
317
- line = getLine(e.index);
318
-
319
- for (var n = e.index, column = -1;
320
- n >= 0 && input.charAt(n) !== '\n';
321
- n--) { column++ }
322
-
323
- throw {
324
- type: e.type,
325
- message: e.message,
326
- filename: env.filename,
327
- index: e.index,
328
- line: typeof(line) === 'number' ? line + 1 : null,
329
- callLine: e.call && (getLine(e.call) + 1),
330
- callExtract: lines[getLine(e.call)],
331
- stack: e.stack,
332
- column: column,
333
- extract: [
334
- lines[line - 1],
335
- lines[line],
336
- lines[line + 1]
337
- ]
338
- };
373
+ throw new(LessError)(e, env);
374
+ }
375
+
376
+ if ((importError = parser.imports.error)) { // Check if there was an error during importing
377
+ if (importError instanceof LessError) throw importError;
378
+ else throw new(LessError)(importError, env);
339
379
  }
340
- if (options.compress) {
380
+
381
+ if (options.yuicompress && less.mode === 'node') {
382
+ return require('./cssmin').compressor.cssmin(css);
383
+ } else if (options.compress) {
341
384
  return css.replace(/(\s)+/g, "$1");
342
385
  } else {
343
386
  return css;
344
387
  }
345
-
346
- function getLine(index) {
347
- return index ? (input.slice(0, index).match(/\n/g) || "").length : null;
348
- }
349
388
  };
350
389
  })(root.eval);
351
390
 
@@ -365,7 +404,7 @@ less.Parser = function Parser(env) {
365
404
  for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ }
366
405
 
367
406
  error = {
368
- name: "ParseError",
407
+ type: "Parse",
369
408
  message: "Syntax Error on line " + line,
370
409
  index: i,
371
410
  filename: env.filename,
@@ -486,7 +525,15 @@ less.Parser = function Parser(env) {
486
525
  //
487
526
  keyword: function () {
488
527
  var k;
489
- if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) { return new(tree.Keyword)(k) }
528
+
529
+ if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) {
530
+ if (tree.colors.hasOwnProperty(k)) {
531
+ // detect named color
532
+ return new(tree.Color)(tree.colors[k].slice(1));
533
+ } else {
534
+ return new(tree.Keyword)(k);
535
+ }
536
+ }
490
537
  },
491
538
 
492
539
  //
@@ -517,7 +564,7 @@ less.Parser = function Parser(env) {
517
564
 
518
565
  if (! $(')')) return;
519
566
 
520
- if (name) { return new(tree.Call)(name, args, index) }
567
+ if (name) { return new(tree.Call)(name, args, index, env.filename) }
521
568
  },
522
569
  arguments: function () {
523
570
  var args = [], arg;
@@ -560,7 +607,8 @@ less.Parser = function Parser(env) {
560
607
  if (input.charAt(i) !== 'u' || !$(/^url\(/)) return;
561
608
  value = $(this.entities.quoted) || $(this.entities.variable) ||
562
609
  $(this.entities.dataURI) || $(/^[-\w%@$\/.&=:;#+?~]+/) || "";
563
- if (! $(')')) throw new(Error)("missing closing ) for url()");
610
+
611
+ expect(')');
564
612
 
565
613
  return new(tree.URL)((value.value || value.data || value instanceof tree.Variable)
566
614
  ? value : new(tree.Anonymous)(value), imports.paths);
@@ -592,7 +640,7 @@ less.Parser = function Parser(env) {
592
640
  var name, index = i;
593
641
 
594
642
  if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
595
- return new(tree.Variable)(name, index);
643
+ return new(tree.Variable)(name, index, env.filename);
596
644
  }
597
645
  },
598
646
 
@@ -620,7 +668,7 @@ less.Parser = function Parser(env) {
620
668
  var value, c = input.charCodeAt(i);
621
669
  if ((c > 57 || c < 45) || c === 47) return;
622
670
 
623
- if (value = $(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) {
671
+ if (value = $(/^(-?\d*\.?\d+)(px|%|em|rem|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) {
624
672
  return new(tree.Dimension)(value[1], value[2]);
625
673
  }
626
674
  },
@@ -688,7 +736,7 @@ less.Parser = function Parser(env) {
688
736
  // selector for now.
689
737
  //
690
738
  call: function () {
691
- var elements = [], e, c, args, index = i, s = input.charAt(i);
739
+ var elements = [], e, c, args, index = i, s = input.charAt(i), important = false;
692
740
 
693
741
  if (s !== '.' && s !== '#') { return }
694
742
 
@@ -698,8 +746,12 @@ less.Parser = function Parser(env) {
698
746
  }
699
747
  $('(') && (args = $(this.entities.arguments)) && $(')');
700
748
 
749
+ if ($(this.important)) {
750
+ important = true;
751
+ }
752
+
701
753
  if (elements.length > 0 && ($(';') || peek('}'))) {
702
- return new(tree.mixin.Call)(elements, args, index);
754
+ return new(tree.mixin.Call)(elements, args, index, env.filename, important);
703
755
  }
704
756
  },
705
757
 
@@ -723,11 +775,12 @@ less.Parser = function Parser(env) {
723
775
  // the `{...}` block.
724
776
  //
725
777
  definition: function () {
726
- var name, params = [], match, ruleset, param, value;
727
-
778
+ var name, params = [], match, ruleset, param, value, cond;
728
779
  if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') ||
729
780
  peek(/^[^{]*(;|})/)) return;
730
781
 
782
+ save();
783
+
731
784
  if (match = $(/^([#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+)\s*\(/)) {
732
785
  name = match[1];
733
786
 
@@ -736,11 +789,8 @@ less.Parser = function Parser(env) {
736
789
  // Variable
737
790
  if (param instanceof tree.Variable) {
738
791
  if ($(':')) {
739
- if (value = $(this.expression)) {
740
- params.push({ name: param.name, value: value });
741
- } else {
742
- throw new(Error)("Expected value");
743
- }
792
+ value = expect(this.expression, 'expected expression');
793
+ params.push({ name: param.name, value: value });
744
794
  } else {
745
795
  params.push({ name: param.name });
746
796
  }
@@ -749,12 +799,18 @@ less.Parser = function Parser(env) {
749
799
  }
750
800
  if (! $(',')) { break }
751
801
  }
752
- if (! $(')')) throw new(Error)("Expected )");
802
+ expect(')');
803
+
804
+ if ($(/^when/)) { // Guard
805
+ cond = expect(this.conditions, 'expected condition');
806
+ }
753
807
 
754
808
  ruleset = $(this.block);
755
809
 
756
810
  if (ruleset) {
757
- return new(tree.mixin.Definition)(name, params, ruleset);
811
+ return new(tree.mixin.Definition)(name, params, ruleset, cond);
812
+ } else {
813
+ restore();
758
814
  }
759
815
  }
760
816
  }
@@ -789,7 +845,7 @@ less.Parser = function Parser(env) {
789
845
 
790
846
  if (! $(/^\(opacity=/i)) return;
791
847
  if (value = $(/^\d+/) || $(this.entities.variable)) {
792
- if (! $(')')) throw new(Error)("missing closing ) for alpha()");
848
+ expect(')');
793
849
  return new(tree.Alpha)(value);
794
850
  }
795
851
  },
@@ -807,12 +863,16 @@ less.Parser = function Parser(env) {
807
863
  // and an element name, such as a tag a class, or `*`.
808
864
  //
809
865
  element: function () {
810
- var e, t, c;
866
+ var e, t, c, v;
811
867
 
812
868
  c = $(this.combinator);
813
869
  e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/) ||
814
870
  $('*') || $(this.attribute) || $(/^\([^)@]+\)/);
815
871
 
872
+ if (! e) {
873
+ $('(') && (v = $(this.entities.variable)) && $(')') && (e = new(tree.Paren)(v));
874
+ }
875
+
816
876
  if (e) { return new(tree.Element)(c, e, i) }
817
877
 
818
878
  if (c.value && c.value.charAt(0) === '&') {
@@ -965,11 +1025,60 @@ less.Parser = function Parser(env) {
965
1025
  // stored in `import`, which we pass to the Import constructor.
966
1026
  //
967
1027
  "import": function () {
968
- var path;
1028
+ var path, features, index = i;
969
1029
  if ($(/^@import\s+/) &&
970
- (path = $(this.entities.quoted) || $(this.entities.url)) &&
971
- $(';')) {
972
- return new(tree.Import)(path, imports);
1030
+ (path = $(this.entities.quoted) || $(this.entities.url))) {
1031
+ features = $(this.mediaFeatures);
1032
+ if ($(';')) {
1033
+ return new(tree.Import)(path, imports, features, index);
1034
+ }
1035
+ }
1036
+ },
1037
+
1038
+ mediaFeature: function () {
1039
+ var nodes = [];
1040
+
1041
+ do {
1042
+ if (e = $(this.entities.keyword)) {
1043
+ nodes.push(e);
1044
+ } else if ($('(')) {
1045
+ p = $(this.property);
1046
+ e = $(this.entity);
1047
+ if ($(')')) {
1048
+ if (p && e) {
1049
+ nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, i, true)));
1050
+ } else if (e) {
1051
+ nodes.push(new(tree.Paren)(e));
1052
+ } else {
1053
+ return null;
1054
+ }
1055
+ } else { return null }
1056
+ }
1057
+ } while (e);
1058
+
1059
+ if (nodes.length > 0) {
1060
+ return new(tree.Expression)(nodes);
1061
+ }
1062
+ },
1063
+
1064
+ mediaFeatures: function () {
1065
+ var f, features = [];
1066
+ while (f = $(this.mediaFeature)) {
1067
+ features.push(f);
1068
+ if (! $(',')) { break }
1069
+ }
1070
+ return features.length > 0 ? features : null;
1071
+ },
1072
+
1073
+ media: function () {
1074
+ var features;
1075
+
1076
+ if ($(/^@media/)) {
1077
+ features = $(this.mediaFeatures);
1078
+
1079
+ if (rules = $(this.block)) {
1080
+ return new(tree.Directive)('@media', rules, features);
1081
+ }
973
1082
  }
974
1083
  },
975
1084
 
@@ -979,13 +1088,13 @@ less.Parser = function Parser(env) {
979
1088
  // @charset "utf-8";
980
1089
  //
981
1090
  directive: function () {
982
- var name, value, rules, types;
1091
+ var name, value, rules, types, e, nodes;
983
1092
 
984
1093
  if (input.charAt(i) !== '@') return;
985
1094
 
986
- if (value = $(this['import'])) {
1095
+ if (value = $(this['import']) || $(this.media)) {
987
1096
  return value;
988
- } else if (name = $(/^@media|@page/) || $(/^@(?:-webkit-|-moz-|-o-)[a-z0-9-]+/) || $('keyframes')) {
1097
+ } else if (name = $(/^@page|@keyframes/) || $(/^@(?:-webkit-|-moz-|-o-|-ms-)[a-z0-9-]+/)) {
989
1098
  types = ($(/^[^{]+/) || '').trim();
990
1099
  if (rules = $(this.block)) {
991
1100
  return new(tree.Directive)(name + " " + types, rules);
@@ -1052,7 +1161,7 @@ less.Parser = function Parser(env) {
1052
1161
  multiplication: function () {
1053
1162
  var m, a, op, operation;
1054
1163
  if (m = $(this.operand)) {
1055
- while ((op = ($('/') || $('*'))) && (a = $(this.operand))) {
1164
+ while (!peek(/^\/\*/) && (op = ($('/') || $('*'))) && (a = $(this.operand))) {
1056
1165
  operation = new(tree.Operation)(op, [operation || m, a]);
1057
1166
  }
1058
1167
  return operation || m;
@@ -1068,6 +1177,35 @@ less.Parser = function Parser(env) {
1068
1177
  return operation || m;
1069
1178
  }
1070
1179
  },
1180
+ conditions: function () {
1181
+ var a, b, index = i, condition;
1182
+
1183
+ if (a = $(this.condition)) {
1184
+ while ($(',') && (b = $(this.condition))) {
1185
+ condition = new(tree.Condition)('or', condition || a, b, index);
1186
+ }
1187
+ return condition || a;
1188
+ }
1189
+ },
1190
+ condition: function () {
1191
+ var a, b, c, op, index = i, negate = false;
1192
+
1193
+ if ($(/^not/)) { negate = true }
1194
+ expect('(');
1195
+ if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1196
+ if (op = $(/^(?:>=|=<|[<=>])/)) {
1197
+ if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1198
+ c = new(tree.Condition)(op, a, b, index, negate);
1199
+ } else {
1200
+ error('expected expression');
1201
+ }
1202
+ } else {
1203
+ c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate);
1204
+ }
1205
+ expect(')');
1206
+ return $(/^and/) ? new(tree.Condition)('and', c, $(this.condition)) : c;
1207
+ }
1208
+ },
1071
1209
 
1072
1210
  //
1073
1211
  // An operand is anything that can be part of an operation,
@@ -1117,7 +1255,7 @@ if (less.mode === 'browser' || less.mode === 'rhino') {
1117
1255
  // Used by `@import` directives
1118
1256
  //
1119
1257
  less.Parser.importer = function (path, paths, callback, env) {
1120
- if (path.charAt(0) !== '/' && paths.length > 0) {
1258
+ if (!/^([a-z]+:)?\//.test(path) && paths.length > 0) {
1121
1259
  path = paths[0] + path;
1122
1260
  }
1123
1261
  // We pass `true` as 3rd argument, to force the reload of the import.