Skip to content
This repository

Update LESS to v1.3.0 #40

Closed
wants to merge 2 commits into from
Vincent Costel
Costo commented March 17, 2012

Tested with Twitter Bootstrap v2.0.2, works great!

Vincent Costel Update LESS to v1.3.0
Tested with Twitter Bootstrap v2.0.2, works great!
ed2f6b2
Vincent Costel
Costo commented March 17, 2012

If you need this right now, you can replace the content of C:\Program Files (x86)\SimpLESS\Resources\js\less.js
with this version https://github.com/Costo/SimpLESS/blob/patch-1/Resources/js/less.js

Works on my machine!
Backup the file!

Vincent Costel Costo referenced this pull request March 17, 2012
Closed

LESS 1.3.0 ? #39

Vincent Costel
Costo commented March 17, 2012

Ok there is actually a problem with Twitter Bootstrap.
In variables.less, these two variables:

// Sprite icons path
// -------------------------
@iconSpritePath:          "../img/glyphicons-halflings.png";
@iconWhiteSpritePath:     "../img/glyphicons-halflings-white.png";

When used in sprites.less:

.icon-white {
  background-image: url('@{iconWhiteSpritePath}');
}

The resulting CSS looks like this:

.icon-white {
  background-image: url('app://com.wearekiss.simpless.open/app://com.wearekiss.simpless.open/app://com.wearekiss.simpless.open/app://com.wearekiss.simpless.open/../img/glyphicons-halflings-white.png');
}
Simon Perdrisat

Did you try with the last version of bootstrap? I think they was a bug like that on their side.

Vincent Costel
Costo commented March 19, 2012

Yes I tested with Twitter Bootstrap v2.0.2.

Morten Bengtson

It seems to work if you remove the if-statement in less.js right after the comment "// Add the base path if the URL is relative and we are in the browser". Will that cause any problems?

if (typeof(window) !== 'undefined' && !/^(?:https?:\/\/|file:\/\/|data:|\/)/.test(val.value) && paths.length > 0) {
val.value = paths[0] + (val.value.charAt(0) === '/' ? val.value.slice(1) : val.value);
}

Simon Perdrisat

I will not hack the lib by removing this. But instead calling less.js properly. How less.js is used now? Why we don't use lessc?

We can also make a "delete windows;" right before calling this script.

delete window; doesn't seem to work. It is the root object, I don't think it can be deleted.
I think the simplest option is to comment out those lines.

Simon Perdrisat

How I get it now.. SimpleLess use Titanium Desktop so we really are in a browser.

So I agree removing those lines are certainly the best we can do.

Christian Engel
Owner

less.js version 1.3 will be included within our next SimpLESS update which will be released somewhere around monday next week.
There will also be be an easy way for users to update the less.js in SimpLESS without having to wait again until we provide an update by ourselves.

Timo

still no new version :(

David Thalmann
boast commented April 17, 2012

still no new version :( (+one week)

Christian Engel
Owner

Yes, sorry for that. We have currently all our coding power bound to some customer projects and cant spend any time on free stuff. Because of that, the SimpLESS update will have to wait.

Notheless I have created a quick fix a few days ago to help you guys out with a recent version of the less compiler at least (i mentioned it at twitter, you guys should follow us there -> @wearekiss ).

Download http://wearekiss.com/less.js and put it in the application under resources/lib/js/less.js (replace the old file).
After a restart of the SimpLESS app, you have less compiler 1.3 available.

Sorry guys for getting this late with the app update, maybe you are familiar with our problems. I currently wish that we have 26 hours a day ;)

Maxim Moneta

Great thanks! It works :)

Emanuele Ingrosso
ingro commented April 27, 2012

Hello, I don't know if it is caused by the new less.js but the annoying url bug (which insert "app://com.wearekiss.simpless.open/" before the proper url) is back :\

Damon Poole

Works perfectly, thanks! :D

Chris Geoghegan
cghobbs commented May 11, 2012

Any updates on this?

mapb1990
background-image:url("app://com.wearekiss.simpless.open/app://com.wearekiss.simpless.open/../../less/app://com.wearekiss.simpless.open/app://com.wearekiss.simpless.open/../../less/../images/glyphicons-halflings.png");

Any updates on this?

Jake Worrell

I can confirm that this works on both my MacBook Pro and my iMac at work. My Colleague has also confirmed this as working on Windows 7.

slaur
slaur commented June 16, 2012

@ingro, @mapb1990
I confirm URL bug was solved for me using the method provided by @Costo : b7c44bc

Emanuele Ingrosso
ingro commented June 16, 2012

Thank you for the info!

Christian Engel
Owner

Fixed in 1.4

Christian Engel Paratron closed this June 25, 2012
Andrew Smith

This is not fixed as I am still getting backrgound: url(''); with some relative path, I did as @slaur pointed out and that worked for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 2 unique commits by 1 author.

Mar 17, 2012
Vincent Costel Update LESS to v1.3.0
Tested with Twitter Bootstrap v2.0.2, works great!
ed2f6b2
Mar 19, 2012
Vincent Costel Avoid transforming relative paths to absolute paths b7c44bc
This page is out of date. Refresh to see the latest.

Showing 1 changed file with 642 additions and 172 deletions. Show diff stats Hide diff stats

  1. 814  Resources/js/less.js
814  Resources/js/less.js
... ...
@@ -1,5 +1,5 @@
1 1
 //
2  
-// LESS - Leaner CSS v1.1.6
  2
+// LESS - Leaner CSS v1.3.0
3 3
 // http://lesscss.org
4 4
 // 
5 5
 // Copyright (c) 2009-2011, Alexis Sellier
@@ -13,6 +13,12 @@ function require(arg) {
13 13
     return window.less[arg.split('/')[1]];
14 14
 };
15 15
 
  16
+// amd.js
  17
+//
  18
+// Define Less as an AMD module.
  19
+if (typeof define === "function" && define.amd) {
  20
+    define("less", [], function () { return less; } );
  21
+}
16 22
 
17 23
 // ecma-5.js
18 24
 //
@@ -138,7 +144,8 @@ var less, tree;
138 144
 if (typeof environment === "object" && ({}).toString.call(environment) === "[object Environment]") {
139 145
     // Rhino
140 146
     // Details on how to detect Rhino: https://github.com/ringo/ringojs/issues/88
141  
-    less = {};
  147
+    if (typeof(window) === 'undefined') { less = {} }
  148
+    else                                { less = window.less = {} }
142 149
     tree = less.tree = {};
143 150
     less.mode = 'rhino';
144 151
 } else if (typeof(window) === 'undefined') {
@@ -207,7 +214,9 @@ less.Parser = function Parser(env) {
207 214
         paths: env && env.paths || [],  // Search paths, when importing
208 215
         queue: [],                      // Files which haven't been imported yet
209 216
         files: {},                      // Holds the imported parse trees
  217
+        contents: {},                   // Holds the imported file contents
210 218
         mime:  env && env.mime,         // MIME type of .less files
  219
+        error: null,                    // Error in parsing/evaluating an import
211 220
         push: function (path, callback) {
212 221
             var that = this;
213 222
             this.queue.push(path);
@@ -215,11 +224,13 @@ less.Parser = function Parser(env) {
215 224
             //
216 225
             // Import a file asynchronously
217 226
             //
218  
-            less.Parser.importer(path, this.paths, function (root) {
  227
+            less.Parser.importer(path, this.paths, function (e, root, contents) {
219 228
                 that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue
220 229
                 that.files[path] = root;                        // Store the root
  230
+                that.contents[path] = contents;
221 231
 
222  
-                callback(root);
  232
+                if (e && !that.error) { that.error = e }
  233
+                callback(e, root);
223 234
 
224 235
                 if (that.queue.length === 0) { finish() }       // Call `finish` if we're done importing
225 236
             }, env);
@@ -293,6 +304,20 @@ less.Parser = function Parser(env) {
293 304
         }
294 305
     }
295 306
 
  307
+    function expect(arg, msg) {
  308
+        var result = $(arg);
  309
+        if (! result) {
  310
+            error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'"
  311
+                                                   : "unexpected token"));
  312
+        } else {
  313
+            return result;
  314
+        }
  315
+    }
  316
+
  317
+    function error(msg, type) {
  318
+        throw { index: i, type: type || 'Syntax', message: msg };
  319
+    }
  320
+
296 321
     // Same as $(), but don't change the state of the parser,
297 322
     // just return the match.
298 323
     function peek(tok) {
@@ -307,6 +332,54 @@ less.Parser = function Parser(env) {
307 332
         }
308 333
     }
309 334
 
  335
+    function basename(pathname) {
  336
+        if (less.mode === 'node') {
  337
+            return require('path').basename(pathname);
  338
+        } else {
  339
+            return pathname.match(/[^\/]+$/)[0];
  340
+        }
  341
+    }
  342
+
  343
+    function getInput(e, env) {
  344
+        if (e.filename && env.filename && (e.filename !== env.filename)) {
  345
+            return parser.imports.contents[basename(e.filename)];
  346
+        } else {
  347
+            return input;
  348
+        }
  349
+    }
  350
+
  351
+    function getLocation(index, input) {
  352
+        for (var n = index, column = -1;
  353
+                 n >= 0 && input.charAt(n) !== '\n';
  354
+                 n--) { column++ }
  355
+
  356
+        return { line:   typeof(index) === 'number' ? (input.slice(0, index).match(/\n/g) || "").length : null,
  357
+                 column: column };
  358
+    }
  359
+
  360
+    function LessError(e, env) {
  361
+        var input = getInput(e, env),
  362
+            loc = getLocation(e.index, input),
  363
+            line = loc.line,
  364
+            col  = loc.column,
  365
+            lines = input.split('\n');
  366
+
  367
+        this.type = e.type || 'Syntax';
  368
+        this.message = e.message;
  369
+        this.filename = e.filename || env.filename;
  370
+        this.index = e.index;
  371
+        this.line = typeof(line) === 'number' ? line + 1 : null;
  372
+        this.callLine = e.call && (getLocation(e.call, input).line + 1);
  373
+        this.callExtract = lines[getLocation(e.call, input).line];
  374
+        this.stack = e.stack;
  375
+        this.column = col;
  376
+        this.extract = [
  377
+            lines[line - 1],
  378
+            lines[line],
  379
+            lines[line + 1]
  380
+        ];
  381
+    }
  382
+
310 383
     this.env = env = env || {};
311 384
 
312 385
     // The optimization level dictates the thoroughness of the parser,
@@ -331,19 +404,18 @@ less.Parser = function Parser(env) {
331 404
             var root, start, end, zone, line, lines, buff = [], c, error = null;
332 405
 
333 406
             i = j = current = furthest = 0;
334  
-            chunks = [];
335 407
             input = str.replace(/\r\n/g, '\n');
336 408
 
337 409
             // Split the input into chunks.
338 410
             chunks = (function (chunks) {
339 411
                 var j = 0,
340  
-                    skip = /[^"'`\{\}\/\(\)]+/g,
  412
+                    skip = /[^"'`\{\}\/\(\)\\]+/g,
341 413
                     comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
  414
+                    string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`\\\r\n]|\\.)*)`/g,
342 415
                     level = 0,
343 416
                     match,
344 417
                     chunk = chunks[0],
345  
-                    inParam,
346  
-                    inString;
  418
+                    inParam;
347 419
 
348 420
                 for (var i = 0, c, cc; i < input.length; i++) {
349 421
                     skip.lastIndex = i;
@@ -354,9 +426,17 @@ less.Parser = function Parser(env) {
354 426
                         }
355 427
                     }
356 428
                     c = input.charAt(i);
357  
-                    comment.lastIndex = i;
  429
+                    comment.lastIndex = string.lastIndex = i;
  430
+
  431
+                    if (match = string.exec(input)) {
  432
+                        if (match.index === i) {
  433
+                            i += match[0].length;
  434
+                            chunk.push(match[0]);
  435
+                            c = input.charAt(i);
  436
+                        }
  437
+                    }
358 438
 
359  
-                    if (!inString && !inParam && c === '/') {
  439
+                    if (!inParam && c === '/') {
360 440
                         cc = input.charAt(i + 1);
361 441
                         if (cc === '/' || cc === '*') {
362 442
                             if (match = comment.exec(input)) {
@@ -369,51 +449,46 @@ less.Parser = function Parser(env) {
369 449
                         }
370 450
                     }
371 451
 
372  
-                    if        (c === '{' && !inString && !inParam) { level ++;
373  
-                        chunk.push(c);
374  
-                    } else if (c === '}' && !inString && !inParam) { level --;
375  
-                        chunk.push(c);
376  
-                        chunks[++j] = chunk = [];
377  
-                    } else if (c === '(' && !inString && !inParam) {
378  
-                        chunk.push(c);
379  
-                        inParam = true;
380  
-                    } else if (c === ')' && !inString && inParam) {
381  
-                        chunk.push(c);
382  
-                        inParam = false;
383  
-                    } else {
384  
-                        if (c === '"' || c === "'" || c === '`') {
385  
-                            if (! inString) {
386  
-                                inString = c;
387  
-                            } else {
388  
-                                inString = inString === c ? false : inString;
389  
-                            }
390  
-                        }
391  
-                        chunk.push(c);
  452
+                    switch (c) {
  453
+                        case '{': if (! inParam) { level ++;        chunk.push(c);                           break }
  454
+                        case '}': if (! inParam) { level --;        chunk.push(c); chunks[++j] = chunk = []; break }
  455
+                        case '(': if (! inParam) { inParam = true;  chunk.push(c);                           break }
  456
+                        case ')': if (  inParam) { inParam = false; chunk.push(c);                           break }
  457
+                        default:                                    chunk.push(c);
392 458
                     }
393 459
                 }
394 460
                 if (level > 0) {
395  
-                    throw {
396  
-                        type: 'Syntax',
397  
-                        message: "Missing closing `}`",
  461
+                    error = new(LessError)({
  462
+                        index: i,
  463
+                        type: 'Parse',
  464
+                        message: "missing closing `}`",
398 465
                         filename: env.filename
399  
-                    };
  466
+                    }, env);
400 467
                 }
401 468
 
402 469
                 return chunks.map(function (c) { return c.join('') });;
403 470
             })([[]]);
404 471
 
  472
+            if (error) {
  473
+                return callback(error);
  474
+            }
  475
+
405 476
             // Start with the primary rule.
406 477
             // The whole syntax tree is held under a Ruleset node,
407 478
             // with the `root` property set to true, so no `{}` are
408 479
             // output. The callback is called when the input is parsed.
409  
-            root = new(tree.Ruleset)([], $(this.parsers.primary));
410  
-            root.root = true;
  480
+            try {
  481
+                root = new(tree.Ruleset)([], $(this.parsers.primary));
  482
+                root.root = true;
  483
+            } catch (e) {
  484
+                return callback(new(LessError)(e, env));
  485
+            }
411 486
 
412 487
             root.toCSS = (function (evaluate) {
413 488
                 var line, lines, column;
414 489
 
415 490
                 return function (options, variables) {
416  
-                    var frames = [];
  491
+                    var frames = [], importError;
417 492
 
418 493
                     options = options || {};
419 494
                     //
@@ -448,30 +523,14 @@ less.Parser = function Parser(env) {
448 523
                         var css = evaluate.call(this, { frames: frames })
449 524
                                           .toCSS([], { compress: options.compress || false });
450 525
                     } catch (e) {
451  
-                        lines = input.split('\n');
452  
-                        line = getLine(e.index);
453  
-
454  
-                        for (var n = e.index, column = -1;
455  
-                                 n >= 0 && input.charAt(n) !== '\n';
456  
-                                 n--) { column++ }
457  
-
458  
-                        throw {
459  
-                            type: e.type,
460  
-                            message: e.message,
461  
-                            filename: env.filename,
462  
-                            index: e.index,
463  
-                            line: typeof(line) === 'number' ? line + 1 : null,
464  
-                            callLine: e.call && (getLine(e.call) + 1),
465  
-                            callExtract: lines[getLine(e.call)],
466  
-                            stack: e.stack,
467  
-                            column: column,
468  
-                            extract: [
469  
-                                lines[line - 1],
470  
-                                lines[line],
471  
-                                lines[line + 1]
472  
-                            ]
473  
-                        };
  526
+                        throw new(LessError)(e, env);
474 527
                     }
  528
+
  529
+                    if ((importError = parser.imports.error)) { // Check if there was an error during importing
  530
+                        if (importError instanceof LessError) throw importError;
  531
+                        else                                  throw new(LessError)(importError, env);
  532
+                    }
  533
+
475 534
                     if (options.yuicompress && less.mode === 'node') {
476 535
                         return require('./cssmin').compressor.cssmin(css);
477 536
                     } else if (options.compress) {
@@ -479,10 +538,6 @@ less.Parser = function Parser(env) {
479 538
                     } else {
480 539
                         return css;
481 540
                     }
482  
-
483  
-                    function getLine(index) {
484  
-                        return index ? (input.slice(0, index).match(/\n/g) || "").length : null;
485  
-                    }
486 541
                 };
487 542
             })(root.eval);
488 543
 
@@ -502,7 +557,7 @@ less.Parser = function Parser(env) {
502 557
                 for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ }
503 558
 
504 559
                 error = {
505  
-                    name: "ParseError",
  560
+                    type: "Parse",
506 561
                     message: "Syntax Error on line " + line,
507 562
                     index: i,
508 563
                     filename: env.filename,
@@ -629,7 +684,7 @@ less.Parser = function Parser(env) {
629 684
                             // detect named color
630 685
                             return new(tree.Color)(tree.colors[k].slice(1));
631 686
                         } else {
632  
-                            return new(tree.Keyword)(k) 
  687
+                            return new(tree.Keyword)(k);
633 688
                         }
634 689
                     }
635 690
                 },
@@ -662,7 +717,7 @@ less.Parser = function Parser(env) {
662 717
 
663 718
                     if (! $(')')) return;
664 719
 
665  
-                    if (name) { return new(tree.Call)(name, args, index) }
  720
+                    if (name) { return new(tree.Call)(name, args, index, env.filename) }
666 721
                 },
667 722
                 arguments: function () {
668 723
                     var args = [], arg;
@@ -705,7 +760,8 @@ less.Parser = function Parser(env) {
705 760
                     if (input.charAt(i) !== 'u' || !$(/^url\(/)) return;
706 761
                     value = $(this.entities.quoted)  || $(this.entities.variable) ||
707 762
                             $(this.entities.dataURI) || $(/^[-\w%@$\/.&=:;#+?~]+/) || "";
708  
-                    if (! $(')')) throw new(Error)("missing closing ) for url()");
  763
+
  764
+                    expect(')');
709 765
 
710 766
                     return new(tree.URL)((value.value || value.data || value instanceof tree.Variable)
711 767
                                         ? value : new(tree.Anonymous)(value), imports.paths);
@@ -737,7 +793,7 @@ less.Parser = function Parser(env) {
737 793
                     var name, index = i;
738 794
 
739 795
                     if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
740  
-                        return new(tree.Variable)(name, index);
  796
+                        return new(tree.Variable)(name, index, env.filename);
741 797
                     }
742 798
                 },
743 799
 
@@ -833,7 +889,7 @@ less.Parser = function Parser(env) {
833 889
                 // selector for now.
834 890
                 //
835 891
                 call: function () {
836  
-                    var elements = [], e, c, args, index = i, s = input.charAt(i);
  892
+                    var elements = [], e, c, args, index = i, s = input.charAt(i), important = false;
837 893
 
838 894
                     if (s !== '.' && s !== '#') { return }
839 895
 
@@ -843,8 +899,12 @@ less.Parser = function Parser(env) {
843 899
                     }
844 900
                     $('(') && (args = $(this.entities.arguments)) && $(')');
845 901
 
  902
+                    if ($(this.important)) {
  903
+                        important = true;
  904
+                    }
  905
+
846 906
                     if (elements.length > 0 && ($(';') || peek('}'))) {
847  
-                        return new(tree.mixin.Call)(elements, args, index);
  907
+                        return new(tree.mixin.Call)(elements, args || [], index, env.filename, important);
848 908
                     }
849 909
                 },
850 910
 
@@ -868,38 +928,53 @@ less.Parser = function Parser(env) {
868 928
                 // the `{...}` block.
869 929
                 //
870 930
                 definition: function () {
871  
-                    var name, params = [], match, ruleset, param, value;
872  
-
  931
+                    var name, params = [], match, ruleset, param, value, cond, variadic = false;
873 932
                     if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') ||
874 933
                         peek(/^[^{]*(;|})/)) return;
875 934
 
  935
+                    save();
  936
+
876 937
                     if (match = $(/^([#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+)\s*\(/)) {
877 938
                         name = match[1];
878 939
 
879  
-                        while (param = $(this.entities.variable) || $(this.entities.literal)
880  
-                                                                 || $(this.entities.keyword)) {
881  
-                            // Variable
882  
-                            if (param instanceof tree.Variable) {
883  
-                                if ($(':')) {
884  
-                                    if (value = $(this.expression)) {
  940
+                        do {
  941
+                            if (input.charAt(i) === '.' && $(/^\.{3}/)) {
  942
+                                variadic = true;
  943
+                                break;
  944
+                            } else if (param = $(this.entities.variable) || $(this.entities.literal)
  945
+                                                                         || $(this.entities.keyword)) {
  946
+                                // Variable
  947
+                                if (param instanceof tree.Variable) {
  948
+                                    if ($(':')) {
  949
+                                        value = expect(this.expression, 'expected expression');
885 950
                                         params.push({ name: param.name, value: value });
  951
+                                    } else if ($(/^\.{3}/)) {
  952
+                                        params.push({ name: param.name, variadic: true });
  953
+                                        variadic = true;
  954
+                                        break;
886 955
                                     } else {
887  
-                                        throw new(Error)("Expected value");
  956
+                                        params.push({ name: param.name });
888 957
                                     }
889 958
                                 } else {
890  
-                                    params.push({ name: param.name });
  959
+                                    params.push({ value: param });
891 960
                                 }
892 961
                             } else {
893  
-                                params.push({ value: param });
  962
+                                break;
894 963
                             }
895  
-                            if (! $(',')) { break }
  964
+                        } while ($(','))
  965
+
  966
+                        expect(')');
  967
+
  968
+                        if ($(/^when/)) { // Guard
  969
+                            cond = expect(this.conditions, 'expected condition');
896 970
                         }
897  
-                        if (! $(')')) throw new(Error)("Expected )");
898 971
 
899 972
                         ruleset = $(this.block);
900 973
 
901 974
                         if (ruleset) {
902  
-                            return new(tree.mixin.Definition)(name, params, ruleset);
  975
+                            return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
  976
+                        } else {
  977
+                            restore();
903 978
                         }
904 979
                     }
905 980
                 }
@@ -934,7 +1009,7 @@ less.Parser = function Parser(env) {
934 1009
 
935 1010
                 if (! $(/^\(opacity=/i)) return;
936 1011
                 if (value = $(/^\d+/) || $(this.entities.variable)) {
937  
-                    if (! $(')')) throw new(Error)("missing closing ) for alpha()");
  1012
+                    expect(')');
938 1013
                     return new(tree.Alpha)(value);
939 1014
                 }
940 1015
             },
@@ -952,12 +1027,16 @@ less.Parser = function Parser(env) {
952 1027
             // and an element name, such as a tag a class, or `*`.
953 1028
             //
954 1029
             element: function () {
955  
-                var e, t, c;
  1030
+                var e, t, c, v;
956 1031
 
957 1032
                 c = $(this.combinator);
958 1033
                 e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/) ||
959 1034
                     $('*') || $(this.attribute) || $(/^\([^)@]+\)/);
960 1035
 
  1036
+                if (! e) {
  1037
+                    $('(') && (v = $(this.entities.variable)) && $(')') && (e = new(tree.Paren)(v));
  1038
+                }
  1039
+
961 1040
                 if (e) { return new(tree.Element)(c, e, i) }
962 1041
 
963 1042
                 if (c.value && c.value.charAt(0) === '&') {
@@ -989,10 +1068,6 @@ less.Parser = function Parser(env) {
989 1068
                     }
990 1069
                     while (input.charAt(i) === ' ') { i++ }
991 1070
                     return new(tree.Combinator)(match);
992  
-                } else if (c === ':' && input.charAt(i + 1) === ':') {
993  
-                    i += 2;
994  
-                    while (input.charAt(i) === ' ') { i++ }
995  
-                    return new(tree.Combinator)('::');
996 1071
                 } else if (input.charAt(i - 1) === ' ') {
997 1072
                     return new(tree.Combinator)(" ");
998 1073
                 } else {
@@ -1011,6 +1086,12 @@ less.Parser = function Parser(env) {
1011 1086
             selector: function () {
1012 1087
                 var sel, e, elements = [], c, match;
1013 1088
 
  1089
+                if ($('(')) {
  1090
+                    sel = $(this.entity);
  1091
+                    expect(')');
  1092
+                    return new(tree.Selector)([new(tree.Element)('', sel, i)]);
  1093
+                }
  1094
+
1014 1095
                 while (e = $(this.element)) {
1015 1096
                     c = input.charAt(i);
1016 1097
                     elements.push(e)
@@ -1066,7 +1147,7 @@ less.Parser = function Parser(env) {
1066 1147
                 }
1067 1148
 
1068 1149
                 if (selectors.length > 0 && (rules = $(this.block))) {
1069  
-                    return new(tree.Ruleset)(selectors, rules);
  1150
+                    return new(tree.Ruleset)(selectors, rules, env.strictImports);
1070 1151
                 } else {
1071 1152
                     // Backtrack
1072 1153
                     furthest = i;
@@ -1110,11 +1191,67 @@ less.Parser = function Parser(env) {
1110 1191
             // stored in `import`, which we pass to the Import constructor.
1111 1192
             //
1112 1193
             "import": function () {
1113  
-                var path;
  1194
+                var path, features, index = i;
1114 1195
                 if ($(/^@import\s+/) &&
1115  
-                    (path = $(this.entities.quoted) || $(this.entities.url)) &&
1116  
-                    $(';')) {
1117  
-                    return new(tree.Import)(path, imports);
  1196
+                    (path = $(this.entities.quoted) || $(this.entities.url))) {
  1197
+                    features = $(this.mediaFeatures);
  1198
+                    if ($(';')) {
  1199
+                        return new(tree.Import)(path, imports, features, index);
  1200
+                    }
  1201
+                }
  1202
+            },
  1203
+
  1204
+            mediaFeature: function () {
  1205
+                var e, p, nodes = [];
  1206
+
  1207
+                do {
  1208
+                    if (e = $(this.entities.keyword)) {
  1209
+                        nodes.push(e);
  1210
+                    } else if ($('(')) {
  1211
+                        p = $(this.property);
  1212
+                        e = $(this.entity);
  1213
+                        if ($(')')) {
  1214
+                            if (p && e) {
  1215
+                                nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, i, true)));
  1216
+                            } else if (e) {
  1217
+                                nodes.push(new(tree.Paren)(e));
  1218
+                            } else {
  1219
+                                return null;
  1220
+                            }
  1221
+                        } else { return null }
  1222
+                    }
  1223
+                } while (e);
  1224
+
  1225
+                if (nodes.length > 0) {
  1226
+                    return new(tree.Expression)(nodes);
  1227
+                }
  1228
+            },
  1229
+
  1230
+            mediaFeatures: function () {
  1231
+                var e, features = [];
  1232
+                
  1233
+                do {
  1234
+                  if (e = $(this.mediaFeature)) {
  1235
+                      features.push(e);
  1236
+                      if (! $(',')) { break }
  1237
+                  } else if (e = $(this.entities.variable)) {
  1238
+                      features.push(e);
  1239
+                      if (! $(',')) { break }
  1240
+                  }
  1241
+                } while (e);
  1242
+                
  1243
+                return features.length > 0 ? features : null;
  1244
+            },
  1245
+
  1246
+            media: function () {
  1247
+                var features, rules;
  1248
+
  1249
+                if ($(/^@media/)) {
  1250
+                    features = $(this.mediaFeatures);
  1251
+
  1252
+                    if (rules = $(this.block)) {
  1253
+                        return new(tree.Media)(rules, features);
  1254
+                    }
1118 1255
                 }
1119 1256
             },
1120 1257
 
@@ -1124,13 +1261,13 @@ less.Parser = function Parser(env) {
1124 1261
             //     @charset "utf-8";
1125 1262
             //
1126 1263
             directive: function () {
1127  
-                var name, value, rules, types;
  1264
+                var name, value, rules, types, e, nodes;
1128 1265
 
1129 1266
                 if (input.charAt(i) !== '@') return;
1130 1267
 
1131  
-                if (value = $(this['import'])) {
  1268
+                if (value = $(this['import']) || $(this.media)) {
1132 1269
                     return value;
1133  
-                } else if (name = $(/^@media|@page/) || $(/^@(?:-webkit-|-moz-|-o-|-ms-)[a-z0-9-]+/) || $('keyframes')) {
  1270
+                } else if (name = $(/^@page|@keyframes/) || $(/^@(?:-webkit-|-moz-|-o-|-ms-)[a-z0-9-]+/)) {
1134 1271
                     types = ($(/^[^{]+/) || '').trim();
1135 1272
                     if (rules = $(this.block)) {
1136 1273
                         return new(tree.Directive)(name + " " + types, rules);
@@ -1213,6 +1350,35 @@ less.Parser = function Parser(env) {
1213 1350
                     return operation || m;
1214 1351
                 }
1215 1352
             },
  1353
+            conditions: function () {
  1354
+                var a, b, index = i, condition;
  1355
+
  1356
+                if (a = $(this.condition)) {
  1357
+                    while ($(',') && (b = $(this.condition))) {
  1358
+                        condition = new(tree.Condition)('or', condition || a, b, index);
  1359
+                    }
  1360
+                    return condition || a;
  1361
+                }
  1362
+            },
  1363
+            condition: function () {
  1364
+                var a, b, c, op, index = i, negate = false;
  1365
+
  1366
+                if ($(/^not/)) { negate = true }
  1367
+                expect('(');
  1368
+                if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
  1369
+                    if (op = $(/^(?:>=|=<|[<=>])/)) {
  1370
+                        if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
  1371
+                            c = new(tree.Condition)(op, a, b, index, negate);
  1372
+                        } else {
  1373
+                            error('expected expression');
  1374
+                        }
  1375
+                    } else {
  1376
+                        c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate);
  1377
+                    }
  1378
+                    expect(')');
  1379
+                    return $(/^and/) ? new(tree.Condition)('and', c, $(this.condition)) : c;
  1380
+                }
  1381
+            },
1216 1382
 
1217 1383
             //
1218 1384
             // An operand is anything that can be part of an operation,
@@ -1262,13 +1428,19 @@ if (less.mode === 'browser' || less.mode === 'rhino') {
1262 1428
     // Used by `@import` directives
1263 1429
     //
1264 1430
     less.Parser.importer = function (path, paths, callback, env) {
1265  
-        if (path.charAt(0) !== '/' && paths.length > 0) {
  1431
+        if (!/^([a-z]+:)?\//.test(path) && paths.length > 0) {
1266 1432
             path = paths[0] + path;
1267 1433
         }
1268 1434
         // We pass `true` as 3rd argument, to force the reload of the import.
1269 1435
         // This is so we can get the syntax tree as opposed to just the CSS output,
1270 1436
         // as we need this to evaluate the current stylesheet.
1271  
-        loadStyleSheet({ href: path, title: path, type: env.mime }, callback, true);
  1437
+        loadStyleSheet({ href: path, title: path, type: env.mime }, function (e) {
  1438
+            if (e && typeof(env.errback) === "function") {
  1439
+                env.errback.call(null, path, paths, callback, env);
  1440
+            } else {
  1441
+                callback.apply(null, arguments);
  1442
+            }
  1443
+        }, true);
1272 1444
     };
1273 1445
 }
1274 1446
 
@@ -1432,15 +1604,49 @@ tree.functions = {
1432 1604
         } else if (typeof(n) === 'number') {
1433 1605
             return Math[fn](n);
1434 1606
         } else {
1435  
-            throw {
1436  
-                error: "RuntimeError",
1437  
-                message: "math functions take numbers as parameters"
1438  
-            };
  1607
+            throw { type: "Argument", message: "argument must be a number" };
1439 1608
         }
1440 1609
     },
1441 1610
     argb: function (color) {
1442 1611
         return new(tree.Anonymous)(color.toARGB());
1443 1612
 
  1613
+    },
  1614
+    percentage: function (n) {
  1615
+        return new(tree.Dimension)(n.value * 100, '%');
  1616
+    },
  1617
+    color: function (n) {
  1618
+        if (n instanceof tree.Quoted) {
  1619
+            return new(tree.Color)(n.value.slice(1));
  1620
+        } else {
  1621
+            throw { type: "Argument", message: "argument must be a string" };
  1622
+        }
  1623
+    },
  1624
+    iscolor: function (n) {
  1625
+        return this._isa(n, tree.Color);
  1626
+    },
  1627
+    isnumber: function (n) {
  1628
+        return this._isa(n, tree.Dimension);
  1629
+    },
  1630
+    isstring: function (n) {
  1631
+        return this._isa(n, tree.Quoted);
  1632
+    },
  1633
+    iskeyword: function (n) {
  1634
+        return this._isa(n, tree.Keyword);
  1635
+    },
  1636
+    isurl: function (n) {
  1637
+        return this._isa(n, tree.URL);
  1638
+    },
  1639
+    ispixel: function (n) {
  1640
+        return (n instanceof tree.Dimension) && n.unit === 'px' ? tree.True : tree.False;
  1641
+    },
  1642
+    ispercentage: function (n) {
  1643
+        return (n instanceof tree.Dimension) && n.unit === '%' ? tree.True : tree.False;
  1644
+    },
  1645
+    isem: function (n) {
  1646
+        return (n instanceof tree.Dimension) && n.unit === 'em' ? tree.True : tree.False;
  1647
+    },
  1648
+    _isa: function (n, Type) {
  1649
+        return (n instanceof Type) ? tree.True : tree.False;
1444 1650
     }
1445 1651
 };
1446 1652
 
@@ -1668,10 +1874,11 @@ tree.Assignment.prototype = {
1668 1874
 //
1669 1875
 // A function call node.
1670 1876
 //
1671  
-tree.Call = function (name, args, index) {
  1877
+tree.Call = function (name, args, index, filename) {
1672 1878
     this.name = name;
1673 1879
     this.args = args;
1674 1880
     this.index = index;
  1881
+    this.filename = filename;
1675 1882
 };
1676 1883
 tree.Call.prototype = {
1677 1884
     //
@@ -1693,8 +1900,10 @@ tree.Call.prototype = {
1693 1900
             try {
1694 1901
                 return tree.functions[this.name].apply(tree.functions, args);
1695 1902
             } catch (e) {
1696  
-                throw { message: "error evaluating function `" + this.name + "`",
1697  
-                        index: this.index };
  1903
+                throw { type: e.type || "Runtime",
  1904
+                        message: "error evaluating function `" + this.name + "`" +
  1905
+                                 (e.message ? ': ' + e.message : ''),
  1906
+                        index: this.index, filename: this.filename };
1698 1907
             }
1699 1908
         } else { // 2.
1700 1909
             return new(tree.Anonymous)(this.name +
@@ -1825,6 +2034,48 @@ tree.Comment.prototype = {
1825 2034
 })(require('../tree'));
1826 2035
 (function (tree) {
1827 2036
 
  2037
+tree.Condition = function (op, l, r, i, negate) {
  2038
+    this.op = op.trim();
  2039
+    this.lvalue = l;
  2040
+    this.rvalue = r;
  2041
+    this.index = i;
  2042
+    this.negate = negate;
  2043
+};
  2044
+tree.Condition.prototype.eval = function (env) {
  2045
+    var a = this.lvalue.eval(env),
  2046
+        b = this.rvalue.eval(env);
  2047
+
  2048
+    var i = this.index, result;
  2049
+
  2050
+    var result = (function (op) {
  2051
+        switch (op) {
  2052
+            case 'and':
  2053
+                return a && b;
  2054
+            case 'or':
  2055
+                return a || b;
  2056
+            default:
  2057
+                if (a.compare) {
  2058
+                    result = a.compare(b);
  2059
+                } else if (b.compare) {
  2060
+                    result = b.compare(a);
  2061
+                } else {
  2062
+                    throw { type: "Type",
  2063
+                            message: "Unable to perform comparison",
  2064
+                            index: i };
  2065
+                }
  2066
+                switch (result) {
  2067
+                    case -1: return op === '<' || op === '=<';
  2068
+                    case  0: return op === '=' || op === '>=' || op === '=<';
  2069
+                    case  1: return op === '>' || op === '>=';
  2070
+                }
  2071
+        }
  2072
+    })(this.op);
  2073
+    return this.negate ? !result : result;
  2074
+};
  2075
+
  2076
+})(require('../tree'));
  2077
+(function (tree) {
  2078
+
1828 2079
 //
1829 2080
 // A number with a unit
1830 2081
 //
@@ -1853,16 +2104,33 @@ tree.Dimension.prototype = {
1853 2104
         return new(tree.Dimension)
1854 2105
                   (tree.operate(op, this.value, other.value),
1855 2106
                   this.unit || other.unit);
  2107
+    },
  2108
+
  2109
+    // TODO: Perform unit conversion before comparing
  2110
+    compare: function (other) {
  2111
+        if (other instanceof tree.Dimension) {
  2112
+            if (other.value > this.value) {
  2113
+                return -1;
  2114
+            } else if (other.value < this.value) {
  2115
+                return 1;
  2116
+            } else {
  2117
+                return 0;
  2118
+            }
  2119
+        } else {
  2120
+            return -1;
  2121
+        }
1856 2122
     }
1857 2123
 };
1858 2124
 
1859 2125
 })(require('../tree'));
1860 2126
 (function (tree) {
1861 2127
 
1862  
-tree.Directive = function (name, value) {
  2128
+tree.Directive = function (name, value, features) {
1863 2129
     this.name = name;
  2130
+
1864 2131
     if (Array.isArray(value)) {
1865 2132
         this.ruleset = new(tree.Ruleset)([], value);
  2133
+        this.ruleset.allowImports = true;
1866 2134
     } else {
1867 2135
         this.value = value;
1868 2136
     }
@@ -1895,11 +2163,23 @@ tree.Directive.prototype = {
1895 2163
 tree.Element = function (combinator, value, index) {
1896 2164
     this.combinator = combinator instanceof tree.Combinator ?
1897 2165
                       combinator : new(tree.Combinator)(combinator);
1898  
-    this.value = value ? value.trim() : "";
  2166
+
  2167
+    if (typeof(value) === 'string') {
  2168
+        this.value = value.trim();
  2169
+    } else if (value) {
  2170
+        this.value = value;
  2171
+    } else {
  2172
+        this.value = "";
  2173
+    }
1899 2174
     this.index = index;
1900 2175
 };
  2176
+tree.Element.prototype.eval = function (env) {
  2177
+    return new(tree.Element)(this.combinator,
  2178
+                             this.value.eval ? this.value.eval(env) : this.value,
  2179
+                             this.index);
  2180
+};
1901 2181
 tree.Element.prototype.toCSS = function (env) {
1902  
-    return this.combinator.toCSS(env || {}) + this.value;
  2182
+    return this.combinator.toCSS(env || {}) + (this.value.toCSS ? this.value.toCSS(env) : this.value);
1903 2183
 };
1904 2184
 
1905 2185
 tree.Combinator = function (value) {
@@ -1918,7 +2198,6 @@ tree.Combinator.prototype.toCSS = function (env) {
1918 2198
         '&' : '',
1919 2199
         '& ' : ' ',
1920 2200
         ':' : ' :',
1921  
-        '::': '::',
1922 2201
         '+' : env.compress ? '+' : ' + ',
1923 2202
         '~' : env.compress ? '~' : ' ~ ',
1924 2203
         '>' : env.compress ? '>' : ' > '
@@ -1943,7 +2222,7 @@ tree.Expression.prototype = {
1943 2222
     },
1944 2223
     toCSS: function (env) {
1945 2224
         return this.value.map(function (e) {
1946  
-            return e.toCSS(env);
  2225
+            return e.toCSS ? e.toCSS(env) : '';
1947 2226
         }).join(' ');
1948 2227
     }
1949 2228
 };
@@ -1962,10 +2241,12 @@ tree.Expression.prototype = {
1962 2241
 // `import,push`, we also pass it a callback, which it'll call once
1963 2242
 // the file has been fetched, and parsed.
1964 2243
 //
1965  
-tree.Import = function (path, imports) {
  2244
+tree.Import = function (path, imports, features, index) {
1966 2245
     var that = this;
1967 2246
 
  2247
+    this.index = index;
1968 2248
     this._path = path;
  2249
+    this.features = features && new(tree.Value)(features);
1969 2250
 
1970 2251
     // The '.less' extension is optional
1971 2252
     if (path instanceof tree.Quoted) {
@@ -1978,11 +2259,9 @@ tree.Import = function (path, imports) {
1978 2259
 
1979 2260
     // Only pre-compile .less files
1980 2261
     if (! this.css) {
1981  
-        imports.push(this.path, function (root) {
1982  
-            if (! root) {
1983  
-                throw new(Error)("Error parsing " + that.path);
1984  
-            }
1985  
-            that.root = root;
  2262
+        imports.push(this.path, function (e, root) {
  2263
+            if (e) { e.index = index }
  2264
+            that.root = root || new(tree.Ruleset)([], []);
1986 2265
         });
1987 2266
     }
1988 2267
 };
@@ -1997,20 +2276,22 @@ tree.Import = function (path, imports) {
1997 2276
 // ruleset.
1998 2277
 //
1999 2278
 tree.Import.prototype = {
2000  
-    toCSS: function () {
  2279
+    toCSS: function (env) {
  2280
+        var features = this.features ? ' ' + this.features.toCSS(env) : '';
  2281
+
2001 2282
         if (this.css) {
2002  
-            return "@import " + this._path.toCSS() + ';\n';
  2283
+            return "@import " + this._path.toCSS() + features + ';\n';
2003 2284
         } else {
2004 2285
             return "";
2005 2286
         }
2006 2287
     },
2007 2288
     eval: function (env) {
2008  
-        var ruleset;
  2289
+        var ruleset, features = this.features && this.features.eval(env);
2009 2290
 
2010 2291
         if (this.css) {
2011 2292
             return this;
2012 2293
         } else {
2013  
-            ruleset = new(tree.Ruleset)(null, this.root.rules.slice(0));
  2294
+            ruleset = new(tree.Ruleset)([], this.root.rules.slice(0));
2014 2295
 
2015 2296
             for (var i = 0; i < ruleset.rules.length; i++) {
2016 2297
                 if (ruleset.rules[i] instanceof tree.Import) {
@@ -2020,7 +2301,7 @@ tree.Import.prototype = {
2020 2301
                                 [i, 1].concat(ruleset.rules[i].eval(env)));
2021 2302
                 }
2022 2303
             }
2023  
-            return ruleset.rules;
  2304
+            return this.features ? new(tree.Media)(ruleset.rules, this.features.value) : ruleset.rules;
2024 2305
         }
2025 2306
     }
2026 2307
 };
@@ -2082,17 +2363,143 @@ tree.JavaScript.prototype = {
2082 2363
 tree.Keyword = function (value) { this.value = value };
2083 2364
 tree.Keyword.prototype = {
2084 2365
     eval: function () { return this },
2085  
-    toCSS: function () { return this.value }
  2366
+    toCSS: function () { return this.value },
  2367
+    compare: function (other) {
  2368
+        if (other instanceof tree.Keyword) {
  2369
+            return other.value === this.value ? 0 : 1;
  2370
+        } else {
  2371
+            return -1;
  2372
+        }
  2373
+    }
  2374
+};
  2375
+
  2376
+tree.True = new(tree.Keyword)('true');
  2377
+tree.False = new(tree.Keyword)('false');
  2378
+
  2379
+})(require('../tree'));
  2380
+(function (tree) {
  2381
+
  2382
+tree.Media = function (value, features) {
  2383
+    var el = new(tree.Element)('&', null, 0),
  2384
+        selectors = [new(tree.Selector)([el])];
  2385
+
  2386
+    this.features = new(tree.Value)(features);
  2387
+    this.ruleset = new(tree.Ruleset)(selectors, value);
  2388
+    this.ruleset.allowImports = true;
  2389
+};
  2390
+tree.Media.prototype = {
  2391
+    toCSS: function (ctx, env) {
  2392
+        var features = this.features.toCSS(env);
  2393
+
  2394
+        this.ruleset.root = (ctx.length === 0 || ctx[0].multiMedia);
  2395
+        return '@media ' + features + (env.compress ? '{' : ' {\n  ') +
  2396
+               this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n  ') +
  2397
+                           (env.compress ? '}': '\n}\n');
  2398
+    },
  2399
+    eval: function (env) {
  2400
+        if (!env.mediaBlocks) {
  2401
+            env.mediaBlocks = [];
  2402
+            env.mediaPath = [];
  2403
+        }
  2404
+        
  2405
+        var blockIndex = env.mediaBlocks.length;
  2406
+        env.mediaPath.push(this);
  2407
+        env.mediaBlocks.push(this);
  2408
+
  2409
+        var media = new(tree.Media)([], []);
  2410
+        media.features = this.features.eval(env);
  2411
+        
  2412
+        env.frames.unshift(this.ruleset);
  2413
+        media.ruleset = this.ruleset.eval(env);
  2414
+        env.frames.shift();
  2415
+        
  2416
+        env.mediaBlocks[blockIndex] = media;
  2417
+        env.mediaPath.pop();
  2418
+
  2419
+        return env.mediaPath.length === 0 ? media.evalTop(env) :
  2420
+                    media.evalNested(env)
  2421
+    },
  2422
+    variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) },
  2423
+    find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) },
  2424
+    rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) },
  2425
+
  2426
+    evalTop: function (env) {
  2427
+        var result = this;
  2428
+
  2429
+        // Render all dependent Media blocks.
  2430
+        if (env.mediaBlocks.length > 1) {
  2431
+            var el = new(tree.Element)('&', null, 0);
  2432
+            var selectors = [new(tree.Selector)([el])];
  2433
+            result = new(tree.Ruleset)(selectors, env.mediaBlocks);
  2434
+            result.multiMedia = true;
  2435
+        }
  2436
+
  2437
+        delete env.mediaBlocks;
  2438
+        delete env.mediaPath;
  2439
+
  2440
+        return result;
  2441
+    },
  2442
+    evalNested: function (env) {
  2443
+        var i, value,
  2444
+            path = env.mediaPath.concat([this]);
  2445
+
  2446
+        // Extract the media-query conditions separated with `,` (OR).
  2447
+        for (i = 0; i < path.length; i++) {
  2448
+            value = path[i].features instanceof tree.Value ?
  2449
+                        path[i].features.value : path[i].features;
  2450
+            path[i] = Array.isArray(value) ? value : [value];
  2451
+        }
  2452
+
  2453
+        // Trace all permutations to generate the resulting media-query.
  2454
+        //
  2455
+        // (a, b and c) with nested (d, e) ->
  2456
+        //    a and d
  2457
+        //    a and e
  2458
+        //    b and c and d
  2459
+        //    b and c and e
  2460
+        this.features = new(tree.Value)(this.permute(path).map(function (path) {
  2461
+            path = path.map(function (fragment) {
  2462
+                return fragment.toCSS ? fragment : new(tree.Anonymous)(fragment);
  2463
+            });
  2464
+
  2465
+            for(i = path.length - 1; i > 0; i--) {
  2466
+                path.splice(i, 0, new(tree.Anonymous)("and"));
  2467
+            }
  2468
+
  2469
+            return new(tree.Expression)(path);
  2470
+        }));
  2471
+
  2472
+        // Fake a tree-node that doesn't output anything.
  2473
+        return new(tree.Ruleset)([], []);
  2474
+    },
  2475
+    permute: function (arr) {
  2476
+      if (arr.length === 0) {
  2477
+          return [];
  2478
+      } else if (arr.length === 1) {
  2479
+          return arr[0];
  2480
+      } else {
  2481
+          var result = [];
  2482
+          var rest = this.permute(arr.slice(1));
  2483
+          for (var i = 0; i < rest.length; i++) {
  2484
+              for (var j = 0; j < arr[0].length; j++) {