dfff28a4b17eed45ea2f8acdcecd7c640897b8f1
[website] / src / main / webapp / js / less-1.5.1.js
1 /*! 
2  * LESS - Leaner CSS v1.5.1 
3  * http://lesscss.org 
4  * 
5  * Copyright (c) 2009-2013, Alexis Sellier <self@cloudhead.net> 
6  * Licensed under the Apache v2 License. 
7  * 
8  * @licence 
9  */ 
10
11
12
13 (function (window, undefined) {//
14 // Stub out `require` in the browser
15 //
16 function require(arg) {
17     return window.less[arg.split('/')[1]];
18 };
19
20
21 if (typeof(window.less) === 'undefined' || typeof(window.less.nodeType) !== 'undefined') { window.less = {}; }
22 less = window.less;
23 tree = window.less.tree = {};
24 less.mode = 'browser';
25
26 var less, tree;
27
28 // Node.js does not have a header file added which defines less
29 if (less === undefined) {
30     less = exports;
31     tree = require('./tree');
32     less.mode = 'node';
33 }
34 //
35 // less.js - parser
36 //
37 //    A relatively straight-forward predictive parser.
38 //    There is no tokenization/lexing stage, the input is parsed
39 //    in one sweep.
40 //
41 //    To make the parser fast enough to run in the browser, several
42 //    optimization had to be made:
43 //
44 //    - Matching and slicing on a huge input is often cause of slowdowns.
45 //      The solution is to chunkify the input into smaller strings.
46 //      The chunks are stored in the `chunks` var,
47 //      `j` holds the current chunk index, and `current` holds
48 //      the index of the current chunk in relation to `input`.
49 //      This gives us an almost 4x speed-up.
50 //
51 //    - In many cases, we don't need to match individual tokens;
52 //      for example, if a value doesn't hold any variables, operations
53 //      or dynamic references, the parser can effectively 'skip' it,
54 //      treating it as a literal.
55 //      An example would be '1px solid #000' - which evaluates to itself,
56 //      we don't need to know what the individual components are.
57 //      The drawback, of course is that you don't get the benefits of
58 //      syntax-checking on the CSS. This gives us a 50% speed-up in the parser,
59 //      and a smaller speed-up in the code-gen.
60 //
61 //
62 //    Token matching is done with the `$` function, which either takes
63 //    a terminal string or regexp, or a non-terminal function to call.
64 //    It also takes care of moving all the indices forwards.
65 //
66 //
67 less.Parser = function Parser(env) {
68     var input,       // LeSS input string
69         i,           // current index in `input`
70         j,           // current chunk
71         temp,        // temporarily holds a chunk's state, for backtracking
72         memo,        // temporarily holds `i`, when backtracking
73         furthest,    // furthest index the parser has gone to
74         chunks,      // chunkified input
75         current,     // index of current chunk, in `input`
76         parser,
77         rootFilename = env && env.filename;
78
79     // Top parser on an import tree must be sure there is one "env"
80     // which will then be passed around by reference.
81     if (!(env instanceof tree.parseEnv)) {
82         env = new tree.parseEnv(env);
83     }
84
85     var imports = this.imports = {
86         paths: env.paths || [],  // Search paths, when importing
87         queue: [],               // Files which haven't been imported yet
88         files: env.files,        // Holds the imported parse trees
89         contents: env.contents,  // Holds the imported file contents
90         mime:  env.mime,         // MIME type of .less files
91         error: null,             // Error in parsing/evaluating an import
92         push: function (path, currentFileInfo, importOptions, callback) {
93             var parserImports = this;
94             this.queue.push(path);
95
96             var fileParsedFunc = function (e, root, fullPath) {
97                 parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue
98
99                 var importedPreviously = fullPath in parserImports.files || fullPath === rootFilename;
100
101                 parserImports.files[fullPath] = root;                        // Store the root
102
103                 if (e && !parserImports.error) { parserImports.error = e; }
104
105                 callback(e, root, importedPreviously, fullPath);
106             };
107
108             if (less.Parser.importer) {
109                 less.Parser.importer(path, currentFileInfo, fileParsedFunc, env);
110             } else {
111                 less.Parser.fileLoader(path, currentFileInfo, function(e, contents, fullPath, newFileInfo) {
112                     if (e) {fileParsedFunc(e); return;}
113
114                     var newEnv = new tree.parseEnv(env);
115
116                     newEnv.currentFileInfo = newFileInfo;
117                     newEnv.processImports = false;
118                     newEnv.contents[fullPath] = contents;
119
120                     if (currentFileInfo.reference || importOptions.reference) {
121                         newFileInfo.reference = true;
122                     }
123
124                     if (importOptions.inline) {
125                         fileParsedFunc(null, contents, fullPath);
126                     } else {
127                         new(less.Parser)(newEnv).parse(contents, function (e, root) {
128                             fileParsedFunc(e, root, fullPath);
129                         });
130                     }
131                 }, env);
132             }
133         }
134     };
135
136     function save()    { temp = chunks[j], memo = i, current = i; }
137     function restore() { chunks[j] = temp, i = memo, current = i; }
138
139     function sync() {
140         if (i > current) {
141             chunks[j] = chunks[j].slice(i - current);
142             current = i;
143         }
144     }
145     function isWhitespace(c) {
146         // Could change to \s?
147         var code = c.charCodeAt(0);
148         return code === 32 || code === 10 || code === 9;
149     }
150     //
151     // Parse from a token, regexp or string, and move forward if match
152     //
153     function $(tok) {
154         var match, length;
155
156         //
157         // Non-terminal
158         //
159         if (tok instanceof Function) {
160             return tok.call(parser.parsers);
161         //
162         // Terminal
163         //
164         //     Either match a single character in the input,
165         //     or match a regexp in the current chunk (chunk[j]).
166         //
167         } else if (typeof(tok) === 'string') {
168             match = input.charAt(i) === tok ? tok : null;
169             length = 1;
170             sync ();
171         } else {
172             sync ();
173
174             if (match = tok.exec(chunks[j])) {
175                 length = match[0].length;
176             } else {
177                 return null;
178             }
179         }
180
181         // The match is confirmed, add the match length to `i`,
182         // and consume any extra white-space characters (' ' || '\n')
183         // which come after that. The reason for this is that LeSS's
184         // grammar is mostly white-space insensitive.
185         //
186         if (match) {
187             skipWhitespace(length);
188
189             if(typeof(match) === 'string') {
190                 return match;
191             } else {
192                 return match.length === 1 ? match[0] : match;
193             }
194         }
195     }
196
197     function skipWhitespace(length) {
198         var oldi = i, oldj = j,
199             endIndex = i + chunks[j].length,
200             mem = i += length;
201
202         while (i < endIndex) {
203             if (! isWhitespace(input.charAt(i))) { break; }
204             i++;
205         }
206         chunks[j] = chunks[j].slice(length + (i - mem));
207         current = i;
208
209         if (chunks[j].length === 0 && j < chunks.length - 1) { j++; }
210
211         return oldi !== i || oldj !== j;
212     }
213
214     function expect(arg, msg) {
215         var result = $(arg);
216         if (! result) {
217             error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'"
218                                                    : "unexpected token"));
219         } else {
220             return result;
221         }
222     }
223
224     function error(msg, type) {
225         var e = new Error(msg);
226         e.index = i;
227         e.type = type || 'Syntax';
228         throw e;
229     }
230
231     // Same as $(), but don't change the state of the parser,
232     // just return the match.
233     function peek(tok) {
234         if (typeof(tok) === 'string') {
235             return input.charAt(i) === tok;
236         } else {
237             return tok.test(chunks[j]);
238         }
239     }
240
241     function getInput(e, env) {
242         if (e.filename && env.currentFileInfo.filename && (e.filename !== env.currentFileInfo.filename)) {
243             return parser.imports.contents[e.filename];
244         } else {
245             return input;
246         }
247     }
248
249     function getLocation(index, inputStream) {
250         var n = index + 1,
251             line = null,
252             column = -1;
253
254         while (--n >= 0 && inputStream.charAt(n) !== '\n') {
255             column++;
256         }
257
258         if (typeof index === 'number') {
259             line = (inputStream.slice(0, index).match(/\n/g) || "").length;
260         }
261
262         return {
263             line: line,
264             column: column
265         };
266     }
267
268     function getDebugInfo(index, inputStream, env) {
269         var filename = env.currentFileInfo.filename;
270         if(less.mode !== 'browser' && less.mode !== 'rhino') {
271             filename = require('path').resolve(filename);
272         }
273
274         return {
275             lineNumber: getLocation(index, inputStream).line + 1,
276             fileName: filename
277         };
278     }
279
280     function LessError(e, env) {
281         var input = getInput(e, env),
282             loc = getLocation(e.index, input),
283             line = loc.line,
284             col  = loc.column,
285             callLine = e.call && getLocation(e.call, input).line,
286             lines = input.split('\n');
287
288         this.type = e.type || 'Syntax';
289         this.message = e.message;
290         this.filename = e.filename || env.currentFileInfo.filename;
291         this.index = e.index;
292         this.line = typeof(line) === 'number' ? line + 1 : null;
293         this.callLine = callLine + 1;
294         this.callExtract = lines[callLine];
295         this.stack = e.stack;
296         this.column = col;
297         this.extract = [
298             lines[line - 1],
299             lines[line],
300             lines[line + 1]
301         ];
302     }
303
304     LessError.prototype = new Error();
305     LessError.prototype.constructor = LessError;
306
307     this.env = env = env || {};
308
309     // The optimization level dictates the thoroughness of the parser,
310     // the lower the number, the less nodes it will create in the tree.
311     // This could matter for debugging, or if you want to access
312     // the individual nodes in the tree.
313     this.optimization = ('optimization' in this.env) ? this.env.optimization : 1;
314
315     //
316     // The Parser
317     //
318     return parser = {
319
320         imports: imports,
321         //
322         // Parse an input string into an abstract syntax tree,
323         // call `callback` when done.
324         //
325         parse: function (str, callback) {
326             var root, line, lines, error = null;
327
328             i = j = current = furthest = 0;
329             input = str.replace(/\r\n/g, '\n');
330
331             // Remove potential UTF Byte Order Mark
332             input = input.replace(/^\uFEFF/, '');
333
334             parser.imports.contents[env.currentFileInfo.filename] = input;
335
336             // Split the input into chunks.
337             chunks = (function (chunks) {
338                 var j = 0,
339                     skip = /(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,
340                     comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
341                     string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,
342                     level = 0,
343                     match,
344                     chunk = chunks[0],
345                     inParam;
346
347                 for (var i = 0, c, cc; i < input.length;) {
348                     skip.lastIndex = i;
349                     if (match = skip.exec(input)) {
350                         if (match.index === i) {
351                             i += match[0].length;
352                             chunk.push(match[0]);
353                         }
354                     }
355                     c = input.charAt(i);
356                     comment.lastIndex = string.lastIndex = i;
357
358                     if (match = string.exec(input)) {
359                         if (match.index === i) {
360                             i += match[0].length;
361                             chunk.push(match[0]);
362                             continue;
363                         }
364                     }
365
366                     if (!inParam && c === '/') {
367                         cc = input.charAt(i + 1);
368                         if (cc === '/' || cc === '*') {
369                             if (match = comment.exec(input)) {
370                                 if (match.index === i) {
371                                     i += match[0].length;
372                                     chunk.push(match[0]);
373                                     continue;
374                                 }
375                             }
376                         }
377                     }
378                     
379                     switch (c) {
380                         case '{':
381                             if (!inParam) {
382                                 level++;
383                                 chunk.push(c);
384                                 break;
385                             }
386                             /* falls through */
387                         case '}':
388                             if (!inParam) {
389                                 level--;
390                                 chunk.push(c);
391                                 chunks[++j] = chunk = [];
392                                 break;
393                             }
394                             /* falls through */
395                         case '(':
396                             if (!inParam) {
397                                 inParam = true;
398                                 chunk.push(c);
399                                 break;
400                             }
401                             /* falls through */
402                         case ')':
403                             if (inParam) {
404                                 inParam = false;
405                                 chunk.push(c);
406                                 break;
407                             }
408                             /* falls through */
409                         default:
410                             chunk.push(c);
411                     }
412                     
413                     i++;
414                 }
415                 if (level !== 0) {
416                     error = new(LessError)({
417                         index: i-1,
418                         type: 'Parse',
419                         message: (level > 0) ? "missing closing `}`" : "missing opening `{`",
420                         filename: env.currentFileInfo.filename
421                     }, env);
422                 }
423
424                 return chunks.map(function (c) { return c.join(''); });
425             })([[]]);
426
427             if (error) {
428                 return callback(new(LessError)(error, env));
429             }
430
431             // Start with the primary rule.
432             // The whole syntax tree is held under a Ruleset node,
433             // with the `root` property set to true, so no `{}` are
434             // output. The callback is called when the input is parsed.
435             try {
436                 root = new(tree.Ruleset)([], $(this.parsers.primary));
437                 root.root = true;
438                 root.firstRoot = true;
439             } catch (e) {
440                 return callback(new(LessError)(e, env));
441             }
442
443             root.toCSS = (function (evaluate) {
444                 return function (options, variables) {
445                     options = options || {};
446                     var evaldRoot,
447                         css,
448                         evalEnv = new tree.evalEnv(options);
449                         
450                     //
451                     // Allows setting variables with a hash, so:
452                     //
453                     //   `{ color: new(tree.Color)('#f01') }` will become:
454                     //
455                     //   new(tree.Rule)('@color',
456                     //     new(tree.Value)([
457                     //       new(tree.Expression)([
458                     //         new(tree.Color)('#f01')
459                     //       ])
460                     //     ])
461                     //   )
462                     //
463                     if (typeof(variables) === 'object' && !Array.isArray(variables)) {
464                         variables = Object.keys(variables).map(function (k) {
465                             var value = variables[k];
466
467                             if (! (value instanceof tree.Value)) {
468                                 if (! (value instanceof tree.Expression)) {
469                                     value = new(tree.Expression)([value]);
470                                 }
471                                 value = new(tree.Value)([value]);
472                             }
473                             return new(tree.Rule)('@' + k, value, false, null, 0);
474                         });
475                         evalEnv.frames = [new(tree.Ruleset)(null, variables)];
476                     }
477
478                     try {
479                         evaldRoot = evaluate.call(this, evalEnv);
480
481                         new(tree.joinSelectorVisitor)()
482                             .run(evaldRoot);
483
484                         new(tree.processExtendsVisitor)()
485                             .run(evaldRoot);
486
487                         new(tree.toCSSVisitor)({compress: Boolean(options.compress)})
488                             .run(evaldRoot);
489
490                         if (options.sourceMap) {
491                             evaldRoot = new tree.sourceMapOutput(
492                                 {
493                                     writeSourceMap: options.writeSourceMap,
494                                     rootNode: evaldRoot,
495                                     contentsMap: parser.imports.contents,
496                                     sourceMapFilename: options.sourceMapFilename,
497                                     sourceMapURL: options.sourceMapURL,
498                                     outputFilename: options.sourceMapOutputFilename,
499                                     sourceMapBasepath: options.sourceMapBasepath,
500                                     sourceMapRootpath: options.sourceMapRootpath,
501                                     outputSourceFiles: options.outputSourceFiles,
502                                     sourceMapGenerator: options.sourceMapGenerator
503                                 });
504                         }
505
506                         css = evaldRoot.toCSS({
507                                 compress: Boolean(options.compress),
508                                 dumpLineNumbers: env.dumpLineNumbers,
509                                 strictUnits: Boolean(options.strictUnits)});
510                     } catch (e) {
511                         throw new(LessError)(e, env);
512                     }
513
514                     if (options.cleancss && less.mode === 'node') {
515                         var CleanCSS = require('clean-css');
516                         //TODO would be nice for no advanced to be an option
517                         return new CleanCSS({keepSpecialComments: '*', processImport: false, noRebase: true, noAdvanced: true}).minify(css);
518                     } else if (options.compress) {
519                         return css.replace(/(^(\s)+)|((\s)+$)/g, "");
520                     } else {
521                         return css;
522                     }
523                 };
524             })(root.eval);
525
526             // If `i` is smaller than the `input.length - 1`,
527             // it means the parser wasn't able to parse the whole
528             // string, so we've got a parsing error.
529             //
530             // We try to extract a \n delimited string,
531             // showing the line where the parse error occured.
532             // We split it up into two parts (the part which parsed,
533             // and the part which didn't), so we can color them differently.
534             if (i < input.length - 1) {
535                 i = furthest;
536                 var loc = getLocation(i, input);
537                 lines = input.split('\n');
538                 line = loc.line + 1;
539
540                 error = {
541                     type: "Parse",
542                     message: "Unrecognised input",
543                     index: i,
544                     filename: env.currentFileInfo.filename,
545                     line: line,
546                     column: loc.column,
547                     extract: [
548                         lines[line - 2],
549                         lines[line - 1],
550                         lines[line]
551                     ]
552                 };
553             }
554
555             var finish = function (e) {
556                 e = error || e || parser.imports.error;
557
558                 if (e) {
559                     if (!(e instanceof LessError)) {
560                         e = new(LessError)(e, env);
561                     }
562
563                     return callback(e);
564                 }
565                 else {
566                     return callback(null, root);
567                 }
568             };
569
570             if (env.processImports !== false) {
571                 new tree.importVisitor(this.imports, finish)
572                     .run(root);
573             } else {
574                 return finish();
575             }
576         },
577
578         //
579         // Here in, the parsing rules/functions
580         //
581         // The basic structure of the syntax tree generated is as follows:
582         //
583         //   Ruleset ->  Rule -> Value -> Expression -> Entity
584         //
585         // Here's some LESS code:
586         //
587         //    .class {
588         //      color: #fff;
589         //      border: 1px solid #000;
590         //      width: @w + 4px;
591         //      > .child {...}
592         //    }
593         //
594         // And here's what the parse tree might look like:
595         //
596         //     Ruleset (Selector '.class', [
597         //         Rule ("color",  Value ([Expression [Color #fff]]))
598         //         Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
599         //         Rule ("width",  Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
600         //         Ruleset (Selector [Element '>', '.child'], [...])
601         //     ])
602         //
603         //  In general, most rules will try to parse a token with the `$()` function, and if the return
604         //  value is truly, will return a new node, of the relevant type. Sometimes, we need to check
605         //  first, before parsing, that's when we use `peek()`.
606         //
607         parsers: {
608             //
609             // The `primary` rule is the *entry* and *exit* point of the parser.
610             // The rules here can appear at any level of the parse tree.
611             //
612             // The recursive nature of the grammar is an interplay between the `block`
613             // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
614             // as represented by this simplified grammar:
615             //
616             //     primary  â†’  (ruleset | rule)+
617             //     ruleset  â†’  selector+ block
618             //     block    â†’  '{' primary '}'
619             //
620             // Only at one point is the primary rule not called from the
621             // block rule: at the root level.
622             //
623             primary: function () {
624                 var node, root = [];
625
626                 while ((node = $(this.extendRule) || $(this.mixin.definition) || $(this.rule)    ||  $(this.ruleset) ||
627                                $(this.mixin.call)       || $(this.comment) ||  $(this.directive))
628                                || $(/^[\s\n]+/) || $(/^;+/)) {
629                     node && root.push(node);
630                 }
631                 return root;
632             },
633
634             // We create a Comment node for CSS comments `/* */`,
635             // but keep the LeSS comments `//` silent, by just skipping
636             // over them.
637             comment: function () {
638                 var comment;
639
640                 if (input.charAt(i) !== '/') { return; }
641
642                 if (input.charAt(i + 1) === '/') {
643                     return new(tree.Comment)($(/^\/\/.*/), true, i, env.currentFileInfo);
644                 } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) {
645                     return new(tree.Comment)(comment, false, i, env.currentFileInfo);
646                 }
647             },
648
649             comments: function () {
650                 var comment, comments = [];
651
652                 while(comment = $(this.comment)) {
653                     comments.push(comment);
654                 }
655
656                 return comments;
657             },
658
659             //
660             // Entities are tokens which can be found inside an Expression
661             //
662             entities: {
663                 //
664                 // A string, which supports escaping " and '
665                 //
666                 //     "milky way" 'he\'s the one!'
667                 //
668                 quoted: function () {
669                     var str, j = i, e, index = i;
670
671                     if (input.charAt(j) === '~') { j++, e = true; } // Escaped strings
672                     if (input.charAt(j) !== '"' && input.charAt(j) !== "'") { return; }
673
674                     e && $('~');
675
676                     if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) {
677                         return new(tree.Quoted)(str[0], str[1] || str[2], e, index, env.currentFileInfo);
678                     }
679                 },
680
681                 //
682                 // A catch-all word, such as:
683                 //
684                 //     black border-collapse
685                 //
686                 keyword: function () {
687                     var k;
688
689                     if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) {
690                         var color = tree.Color.fromKeyword(k);
691                         if (color) {
692                             return color;
693                         }
694                         return new(tree.Keyword)(k);
695                     }
696                 },
697
698                 //
699                 // A function call
700                 //
701                 //     rgb(255, 0, 255)
702                 //
703                 // We also try to catch IE's `alpha()`, but let the `alpha` parser
704                 // deal with the details.
705                 //
706                 // The arguments are parsed with the `entities.arguments` parser.
707                 //
708                 call: function () {
709                     var name, nameLC, args, alpha_ret, index = i;
710
711                     if (! (name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(chunks[j]))) { return; }
712
713                     name = name[1];
714                     nameLC = name.toLowerCase();
715
716                     if (nameLC === 'url') { return null; }
717                     else                  { i += name.length; }
718
719                     if (nameLC === 'alpha') {
720                         alpha_ret = $(this.alpha);
721                         if(typeof alpha_ret !== 'undefined') {
722                             return alpha_ret;
723                         }
724                     }
725
726                     $('('); // Parse the '(' and consume whitespace.
727
728                     args = $(this.entities.arguments);
729
730                     if (! $(')')) {
731                         return;
732                     }
733
734                     if (name) { return new(tree.Call)(name, args, index, env.currentFileInfo); }
735                 },
736                 arguments: function () {
737                     var args = [], arg;
738
739                     while (arg = $(this.entities.assignment) || $(this.expression)) {
740                         args.push(arg);
741                         if (! $(',')) {
742                             break;
743                         }
744                     }
745                     return args;
746                 },
747                 literal: function () {
748                     return $(this.entities.dimension) ||
749                            $(this.entities.color) ||
750                            $(this.entities.quoted) ||
751                            $(this.entities.unicodeDescriptor);
752                 },
753
754                 // Assignments are argument entities for calls.
755                 // They are present in ie filter properties as shown below.
756                 //
757                 //     filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
758                 //
759
760                 assignment: function () {
761                     var key, value;
762                     if ((key = $(/^\w+(?=\s?=)/i)) && $('=') && (value = $(this.entity))) {
763                         return new(tree.Assignment)(key, value);
764                     }
765                 },
766
767                 //
768                 // Parse url() tokens
769                 //
770                 // We use a specific rule for urls, because they don't really behave like
771                 // standard function calls. The difference is that the argument doesn't have
772                 // to be enclosed within a string, so it can't be parsed as an Expression.
773                 //
774                 url: function () {
775                     var value;
776
777                     if (input.charAt(i) !== 'u' || !$(/^url\(/)) {
778                         return;
779                     }
780
781                     value = $(this.entities.quoted)  || $(this.entities.variable) ||
782                             $(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || "";
783
784                     expect(')');
785
786                     /*jshint eqnull:true */
787                     return new(tree.URL)((value.value != null || value instanceof tree.Variable)
788                                         ? value : new(tree.Anonymous)(value), env.currentFileInfo);
789                 },
790
791                 //
792                 // A Variable entity, such as `@fink`, in
793                 //
794                 //     width: @fink + 2px
795                 //
796                 // We use a different parser for variable definitions,
797                 // see `parsers.variable`.
798                 //
799                 variable: function () {
800                     var name, index = i;
801
802                     if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
803                         return new(tree.Variable)(name, index, env.currentFileInfo);
804                     }
805                 },
806
807                 // A variable entity useing the protective {} e.g. @{var}
808                 variableCurly: function () {
809                     var curly, index = i;
810
811                     if (input.charAt(i) === '@' && (curly = $(/^@\{([\w-]+)\}/))) {
812                         return new(tree.Variable)("@" + curly[1], index, env.currentFileInfo);
813                     }
814                 },
815
816                 //
817                 // A Hexadecimal color
818                 //
819                 //     #4F3C2F
820                 //
821                 // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
822                 //
823                 color: function () {
824                     var rgb;
825
826                     if (input.charAt(i) === '#' && (rgb = $(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))) {
827                         return new(tree.Color)(rgb[1]);
828                     }
829                 },
830
831                 //
832                 // A Dimension, that is, a number and a unit
833                 //
834                 //     0.5em 95%
835                 //
836                 dimension: function () {
837                     var value, c = input.charCodeAt(i);
838                     //Is the first char of the dimension 0-9, '.', '+' or '-'
839                     if ((c > 57 || c < 43) || c === 47 || c == 44) {
840                         return;
841                     }
842
843                     if (value = $(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/)) {
844                         return new(tree.Dimension)(value[1], value[2]);
845                     }
846                 },
847
848                 //
849                 // A unicode descriptor, as is used in unicode-range
850                 //
851                 // U+0??  or U+00A1-00A9
852                 //
853                 unicodeDescriptor: function () {
854                     var ud;
855                     
856                     if (ud = $(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/)) {
857                         return new(tree.UnicodeDescriptor)(ud[0]);
858                     }
859                 },
860
861                 //
862                 // JavaScript code to be evaluated
863                 //
864                 //     `window.location.href`
865                 //
866                 javascript: function () {
867                     var str, j = i, e;
868
869                     if (input.charAt(j) === '~') { j++; e = true; } // Escaped strings
870                     if (input.charAt(j) !== '`') { return; }
871                     if (env.javascriptEnabled !== undefined && !env.javascriptEnabled) {
872                         error("You are using JavaScript, which has been disabled.");
873                     }
874
875                     if (e) { $('~'); }
876
877                     if (str = $(/^`([^`]*)`/)) {
878                         return new(tree.JavaScript)(str[1], i, e);
879                     }
880                 }
881             },
882
883             //
884             // The variable part of a variable definition. Used in the `rule` parser
885             //
886             //     @fink:
887             //
888             variable: function () {
889                 var name;
890
891                 if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1]; }
892             },
893
894             //
895             // extend syntax - used to extend selectors
896             //
897             extend: function(isRule) {
898                 var elements, e, index = i, option, extendList = [];
899
900                 if (!$(isRule ? /^&:extend\(/ : /^:extend\(/)) { return; }
901
902                 do {
903                     option = null;
904                     elements = [];
905                     while (true) {
906                         option = $(/^(all)(?=\s*(\)|,))/);
907                         if (option) { break; }
908                         e = $(this.element);
909                         if (!e) { break; }
910                         elements.push(e);
911                     }
912
913                     option = option && option[1];
914
915                     extendList.push(new(tree.Extend)(new(tree.Selector)(elements), option, index));
916
917                 } while($(","));
918                 
919                 expect(/^\)/);
920
921                 if (isRule) {
922                     expect(/^;/);
923                 }
924
925                 return extendList;
926             },
927
928             //
929             // extendRule - used in a rule to extend all the parent selectors
930             //
931             extendRule: function() {
932                 return this.extend(true);
933             },
934             
935             //
936             // Mixins
937             //
938             mixin: {
939                 //
940                 // A Mixin call, with an optional argument list
941                 //
942                 //     #mixins > .square(#fff);
943                 //     .rounded(4px, black);
944                 //     .button;
945                 //
946                 // The `while` loop is there because mixins can be
947                 // namespaced, but we only support the child and descendant
948                 // selector for now.
949                 //
950                 call: function () {
951                     var elements = [], e, c, args, index = i, s = input.charAt(i), important = false;
952
953                     if (s !== '.' && s !== '#') { return; }
954
955                     save(); // stop us absorbing part of an invalid selector
956
957                     while (e = $(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)) {
958                         elements.push(new(tree.Element)(c, e, i, env.currentFileInfo));
959                         c = $('>');
960                     }
961                     if ($('(')) {
962                         args = this.mixin.args.call(this, true).args;
963                         expect(')');
964                     }
965
966                     args = args || [];
967
968                     if ($(this.important)) {
969                         important = true;
970                     }
971
972                     if (elements.length > 0 && ($(';') || peek('}'))) {
973                         return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important);
974                     }
975
976                     restore();
977                 },
978                 args: function (isCall) {
979                     var expressions = [], argsSemiColon = [], isSemiColonSeperated, argsComma = [], expressionContainsNamed, name, nameLoop, value, arg,
980                         returner = {args:null, variadic: false};
981                     while (true) {
982                         if (isCall) {
983                             arg = $(this.expression);
984                         } else {
985                             $(this.comments);
986                             if (input.charAt(i) === '.' && $(/^\.{3}/)) {
987                                 returner.variadic = true;
988                                 if ($(";") && !isSemiColonSeperated) {
989                                     isSemiColonSeperated = true;
990                                 }
991                                 (isSemiColonSeperated ? argsSemiColon : argsComma)
992                                     .push({ variadic: true });
993                                 break;
994                             }
995                             arg = $(this.entities.variable) || $(this.entities.literal)
996                                 || $(this.entities.keyword);
997                         }
998
999                         if (!arg) {
1000                             break;
1001                         }
1002
1003                         nameLoop = null;
1004                         if (arg.throwAwayComments) {
1005                             arg.throwAwayComments();
1006                         }
1007                         value = arg;
1008                         var val = null;
1009
1010                         if (isCall) {
1011                             // Variable
1012                             if (arg.value.length == 1) {
1013                                 val = arg.value[0];
1014                             }
1015                         } else {
1016                             val = arg;
1017                         }
1018
1019                         if (val && val instanceof tree.Variable) {
1020                             if ($(':')) {
1021                                 if (expressions.length > 0) {
1022                                     if (isSemiColonSeperated) {
1023                                         error("Cannot mix ; and , as delimiter types");
1024                                     }
1025                                     expressionContainsNamed = true;
1026                                 }
1027                                 value = expect(this.expression);
1028                                 nameLoop = (name = val.name);
1029                             } else if (!isCall && $(/^\.{3}/)) {
1030                                 returner.variadic = true;
1031                                 if ($(";") && !isSemiColonSeperated) {
1032                                     isSemiColonSeperated = true;
1033                                 }
1034                                 (isSemiColonSeperated ? argsSemiColon : argsComma)
1035                                     .push({ name: arg.name, variadic: true });
1036                                 break;
1037                             } else if (!isCall) {
1038                                 name = nameLoop = val.name;
1039                                 value = null;
1040                             }
1041                         }
1042
1043                         if (value) {
1044                             expressions.push(value);
1045                         }
1046
1047                         argsComma.push({ name:nameLoop, value:value });
1048
1049                         if ($(',')) {
1050                             continue;
1051                         }
1052
1053                         if ($(';') || isSemiColonSeperated) {
1054
1055                             if (expressionContainsNamed) {
1056                                 error("Cannot mix ; and , as delimiter types");
1057                             }
1058
1059                             isSemiColonSeperated = true;
1060
1061                             if (expressions.length > 1) {
1062                                 value = new(tree.Value)(expressions);
1063                             }
1064                             argsSemiColon.push({ name:name, value:value });
1065
1066                             name = null;
1067                             expressions = [];
1068                             expressionContainsNamed = false;
1069                         }
1070                     }
1071
1072                     returner.args = isSemiColonSeperated ? argsSemiColon : argsComma;
1073                     return returner;
1074                 },
1075                 //
1076                 // A Mixin definition, with a list of parameters
1077                 //
1078                 //     .rounded (@radius: 2px, @color) {
1079                 //        ...
1080                 //     }
1081                 //
1082                 // Until we have a finer grained state-machine, we have to
1083                 // do a look-ahead, to make sure we don't have a mixin call.
1084                 // See the `rule` function for more information.
1085                 //
1086                 // We start by matching `.rounded (`, and then proceed on to
1087                 // the argument list, which has optional default values.
1088                 // We store the parameters in `params`, with a `value` key,
1089                 // if there is a value, such as in the case of `@radius`.
1090                 //
1091                 // Once we've got our params list, and a closing `)`, we parse
1092                 // the `{...}` block.
1093                 //
1094                 definition: function () {
1095                     var name, params = [], match, ruleset, cond, variadic = false;
1096                     if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') ||
1097                         peek(/^[^{]*\}/)) {
1098                         return;
1099                     }
1100
1101                     save();
1102
1103                     if (match = $(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)) {
1104                         name = match[1];
1105
1106                         var argInfo = this.mixin.args.call(this, false);
1107                         params = argInfo.args;
1108                         variadic = argInfo.variadic;
1109
1110                         // .mixincall("@{a}");
1111                         // looks a bit like a mixin definition.. so we have to be nice and restore
1112                         if (!$(')')) {
1113                             furthest = i;
1114                             restore();
1115                         }
1116                         
1117                         $(this.comments);
1118
1119                         if ($(/^when/)) { // Guard
1120                             cond = expect(this.conditions, 'expected condition');
1121                         }
1122
1123                         ruleset = $(this.block);
1124
1125                         if (ruleset) {
1126                             return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
1127                         } else {
1128                             restore();
1129                         }
1130                     }
1131                 }
1132             },
1133
1134             //
1135             // Entities are the smallest recognized token,
1136             // and can be found inside a rule's value.
1137             //
1138             entity: function () {
1139                 return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) ||
1140                        $(this.entities.call)    || $(this.entities.keyword)  ||$(this.entities.javascript) ||
1141                        $(this.comment);
1142             },
1143
1144             //
1145             // A Rule terminator. Note that we use `peek()` to check for '}',
1146             // because the `block` rule will be expecting it, but we still need to make sure
1147             // it's there, if ';' was ommitted.
1148             //
1149             end: function () {
1150                 return $(';') || peek('}');
1151             },
1152
1153             //
1154             // IE's alpha function
1155             //
1156             //     alpha(opacity=88)
1157             //
1158             alpha: function () {
1159                 var value;
1160
1161                 if (! $(/^\(opacity=/i)) { return; }
1162                 if (value = $(/^\d+/) || $(this.entities.variable)) {
1163                     expect(')');
1164                     return new(tree.Alpha)(value);
1165                 }
1166             },
1167
1168             //
1169             // A Selector Element
1170             //
1171             //     div
1172             //     + h1
1173             //     #socks
1174             //     input[type="text"]
1175             //
1176             // Elements are the building blocks for Selectors,
1177             // they are made out of a `Combinator` (see combinator rule),
1178             // and an element name, such as a tag a class, or `*`.
1179             //
1180             element: function () {
1181                 var e, c, v;
1182
1183                 c = $(this.combinator);
1184
1185                 e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
1186                     $('*') || $('&') || $(this.attribute) || $(/^\([^()@]+\)/) || $(/^[\.#](?=@)/) || $(this.entities.variableCurly);
1187
1188                 if (! e) {
1189                     if ($('(')) {
1190                         if ((v = ($(this.selector))) &&
1191                                 $(')')) {
1192                             e = new(tree.Paren)(v);
1193                         }
1194                     }
1195                 }
1196
1197                 if (e) { return new(tree.Element)(c, e, i, env.currentFileInfo); }
1198             },
1199
1200             //
1201             // Combinators combine elements together, in a Selector.
1202             //
1203             // Because our parser isn't white-space sensitive, special care
1204             // has to be taken, when parsing the descendant combinator, ` `,
1205             // as it's an empty space. We have to check the previous character
1206             // in the input, to see if it's a ` ` character. More info on how
1207             // we deal with this in *combinator.js*.
1208             //
1209             combinator: function () {
1210                 var c = input.charAt(i);
1211
1212                 if (c === '>' || c === '+' || c === '~' || c === '|') {
1213                     i++;
1214                     while (input.charAt(i).match(/\s/)) { i++; }
1215                     return new(tree.Combinator)(c);
1216                 } else if (input.charAt(i - 1).match(/\s/)) {
1217                     return new(tree.Combinator)(" ");
1218                 } else {
1219                     return new(tree.Combinator)(null);
1220                 }
1221             },
1222             //
1223             // A CSS selector (see selector below)
1224             // with less extensions e.g. the ability to extend and guard
1225             //
1226             lessSelector: function () {
1227                 return this.selector(true);
1228             },
1229             //
1230             // A CSS Selector
1231             //
1232             //     .class > div + h1
1233             //     li a:hover
1234             //
1235             // Selectors are made out of one or more Elements, see above.
1236             //
1237             selector: function (isLess) {
1238                 var e, elements = [], c, extend, extendList = [], when, condition;
1239
1240                 while ((isLess && (extend = $(this.extend))) || (isLess && (when = $(/^when/))) || (e = $(this.element))) {
1241                     if (when) {
1242                         condition = expect(this.conditions, 'expected condition');
1243                     } else if (condition) {
1244                         error("CSS guard can only be used at the end of selector");
1245                     } else if (extend) {
1246                         extendList.push.apply(extendList, extend);
1247                     } else {
1248                         if (extendList.length) {
1249                             error("Extend can only be used at the end of selector");
1250                         }
1251                         c = input.charAt(i);
1252                         elements.push(e);
1253                         e = null;
1254                     }
1255                     if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') {
1256                         break;
1257                     }
1258                 }
1259
1260                 if (elements.length > 0) { return new(tree.Selector)(elements, extendList, condition, i, env.currentFileInfo); }
1261                 if (extendList.length) { error("Extend must be used to extend a selector, it cannot be used on its own"); }
1262             },
1263             attribute: function () {
1264                 var key, val, op;
1265
1266                 if (! $('[')) { return; }
1267
1268                 if (!(key = $(this.entities.variableCurly))) {
1269                     key = expect(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/);
1270                 }
1271
1272                 if ((op = $(/^[|~*$^]?=/))) {
1273                     val = $(this.entities.quoted) || $(/^[0-9]+%/) || $(/^[\w-]+/) || $(this.entities.variableCurly);
1274                 }
1275
1276                 expect(']');
1277
1278                 return new(tree.Attribute)(key, op, val);
1279             },
1280
1281             //
1282             // The `block` rule is used by `ruleset` and `mixin.definition`.
1283             // It's a wrapper around the `primary` rule, with added `{}`.
1284             //
1285             block: function () {
1286                 var content;
1287                 if ($('{') && (content = $(this.primary)) && $('}')) {
1288                     return content;
1289                 }
1290             },
1291
1292             //
1293             // div, .class, body > p {...}
1294             //
1295             ruleset: function () {
1296                 var selectors = [], s, rules, debugInfo;
1297                 
1298                 save();
1299
1300                 if (env.dumpLineNumbers) {
1301                     debugInfo = getDebugInfo(i, input, env);
1302                 }
1303
1304                 while (s = $(this.lessSelector)) {
1305                     selectors.push(s);
1306                     $(this.comments);
1307                     if (! $(',')) { break; }
1308                     if (s.condition) {
1309                         error("Guards are only currently allowed on a single selector.");
1310                     }
1311                     $(this.comments);
1312                 }
1313
1314                 if (selectors.length > 0 && (rules = $(this.block))) {
1315                     var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports);
1316                     if (env.dumpLineNumbers) {
1317                         ruleset.debugInfo = debugInfo;
1318                     }
1319                     return ruleset;
1320                 } else {
1321                     // Backtrack
1322                     furthest = i;
1323                     restore();
1324                 }
1325             },
1326             rule: function (tryAnonymous) {
1327                 var name, value, c = input.charAt(i), important, merge = false;
1328                 save();
1329
1330                 if (c === '.' || c === '#' || c === '&') { return; }
1331
1332                 if (name = $(this.variable) || $(this.ruleProperty)) {
1333                     // prefer to try to parse first if its a variable or we are compressing
1334                     // but always fallback on the other one
1335                     value = !tryAnonymous && (env.compress || (name.charAt(0) === '@')) ?
1336                         ($(this.value) || $(this.anonymousValue)) :
1337                         ($(this.anonymousValue) || $(this.value));
1338
1339
1340                     important = $(this.important);
1341                     if (name[name.length-1] === "+") {
1342                         merge = true;
1343                         name = name.substr(0, name.length - 1);
1344                     }
1345
1346                     if (value && $(this.end)) {
1347                         return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo);
1348                     } else {
1349                         furthest = i;
1350                         restore();
1351                         if (value && !tryAnonymous) {
1352                             return this.rule(true);
1353                         }
1354                     }
1355                 }
1356             },
1357             anonymousValue: function () {
1358                 var match;
1359                 if (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j])) {
1360                     i += match[0].length - 1;
1361                     return new(tree.Anonymous)(match[1]);
1362                 }
1363             },
1364
1365             //
1366             // An @import directive
1367             //
1368             //     @import "lib";
1369             //
1370             // Depending on our environemnt, importing is done differently:
1371             // In the browser, it's an XHR request, in Node, it would be a
1372             // file-system operation. The function used for importing is
1373             // stored in `import`, which we pass to the Import constructor.
1374             //
1375             "import": function () {
1376                 var path, features, index = i;
1377
1378                 save();
1379
1380                 var dir = $(/^@import?\s+/);
1381
1382                 var options = (dir ? $(this.importOptions) : null) || {};
1383
1384                 if (dir && (path = $(this.entities.quoted) || $(this.entities.url))) {
1385                     features = $(this.mediaFeatures);
1386                     if ($(';')) {
1387                         features = features && new(tree.Value)(features);
1388                         return new(tree.Import)(path, features, options, index, env.currentFileInfo);
1389                     }
1390                 }
1391
1392                 restore();
1393             },
1394
1395             importOptions: function() {
1396                 var o, options = {}, optionName, value;
1397
1398                 // list of options, surrounded by parens
1399                 if (! $('(')) { return null; }
1400                 do {
1401                     if (o = $(this.importOption)) {
1402                         optionName = o;
1403                         value = true;
1404                         switch(optionName) {
1405                             case "css":
1406                                 optionName = "less";
1407                                 value = false;
1408                             break;
1409                             case "once":
1410                                 optionName = "multiple";
1411                                 value = false;
1412                             break;
1413                         }
1414                         options[optionName] = value;
1415                         if (! $(',')) { break; }
1416                     }
1417                 } while (o);
1418                 expect(')');
1419                 return options;
1420             },
1421
1422             importOption: function() {
1423                 var opt = $(/^(less|css|multiple|once|inline|reference)/);
1424                 if (opt) {
1425                     return opt[1];
1426                 }
1427             },
1428
1429             mediaFeature: function () {
1430                 var e, p, nodes = [];
1431
1432                 do {
1433                     if (e = ($(this.entities.keyword) || $(this.entities.variable))) {
1434                         nodes.push(e);
1435                     } else if ($('(')) {
1436                         p = $(this.property);
1437                         e = $(this.value);
1438                         if ($(')')) {
1439                             if (p && e) {
1440                                 nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, null, i, env.currentFileInfo, true)));
1441                             } else if (e) {
1442                                 nodes.push(new(tree.Paren)(e));
1443                             } else {
1444                                 return null;
1445                             }
1446                         } else { return null; }
1447                     }
1448                 } while (e);
1449
1450                 if (nodes.length > 0) {
1451                     return new(tree.Expression)(nodes);
1452                 }
1453             },
1454
1455             mediaFeatures: function () {
1456                 var e, features = [];
1457
1458                 do {
1459                   if (e = $(this.mediaFeature)) {
1460                       features.push(e);
1461                       if (! $(',')) { break; }
1462                   } else if (e = $(this.entities.variable)) {
1463                       features.push(e);
1464                       if (! $(',')) { break; }
1465                   }
1466                 } while (e);
1467
1468                 return features.length > 0 ? features : null;
1469             },
1470
1471             media: function () {
1472                 var features, rules, media, debugInfo;
1473
1474                 if (env.dumpLineNumbers) {
1475                     debugInfo = getDebugInfo(i, input, env);
1476                 }
1477
1478                 if ($(/^@media/)) {
1479                     features = $(this.mediaFeatures);
1480
1481                     if (rules = $(this.block)) {
1482                         media = new(tree.Media)(rules, features, i, env.currentFileInfo);
1483                         if (env.dumpLineNumbers) {
1484                             media.debugInfo = debugInfo;
1485                         }
1486                         return media;
1487                     }
1488                 }
1489             },
1490
1491             //
1492             // A CSS Directive
1493             //
1494             //     @charset "utf-8";
1495             //
1496             directive: function () {
1497                 var name, value, rules, nonVendorSpecificName,
1498                     hasBlock, hasIdentifier, hasExpression, identifier;
1499
1500                 if (input.charAt(i) !== '@') { return; }
1501
1502                 if (value = $(this['import']) || $(this.media)) {
1503                     return value;
1504                 }
1505
1506                 save();
1507
1508                 name = $(/^@[a-z-]+/);
1509                 
1510                 if (!name) { return; }
1511
1512                 nonVendorSpecificName = name;
1513                 if (name.charAt(1) == '-' && name.indexOf('-', 2) > 0) {
1514                     nonVendorSpecificName = "@" + name.slice(name.indexOf('-', 2) + 1);
1515                 }
1516
1517                 switch(nonVendorSpecificName) {
1518                     case "@font-face":
1519                         hasBlock = true;
1520                         break;
1521                     case "@viewport":
1522                     case "@top-left":
1523                     case "@top-left-corner":
1524                     case "@top-center":
1525                     case "@top-right":
1526                     case "@top-right-corner":
1527                     case "@bottom-left":
1528                     case "@bottom-left-corner":
1529                     case "@bottom-center":
1530                     case "@bottom-right":
1531                     case "@bottom-right-corner":
1532                     case "@left-top":
1533                     case "@left-middle":
1534                     case "@left-bottom":
1535                     case "@right-top":
1536                     case "@right-middle":
1537                     case "@right-bottom":
1538                         hasBlock = true;
1539                         break;
1540                     case "@host":
1541                     case "@page":
1542                     case "@document":
1543                     case "@supports":
1544                     case "@keyframes":
1545                         hasBlock = true;
1546                         hasIdentifier = true;
1547                         break;
1548                     case "@namespace":
1549                         hasExpression = true;
1550                         break;
1551                 }
1552
1553                 if (hasIdentifier) {
1554                     identifier = ($(/^[^{]+/) || '').trim();
1555                     if (identifier) {
1556                         name += " " + identifier;
1557                     }
1558                 }
1559
1560                 if (hasBlock)
1561                 {
1562                     if (rules = $(this.block)) {
1563                         return new(tree.Directive)(name, rules, i, env.currentFileInfo);
1564                     }
1565                 } else {
1566                     if ((value = hasExpression ? $(this.expression) : $(this.entity)) && $(';')) {
1567                         var directive = new(tree.Directive)(name, value, i, env.currentFileInfo);
1568                         if (env.dumpLineNumbers) {
1569                             directive.debugInfo = getDebugInfo(i, input, env);
1570                         }
1571                         return directive;
1572                     }
1573                 }
1574
1575                 restore();
1576             },
1577
1578             //
1579             // A Value is a comma-delimited list of Expressions
1580             //
1581             //     font-family: Baskerville, Georgia, serif;
1582             //
1583             // In a Rule, a Value represents everything after the `:`,
1584             // and before the `;`.
1585             //
1586             value: function () {
1587                 var e, expressions = [];
1588
1589                 while (e = $(this.expression)) {
1590                     expressions.push(e);
1591                     if (! $(',')) { break; }
1592                 }
1593
1594                 if (expressions.length > 0) {
1595                     return new(tree.Value)(expressions);
1596                 }
1597             },
1598             important: function () {
1599                 if (input.charAt(i) === '!') {
1600                     return $(/^! *important/);
1601                 }
1602             },
1603             sub: function () {
1604                 var a, e;
1605
1606                 if ($('(')) {
1607                     if (a = $(this.addition)) {
1608                         e = new(tree.Expression)([a]);
1609                         expect(')');
1610                         e.parens = true;
1611                         return e;
1612                     }
1613                 }
1614             },
1615             multiplication: function () {
1616                 var m, a, op, operation, isSpaced;
1617                 if (m = $(this.operand)) {
1618                     isSpaced = isWhitespace(input.charAt(i - 1));
1619                     while (!peek(/^\/[*\/]/) && (op = ($('/') || $('*')))) {
1620                         if (a = $(this.operand)) {
1621                             m.parensInOp = true;
1622                             a.parensInOp = true;
1623                             operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
1624                             isSpaced = isWhitespace(input.charAt(i - 1));
1625                         } else {
1626                             break;
1627                         }
1628                     }
1629                     return operation || m;
1630                 }
1631             },
1632             addition: function () {
1633                 var m, a, op, operation, isSpaced;
1634                 if (m = $(this.multiplication)) {
1635                     isSpaced = isWhitespace(input.charAt(i - 1));
1636                     while ((op = $(/^[-+]\s+/) || (!isSpaced && ($('+') || $('-')))) &&
1637                            (a = $(this.multiplication))) {
1638                         m.parensInOp = true;
1639                         a.parensInOp = true;
1640                         operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
1641                         isSpaced = isWhitespace(input.charAt(i - 1));
1642                     }
1643                     return operation || m;
1644                 }
1645             },
1646             conditions: function () {
1647                 var a, b, index = i, condition;
1648
1649                 if (a = $(this.condition)) {
1650                     while (peek(/^,\s*(not\s*)?\(/) && $(',') && (b = $(this.condition))) {
1651                         condition = new(tree.Condition)('or', condition || a, b, index);
1652                     }
1653                     return condition || a;
1654                 }
1655             },
1656             condition: function () {
1657                 var a, b, c, op, index = i, negate = false;
1658
1659                 if ($(/^not/)) { negate = true; }
1660                 expect('(');
1661                 if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1662                     if (op = $(/^(?:>=|<=|=<|[<=>])/)) {
1663                         if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1664                             c = new(tree.Condition)(op, a, b, index, negate);
1665                         } else {
1666                             error('expected expression');
1667                         }
1668                     } else {
1669                         c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate);
1670                     }
1671                     expect(')');
1672                     return $(/^and/) ? new(tree.Condition)('and', c, $(this.condition)) : c;
1673                 }
1674             },
1675
1676             //
1677             // An operand is anything that can be part of an operation,
1678             // such as a Color, or a Variable
1679             //
1680             operand: function () {
1681                 var negate, p = input.charAt(i + 1);
1682
1683                 if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-'); }
1684                 var o = $(this.sub) || $(this.entities.dimension) ||
1685                         $(this.entities.color) || $(this.entities.variable) ||
1686                         $(this.entities.call);
1687
1688                 if (negate) {
1689                     o.parensInOp = true;
1690                     o = new(tree.Negative)(o);
1691                 }
1692
1693                 return o;
1694             },
1695
1696             //
1697             // Expressions either represent mathematical operations,
1698             // or white-space delimited Entities.
1699             //
1700             //     1px solid black
1701             //     @var * 2
1702             //
1703             expression: function () {
1704                 var e, delim, entities = [];
1705
1706                 while (e = $(this.addition) || $(this.entity)) {
1707                     entities.push(e);
1708                     // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
1709                     if (!peek(/^\/[\/*]/) && (delim = $('/'))) {
1710                         entities.push(new(tree.Anonymous)(delim));
1711                     }
1712                 }
1713                 if (entities.length > 0) {
1714                     return new(tree.Expression)(entities);
1715                 }
1716             },
1717             property: function () {
1718                 var name;
1719
1720                 if (name = $(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/)) {
1721                     return name[1];
1722                 }
1723             },
1724             ruleProperty: function () {
1725                 var name;
1726
1727                 if (name = $(/^(\*?-?[_a-zA-Z0-9-]+)\s*(\+?)\s*:/)) {
1728                     return name[1] + (name[2] || "");
1729                 }
1730             }
1731         }
1732     };
1733 };
1734
1735
1736 (function (tree) {
1737
1738 tree.functions = {
1739     rgb: function (r, g, b) {
1740         return this.rgba(r, g, b, 1.0);
1741     },
1742     rgba: function (r, g, b, a) {
1743         var rgb = [r, g, b].map(function (c) { return scaled(c, 256); });
1744         a = number(a);
1745         return new(tree.Color)(rgb, a);
1746     },
1747     hsl: function (h, s, l) {
1748         return this.hsla(h, s, l, 1.0);
1749     },
1750     hsla: function (h, s, l, a) {
1751         function hue(h) {
1752             h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h);
1753             if      (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; }
1754             else if (h * 2 < 1) { return m2; }
1755             else if (h * 3 < 2) { return m1 + (m2 - m1) * (2/3 - h) * 6; }
1756             else                { return m1; }
1757         }
1758
1759         h = (number(h) % 360) / 360;
1760         s = clamp(number(s)); l = clamp(number(l)); a = clamp(number(a));
1761
1762         var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
1763         var m1 = l * 2 - m2;
1764
1765         return this.rgba(hue(h + 1/3) * 255,
1766                          hue(h)       * 255,
1767                          hue(h - 1/3) * 255,
1768                          a);
1769     },
1770
1771     hsv: function(h, s, v) {
1772         return this.hsva(h, s, v, 1.0);
1773     },
1774
1775     hsva: function(h, s, v, a) {
1776         h = ((number(h) % 360) / 360) * 360;
1777         s = number(s); v = number(v); a = number(a);
1778
1779         var i, f;
1780         i = Math.floor((h / 60) % 6);
1781         f = (h / 60) - i;
1782
1783         var vs = [v,
1784                   v * (1 - s),
1785                   v * (1 - f * s),
1786                   v * (1 - (1 - f) * s)];
1787         var perm = [[0, 3, 1],
1788                     [2, 0, 1],
1789                     [1, 0, 3],
1790                     [1, 2, 0],
1791                     [3, 1, 0],
1792                     [0, 1, 2]];
1793
1794         return this.rgba(vs[perm[i][0]] * 255,
1795                          vs[perm[i][1]] * 255,
1796                          vs[perm[i][2]] * 255,
1797                          a);
1798     },
1799
1800     hue: function (color) {
1801         return new(tree.Dimension)(Math.round(color.toHSL().h));
1802     },
1803     saturation: function (color) {
1804         return new(tree.Dimension)(Math.round(color.toHSL().s * 100), '%');
1805     },
1806     lightness: function (color) {
1807         return new(tree.Dimension)(Math.round(color.toHSL().l * 100), '%');
1808     },
1809     hsvhue: function(color) {
1810         return new(tree.Dimension)(Math.round(color.toHSV().h));
1811     },
1812     hsvsaturation: function (color) {
1813         return new(tree.Dimension)(Math.round(color.toHSV().s * 100), '%');
1814     },
1815     hsvvalue: function (color) {
1816         return new(tree.Dimension)(Math.round(color.toHSV().v * 100), '%');
1817     },
1818     red: function (color) {
1819         return new(tree.Dimension)(color.rgb[0]);
1820     },
1821     green: function (color) {
1822         return new(tree.Dimension)(color.rgb[1]);
1823     },
1824     blue: function (color) {
1825         return new(tree.Dimension)(color.rgb[2]);
1826     },
1827     alpha: function (color) {
1828         return new(tree.Dimension)(color.toHSL().a);
1829     },
1830     luma: function (color) {
1831         return new(tree.Dimension)(Math.round(color.luma() * color.alpha * 100), '%');
1832     },
1833     saturate: function (color, amount) {
1834         // filter: saturate(3.2);
1835         // should be kept as is, so check for color
1836         if (!color.rgb) {
1837             return null;
1838         }
1839         var hsl = color.toHSL();
1840
1841         hsl.s += amount.value / 100;
1842         hsl.s = clamp(hsl.s);
1843         return hsla(hsl);
1844     },
1845     desaturate: function (color, amount) {
1846         var hsl = color.toHSL();
1847
1848         hsl.s -= amount.value / 100;
1849         hsl.s = clamp(hsl.s);
1850         return hsla(hsl);
1851     },
1852     lighten: function (color, amount) {
1853         var hsl = color.toHSL();
1854
1855         hsl.l += amount.value / 100;
1856         hsl.l = clamp(hsl.l);
1857         return hsla(hsl);
1858     },
1859     darken: function (color, amount) {
1860         var hsl = color.toHSL();
1861
1862         hsl.l -= amount.value / 100;
1863         hsl.l = clamp(hsl.l);
1864         return hsla(hsl);
1865     },
1866     fadein: function (color, amount) {
1867         var hsl = color.toHSL();
1868
1869         hsl.a += amount.value / 100;
1870         hsl.a = clamp(hsl.a);
1871         return hsla(hsl);
1872     },
1873     fadeout: function (color, amount) {
1874         var hsl = color.toHSL();
1875
1876         hsl.a -= amount.value / 100;
1877         hsl.a = clamp(hsl.a);
1878         return hsla(hsl);
1879     },
1880     fade: function (color, amount) {
1881         var hsl = color.toHSL();
1882
1883         hsl.a = amount.value / 100;
1884         hsl.a = clamp(hsl.a);
1885         return hsla(hsl);
1886     },
1887     spin: function (color, amount) {
1888         var hsl = color.toHSL();
1889         var hue = (hsl.h + amount.value) % 360;
1890
1891         hsl.h = hue < 0 ? 360 + hue : hue;
1892
1893         return hsla(hsl);
1894     },
1895     //
1896     // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
1897     // http://sass-lang.com
1898     //
1899     mix: function (color1, color2, weight) {
1900         if (!weight) {
1901             weight = new(tree.Dimension)(50);
1902         }
1903         var p = weight.value / 100.0;
1904         var w = p * 2 - 1;
1905         var a = color1.toHSL().a - color2.toHSL().a;
1906
1907         var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
1908         var w2 = 1 - w1;
1909
1910         var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2,
1911                    color1.rgb[1] * w1 + color2.rgb[1] * w2,
1912                    color1.rgb[2] * w1 + color2.rgb[2] * w2];
1913
1914         var alpha = color1.alpha * p + color2.alpha * (1 - p);
1915
1916         return new(tree.Color)(rgb, alpha);
1917     },
1918     greyscale: function (color) {
1919         return this.desaturate(color, new(tree.Dimension)(100));
1920     },
1921     contrast: function (color, dark, light, threshold) {
1922         // filter: contrast(3.2);
1923         // should be kept as is, so check for color
1924         if (!color.rgb) {
1925             return null;
1926         }
1927         if (typeof light === 'undefined') {
1928             light = this.rgba(255, 255, 255, 1.0);
1929         }
1930         if (typeof dark === 'undefined') {
1931             dark = this.rgba(0, 0, 0, 1.0);
1932         }
1933         //Figure out which is actually light and dark!
1934         if (dark.luma() > light.luma()) {
1935             var t = light;
1936             light = dark;
1937             dark = t;
1938         }
1939         if (typeof threshold === 'undefined') {
1940             threshold = 0.43;
1941         } else {
1942             threshold = number(threshold);
1943         }
1944         if ((color.luma() * color.alpha) < threshold) {
1945             return light;
1946         } else {
1947             return dark;
1948         }
1949     },
1950     e: function (str) {
1951         return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str);
1952     },
1953     escape: function (str) {
1954         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"));
1955     },
1956     '%': function (quoted /* arg, arg, ...*/) {
1957         var args = Array.prototype.slice.call(arguments, 1),
1958             str = quoted.value;
1959
1960         for (var i = 0; i < args.length; i++) {
1961             /*jshint loopfunc:true */
1962             str = str.replace(/%[sda]/i, function(token) {
1963                 var value = token.match(/s/i) ? args[i].value : args[i].toCSS();
1964                 return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value;
1965             });
1966         }
1967         str = str.replace(/%%/g, '%');
1968         return new(tree.Quoted)('"' + str + '"', str);
1969     },
1970     unit: function (val, unit) {
1971         if(!(val instanceof tree.Dimension)) {
1972             throw { type: "Argument", message: "the first argument to unit must be a number" + (val instanceof tree.Operation ? ". Have you forgotten parenthesis?" : "") };
1973         }
1974         return new(tree.Dimension)(val.value, unit ? unit.toCSS() : "");
1975     },
1976     convert: function (val, unit) {
1977         return val.convertTo(unit.value);
1978     },
1979     round: function (n, f) {
1980         var fraction = typeof(f) === "undefined" ? 0 : f.value;
1981         return this._math(function(num) { return num.toFixed(fraction); }, null, n);
1982     },
1983     pi: function () {
1984         return new(tree.Dimension)(Math.PI);
1985     },
1986     mod: function(a, b) {
1987         return new(tree.Dimension)(a.value % b.value, a.unit);
1988     },
1989     pow: function(x, y) {
1990         if (typeof x === "number" && typeof y === "number") {
1991             x = new(tree.Dimension)(x);
1992             y = new(tree.Dimension)(y);
1993         } else if (!(x instanceof tree.Dimension) || !(y instanceof tree.Dimension)) {
1994             throw { type: "Argument", message: "arguments must be numbers" };
1995         }
1996
1997         return new(tree.Dimension)(Math.pow(x.value, y.value), x.unit);
1998     },
1999     _math: function (fn, unit, n) {
2000         if (n instanceof tree.Dimension) {
2001             /*jshint eqnull:true */
2002             return new(tree.Dimension)(fn(parseFloat(n.value)), unit == null ? n.unit : unit);
2003         } else if (typeof(n) === 'number') {
2004             return fn(n);
2005         } else {
2006             throw { type: "Argument", message: "argument must be a number" };
2007         }
2008     },
2009     _minmax: function (isMin, args) {
2010         args = Array.prototype.slice.call(args);
2011         switch(args.length) {
2012         case 0: throw { type: "Argument", message: "one or more arguments required" };
2013         case 1: return args[0];
2014         }
2015         var i, j, current, currentUnified, referenceUnified, unit,
2016             order  = [], // elems only contains original argument values.
2017             values = {}; // key is the unit.toString() for unified tree.Dimension values,
2018                          // value is the index into the order array.
2019         for (i = 0; i < args.length; i++) {
2020             current = args[i];
2021             if (!(current instanceof tree.Dimension)) {
2022                 order.push(current);
2023                 continue;
2024             }
2025             currentUnified = current.unify();
2026             unit = currentUnified.unit.toString();
2027             j = values[unit];
2028             if (j === undefined) {
2029                 values[unit] = order.length;
2030                 order.push(current);
2031                 continue;
2032             }
2033             referenceUnified = order[j].unify();
2034             if ( isMin && currentUnified.value < referenceUnified.value ||
2035                 !isMin && currentUnified.value > referenceUnified.value) {
2036                 order[j] = current;
2037             }
2038         }
2039         if (order.length == 1) {
2040             return order[0];
2041         }
2042         args = order.map(function (a) { return a.toCSS(this.env); })
2043                     .join(this.env.compress ? "," : ", ");
2044         return new(tree.Anonymous)((isMin ? "min" : "max") + "(" + args + ")");
2045     },
2046     min: function () {
2047         return this._minmax(true, arguments);
2048     },
2049     max: function () {
2050         return this._minmax(false, arguments);
2051     },
2052     argb: function (color) {
2053         return new(tree.Anonymous)(color.toARGB());
2054
2055     },
2056     percentage: function (n) {
2057         return new(tree.Dimension)(n.value * 100, '%');
2058     },
2059     color: function (n) {
2060         if (n instanceof tree.Quoted) {
2061             var colorCandidate = n.value,
2062                 returnColor;
2063             returnColor = tree.Color.fromKeyword(colorCandidate);
2064             if (returnColor) {
2065                 return returnColor;
2066             }
2067             if (/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/.test(colorCandidate)) {
2068                 return new(tree.Color)(colorCandidate.slice(1));
2069             }
2070             throw { type: "Argument", message: "argument must be a color keyword or 3/6 digit hex e.g. #FFF" };
2071         } else {
2072             throw { type: "Argument", message: "argument must be a string" };
2073         }
2074     },
2075     iscolor: function (n) {
2076         return this._isa(n, tree.Color);
2077     },
2078     isnumber: function (n) {
2079         return this._isa(n, tree.Dimension);
2080     },
2081     isstring: function (n) {
2082         return this._isa(n, tree.Quoted);
2083     },
2084     iskeyword: function (n) {
2085         return this._isa(n, tree.Keyword);
2086     },
2087     isurl: function (n) {
2088         return this._isa(n, tree.URL);
2089     },
2090     ispixel: function (n) {
2091         return this.isunit(n, 'px');
2092     },
2093     ispercentage: function (n) {
2094         return this.isunit(n, '%');
2095     },
2096     isem: function (n) {
2097         return this.isunit(n, 'em');
2098     },
2099     isunit: function (n, unit) {
2100         return (n instanceof tree.Dimension) && n.unit.is(unit.value || unit) ? tree.True : tree.False;
2101     },
2102     _isa: function (n, Type) {
2103         return (n instanceof Type) ? tree.True : tree.False;
2104     },
2105     
2106     /* Blending modes */
2107     
2108     multiply: function(color1, color2) {
2109         var r = color1.rgb[0] * color2.rgb[0] / 255;
2110         var g = color1.rgb[1] * color2.rgb[1] / 255;
2111         var b = color1.rgb[2] * color2.rgb[2] / 255;
2112         return this.rgb(r, g, b);
2113     },
2114     screen: function(color1, color2) {
2115         var r = 255 - (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255;
2116         var g = 255 - (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255;
2117         var b = 255 - (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255;
2118         return this.rgb(r, g, b);
2119     },
2120     overlay: function(color1, color2) {
2121         var r = color1.rgb[0] < 128 ? 2 * color1.rgb[0] * color2.rgb[0] / 255 : 255 - 2 * (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255;
2122         var g = color1.rgb[1] < 128 ? 2 * color1.rgb[1] * color2.rgb[1] / 255 : 255 - 2 * (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255;
2123         var b = color1.rgb[2] < 128 ? 2 * color1.rgb[2] * color2.rgb[2] / 255 : 255 - 2 * (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255;
2124         return this.rgb(r, g, b);
2125     },
2126     softlight: function(color1, color2) {
2127         var t = color2.rgb[0] * color1.rgb[0] / 255;
2128         var r = t + color1.rgb[0] * (255 - (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255 - t) / 255;
2129         t = color2.rgb[1] * color1.rgb[1] / 255;
2130         var g = t + color1.rgb[1] * (255 - (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255 - t) / 255;
2131         t = color2.rgb[2] * color1.rgb[2] / 255;
2132         var b = t + color1.rgb[2] * (255 - (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255 - t) / 255;
2133         return this.rgb(r, g, b);
2134     },
2135     hardlight: function(color1, color2) {
2136         var r = color2.rgb[0] < 128 ? 2 * color2.rgb[0] * color1.rgb[0] / 255 : 255 - 2 * (255 - color2.rgb[0]) * (255 - color1.rgb[0]) / 255;
2137         var g = color2.rgb[1] < 128 ? 2 * color2.rgb[1] * color1.rgb[1] / 255 : 255 - 2 * (255 - color2.rgb[1]) * (255 - color1.rgb[1]) / 255;
2138         var b = color2.rgb[2] < 128 ? 2 * color2.rgb[2] * color1.rgb[2] / 255 : 255 - 2 * (255 - color2.rgb[2]) * (255 - color1.rgb[2]) / 255;
2139         return this.rgb(r, g, b);
2140     },
2141     difference: function(color1, color2) {
2142         var r = Math.abs(color1.rgb[0] - color2.rgb[0]);
2143         var g = Math.abs(color1.rgb[1] - color2.rgb[1]);
2144         var b = Math.abs(color1.rgb[2] - color2.rgb[2]);
2145         return this.rgb(r, g, b);
2146     },
2147     exclusion: function(color1, color2) {
2148         var r = color1.rgb[0] + color2.rgb[0] * (255 - color1.rgb[0] - color1.rgb[0]) / 255;
2149         var g = color1.rgb[1] + color2.rgb[1] * (255 - color1.rgb[1] - color1.rgb[1]) / 255;
2150         var b = color1.rgb[2] + color2.rgb[2] * (255 - color1.rgb[2] - color1.rgb[2]) / 255;
2151         return this.rgb(r, g, b);
2152     },
2153     average: function(color1, color2) {
2154         var r = (color1.rgb[0] + color2.rgb[0]) / 2;
2155         var g = (color1.rgb[1] + color2.rgb[1]) / 2;
2156         var b = (color1.rgb[2] + color2.rgb[2]) / 2;
2157         return this.rgb(r, g, b);
2158     },
2159     negation: function(color1, color2) {
2160         var r = 255 - Math.abs(255 - color2.rgb[0] - color1.rgb[0]);
2161         var g = 255 - Math.abs(255 - color2.rgb[1] - color1.rgb[1]);
2162         var b = 255 - Math.abs(255 - color2.rgb[2] - color1.rgb[2]);
2163         return this.rgb(r, g, b);
2164     },
2165     tint: function(color, amount) {
2166         return this.mix(this.rgb(255,255,255), color, amount);
2167     },
2168     shade: function(color, amount) {
2169         return this.mix(this.rgb(0, 0, 0), color, amount);
2170     },   
2171     extract: function(values, index) {
2172         index = index.value - 1; // (1-based index)       
2173         // handle non-array values as an array of length 1
2174         // return 'undefined' if index is invalid
2175         return Array.isArray(values.value) 
2176             ? values.value[index] : Array(values)[index];
2177     },
2178     length: function(values) {
2179         var n = Array.isArray(values.value) ? values.value.length : 1;
2180         return new tree.Dimension(n);
2181     },
2182
2183     "data-uri": function(mimetypeNode, filePathNode) {
2184
2185         if (typeof window !== 'undefined') {
2186             return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
2187         }
2188
2189         var mimetype = mimetypeNode.value;
2190         var filePath = (filePathNode && filePathNode.value);
2191
2192         var fs = require("fs"),
2193             path = require("path"),
2194             useBase64 = false;
2195
2196         if (arguments.length < 2) {
2197             filePath = mimetype;
2198         }
2199
2200         if (this.env.isPathRelative(filePath)) {
2201             if (this.currentFileInfo.relativeUrls) {
2202                 filePath = path.join(this.currentFileInfo.currentDirectory, filePath);
2203             } else {
2204                 filePath = path.join(this.currentFileInfo.entryPath, filePath);
2205             }
2206         }
2207
2208         // detect the mimetype if not given
2209         if (arguments.length < 2) {
2210             var mime;
2211             try {
2212                 mime = require('mime');
2213             } catch (ex) {
2214                 mime = tree._mime;
2215             }
2216
2217             mimetype = mime.lookup(filePath);
2218
2219             // use base 64 unless it's an ASCII or UTF-8 format
2220             var charset = mime.charsets.lookup(mimetype);
2221             useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
2222             if (useBase64) { mimetype += ';base64'; }
2223         }
2224         else {
2225             useBase64 = /;base64$/.test(mimetype);
2226         }
2227
2228         var buf = fs.readFileSync(filePath);
2229
2230         // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
2231         // and the --ieCompat flag is enabled, return a normal url() instead.
2232         var DATA_URI_MAX_KB = 32,
2233             fileSizeInKB = parseInt((buf.length / 1024), 10);
2234         if (fileSizeInKB >= DATA_URI_MAX_KB) {
2235
2236             if (this.env.ieCompat !== false) {
2237                 if (!this.env.silent) {
2238                     console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
2239                 }
2240
2241                 return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
2242             }
2243         }
2244
2245         buf = useBase64 ? buf.toString('base64')
2246                         : encodeURIComponent(buf);
2247
2248         var uri = "'data:" + mimetype + ',' + buf + "'";
2249         return new(tree.URL)(new(tree.Anonymous)(uri));
2250     },
2251
2252     "svg-gradient": function(direction) {
2253
2254         function throwArgumentDescriptor() {
2255             throw { type: "Argument", message: "svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]" };
2256         }
2257
2258         if (arguments.length < 3) {
2259             throwArgumentDescriptor();
2260         }
2261         var stops = Array.prototype.slice.call(arguments, 1),
2262             gradientDirectionSvg,
2263             gradientType = "linear",
2264             rectangleDimension = 'x="0" y="0" width="1" height="1"',
2265             useBase64 = true,
2266             renderEnv = {compress: false},
2267             returner,
2268             directionValue = direction.toCSS(renderEnv),
2269             i, color, position, positionValue, alpha;
2270
2271         switch (directionValue) {
2272             case "to bottom":
2273                 gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
2274                 break;
2275             case "to right":
2276                 gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
2277                 break;
2278             case "to bottom right":
2279                 gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
2280                 break;
2281             case "to top right":
2282                 gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
2283                 break;
2284             case "ellipse":
2285             case "ellipse at center":
2286                 gradientType = "radial";
2287                 gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
2288                 rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
2289                 break;
2290             default:
2291                 throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" };
2292         }
2293         returner = '<?xml version="1.0" ?>' +
2294             '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' +
2295             '<' + gradientType + 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' + gradientDirectionSvg + '>';
2296
2297         for (i = 0; i < stops.length; i+= 1) {
2298             if (stops[i].value) {
2299                 color = stops[i].value[0];
2300                 position = stops[i].value[1];
2301             } else {
2302                 color = stops[i];
2303                 position = undefined;
2304             }
2305
2306             if (!(color instanceof tree.Color) || (!((i === 0 || i+1 === stops.length) && position === undefined) && !(position instanceof tree.Dimension))) {
2307                 throwArgumentDescriptor();
2308             }
2309             positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%";
2310             alpha = color.alpha;
2311             returner += '<stop offset="' + positionValue + '" stop-color="' + color.toRGB() + '"' + (alpha < 1 ? ' stop-opacity="' + alpha + '"' : '') + '/>';
2312         }
2313         returner += '</' + gradientType + 'Gradient>' +
2314                     '<rect ' + rectangleDimension + ' fill="url(#gradient)" /></svg>';
2315
2316         if (useBase64) {
2317             // only works in node, needs interface to what is supported in environment
2318             try {
2319                 returner = new Buffer(returner).toString('base64');
2320             } catch(e) {
2321                 useBase64 = false;
2322             }
2323         }
2324
2325         returner = "'data:image/svg+xml" + (useBase64 ? ";base64" : "") + "," + returner + "'";
2326         return new(tree.URL)(new(tree.Anonymous)(returner));
2327     }
2328 };
2329
2330 // these static methods are used as a fallback when the optional 'mime' dependency is missing
2331 tree._mime = {
2332     // this map is intentionally incomplete
2333     // if you want more, install 'mime' dep
2334     _types: {
2335         '.htm' : 'text/html',
2336         '.html': 'text/html',
2337         '.gif' : 'image/gif',
2338         '.jpg' : 'image/jpeg',
2339         '.jpeg': 'image/jpeg',
2340         '.png' : 'image/png'
2341     },
2342     lookup: function (filepath) {
2343         var ext = require('path').extname(filepath),
2344             type = tree._mime._types[ext];
2345         if (type === undefined) {
2346             throw new Error('Optional dependency "mime" is required for ' + ext);
2347         }
2348         return type;
2349     },
2350     charsets: {
2351         lookup: function (type) {
2352             // assumes all text types are UTF-8
2353             return type && (/^text\//).test(type) ? 'UTF-8' : '';
2354         }
2355     }
2356 };
2357
2358 var mathFunctions = [{name:"ceil"}, {name:"floor"}, {name: "sqrt"}, {name:"abs"},
2359         {name:"tan", unit: ""}, {name:"sin", unit: ""}, {name:"cos", unit: ""},
2360         {name:"atan", unit: "rad"}, {name:"asin", unit: "rad"}, {name:"acos", unit: "rad"}],
2361     createMathFunction = function(name, unit) {
2362         return function(n) {
2363             /*jshint eqnull:true */
2364             if (unit != null) {
2365                 n = n.unify();
2366             }
2367             return this._math(Math[name], unit, n);
2368         };
2369     };
2370
2371 for(var i = 0; i < mathFunctions.length; i++) {
2372     tree.functions[mathFunctions[i].name] = createMathFunction(mathFunctions[i].name, mathFunctions[i].unit);
2373 }
2374
2375 function hsla(color) {
2376     return tree.functions.hsla(color.h, color.s, color.l, color.a);
2377 }
2378
2379 function scaled(n, size) {
2380     if (n instanceof tree.Dimension && n.unit.is('%')) {
2381         return parseFloat(n.value * size / 100);
2382     } else {
2383         return number(n);
2384     }
2385 }
2386
2387 function number(n) {
2388     if (n instanceof tree.Dimension) {
2389         return parseFloat(n.unit.is('%') ? n.value / 100 : n.value);
2390     } else if (typeof(n) === 'number') {
2391         return n;
2392     } else {
2393         throw {
2394             error: "RuntimeError",
2395             message: "color functions take numbers as parameters"
2396         };
2397     }
2398 }
2399
2400 function clamp(val) {
2401     return Math.min(1, Math.max(0, val));
2402 }
2403
2404 tree.functionCall = function(env, currentFileInfo) {
2405     this.env = env;
2406     this.currentFileInfo = currentFileInfo;
2407 };
2408
2409 tree.functionCall.prototype = tree.functions;
2410
2411 })(require('./tree'));
2412
2413 (function (tree) {
2414     tree.colors = {
2415         'aliceblue':'#f0f8ff',
2416         'antiquewhite':'#faebd7',
2417         'aqua':'#00ffff',
2418         'aquamarine':'#7fffd4',
2419         'azure':'#f0ffff',
2420         'beige':'#f5f5dc',
2421         'bisque':'#ffe4c4',
2422         'black':'#000000',
2423         'blanchedalmond':'#ffebcd',
2424         'blue':'#0000ff',
2425         'blueviolet':'#8a2be2',
2426         'brown':'#a52a2a',
2427         'burlywood':'#deb887',
2428         'cadetblue':'#5f9ea0',
2429         'chartreuse':'#7fff00',
2430         'chocolate':'#d2691e',
2431         'coral':'#ff7f50',
2432         'cornflowerblue':'#6495ed',
2433         'cornsilk':'#fff8dc',
2434         'crimson':'#dc143c',
2435         'cyan':'#00ffff',
2436         'darkblue':'#00008b',
2437         'darkcyan':'#008b8b',
2438         'darkgoldenrod':'#b8860b',
2439         'darkgray':'#a9a9a9',
2440         'darkgrey':'#a9a9a9',
2441         'darkgreen':'#006400',
2442         'darkkhaki':'#bdb76b',
2443         'darkmagenta':'#8b008b',
2444         'darkolivegreen':'#556b2f',
2445         'darkorange':'#ff8c00',
2446         'darkorchid':'#9932cc',
2447         'darkred':'#8b0000',
2448         'darksalmon':'#e9967a',
2449         'darkseagreen':'#8fbc8f',
2450         'darkslateblue':'#483d8b',
2451         'darkslategray':'#2f4f4f',
2452         'darkslategrey':'#2f4f4f',
2453         'darkturquoise':'#00ced1',
2454         'darkviolet':'#9400d3',
2455         'deeppink':'#ff1493',
2456         'deepskyblue':'#00bfff',
2457         'dimgray':'#696969',
2458         'dimgrey':'#696969',
2459         'dodgerblue':'#1e90ff',
2460         'firebrick':'#b22222',
2461         'floralwhite':'#fffaf0',
2462         'forestgreen':'#228b22',
2463         'fuchsia':'#ff00ff',
2464         'gainsboro':'#dcdcdc',
2465         'ghostwhite':'#f8f8ff',
2466         'gold':'#ffd700',
2467         'goldenrod':'#daa520',
2468         'gray':'#808080',
2469         'grey':'#808080',
2470         'green':'#008000',
2471         'greenyellow':'#adff2f',
2472         'honeydew':'#f0fff0',
2473         'hotpink':'#ff69b4',
2474         'indianred':'#cd5c5c',
2475         'indigo':'#4b0082',
2476         'ivory':'#fffff0',
2477         'khaki':'#f0e68c',
2478         'lavender':'#e6e6fa',
2479         'lavenderblush':'#fff0f5',
2480         'lawngreen':'#7cfc00',
2481         'lemonchiffon':'#fffacd',
2482         'lightblue':'#add8e6',
2483         'lightcoral':'#f08080',
2484         'lightcyan':'#e0ffff',
2485         'lightgoldenrodyellow':'#fafad2',
2486         'lightgray':'#d3d3d3',
2487         'lightgrey':'#d3d3d3',
2488         'lightgreen':'#90ee90',
2489         'lightpink':'#ffb6c1',
2490         'lightsalmon':'#ffa07a',
2491         'lightseagreen':'#20b2aa',
2492         'lightskyblue':'#87cefa',
2493         'lightslategray':'#778899',
2494         'lightslategrey':'#778899',
2495         'lightsteelblue':'#b0c4de',
2496         'lightyellow':'#ffffe0',
2497         'lime':'#00ff00',
2498         'limegreen':'#32cd32',
2499         'linen':'#faf0e6',
2500         'magenta':'#ff00ff',
2501         'maroon':'#800000',
2502         'mediumaquamarine':'#66cdaa',
2503         'mediumblue':'#0000cd',
2504         'mediumorchid':'#ba55d3',
2505         'mediumpurple':'#9370d8',
2506         'mediumseagreen':'#3cb371',
2507         'mediumslateblue':'#7b68ee',
2508         'mediumspringgreen':'#00fa9a',
2509         'mediumturquoise':'#48d1cc',
2510         'mediumvioletred':'#c71585',
2511         'midnightblue':'#191970',
2512         'mintcream':'#f5fffa',
2513         'mistyrose':'#ffe4e1',
2514         'moccasin':'#ffe4b5',
2515         'navajowhite':'#ffdead',
2516         'navy':'#000080',
2517         'oldlace':'#fdf5e6',
2518         'olive':'#808000',
2519         'olivedrab':'#6b8e23',
2520         'orange':'#ffa500',
2521         'orangered':'#ff4500',
2522         'orchid':'#da70d6',
2523         'palegoldenrod':'#eee8aa',
2524         'palegreen':'#98fb98',
2525         'paleturquoise':'#afeeee',
2526         'palevioletred':'#d87093',
2527         'papayawhip':'#ffefd5',
2528         'peachpuff':'#ffdab9',
2529         'peru':'#cd853f',
2530         'pink':'#ffc0cb',
2531         'plum':'#dda0dd',
2532         'powderblue':'#b0e0e6',
2533         'purple':'#800080',
2534         'red':'#ff0000',
2535         'rosybrown':'#bc8f8f',
2536         'royalblue':'#4169e1',
2537         'saddlebrown':'#8b4513',
2538         'salmon':'#fa8072',
2539         'sandybrown':'#f4a460',
2540         'seagreen':'#2e8b57',
2541         'seashell':'#fff5ee',
2542         'sienna':'#a0522d',
2543         'silver':'#c0c0c0',
2544         'skyblue':'#87ceeb',
2545         'slateblue':'#6a5acd',
2546         'slategray':'#708090',
2547         'slategrey':'#708090',
2548         'snow':'#fffafa',
2549         'springgreen':'#00ff7f',
2550         'steelblue':'#4682b4',
2551         'tan':'#d2b48c',
2552         'teal':'#008080',
2553         'thistle':'#d8bfd8',
2554         'tomato':'#ff6347',
2555         'turquoise':'#40e0d0',
2556         'violet':'#ee82ee',
2557         'wheat':'#f5deb3',
2558         'white':'#ffffff',
2559         'whitesmoke':'#f5f5f5',
2560         'yellow':'#ffff00',
2561         'yellowgreen':'#9acd32'
2562     };
2563 })(require('./tree'));
2564
2565 (function (tree) {
2566
2567 tree.debugInfo = function(env, ctx, lineSeperator) {
2568     var result="";
2569     if (env.dumpLineNumbers && !env.compress) {
2570         switch(env.dumpLineNumbers) {
2571             case 'comments':
2572                 result = tree.debugInfo.asComment(ctx);
2573                 break;
2574             case 'mediaquery':
2575                 result = tree.debugInfo.asMediaQuery(ctx);
2576                 break;
2577             case 'all':
2578                 result = tree.debugInfo.asComment(ctx) + (lineSeperator || "") + tree.debugInfo.asMediaQuery(ctx);
2579                 break;
2580         }
2581     }
2582     return result;
2583 };
2584
2585 tree.debugInfo.asComment = function(ctx) {
2586     return '/* line ' + ctx.debugInfo.lineNumber + ', ' + ctx.debugInfo.fileName + ' */\n';
2587 };
2588
2589 tree.debugInfo.asMediaQuery = function(ctx) {
2590     return '@media -sass-debug-info{filename{font-family:' +
2591         ('file://' + ctx.debugInfo.fileName).replace(/([.:/\\])/g, function (a) {
2592             if (a == '\\') {
2593                 a = '\/';
2594             }
2595             return '\\' + a;
2596         }) +
2597         '}line{font-family:\\00003' + ctx.debugInfo.lineNumber + '}}\n';
2598 };
2599
2600 tree.find = function (obj, fun) {
2601     for (var i = 0, r; i < obj.length; i++) {
2602         if (r = fun.call(obj, obj[i])) { return r; }
2603     }
2604     return null;
2605 };
2606
2607 tree.jsify = function (obj) {
2608     if (Array.isArray(obj.value) && (obj.value.length > 1)) {
2609         return '[' + obj.value.map(function (v) { return v.toCSS(false); }).join(', ') + ']';
2610     } else {
2611         return obj.toCSS(false);
2612     }
2613 };
2614
2615 tree.toCSS = function (env) {
2616     var strs = [];
2617     this.genCSS(env, {
2618         add: function(chunk, fileInfo, index) {
2619             strs.push(chunk);
2620         },
2621         isEmpty: function () {
2622             return strs.length === 0;
2623         }
2624     });
2625     return strs.join('');
2626 };
2627
2628 tree.outputRuleset = function (env, output, rules) {
2629     output.add((env.compress ? '{' : ' {\n'));
2630     env.tabLevel = (env.tabLevel || 0) + 1;
2631     var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join("  "),
2632         tabSetStr = env.compress ? '' : Array(env.tabLevel).join("  ");
2633     for(var i = 0; i < rules.length; i++) {
2634         output.add(tabRuleStr);
2635         rules[i].genCSS(env, output);
2636         output.add(env.compress ? '' : '\n');
2637     }
2638     env.tabLevel--;
2639     output.add(tabSetStr + "}");
2640 };
2641
2642 })(require('./tree'));
2643
2644 (function (tree) {
2645
2646 tree.Alpha = function (val) {
2647     this.value = val;
2648 };
2649 tree.Alpha.prototype = {
2650     type: "Alpha",
2651     accept: function (visitor) {
2652         this.value = visitor.visit(this.value);
2653     },
2654     eval: function (env) {
2655         if (this.value.eval) { return new tree.Alpha(this.value.eval(env)); }
2656         return this;
2657     },
2658     genCSS: function (env, output) {
2659         output.add("alpha(opacity=");
2660
2661         if (this.value.genCSS) {
2662             this.value.genCSS(env, output);
2663         } else {
2664             output.add(this.value);
2665         }
2666
2667         output.add(")");
2668     },
2669     toCSS: tree.toCSS
2670 };
2671
2672 })(require('../tree'));
2673
2674 (function (tree) {
2675
2676 tree.Anonymous = function (string, index, currentFileInfo, mapLines) {
2677     this.value = string.value || string;
2678     this.index = index;
2679     this.mapLines = mapLines;
2680     this.currentFileInfo = currentFileInfo;
2681 };
2682 tree.Anonymous.prototype = {
2683     type: "Anonymous",
2684     eval: function () { return this; },
2685     compare: function (x) {
2686         if (!x.toCSS) {
2687             return -1;
2688         }
2689         
2690         var left = this.toCSS(),
2691             right = x.toCSS();
2692         
2693         if (left === right) {
2694             return 0;
2695         }
2696         
2697         return left < right ? -1 : 1;
2698     },
2699     genCSS: function (env, output) {
2700         output.add(this.value, this.currentFileInfo, this.index, this.mapLines);
2701     },
2702     toCSS: tree.toCSS
2703 };
2704
2705 })(require('../tree'));
2706
2707 (function (tree) {
2708
2709 tree.Assignment = function (key, val) {
2710     this.key = key;
2711     this.value = val;
2712 };
2713 tree.Assignment.prototype = {
2714     type: "Assignment",
2715     accept: function (visitor) {
2716         this.value = visitor.visit(this.value);
2717     },
2718     eval: function (env) {
2719         if (this.value.eval) {
2720             return new(tree.Assignment)(this.key, this.value.eval(env));
2721         }
2722         return this;
2723     },
2724     genCSS: function (env, output) {
2725         output.add(this.key + '=');
2726         if (this.value.genCSS) {
2727             this.value.genCSS(env, output);
2728         } else {
2729             output.add(this.value);
2730         }
2731     },
2732     toCSS: tree.toCSS
2733 };
2734
2735 })(require('../tree'));
2736
2737 (function (tree) {
2738
2739 //
2740 // A function call node.
2741 //
2742 tree.Call = function (name, args, index, currentFileInfo) {
2743     this.name = name;
2744     this.args = args;
2745     this.index = index;
2746     this.currentFileInfo = currentFileInfo;
2747 };
2748 tree.Call.prototype = {
2749     type: "Call",
2750     accept: function (visitor) {
2751         this.args = visitor.visit(this.args);
2752     },
2753     //
2754     // When evaluating a function call,
2755     // we either find the function in `tree.functions` [1],
2756     // in which case we call it, passing the  evaluated arguments,
2757     // if this returns null or we cannot find the function, we 
2758     // simply print it out as it appeared originally [2].
2759     //
2760     // The *functions.js* file contains the built-in functions.
2761     //
2762     // The reason why we evaluate the arguments, is in the case where
2763     // we try to pass a variable to a function, like: `saturate(@color)`.
2764     // The function should receive the value, not the variable.
2765     //
2766     eval: function (env) {
2767         var args = this.args.map(function (a) { return a.eval(env); }),
2768             nameLC = this.name.toLowerCase(),
2769             result, func;
2770
2771         if (nameLC in tree.functions) { // 1.
2772             try {
2773                 func = new tree.functionCall(env, this.currentFileInfo);
2774                 result = func[nameLC].apply(func, args);
2775                 /*jshint eqnull:true */
2776                 if (result != null) {
2777                     return result;
2778                 }
2779             } catch (e) {
2780                 throw { type: e.type || "Runtime",
2781                         message: "error evaluating function `" + this.name + "`" +
2782                                  (e.message ? ': ' + e.message : ''),
2783                         index: this.index, filename: this.currentFileInfo.filename };
2784             }
2785         }
2786
2787         return new tree.Call(this.name, args, this.index, this.currentFileInfo);
2788     },
2789
2790     genCSS: function (env, output) {
2791         output.add(this.name + "(", this.currentFileInfo, this.index);
2792
2793         for(var i = 0; i < this.args.length; i++) {
2794             this.args[i].genCSS(env, output);
2795             if (i + 1 < this.args.length) {
2796                 output.add(", ");
2797             }
2798         }
2799
2800         output.add(")");
2801     },
2802
2803     toCSS: tree.toCSS
2804 };
2805
2806 })(require('../tree'));
2807
2808 (function (tree) {
2809 //
2810 // RGB Colors - #ff0014, #eee
2811 //
2812 tree.Color = function (rgb, a) {
2813     //
2814     // The end goal here, is to parse the arguments
2815     // into an integer triplet, such as `128, 255, 0`
2816     //
2817     // This facilitates operations and conversions.
2818     //
2819     if (Array.isArray(rgb)) {
2820         this.rgb = rgb;
2821     } else if (rgb.length == 6) {
2822         this.rgb = rgb.match(/.{2}/g).map(function (c) {
2823             return parseInt(c, 16);
2824         });
2825     } else {
2826         this.rgb = rgb.split('').map(function (c) {
2827             return parseInt(c + c, 16);
2828         });
2829     }
2830     this.alpha = typeof(a) === 'number' ? a : 1;
2831 };
2832
2833 var transparentKeyword = "transparent";
2834
2835 tree.Color.prototype = {
2836     type: "Color",
2837     eval: function () { return this; },
2838     luma: function () { return (0.2126 * this.rgb[0] / 255) + (0.7152 * this.rgb[1] / 255) + (0.0722 * this.rgb[2] / 255); },
2839
2840     genCSS: function (env, output) {
2841         output.add(this.toCSS(env));
2842     },
2843     toCSS: function (env, doNotCompress) {
2844         var compress = env && env.compress && !doNotCompress;
2845
2846         // If we have some transparency, the only way to represent it
2847         // is via `rgba`. Otherwise, we use the hex representation,
2848         // which has better compatibility with older browsers.
2849         // Values are capped between `0` and `255`, rounded and zero-padded.
2850         if (this.alpha < 1.0) {
2851             if (this.alpha === 0 && this.isTransparentKeyword) {
2852                 return transparentKeyword;
2853             }
2854             return "rgba(" + this.rgb.map(function (c) {
2855                 return Math.round(c);
2856             }).concat(this.alpha).join(',' + (compress ? '' : ' ')) + ")";
2857         } else {
2858             var color = this.toRGB();
2859
2860             if (compress) {
2861                 var splitcolor = color.split('');
2862
2863                 // Convert color to short format
2864                 if (splitcolor[1] === splitcolor[2] && splitcolor[3] === splitcolor[4] && splitcolor[5] === splitcolor[6]) {
2865                     color = '#' + splitcolor[1] + splitcolor[3] + splitcolor[5];
2866                 }
2867             }
2868
2869             return color;
2870         }
2871     },
2872
2873     //
2874     // Operations have to be done per-channel, if not,
2875     // channels will spill onto each other. Once we have
2876     // our result, in the form of an integer triplet,
2877     // we create a new Color node to hold the result.
2878     //
2879     operate: function (env, op, other) {
2880         var result = [];
2881
2882         if (! (other instanceof tree.Color)) {
2883             other = other.toColor();
2884         }
2885
2886         for (var c = 0; c < 3; c++) {
2887             result[c] = tree.operate(env, op, this.rgb[c], other.rgb[c]);
2888         }
2889         return new(tree.Color)(result, this.alpha + other.alpha);
2890     },
2891
2892     toRGB: function () {
2893         return '#' + this.rgb.map(function (i) {
2894             i = Math.round(i);
2895             i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16);
2896             return i.length === 1 ? '0' + i : i;
2897         }).join('');
2898     },
2899
2900     toHSL: function () {
2901         var r = this.rgb[0] / 255,
2902             g = this.rgb[1] / 255,
2903             b = this.rgb[2] / 255,
2904             a = this.alpha;
2905
2906         var max = Math.max(r, g, b), min = Math.min(r, g, b);
2907         var h, s, l = (max + min) / 2, d = max - min;
2908
2909         if (max === min) {
2910             h = s = 0;
2911         } else {
2912             s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
2913
2914             switch (max) {
2915                 case r: h = (g - b) / d + (g < b ? 6 : 0); break;
2916                 case g: h = (b - r) / d + 2;               break;
2917                 case b: h = (r - g) / d + 4;               break;
2918             }
2919             h /= 6;
2920         }
2921         return { h: h * 360, s: s, l: l, a: a };
2922     },
2923     //Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
2924     toHSV: function () {
2925         var r = this.rgb[0] / 255,
2926             g = this.rgb[1] / 255,
2927             b = this.rgb[2] / 255,
2928             a = this.alpha;
2929
2930         var max = Math.max(r, g, b), min = Math.min(r, g, b);
2931         var h, s, v = max;
2932
2933         var d = max - min;
2934         if (max === 0) {
2935             s = 0;
2936         } else {
2937             s = d / max;
2938         }
2939
2940         if (max === min) {
2941             h = 0;
2942         } else {
2943             switch(max){
2944                 case r: h = (g - b) / d + (g < b ? 6 : 0); break;
2945                 case g: h = (b - r) / d + 2; break;
2946                 case b: h = (r - g) / d + 4; break;
2947             }
2948             h /= 6;
2949         }
2950         return { h: h * 360, s: s, v: v, a: a };
2951     },
2952     toARGB: function () {
2953         var argb = [Math.round(this.alpha * 255)].concat(this.rgb);
2954         return '#' + argb.map(function (i) {
2955             i = Math.round(i);
2956             i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16);
2957             return i.length === 1 ? '0' + i : i;
2958         }).join('');
2959     },
2960     compare: function (x) {
2961         if (!x.rgb) {
2962             return -1;
2963         }
2964         
2965         return (x.rgb[0] === this.rgb[0] &&
2966             x.rgb[1] === this.rgb[1] &&
2967             x.rgb[2] === this.rgb[2] &&
2968             x.alpha === this.alpha) ? 0 : -1;
2969     }
2970 };
2971
2972 tree.Color.fromKeyword = function(keyword) {
2973     if (tree.colors.hasOwnProperty(keyword)) {
2974         // detect named color
2975         return new(tree.Color)(tree.colors[keyword].slice(1));
2976     }
2977     if (keyword === transparentKeyword) {
2978         var transparent = new(tree.Color)([0, 0, 0], 0);
2979         transparent.isTransparentKeyword = true;
2980         return transparent;
2981     }
2982 };
2983
2984
2985 })(require('../tree'));
2986
2987 (function (tree) {
2988
2989 tree.Comment = function (value, silent, index, currentFileInfo) {
2990     this.value = value;
2991     this.silent = !!silent;
2992     this.currentFileInfo = currentFileInfo;
2993 };
2994 tree.Comment.prototype = {
2995     type: "Comment",
2996     genCSS: function (env, output) {
2997         if (this.debugInfo) {
2998             output.add(tree.debugInfo(env, this), this.currentFileInfo, this.index);
2999         }
3000         output.add(this.value.trim()); //TODO shouldn't need to trim, we shouldn't grab the \n
3001     },
3002     toCSS: tree.toCSS,
3003     isSilent: function(env) {
3004         var isReference = (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced),
3005             isCompressed = env.compress && !this.value.match(/^\/\*!/);
3006         return this.silent || isReference || isCompressed;
3007     },
3008     eval: function () { return this; },
3009     markReferenced: function () {
3010         this.isReferenced = true;
3011     }
3012 };
3013
3014 })(require('../tree'));
3015
3016 (function (tree) {
3017
3018 tree.Condition = function (op, l, r, i, negate) {
3019     this.op = op.trim();
3020     this.lvalue = l;
3021     this.rvalue = r;
3022     this.index = i;
3023     this.negate = negate;
3024 };
3025 tree.Condition.prototype = {
3026     type: "Condition",
3027     accept: function (visitor) {
3028         this.lvalue = visitor.visit(this.lvalue);
3029         this.rvalue = visitor.visit(this.rvalue);
3030     },
3031     eval: function (env) {
3032         var a = this.lvalue.eval(env),
3033             b = this.rvalue.eval(env);
3034
3035         var i = this.index, result;
3036
3037         result = (function (op) {
3038             switch (op) {
3039                 case 'and':
3040                     return a && b;
3041                 case 'or':
3042                     return a || b;
3043                 default:
3044                     if (a.compare) {
3045                         result = a.compare(b);
3046                     } else if (b.compare) {
3047                         result = b.compare(a);
3048                     } else {
3049                         throw { type: "Type",
3050                                 message: "Unable to perform comparison",
3051                                 index: i };
3052                     }
3053                     switch (result) {
3054                         case -1: return op === '<' || op === '=<' || op === '<=';
3055                         case  0: return op === '=' || op === '>=' || op === '=<' || op === '<=';
3056                         case  1: return op === '>' || op === '>=';
3057                     }
3058             }
3059         })(this.op);
3060         return this.negate ? !result : result;
3061     }
3062 };
3063
3064 })(require('../tree'));
3065
3066 (function (tree) {
3067
3068 //
3069 // A number with a unit
3070 //
3071 tree.Dimension = function (value, unit) {
3072     this.value = parseFloat(value);
3073     this.unit = (unit && unit instanceof tree.Unit) ? unit :
3074       new(tree.Unit)(unit ? [unit] : undefined);
3075 };
3076
3077 tree.Dimension.prototype = {
3078     type: "Dimension",
3079     accept: function (visitor) {
3080         this.unit = visitor.visit(this.unit);
3081     },
3082     eval: function (env) {
3083         return this;
3084     },
3085     toColor: function () {
3086         return new(tree.Color)([this.value, this.value, this.value]);
3087     },
3088     genCSS: function (env, output) {
3089         if ((env && env.strictUnits) && !this.unit.isSingular()) {
3090             throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString());
3091         }
3092
3093         var value = this.value,
3094             strValue = String(value);
3095
3096         if (value !== 0 && value < 0.000001 && value > -0.000001) {
3097             // would be output 1e-6 etc.
3098             strValue = value.toFixed(20).replace(/0+$/, "");
3099         }
3100
3101         if (env && env.compress) {
3102             // Zero values doesn't need a unit
3103             if (value === 0 && this.unit.isLength()) {
3104                 output.add(strValue);
3105                 return;
3106             }
3107
3108             // Float values doesn't need a leading zero
3109             if (value > 0 && value < 1) {
3110                 strValue = (strValue).substr(1);
3111             }
3112         }
3113
3114         output.add(strValue);
3115         this.unit.genCSS(env, output);
3116     },
3117     toCSS: tree.toCSS,
3118
3119     // In an operation between two Dimensions,
3120     // we default to the first Dimension's unit,
3121     // so `1px + 2` will yield `3px`.
3122     operate: function (env, op, other) {
3123         /*jshint noempty:false */
3124         var value = tree.operate(env, op, this.value, other.value),
3125             unit = this.unit.clone();
3126
3127         if (op === '+' || op === '-') {
3128             if (unit.numerator.length === 0 && unit.denominator.length === 0) {
3129                 unit.numerator = other.unit.numerator.slice(0);
3130                 unit.denominator = other.unit.denominator.slice(0);
3131             } else if (other.unit.numerator.length === 0 && unit.denominator.length === 0) {
3132                 // do nothing
3133             } else {
3134                 other = other.convertTo(this.unit.usedUnits());
3135
3136                 if(env.strictUnits && other.unit.toString() !== unit.toString()) {
3137                   throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '" + unit.toString() +
3138                     "' and '" + other.unit.toString() + "'.");
3139                 }
3140
3141                 value = tree.operate(env, op, this.value, other.value);
3142             }
3143         } else if (op === '*') {
3144             unit.numerator = unit.numerator.concat(other.unit.numerator).sort();
3145             unit.denominator = unit.denominator.concat(other.unit.denominator).sort();
3146             unit.cancel();
3147         } else if (op === '/') {
3148             unit.numerator = unit.numerator.concat(other.unit.denominator).sort();
3149             unit.denominator = unit.denominator.concat(other.unit.numerator).sort();
3150             unit.cancel();
3151         }
3152         return new(tree.Dimension)(value, unit);
3153     },
3154
3155     compare: function (other) {
3156         if (other instanceof tree.Dimension) {
3157             var a = this.unify(), b = other.unify(),
3158                 aValue = a.value, bValue = b.value;
3159
3160             if (bValue > aValue) {
3161                 return -1;
3162             } else if (bValue < aValue) {
3163                 return 1;
3164             } else {
3165                 if (!b.unit.isEmpty() && a.unit.compare(b.unit) !== 0) {
3166                     return -1;
3167                 }
3168                 return 0;
3169             }
3170         } else {
3171             return -1;
3172         }
3173     },
3174
3175     unify: function () {
3176         return this.convertTo({ length: 'm', duration: 's', angle: 'rad' });
3177     },
3178
3179     convertTo: function (conversions) {
3180         var value = this.value, unit = this.unit.clone(),
3181             i, groupName, group, targetUnit, derivedConversions = {}, applyUnit;
3182
3183         if (typeof conversions === 'string') {
3184             for(i in tree.UnitConversions) {
3185                 if (tree.UnitConversions[i].hasOwnProperty(conversions)) {
3186                     derivedConversions = {};
3187                     derivedConversions[i] = conversions;
3188                 }
3189             }
3190             conversions = derivedConversions;
3191         }
3192         applyUnit = function (atomicUnit, denominator) {
3193           /*jshint loopfunc:true */
3194             if (group.hasOwnProperty(atomicUnit)) {
3195                 if (denominator) {
3196                     value = value / (group[atomicUnit] / group[targetUnit]);
3197                 } else {
3198                     value = value * (group[atomicUnit] / group[targetUnit]);
3199                 }
3200
3201                 return targetUnit;
3202             }
3203
3204             return atomicUnit;
3205         };
3206
3207         for (groupName in conversions) {
3208             if (conversions.hasOwnProperty(groupName)) {
3209                 targetUnit = conversions[groupName];
3210                 group = tree.UnitConversions[groupName];
3211
3212                 unit.map(applyUnit);
3213             }
3214         }
3215
3216         unit.cancel();
3217
3218         return new(tree.Dimension)(value, unit);
3219     }
3220 };
3221
3222 // http://www.w3.org/TR/css3-values/#absolute-lengths
3223 tree.UnitConversions = {
3224     length: {
3225          'm': 1,
3226         'cm': 0.01,
3227         'mm': 0.001,
3228         'in': 0.0254,
3229         'pt': 0.0254 / 72,
3230         'pc': 0.0254 / 72 * 12
3231     },
3232     duration: {
3233         's': 1,
3234         'ms': 0.001
3235     },
3236     angle: {
3237         'rad': 1/(2*Math.PI),
3238         'deg': 1/360,
3239         'grad': 1/400,
3240         'turn': 1
3241     }
3242 };
3243
3244 tree.Unit = function (numerator, denominator, backupUnit) {
3245     this.numerator = numerator ? numerator.slice(0).sort() : [];
3246     this.denominator = denominator ? denominator.slice(0).sort() : [];
3247     this.backupUnit = backupUnit;
3248 };
3249
3250 tree.Unit.prototype = {
3251     type: "Unit",
3252     clone: function () {
3253         return new tree.Unit(this.numerator.slice(0), this.denominator.slice(0), this.backupUnit);
3254     },
3255     genCSS: function (env, output) {
3256         if (this.numerator.length >= 1) {
3257             output.add(this.numerator[0]);
3258         } else
3259         if (this.denominator.length >= 1) {
3260             output.add(this.denominator[0]);
3261         } else
3262         if ((!env || !env.strictUnits) && this.backupUnit) {
3263             output.add(this.backupUnit);
3264         }
3265     },
3266     toCSS: tree.toCSS,
3267
3268     toString: function () {
3269       var i, returnStr = this.numerator.join("*");
3270       for (i = 0; i < this.denominator.length; i++) {
3271           returnStr += "/" + this.denominator[i];
3272       }
3273       return returnStr;
3274     },
3275
3276     compare: function (other) {
3277         return this.is(other.toString()) ? 0 : -1;
3278     },
3279
3280     is: function (unitString) {
3281         return this.toString() === unitString;
3282     },
3283
3284     isLength: function () {
3285         return Boolean(this.toCSS().match(/px|em|%|in|cm|mm|pc|pt|ex/));
3286     },
3287
3288     isEmpty: function () {
3289         return this.numerator.length === 0 && this.denominator.length === 0;
3290     },
3291
3292     isSingular: function() {
3293         return this.numerator.length <= 1 && this.denominator.length === 0;
3294     },
3295
3296     map: function(callback) {
3297         var i;
3298
3299         for (i = 0; i < this.numerator.length; i++) {
3300             this.numerator[i] = callback(this.numerator[i], false);
3301         }
3302
3303         for (i = 0; i < this.denominator.length; i++) {
3304             this.denominator[i] = callback(this.denominator[i], true);
3305         }
3306     },
3307
3308     usedUnits: function() {
3309         var group, result = {}, mapUnit;
3310
3311         mapUnit = function (atomicUnit) {
3312         /*jshint loopfunc:true */
3313             if (group.hasOwnProperty(atomicUnit) && !result[groupName]) {
3314                 result[groupName] = atomicUnit;
3315             }
3316
3317             return atomicUnit;
3318         };
3319
3320         for (var groupName in tree.UnitConversions) {
3321             if (tree.UnitConversions.hasOwnProperty(groupName)) {
3322                 group = tree.UnitConversions[groupName];
3323
3324                 this.map(mapUnit);
3325             }
3326         }
3327
3328         return result;
3329     },
3330
3331     cancel: function () {
3332         var counter = {}, atomicUnit, i, backup;
3333
3334         for (i = 0; i < this.numerator.length; i++) {
3335             atomicUnit = this.numerator[i];
3336             if (!backup) {
3337                 backup = atomicUnit;
3338             }
3339             counter[atomicUnit] = (counter[atomicUnit] || 0) + 1;
3340         }
3341
3342         for (i = 0; i < this.denominator.length; i++) {
3343             atomicUnit = this.denominator[i];
3344             if (!backup) {
3345                 backup = atomicUnit;
3346             }
3347             counter[atomicUnit] = (counter[atomicUnit] || 0) - 1;
3348         }
3349
3350         this.numerator = [];
3351         this.denominator = [];
3352
3353         for (atomicUnit in counter) {
3354             if (counter.hasOwnProperty(atomicUnit)) {
3355                 var count = counter[atomicUnit];
3356
3357                 if (count > 0) {
3358                     for (i = 0; i < count; i++) {
3359                         this.numerator.push(atomicUnit);
3360                     }
3361                 } else if (count < 0) {
3362                     for (i = 0; i < -count; i++) {
3363                         this.denominator.push(atomicUnit);
3364                     }
3365                 }
3366             }
3367         }
3368
3369         if (this.numerator.length === 0 && this.denominator.length === 0 && backup) {
3370             this.backupUnit = backup;
3371         }
3372
3373         this.numerator.sort();
3374         this.denominator.sort();
3375     }
3376 };
3377
3378 })(require('../tree'));
3379
3380 (function (tree) {
3381
3382 tree.Directive = function (name, value, index, currentFileInfo) {
3383     this.name = name;
3384
3385     if (Array.isArray(value)) {
3386         this.rules = [new(tree.Ruleset)([], value)];
3387         this.rules[0].allowImports = true;
3388     } else {
3389         this.value = value;
3390     }
3391     this.currentFileInfo = currentFileInfo;
3392
3393 };
3394 tree.Directive.prototype = {
3395     type: "Directive",
3396     accept: function (visitor) {
3397         this.rules = visitor.visit(this.rules);
3398         this.value = visitor.visit(this.value);
3399     },
3400     genCSS: function (env, output) {
3401         output.add(this.name, this.currentFileInfo, this.index);
3402         if (this.rules) {
3403             tree.outputRuleset(env, output, this.rules);
3404         } else {
3405             output.add(' ');
3406             this.value.genCSS(env, output);
3407             output.add(';');
3408         }
3409     },
3410     toCSS: tree.toCSS,
3411     eval: function (env) {
3412         var evaldDirective = this;
3413         if (this.rules) {
3414             env.frames.unshift(this);
3415             evaldDirective = new(tree.Directive)(this.name, null, this.index, this.currentFileInfo);
3416             evaldDirective.rules = [this.rules[0].eval(env)];
3417             evaldDirective.rules[0].root = true;
3418             env.frames.shift();
3419         }
3420         return evaldDirective;
3421     },
3422     variable: function (name) { return tree.Ruleset.prototype.variable.call(this.rules[0], name); },
3423     find: function () { return tree.Ruleset.prototype.find.apply(this.rules[0], arguments); },
3424     rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); },
3425     markReferenced: function () {
3426         var i, rules;
3427         this.isReferenced = true;
3428         if (this.rules) {
3429             rules = this.rules[0].rules;
3430             for (i = 0; i < rules.length; i++) {
3431                 if (rules[i].markReferenced) {
3432                     rules[i].markReferenced();
3433                 }
3434             }
3435         }
3436     }
3437 };
3438
3439 })(require('../tree'));
3440
3441 (function (tree) {
3442
3443 tree.Element = function (combinator, value, index, currentFileInfo) {
3444     this.combinator = combinator instanceof tree.Combinator ?
3445                       combinator : new(tree.Combinator)(combinator);
3446
3447     if (typeof(value) === 'string') {
3448         this.value = value.trim();
3449     } else if (value) {
3450         this.value = value;
3451     } else {
3452         this.value = "";
3453     }
3454     this.index = index;
3455     this.currentFileInfo = currentFileInfo;
3456 };
3457 tree.Element.prototype = {
3458     type: "Element",
3459     accept: function (visitor) {
3460         this.combinator = visitor.visit(this.combinator);
3461         this.value = visitor.visit(this.value);
3462     },
3463     eval: function (env) {
3464         return new(tree.Element)(this.combinator,
3465                                  this.value.eval ? this.value.eval(env) : this.value,
3466                                  this.index,
3467                                  this.currentFileInfo);
3468     },
3469     genCSS: function (env, output) {
3470         output.add(this.toCSS(env), this.currentFileInfo, this.index);
3471     },
3472     toCSS: function (env) {
3473         var value = (this.value.toCSS ? this.value.toCSS(env) : this.value);
3474         if (value === '' && this.combinator.value.charAt(0) === '&') {
3475             return '';
3476         } else {
3477             return this.combinator.toCSS(env || {}) + value;
3478         }
3479     }
3480 };
3481
3482 tree.Attribute = function (key, op, value) {
3483     this.key = key;
3484     this.op = op;
3485     this.value = value;
3486 };
3487 tree.Attribute.prototype = {
3488     type: "Attribute",
3489     accept: function (visitor) {
3490         this.value = visitor.visit(this.value);
3491     },
3492     eval: function (env) {
3493         return new(tree.Attribute)(this.key.eval ? this.key.eval(env) : this.key,
3494             this.op, (this.value && this.value.eval) ? this.value.eval(env) : this.value);
3495     },
3496     genCSS: function (env, output) {
3497         output.add(this.toCSS(env));
3498     },
3499     toCSS: function (env) {
3500         var value = this.key.toCSS ? this.key.toCSS(env) : this.key;
3501
3502         if (this.op) {
3503             value += this.op;
3504             value += (this.value.toCSS ? this.value.toCSS(env) : this.value);
3505         }
3506
3507         return '[' + value + ']';
3508     }
3509 };
3510
3511 tree.Combinator = function (value) {
3512     if (value === ' ') {
3513         this.value = ' ';
3514     } else {
3515         this.value = value ? value.trim() : "";
3516     }
3517 };
3518 tree.Combinator.prototype = {
3519     type: "Combinator",
3520     _outputMap: {
3521         ''  : '',
3522         ' ' : ' ',
3523         ':' : ' :',
3524         '+' : ' + ',
3525         '~' : ' ~ ',
3526         '>' : ' > ',
3527         '|' : '|'
3528     },
3529     _outputMapCompressed: {
3530         ''  : '',
3531         ' ' : ' ',
3532         ':' : ' :',
3533         '+' : '+',
3534         '~' : '~',
3535         '>' : '>',
3536         '|' : '|'
3537     },
3538     genCSS: function (env, output) {
3539         output.add((env.compress ? this._outputMapCompressed : this._outputMap)[this.value]);
3540     },
3541     toCSS: tree.toCSS
3542 };
3543
3544 })(require('../tree'));
3545
3546 (function (tree) {
3547
3548 tree.Expression = function (value) { this.value = value; };
3549 tree.Expression.prototype = {
3550     type: "Expression",
3551     accept: function (visitor) {
3552         this.value = visitor.visit(this.value);
3553     },
3554     eval: function (env) {
3555         var returnValue,
3556             inParenthesis = this.parens && !this.parensInOp,
3557             doubleParen = false;
3558         if (inParenthesis) {
3559             env.inParenthesis();
3560         }
3561         if (this.value.length > 1) {
3562             returnValue = new(tree.Expression)(this.value.map(function (e) {
3563                 return e.eval(env);
3564             }));
3565         } else if (this.value.length === 1) {
3566             if (this.value[0].parens && !this.value[0].parensInOp) {
3567                 doubleParen = true;
3568             }
3569             returnValue = this.value[0].eval(env);
3570         } else {
3571             returnValue = this;
3572         }
3573         if (inParenthesis) {
3574             env.outOfParenthesis();
3575         }
3576         if (this.parens && this.parensInOp && !(env.isMathOn()) && !doubleParen) {
3577             returnValue = new(tree.Paren)(returnValue);
3578         }
3579         return returnValue;
3580     },
3581     genCSS: function (env, output) {
3582         for(var i = 0; i < this.value.length; i++) {
3583             this.value[i].genCSS(env, output);
3584             if (i + 1 < this.value.length) {
3585                 output.add(" ");
3586             }
3587         }
3588     },
3589     toCSS: tree.toCSS,
3590     throwAwayComments: function () {
3591         this.value = this.value.filter(function(v) {
3592             return !(v instanceof tree.Comment);
3593         });
3594     }
3595 };
3596
3597 })(require('../tree'));
3598
3599 (function (tree) {
3600
3601 tree.Extend = function Extend(selector, option, index) {
3602     this.selector = selector;
3603     this.option = option;
3604     this.index = index;
3605
3606     switch(option) {
3607         case "all":
3608             this.allowBefore = true;
3609             this.allowAfter = true;
3610         break;
3611         default:
3612             this.allowBefore = false;
3613             this.allowAfter = false;
3614         break;
3615     }
3616 };
3617
3618 tree.Extend.prototype = {
3619     type: "Extend",
3620     accept: function (visitor) {
3621         this.selector = visitor.visit(this.selector);
3622     },
3623     eval: function (env) {
3624         return new(tree.Extend)(this.selector.eval(env), this.option, this.index);
3625     },
3626     clone: function (env) {
3627         return new(tree.Extend)(this.selector, this.option, this.index);
3628     },
3629     findSelfSelectors: function (selectors) {
3630         var selfElements = [],
3631             i,
3632             selectorElements;
3633
3634         for(i = 0; i < selectors.length; i++) {
3635             selectorElements = selectors[i].elements;
3636             // duplicate the logic in genCSS function inside the selector node.
3637             // future TODO - move both logics into the selector joiner visitor
3638             if (i > 0 && selectorElements.length && selectorElements[0].combinator.value === "") {
3639                 selectorElements[0].combinator.value = ' ';
3640             }
3641             selfElements = selfElements.concat(selectors[i].elements);
3642         }
3643
3644         this.selfSelectors = [{ elements: selfElements }];
3645     }
3646 };
3647
3648 })(require('../tree'));
3649
3650 (function (tree) {
3651 //
3652 // CSS @import node
3653 //
3654 // The general strategy here is that we don't want to wait
3655 // for the parsing to be completed, before we start importing
3656 // the file. That's because in the context of a browser,
3657 // most of the time will be spent waiting for the server to respond.
3658 //
3659 // On creation, we push the import path to our import queue, though
3660 // `import,push`, we also pass it a callback, which it'll call once
3661 // the file has been fetched, and parsed.
3662 //
3663 tree.Import = function (path, features, options, index, currentFileInfo) {
3664     this.options = options;
3665     this.index = index;
3666     this.path = path;
3667     this.features = features;
3668     this.currentFileInfo = currentFileInfo;
3669
3670     if (this.options.less !== undefined || this.options.inline) {
3671         this.css = !this.options.less || this.options.inline;
3672     } else {
3673         var pathValue = this.getPath();
3674         if (pathValue && /css([\?;].*)?$/.test(pathValue)) {
3675             this.css = true;
3676         }
3677     }
3678 };
3679
3680 //
3681 // The actual import node doesn't return anything, when converted to CSS.
3682 // The reason is that it's used at the evaluation stage, so that the rules
3683 // it imports can be treated like any other rules.
3684 //
3685 // In `eval`, we make sure all Import nodes get evaluated, recursively, so
3686 // we end up with a flat structure, which can easily be imported in the parent
3687 // ruleset.
3688 //
3689 tree.Import.prototype = {
3690     type: "Import",
3691     accept: function (visitor) {
3692         this.features = visitor.visit(this.features);
3693         this.path = visitor.visit(this.path);
3694         if (!this.options.inline) {
3695             this.root = visitor.visit(this.root);
3696         }
3697     },
3698     genCSS: function (env, output) {
3699         if (this.css) {
3700             output.add("@import ", this.currentFileInfo, this.index);
3701             this.path.genCSS(env, output);
3702             if (this.features) {
3703                 output.add(" ");
3704                 this.features.genCSS(env, output);
3705             }
3706             output.add(';');
3707         }
3708     },
3709     toCSS: tree.toCSS,
3710     getPath: function () {
3711         if (this.path instanceof tree.Quoted) {
3712             var path = this.path.value;
3713             return (this.css !== undefined || /(\.[a-z]*$)|([\?;].*)$/.test(path)) ? path : path + '.less';
3714         } else if (this.path instanceof tree.URL) {
3715             return this.path.value.value;
3716         }
3717         return null;
3718     },
3719     evalForImport: function (env) {
3720         return new(tree.Import)(this.path.eval(env), this.features, this.options, this.index, this.currentFileInfo);
3721     },
3722     evalPath: function (env) {
3723         var path = this.path.eval(env);
3724         var rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
3725
3726         if (!(path instanceof tree.URL)) {
3727             if (rootpath) {
3728                 var pathValue = path.value;
3729                 // Add the base path if the import is relative
3730                 if (pathValue && env.isPathRelative(pathValue)) {
3731                     path.value = rootpath + pathValue;
3732                 }
3733             }
3734             path.value = env.normalizePath(path.value);
3735         }
3736
3737         return path;
3738     },
3739     eval: function (env) {
3740         var ruleset, features = this.features && this.features.eval(env);
3741
3742         if (this.skip) { return []; }
3743
3744         if (this.options.inline) {
3745             //todo needs to reference css file not import
3746             var contents = new(tree.Anonymous)(this.root, 0, {filename: this.importedFilename}, true);
3747             return this.features ? new(tree.Media)([contents], this.features.value) : [contents];
3748         } else if (this.css) {
3749             var newImport = new(tree.Import)(this.evalPath(env), features, this.options, this.index);
3750             if (!newImport.css && this.error) {
3751                 throw this.error;
3752             }
3753             return newImport;
3754         } else {
3755             ruleset = new(tree.Ruleset)([], this.root.rules.slice(0));
3756
3757             ruleset.evalImports(env);
3758
3759             return this.features ? new(tree.Media)(ruleset.rules, this.features.value) : ruleset.rules;
3760         }
3761     }
3762 };
3763
3764 })(require('../tree'));
3765
3766 (function (tree) {
3767
3768 tree.JavaScript = function (string, index, escaped) {
3769     this.escaped = escaped;
3770     this.expression = string;
3771     this.index = index;
3772 };
3773 tree.JavaScript.prototype = {
3774     type: "JavaScript",
3775     eval: function (env) {
3776         var result,
3777             that = this,
3778             context = {};
3779
3780         var expression = this.expression.replace(/@\{([\w-]+)\}/g, function (_, name) {
3781             return tree.jsify(new(tree.Variable)('@' + name, that.index).eval(env));
3782         });
3783
3784         try {
3785             expression = new(Function)('return (' + expression + ')');
3786         } catch (e) {
3787             throw { message: "JavaScript evaluation error: " + e.message + " from `" + expression + "`" ,
3788                     index: this.index };
3789         }
3790
3791         for (var k in env.frames[0].variables()) {
3792             /*jshint loopfunc:true */
3793             context[k.slice(1)] = {
3794                 value: env.frames[0].variables()[k].value,
3795                 toJS: function () {
3796                     return this.value.eval(env).toCSS();
3797                 }
3798             };
3799         }
3800
3801         try {
3802             result = expression.call(context);
3803         } catch (e) {
3804             throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" ,
3805                     index: this.index };
3806         }
3807         if (typeof(result) === 'string') {
3808             return new(tree.Quoted)('"' + result + '"', result, this.escaped, this.index);
3809         } else if (Array.isArray(result)) {
3810             return new(tree.Anonymous)(result.join(', '));
3811         } else {
3812             return new(tree.Anonymous)(result);
3813         }
3814     }
3815 };
3816
3817 })(require('../tree'));
3818
3819
3820 (function (tree) {
3821
3822 tree.Keyword = function (value) { this.value = value; };
3823 tree.Keyword.prototype = {
3824     type: "Keyword",
3825     eval: function () { return this; },
3826     genCSS: function (env, output) {
3827         output.add(this.value);
3828     },
3829     toCSS: tree.toCSS,
3830     compare: function (other) {
3831         if (other instanceof tree.Keyword) {
3832             return other.value === this.value ? 0 : 1;
3833         } else {
3834             return -1;
3835         }
3836     }
3837 };
3838
3839 tree.True = new(tree.Keyword)('true');
3840 tree.False = new(tree.Keyword)('false');
3841
3842 })(require('../tree'));
3843
3844 (function (tree) {
3845
3846 tree.Media = function (value, features, index, currentFileInfo) {
3847     this.index = index;
3848     this.currentFileInfo = currentFileInfo;
3849
3850     var selectors = this.emptySelectors();
3851
3852     this.features = new(tree.Value)(features);
3853     this.rules = [new(tree.Ruleset)(selectors, value)];
3854     this.rules[0].allowImports = true;
3855 };
3856 tree.Media.prototype = {
3857     type: "Media",
3858     accept: function (visitor) {
3859         this.features = visitor.visit(this.features);
3860         this.rules = visitor.visit(this.rules);
3861     },
3862     genCSS: function (env, output) {
3863         output.add('@media ', this.currentFileInfo, this.index);
3864         this.features.genCSS(env, output);
3865         tree.outputRuleset(env, output, this.rules);
3866     },
3867     toCSS: tree.toCSS,
3868     eval: function (env) {
3869         if (!env.mediaBlocks) {
3870             env.mediaBlocks = [];
3871             env.mediaPath = [];
3872         }
3873         
3874         var media = new(tree.Media)([], [], this.index, this.currentFileInfo);
3875         if(this.debugInfo) {
3876             this.rules[0].debugInfo = this.debugInfo;
3877             media.debugInfo = this.debugInfo;
3878         }
3879         var strictMathBypass = false;
3880         if (!env.strictMath) {
3881             strictMathBypass = true;
3882             env.strictMath = true;
3883         }
3884         try {
3885             media.features = this.features.eval(env);
3886         }
3887         finally {
3888             if (strictMathBypass) {
3889                 env.strictMath = false;
3890             }
3891         }
3892         
3893         env.mediaPath.push(media);
3894         env.mediaBlocks.push(media);
3895         
3896         env.frames.unshift(this.rules[0]);
3897         media.rules = [this.rules[0].eval(env)];
3898         env.frames.shift();
3899         
3900         env.mediaPath.pop();
3901
3902         return env.mediaPath.length === 0 ? media.evalTop(env) :
3903                     media.evalNested(env);
3904     },
3905     variable: function (name) { return tree.Ruleset.prototype.variable.call(this.rules[0], name); },
3906     find: function () { return tree.Ruleset.prototype.find.apply(this.rules[0], arguments); },
3907     rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); },
3908     emptySelectors: function() { 
3909         var el = new(tree.Element)('', '&', this.index, this.currentFileInfo);
3910         return [new(tree.Selector)([el], null, null, this.index, this.currentFileInfo)];
3911     },
3912     markReferenced: function () {
3913         var i, rules = this.rules[0].rules;
3914         this.isReferenced = true;
3915         for (i = 0; i < rules.length; i++) {
3916             if (rules[i].markReferenced) {
3917                 rules[i].markReferenced();
3918             }
3919         }
3920     },
3921
3922     evalTop: function (env) {
3923         var result = this;
3924
3925         // Render all dependent Media blocks.
3926         if (env.mediaBlocks.length > 1) {
3927             var selectors = this.emptySelectors();
3928             result = new(tree.Ruleset)(selectors, env.mediaBlocks);
3929             result.multiMedia = true;
3930         }
3931
3932         delete env.mediaBlocks;
3933         delete env.mediaPath;
3934
3935         return result;
3936     },
3937     evalNested: function (env) {
3938         var i, value,
3939             path = env.mediaPath.concat([this]);
3940
3941         // Extract the media-query conditions separated with `,` (OR).
3942         for (i = 0; i < path.length; i++) {
3943             value = path[i].features instanceof tree.Value ?
3944                         path[i].features.value : path[i].features;
3945             path[i] = Array.isArray(value) ? value : [value];
3946         }
3947
3948         // Trace all permutations to generate the resulting media-query.
3949         //
3950         // (a, b and c) with nested (d, e) ->
3951         //    a and d
3952         //    a and e
3953         //    b and c and d
3954         //    b and c and e
3955         this.features = new(tree.Value)(this.permute(path).map(function (path) {
3956             path = path.map(function (fragment) {
3957                 return fragment.toCSS ? fragment : new(tree.Anonymous)(fragment);
3958             });
3959
3960             for(i = path.length - 1; i > 0; i--) {
3961                 path.splice(i, 0, new(tree.Anonymous)("and"));
3962             }
3963
3964             return new(tree.Expression)(path);
3965         }));
3966
3967         // Fake a tree-node that doesn't output anything.
3968         return new(tree.Ruleset)([], []);
3969     },
3970     permute: function (arr) {
3971       if (arr.length === 0) {
3972           return [];
3973       } else if (arr.length === 1) {
3974           return arr[0];
3975       } else {
3976           var result = [];
3977           var rest = this.permute(arr.slice(1));
3978           for (var i = 0; i < rest.length; i++) {
3979               for (var j = 0; j < arr[0].length; j++) {
3980                   result.push([arr[0][j]].concat(rest[i]));
3981               }
3982           }
3983           return result;
3984       }
3985     },
3986     bubbleSelectors: function (selectors) {
3987       this.rules = [new(tree.Ruleset)(selectors.slice(0), [this.rules[0]])];
3988     }
3989 };
3990
3991 })(require('../tree'));
3992
3993 (function (tree) {
3994
3995 tree.mixin = {};
3996 tree.mixin.Call = function (elements, args, index, currentFileInfo, important) {
3997     this.selector = new(tree.Selector)(elements);
3998     this.arguments = args;
3999     this.index = index;
4000     this.currentFileInfo = currentFileInfo;
4001     this.important = important;
4002 };
4003 tree.mixin.Call.prototype = {
4004     type: "MixinCall",
4005     accept: function (visitor) {
4006         this.selector = visitor.visit(this.selector);
4007         this.arguments = visitor.visit(this.arguments);
4008     },
4009     eval: function (env) {
4010         var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule;
4011
4012         args = this.arguments && this.arguments.map(function (a) {
4013             return { name: a.name, value: a.value.eval(env) };
4014         });
4015
4016         for (i = 0; i < env.frames.length; i++) {
4017             if ((mixins = env.frames[i].find(this.selector)).length > 0) {
4018                 isOneFound = true;
4019                 for (m = 0; m < mixins.length; m++) {
4020                     mixin = mixins[m];
4021                     isRecursive = false;
4022                     for(f = 0; f < env.frames.length; f++) {
4023                         if ((!(mixin instanceof tree.mixin.Definition)) && mixin === (env.frames[f].originalRuleset || env.frames[f])) {
4024                             isRecursive = true;
4025                             break;
4026                         }
4027                     }
4028                     if (isRecursive) {
4029                         continue;
4030                     }
4031                     if (mixin.matchArgs(args, env)) {
4032                         if (!mixin.matchCondition || mixin.matchCondition(args, env)) {
4033                             try {
4034                                 if (!(mixin instanceof tree.mixin.Definition)) {
4035                                     mixin = new tree.mixin.Definition("", [], mixin.rules, null, false);
4036                                     mixin.originalRuleset = mixins[m].originalRuleset || mixins[m];
4037                                 }
4038                                 //if (this.important) {
4039                                 //    isImportant = env.isImportant;
4040                                 //    env.isImportant = true;
4041                                 //}
4042                                 Array.prototype.push.apply(
4043                                       rules, mixin.eval(env, args, this.important).rules);
4044                                 //if (this.important) {
4045                                 //    env.isImportant = isImportant;
4046                                 //}
4047                             } catch (e) {
4048                                 throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
4049                             }
4050                         }
4051                         match = true;
4052                     }
4053                 }
4054                 if (match) {
4055                     if (!this.currentFileInfo || !this.currentFileInfo.reference) {
4056                         for (i = 0; i < rules.length; i++) {
4057                             rule = rules[i];
4058                             if (rule.markReferenced) {
4059                                 rule.markReferenced();
4060                             }
4061                         }
4062                     }
4063                     return rules;
4064                 }
4065             }
4066         }
4067         if (isOneFound) {
4068             throw { type:    'Runtime',
4069                     message: 'No matching definition was found for `' +
4070                               this.selector.toCSS().trim() + '('      +
4071                               (args ? args.map(function (a) {
4072                                   var argValue = "";
4073                                   if (a.name) {
4074                                       argValue += a.name + ":";
4075                                   }
4076                                   if (a.value.toCSS) {
4077                                       argValue += a.value.toCSS();
4078                                   } else {
4079                                       argValue += "???";
4080                                   }
4081                                   return argValue;
4082                               }).join(', ') : "") + ")`",
4083                     index:   this.index, filename: this.currentFileInfo.filename };
4084         } else {
4085             throw { type: 'Name',
4086                 message: this.selector.toCSS().trim() + " is undefined",
4087                 index: this.index, filename: this.currentFileInfo.filename };
4088         }
4089     }
4090 };
4091
4092 tree.mixin.Definition = function (name, params, rules, condition, variadic) {
4093     this.name = name;
4094     this.selectors = [new(tree.Selector)([new(tree.Element)(null, name, this.index, this.currentFileInfo)])];
4095     this.params = params;
4096     this.condition = condition;
4097     this.variadic = variadic;
4098     this.arity = params.length;
4099     this.rules = rules;
4100     this._lookups = {};
4101     this.required = params.reduce(function (count, p) {
4102         if (!p.name || (p.name && !p.value)) { return count + 1; }
4103         else                                 { return count; }
4104     }, 0);
4105     this.parent = tree.Ruleset.prototype;
4106     this.frames = [];
4107 };
4108 tree.mixin.Definition.prototype = {
4109     type: "MixinDefinition",
4110     accept: function (visitor) {
4111         this.params = visitor.visit(this.params);
4112         this.rules = visitor.visit(this.rules);
4113         this.condition = visitor.visit(this.condition);
4114     },
4115     variable:  function (name) { return this.parent.variable.call(this, name); },
4116     variables: function ()     { return this.parent.variables.call(this); },
4117     find:      function ()     { return this.parent.find.apply(this, arguments); },
4118     rulesets:  function ()     { return this.parent.rulesets.apply(this); },
4119
4120     evalParams: function (env, mixinEnv, args, evaldArguments) {
4121         /*jshint boss:true */
4122         var frame = new(tree.Ruleset)(null, []),
4123             varargs, arg,
4124             params = this.params.slice(0),
4125             i, j, val, name, isNamedFound, argIndex;
4126
4127         mixinEnv = new tree.evalEnv(mixinEnv, [frame].concat(mixinEnv.frames));
4128         
4129         if (args) {
4130             args = args.slice(0);
4131
4132             for(i = 0; i < args.length; i++) {
4133                 arg = args[i];
4134                 if (name = (arg && arg.name)) {
4135                     isNamedFound = false;
4136                     for(j = 0; j < params.length; j++) {
4137                         if (!evaldArguments[j] && name === params[j].name) {
4138                             evaldArguments[j] = arg.value.eval(env);
4139                             frame.rules.unshift(new(tree.Rule)(name, arg.value.eval(env)));
4140                             isNamedFound = true;
4141                             break;
4142                         }
4143                     }
4144                     if (isNamedFound) {
4145                         args.splice(i, 1);
4146                         i--;
4147                         continue;
4148                     } else {
4149                         throw { type: 'Runtime', message: "Named argument for " + this.name +
4150                             ' ' + args[i].name + ' not found' };
4151                     }
4152                 }
4153             }
4154         }
4155         argIndex = 0;
4156         for (i = 0; i < params.length; i++) {
4157             if (evaldArguments[i]) { continue; }
4158             
4159             arg = args && args[argIndex];
4160
4161             if (name = params[i].name) {
4162                 if (params[i].variadic && args) {
4163                     varargs = [];
4164                     for (j = argIndex; j < args.length; j++) {
4165                         varargs.push(args[j].value.eval(env));
4166                     }
4167                     frame.rules.unshift(new(tree.Rule)(name, new(tree.Expression)(varargs).eval(env)));
4168                 } else {
4169                     val = arg && arg.value;
4170                     if (val) {
4171                         val = val.eval(env);
4172                     } else if (params[i].value) {
4173                         val = params[i].value.eval(mixinEnv);
4174                         frame.resetCache();
4175                     } else {
4176                         throw { type: 'Runtime', message: "wrong number of arguments for " + this.name +
4177                             ' (' + args.length + ' for ' + this.arity + ')' };
4178                     }
4179                     
4180                     frame.rules.unshift(new(tree.Rule)(name, val));
4181                     evaldArguments[i] = val;
4182                 }
4183             }
4184             
4185             if (params[i].variadic && args) {
4186                 for (j = argIndex; j < args.length; j++) {
4187                     evaldArguments[j] = args[j].value.eval(env);
4188                 }
4189             }
4190             argIndex++;
4191         }
4192
4193         return frame;
4194     },
4195     eval: function (env, args, important) {
4196         var _arguments = [],
4197             mixinFrames = this.frames.concat(env.frames),
4198             frame = this.evalParams(env, new(tree.evalEnv)(env, mixinFrames), args, _arguments),
4199             rules, ruleset;
4200
4201         frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env)));
4202
4203         rules = this.rules.slice(0);
4204
4205         ruleset = new(tree.Ruleset)(null, rules);
4206         ruleset.originalRuleset = this;
4207         ruleset = ruleset.eval(new(tree.evalEnv)(env, [this, frame].concat(mixinFrames)));
4208         if (important) {
4209             ruleset = this.parent.makeImportant.apply(ruleset);
4210         }
4211         return ruleset;
4212     },
4213     matchCondition: function (args, env) {
4214         if (this.condition && !this.condition.eval(
4215             new(tree.evalEnv)(env,
4216                 [this.evalParams(env, new(tree.evalEnv)(env, this.frames.concat(env.frames)), args, [])] // the parameter variables
4217                     .concat(this.frames) // the parent namespace/mixin frames
4218                     .concat(env.frames)))) { // the current environment frames
4219             return false;
4220         }
4221         return true;
4222     },
4223     matchArgs: function (args, env) {
4224         var argsLength = (args && args.length) || 0, len;
4225
4226         if (! this.variadic) {
4227             if (argsLength < this.required)                               { return false; }
4228             if (argsLength > this.params.length)                          { return false; }
4229         } else {
4230             if (argsLength < (this.required - 1))                         { return false; }
4231         }
4232
4233         len = Math.min(argsLength, this.arity);
4234
4235         for (var i = 0; i < len; i++) {
4236             if (!this.params[i].name && !this.params[i].variadic) {
4237                 if (args[i].value.eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
4238                     return false;
4239                 }
4240             }
4241         }
4242         return true;
4243     }
4244 };
4245
4246 })(require('../tree'));
4247
4248 (function (tree) {
4249
4250 tree.Negative = function (node) {
4251     this.value = node;
4252 };
4253 tree.Negative.prototype = {
4254     type: "Negative",
4255     accept: function (visitor) {
4256         this.value = visitor.visit(this.value);
4257     },
4258     genCSS: function (env, output) {
4259         output.add('-');
4260         this.value.genCSS(env, output);
4261     },
4262     toCSS: tree.toCSS,
4263     eval: function (env) {
4264         if (env.isMathOn()) {
4265             return (new(tree.Operation)('*', [new(tree.Dimension)(-1), this.value])).eval(env);
4266         }
4267         return new(tree.Negative)(this.value.eval(env));
4268     }
4269 };
4270
4271 })(require('../tree'));
4272
4273 (function (tree) {
4274
4275 tree.Operation = function (op, operands, isSpaced) {
4276     this.op = op.trim();
4277     this.operands = operands;
4278     this.isSpaced = isSpaced;
4279 };
4280 tree.Operation.prototype = {
4281     type: "Operation",
4282     accept: function (visitor) {
4283         this.operands = visitor.visit(this.operands);
4284     },
4285     eval: function (env) {
4286         var a = this.operands[0].eval(env),
4287             b = this.operands[1].eval(env),
4288             temp;
4289
4290         if (env.isMathOn()) {
4291             if (a instanceof tree.Dimension && b instanceof tree.Color) {
4292                 if (this.op === '*' || this.op === '+') {
4293                     temp = b, b = a, a = temp;
4294                 } else {
4295                     throw { type: "Operation",
4296                             message: "Can't substract or divide a color from a number" };
4297                 }
4298             }
4299             if (!a.operate) {
4300                 throw { type: "Operation",
4301                         message: "Operation on an invalid type" };
4302             }
4303
4304             return a.operate(env, this.op, b);
4305         } else {
4306             return new(tree.Operation)(this.op, [a, b], this.isSpaced);
4307         }
4308     },
4309     genCSS: function (env, output) {
4310         this.operands[0].genCSS(env, output);
4311         if (this.isSpaced) {
4312             output.add(" ");
4313         }
4314         output.add(this.op);
4315         if (this.isSpaced) {
4316             output.add(" ");
4317         }
4318         this.operands[1].genCSS(env, output);
4319     },
4320     toCSS: tree.toCSS
4321 };
4322
4323 tree.operate = function (env, op, a, b) {
4324     switch (op) {
4325         case '+': return a + b;
4326         case '-': return a - b;
4327         case '*': return a * b;
4328         case '/': return a / b;
4329     }
4330 };
4331
4332 })(require('../tree'));
4333
4334
4335 (function (tree) {
4336
4337 tree.Paren = function (node) {
4338     this.value = node;
4339 };
4340 tree.Paren.prototype = {
4341     type: "Paren",
4342     accept: function (visitor) {
4343         this.value = visitor.visit(this.value);
4344     },
4345     genCSS: function (env, output) {
4346         output.add('(');
4347         this.value.genCSS(env, output);
4348         output.add(')');
4349     },
4350     toCSS: tree.toCSS,
4351     eval: function (env) {
4352         return new(tree.Paren)(this.value.eval(env));
4353     }
4354 };
4355
4356 })(require('../tree'));
4357
4358 (function (tree) {
4359
4360 tree.Quoted = function (str, content, escaped, index, currentFileInfo) {
4361     this.escaped = escaped;
4362     this.value = content || '';
4363     this.quote = str.charAt(0);
4364     this.index = index;
4365     this.currentFileInfo = currentFileInfo;
4366 };
4367 tree.Quoted.prototype = {
4368     type: "Quoted",
4369     genCSS: function (env, output) {
4370         if (!this.escaped) {
4371             output.add(this.quote, this.currentFileInfo, this.index);
4372         }
4373         output.add(this.value);
4374         if (!this.escaped) {
4375             output.add(this.quote);
4376         }
4377     },
4378     toCSS: tree.toCSS,
4379     eval: function (env) {
4380         var that = this;
4381         var value = this.value.replace(/`([^`]+)`/g, function (_, exp) {
4382             return new(tree.JavaScript)(exp, that.index, true).eval(env).value;
4383         }).replace(/@\{([\w-]+)\}/g, function (_, name) {
4384             var v = new(tree.Variable)('@' + name, that.index, that.currentFileInfo).eval(env, true);
4385             return (v instanceof tree.Quoted) ? v.value : v.toCSS();
4386         });
4387         return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index, this.currentFileInfo);
4388     },
4389     compare: function (x) {
4390         if (!x.toCSS) {
4391             return -1;
4392         }
4393         
4394         var left = this.toCSS(),
4395             right = x.toCSS();
4396         
4397         if (left === right) {
4398             return 0;
4399         }
4400         
4401         return left < right ? -1 : 1;
4402     }
4403 };
4404
4405 })(require('../tree'));
4406
4407 (function (tree) {
4408
4409 tree.Rule = function (name, value, important, merge, index, currentFileInfo, inline) {
4410     this.name = name;
4411     this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]);
4412     this.important = important ? ' ' + important.trim() : '';
4413     this.merge = merge;
4414     this.index = index;
4415     this.currentFileInfo = currentFileInfo;
4416     this.inline = inline || false;
4417     this.variable = (name.charAt(0) === '@');
4418 };
4419
4420 tree.Rule.prototype = {
4421     type: "Rule",
4422     accept: function (visitor) {
4423         this.value = visitor.visit(this.value);
4424     },
4425     genCSS: function (env, output) {
4426         output.add(this.name + (env.compress ? ':' : ': '), this.currentFileInfo, this.index);
4427         try {
4428             this.value.genCSS(env, output);
4429         }
4430         catch(e) {
4431             e.index = this.index;
4432             e.filename = this.currentFileInfo.filename;
4433             throw e;
4434         }
4435         output.add(this.important + ((this.inline || (env.lastRule && env.compress)) ? "" : ";"), this.currentFileInfo, this.index);
4436     },
4437     toCSS: tree.toCSS,
4438     eval: function (env) {
4439         var strictMathBypass = false;
4440         if (this.name === "font" && !env.strictMath) {
4441             strictMathBypass = true;
4442             env.strictMath = true;
4443         }
4444         try {
4445             return new(tree.Rule)(this.name,
4446                               this.value.eval(env),
4447                               this.important,
4448                               this.merge,
4449                               this.index, this.currentFileInfo, this.inline);
4450         }
4451         finally {
4452             if (strictMathBypass) {
4453                 env.strictMath = false;
4454             }
4455         }
4456     },
4457     makeImportant: function () {
4458         return new(tree.Rule)(this.name,
4459                               this.value,
4460                               "!important",
4461                               this.merge,
4462                               this.index, this.currentFileInfo, this.inline);
4463     }
4464 };
4465
4466 })(require('../tree'));
4467
4468 (function (tree) {
4469
4470 tree.Ruleset = function (selectors, rules, strictImports) {
4471     this.selectors = selectors;
4472     this.rules = rules;
4473     this._lookups = {};
4474     this.strictImports = strictImports;
4475 };
4476 tree.Ruleset.prototype = {
4477     type: "Ruleset",
4478     accept: function (visitor) {
4479         if (this.paths) {
4480             for(var i = 0; i < this.paths.length; i++) {
4481                 this.paths[i] = visitor.visit(this.paths[i]);
4482             }
4483         } else {
4484             this.selectors = visitor.visit(this.selectors);
4485         }
4486         this.rules = visitor.visit(this.rules);
4487     },
4488     eval: function (env) {
4489         var selectors = this.selectors && this.selectors.map(function (s) { return s.eval(env); });
4490         var ruleset = new(tree.Ruleset)(selectors, this.rules.slice(0), this.strictImports);
4491         var rules;
4492         var rule;
4493         var i;
4494         
4495         ruleset.originalRuleset = this;
4496         ruleset.root = this.root;
4497         ruleset.firstRoot = this.firstRoot;
4498         ruleset.allowImports = this.allowImports;
4499
4500         if(this.debugInfo) {
4501             ruleset.debugInfo = this.debugInfo;
4502         }
4503
4504         // push the current ruleset to the frames stack
4505         env.frames.unshift(ruleset);
4506
4507         // currrent selectors
4508         if (!env.selectors) {
4509             env.selectors = [];
4510         }
4511         env.selectors.unshift(this.selectors);
4512
4513         // Evaluate imports
4514         if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) {
4515             ruleset.evalImports(env);
4516         }
4517
4518         // Store the frames around mixin definitions,
4519         // so they can be evaluated like closures when the time comes.
4520         for (i = 0; i < ruleset.rules.length; i++) {
4521             if (ruleset.rules[i] instanceof tree.mixin.Definition) {
4522                 ruleset.rules[i].frames = env.frames.slice(0);
4523             }
4524         }
4525         
4526         var mediaBlockCount = (env.mediaBlocks && env.mediaBlocks.length) || 0;
4527
4528         // Evaluate mixin calls.
4529         for (i = 0; i < ruleset.rules.length; i++) {
4530             if (ruleset.rules[i] instanceof tree.mixin.Call) {
4531                 /*jshint loopfunc:true */
4532                 rules = ruleset.rules[i].eval(env).filter(function(r) {
4533                     if ((r instanceof tree.Rule) && r.variable) {
4534                         // do not pollute the scope if the variable is
4535                         // already there. consider returning false here
4536                         // but we need a way to "return" variable from mixins
4537                         return !(ruleset.variable(r.name));
4538                     }
4539                     return true;
4540                 });
4541                 ruleset.rules.splice.apply(ruleset.rules, [i, 1].concat(rules));
4542                 i += rules.length-1;
4543                 ruleset.resetCache();
4544             }
4545         }
4546         
4547         // Evaluate everything else
4548         for (i = 0; i < ruleset.rules.length; i++) {
4549             rule = ruleset.rules[i];
4550
4551             if (! (rule instanceof tree.mixin.Definition)) {
4552                 ruleset.rules[i] = rule.eval ? rule.eval(env) : rule;
4553             }
4554         }
4555
4556         // Pop the stack
4557         env.frames.shift();
4558         env.selectors.shift();
4559         
4560         if (env.mediaBlocks) {
4561             for (i = mediaBlockCount; i < env.mediaBlocks.length; i++) {
4562                 env.mediaBlocks[i].bubbleSelectors(selectors);
4563             }
4564         }
4565
4566         return ruleset;
4567     },
4568     evalImports: function(env) {
4569         var i, rules;
4570         for (i = 0; i < this.rules.length; i++) {
4571             if (this.rules[i] instanceof tree.Import) {
4572                 rules = this.rules[i].eval(env);
4573                 if (typeof rules.length === "number") {
4574                     this.rules.splice.apply(this.rules, [i, 1].concat(rules));
4575                     i+= rules.length-1;
4576                 } else {
4577                     this.rules.splice(i, 1, rules);
4578                 }
4579                 this.resetCache();
4580             }
4581         }
4582     },
4583     makeImportant: function() {
4584         return new tree.Ruleset(this.selectors, this.rules.map(function (r) {
4585                     if (r.makeImportant) {
4586                         return r.makeImportant();
4587                     } else {
4588                         return r;
4589                     }
4590                 }), this.strictImports);
4591     },
4592     matchArgs: function (args) {
4593         return !args || args.length === 0;
4594     },
4595     matchCondition: function (args, env) {
4596         var lastSelector = this.selectors[this.selectors.length-1];
4597         if (lastSelector.condition &&
4598             !lastSelector.condition.eval(
4599                 new(tree.evalEnv)(env,
4600                     env.frames))) {
4601             return false;
4602         }
4603         return true;
4604     },
4605     resetCache: function () {
4606         this._rulesets = null;
4607         this._variables = null;
4608         this._lookups = {};
4609     },
4610     variables: function () {
4611         if (this._variables) { return this._variables; }
4612         else {
4613             return this._variables = this.rules.reduce(function (hash, r) {
4614                 if (r instanceof tree.Rule && r.variable === true) {
4615                     hash[r.name] = r;
4616                 }
4617                 return hash;
4618             }, {});
4619         }
4620     },
4621     variable: function (name) {
4622         return this.variables()[name];
4623     },
4624     rulesets: function () {
4625         return this.rules.filter(function (r) {
4626             return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition);
4627         });
4628     },
4629     find: function (selector, self) {
4630         self = self || this;
4631         var rules = [], match,
4632             key = selector.toCSS();
4633
4634         if (key in this._lookups) { return this._lookups[key]; }
4635
4636         this.rulesets().forEach(function (rule) {
4637             if (rule !== self) {
4638                 for (var j = 0; j < rule.selectors.length; j++) {
4639                     if (match = selector.match(rule.selectors[j])) {
4640                         if (selector.elements.length > match) {
4641                             Array.prototype.push.apply(rules, rule.find(
4642                                 new(tree.Selector)(selector.elements.slice(match)), self));
4643                         } else {
4644                             rules.push(rule);
4645                         }
4646                         break;
4647                     }
4648                 }
4649             }
4650         });
4651         return this._lookups[key] = rules;
4652     },
4653     genCSS: function (env, output) {
4654         var i, j,
4655             ruleNodes = [],
4656             rulesetNodes = [],
4657             debugInfo,     // Line number debugging
4658             rule,
4659             firstRuleset = true,
4660             path;
4661
4662         env.tabLevel = (env.tabLevel || 0);
4663
4664         if (!this.root) {
4665             env.tabLevel++;
4666         }
4667
4668         var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join("  "),
4669             tabSetStr = env.compress ? '' : Array(env.tabLevel).join("  ");
4670
4671         for (i = 0; i < this.rules.length; i++) {
4672             rule = this.rules[i];
4673             if (rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive || (this.root && rule instanceof tree.Comment)) {
4674                 rulesetNodes.push(rule);
4675             } else {
4676                 ruleNodes.push(rule);
4677             }
4678         }
4679
4680         // If this is the root node, we don't render
4681         // a selector, or {}.
4682         if (!this.root) {
4683             debugInfo = tree.debugInfo(env, this, tabSetStr);
4684
4685             if (debugInfo) {
4686                 output.add(debugInfo);
4687                 output.add(tabSetStr);
4688             }
4689
4690             for(i = 0; i < this.paths.length; i++) {
4691                 path = this.paths[i];
4692                 env.firstSelector = true;
4693                 for(j = 0; j < path.length; j++) {
4694                     path[j].genCSS(env, output);
4695                     env.firstSelector = false;
4696                 }
4697                 if (i + 1 < this.paths.length) {
4698                     output.add(env.compress ? ',' : (',\n' + tabSetStr));
4699                 }
4700             }
4701
4702             output.add((env.compress ? '{' : ' {\n') + tabRuleStr);
4703         }
4704
4705         // Compile rules and rulesets
4706         for (i = 0; i < ruleNodes.length; i++) {
4707             rule = ruleNodes[i];
4708
4709             // @page{ directive ends up with root elements inside it, a mix of rules and rulesets
4710             // In this instance we do not know whether it is the last property
4711             if (i + 1 === ruleNodes.length && (!this.root || rulesetNodes.length === 0 || this.firstRoot)) {
4712                 env.lastRule = true;
4713             }
4714
4715             if (rule.genCSS) {
4716                 rule.genCSS(env, output);
4717             } else if (rule.value) {
4718                 output.add(rule.value.toString());
4719             }
4720
4721             if (!env.lastRule) {
4722                 output.add(env.compress ? '' : ('\n' + tabRuleStr));
4723             } else {
4724                 env.lastRule = false;
4725             }
4726         }
4727
4728         if (!this.root) {
4729             output.add((env.compress ? '}' : '\n' + tabSetStr + '}'));
4730             env.tabLevel--;
4731         }
4732
4733         for (i = 0; i < rulesetNodes.length; i++) {
4734             if (ruleNodes.length && firstRuleset) {
4735                 output.add((env.compress ? "" : "\n") + (this.root ? tabRuleStr : tabSetStr));
4736             }
4737             if (!firstRuleset) {
4738                 output.add((env.compress ? "" : "\n") + (this.root ? tabRuleStr : tabSetStr));
4739             }
4740             firstRuleset = false;
4741             rulesetNodes[i].genCSS(env, output);
4742         }
4743
4744         if (!output.isEmpty() && !env.compress && this.firstRoot) {
4745             output.add('\n');
4746         }
4747     },
4748
4749     toCSS: tree.toCSS,
4750
4751     markReferenced: function () {
4752         for (var s = 0; s < this.selectors.length; s++) {
4753             this.selectors[s].markReferenced();
4754         }
4755     },
4756
4757     joinSelectors: function (paths, context, selectors) {
4758         for (var s = 0; s < selectors.length; s++) {
4759             this.joinSelector(paths, context, selectors[s]);
4760         }
4761     },
4762
4763     joinSelector: function (paths, context, selector) {
4764
4765         var i, j, k, 
4766             hasParentSelector, newSelectors, el, sel, parentSel, 
4767             newSelectorPath, afterParentJoin, newJoinedSelector, 
4768             newJoinedSelectorEmpty, lastSelector, currentElements,
4769             selectorsMultiplied;
4770     
4771         for (i = 0; i < selector.elements.length; i++) {
4772             el = selector.elements[i];
4773             if (el.value === '&') {
4774                 hasParentSelector = true;
4775             }
4776         }
4777     
4778         if (!hasParentSelector) {
4779             if (context.length > 0) {
4780                 for (i = 0; i < context.length; i++) {
4781                     paths.push(context[i].concat(selector));
4782                 }
4783             }
4784             else {
4785                 paths.push([selector]);
4786             }
4787             return;
4788         }
4789
4790         // The paths are [[Selector]]
4791         // The first list is a list of comma seperated selectors
4792         // The inner list is a list of inheritance seperated selectors
4793         // e.g.
4794         // .a, .b {
4795         //   .c {
4796         //   }
4797         // }
4798         // == [[.a] [.c]] [[.b] [.c]]
4799         //
4800
4801         // the elements from the current selector so far
4802         currentElements = [];
4803         // the current list of new selectors to add to the path.
4804         // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
4805         // by the parents
4806         newSelectors = [[]];
4807
4808         for (i = 0; i < selector.elements.length; i++) {
4809             el = selector.elements[i];
4810             // non parent reference elements just get added
4811             if (el.value !== "&") {
4812                 currentElements.push(el);
4813             } else {
4814                 // the new list of selectors to add
4815                 selectorsMultiplied = [];
4816
4817                 // merge the current list of non parent selector elements
4818                 // on to the current list of selectors to add
4819                 if (currentElements.length > 0) {
4820                     this.mergeElementsOnToSelectors(currentElements, newSelectors);
4821                 }
4822
4823                 // loop through our current selectors
4824                 for (j = 0; j < newSelectors.length; j++) {
4825                     sel = newSelectors[j];
4826                     // if we don't have any parent paths, the & might be in a mixin so that it can be used
4827                     // whether there are parents or not
4828                     if (context.length === 0) {
4829                         // the combinator used on el should now be applied to the next element instead so that
4830                         // it is not lost
4831                         if (sel.length > 0) {
4832                             sel[0].elements = sel[0].elements.slice(0);
4833                             sel[0].elements.push(new(tree.Element)(el.combinator, '', 0, el.index, el.currentFileInfo));
4834                         }
4835                         selectorsMultiplied.push(sel);
4836                     }
4837                     else {
4838                         // and the parent selectors
4839                         for (k = 0; k < context.length; k++) {
4840                             parentSel = context[k];
4841                             // We need to put the current selectors
4842                             // then join the last selector's elements on to the parents selectors
4843
4844                             // our new selector path
4845                             newSelectorPath = [];
4846                             // selectors from the parent after the join
4847                             afterParentJoin = [];
4848                             newJoinedSelectorEmpty = true;
4849
4850                             //construct the joined selector - if & is the first thing this will be empty,
4851                             // if not newJoinedSelector will be the last set of elements in the selector
4852                             if (sel.length > 0) {
4853                                 newSelectorPath = sel.slice(0);
4854                                 lastSelector = newSelectorPath.pop();
4855                                 newJoinedSelector = selector.createDerived(lastSelector.elements.slice(0));
4856                                 newJoinedSelectorEmpty = false;
4857                             }
4858                             else {
4859                                 newJoinedSelector = selector.createDerived([]);
4860                             }
4861
4862                             //put together the parent selectors after the join
4863                             if (parentSel.length > 1) {
4864                                 afterParentJoin = afterParentJoin.concat(parentSel.slice(1));
4865                             }
4866
4867                             if (parentSel.length > 0) {
4868                                 newJoinedSelectorEmpty = false;
4869
4870                                 // join the elements so far with the first part of the parent
4871                                 newJoinedSelector.elements.push(new(tree.Element)(el.combinator, parentSel[0].elements[0].value, el.index, el.currentFileInfo));
4872                                 newJoinedSelector.elements = newJoinedSelector.elements.concat(parentSel[0].elements.slice(1));
4873                             }
4874
4875                             if (!newJoinedSelectorEmpty) {
4876                                 // now add the joined selector
4877                                 newSelectorPath.push(newJoinedSelector);
4878                             }
4879
4880                             // and the rest of the parent
4881                             newSelectorPath = newSelectorPath.concat(afterParentJoin);
4882
4883                             // add that to our new set of selectors
4884                             selectorsMultiplied.push(newSelectorPath);
4885                         }
4886                     }
4887                 }
4888
4889                 // our new selectors has been multiplied, so reset the state
4890                 newSelectors = selectorsMultiplied;
4891                 currentElements = [];
4892             }
4893         }
4894
4895         // if we have any elements left over (e.g. .a& .b == .b)
4896         // add them on to all the current selectors
4897         if (currentElements.length > 0) {
4898             this.mergeElementsOnToSelectors(currentElements, newSelectors);
4899         }
4900
4901         for (i = 0; i < newSelectors.length; i++) {
4902             if (newSelectors[i].length > 0) {
4903                 paths.push(newSelectors[i]);
4904             }
4905         }
4906     },
4907     
4908     mergeElementsOnToSelectors: function(elements, selectors) {
4909         var i, sel;
4910
4911         if (selectors.length === 0) {
4912             selectors.push([ new(tree.Selector)(elements) ]);
4913             return;
4914         }
4915
4916         for (i = 0; i < selectors.length; i++) {
4917             sel = selectors[i];
4918
4919             // if the previous thing in sel is a parent this needs to join on to it
4920             if (sel.length > 0) {
4921                 sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements));
4922             }
4923             else {
4924                 sel.push(new(tree.Selector)(elements));
4925             }
4926         }
4927     }
4928 };
4929 })(require('../tree'));
4930
4931 (function (tree) {
4932
4933 tree.Selector = function (elements, extendList, condition, index, currentFileInfo, isReferenced) {
4934     this.elements = elements;
4935     this.extendList = extendList || [];
4936     this.condition = condition;
4937     this.currentFileInfo = currentFileInfo || {};
4938     this.isReferenced = isReferenced;
4939     if (!condition) {
4940         this.evaldCondition = true;
4941     }
4942 };
4943 tree.Selector.prototype = {
4944     type: "Selector",
4945     accept: function (visitor) {
4946         this.elements = visitor.visit(this.elements);
4947         this.extendList = visitor.visit(this.extendList);
4948         this.condition = visitor.visit(this.condition);
4949     },
4950     createDerived: function(elements, extendList, evaldCondition) {
4951         /*jshint eqnull:true */
4952         evaldCondition = evaldCondition != null ? evaldCondition : this.evaldCondition;
4953         var newSelector = new(tree.Selector)(elements, extendList || this.extendList, this.condition, this.index, this.currentFileInfo, this.isReferenced);
4954         newSelector.evaldCondition = evaldCondition;
4955         return newSelector;
4956     },
4957     match: function (other) {
4958         var elements = this.elements,
4959             len = elements.length,
4960             oelements, olen, max, i;
4961
4962         oelements = other.elements.slice(
4963             (other.elements.length && other.elements[0].value === "&") ? 1 : 0);
4964         olen = oelements.length;
4965         max = Math.min(len, olen);
4966
4967         if (olen === 0 || len < olen) {
4968             return 0;
4969         } else {
4970             for (i = 0; i < max; i++) {
4971                 if (elements[i].value !== oelements[i].value) {
4972                     return 0;
4973                 }
4974             }
4975         }
4976         return max; // return number of matched selectors 
4977     },
4978     eval: function (env) {
4979         var evaldCondition = this.condition && this.condition.eval(env);
4980
4981         return this.createDerived(this.elements.map(function (e) {
4982             return e.eval(env);
4983         }), this.extendList.map(function(extend) {
4984             return extend.eval(env);
4985         }), evaldCondition);
4986     },
4987     genCSS: function (env, output) {
4988         var i, element;
4989         if ((!env || !env.firstSelector) && this.elements[0].combinator.value === "") {
4990             output.add(' ', this.currentFileInfo, this.index);
4991         }
4992         if (!this._css) {
4993             //TODO caching? speed comparison?
4994             for(i = 0; i < this.elements.length; i++) {
4995                 element = this.elements[i];
4996                 element.genCSS(env, output);
4997             }
4998         }
4999     },
5000     toCSS: tree.toCSS,
5001     markReferenced: function () {
5002         this.isReferenced = true;
5003     },
5004     getIsReferenced: function() {
5005         return !this.currentFileInfo.reference || this.isReferenced;
5006     },
5007     getIsOutput: function() {
5008         return this.evaldCondition;
5009     }
5010 };
5011
5012 })(require('../tree'));
5013
5014 (function (tree) {
5015
5016 tree.UnicodeDescriptor = function (value) {
5017     this.value = value;
5018 };
5019 tree.UnicodeDescriptor.prototype = {
5020     type: "UnicodeDescriptor",
5021     genCSS: function (env, output) {
5022         output.add(this.value);
5023     },
5024     toCSS: tree.toCSS,
5025     eval: function () { return this; }
5026 };
5027
5028 })(require('../tree'));
5029
5030 (function (tree) {
5031
5032 tree.URL = function (val, currentFileInfo) {
5033     this.value = val;
5034     this.currentFileInfo = currentFileInfo;
5035 };
5036 tree.URL.prototype = {
5037     type: "Url",
5038     accept: function (visitor) {
5039         this.value = visitor.visit(this.value);
5040     },
5041     genCSS: function (env, output) {
5042         output.add("url(");
5043         this.value.genCSS(env, output);
5044         output.add(")");
5045     },
5046     toCSS: tree.toCSS,
5047     eval: function (ctx) {
5048         var val = this.value.eval(ctx), rootpath;
5049
5050         // Add the base path if the URL is relative
5051         rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
5052         if (rootpath && typeof val.value === "string" && ctx.isPathRelative(val.value)) {
5053             if (!val.quote) {
5054                 rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\"+match; });
5055             }
5056             val.value = rootpath + val.value;
5057         }
5058
5059         val.value = ctx.normalizePath(val.value);
5060
5061         return new(tree.URL)(val, null);
5062     }
5063 };
5064
5065 })(require('../tree'));
5066
5067 (function (tree) {
5068
5069 tree.Value = function (value) {
5070     this.value = value;
5071 };
5072 tree.Value.prototype = {
5073     type: "Value",
5074     accept: function (visitor) {
5075         this.value = visitor.visit(this.value);
5076     },
5077     eval: function (env) {
5078         if (this.value.length === 1) {
5079             return this.value[0].eval(env);
5080         } else {
5081             return new(tree.Value)(this.value.map(function (v) {
5082                 return v.eval(env);
5083             }));
5084         }
5085     },
5086     genCSS: function (env, output) {
5087         var i;
5088         for(i = 0; i < this.value.length; i++) {
5089             this.value[i].genCSS(env, output);
5090             if (i+1 < this.value.length) {
5091                 output.add((env && env.compress) ? ',' : ', ');
5092             }
5093         }
5094     },
5095     toCSS: tree.toCSS
5096 };
5097
5098 })(require('../tree'));
5099
5100 (function (tree) {
5101
5102 tree.Variable = function (name, index, currentFileInfo) {
5103     this.name = name;
5104     this.index = index;
5105     this.currentFileInfo = currentFileInfo;
5106 };
5107 tree.Variable.prototype = {
5108     type: "Variable",
5109     eval: function (env) {
5110         var variable, v, name = this.name;
5111
5112         if (name.indexOf('@@') === 0) {
5113             name = '@' + new(tree.Variable)(name.slice(1)).eval(env).value;
5114         }
5115         
5116         if (this.evaluating) {
5117             throw { type: 'Name',
5118                     message: "Recursive variable definition for " + name,
5119                     filename: this.currentFileInfo.file,
5120                     index: this.index };
5121         }
5122         
5123         this.evaluating = true;
5124
5125         if (variable = tree.find(env.frames, function (frame) {
5126             if (v = frame.variable(name)) {
5127                 return v.value.eval(env);
5128             }
5129         })) { 
5130             this.evaluating = false;
5131             return variable;
5132         }
5133         else {
5134             throw { type: 'Name',
5135                     message: "variable " + name + " is undefined",
5136                     filename: this.currentFileInfo.filename,
5137                     index: this.index };
5138         }
5139     }
5140 };
5141
5142 })(require('../tree'));
5143
5144 (function (tree) {
5145
5146     var parseCopyProperties = [
5147         'paths',            // option - unmodified - paths to search for imports on
5148         'optimization',     // option - optimization level (for the chunker)
5149         'files',            // list of files that have been imported, used for import-once
5150         'contents',         // browser-only, contents of all the files
5151         'relativeUrls',     // option - whether to adjust URL's to be relative
5152         'rootpath',         // option - rootpath to append to URL's
5153         'strictImports',    // option -
5154         'insecure',         // option - whether to allow imports from insecure ssl hosts
5155         'dumpLineNumbers',  // option - whether to dump line numbers
5156         'compress',         // option - whether to compress
5157         'processImports',   // option - whether to process imports. if false then imports will not be imported
5158         'syncImport',       // option - whether to import synchronously
5159         'javascriptEnabled',// option - whether JavaScript is enabled. if undefined, defaults to true
5160         'mime',             // browser only - mime type for sheet import
5161         'useFileCache',     // browser only - whether to use the per file session cache
5162         'currentFileInfo'   // information about the current file - for error reporting and importing and making urls relative etc.
5163     ];
5164
5165     //currentFileInfo = {
5166     //  'relativeUrls' - option - whether to adjust URL's to be relative
5167     //  'filename' - full resolved filename of current file
5168     //  'rootpath' - path to append to normal URLs for this node
5169     //  'currentDirectory' - path to the current file, absolute
5170     //  'rootFilename' - filename of the base file
5171     //  'entryPath' - absolute path to the entry file
5172     //  'reference' - whether the file should not be output and only output parts that are referenced
5173
5174     tree.parseEnv = function(options) {
5175         copyFromOriginal(options, this, parseCopyProperties);
5176
5177         if (!this.contents) { this.contents = {}; }
5178         if (!this.files) { this.files = {}; }
5179
5180         if (!this.currentFileInfo) {
5181             var filename = (options && options.filename) || "input";
5182             var entryPath = filename.replace(/[^\/\\]*$/, "");
5183             if (options) {
5184                 options.filename = null;
5185             }
5186             this.currentFileInfo = {
5187                 filename: filename,
5188                 relativeUrls: this.relativeUrls,
5189                 rootpath: (options && options.rootpath) || "",
5190                 currentDirectory: entryPath,
5191                 entryPath: entryPath,
5192                 rootFilename: filename
5193             };
5194         }
5195     };
5196
5197     var evalCopyProperties = [
5198         'silent',      // whether to swallow errors and warnings
5199         'verbose',     // whether to log more activity
5200         'compress',    // whether to compress
5201         'yuicompress', // whether to compress with the outside tool yui compressor
5202         'ieCompat',    // whether to enforce IE compatibility (IE8 data-uri)
5203         'strictMath',  // whether math has to be within parenthesis
5204         'strictUnits', // whether units need to evaluate correctly
5205         'cleancss',    // whether to compress with clean-css
5206         'sourceMap',   // whether to output a source map
5207         'importMultiple'// whether we are currently importing multiple copies
5208         ];
5209
5210     tree.evalEnv = function(options, frames) {
5211         copyFromOriginal(options, this, evalCopyProperties);
5212
5213         this.frames = frames || [];
5214     };
5215
5216     tree.evalEnv.prototype.inParenthesis = function () {
5217         if (!this.parensStack) {
5218             this.parensStack = [];
5219         }
5220         this.parensStack.push(true);
5221     };
5222
5223     tree.evalEnv.prototype.outOfParenthesis = function () {
5224         this.parensStack.pop();
5225     };
5226
5227     tree.evalEnv.prototype.isMathOn = function () {
5228         return this.strictMath ? (this.parensStack && this.parensStack.length) : true;
5229     };
5230
5231     tree.evalEnv.prototype.isPathRelative = function (path) {
5232         return !/^(?:[a-z-]+:|\/)/.test(path);
5233     };
5234
5235     tree.evalEnv.prototype.normalizePath = function( path ) {
5236         var
5237           segments = path.split("/").reverse(),
5238           segment;
5239
5240         path = [];
5241         while (segments.length !== 0 ) {
5242             segment = segments.pop();
5243             switch( segment ) {
5244                 case ".":
5245                     break;
5246                 case "..":
5247                     if ((path.length === 0) || (path[path.length - 1] === "..")) {
5248                         path.push( segment );
5249                     } else {
5250                         path.pop();
5251                     }
5252                     break;
5253                 default:
5254                     path.push( segment );
5255                     break;
5256             }
5257         }
5258
5259         return path.join("/");
5260     };
5261
5262     //todo - do the same for the toCSS env
5263     //tree.toCSSEnv = function (options) {
5264     //};
5265
5266     var copyFromOriginal = function(original, destination, propertiesToCopy) {
5267         if (!original) { return; }
5268
5269         for(var i = 0; i < propertiesToCopy.length; i++) {
5270             if (original.hasOwnProperty(propertiesToCopy[i])) {
5271                 destination[propertiesToCopy[i]] = original[propertiesToCopy[i]];
5272             }
5273         }
5274     };
5275
5276 })(require('./tree'));
5277
5278 (function (tree) {
5279
5280     tree.visitor = function(implementation) {
5281         this._implementation = implementation;
5282     };
5283
5284     tree.visitor.prototype = {
5285         visit: function(node) {
5286
5287             if (node instanceof Array) {
5288                 return this.visitArray(node);
5289             }
5290
5291             if (!node || !node.type) {
5292                 return node;
5293             }
5294
5295             var funcName = "visit" + node.type,
5296                 func = this._implementation[funcName],
5297                 visitArgs, newNode;
5298             if (func) {
5299                 visitArgs = {visitDeeper: true};
5300                 newNode = func.call(this._implementation, node, visitArgs);
5301                 if (this._implementation.isReplacing) {
5302                     node = newNode;
5303                 }
5304             }
5305             if ((!visitArgs || visitArgs.visitDeeper) && node && node.accept) {
5306                 node.accept(this);
5307             }
5308             funcName = funcName + "Out";
5309             if (this._implementation[funcName]) {
5310                 this._implementation[funcName](node);
5311             }
5312             return node;
5313         },
5314         visitArray: function(nodes) {
5315             var i, newNodes = [];
5316             for(i = 0; i < nodes.length; i++) {
5317                 var evald = this.visit(nodes[i]);
5318                 if (evald instanceof Array) {
5319                     evald = this.flatten(evald);
5320                     newNodes = newNodes.concat(evald);
5321                 } else {
5322                     newNodes.push(evald);
5323                 }
5324             }
5325             if (this._implementation.isReplacing) {
5326                 return newNodes;
5327             }
5328             return nodes;
5329         },
5330         doAccept: function (node) {
5331             node.accept(this);
5332         },
5333         flatten: function(arr, master) {
5334             return arr.reduce(this.flattenReduce.bind(this), master || []);
5335         },
5336         flattenReduce: function(sum, element) {
5337             if (element instanceof Array) {
5338                 sum = this.flatten(element, sum);
5339             } else {
5340                 sum.push(element);
5341             }
5342             return sum;
5343         }
5344     };
5345
5346 })(require('./tree'));
5347 (function (tree) {
5348     tree.importVisitor = function(importer, finish, evalEnv) {
5349         this._visitor = new tree.visitor(this);
5350         this._importer = importer;
5351         this._finish = finish;
5352         this.env = evalEnv || new tree.evalEnv();
5353         this.importCount = 0;
5354     };
5355
5356     tree.importVisitor.prototype = {
5357         isReplacing: true,
5358         run: function (root) {
5359             var error;
5360             try {
5361                 // process the contents
5362                 this._visitor.visit(root);
5363             }
5364             catch(e) {
5365                 error = e;
5366             }
5367
5368             this.isFinished = true;
5369
5370             if (this.importCount === 0) {
5371                 this._finish(error);
5372             }
5373         },
5374         visitImport: function (importNode, visitArgs) {
5375             var importVisitor = this,
5376                 evaldImportNode,
5377                 inlineCSS = importNode.options.inline;
5378
5379             if (!importNode.css || inlineCSS) {
5380
5381                 try {
5382                     evaldImportNode = importNode.evalForImport(this.env);
5383                 } catch(e){
5384                     if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
5385                     // attempt to eval properly and treat as css
5386                     importNode.css = true;
5387                     // if that fails, this error will be thrown
5388                     importNode.error = e;
5389                 }
5390
5391                 if (evaldImportNode && (!evaldImportNode.css || inlineCSS)) {
5392                     importNode = evaldImportNode;
5393                     this.importCount++;
5394                     var env = new tree.evalEnv(this.env, this.env.frames.slice(0));
5395
5396                     if (importNode.options.multiple) {
5397                         env.importMultiple = true;
5398                     }
5399
5400                     this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, imported, fullPath) {
5401                         if (e && !e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
5402
5403                         if (imported && !env.importMultiple) { importNode.skip = imported; }
5404
5405                         var subFinish = function(e) {
5406                             importVisitor.importCount--;
5407
5408                             if (importVisitor.importCount === 0 && importVisitor.isFinished) {
5409                                 importVisitor._finish(e);
5410                             }
5411                         };
5412
5413                         if (root) {
5414                             importNode.root = root;
5415                             importNode.importedFilename = fullPath;
5416                             if (!inlineCSS && !importNode.skip) {
5417                                 new(tree.importVisitor)(importVisitor._importer, subFinish, env)
5418                                     .run(root);
5419                                 return;
5420                             }
5421                         }
5422
5423                         subFinish();
5424                     });
5425                 }
5426             }
5427             visitArgs.visitDeeper = false;
5428             return importNode;
5429         },
5430         visitRule: function (ruleNode, visitArgs) {
5431             visitArgs.visitDeeper = false;
5432             return ruleNode;
5433         },
5434         visitDirective: function (directiveNode, visitArgs) {
5435             this.env.frames.unshift(directiveNode);
5436             return directiveNode;
5437         },
5438         visitDirectiveOut: function (directiveNode) {
5439             this.env.frames.shift();
5440         },
5441         visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
5442             this.env.frames.unshift(mixinDefinitionNode);
5443             return mixinDefinitionNode;
5444         },
5445         visitMixinDefinitionOut: function (mixinDefinitionNode) {
5446             this.env.frames.shift();
5447         },
5448         visitRuleset: function (rulesetNode, visitArgs) {
5449             this.env.frames.unshift(rulesetNode);
5450             return rulesetNode;
5451         },
5452         visitRulesetOut: function (rulesetNode) {
5453             this.env.frames.shift();
5454         },
5455         visitMedia: function (mediaNode, visitArgs) {
5456             this.env.frames.unshift(mediaNode.ruleset);
5457             return mediaNode;
5458         },
5459         visitMediaOut: function (mediaNode) {
5460             this.env.frames.shift();
5461         }
5462     };
5463
5464 })(require('./tree'));
5465 (function (tree) {
5466     tree.joinSelectorVisitor = function() {
5467         this.contexts = [[]];
5468         this._visitor = new tree.visitor(this);
5469     };
5470
5471     tree.joinSelectorVisitor.prototype = {
5472         run: function (root) {
5473             return this._visitor.visit(root);
5474         },
5475         visitRule: function (ruleNode, visitArgs) {
5476             visitArgs.visitDeeper = false;
5477         },
5478         visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
5479             visitArgs.visitDeeper = false;
5480         },
5481
5482         visitRuleset: function (rulesetNode, visitArgs) {
5483             var context = this.contexts[this.contexts.length - 1];
5484             var paths = [];
5485             this.contexts.push(paths);
5486
5487             if (! rulesetNode.root) {
5488                 rulesetNode.selectors = rulesetNode.selectors.filter(function(selector) { return selector.getIsOutput(); });
5489                 if (rulesetNode.selectors.length === 0) {
5490                     rulesetNode.rules.length = 0;
5491                 }
5492                 rulesetNode.joinSelectors(paths, context, rulesetNode.selectors);
5493                 rulesetNode.paths = paths;
5494             }
5495         },
5496         visitRulesetOut: function (rulesetNode) {
5497             this.contexts.length = this.contexts.length - 1;
5498         },
5499         visitMedia: function (mediaNode, visitArgs) {
5500             var context = this.contexts[this.contexts.length - 1];
5501             mediaNode.rules[0].root = (context.length === 0 || context[0].multiMedia);
5502         }
5503     };
5504
5505 })(require('./tree'));
5506 (function (tree) {
5507     tree.toCSSVisitor = function(env) {
5508         this._visitor = new tree.visitor(this);
5509         this._env = env;
5510     };
5511
5512     tree.toCSSVisitor.prototype = {
5513         isReplacing: true,
5514         run: function (root) {
5515             return this._visitor.visit(root);
5516         },
5517
5518         visitRule: function (ruleNode, visitArgs) {
5519             if (ruleNode.variable) {
5520                 return [];
5521             }
5522             return ruleNode;
5523         },
5524
5525         visitMixinDefinition: function (mixinNode, visitArgs) {
5526             return [];
5527         },
5528
5529         visitExtend: function (extendNode, visitArgs) {
5530             return [];
5531         },
5532
5533         visitComment: function (commentNode, visitArgs) {
5534             if (commentNode.isSilent(this._env)) {
5535                 return [];
5536             }
5537             return commentNode;
5538         },
5539
5540         visitMedia: function(mediaNode, visitArgs) {
5541             mediaNode.accept(this._visitor);
5542             visitArgs.visitDeeper = false;
5543
5544             if (!mediaNode.rules.length) {
5545                 return [];
5546             }
5547             return mediaNode;
5548         },
5549
5550         visitDirective: function(directiveNode, visitArgs) {
5551             if (directiveNode.currentFileInfo.reference && !directiveNode.isReferenced) {
5552                 return [];
5553             }
5554             if (directiveNode.name === "@charset") {
5555                 // Only output the debug info together with subsequent @charset definitions
5556                 // a comment (or @media statement) before the actual @charset directive would
5557                 // be considered illegal css as it has to be on the first line
5558                 if (this.charset) {
5559                     if (directiveNode.debugInfo) {
5560                         var comment = new tree.Comment("/* " + directiveNode.toCSS(this._env).replace(/\n/g, "")+" */\n");
5561                         comment.debugInfo = directiveNode.debugInfo;
5562                         return this._visitor.visit(comment);
5563                     }
5564                     return [];
5565                 }
5566                 this.charset = true;
5567             }
5568             return directiveNode;
5569         },
5570
5571         checkPropertiesInRoot: function(rules) {
5572             var ruleNode;
5573             for(var i = 0; i < rules.length; i++) {
5574                 ruleNode = rules[i];
5575                 if (ruleNode instanceof tree.Rule && !ruleNode.variable) {
5576                     throw { message: "properties must be inside selector blocks, they cannot be in the root.",
5577                         index: ruleNode.index, filename: ruleNode.currentFileInfo ? ruleNode.currentFileInfo.filename : null};
5578                 }
5579             }
5580         },
5581
5582         visitRuleset: function (rulesetNode, visitArgs) {
5583             var rule, rulesets = [];
5584             if (rulesetNode.firstRoot) {
5585                 this.checkPropertiesInRoot(rulesetNode.rules);
5586             }
5587             if (! rulesetNode.root) {
5588
5589                 rulesetNode.paths = rulesetNode.paths
5590                     .filter(function(p) {
5591                         var i;
5592                         if (p[0].elements[0].combinator.value === ' ') {
5593                             p[0].elements[0].combinator = new(tree.Combinator)('');
5594                         }
5595                         for(i = 0; i < p.length; i++) {
5596                             if (p[i].getIsReferenced() && p[i].getIsOutput()) {
5597                                 return true;
5598                             }
5599                             return false;
5600                         }
5601                     });
5602
5603                 // Compile rules and rulesets
5604                 for (var i = 0; i < rulesetNode.rules.length; i++) {
5605                     rule = rulesetNode.rules[i];
5606
5607                     if (rule.rules) {
5608                         // visit because we are moving them out from being a child
5609                         rulesets.push(this._visitor.visit(rule));
5610                         rulesetNode.rules.splice(i, 1);
5611                         i--;
5612                         continue;
5613                     }
5614                 }
5615                 // accept the visitor to remove rules and refactor itself
5616                 // then we can decide now whether we want it or not
5617                 if (rulesetNode.rules.length > 0) {
5618                     rulesetNode.accept(this._visitor);
5619                 }
5620                 visitArgs.visitDeeper = false;
5621
5622                 this._mergeRules(rulesetNode.rules);
5623                 this._removeDuplicateRules(rulesetNode.rules);
5624
5625                 // now decide whether we keep the ruleset
5626                 if (rulesetNode.rules.length > 0 && rulesetNode.paths.length > 0) {
5627                     rulesets.splice(0, 0, rulesetNode);
5628                 }
5629             } else {
5630                 rulesetNode.accept(this._visitor);
5631                 visitArgs.visitDeeper = false;
5632                 if (rulesetNode.firstRoot || rulesetNode.rules.length > 0) {
5633                     rulesets.splice(0, 0, rulesetNode);
5634                 }
5635             }
5636             if (rulesets.length === 1) {
5637                 return rulesets[0];
5638             }
5639             return rulesets;
5640         },
5641
5642         _removeDuplicateRules: function(rules) {
5643             // remove duplicates
5644             var ruleCache = {},
5645                 ruleList, rule, i;
5646             for(i = rules.length - 1; i >= 0 ; i--) {
5647                 rule = rules[i];
5648                 if (rule instanceof tree.Rule) {
5649                     if (!ruleCache[rule.name]) {
5650                         ruleCache[rule.name] = rule;
5651                     } else {
5652                         ruleList = ruleCache[rule.name];
5653                         if (ruleList instanceof tree.Rule) {
5654                             ruleList = ruleCache[rule.name] = [ruleCache[rule.name].toCSS(this._env)];
5655                         }
5656                         var ruleCSS = rule.toCSS(this._env);
5657                         if (ruleList.indexOf(ruleCSS) !== -1) {
5658                             rules.splice(i, 1);
5659                         } else {
5660                             ruleList.push(ruleCSS);
5661                         }
5662                     }
5663                 }
5664             }
5665         },
5666
5667         _mergeRules: function (rules) {
5668             var groups = {},
5669                 parts,
5670                 rule,
5671                 key;
5672
5673             for (var i = 0; i < rules.length; i++) {
5674                 rule = rules[i];
5675
5676                 if ((rule instanceof tree.Rule) && rule.merge) {
5677                     key = [rule.name,
5678                         rule.important ? "!" : ""].join(",");
5679
5680                     if (!groups[key]) {
5681                         parts = groups[key] = [];
5682                     } else {
5683                         rules.splice(i--, 1);
5684                     }
5685
5686                     parts.push(rule);
5687                 }
5688             }
5689
5690             Object.keys(groups).map(function (k) {
5691                 parts = groups[k];
5692
5693                 if (parts.length > 1) {
5694                     rule = parts[0];
5695
5696                     rule.value = new (tree.Value)(parts.map(function (p) {
5697                         return p.value;
5698                     }));
5699                 }
5700             });
5701         }
5702     };
5703
5704 })(require('./tree'));
5705 (function (tree) {
5706     /*jshint loopfunc:true */
5707
5708     tree.extendFinderVisitor = function() {
5709         this._visitor = new tree.visitor(this);
5710         this.contexts = [];
5711         this.allExtendsStack = [[]];
5712     };
5713
5714     tree.extendFinderVisitor.prototype = {
5715         run: function (root) {
5716             root = this._visitor.visit(root);
5717             root.allExtends = this.allExtendsStack[0];
5718             return root;
5719         },
5720         visitRule: function (ruleNode, visitArgs) {
5721             visitArgs.visitDeeper = false;
5722         },
5723         visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
5724             visitArgs.visitDeeper = false;
5725         },
5726         visitRuleset: function (rulesetNode, visitArgs) {
5727
5728             if (rulesetNode.root) {
5729                 return;
5730             }
5731
5732             var i, j, extend, allSelectorsExtendList = [], extendList;
5733
5734             // get &:extend(.a); rules which apply to all selectors in this ruleset
5735             for(i = 0; i < rulesetNode.rules.length; i++) {
5736                 if (rulesetNode.rules[i] instanceof tree.Extend) {
5737                     allSelectorsExtendList.push(rulesetNode.rules[i]);
5738                     rulesetNode.extendOnEveryPath = true;
5739                 }
5740             }
5741
5742             // now find every selector and apply the extends that apply to all extends
5743             // and the ones which apply to an individual extend
5744             for(i = 0; i < rulesetNode.paths.length; i++) {
5745                 var selectorPath = rulesetNode.paths[i],
5746                     selector = selectorPath[selectorPath.length-1];
5747                 extendList = selector.extendList.slice(0).concat(allSelectorsExtendList).map(function(allSelectorsExtend) {
5748                     return allSelectorsExtend.clone();
5749                 });
5750                 for(j = 0; j < extendList.length; j++) {
5751                     this.foundExtends = true;
5752                     extend = extendList[j];
5753                     extend.findSelfSelectors(selectorPath);
5754                     extend.ruleset = rulesetNode;
5755                     if (j === 0) { extend.firstExtendOnThisSelectorPath = true; }
5756                     this.allExtendsStack[this.allExtendsStack.length-1].push(extend);
5757                 }
5758             }
5759
5760             this.contexts.push(rulesetNode.selectors);
5761         },
5762         visitRulesetOut: function (rulesetNode) {
5763             if (!rulesetNode.root) {
5764                 this.contexts.length = this.contexts.length - 1;
5765             }
5766         },
5767         visitMedia: function (mediaNode, visitArgs) {
5768             mediaNode.allExtends = [];
5769             this.allExtendsStack.push(mediaNode.allExtends);
5770         },
5771         visitMediaOut: function (mediaNode) {
5772             this.allExtendsStack.length = this.allExtendsStack.length - 1;
5773         },
5774         visitDirective: function (directiveNode, visitArgs) {
5775             directiveNode.allExtends = [];
5776             this.allExtendsStack.push(directiveNode.allExtends);
5777         },
5778         visitDirectiveOut: function (directiveNode) {
5779             this.allExtendsStack.length = this.allExtendsStack.length - 1;
5780         }
5781     };
5782
5783     tree.processExtendsVisitor = function() {
5784         this._visitor = new tree.visitor(this);
5785     };
5786
5787     tree.processExtendsVisitor.prototype = {
5788         run: function(root) {
5789             var extendFinder = new tree.extendFinderVisitor();
5790             extendFinder.run(root);
5791             if (!extendFinder.foundExtends) { return root; }
5792             root.allExtends = root.allExtends.concat(this.doExtendChaining(root.allExtends, root.allExtends));
5793             this.allExtendsStack = [root.allExtends];
5794             return this._visitor.visit(root);
5795         },
5796         doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
5797             //
5798             // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
5799             // the selector we would do normally, but we are also adding an extend with the same target selector
5800             // this means this new extend can then go and alter other extends
5801             //
5802             // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
5803             // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
5804             // we look at each selector at a time, as is done in visitRuleset
5805
5806             var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend, newExtend;
5807
5808             iterationCount = iterationCount || 0;
5809
5810             //loop through comparing every extend with every target extend.
5811             // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
5812             // e.g.  .a:extend(.b) {}  and .b:extend(.c) {} then the first extend extends the second one
5813             // and the second is the target.
5814             // the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
5815             // case when processing media queries
5816             for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
5817                 for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
5818
5819                     extend = extendsList[extendIndex];
5820                     targetExtend = extendsListTarget[targetExtendIndex];
5821
5822                     // look for circular references
5823                     if (this.inInheritanceChain(targetExtend, extend)) { continue; }
5824
5825                     // find a match in the target extends self selector (the bit before :extend)
5826                     selectorPath = [targetExtend.selfSelectors[0]];
5827                     matches = extendVisitor.findMatch(extend, selectorPath);
5828
5829                     if (matches.length) {
5830
5831                         // we found a match, so for each self selector..
5832                         extend.selfSelectors.forEach(function(selfSelector) {
5833
5834                             // process the extend as usual
5835                             newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector);
5836
5837                             // but now we create a new extend from it
5838                             newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0);
5839                             newExtend.selfSelectors = newSelector;
5840
5841                             // add the extend onto the list of extends for that selector
5842                             newSelector[newSelector.length-1].extendList = [newExtend];
5843
5844                             // record that we need to add it.
5845                             extendsToAdd.push(newExtend);
5846                             newExtend.ruleset = targetExtend.ruleset;
5847
5848                             //remember its parents for circular references
5849                             newExtend.parents = [targetExtend, extend];
5850
5851                             // only process the selector once.. if we have :extend(.a,.b) then multiple
5852                             // extends will look at the same selector path, so when extending
5853                             // we know that any others will be duplicates in terms of what is added to the css
5854                             if (targetExtend.firstExtendOnThisSelectorPath) {
5855                                 newExtend.firstExtendOnThisSelectorPath = true;
5856                                 targetExtend.ruleset.paths.push(newSelector);
5857                             }
5858                         });
5859                     }
5860                 }
5861             }
5862
5863             if (extendsToAdd.length) {
5864                 // try to detect circular references to stop a stack overflow.
5865                 // may no longer be needed.
5866                 this.extendChainCount++;
5867                 if (iterationCount > 100) {
5868                     var selectorOne = "{unable to calculate}";
5869                     var selectorTwo = "{unable to calculate}";
5870                     try
5871                     {
5872                         selectorOne = extendsToAdd[0].selfSelectors[0].toCSS();
5873                         selectorTwo = extendsToAdd[0].selector.toCSS();
5874                     }
5875                     catch(e) {}
5876                     throw {message: "extend circular reference detected. One of the circular extends is currently:"+selectorOne+":extend(" + selectorTwo+")"};
5877                 }
5878
5879                 // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
5880                 return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
5881             } else {
5882                 return extendsToAdd;
5883             }
5884         },
5885         inInheritanceChain: function (possibleParent, possibleChild) {
5886             if (possibleParent === possibleChild) {
5887                 return true;
5888             }
5889             if (possibleChild.parents) {
5890                 if (this.inInheritanceChain(possibleParent, possibleChild.parents[0])) {
5891                     return true;
5892                 }
5893                 if (this.inInheritanceChain(possibleParent, possibleChild.parents[1])) {
5894                     return true;
5895                 }
5896             }
5897             return false;
5898         },
5899         visitRule: function (ruleNode, visitArgs) {
5900             visitArgs.visitDeeper = false;
5901         },
5902         visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
5903             visitArgs.visitDeeper = false;
5904         },
5905         visitSelector: function (selectorNode, visitArgs) {
5906             visitArgs.visitDeeper = false;
5907         },
5908         visitRuleset: function (rulesetNode, visitArgs) {
5909             if (rulesetNode.root) {
5910                 return;
5911             }
5912             var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = [], extendVisitor = this, selectorPath;
5913
5914             // look at each selector path in the ruleset, find any extend matches and then copy, find and replace
5915
5916             for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
5917                 for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
5918
5919                     selectorPath = rulesetNode.paths[pathIndex];
5920
5921                     // extending extends happens initially, before the main pass
5922                     if (rulesetNode.extendOnEveryPath || selectorPath[selectorPath.length-1].extendList.length) { continue; }
5923
5924                     matches = this.findMatch(allExtends[extendIndex], selectorPath);
5925
5926                     if (matches.length) {
5927
5928                         allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) {
5929                             selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector));
5930                         });
5931                     }
5932                 }
5933             }
5934             rulesetNode.paths = rulesetNode.paths.concat(selectorsToAdd);
5935         },
5936         findMatch: function (extend, haystackSelectorPath) {
5937             //
5938             // look through the haystack selector path to try and find the needle - extend.selector
5939             // returns an array of selector matches that can then be replaced
5940             //
5941             var haystackSelectorIndex, hackstackSelector, hackstackElementIndex, haystackElement,
5942                 targetCombinator, i,
5943                 extendVisitor = this,
5944                 needleElements = extend.selector.elements,
5945                 potentialMatches = [], potentialMatch, matches = [];
5946
5947             // loop through the haystack elements
5948             for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
5949                 hackstackSelector = haystackSelectorPath[haystackSelectorIndex];
5950
5951                 for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
5952
5953                     haystackElement = hackstackSelector.elements[hackstackElementIndex];
5954
5955                     // if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
5956                     if (extend.allowBefore || (haystackSelectorIndex === 0 && hackstackElementIndex === 0)) {
5957                         potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator});
5958                     }
5959
5960                     for(i = 0; i < potentialMatches.length; i++) {
5961                         potentialMatch = potentialMatches[i];
5962
5963                         // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
5964                         // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
5965                         // what the resulting combinator will be
5966                         targetCombinator = haystackElement.combinator.value;
5967                         if (targetCombinator === '' && hackstackElementIndex === 0) {
5968                             targetCombinator = ' ';
5969                         }
5970
5971                         // if we don't match, null our match to indicate failure
5972                         if (!extendVisitor.isElementValuesEqual(needleElements[potentialMatch.matched].value, haystackElement.value) ||
5973                             (potentialMatch.matched > 0 && needleElements[potentialMatch.matched].combinator.value !== targetCombinator)) {
5974                             potentialMatch = null;
5975                         } else {
5976                             potentialMatch.matched++;
5977                         }
5978
5979                         // if we are still valid and have finished, test whether we have elements after and whether these are allowed
5980                         if (potentialMatch) {
5981                             potentialMatch.finished = potentialMatch.matched === needleElements.length;
5982                             if (potentialMatch.finished &&
5983                                 (!extend.allowAfter && (hackstackElementIndex+1 < hackstackSelector.elements.length || haystackSelectorIndex+1 < haystackSelectorPath.length))) {
5984                                 potentialMatch = null;
5985                             }
5986                         }
5987                         // if null we remove, if not, we are still valid, so either push as a valid match or continue
5988                         if (potentialMatch) {
5989                             if (potentialMatch.finished) {
5990                                 potentialMatch.length = needleElements.length;
5991                                 potentialMatch.endPathIndex = haystackSelectorIndex;
5992                                 potentialMatch.endPathElementIndex = hackstackElementIndex + 1; // index after end of match
5993                                 potentialMatches.length = 0; // we don't allow matches to overlap, so start matching again
5994                                 matches.push(potentialMatch);
5995                             }
5996                         } else {
5997                             potentialMatches.splice(i, 1);
5998                             i--;
5999                         }
6000                     }
6001                 }
6002             }
6003             return matches;
6004         },
6005         isElementValuesEqual: function(elementValue1, elementValue2) {
6006             if (typeof elementValue1 === "string" || typeof elementValue2 === "string") {
6007                 return elementValue1 === elementValue2;
6008             }
6009             if (elementValue1 instanceof tree.Attribute) {
6010                 if (elementValue1.op !== elementValue2.op || elementValue1.key !== elementValue2.key) {
6011                     return false;
6012                 }
6013                 if (!elementValue1.value || !elementValue2.value) {
6014                     if (elementValue1.value || elementValue2.value) {
6015                         return false;
6016                     }
6017                     return true;
6018                 }
6019                 elementValue1 = elementValue1.value.value || elementValue1.value;
6020                 elementValue2 = elementValue2.value.value || elementValue2.value;
6021                 return elementValue1 === elementValue2;
6022             }
6023             elementValue1 = elementValue1.value;
6024             elementValue2 = elementValue2.value;
6025             if (elementValue1 instanceof tree.Selector) {
6026                 if (!(elementValue2 instanceof tree.Selector) || elementValue1.elements.length !== elementValue2.elements.length) {
6027                     return false;
6028                 }
6029                 for(var i = 0; i <elementValue1.elements.length; i++) {
6030                     if (elementValue1.elements[i].combinator.value !== elementValue2.elements[i].combinator.value) {
6031                         if (i !== 0 || (elementValue1.elements[i].combinator.value || ' ') !== (elementValue2.elements[i].combinator.value || ' ')) {
6032                             return false;
6033                         }
6034                     }
6035                     if (!this.isElementValuesEqual(elementValue1.elements[i].value, elementValue2.elements[i].value)) {
6036                         return false;
6037                     }
6038                 }
6039                 return true;
6040             }
6041             return false;
6042         },
6043         extendSelector:function (matches, selectorPath, replacementSelector) {
6044
6045             //for a set of matches, replace each match with the replacement selector
6046
6047             var currentSelectorPathIndex = 0,
6048                 currentSelectorPathElementIndex = 0,
6049                 path = [],
6050                 matchIndex,
6051                 selector,
6052                 firstElement,
6053                 match,
6054                 newElements;
6055
6056             for (matchIndex = 0; matchIndex < matches.length; matchIndex++) {
6057                 match = matches[matchIndex];
6058                 selector = selectorPath[match.pathIndex];
6059                 firstElement = new tree.Element(
6060                     match.initialCombinator,
6061                     replacementSelector.elements[0].value,
6062                     replacementSelector.elements[0].index,
6063                     replacementSelector.elements[0].currentFileInfo
6064                 );
6065
6066                 if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) {
6067                     path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
6068                     currentSelectorPathElementIndex = 0;
6069                     currentSelectorPathIndex++;
6070                 }
6071
6072                 newElements = selector.elements
6073                     .slice(currentSelectorPathElementIndex, match.index)
6074                     .concat([firstElement])
6075                     .concat(replacementSelector.elements.slice(1));
6076
6077                 if (currentSelectorPathIndex === match.pathIndex && matchIndex > 0) {
6078                     path[path.length - 1].elements =
6079                         path[path.length - 1].elements.concat(newElements);
6080                 } else {
6081                     path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex));
6082
6083                     path.push(new tree.Selector(
6084                         newElements
6085                     ));
6086                 }
6087                 currentSelectorPathIndex = match.endPathIndex;
6088                 currentSelectorPathElementIndex = match.endPathElementIndex;
6089                 if (currentSelectorPathElementIndex >= selectorPath[currentSelectorPathIndex].elements.length) {
6090                     currentSelectorPathElementIndex = 0;
6091                     currentSelectorPathIndex++;
6092                 }
6093             }
6094
6095             if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
6096                 path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
6097                 currentSelectorPathIndex++;
6098             }
6099
6100             path = path.concat(selectorPath.slice(currentSelectorPathIndex, selectorPath.length));
6101
6102             return path;
6103         },
6104         visitRulesetOut: function (rulesetNode) {
6105         },
6106         visitMedia: function (mediaNode, visitArgs) {
6107             var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
6108             newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, mediaNode.allExtends));
6109             this.allExtendsStack.push(newAllExtends);
6110         },
6111         visitMediaOut: function (mediaNode) {
6112             this.allExtendsStack.length = this.allExtendsStack.length - 1;
6113         },
6114         visitDirective: function (directiveNode, visitArgs) {
6115             var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
6116             newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, directiveNode.allExtends));
6117             this.allExtendsStack.push(newAllExtends);
6118         },
6119         visitDirectiveOut: function (directiveNode) {
6120             this.allExtendsStack.length = this.allExtendsStack.length - 1;
6121         }
6122     };
6123
6124 })(require('./tree'));
6125
6126 (function (tree) {
6127
6128     tree.sourceMapOutput = function (options) {
6129         this._css = [];
6130         this._rootNode = options.rootNode;
6131         this._writeSourceMap = options.writeSourceMap;
6132         this._contentsMap = options.contentsMap;
6133         this._sourceMapFilename = options.sourceMapFilename;
6134         this._outputFilename = options.outputFilename;
6135         this._sourceMapURL = options.sourceMapURL;
6136         this._sourceMapBasepath = options.sourceMapBasepath;
6137         this._sourceMapRootpath = options.sourceMapRootpath;
6138         this._outputSourceFiles = options.outputSourceFiles;
6139         this._sourceMapGeneratorConstructor = options.sourceMapGenerator || require("source-map").SourceMapGenerator;
6140
6141         if (this._sourceMapRootpath && this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1) !== '/') {
6142             this._sourceMapRootpath += '/';
6143         }
6144
6145         this._lineNumber = 0;
6146         this._column = 0;
6147     };
6148
6149     tree.sourceMapOutput.prototype.normalizeFilename = function(filename) {
6150         if (this._sourceMapBasepath && filename.indexOf(this._sourceMapBasepath) === 0) {
6151              filename = filename.substring(this._sourceMapBasepath.length);
6152              if (filename.charAt(0) === '\\' || filename.charAt(0) === '/') {
6153                 filename = filename.substring(1);
6154              }
6155         }
6156         return (this._sourceMapRootpath || "") + filename.replace(/\\/g, '/');
6157     };
6158
6159     tree.sourceMapOutput.prototype.add = function(chunk, fileInfo, index, mapLines) {
6160
6161         //ignore adding empty strings
6162         if (!chunk) {
6163             return;
6164         }
6165
6166         var lines,
6167             sourceLines,
6168             columns,
6169             sourceColumns,
6170             i;
6171
6172         if (fileInfo) {
6173             var inputSource = this._contentsMap[fileInfo.filename].substring(0, index);
6174             sourceLines = inputSource.split("\n");
6175             sourceColumns = sourceLines[sourceLines.length-1];
6176         }
6177
6178         lines = chunk.split("\n");
6179         columns = lines[lines.length-1];
6180
6181         if (fileInfo) {
6182             if (!mapLines) {
6183                 this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + 1, column: this._column},
6184                     original: { line: sourceLines.length, column: sourceColumns.length},
6185                     source: this.normalizeFilename(fileInfo.filename)});
6186             } else {
6187                 for(i = 0; i < lines.length; i++) {
6188                     this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + i + 1, column: i === 0 ? this._column : 0},
6189                         original: { line: sourceLines.length + i, column: i === 0 ? sourceColumns.length : 0},
6190                         source: this.normalizeFilename(fileInfo.filename)});
6191                 }
6192             }
6193         }
6194
6195         if (lines.length === 1) {
6196             this._column += columns.length;
6197         } else {
6198             this._lineNumber += lines.length - 1;
6199             this._column = columns.length;
6200         }
6201
6202         this._css.push(chunk);
6203     };
6204
6205     tree.sourceMapOutput.prototype.isEmpty = function() {
6206         return this._css.length === 0;
6207     };
6208
6209     tree.sourceMapOutput.prototype.toCSS = function(env) {
6210         this._sourceMapGenerator = new this._sourceMapGeneratorConstructor({ file: this._outputFilename, sourceRoot: null });
6211
6212         if (this._outputSourceFiles) {
6213             for(var filename in this._contentsMap) {
6214                 this._sourceMapGenerator.setSourceContent(this.normalizeFilename(filename), this._contentsMap[filename]);
6215             }
6216         }
6217
6218         this._rootNode.genCSS(env, this);
6219
6220         if (this._css.length > 0) {
6221             var sourceMapURL,
6222                 sourceMapContent = JSON.stringify(this._sourceMapGenerator.toJSON());
6223
6224             if (this._sourceMapURL) {
6225                 sourceMapURL = this._sourceMapURL;
6226             } else if (this._sourceMapFilename) {
6227                 sourceMapURL = this.normalizeFilename(this._sourceMapFilename);
6228             }
6229
6230             if (this._writeSourceMap) {
6231                 this._writeSourceMap(sourceMapContent);
6232             } else {
6233                 sourceMapURL = "data:application/json," + encodeURIComponent(sourceMapContent);
6234             }
6235
6236             if (sourceMapURL) {
6237                 this._css.push("/*# sourceMappingURL=" + sourceMapURL + " */");
6238             }
6239         }
6240
6241         return this._css.join('');
6242     };
6243
6244 })(require('./tree'));
6245
6246 //
6247 // browser.js - client-side engine
6248 //
6249 /*global less, window, document, XMLHttpRequest, location */
6250
6251 var isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);
6252
6253 less.env = less.env || (location.hostname == '127.0.0.1' ||
6254                         location.hostname == '0.0.0.0'   ||
6255                         location.hostname == 'localhost' ||
6256                         (location.port &&
6257                           location.port.length > 0)      ||
6258                         isFileProtocol                   ? 'development'
6259                                                          : 'production');
6260
6261 var logLevel = {
6262     info: 2,
6263     errors: 1,
6264     none: 0
6265 };
6266
6267 // The amount of logging in the javascript console.
6268 // 2 - Information and errors
6269 // 1 - Errors
6270 // 0 - None
6271 // Defaults to 2
6272 less.logLevel = typeof(less.logLevel) != 'undefined' ? less.logLevel : logLevel.info;
6273
6274 // Load styles asynchronously (default: false)
6275 //
6276 // This is set to `false` by default, so that the body
6277 // doesn't start loading before the stylesheets are parsed.
6278 // Setting this to `true` can result in flickering.
6279 //
6280 less.async = less.async || false;
6281 less.fileAsync = less.fileAsync || false;
6282
6283 // Interval between watch polls
6284 less.poll = less.poll || (isFileProtocol ? 1000 : 1500);
6285
6286 //Setup user functions
6287 if (less.functions) {
6288     for(var func in less.functions) {
6289         less.tree.functions[func] = less.functions[func];
6290    }
6291 }
6292
6293 var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);
6294 if (dumpLineNumbers) {
6295     less.dumpLineNumbers = dumpLineNumbers[1];
6296 }
6297
6298 var typePattern = /^text\/(x-)?less$/;
6299 var cache = null;
6300 var fileCache = {};
6301 var varsPre = "";
6302
6303 function log(str, level) {
6304     if (less.env == 'development' && typeof(console) !== 'undefined' && less.logLevel >= level) {
6305         console.log('less: ' + str);
6306     }
6307 }
6308
6309 function extractId(href) {
6310     return href.replace(/^[a-z-]+:\/+?[^\/]+/, '' )  // Remove protocol & domain
6311         .replace(/^\//,                 '' )  // Remove root /
6312         .replace(/\.[a-zA-Z]+$/,        '' )  // Remove simple extension
6313         .replace(/[^\.\w-]+/g,          '-')  // Replace illegal characters
6314         .replace(/\./g,                 ':'); // Replace dots with colons(for valid id)
6315 }
6316
6317 function errorConsole(e, rootHref) {
6318     var template = '{line} {content}';
6319     var filename = e.filename || rootHref;
6320     var errors = [];
6321     var content = (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') +
6322         " in " + filename + " ";
6323
6324     var errorline = function (e, i, classname) {
6325         if (e.extract[i] !== undefined) {
6326             errors.push(template.replace(/\{line\}/, (parseInt(e.line, 10) || 0) + (i - 1))
6327                 .replace(/\{class\}/, classname)
6328                 .replace(/\{content\}/, e.extract[i]));
6329         }
6330     };
6331
6332     if (e.extract) {
6333         errorline(e, 0, '');
6334         errorline(e, 1, 'line');
6335         errorline(e, 2, '');
6336         content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n' +
6337             errors.join('\n');
6338     } else if (e.stack) {
6339         content += e.stack;
6340     }
6341     log(content, logLevel.errors);
6342 }
6343
6344 function createCSS(styles, sheet, lastModified) {
6345     // Strip the query-string
6346     var href = sheet.href || '';
6347
6348     // If there is no title set, use the filename, minus the extension
6349     var id = 'less:' + (sheet.title || extractId(href));
6350
6351     // If this has already been inserted into the DOM, we may need to replace it
6352     var oldCss = document.getElementById(id);
6353     var keepOldCss = false;
6354
6355     // Create a new stylesheet node for insertion or (if necessary) replacement
6356     var css = document.createElement('style');
6357     css.setAttribute('type', 'text/css');
6358     if (sheet.media) {
6359         css.setAttribute('media', sheet.media);
6360     }
6361     css.id = id;
6362
6363     if (css.styleSheet) { // IE
6364         try {
6365             css.styleSheet.cssText = styles;
6366         } catch (e) {
6367             throw new(Error)("Couldn't reassign styleSheet.cssText.");
6368         }
6369     } else {
6370         css.appendChild(document.createTextNode(styles));
6371
6372         // If new contents match contents of oldCss, don't replace oldCss
6373         keepOldCss = (oldCss !== null && oldCss.childNodes.length > 0 && css.childNodes.length > 0 &&
6374             oldCss.firstChild.nodeValue === css.firstChild.nodeValue);
6375     }
6376
6377     var head = document.getElementsByTagName('head')[0];
6378
6379     // If there is no oldCss, just append; otherwise, only append if we need
6380     // to replace oldCss with an updated stylesheet
6381     if (oldCss === null || keepOldCss === false) {
6382         var nextEl = sheet && sheet.nextSibling || null;
6383         if (nextEl) {
6384             nextEl.parentNode.insertBefore(css, nextEl);
6385         } else {
6386             head.appendChild(css);
6387         }
6388     }
6389     if (oldCss && keepOldCss === false) {
6390         oldCss.parentNode.removeChild(oldCss);
6391     }
6392
6393     // Don't update the local store if the file wasn't modified
6394     if (lastModified && cache) {
6395         log('saving ' + href + ' to cache.', logLevel.info);
6396         try {
6397             cache.setItem(href, styles);
6398             cache.setItem(href + ':timestamp', lastModified);
6399         } catch(e) {
6400             //TODO - could do with adding more robust error handling
6401             log('failed to save', logLevel.errors);
6402         }
6403     }
6404 }
6405
6406 function errorHTML(e, rootHref) {
6407     var id = 'less-error-message:' + extractId(rootHref || "");
6408     var template = '<li><label>{line}</label><pre class="{class}">{content}</pre></li>';
6409     var elem = document.createElement('div'), timer, content, errors = [];
6410     var filename = e.filename || rootHref;
6411     var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1];
6412
6413     elem.id        = id;
6414     elem.className = "less-error-message";
6415
6416     content = '<h3>'  + (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') +
6417         '</h3>' + '<p>in <a href="' + filename   + '">' + filenameNoPath + "</a> ";
6418
6419     var errorline = function (e, i, classname) {
6420         if (e.extract[i] !== undefined) {
6421             errors.push(template.replace(/\{line\}/, (parseInt(e.line, 10) || 0) + (i - 1))
6422                 .replace(/\{class\}/, classname)
6423                 .replace(/\{content\}/, e.extract[i]));
6424         }
6425     };
6426
6427     if (e.extract) {
6428         errorline(e, 0, '');
6429         errorline(e, 1, 'line');
6430         errorline(e, 2, '');
6431         content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':</p>' +
6432             '<ul>' + errors.join('') + '</ul>';
6433     } else if (e.stack) {
6434         content += '<br/>' + e.stack.split('\n').slice(1).join('<br/>');
6435     }
6436     elem.innerHTML = content;
6437
6438     // CSS for error messages
6439     createCSS([
6440         '.less-error-message ul, .less-error-message li {',
6441         'list-style-type: none;',
6442         'margin-right: 15px;',
6443         'padding: 4px 0;',
6444         'margin: 0;',
6445         '}',
6446         '.less-error-message label {',
6447         'font-size: 12px;',
6448         'margin-right: 15px;',
6449         'padding: 4px 0;',
6450         'color: #cc7777;',
6451         '}',
6452         '.less-error-message pre {',
6453         'color: #dd6666;',
6454         'padding: 4px 0;',
6455         'margin: 0;',
6456         'display: inline-block;',
6457         '}',
6458         '.less-error-message pre.line {',
6459         'color: #ff0000;',
6460         '}',
6461         '.less-error-message h3 {',
6462         'font-size: 20px;',
6463         'font-weight: bold;',
6464         'padding: 15px 0 5px 0;',
6465         'margin: 0;',
6466         '}',
6467         '.less-error-message a {',
6468         'color: #10a',
6469         '}',
6470         '.less-error-message .error {',
6471         'color: red;',
6472         'font-weight: bold;',
6473         'padding-bottom: 2px;',
6474         'border-bottom: 1px dashed red;',
6475         '}'
6476     ].join('\n'), { title: 'error-message' });
6477
6478     elem.style.cssText = [
6479         "font-family: Arial, sans-serif",
6480         "border: 1px solid #e00",
6481         "background-color: #eee",
6482         "border-radius: 5px",
6483         "-webkit-border-radius: 5px",
6484         "-moz-border-radius: 5px",
6485         "color: #e00",
6486         "padding: 15px",
6487         "margin-bottom: 15px"
6488     ].join(';');
6489
6490     if (less.env == 'development') {
6491         timer = setInterval(function () {
6492             if (document.body) {
6493                 if (document.getElementById(id)) {
6494                     document.body.replaceChild(elem, document.getElementById(id));
6495                 } else {
6496                     document.body.insertBefore(elem, document.body.firstChild);
6497                 }
6498                 clearInterval(timer);
6499             }
6500         }, 10);
6501     }
6502 }
6503
6504 function error(e, rootHref) {
6505     if (!less.errorReporting || less.errorReporting === "html") {
6506         errorHTML(e, rootHref);
6507     } else if (less.errorReporting === "console") {
6508         errorConsole(e, rootHref);
6509     } else if (typeof less.errorReporting === 'function') {
6510         less.errorReporting("add", e, rootHref);
6511     }
6512 }
6513
6514 function removeErrorHTML(path) {
6515     var node = document.getElementById('less-error-message:' + extractId(path));
6516     if (node) {
6517         node.parentNode.removeChild(node);
6518     }
6519 }
6520
6521 function removeErrorConsole(path) {
6522     //no action
6523 }
6524
6525 function removeError(path) {
6526     if (!less.errorReporting || less.errorReporting === "html") {
6527         removeErrorHTML(path);
6528     } else if (less.errorReporting === "console") {
6529         removeErrorConsole(path);
6530     } else if (typeof less.errorReporting === 'function') {
6531         less.errorReporting("remove", path);
6532     }
6533 }
6534
6535 function loadStyles(newVars) {
6536     var styles = document.getElementsByTagName('style'),
6537         style;
6538     for (var i = 0; i < styles.length; i++) {
6539         style = styles[i];
6540         if (style.type.match(typePattern)) {
6541             var env = new less.tree.parseEnv(less),
6542                 lessText = style.innerHTML || '';
6543             env.filename = document.location.href.replace(/#.*$/, '');
6544
6545             if (newVars || varsPre) {
6546                 env.useFileCache = true;
6547
6548                 lessText = varsPre + lessText;
6549
6550                 if (newVars) {
6551                     lessText += "\n" + newVars;
6552                 }
6553             }
6554
6555             /*jshint loopfunc:true */
6556             // use closure to store current value of i
6557             var callback = (function(style) {
6558                 return function (e, cssAST) {
6559                     if (e) {
6560                         return error(e, "inline");
6561                     }
6562                     var css = cssAST.toCSS(less);
6563                     style.type = 'text/css';
6564                     if (style.styleSheet) {
6565                         style.styleSheet.cssText = css;
6566                     } else {
6567                         style.innerHTML = css;
6568                     }
6569                 };
6570             })(style);
6571             new(less.Parser)(env).parse(lessText, callback);
6572         }
6573     }
6574 }
6575
6576 function extractUrlParts(url, baseUrl) {
6577     // urlParts[1] = protocol&hostname || /
6578     // urlParts[2] = / if path relative to host base
6579     // urlParts[3] = directories
6580     // urlParts[4] = filename
6581     // urlParts[5] = parameters
6582
6583     var urlPartsRegex = /^((?:[a-z-]+:)?\/+?(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/i,
6584         urlParts = url.match(urlPartsRegex),
6585         returner = {}, directories = [], i, baseUrlParts;
6586
6587     if (!urlParts) {
6588         throw new Error("Could not parse sheet href - '"+url+"'");
6589     }
6590
6591     // Stylesheets in IE don't always return the full path
6592     if (!urlParts[1] || urlParts[2]) {
6593         baseUrlParts = baseUrl.match(urlPartsRegex);
6594         if (!baseUrlParts) {
6595             throw new Error("Could not parse page url - '"+baseUrl+"'");
6596         }
6597         urlParts[1] = urlParts[1] || baseUrlParts[1] || "";
6598         if (!urlParts[2]) {
6599             urlParts[3] = baseUrlParts[3] + urlParts[3];
6600         }
6601     }
6602
6603     if (urlParts[3]) {
6604         directories = urlParts[3].replace(/\\/g, "/").split("/");
6605
6606         // extract out . before .. so .. doesn't absorb a non-directory
6607         for(i = 0; i < directories.length; i++) {
6608             if (directories[i] === ".") {
6609                 directories.splice(i, 1);
6610                 i -= 1;
6611             }
6612         }
6613
6614         for(i = 0; i < directories.length; i++) {
6615             if (directories[i] === ".." && i > 0) {
6616                 directories.splice(i-1, 2);
6617                 i -= 2;
6618             }
6619         }
6620     }
6621
6622     returner.hostPart = urlParts[1];
6623     returner.directories = directories;
6624     returner.path = urlParts[1] + directories.join("/");
6625     returner.fileUrl = returner.path + (urlParts[4] || "");
6626     returner.url = returner.fileUrl + (urlParts[5] || "");
6627     return returner;
6628 }
6629
6630 function pathDiff(url, baseUrl) {
6631     // diff between two paths to create a relative path
6632
6633     var urlParts = extractUrlParts(url),
6634         baseUrlParts = extractUrlParts(baseUrl),
6635         i, max, urlDirectories, baseUrlDirectories, diff = "";
6636     if (urlParts.hostPart !== baseUrlParts.hostPart) {
6637         return "";
6638     }
6639     max = Math.max(baseUrlParts.directories.length, urlParts.directories.length);
6640     for(i = 0; i < max; i++) {
6641         if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; }
6642     }
6643     baseUrlDirectories = baseUrlParts.directories.slice(i);
6644     urlDirectories = urlParts.directories.slice(i);
6645     for(i = 0; i < baseUrlDirectories.length-1; i++) {
6646         diff += "../";
6647     }
6648     for(i = 0; i < urlDirectories.length-1; i++) {
6649         diff += urlDirectories[i] + "/";
6650     }
6651     return diff;
6652 }
6653
6654 function getXMLHttpRequest() {
6655     if (window.XMLHttpRequest) {
6656         return new XMLHttpRequest();
6657     } else {
6658         try {
6659             /*global ActiveXObject */
6660             return new ActiveXObject("MSXML2.XMLHTTP.3.0");
6661         } catch (e) {
6662             log("browser doesn't support AJAX.", logLevel.errors);
6663             return null;
6664         }
6665     }
6666 }
6667
6668 function doXHR(url, type, callback, errback) {
6669     var xhr = getXMLHttpRequest();
6670     var async = isFileProtocol ? less.fileAsync : less.async;
6671
6672     if (typeof(xhr.overrideMimeType) === 'function') {
6673         xhr.overrideMimeType('text/css');
6674     }
6675     log("XHR: Getting '" + url + "'", logLevel.info);
6676     xhr.open('GET', url, async);
6677     xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
6678     xhr.send(null);
6679
6680     function handleResponse(xhr, callback, errback) {
6681         if (xhr.status >= 200 && xhr.status < 300) {
6682             callback(xhr.responseText,
6683                 xhr.getResponseHeader("Last-Modified"));
6684         } else if (typeof(errback) === 'function') {
6685             errback(xhr.status, url);
6686         }
6687     }
6688
6689     if (isFileProtocol && !less.fileAsync) {
6690         if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) {
6691             callback(xhr.responseText);
6692         } else {
6693             errback(xhr.status, url);
6694         }
6695     } else if (async) {
6696         xhr.onreadystatechange = function () {
6697             if (xhr.readyState == 4) {
6698                 handleResponse(xhr, callback, errback);
6699             }
6700         };
6701     } else {
6702         handleResponse(xhr, callback, errback);
6703     }
6704 }
6705
6706 function loadFile(originalHref, currentFileInfo, callback, env, newVars) {
6707
6708     if (currentFileInfo && currentFileInfo.currentDirectory && !/^([a-z-]+:)?\//.test(originalHref)) {
6709         originalHref = currentFileInfo.currentDirectory + originalHref;
6710     }
6711
6712     // sheet may be set to the stylesheet for the initial load or a collection of properties including
6713     // some env variables for imports
6714     var hrefParts = extractUrlParts(originalHref, window.location.href);
6715     var href      = hrefParts.url;
6716     var newFileInfo = {
6717         currentDirectory: hrefParts.path,
6718         filename: href
6719     };
6720
6721     if (currentFileInfo) {
6722         newFileInfo.entryPath = currentFileInfo.entryPath;
6723         newFileInfo.rootpath = currentFileInfo.rootpath;
6724         newFileInfo.rootFilename = currentFileInfo.rootFilename;
6725         newFileInfo.relativeUrls = currentFileInfo.relativeUrls;
6726     } else {
6727         newFileInfo.entryPath = hrefParts.path;
6728         newFileInfo.rootpath = less.rootpath || hrefParts.path;
6729         newFileInfo.rootFilename = href;
6730         newFileInfo.relativeUrls = env.relativeUrls;
6731     }
6732
6733     if (newFileInfo.relativeUrls) {
6734         if (env.rootpath) {
6735             newFileInfo.rootpath = extractUrlParts(env.rootpath + pathDiff(hrefParts.path, newFileInfo.entryPath)).path;
6736         } else {
6737             newFileInfo.rootpath = hrefParts.path;
6738         }
6739     }
6740
6741     if (env.useFileCache && fileCache[href]) {
6742         try {
6743             var lessText = fileCache[href];
6744             if (newVars) {
6745                 lessText += "\n" + newVars;
6746             }
6747             callback(null, lessText, href, newFileInfo, { lastModified: new Date() });
6748         } catch (e) {
6749             callback(e, null, href);
6750         }
6751         return;
6752     }
6753
6754     doXHR(href, env.mime, function (data, lastModified) {
6755         data = varsPre + data;
6756
6757         // per file cache
6758         fileCache[href] = data;
6759
6760         // Use remote copy (re-parse)
6761         try {
6762             callback(null, data, href, newFileInfo, { lastModified: lastModified });
6763         } catch (e) {
6764             callback(e, null, href);
6765         }
6766     }, function (status, url) {
6767         callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, href);
6768     });
6769 }
6770
6771 function loadStyleSheet(sheet, callback, reload, remaining, newVars) {
6772
6773     var env = new less.tree.parseEnv(less);
6774     env.mime = sheet.type;
6775
6776     if (newVars || varsPre) {
6777         env.useFileCache = true;
6778     }
6779
6780     loadFile(sheet.href, null, function(e, data, path, newFileInfo, webInfo) {
6781
6782         if (webInfo) {
6783             webInfo.remaining = remaining;
6784
6785             var css       = cache && cache.getItem(path),
6786                 timestamp = cache && cache.getItem(path + ':timestamp');
6787
6788             if (!reload && timestamp && webInfo.lastModified &&
6789                 (new(Date)(webInfo.lastModified).valueOf() ===
6790                     new(Date)(timestamp).valueOf())) {
6791                 // Use local copy
6792                 createCSS(css, sheet);
6793                 webInfo.local = true;
6794                 callback(null, null, data, sheet, webInfo, path);
6795                 return;
6796             }
6797         }
6798
6799         //TODO add tests around how this behaves when reloading
6800         removeError(path);
6801
6802         if (data) {
6803             env.currentFileInfo = newFileInfo;
6804             new(less.Parser)(env).parse(data, function (e, root) {
6805                 if (e) { return callback(e, null, null, sheet); }
6806                 try {
6807                     callback(e, root, data, sheet, webInfo, path);
6808                 } catch (e) {
6809                     callback(e, null, null, sheet);
6810                 }
6811             });
6812         } else {
6813             callback(e, null, null, sheet, webInfo, path);
6814         }
6815     }, env, newVars);
6816 }
6817
6818 function loadStyleSheets(callback, reload, newVars) {
6819     for (var i = 0; i < less.sheets.length; i++) {
6820         loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), newVars);
6821     }
6822 }
6823
6824 function initRunningMode(){
6825     if (less.env === 'development') {
6826         less.optimization = 0;
6827         less.watchTimer = setInterval(function () {
6828             if (less.watchMode) {
6829                 loadStyleSheets(function (e, root, _, sheet, env) {
6830                     if (e) {
6831                         error(e, sheet.href);
6832                     } else if (root) {
6833                         createCSS(root.toCSS(less), sheet, env.lastModified);
6834                     }
6835                 });
6836             }
6837         }, less.poll);
6838     } else {
6839         less.optimization = 3;
6840     }
6841 }
6842
6843 function serializeVars(vars) {
6844     var s = "";
6845
6846     for (var name in vars) {
6847         s += ((name.slice(0,1) === '@')? '' : '@') + name +': '+
6848                 ((vars[name].slice(-1) === ';')? vars[name] : vars[name] +';');
6849     }
6850
6851     return s;
6852 }
6853
6854
6855 //
6856 // Watch mode
6857 //
6858 less.watch   = function () {
6859     if (!less.watchMode ){
6860         less.env = 'development';
6861          initRunningMode();
6862     }
6863     return this.watchMode = true;
6864 };
6865
6866 less.unwatch = function () {clearInterval(less.watchTimer); return this.watchMode = false; };
6867
6868 if (/!watch/.test(location.hash)) {
6869     less.watch();
6870 }
6871
6872 if (less.env != 'development') {
6873     try {
6874         cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
6875     } catch (_) {}
6876 }
6877
6878 //
6879 // Get all <link> tags with the 'rel' attribute set to "stylesheet/less"
6880 //
6881 var links = document.getElementsByTagName('link');
6882
6883 less.sheets = [];
6884
6885 for (var i = 0; i < links.length; i++) {
6886     if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
6887        (links[i].type.match(typePattern)))) {
6888         less.sheets.push(links[i]);
6889     }
6890 }
6891
6892 //
6893 // With this function, it's possible to alter variables and re-render
6894 // CSS without reloading less-files
6895 //
6896 less.modifyVars = function(record) {
6897     less.refresh(false, serializeVars(record));
6898 };
6899
6900 less.refresh = function (reload, newVars) {
6901     var startTime, endTime;
6902     startTime = endTime = new Date();
6903
6904     loadStyleSheets(function (e, root, _, sheet, env) {
6905         if (e) {
6906             return error(e, sheet.href);
6907         }
6908         if (env.local) {
6909             log("loading " + sheet.href + " from cache.", logLevel.info);
6910         } else {
6911             log("parsed " + sheet.href + " successfully.", logLevel.info);
6912             createCSS(root.toCSS(less), sheet, env.lastModified);
6913         }
6914         log("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms', logLevel.info);
6915         if (env.remaining === 0) {
6916             log("css generated in " + (new Date() - startTime) + 'ms', logLevel.info);
6917         }
6918         endTime = new Date();
6919     }, reload, newVars);
6920
6921     loadStyles(newVars);
6922 };
6923
6924 if (less.globalVars) {
6925     varsPre = serializeVars(less.globalVars) + "\n";
6926 }
6927
6928 less.refreshStyles = loadStyles;
6929
6930 less.Parser.fileLoader = loadFile;
6931
6932 less.refresh(less.env === 'development');
6933
6934 // amd.js
6935 //
6936 // Define Less as an AMD module.
6937 if (typeof define === "function" && define.amd) {
6938     define(function () { return less; } );
6939 }
6940
6941 })(window);