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