source: Dev/trunk/src/client/util/less/dist/less-1.1.5.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 92.0 KB
Line 
1//
2// LESS - Leaner CSS v1.1.5
3// http://lesscss.org
4//
5// Copyright (c) 2009-2011, Alexis Sellier
6// Licensed under the Apache 2.0 License.
7//
8(function (window, undefined) {
9//
10// Stub out `require` in the browser
11//
12function require(arg) {
13    return window.less[arg.split('/')[1]];
14};
15
16
17// ecma-5.js
18//
19// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License
20// -- tlrobinson Tom Robinson
21// dantman Daniel Friesen
22
23//
24// Array
25//
26if (!Array.isArray) {
27    Array.isArray = function(obj) {
28        return Object.prototype.toString.call(obj) === "[object Array]" ||
29               (obj instanceof Array);
30    };
31}
32if (!Array.prototype.forEach) {
33    Array.prototype.forEach =  function(block, thisObject) {
34        var len = this.length >>> 0;
35        for (var i = 0; i < len; i++) {
36            if (i in this) {
37                block.call(thisObject, this[i], i, this);
38            }
39        }
40    };
41}
42if (!Array.prototype.map) {
43    Array.prototype.map = function(fun /*, thisp*/) {
44        var len = this.length >>> 0;
45        var res = new Array(len);
46        var thisp = arguments[1];
47
48        for (var i = 0; i < len; i++) {
49            if (i in this) {
50                res[i] = fun.call(thisp, this[i], i, this);
51            }
52        }
53        return res;
54    };
55}
56if (!Array.prototype.filter) {
57    Array.prototype.filter = function (block /*, thisp */) {
58        var values = [];
59        var thisp = arguments[1];
60        for (var i = 0; i < this.length; i++) {
61            if (block.call(thisp, this[i])) {
62                values.push(this[i]);
63            }
64        }
65        return values;
66    };
67}
68if (!Array.prototype.reduce) {
69    Array.prototype.reduce = function(fun /*, initial*/) {
70        var len = this.length >>> 0;
71        var i = 0;
72
73        // no value to return if no initial value and an empty array
74        if (len === 0 && arguments.length === 1) throw new TypeError();
75
76        if (arguments.length >= 2) {
77            var rv = arguments[1];
78        } else {
79            do {
80                if (i in this) {
81                    rv = this[i++];
82                    break;
83                }
84                // if array contains no values, no initial value to return
85                if (++i >= len) throw new TypeError();
86            } while (true);
87        }
88        for (; i < len; i++) {
89            if (i in this) {
90                rv = fun.call(null, rv, this[i], i, this);
91            }
92        }
93        return rv;
94    };
95}
96if (!Array.prototype.indexOf) {
97    Array.prototype.indexOf = function (value /*, fromIndex */ ) {
98        var length = this.length;
99        var i = arguments[1] || 0;
100
101        if (!length)     return -1;
102        if (i >= length) return -1;
103        if (i < 0)       i += length;
104
105        for (; i < length; i++) {
106            if (!Object.prototype.hasOwnProperty.call(this, i)) { continue }
107            if (value === this[i]) return i;
108        }
109        return -1;
110    };
111}
112
113//
114// Object
115//
116if (!Object.keys) {
117    Object.keys = function (object) {
118        var keys = [];
119        for (var name in object) {
120            if (Object.prototype.hasOwnProperty.call(object, name)) {
121                keys.push(name);
122            }
123        }
124        return keys;
125    };
126}
127
128//
129// String
130//
131if (!String.prototype.trim) {
132    String.prototype.trim = function () {
133        return String(this).replace(/^\s\s*/, '').replace(/\s\s*$/, '');
134    };
135}
136var less, tree;
137
138if (typeof environment === "object" && ({}).toString.call(environment) === "[object Environment]") {
139    // Rhino
140    // Details on how to detect Rhino: https://github.com/ringo/ringojs/issues/88
141    less = {};
142    tree = less.tree = {};
143    less.mode = 'rhino';
144} else if (typeof(window) === 'undefined') {
145    // Node.js
146    less = exports,
147    tree = require('./tree');
148    less.mode = 'node';
149} else {
150    // Browser
151    if (typeof(window.less) === 'undefined') { window.less = {} }
152    less = window.less,
153    tree = window.less.tree = {};
154    less.mode = 'browser';
155}
156//
157// less.js - parser
158//
159//    A relatively straight-forward predictive parser.
160//    There is no tokenization/lexing stage, the input is parsed
161//    in one sweep.
162//
163//    To make the parser fast enough to run in the browser, several
164//    optimization had to be made:
165//
166//    - Matching and slicing on a huge input is often cause of slowdowns.
167//      The solution is to chunkify the input into smaller strings.
168//      The chunks are stored in the `chunks` var,
169//      `j` holds the current chunk index, and `current` holds
170//      the index of the current chunk in relation to `input`.
171//      This gives us an almost 4x speed-up.
172//
173//    - In many cases, we don't need to match individual tokens;
174//      for example, if a value doesn't hold any variables, operations
175//      or dynamic references, the parser can effectively 'skip' it,
176//      treating it as a literal.
177//      An example would be '1px solid #000' - which evaluates to itself,
178//      we don't need to know what the individual components are.
179//      The drawback, of course is that you don't get the benefits of
180//      syntax-checking on the CSS. This gives us a 50% speed-up in the parser,
181//      and a smaller speed-up in the code-gen.
182//
183//
184//    Token matching is done with the `$` function, which either takes
185//    a terminal string or regexp, or a non-terminal function to call.
186//    It also takes care of moving all the indices forwards.
187//
188//
189less.Parser = function Parser(env) {
190    var input,       // LeSS input string
191        i,           // current index in `input`
192        j,           // current chunk
193        temp,        // temporarily holds a chunk's state, for backtracking
194        memo,        // temporarily holds `i`, when backtracking
195        furthest,    // furthest index the parser has gone to
196        chunks,      // chunkified input
197        current,     // index of current chunk, in `input`
198        parser;
199
200    var that = this;
201
202    // This function is called after all files
203    // have been imported through `@import`.
204    var finish = function () {};
205
206    var imports = this.imports = {
207        paths: env && env.paths || [],  // Search paths, when importing
208        queue: [],                      // Files which haven't been imported yet
209        files: {},                      // Holds the imported parse trees
210        mime:  env && env.mime,         // MIME type of .less files
211        push: function (path, callback) {
212            var that = this;
213            this.queue.push(path);
214
215            //
216            // Import a file asynchronously
217            //
218            less.Parser.importer(path, this.paths, function (root) {
219                that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue
220                that.files[path] = root;                        // Store the root
221
222                callback(root);
223
224                if (that.queue.length === 0) { finish() }       // Call `finish` if we're done importing
225            }, env);
226        }
227    };
228
229    function save()    { temp = chunks[j], memo = i, current = i }
230    function restore() { chunks[j] = temp, i = memo, current = i }
231
232    function sync() {
233        if (i > current) {
234            chunks[j] = chunks[j].slice(i - current);
235            current = i;
236        }
237    }
238    //
239    // Parse from a token, regexp or string, and move forward if match
240    //
241    function $(tok) {
242        var match, args, length, c, index, endIndex, k, mem;
243
244        //
245        // Non-terminal
246        //
247        if (tok instanceof Function) {
248            return tok.call(parser.parsers);
249        //
250        // Terminal
251        //
252        //     Either match a single character in the input,
253        //     or match a regexp in the current chunk (chunk[j]).
254        //
255        } else if (typeof(tok) === 'string') {
256            match = input.charAt(i) === tok ? tok : null;
257            length = 1;
258            sync ();
259        } else {
260            sync ();
261
262            if (match = tok.exec(chunks[j])) {
263                length = match[0].length;
264            } else {
265                return null;
266            }
267        }
268
269        // The match is confirmed, add the match length to `i`,
270        // and consume any extra white-space characters (' ' || '\n')
271        // which come after that. The reason for this is that LeSS's
272        // grammar is mostly white-space insensitive.
273        //
274        if (match) {
275            mem = i += length;
276            endIndex = i + chunks[j].length - length;
277
278            while (i < endIndex) {
279                c = input.charCodeAt(i);
280                if (! (c === 32 || c === 10 || c === 9)) { break }
281                i++;
282            }
283            chunks[j] = chunks[j].slice(length + (i - mem));
284            current = i;
285
286            if (chunks[j].length === 0 && j < chunks.length - 1) { j++ }
287
288            if(typeof(match) === 'string') {
289                return match;
290            } else {
291                return match.length === 1 ? match[0] : match;
292            }
293        }
294    }
295
296    // Same as $(), but don't change the state of the parser,
297    // just return the match.
298    function peek(tok) {
299        if (typeof(tok) === 'string') {
300            return input.charAt(i) === tok;
301        } else {
302            if (tok.test(chunks[j])) {
303                return true;
304            } else {
305                return false;
306            }
307        }
308    }
309
310    this.env = env = env || {};
311
312    // The optimization level dictates the thoroughness of the parser,
313    // the lower the number, the less nodes it will create in the tree.
314    // This could matter for debugging, or if you want to access
315    // the individual nodes in the tree.
316    this.optimization = ('optimization' in this.env) ? this.env.optimization : 1;
317
318    this.env.filename = this.env.filename || null;
319
320    //
321    // The Parser
322    //
323    return parser = {
324
325        imports: imports,
326        //
327        // Parse an input string into an abstract syntax tree,
328        // call `callback` when done.
329        //
330        parse: function (str, callback) {
331            var root, start, end, zone, line, lines, buff = [], c, error = null;
332
333            i = j = current = furthest = 0;
334            chunks = [];
335            input = str.replace(/\r\n/g, '\n');
336
337            // Split the input into chunks.
338            chunks = (function (chunks) {
339                var j = 0,
340                    skip = /[^"'`\{\}\/\(\)]+/g,
341                    comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
342                    level = 0,
343                    match,
344                    chunk = chunks[0],
345                    inParam,
346                    inString;
347
348                for (var i = 0, c, cc; i < input.length; i++) {
349                    skip.lastIndex = i;
350                    if (match = skip.exec(input)) {
351                        if (match.index === i) {
352                            i += match[0].length;
353                            chunk.push(match[0]);
354                        }
355                    }
356                    c = input.charAt(i);
357                    comment.lastIndex = i;
358
359                    if (!inString && !inParam && c === '/') {
360                        cc = input.charAt(i + 1);
361                        if (cc === '/' || cc === '*') {
362                            if (match = comment.exec(input)) {
363                                if (match.index === i) {
364                                    i += match[0].length;
365                                    chunk.push(match[0]);
366                                    c = input.charAt(i);
367                                }
368                            }
369                        }
370                    }
371
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);
392                    }
393                }
394                if (level > 0) {
395                    throw {
396                        type: 'Syntax',
397                        message: "Missing closing `}`",
398                        filename: env.filename
399                    };
400                }
401
402                return chunks.map(function (c) { return c.join('') });;
403            })([[]]);
404
405            // Start with the primary rule.
406            // The whole syntax tree is held under a Ruleset node,
407            // with the `root` property set to true, so no `{}` are
408            // output. The callback is called when the input is parsed.
409            root = new(tree.Ruleset)([], $(this.parsers.primary));
410            root.root = true;
411
412            root.toCSS = (function (evaluate) {
413                var line, lines, column;
414
415                return function (options, variables) {
416                    var frames = [];
417
418                    options = options || {};
419                    //
420                    // Allows setting variables with a hash, so:
421                    //
422                    //   `{ color: new(tree.Color)('#f01') }` will become:
423                    //
424                    //   new(tree.Rule)('@color',
425                    //     new(tree.Value)([
426                    //       new(tree.Expression)([
427                    //         new(tree.Color)('#f01')
428                    //       ])
429                    //     ])
430                    //   )
431                    //
432                    if (typeof(variables) === 'object' && !Array.isArray(variables)) {
433                        variables = Object.keys(variables).map(function (k) {
434                            var value = variables[k];
435
436                            if (! (value instanceof tree.Value)) {
437                                if (! (value instanceof tree.Expression)) {
438                                    value = new(tree.Expression)([value]);
439                                }
440                                value = new(tree.Value)([value]);
441                            }
442                            return new(tree.Rule)('@' + k, value, false, 0);
443                        });
444                        frames = [new(tree.Ruleset)(null, variables)];
445                    }
446
447                    try {
448                        var css = evaluate.call(this, { frames: frames })
449                                          .toCSS([], { compress: options.compress || false });
450                    } 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                        };
474                    }
475                    if (options.compress) {
476                        return css.replace(/(\s)+/g, "$1");
477                    } else {
478                        return css;
479                    }
480
481                    function getLine(index) {
482                        return index ? (input.slice(0, index).match(/\n/g) || "").length : null;
483                    }
484                };
485            })(root.eval);
486
487            // If `i` is smaller than the `input.length - 1`,
488            // it means the parser wasn't able to parse the whole
489            // string, so we've got a parsing error.
490            //
491            // We try to extract a \n delimited string,
492            // showing the line where the parse error occured.
493            // We split it up into two parts (the part which parsed,
494            // and the part which didn't), so we can color them differently.
495            if (i < input.length - 1) {
496                i = furthest;
497                lines = input.split('\n');
498                line = (input.slice(0, i).match(/\n/g) || "").length + 1;
499
500                for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ }
501
502                error = {
503                    name: "ParseError",
504                    message: "Syntax Error on line " + line,
505                    index: i,
506                    filename: env.filename,
507                    line: line,
508                    column: column,
509                    extract: [
510                        lines[line - 2],
511                        lines[line - 1],
512                        lines[line]
513                    ]
514                };
515            }
516
517            if (this.imports.queue.length > 0) {
518                finish = function () { callback(error, root) };
519            } else {
520                callback(error, root);
521            }
522        },
523
524        //
525        // Here in, the parsing rules/functions
526        //
527        // The basic structure of the syntax tree generated is as follows:
528        //
529        //   Ruleset ->  Rule -> Value -> Expression -> Entity
530        //
531        // Here's some LESS code:
532        //
533        //    .class {
534        //      color: #fff;
535        //      border: 1px solid #000;
536        //      width: @w + 4px;
537        //      > .child {...}
538        //    }
539        //
540        // And here's what the parse tree might look like:
541        //
542        //     Ruleset (Selector '.class', [
543        //         Rule ("color",  Value ([Expression [Color #fff]]))
544        //         Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
545        //         Rule ("width",  Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
546        //         Ruleset (Selector [Element '>', '.child'], [...])
547        //     ])
548        //
549        //  In general, most rules will try to parse a token with the `$()` function, and if the return
550        //  value is truly, will return a new node, of the relevant type. Sometimes, we need to check
551        //  first, before parsing, that's when we use `peek()`.
552        //
553        parsers: {
554            //
555            // The `primary` rule is the *entry* and *exit* point of the parser.
556            // The rules here can appear at any level of the parse tree.
557            //
558            // The recursive nature of the grammar is an interplay between the `block`
559            // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
560            // as represented by this simplified grammar:
561            //
562            //     primary  →  (ruleset | rule)+
563            //     ruleset  →  selector+ block
564            //     block    →  '{' primary '}'
565            //
566            // Only at one point is the primary rule not called from the
567            // block rule: at the root level.
568            //
569            primary: function () {
570                var node, root = [];
571
572                while ((node = $(this.mixin.definition) || $(this.rule)    ||  $(this.ruleset) ||
573                               $(this.mixin.call)       || $(this.comment) ||  $(this.directive))
574                               || $(/^[\s\n]+/)) {
575                    node && root.push(node);
576                }
577                return root;
578            },
579
580            // We create a Comment node for CSS comments `/* */`,
581            // but keep the LeSS comments `//` silent, by just skipping
582            // over them.
583            comment: function () {
584                var comment;
585
586                if (input.charAt(i) !== '/') return;
587
588                if (input.charAt(i + 1) === '/') {
589                    return new(tree.Comment)($(/^\/\/.*/), true);
590                } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) {
591                    return new(tree.Comment)(comment);
592                }
593            },
594
595            //
596            // Entities are tokens which can be found inside an Expression
597            //
598            entities: {
599                //
600                // A string, which supports escaping " and '
601                //
602                //     "milky way" 'he\'s the one!'
603                //
604                quoted: function () {
605                    var str, j = i, e;
606
607                    if (input.charAt(j) === '~') { j++, e = true } // Escaped strings
608                    if (input.charAt(j) !== '"' && input.charAt(j) !== "'") return;
609
610                    e && $('~');
611
612                    if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) {
613                        return new(tree.Quoted)(str[0], str[1] || str[2], e);
614                    }
615                },
616
617                //
618                // A catch-all word, such as:
619                //
620                //     black border-collapse
621                //
622                keyword: function () {
623                    var k;
624                    if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) { return new(tree.Keyword)(k) }
625                },
626
627                //
628                // A function call
629                //
630                //     rgb(255, 0, 255)
631                //
632                // We also try to catch IE's `alpha()`, but let the `alpha` parser
633                // deal with the details.
634                //
635                // The arguments are parsed with the `entities.arguments` parser.
636                //
637                call: function () {
638                    var name, args, index = i;
639
640                    if (! (name = /^([\w-]+|%)\(/.exec(chunks[j]))) return;
641
642                    name = name[1].toLowerCase();
643
644                    if (name === 'url') { return null }
645                    else                { i += name.length }
646
647                    if (name === 'alpha') { return $(this.alpha) }
648
649                    $('('); // Parse the '(' and consume whitespace.
650
651                    args = $(this.entities.arguments);
652
653                    if (! $(')')) return;
654
655                    if (name) { return new(tree.Call)(name, args, index) }
656                },
657                arguments: function () {
658                    var args = [], arg;
659
660                    while (arg = $(this.expression)) {
661                        args.push(arg);
662                        if (! $(',')) { break }
663                    }
664                    return args;
665                },
666                literal: function () {
667                    return $(this.entities.dimension) ||
668                           $(this.entities.color) ||
669                           $(this.entities.quoted);
670                },
671
672                //
673                // Parse url() tokens
674                //
675                // We use a specific rule for urls, because they don't really behave like
676                // standard function calls. The difference is that the argument doesn't have
677                // to be enclosed within a string, so it can't be parsed as an Expression.
678                //
679                url: function () {
680                    var value;
681
682                    if (input.charAt(i) !== 'u' || !$(/^url\(/)) return;
683                    value = $(this.entities.quoted)  || $(this.entities.variable) ||
684                            $(this.entities.dataURI) || $(/^[-\w%@$\/.&=:;#+?~]+/) || "";
685                    if (! $(')')) throw new(Error)("missing closing ) for url()");
686
687                    return new(tree.URL)((value.value || value.data || value instanceof tree.Variable)
688                                        ? value : new(tree.Anonymous)(value), imports.paths);
689                },
690
691                dataURI: function () {
692                    var obj;
693
694                    if ($(/^data:/)) {
695                        obj         = {};
696                        obj.mime    = $(/^[^\/]+\/[^,;)]+/)     || '';
697                        obj.charset = $(/^;\s*charset=[^,;)]+/) || '';
698                        obj.base64  = $(/^;\s*base64/)          || '';
699                        obj.data    = $(/^,\s*[^)]+/);
700
701                        if (obj.data) { return obj }
702                    }
703                },
704
705                //
706                // A Variable entity, such as `@fink`, in
707                //
708                //     width: @fink + 2px
709                //
710                // We use a different parser for variable definitions,
711                // see `parsers.variable`.
712                //
713                variable: function () {
714                    var name, index = i;
715
716                    if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
717                        return new(tree.Variable)(name, index);
718                    }
719                },
720
721                //
722                // A Hexadecimal color
723                //
724                //     #4F3C2F
725                //
726                // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
727                //
728                color: function () {
729                    var rgb;
730
731                    if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) {
732                        return new(tree.Color)(rgb[1]);
733                    }
734                },
735
736                //
737                // A Dimension, that is, a number and a unit
738                //
739                //     0.5em 95%
740                //
741                dimension: function () {
742                    var value, c = input.charCodeAt(i);
743                    if ((c > 57 || c < 45) || c === 47) return;
744
745                    if (value = $(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) {
746                        return new(tree.Dimension)(value[1], value[2]);
747                    }
748                },
749
750                //
751                // JavaScript code to be evaluated
752                //
753                //     `window.location.href`
754                //
755                javascript: function () {
756                    var str, j = i, e;
757
758                    if (input.charAt(j) === '~') { j++, e = true } // Escaped strings
759                    if (input.charAt(j) !== '`') { return }
760
761                    e && $('~');
762
763                    if (str = $(/^`([^`]*)`/)) {
764                        return new(tree.JavaScript)(str[1], i, e);
765                    }
766                }
767            },
768
769            //
770            // The variable part of a variable definition. Used in the `rule` parser
771            //
772            //     @fink:
773            //
774            variable: function () {
775                var name;
776
777                if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1] }
778            },
779
780            //
781            // A font size/line-height shorthand
782            //
783            //     small/12px
784            //
785            // We need to peek first, or we'll match on keywords and dimensions
786            //
787            shorthand: function () {
788                var a, b;
789
790                if (! peek(/^[@\w.%-]+\/[@\w.-]+/)) return;
791
792                if ((a = $(this.entity)) && $('/') && (b = $(this.entity))) {
793                    return new(tree.Shorthand)(a, b);
794                }
795            },
796
797            //
798            // Mixins
799            //
800            mixin: {
801                //
802                // A Mixin call, with an optional argument list
803                //
804                //     #mixins > .square(#fff);
805                //     .rounded(4px, black);
806                //     .button;
807                //
808                // The `while` loop is there because mixins can be
809                // namespaced, but we only support the child and descendant
810                // selector for now.
811                //
812                call: function () {
813                    var elements = [], e, c, args, index = i, s = input.charAt(i);
814
815                    if (s !== '.' && s !== '#') { return }
816
817                    while (e = $(/^[#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/)) {
818                        elements.push(new(tree.Element)(c, e, i));
819                        c = $('>');
820                    }
821                    $('(') && (args = $(this.entities.arguments)) && $(')');
822
823                    if (elements.length > 0 && ($(';') || peek('}'))) {
824                        return new(tree.mixin.Call)(elements, args, index);
825                    }
826                },
827
828                //
829                // A Mixin definition, with a list of parameters
830                //
831                //     .rounded (@radius: 2px, @color) {
832                //        ...
833                //     }
834                //
835                // Until we have a finer grained state-machine, we have to
836                // do a look-ahead, to make sure we don't have a mixin call.
837                // See the `rule` function for more information.
838                //
839                // We start by matching `.rounded (`, and then proceed on to
840                // the argument list, which has optional default values.
841                // We store the parameters in `params`, with a `value` key,
842                // if there is a value, such as in the case of `@radius`.
843                //
844                // Once we've got our params list, and a closing `)`, we parse
845                // the `{...}` block.
846                //
847                definition: function () {
848                    var name, params = [], match, ruleset, param, value;
849
850                    if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') ||
851                        peek(/^[^{]*(;|})/)) return;
852
853                    if (match = $(/^([#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+)\s*\(/)) {
854                        name = match[1];
855
856                        while (param = $(this.entities.variable) || $(this.entities.literal)
857                                                                 || $(this.entities.keyword)) {
858                            // Variable
859                            if (param instanceof tree.Variable) {
860                                if ($(':')) {
861                                    if (value = $(this.expression)) {
862                                        params.push({ name: param.name, value: value });
863                                    } else {
864                                        throw new(Error)("Expected value");
865                                    }
866                                } else {
867                                    params.push({ name: param.name });
868                                }
869                            } else {
870                                params.push({ value: param });
871                            }
872                            if (! $(',')) { break }
873                        }
874                        if (! $(')')) throw new(Error)("Expected )");
875
876                        ruleset = $(this.block);
877
878                        if (ruleset) {
879                            return new(tree.mixin.Definition)(name, params, ruleset);
880                        }
881                    }
882                }
883            },
884
885            //
886            // Entities are the smallest recognized token,
887            // and can be found inside a rule's value.
888            //
889            entity: function () {
890                return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) ||
891                       $(this.entities.call)    || $(this.entities.keyword)  || $(this.entities.javascript) ||
892                       $(this.comment);
893            },
894
895            //
896            // A Rule terminator. Note that we use `peek()` to check for '}',
897            // because the `block` rule will be expecting it, but we still need to make sure
898            // it's there, if ';' was ommitted.
899            //
900            end: function () {
901                return $(';') || peek('}');
902            },
903
904            //
905            // IE's alpha function
906            //
907            //     alpha(opacity=88)
908            //
909            alpha: function () {
910                var value;
911
912                if (! $(/^\(opacity=/i)) return;
913                if (value = $(/^\d+/) || $(this.entities.variable)) {
914                    if (! $(')')) throw new(Error)("missing closing ) for alpha()");
915                    return new(tree.Alpha)(value);
916                }
917            },
918
919            //
920            // A Selector Element
921            //
922            //     div
923            //     + h1
924            //     #socks
925            //     input[type="text"]
926            //
927            // Elements are the building blocks for Selectors,
928            // they are made out of a `Combinator` (see combinator rule),
929            // and an element name, such as a tag a class, or `*`.
930            //
931            element: function () {
932                var e, t, c;
933
934                c = $(this.combinator);
935                e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/) ||
936                    $('*') || $(this.attribute) || $(/^\([^)@]+\)/);
937
938                if (e) { return new(tree.Element)(c, e, i) }
939
940                if (c.value && c.value.charAt(0) === '&') {
941                    return new(tree.Element)(c, null, i);
942                }
943            },
944
945            //
946            // Combinators combine elements together, in a Selector.
947            //
948            // Because our parser isn't white-space sensitive, special care
949            // has to be taken, when parsing the descendant combinator, ` `,
950            // as it's an empty space. We have to check the previous character
951            // in the input, to see if it's a ` ` character. More info on how
952            // we deal with this in *combinator.js*.
953            //
954            combinator: function () {
955                var match, c = input.charAt(i);
956
957                if (c === '>' || c === '+' || c === '~') {
958                    i++;
959                    while (input.charAt(i) === ' ') { i++ }
960                    return new(tree.Combinator)(c);
961                } else if (c === '&') {
962                    match = '&';
963                    i++;
964                    if(input.charAt(i) === ' ') {
965                        match = '& ';
966                    }
967                    while (input.charAt(i) === ' ') { i++ }
968                    return new(tree.Combinator)(match);
969                } else if (c === ':' && input.charAt(i + 1) === ':') {
970                    i += 2;
971                    while (input.charAt(i) === ' ') { i++ }
972                    return new(tree.Combinator)('::');
973                } else if (input.charAt(i - 1) === ' ') {
974                    return new(tree.Combinator)(" ");
975                } else {
976                    return new(tree.Combinator)(null);
977                }
978            },
979
980            //
981            // A CSS Selector
982            //
983            //     .class > div + h1
984            //     li a:hover
985            //
986            // Selectors are made out of one or more Elements, see above.
987            //
988            selector: function () {
989                var sel, e, elements = [], c, match;
990
991                while (e = $(this.element)) {
992                    c = input.charAt(i);
993                    elements.push(e)
994                    if (c === '{' || c === '}' || c === ';' || c === ',') { break }
995                }
996
997                if (elements.length > 0) { return new(tree.Selector)(elements) }
998            },
999            tag: function () {
1000                return $(/^[a-zA-Z][a-zA-Z-]*[0-9]?/) || $('*');
1001            },
1002            attribute: function () {
1003                var attr = '', key, val, op;
1004
1005                if (! $('[')) return;
1006
1007                if (key = $(/^[a-zA-Z-]+/) || $(this.entities.quoted)) {
1008                    if ((op = $(/^[|~*$^]?=/)) &&
1009                        (val = $(this.entities.quoted) || $(/^[\w-]+/))) {
1010                        attr = [key, op, val.toCSS ? val.toCSS() : val].join('');
1011                    } else { attr = key }
1012                }
1013
1014                if (! $(']')) return;
1015
1016                if (attr) { return "[" + attr + "]" }
1017            },
1018
1019            //
1020            // The `block` rule is used by `ruleset` and `mixin.definition`.
1021            // It's a wrapper around the `primary` rule, with added `{}`.
1022            //
1023            block: function () {
1024                var content;
1025
1026                if ($('{') && (content = $(this.primary)) && $('}')) {
1027                    return content;
1028                }
1029            },
1030
1031            //
1032            // div, .class, body > p {...}
1033            //
1034            ruleset: function () {
1035                var selectors = [], s, rules, match;
1036                save();
1037
1038                while (s = $(this.selector)) {
1039                    selectors.push(s);
1040                    $(this.comment);
1041                    if (! $(',')) { break }
1042                    $(this.comment);
1043                }
1044
1045                if (selectors.length > 0 && (rules = $(this.block))) {
1046                    return new(tree.Ruleset)(selectors, rules);
1047                } else {
1048                    // Backtrack
1049                    furthest = i;
1050                    restore();
1051                }
1052            },
1053            rule: function () {
1054                var name, value, c = input.charAt(i), important, match;
1055                save();
1056
1057                if (c === '.' || c === '#' || c === '&') { return }
1058
1059                if (name = $(this.variable) || $(this.property)) {
1060                    if ((name.charAt(0) != '@') && (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j]))) {
1061                        i += match[0].length - 1;
1062                        value = new(tree.Anonymous)(match[1]);
1063                    } else if (name === "font") {
1064                        value = $(this.font);
1065                    } else {
1066                        value = $(this.value);
1067                    }
1068                    important = $(this.important);
1069
1070                    if (value && $(this.end)) {
1071                        return new(tree.Rule)(name, value, important, memo);
1072                    } else {
1073                        furthest = i;
1074                        restore();
1075                    }
1076                }
1077            },
1078
1079            //
1080            // An @import directive
1081            //
1082            //     @import "lib";
1083            //
1084            // Depending on our environemnt, importing is done differently:
1085            // In the browser, it's an XHR request, in Node, it would be a
1086            // file-system operation. The function used for importing is
1087            // stored in `import`, which we pass to the Import constructor.
1088            //
1089            "import": function () {
1090                var path;
1091                if ($(/^@import\s+/) &&
1092                    (path = $(this.entities.quoted) || $(this.entities.url)) &&
1093                    $(';')) {
1094                    return new(tree.Import)(path, imports);
1095                }
1096            },
1097
1098            //
1099            // A CSS Directive
1100            //
1101            //     @charset "utf-8";
1102            //
1103            directive: function () {
1104                var name, value, rules, types;
1105
1106                if (input.charAt(i) !== '@') return;
1107
1108                if (value = $(this['import'])) {
1109                    return value;
1110                } else if (name = $(/^@media|@page/) || $(/^@(?:-webkit-|-moz-)?keyframes/)) {
1111                    types = ($(/^[^{]+/) || '').trim();
1112                    if (rules = $(this.block)) {
1113                        return new(tree.Directive)(name + " " + types, rules);
1114                    }
1115                } else if (name = $(/^@[-a-z]+/)) {
1116                    if (name === '@font-face') {
1117                        if (rules = $(this.block)) {
1118                            return new(tree.Directive)(name, rules);
1119                        }
1120                    } else if ((value = $(this.entity)) && $(';')) {
1121                        return new(tree.Directive)(name, value);
1122                    }
1123                }
1124            },
1125            font: function () {
1126                var value = [], expression = [], weight, shorthand, font, e;
1127
1128                while (e = $(this.shorthand) || $(this.entity)) {
1129                    expression.push(e);
1130                }
1131                value.push(new(tree.Expression)(expression));
1132
1133                if ($(',')) {
1134                    while (e = $(this.expression)) {
1135                        value.push(e);
1136                        if (! $(',')) { break }
1137                    }
1138                }
1139                return new(tree.Value)(value);
1140            },
1141
1142            //
1143            // A Value is a comma-delimited list of Expressions
1144            //
1145            //     font-family: Baskerville, Georgia, serif;
1146            //
1147            // In a Rule, a Value represents everything after the `:`,
1148            // and before the `;`.
1149            //
1150            value: function () {
1151                var e, expressions = [], important;
1152
1153                while (e = $(this.expression)) {
1154                    expressions.push(e);
1155                    if (! $(',')) { break }
1156                }
1157
1158                if (expressions.length > 0) {
1159                    return new(tree.Value)(expressions);
1160                }
1161            },
1162            important: function () {
1163                if (input.charAt(i) === '!') {
1164                    return $(/^! *important/);
1165                }
1166            },
1167            sub: function () {
1168                var e;
1169
1170                if ($('(') && (e = $(this.expression)) && $(')')) {
1171                    return e;
1172                }
1173            },
1174            multiplication: function () {
1175                var m, a, op, operation;
1176                if (m = $(this.operand)) {
1177                    while ((op = ($('/') || $('*'))) && (a = $(this.operand))) {
1178                        operation = new(tree.Operation)(op, [operation || m, a]);
1179                    }
1180                    return operation || m;
1181                }
1182            },
1183            addition: function () {
1184                var m, a, op, operation;
1185                if (m = $(this.multiplication)) {
1186                    while ((op = $(/^[-+]\s+/) || (input.charAt(i - 1) != ' ' && ($('+') || $('-')))) &&
1187                           (a = $(this.multiplication))) {
1188                        operation = new(tree.Operation)(op, [operation || m, a]);
1189                    }
1190                    return operation || m;
1191                }
1192            },
1193
1194            //
1195            // An operand is anything that can be part of an operation,
1196            // such as a Color, or a Variable
1197            //
1198            operand: function () {
1199                var negate, p = input.charAt(i + 1);
1200
1201                if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-') }
1202                var o = $(this.sub) || $(this.entities.dimension) ||
1203                        $(this.entities.color) || $(this.entities.variable) ||
1204                        $(this.entities.call);
1205                return negate ? new(tree.Operation)('*', [new(tree.Dimension)(-1), o])
1206                              : o;
1207            },
1208
1209            //
1210            // Expressions either represent mathematical operations,
1211            // or white-space delimited Entities.
1212            //
1213            //     1px solid black
1214            //     @var * 2
1215            //
1216            expression: function () {
1217                var e, delim, entities = [], d;
1218
1219                while (e = $(this.addition) || $(this.entity)) {
1220                    entities.push(e);
1221                }
1222                if (entities.length > 0) {
1223                    return new(tree.Expression)(entities);
1224                }
1225            },
1226            property: function () {
1227                var name;
1228
1229                if (name = $(/^(\*?-?[-a-z_0-9]+)\s*:/)) {
1230                    return name[1];
1231                }
1232            }
1233        }
1234    };
1235};
1236
1237if (less.mode === 'browser' || less.mode === 'rhino') {
1238    //
1239    // Used by `@import` directives
1240    //
1241    less.Parser.importer = function (path, paths, callback, env) {
1242        if (path.charAt(0) !== '/' && paths.length > 0) {
1243            path = paths[0] + path;
1244        }
1245        // We pass `true` as 3rd argument, to force the reload of the import.
1246        // This is so we can get the syntax tree as opposed to just the CSS output,
1247        // as we need this to evaluate the current stylesheet.
1248        loadStyleSheet({ href: path, title: path, type: env.mime }, callback, true);
1249    };
1250}
1251
1252(function (tree) {
1253
1254tree.functions = {
1255    rgb: function (r, g, b) {
1256        return this.rgba(r, g, b, 1.0);
1257    },
1258    rgba: function (r, g, b, a) {
1259        var rgb = [r, g, b].map(function (c) { return number(c) }),
1260            a = number(a);
1261        return new(tree.Color)(rgb, a);
1262    },
1263    hsl: function (h, s, l) {
1264        return this.hsla(h, s, l, 1.0);
1265    },
1266    hsla: function (h, s, l, a) {
1267        h = (number(h) % 360) / 360;
1268        s = number(s); l = number(l); a = number(a);
1269
1270        var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
1271        var m1 = l * 2 - m2;
1272
1273        return this.rgba(hue(h + 1/3) * 255,
1274                         hue(h)       * 255,
1275                         hue(h - 1/3) * 255,
1276                         a);
1277
1278        function hue(h) {
1279            h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h);
1280            if      (h * 6 < 1) return m1 + (m2 - m1) * h * 6;
1281            else if (h * 2 < 1) return m2;
1282            else if (h * 3 < 2) return m1 + (m2 - m1) * (2/3 - h) * 6;
1283            else                return m1;
1284        }
1285    },
1286    hue: function (color) {
1287        return new(tree.Dimension)(Math.round(color.toHSL().h));
1288    },
1289    saturation: function (color) {
1290        return new(tree.Dimension)(Math.round(color.toHSL().s * 100), '%');
1291    },
1292    lightness: function (color) {
1293        return new(tree.Dimension)(Math.round(color.toHSL().l * 100), '%');
1294    },
1295    alpha: function (color) {
1296        return new(tree.Dimension)(color.toHSL().a);
1297    },
1298    saturate: function (color, amount) {
1299        var hsl = color.toHSL();
1300
1301        hsl.s += amount.value / 100;
1302        hsl.s = clamp(hsl.s);
1303        return hsla(hsl);
1304    },
1305    desaturate: function (color, amount) {
1306        var hsl = color.toHSL();
1307
1308        hsl.s -= amount.value / 100;
1309        hsl.s = clamp(hsl.s);
1310        return hsla(hsl);
1311    },
1312    lighten: function (color, amount) {
1313        var hsl = color.toHSL();
1314
1315        hsl.l += amount.value / 100;
1316        hsl.l = clamp(hsl.l);
1317        return hsla(hsl);
1318    },
1319    darken: function (color, amount) {
1320        var hsl = color.toHSL();
1321
1322        hsl.l -= amount.value / 100;
1323        hsl.l = clamp(hsl.l);
1324        return hsla(hsl);
1325    },
1326    fadein: function (color, amount) {
1327        var hsl = color.toHSL();
1328
1329        hsl.a += amount.value / 100;
1330        hsl.a = clamp(hsl.a);
1331        return hsla(hsl);
1332    },
1333    fadeout: function (color, amount) {
1334        var hsl = color.toHSL();
1335
1336        hsl.a -= amount.value / 100;
1337        hsl.a = clamp(hsl.a);
1338        return hsla(hsl);
1339    },
1340    fade: function (color, amount) {
1341        var hsl = color.toHSL();
1342
1343        hsl.a = amount.value / 100;
1344        hsl.a = clamp(hsl.a);
1345        return hsla(hsl);
1346    },
1347    spin: function (color, amount) {
1348        var hsl = color.toHSL();
1349        var hue = (hsl.h + amount.value) % 360;
1350
1351        hsl.h = hue < 0 ? 360 + hue : hue;
1352
1353        return hsla(hsl);
1354    },
1355    //
1356    // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
1357    // http://sass-lang.com
1358    //
1359    mix: function (color1, color2, weight) {
1360        var p = weight.value / 100.0;
1361        var w = p * 2 - 1;
1362        var a = color1.toHSL().a - color2.toHSL().a;
1363
1364        var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
1365        var w2 = 1 - w1;
1366
1367        var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2,
1368                   color1.rgb[1] * w1 + color2.rgb[1] * w2,
1369                   color1.rgb[2] * w1 + color2.rgb[2] * w2];
1370
1371        var alpha = color1.alpha * p + color2.alpha * (1 - p);
1372
1373        return new(tree.Color)(rgb, alpha);
1374    },
1375    greyscale: function (color) {
1376        return this.desaturate(color, new(tree.Dimension)(100));
1377    },
1378    e: function (str) {
1379        return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str);
1380    },
1381    escape: function (str) {
1382        return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
1383    },
1384    '%': function (quoted /* arg, arg, ...*/) {
1385        var args = Array.prototype.slice.call(arguments, 1),
1386            str = quoted.value;
1387
1388        for (var i = 0; i < args.length; i++) {
1389            str = str.replace(/%[sda]/i, function(token) {
1390                var value = token.match(/s/i) ? args[i].value : args[i].toCSS();
1391                return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value;
1392            });
1393        }
1394        str = str.replace(/%%/g, '%');
1395        return new(tree.Quoted)('"' + str + '"', str);
1396    },
1397    round: function (n) {
1398        if (n instanceof tree.Dimension) {
1399            return new(tree.Dimension)(Math.round(number(n)), n.unit);
1400        } else if (typeof(n) === 'number') {
1401            return Math.round(n);
1402        } else {
1403            throw {
1404                error: "RuntimeError",
1405                message: "math functions take numbers as parameters"
1406            };
1407        }
1408    },
1409    argb: function (color) {
1410        return new(tree.Anonymous)(color.toARGB());
1411
1412    }
1413};
1414
1415function hsla(hsla) {
1416    return tree.functions.hsla(hsla.h, hsla.s, hsla.l, hsla.a);
1417}
1418
1419function number(n) {
1420    if (n instanceof tree.Dimension) {
1421        return parseFloat(n.unit == '%' ? n.value / 100 : n.value);
1422    } else if (typeof(n) === 'number') {
1423        return n;
1424    } else {
1425        throw {
1426            error: "RuntimeError",
1427            message: "color functions take numbers as parameters"
1428        };
1429    }
1430}
1431
1432function clamp(val) {
1433    return Math.min(1, Math.max(0, val));
1434}
1435
1436})(require('./tree'));
1437(function (tree) {
1438
1439tree.Alpha = function (val) {
1440    this.value = val;
1441};
1442tree.Alpha.prototype = {
1443    toCSS: function () {
1444        return "alpha(opacity=" +
1445               (this.value.toCSS ? this.value.toCSS() : this.value) + ")";
1446    },
1447    eval: function (env) {
1448        if (this.value.eval) { this.value = this.value.eval(env) }
1449        return this;
1450    }
1451};
1452
1453})(require('../tree'));
1454(function (tree) {
1455
1456tree.Anonymous = function (string) {
1457    this.value = string.value || string;
1458};
1459tree.Anonymous.prototype = {
1460    toCSS: function () {
1461        return this.value;
1462    },
1463    eval: function () { return this }
1464};
1465
1466})(require('../tree'));
1467(function (tree) {
1468
1469//
1470// A function call node.
1471//
1472tree.Call = function (name, args, index) {
1473    this.name = name;
1474    this.args = args;
1475    this.index = index;
1476};
1477tree.Call.prototype = {
1478    //
1479    // When evaluating a function call,
1480    // we either find the function in `tree.functions` [1],
1481    // in which case we call it, passing the  evaluated arguments,
1482    // or we simply print it out as it appeared originally [2].
1483    //
1484    // The *functions.js* file contains the built-in functions.
1485    //
1486    // The reason why we evaluate the arguments, is in the case where
1487    // we try to pass a variable to a function, like: `saturate(@color)`.
1488    // The function should receive the value, not the variable.
1489    //
1490    eval: function (env) {
1491        var args = this.args.map(function (a) { return a.eval(env) });
1492
1493        if (this.name in tree.functions) { // 1.
1494            try {
1495                return tree.functions[this.name].apply(tree.functions, args);
1496            } catch (e) {
1497                throw { message: "error evaluating function `" + this.name + "`",
1498                        index: this.index };
1499            }
1500        } else { // 2.
1501            return new(tree.Anonymous)(this.name +
1502                   "(" + args.map(function (a) { return a.toCSS() }).join(', ') + ")");
1503        }
1504    },
1505
1506    toCSS: function (env) {
1507        return this.eval(env).toCSS();
1508    }
1509};
1510
1511})(require('../tree'));
1512(function (tree) {
1513//
1514// RGB Colors - #ff0014, #eee
1515//
1516tree.Color = function (rgb, a) {
1517    //
1518    // The end goal here, is to parse the arguments
1519    // into an integer triplet, such as `128, 255, 0`
1520    //
1521    // This facilitates operations and conversions.
1522    //
1523    if (Array.isArray(rgb)) {
1524        this.rgb = rgb;
1525    } else if (rgb.length == 6) {
1526        this.rgb = rgb.match(/.{2}/g).map(function (c) {
1527            return parseInt(c, 16);
1528        });
1529    } else {
1530        this.rgb = rgb.split('').map(function (c) {
1531            return parseInt(c + c, 16);
1532        });
1533    }
1534    this.alpha = typeof(a) === 'number' ? a : 1;
1535};
1536tree.Color.prototype = {
1537    eval: function () { return this },
1538
1539    //
1540    // If we have some transparency, the only way to represent it
1541    // is via `rgba`. Otherwise, we use the hex representation,
1542    // which has better compatibility with older browsers.
1543    // Values are capped between `0` and `255`, rounded and zero-padded.
1544    //
1545    toCSS: function () {
1546        if (this.alpha < 1.0) {
1547            return "rgba(" + this.rgb.map(function (c) {
1548                return Math.round(c);
1549            }).concat(this.alpha).join(', ') + ")";
1550        } else {
1551            return '#' + this.rgb.map(function (i) {
1552                i = Math.round(i);
1553                i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16);
1554                return i.length === 1 ? '0' + i : i;
1555            }).join('');
1556        }
1557    },
1558
1559    //
1560    // Operations have to be done per-channel, if not,
1561    // channels will spill onto each other. Once we have
1562    // our result, in the form of an integer triplet,
1563    // we create a new Color node to hold the result.
1564    //
1565    operate: function (op, other) {
1566        var result = [];
1567
1568        if (! (other instanceof tree.Color)) {
1569            other = other.toColor();
1570        }
1571
1572        for (var c = 0; c < 3; c++) {
1573            result[c] = tree.operate(op, this.rgb[c], other.rgb[c]);
1574        }
1575        return new(tree.Color)(result, this.alpha + other.alpha);
1576    },
1577
1578    toHSL: function () {
1579        var r = this.rgb[0] / 255,
1580            g = this.rgb[1] / 255,
1581            b = this.rgb[2] / 255,
1582            a = this.alpha;
1583
1584        var max = Math.max(r, g, b), min = Math.min(r, g, b);
1585        var h, s, l = (max + min) / 2, d = max - min;
1586
1587        if (max === min) {
1588            h = s = 0;
1589        } else {
1590            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
1591
1592            switch (max) {
1593                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
1594                case g: h = (b - r) / d + 2;               break;
1595                case b: h = (r - g) / d + 4;               break;
1596            }
1597            h /= 6;
1598        }
1599        return { h: h * 360, s: s, l: l, a: a };
1600    },
1601    toARGB: function () {
1602        var argb = [Math.round(this.alpha * 255)].concat(this.rgb);
1603        return '#' + argb.map(function (i) {
1604            i = Math.round(i);
1605            i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16);
1606            return i.length === 1 ? '0' + i : i;
1607        }).join('');
1608    }
1609};
1610
1611
1612})(require('../tree'));
1613(function (tree) {
1614
1615tree.Comment = function (value, silent) {
1616    this.value = value;
1617    this.silent = !!silent;
1618};
1619tree.Comment.prototype = {
1620    toCSS: function (env) {
1621        return env.compress ? '' : this.value;
1622    },
1623    eval: function () { return this }
1624};
1625
1626})(require('../tree'));
1627(function (tree) {
1628
1629//
1630// A number with a unit
1631//
1632tree.Dimension = function (value, unit) {
1633    this.value = parseFloat(value);
1634    this.unit = unit || null;
1635};
1636
1637tree.Dimension.prototype = {
1638    eval: function () { return this },
1639    toColor: function () {
1640        return new(tree.Color)([this.value, this.value, this.value]);
1641    },
1642    toCSS: function () {
1643        var css = this.value + this.unit;
1644        return css;
1645    },
1646
1647    // In an operation between two Dimensions,
1648    // we default to the first Dimension's unit,
1649    // so `1px + 2em` will yield `3px`.
1650    // In the future, we could implement some unit
1651    // conversions such that `100cm + 10mm` would yield
1652    // `101cm`.
1653    operate: function (op, other) {
1654        return new(tree.Dimension)
1655                  (tree.operate(op, this.value, other.value),
1656                  this.unit || other.unit);
1657    }
1658};
1659
1660})(require('../tree'));
1661(function (tree) {
1662
1663tree.Directive = function (name, value) {
1664    this.name = name;
1665    if (Array.isArray(value)) {
1666        this.ruleset = new(tree.Ruleset)([], value);
1667    } else {
1668        this.value = value;
1669    }
1670};
1671tree.Directive.prototype = {
1672    toCSS: function (ctx, env) {
1673        if (this.ruleset) {
1674            this.ruleset.root = true;
1675            return this.name + (env.compress ? '{' : ' {\n  ') +
1676                   this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n  ') +
1677                               (env.compress ? '}': '\n}\n');
1678        } else {
1679            return this.name + ' ' + this.value.toCSS() + ';\n';
1680        }
1681    },
1682    eval: function (env) {
1683        env.frames.unshift(this);
1684        this.ruleset = this.ruleset && this.ruleset.eval(env);
1685        env.frames.shift();
1686        return this;
1687    },
1688    variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) },
1689    find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) },
1690    rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }
1691};
1692
1693})(require('../tree'));
1694(function (tree) {
1695
1696tree.Element = function (combinator, value, index) {
1697    this.combinator = combinator instanceof tree.Combinator ?
1698                      combinator : new(tree.Combinator)(combinator);
1699    this.value = value ? value.trim() : "";
1700    this.index = index;
1701};
1702tree.Element.prototype.toCSS = function (env) {
1703    return this.combinator.toCSS(env || {}) + this.value;
1704};
1705
1706tree.Combinator = function (value) {
1707    if (value === ' ') {
1708        this.value = ' ';
1709    } else if (value === '& ') {
1710        this.value = '& ';
1711    } else {
1712        this.value = value ? value.trim() : "";
1713    }
1714};
1715tree.Combinator.prototype.toCSS = function (env) {
1716    return {
1717        ''  : '',
1718        ' ' : ' ',
1719        '&' : '',
1720        '& ' : ' ',
1721        ':' : ' :',
1722        '::': '::',
1723        '+' : env.compress ? '+' : ' + ',
1724        '~' : env.compress ? '~' : ' ~ ',
1725        '>' : env.compress ? '>' : ' > '
1726    }[this.value];
1727};
1728
1729})(require('../tree'));
1730(function (tree) {
1731
1732tree.Expression = function (value) { this.value = value };
1733tree.Expression.prototype = {
1734    eval: function (env) {
1735        if (this.value.length > 1) {
1736            return new(tree.Expression)(this.value.map(function (e) {
1737                return e.eval(env);
1738            }));
1739        } else if (this.value.length === 1) {
1740            return this.value[0].eval(env);
1741        } else {
1742            return this;
1743        }
1744    },
1745    toCSS: function (env) {
1746        return this.value.map(function (e) {
1747            return e.toCSS(env);
1748        }).join(' ');
1749    }
1750};
1751
1752})(require('../tree'));
1753(function (tree) {
1754//
1755// CSS @import node
1756//
1757// The general strategy here is that we don't want to wait
1758// for the parsing to be completed, before we start importing
1759// the file. That's because in the context of a browser,
1760// most of the time will be spent waiting for the server to respond.
1761//
1762// On creation, we push the import path to our import queue, though
1763// `import,push`, we also pass it a callback, which it'll call once
1764// the file has been fetched, and parsed.
1765//
1766tree.Import = function (path, imports) {
1767    var that = this;
1768
1769    this._path = path;
1770
1771    // The '.less' extension is optional
1772    if (path instanceof tree.Quoted) {
1773        this.path = /\.(le?|c)ss(\?.*)?$/.test(path.value) ? path.value : path.value + '.less';
1774    } else {
1775        this.path = path.value.value || path.value;
1776    }
1777
1778    this.css = /css(\?.*)?$/.test(this.path);
1779
1780    // Only pre-compile .less files
1781    if (! this.css) {
1782        imports.push(this.path, function (root) {
1783            if (! root) {
1784                throw new(Error)("Error parsing " + that.path);
1785            }
1786            that.root = root;
1787        });
1788    }
1789};
1790
1791//
1792// The actual import node doesn't return anything, when converted to CSS.
1793// The reason is that it's used at the evaluation stage, so that the rules
1794// it imports can be treated like any other rules.
1795//
1796// In `eval`, we make sure all Import nodes get evaluated, recursively, so
1797// we end up with a flat structure, which can easily be imported in the parent
1798// ruleset.
1799//
1800tree.Import.prototype = {
1801    toCSS: function () {
1802        if (this.css) {
1803            return "@import " + this._path.toCSS() + ';\n';
1804        } else {
1805            return "";
1806        }
1807    },
1808    eval: function (env) {
1809        var ruleset;
1810
1811        if (this.css) {
1812            return this;
1813        } else {
1814            ruleset = new(tree.Ruleset)(null, this.root.rules.slice(0));
1815
1816            for (var i = 0; i < ruleset.rules.length; i++) {
1817                if (ruleset.rules[i] instanceof tree.Import) {
1818                    Array.prototype
1819                         .splice
1820                         .apply(ruleset.rules,
1821                                [i, 1].concat(ruleset.rules[i].eval(env)));
1822                }
1823            }
1824            return ruleset.rules;
1825        }
1826    }
1827};
1828
1829})(require('../tree'));
1830(function (tree) {
1831
1832tree.JavaScript = function (string, index, escaped) {
1833    this.escaped = escaped;
1834    this.expression = string;
1835    this.index = index;
1836};
1837tree.JavaScript.prototype = {
1838    eval: function (env) {
1839        var result,
1840            that = this,
1841            context = {};
1842
1843        var expression = this.expression.replace(/@\{([\w-]+)\}/g, function (_, name) {
1844            return tree.jsify(new(tree.Variable)('@' + name, that.index).eval(env));
1845        });
1846
1847        try {
1848            expression = new(Function)('return (' + expression + ')');
1849        } catch (e) {
1850            throw { message: "JavaScript evaluation error: `" + expression + "`" ,
1851                    index: this.index };
1852        }
1853
1854        for (var k in env.frames[0].variables()) {
1855            context[k.slice(1)] = {
1856                value: env.frames[0].variables()[k].value,
1857                toJS: function () {
1858                    return this.value.eval(env).toCSS();
1859                }
1860            };
1861        }
1862
1863        try {
1864            result = expression.call(context);
1865        } catch (e) {
1866            throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" ,
1867                    index: this.index };
1868        }
1869        if (typeof(result) === 'string') {
1870            return new(tree.Quoted)('"' + result + '"', result, this.escaped, this.index);
1871        } else if (Array.isArray(result)) {
1872            return new(tree.Anonymous)(result.join(', '));
1873        } else {
1874            return new(tree.Anonymous)(result);
1875        }
1876    }
1877};
1878
1879})(require('../tree'));
1880
1881(function (tree) {
1882
1883tree.Keyword = function (value) { this.value = value };
1884tree.Keyword.prototype = {
1885    eval: function () { return this },
1886    toCSS: function () { return this.value }
1887};
1888
1889})(require('../tree'));
1890(function (tree) {
1891
1892tree.mixin = {};
1893tree.mixin.Call = function (elements, args, index) {
1894    this.selector = new(tree.Selector)(elements);
1895    this.arguments = args;
1896    this.index = index;
1897};
1898tree.mixin.Call.prototype = {
1899    eval: function (env) {
1900        var mixins, args, rules = [], match = false;
1901
1902        for (var i = 0; i < env.frames.length; i++) {
1903            if ((mixins = env.frames[i].find(this.selector)).length > 0) {
1904                args = this.arguments && this.arguments.map(function (a) { return a.eval(env) });
1905                for (var m = 0; m < mixins.length; m++) {
1906                    if (mixins[m].match(args, env)) {
1907                        try {
1908                            Array.prototype.push.apply(
1909                                  rules, mixins[m].eval(env, this.arguments).rules);
1910                            match = true;
1911                        } catch (e) {
1912                            throw { message: e.message, index: e.index, stack: e.stack, call: this.index };
1913                        }
1914                    }
1915                }
1916                if (match) {
1917                    return rules;
1918                } else {
1919                    throw { message: 'No matching definition was found for `' +
1920                                      this.selector.toCSS().trim() + '('      +
1921                                      this.arguments.map(function (a) {
1922                                          return a.toCSS();
1923                                      }).join(', ') + ")`",
1924                            index:   this.index };
1925                }
1926            }
1927        }
1928        throw { message: this.selector.toCSS().trim() + " is undefined",
1929                index: this.index };
1930    }
1931};
1932
1933tree.mixin.Definition = function (name, params, rules) {
1934    this.name = name;
1935    this.selectors = [new(tree.Selector)([new(tree.Element)(null, name)])];
1936    this.params = params;
1937    this.arity = params.length;
1938    this.rules = rules;
1939    this._lookups = {};
1940    this.required = params.reduce(function (count, p) {
1941        if (!p.name || (p.name && !p.value)) { return count + 1 }
1942        else                                 { return count }
1943    }, 0);
1944    this.parent = tree.Ruleset.prototype;
1945    this.frames = [];
1946};
1947tree.mixin.Definition.prototype = {
1948    toCSS:     function ()     { return "" },
1949    variable:  function (name) { return this.parent.variable.call(this, name) },
1950    variables: function ()     { return this.parent.variables.call(this) },
1951    find:      function ()     { return this.parent.find.apply(this, arguments) },
1952    rulesets:  function ()     { return this.parent.rulesets.apply(this) },
1953
1954    eval: function (env, args) {
1955        var frame = new(tree.Ruleset)(null, []), context, _arguments = [];
1956
1957        for (var i = 0, val; i < this.params.length; i++) {
1958            if (this.params[i].name) {
1959                if (val = (args && args[i]) || this.params[i].value) {
1960                    frame.rules.unshift(new(tree.Rule)(this.params[i].name, val.eval(env)));
1961                } else {
1962                    throw { message: "wrong number of arguments for " + this.name +
1963                            ' (' + args.length + ' for ' + this.arity + ')' };
1964                }
1965            }
1966        }
1967        for (var i = 0; i < Math.max(this.params.length, args && args.length); i++) {
1968            _arguments.push(args[i] || this.params[i].value);
1969        }
1970        frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env)));
1971
1972        return new(tree.Ruleset)(null, this.rules.slice(0)).eval({
1973            frames: [this, frame].concat(this.frames, env.frames)
1974        });
1975    },
1976    match: function (args, env) {
1977        var argsLength = (args && args.length) || 0, len;
1978
1979        if (argsLength < this.required)                               { return false }
1980        if ((this.required > 0) && (argsLength > this.params.length)) { return false }
1981
1982        len = Math.min(argsLength, this.arity);
1983
1984        for (var i = 0; i < len; i++) {
1985            if (!this.params[i].name) {
1986                if (args[i].eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
1987                    return false;
1988                }
1989            }
1990        }
1991        return true;
1992    }
1993};
1994
1995})(require('../tree'));
1996(function (tree) {
1997
1998tree.Operation = function (op, operands) {
1999    this.op = op.trim();
2000    this.operands = operands;
2001};
2002tree.Operation.prototype.eval = function (env) {
2003    var a = this.operands[0].eval(env),
2004        b = this.operands[1].eval(env),
2005        temp;
2006
2007    if (a instanceof tree.Dimension && b instanceof tree.Color) {
2008        if (this.op === '*' || this.op === '+') {
2009            temp = b, b = a, a = temp;
2010        } else {
2011            throw { name: "OperationError",
2012                    message: "Can't substract or divide a color from a number" };
2013        }
2014    }
2015    return a.operate(this.op, b);
2016};
2017
2018tree.operate = function (op, a, b) {
2019    switch (op) {
2020        case '+': return a + b;
2021        case '-': return a - b;
2022        case '*': return a * b;
2023        case '/': return a / b;
2024    }
2025};
2026
2027})(require('../tree'));
2028(function (tree) {
2029
2030tree.Quoted = function (str, content, escaped, i) {
2031    this.escaped = escaped;
2032    this.value = content || '';
2033    this.quote = str.charAt(0);
2034    this.index = i;
2035};
2036tree.Quoted.prototype = {
2037    toCSS: function () {
2038        if (this.escaped) {
2039            return this.value;
2040        } else {
2041            return this.quote + this.value + this.quote;
2042        }
2043    },
2044    eval: function (env) {
2045        var that = this;
2046        var value = this.value.replace(/`([^`]+)`/g, function (_, exp) {
2047            return new(tree.JavaScript)(exp, that.index, true).eval(env).value;
2048        }).replace(/@\{([\w-]+)\}/g, function (_, name) {
2049            var v = new(tree.Variable)('@' + name, that.index).eval(env);
2050            return v.value || v.toCSS();
2051        });
2052        return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index);
2053    }
2054};
2055
2056})(require('../tree'));
2057(function (tree) {
2058
2059tree.Rule = function (name, value, important, index) {
2060    this.name = name;
2061    this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]);
2062    this.important = important ? ' ' + important.trim() : '';
2063    this.index = index;
2064
2065    if (name.charAt(0) === '@') {
2066        this.variable = true;
2067    } else { this.variable = false }
2068};
2069tree.Rule.prototype.toCSS = function (env) {
2070    if (this.variable) { return "" }
2071    else {
2072        return this.name + (env.compress ? ':' : ': ') +
2073               this.value.toCSS(env) +
2074               this.important + ";";
2075    }
2076};
2077
2078tree.Rule.prototype.eval = function (context) {
2079    return new(tree.Rule)(this.name, this.value.eval(context), this.important, this.index);
2080};
2081
2082tree.Shorthand = function (a, b) {
2083    this.a = a;
2084    this.b = b;
2085};
2086
2087tree.Shorthand.prototype = {
2088    toCSS: function (env) {
2089        return this.a.toCSS(env) + "/" + this.b.toCSS(env);
2090    },
2091    eval: function () { return this }
2092};
2093
2094})(require('../tree'));
2095(function (tree) {
2096
2097tree.Ruleset = function (selectors, rules) {
2098    this.selectors = selectors;
2099    this.rules = rules;
2100    this._lookups = {};
2101};
2102tree.Ruleset.prototype = {
2103    eval: function (env) {
2104        var ruleset = new(tree.Ruleset)(this.selectors, this.rules.slice(0));
2105
2106        ruleset.root = this.root;
2107
2108        // push the current ruleset to the frames stack
2109        env.frames.unshift(ruleset);
2110
2111        // Evaluate imports
2112        if (ruleset.root) {
2113            for (var i = 0; i < ruleset.rules.length; i++) {
2114                if (ruleset.rules[i] instanceof tree.Import) {
2115                    Array.prototype.splice
2116                         .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
2117                }
2118            }
2119        }
2120
2121        // Store the frames around mixin definitions,
2122        // so they can be evaluated like closures when the time comes.
2123        for (var i = 0; i < ruleset.rules.length; i++) {
2124            if (ruleset.rules[i] instanceof tree.mixin.Definition) {
2125                ruleset.rules[i].frames = env.frames.slice(0);
2126            }
2127        }
2128
2129        // Evaluate mixin calls.
2130        for (var i = 0; i < ruleset.rules.length; i++) {
2131            if (ruleset.rules[i] instanceof tree.mixin.Call) {
2132                Array.prototype.splice
2133                     .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
2134            }
2135        }
2136
2137        // Evaluate everything else
2138        for (var i = 0, rule; i < ruleset.rules.length; i++) {
2139            rule = ruleset.rules[i];
2140
2141            if (! (rule instanceof tree.mixin.Definition)) {
2142                ruleset.rules[i] = rule.eval ? rule.eval(env) : rule;
2143            }
2144        }
2145
2146        // Pop the stack
2147        env.frames.shift();
2148
2149        return ruleset;
2150    },
2151    match: function (args) {
2152        return !args || args.length === 0;
2153    },
2154    variables: function () {
2155        if (this._variables) { return this._variables }
2156        else {
2157            return this._variables = this.rules.reduce(function (hash, r) {
2158                if (r instanceof tree.Rule && r.variable === true) {
2159                    hash[r.name] = r;
2160                }
2161                return hash;
2162            }, {});
2163        }
2164    },
2165    variable: function (name) {
2166        return this.variables()[name];
2167    },
2168    rulesets: function () {
2169        if (this._rulesets) { return this._rulesets }
2170        else {
2171            return this._rulesets = this.rules.filter(function (r) {
2172                return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition);
2173            });
2174        }
2175    },
2176    find: function (selector, self) {
2177        self = self || this;
2178        var rules = [], rule, match,
2179            key = selector.toCSS();
2180
2181        if (key in this._lookups) { return this._lookups[key] }
2182
2183        this.rulesets().forEach(function (rule) {
2184            if (rule !== self) {
2185                for (var j = 0; j < rule.selectors.length; j++) {
2186                    if (match = selector.match(rule.selectors[j])) {
2187                        if (selector.elements.length > rule.selectors[j].elements.length) {
2188                            Array.prototype.push.apply(rules, rule.find(
2189                                new(tree.Selector)(selector.elements.slice(1)), self));
2190                        } else {
2191                            rules.push(rule);
2192                        }
2193                        break;
2194                    }
2195                }
2196            }
2197        });
2198        return this._lookups[key] = rules;
2199    },
2200    //
2201    // Entry point for code generation
2202    //
2203    //     `context` holds an array of arrays.
2204    //
2205    toCSS: function (context, env) {
2206        var css = [],      // The CSS output
2207            rules = [],    // node.Rule instances
2208            rulesets = [], // node.Ruleset instances
2209            paths = [],    // Current selectors
2210            selector,      // The fully rendered selector
2211            rule;
2212
2213        if (! this.root) {
2214            if (context.length === 0) {
2215                paths = this.selectors.map(function (s) { return [s] });
2216            } else {
2217                this.joinSelectors( paths, context, this.selectors );
2218            }
2219        }
2220
2221        // Compile rules and rulesets
2222        for (var i = 0; i < this.rules.length; i++) {
2223            rule = this.rules[i];
2224
2225            if (rule.rules || (rule instanceof tree.Directive)) {
2226                rulesets.push(rule.toCSS(paths, env));
2227            } else if (rule instanceof tree.Comment) {
2228                if (!rule.silent) {
2229                    if (this.root) {
2230                        rulesets.push(rule.toCSS(env));
2231                    } else {
2232                        rules.push(rule.toCSS(env));
2233                    }
2234                }
2235            } else {
2236                if (rule.toCSS && !rule.variable) {
2237                    rules.push(rule.toCSS(env));
2238                } else if (rule.value && !rule.variable) {
2239                    rules.push(rule.value.toString());
2240                }
2241            }
2242        }
2243
2244        rulesets = rulesets.join('');
2245
2246        // If this is the root node, we don't render
2247        // a selector, or {}.
2248        // Otherwise, only output if this ruleset has rules.
2249        if (this.root) {
2250            css.push(rules.join(env.compress ? '' : '\n'));
2251        } else {
2252            if (rules.length > 0) {
2253                selector = paths.map(function (p) {
2254                    return p.map(function (s) {
2255                        return s.toCSS(env);
2256                    }).join('').trim();
2257                }).join(env.compress ? ',' : (paths.length > 3 ? ',\n' : ', '));
2258                css.push(selector,
2259                        (env.compress ? '{' : ' {\n  ') +
2260                        rules.join(env.compress ? '' : '\n  ') +
2261                        (env.compress ? '}' : '\n}\n'));
2262            }
2263        }
2264        css.push(rulesets);
2265
2266        return css.join('') + (env.compress ? '\n' : '');
2267    },
2268
2269    joinSelectors: function (paths, context, selectors) {
2270        for (var s = 0; s < selectors.length; s++) {
2271            this.joinSelector(paths, context, selectors[s]);
2272        }
2273    },
2274
2275    joinSelector: function (paths, context, selector) {
2276        var before = [], after = [], beforeElements = [],
2277            afterElements = [], hasParentSelector = false, el;
2278
2279        for (var i = 0; i < selector.elements.length; i++) {
2280            el = selector.elements[i];
2281            if (el.combinator.value.charAt(0) === '&') {
2282                hasParentSelector = true;
2283            }
2284            if (hasParentSelector) afterElements.push(el);
2285            else                   beforeElements.push(el);
2286        }
2287
2288        if (! hasParentSelector) {
2289            afterElements = beforeElements;
2290            beforeElements = [];
2291        }
2292
2293        if (beforeElements.length > 0) {
2294            before.push(new(tree.Selector)(beforeElements));
2295        }
2296
2297        if (afterElements.length > 0) {
2298            after.push(new(tree.Selector)(afterElements));
2299        }
2300
2301        for (var c = 0; c < context.length; c++) {
2302            paths.push(before.concat(context[c]).concat(after));
2303        }
2304    }
2305};
2306})(require('../tree'));
2307(function (tree) {
2308
2309tree.Selector = function (elements) {
2310    this.elements = elements;
2311    if (this.elements[0].combinator.value === "") {
2312        this.elements[0].combinator.value = ' ';
2313    }
2314};
2315tree.Selector.prototype.match = function (other) {
2316    var len  = this.elements.length,
2317        olen = other.elements.length,
2318        max  = Math.min(len, olen);
2319
2320    if (len < olen) {
2321        return false;
2322    } else {
2323        for (var i = 0; i < max; i++) {
2324            if (this.elements[i].value !== other.elements[i].value) {
2325                return false;
2326            }
2327        }
2328    }
2329    return true;
2330};
2331tree.Selector.prototype.toCSS = function (env) {
2332    if (this._css) { return this._css }
2333
2334    return this._css = this.elements.map(function (e) {
2335        if (typeof(e) === 'string') {
2336            return ' ' + e.trim();
2337        } else {
2338            return e.toCSS(env);
2339        }
2340    }).join('');
2341};
2342
2343})(require('../tree'));
2344(function (tree) {
2345
2346tree.URL = function (val, paths) {
2347    if (val.data) {
2348        this.attrs = val;
2349    } else {
2350        // Add the base path if the URL is relative and we are in the browser
2351        if (less.mode === 'browser' && !/^(?:https?:\/\/|file:\/\/|data:|\/)/.test(val.value) && paths.length > 0) {
2352            val.value = paths[0] + (val.value.charAt(0) === '/' ? val.value.slice(1) : val.value);
2353        }
2354        this.value = val;
2355        this.paths = paths;
2356    }
2357};
2358tree.URL.prototype = {
2359    toCSS: function () {
2360        return "url(" + (this.attrs ? 'data:' + this.attrs.mime + this.attrs.charset + this.attrs.base64 + this.attrs.data
2361                                    : this.value.toCSS()) + ")";
2362    },
2363    eval: function (ctx) {
2364        return this.attrs ? this : new(tree.URL)(this.value.eval(ctx), this.paths);
2365    }
2366};
2367
2368})(require('../tree'));
2369(function (tree) {
2370
2371tree.Value = function (value) {
2372    this.value = value;
2373    this.is = 'value';
2374};
2375tree.Value.prototype = {
2376    eval: function (env) {
2377        if (this.value.length === 1) {
2378            return this.value[0].eval(env);
2379        } else {
2380            return new(tree.Value)(this.value.map(function (v) {
2381                return v.eval(env);
2382            }));
2383        }
2384    },
2385    toCSS: function (env) {
2386        return this.value.map(function (e) {
2387            return e.toCSS(env);
2388        }).join(env.compress ? ',' : ', ');
2389    }
2390};
2391
2392})(require('../tree'));
2393(function (tree) {
2394
2395tree.Variable = function (name, index) { this.name = name, this.index = index };
2396tree.Variable.prototype = {
2397    eval: function (env) {
2398        var variable, v, name = this.name;
2399
2400        if (name.indexOf('@@') == 0) {
2401            name = '@' + new(tree.Variable)(name.slice(1)).eval(env).value;
2402        }
2403
2404        if (variable = tree.find(env.frames, function (frame) {
2405            if (v = frame.variable(name)) {
2406                return v.value.eval(env);
2407            }
2408        })) { return variable }
2409        else {
2410            throw { message: "variable " + name + " is undefined",
2411                    index: this.index };
2412        }
2413    }
2414};
2415
2416})(require('../tree'));
2417require('./tree').find = function (obj, fun) {
2418    for (var i = 0, r; i < obj.length; i++) {
2419        if (r = fun.call(obj, obj[i])) { return r }
2420    }
2421    return null;
2422};
2423require('./tree').jsify = function (obj) {
2424    if (Array.isArray(obj.value) && (obj.value.length > 1)) {
2425        return '[' + obj.value.map(function (v) { return v.toCSS(false) }).join(', ') + ']';
2426    } else {
2427        return obj.toCSS(false);
2428    }
2429};
2430//
2431// browser.js - client-side engine
2432//
2433
2434var isFileProtocol = (location.protocol === 'file:'    ||
2435                      location.protocol === 'chrome:'  ||
2436                      location.protocol === 'chrome-extension:'  ||
2437                      location.protocol === 'resource:');
2438
2439less.env = less.env || (location.hostname == '127.0.0.1' ||
2440                        location.hostname == '0.0.0.0'   ||
2441                        location.hostname == 'localhost' ||
2442                        location.port.length > 0         ||
2443                        isFileProtocol                   ? 'development'
2444                                                         : 'production');
2445
2446// Load styles asynchronously (default: false)
2447//
2448// This is set to `false` by default, so that the body
2449// doesn't start loading before the stylesheets are parsed.
2450// Setting this to `true` can result in flickering.
2451//
2452less.async = false;
2453
2454// Interval between watch polls
2455less.poll = less.poll || (isFileProtocol ? 1000 : 1500);
2456
2457//
2458// Watch mode
2459//
2460less.watch   = function () { return this.watchMode = true };
2461less.unwatch = function () { return this.watchMode = false };
2462
2463if (less.env === 'development') {
2464    less.optimization = 0;
2465
2466    if (/!watch/.test(location.hash)) {
2467        less.watch();
2468    }
2469    less.watchTimer = setInterval(function () {
2470        if (less.watchMode) {
2471            loadStyleSheets(function (root, sheet, env) {
2472                if (root) {
2473                    createCSS(root.toCSS(), sheet, env.lastModified);
2474                }
2475            });
2476        }
2477    }, less.poll);
2478} else {
2479    less.optimization = 3;
2480}
2481
2482var cache;
2483
2484try {
2485    cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
2486} catch (_) {
2487    cache = null;
2488}
2489
2490//
2491// Get all <link> tags with the 'rel' attribute set to "stylesheet/less"
2492//
2493var links = document.getElementsByTagName('link');
2494var typePattern = /^text\/(x-)?less$/;
2495
2496less.sheets = [];
2497
2498for (var i = 0; i < links.length; i++) {
2499    if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
2500       (links[i].type.match(typePattern)))) {
2501        less.sheets.push(links[i]);
2502    }
2503}
2504
2505
2506less.refresh = function (reload) {
2507    var startTime, endTime;
2508    startTime = endTime = new(Date);
2509
2510    loadStyleSheets(function (root, sheet, env) {
2511        if (env.local) {
2512            log("loading " + sheet.href + " from cache.");
2513        } else {
2514            log("parsed " + sheet.href + " successfully.");
2515            createCSS(root.toCSS(), sheet, env.lastModified);
2516        }
2517        log("css for " + sheet.href + " generated in " + (new(Date) - endTime) + 'ms');
2518        (env.remaining === 0) && log("css generated in " + (new(Date) - startTime) + 'ms');
2519        endTime = new(Date);
2520    }, reload);
2521
2522    loadStyles();
2523};
2524less.refreshStyles = loadStyles;
2525
2526less.refresh(less.env === 'development');
2527
2528function loadStyles() {
2529    var styles = document.getElementsByTagName('style');
2530    for (var i = 0; i < styles.length; i++) {
2531        if (styles[i].type.match(typePattern)) {
2532            new(less.Parser)().parse(styles[i].innerHTML || '', function (e, tree) {
2533                var css = tree.toCSS();
2534                var style = styles[i];
2535                try {
2536                    style.innerHTML = css;
2537                } catch (_) {
2538                    style.styleSheets.cssText = css;
2539                }
2540                style.type = 'text/css';
2541            });
2542        }
2543    }
2544}
2545
2546function loadStyleSheets(callback, reload) {
2547    for (var i = 0; i < less.sheets.length; i++) {
2548        loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1));
2549    }
2550}
2551
2552function loadStyleSheet(sheet, callback, reload, remaining) {
2553    var url       = window.location.href.replace(/[#?].*$/, '');
2554    var href      = sheet.href.replace(/\?.*$/, '');
2555    var css       = cache && cache.getItem(href);
2556    var timestamp = cache && cache.getItem(href + ':timestamp');
2557    var styles    = { css: css, timestamp: timestamp };
2558
2559    // Stylesheets in IE don't always return the full path
2560    if (! /^(https?|file):/.test(href)) {
2561        if (href.charAt(0) == "/") {
2562            href = window.location.protocol + "//" + window.location.host + href;
2563        } else {
2564            href = url.slice(0, url.lastIndexOf('/') + 1) + href;
2565        }
2566    }
2567
2568    xhr(sheet.href, sheet.type, function (data, lastModified) {
2569        if (!reload && styles && lastModified &&
2570           (new(Date)(lastModified).valueOf() ===
2571            new(Date)(styles.timestamp).valueOf())) {
2572            // Use local copy
2573            createCSS(styles.css, sheet);
2574            callback(null, sheet, { local: true, remaining: remaining });
2575        } else {
2576            // Use remote copy (re-parse)
2577            try {
2578                new(less.Parser)({
2579                    optimization: less.optimization,
2580                    paths: [href.replace(/[\w\.-]+$/, '')],
2581                    mime: sheet.type
2582                }).parse(data, function (e, root) {
2583                    if (e) { return error(e, href) }
2584                    try {
2585                        callback(root, sheet, { local: false, lastModified: lastModified, remaining: remaining });
2586                        removeNode(document.getElementById('less-error-message:' + extractId(href)));
2587                    } catch (e) {
2588                        error(e, href);
2589                    }
2590                });
2591            } catch (e) {
2592                error(e, href);
2593            }
2594        }
2595    }, function (status, url) {
2596        throw new(Error)("Couldn't load " + url + " (" + status + ")");
2597    });
2598}
2599
2600function extractId(href) {
2601    return href.replace(/^[a-z]+:\/\/?[^\/]+/, '' )  // Remove protocol & domain
2602               .replace(/^\//,                 '' )  // Remove root /
2603               .replace(/\?.*$/,               '' )  // Remove query
2604               .replace(/\.[^\.\/]+$/,         '' )  // Remove file extension
2605               .replace(/[^\.\w-]+/g,          '-')  // Replace illegal characters
2606               .replace(/\./g,                 ':'); // Replace dots with colons(for valid id)
2607}
2608
2609function createCSS(styles, sheet, lastModified) {
2610    var css;
2611
2612    // Strip the query-string
2613    var href = sheet.href ? sheet.href.replace(/\?.*$/, '') : '';
2614
2615    // If there is no title set, use the filename, minus the extension
2616    var id = 'less:' + (sheet.title || extractId(href));
2617
2618    // If the stylesheet doesn't exist, create a new node
2619    if ((css = document.getElementById(id)) === null) {
2620        css = document.createElement('style');
2621        css.type = 'text/css';
2622        css.media = sheet.media || 'screen';
2623        css.id = id;
2624        document.getElementsByTagName('head')[0].appendChild(css);
2625    }
2626
2627    if (css.styleSheet) { // IE
2628        try {
2629            css.styleSheet.cssText = styles;
2630        } catch (e) {
2631            throw new(Error)("Couldn't reassign styleSheet.cssText.");
2632        }
2633    } else {
2634        (function (node) {
2635            if (css.childNodes.length > 0) {
2636                if (css.firstChild.nodeValue !== node.nodeValue) {
2637                    css.replaceChild(node, css.firstChild);
2638                }
2639            } else {
2640                css.appendChild(node);
2641            }
2642        })(document.createTextNode(styles));
2643    }
2644
2645    // Don't update the local store if the file wasn't modified
2646    if (lastModified && cache) {
2647        log('saving ' + href + ' to cache.');
2648        cache.setItem(href, styles);
2649        cache.setItem(href + ':timestamp', lastModified);
2650    }
2651}
2652
2653function xhr(url, type, callback, errback) {
2654    var xhr = getXMLHttpRequest();
2655    var async = isFileProtocol ? false : less.async;
2656
2657    if (typeof(xhr.overrideMimeType) === 'function') {
2658        xhr.overrideMimeType('text/css');
2659    }
2660    xhr.open('GET', url, async);
2661    xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
2662    xhr.send(null);
2663
2664    if (isFileProtocol) {
2665        if (xhr.status === 0) {
2666            callback(xhr.responseText);
2667        } else {
2668            errback(xhr.status, url);
2669        }
2670    } else if (async) {
2671        xhr.onreadystatechange = function () {
2672            if (xhr.readyState == 4) {
2673                handleResponse(xhr, callback, errback);
2674            }
2675        };
2676    } else {
2677        handleResponse(xhr, callback, errback);
2678    }
2679
2680    function handleResponse(xhr, callback, errback) {
2681        if (xhr.status >= 200 && xhr.status < 300) {
2682            callback(xhr.responseText,
2683                     xhr.getResponseHeader("Last-Modified"));
2684        } else if (typeof(errback) === 'function') {
2685            errback(xhr.status, url);
2686        }
2687    }
2688}
2689
2690function getXMLHttpRequest() {
2691    if (window.XMLHttpRequest) {
2692        return new(XMLHttpRequest);
2693    } else {
2694        try {
2695            return new(ActiveXObject)("MSXML2.XMLHTTP.3.0");
2696        } catch (e) {
2697            log("browser doesn't support AJAX.");
2698            return null;
2699        }
2700    }
2701}
2702
2703function removeNode(node) {
2704    return node && node.parentNode.removeChild(node);
2705}
2706
2707function log(str) {
2708    if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str) }
2709}
2710
2711function error(e, href) {
2712    var id = 'less-error-message:' + extractId(href);
2713
2714    var template = ['<ul>',
2715                        '<li><label>[-1]</label><pre class="ctx">{0}</pre></li>',
2716                        '<li><label>[0]</label><pre>{current}</pre></li>',
2717                        '<li><label>[1]</label><pre class="ctx">{2}</pre></li>',
2718                    '</ul>'].join('\n');
2719
2720    var elem = document.createElement('div'), timer, content;
2721
2722    elem.id        = id;
2723    elem.className = "less-error-message";
2724
2725    content = '<h3>'  + (e.message || 'There is an error in your .less file') +
2726              '</h3>' + '<p><a href="' + href   + '">' + href + "</a> ";
2727
2728    if (e.extract) {
2729        content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':</p>' +
2730            template.replace(/\[(-?\d)\]/g, function (_, i) {
2731                return (parseInt(e.line) + parseInt(i)) || '';
2732            }).replace(/\{(\d)\}/g, function (_, i) {
2733                return e.extract[parseInt(i)] || '';
2734            }).replace(/\{current\}/, e.extract[1].slice(0, e.column) + '<span class="error">' +
2735                                      e.extract[1].slice(e.column)    + '</span>');
2736    }
2737    elem.innerHTML = content;
2738
2739    // CSS for error messages
2740    createCSS([
2741        '.less-error-message ul, .less-error-message li {',
2742            'list-style-type: none;',
2743            'margin-right: 15px;',
2744            'padding: 4px 0;',
2745            'margin: 0;',
2746        '}',
2747        '.less-error-message label {',
2748            'font-size: 12px;',
2749            'margin-right: 15px;',
2750            'padding: 4px 0;',
2751            'color: #cc7777;',
2752        '}',
2753        '.less-error-message pre {',
2754            'color: #ee4444;',
2755            'padding: 4px 0;',
2756            'margin: 0;',
2757            'display: inline-block;',
2758        '}',
2759        '.less-error-message pre.ctx {',
2760            'color: #dd4444;',
2761        '}',
2762        '.less-error-message h3 {',
2763            'font-size: 20px;',
2764            'font-weight: bold;',
2765            'padding: 15px 0 5px 0;',
2766            'margin: 0;',
2767        '}',
2768        '.less-error-message a {',
2769            'color: #10a',
2770        '}',
2771        '.less-error-message .error {',
2772            'color: red;',
2773            'font-weight: bold;',
2774            'padding-bottom: 2px;',
2775            'border-bottom: 1px dashed red;',
2776        '}'
2777    ].join('\n'), { title: 'error-message' });
2778
2779    elem.style.cssText = [
2780        "font-family: Arial, sans-serif",
2781        "border: 1px solid #e00",
2782        "background-color: #eee",
2783        "border-radius: 5px",
2784        "-webkit-border-radius: 5px",
2785        "-moz-border-radius: 5px",
2786        "color: #e00",
2787        "padding: 15px",
2788        "margin-bottom: 15px"
2789    ].join(';');
2790
2791    if (less.env == 'development') {
2792        timer = setInterval(function () {
2793            if (document.body) {
2794                if (document.getElementById(id)) {
2795                    document.body.replaceChild(elem, document.getElementById(id));
2796                } else {
2797                    document.body.insertBefore(elem, document.body.firstChild);
2798                }
2799                clearInterval(timer);
2800            }
2801        }, 10);
2802    }
2803}
2804
2805})(window);
Note: See TracBrowser for help on using the repository browser.