Revert "Export"
[framework/web/web-ui-fw.git] / build-tools / lib / less / parser.js
1 var less, tree;
2
3 if (typeof environment === "object" && ({}).toString.call(environment) === "[object Environment]") {
4     // Rhino
5     // Details on how to detect Rhino: https://github.com/ringo/ringojs/issues/88
6     less = {};
7     tree = less.tree = {};
8     less.mode = 'rhino';
9 } else if (typeof(window) === 'undefined') {
10     // Node.js
11     less = exports,
12     tree = require('less/tree');
13     less.mode = 'rhino';
14 } else {
15     // Browser
16     if (typeof(window.less) === 'undefined') { window.less = {} }
17     less = window.less,
18     tree = window.less.tree = {};
19     less.mode = 'browser';
20 }
21 //
22 // less.js - parser
23 //
24 //    A relatively straight-forward predictive parser.
25 //    There is no tokenization/lexing stage, the input is parsed
26 //    in one sweep.
27 //
28 //    To make the parser fast enough to run in the browser, several
29 //    optimization had to be made:
30 //
31 //    - Matching and slicing on a huge input is often cause of slowdowns.
32 //      The solution is to chunkify the input into smaller strings.
33 //      The chunks are stored in the `chunks` var,
34 //      `j` holds the current chunk index, and `current` holds
35 //      the index of the current chunk in relation to `input`.
36 //      This gives us an almost 4x speed-up.
37 //
38 //    - In many cases, we don't need to match individual tokens;
39 //      for example, if a value doesn't hold any variables, operations
40 //      or dynamic references, the parser can effectively 'skip' it,
41 //      treating it as a literal.
42 //      An example would be '1px solid #000' - which evaluates to itself,
43 //      we don't need to know what the individual components are.
44 //      The drawback, of course is that you don't get the benefits of
45 //      syntax-checking on the CSS. This gives us a 50% speed-up in the parser,
46 //      and a smaller speed-up in the code-gen.
47 //
48 //
49 //    Token matching is done with the `$` function, which either takes
50 //    a terminal string or regexp, or a non-terminal function to call.
51 //    It also takes care of moving all the indices forwards.
52 //
53 //
54 less.Parser = function Parser(env) {
55     var input,       // LeSS input string
56         i,           // current index in `input`
57         j,           // current chunk
58         temp,        // temporarily holds a chunk's state, for backtracking
59         memo,        // temporarily holds `i`, when backtracking
60         furthest,    // furthest index the parser has gone to
61         chunks,      // chunkified input
62         current,     // index of current chunk, in `input`
63         parser;
64
65     var that = this;
66
67     // This function is called after all files
68     // have been imported through `@import`.
69     var finish = function () {};
70
71     var imports = this.imports = {
72         paths: env && env.paths || [],  // Search paths, when importing
73         queue: [],                      // Files which haven't been imported yet
74         files: {},                      // Holds the imported parse trees
75         mime:  env && env.mime,         // MIME type of .less files
76         push: function (path, callback) {
77             var that = this;
78             this.queue.push(path);
79
80             //
81             // Import a file asynchronously
82             //
83             less.Parser.importer(path, this.paths, function (root) {
84                 that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue
85                 that.files[path] = root;                        // Store the root
86
87                 callback(root);
88
89                 if (that.queue.length === 0) { finish() }       // Call `finish` if we're done importing
90             }, env);
91         }
92     };
93
94     function save()    { temp = chunks[j], memo = i, current = i }
95     function restore() { chunks[j] = temp, i = memo, current = i }
96
97     function sync() {
98         if (i > current) {
99             chunks[j] = chunks[j].slice(i - current);
100             current = i;
101         }
102     }
103     //
104     // Parse from a token, regexp or string, and move forward if match
105     //
106     function $(tok) {
107         var match, args, length, c, index, endIndex, k, mem;
108
109         //
110         // Non-terminal
111         //
112         if (tok instanceof Function) {
113             return tok.call(parser.parsers);
114         //
115         // Terminal
116         //
117         //     Either match a single character in the input,
118         //     or match a regexp in the current chunk (chunk[j]).
119         //
120         } else if (typeof(tok) === 'string') {
121             match = input.charAt(i) === tok ? tok : null;
122             length = 1;
123             sync ();
124         } else {
125             sync ();
126
127             if (match = tok.exec(chunks[j])) {
128                 length = match[0].length;
129             } else {
130                 return null;
131             }
132         }
133
134         // The match is confirmed, add the match length to `i`,
135         // and consume any extra white-space characters (' ' || '\n')
136         // which come after that. The reason for this is that LeSS's
137         // grammar is mostly white-space insensitive.
138         //
139         if (match) {
140             mem = i += length;
141             endIndex = i + chunks[j].length - length;
142
143             while (i < endIndex) {
144                 c = input.charCodeAt(i);
145                 if (! (c === 32 || c === 10 || c === 9)) { break }
146                 i++;
147             }
148             chunks[j] = chunks[j].slice(length + (i - mem));
149             current = i;
150
151             if (chunks[j].length === 0 && j < chunks.length - 1) { j++ }
152
153             if(typeof(match) === 'string') {
154                 return match;
155             } else {
156                 return match.length === 1 ? match[0] : match;
157             }
158         }
159     }
160
161     // Same as $(), but don't change the state of the parser,
162     // just return the match.
163     function peek(tok) {
164         if (typeof(tok) === 'string') {
165             return input.charAt(i) === tok;
166         } else {
167             if (tok.test(chunks[j])) {
168                 return true;
169             } else {
170                 return false;
171             }
172         }
173     }
174
175     this.env = env = env || {};
176
177     // The optimization level dictates the thoroughness of the parser,
178     // the lower the number, the less nodes it will create in the tree.
179     // This could matter for debugging, or if you want to access
180     // the individual nodes in the tree.
181     this.optimization = ('optimization' in this.env) ? this.env.optimization : 1;
182
183     this.env.filename = this.env.filename || null;
184
185     //
186     // The Parser
187     //
188     return parser = {
189
190         imports: imports,
191         //
192         // Parse an input string into an abstract syntax tree,
193         // call `callback` when done.
194         //
195         parse: function (str, callback) {
196             var root, start, end, zone, line, lines, buff = [], c, error = null;
197
198             i = j = current = furthest = 0;
199             chunks = [];
200             input = str.replace(/\r\n/g, '\n');
201
202             // Split the input into chunks.
203             chunks = (function (chunks) {
204                 var j = 0,
205                     skip = /[^"'`\{\}\/\(\)]+/g,
206                     comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
207                     level = 0,
208                     match,
209                     chunk = chunks[0],
210                     inParam,
211                     inString;
212
213                 for (var i = 0, c, cc; i < input.length; i++) {
214                     skip.lastIndex = i;
215                     if (match = skip.exec(input)) {
216                         if (match.index === i) {
217                             i += match[0].length;
218                             chunk.push(match[0]);
219                         }
220                     }
221                     c = input.charAt(i);
222                     comment.lastIndex = i;
223
224                     if (!inString && !inParam && c === '/') {
225                         cc = input.charAt(i + 1);
226                         if (cc === '/' || cc === '*') {
227                             if (match = comment.exec(input)) {
228                                 if (match.index === i) {
229                                     i += match[0].length;
230                                     chunk.push(match[0]);
231                                     c = input.charAt(i);
232                                 }
233                             }
234                         }
235                     }
236
237                     if        (c === '{' && !inString && !inParam) { level ++;
238                         chunk.push(c);
239                     } else if (c === '}' && !inString && !inParam) { level --;
240                         chunk.push(c);
241                         chunks[++j] = chunk = [];
242                     } else if (c === '(' && !inString && !inParam) {
243                         chunk.push(c);
244                         inParam = true;
245                     } else if (c === ')' && !inString && inParam) {
246                         chunk.push(c);
247                         inParam = false;
248                     } else {
249                         if (c === '"' || c === "'" || c === '`') {
250                             if (! inString) {
251                                 inString = c;
252                             } else {
253                                 inString = inString === c ? false : inString;
254                             }
255                         }
256                         chunk.push(c);
257                     }
258                 }
259                 if (level > 0) {
260                     throw {
261                         type: 'Syntax',
262                         message: "Missing closing `}`",
263                         filename: env.filename
264                     };
265                 }
266
267                 return chunks.map(function (c) { return c.join('') });;
268             })([[]]);
269
270             // Start with the primary rule.
271             // The whole syntax tree is held under a Ruleset node,
272             // with the `root` property set to true, so no `{}` are
273             // output. The callback is called when the input is parsed.
274             root = new(tree.Ruleset)([], $(this.parsers.primary));
275             root.root = true;
276
277             root.toCSS = (function (evaluate) {
278                 var line, lines, column;
279
280                 return function (options, variables) {
281                     var frames = [];
282
283                     options = options || {};
284                     //
285                     // Allows setting variables with a hash, so:
286                     //
287                     //   `{ color: new(tree.Color)('#f01') }` will become:
288                     //
289                     //   new(tree.Rule)('@color',
290                     //     new(tree.Value)([
291                     //       new(tree.Expression)([
292                     //         new(tree.Color)('#f01')
293                     //       ])
294                     //     ])
295                     //   )
296                     //
297                     if (typeof(variables) === 'object' && !Array.isArray(variables)) {
298                         variables = Object.keys(variables).map(function (k) {
299                             var value = variables[k];
300
301                             if (! (value instanceof tree.Value)) {
302                                 if (! (value instanceof tree.Expression)) {
303                                     value = new(tree.Expression)([value]);
304                                 }
305                                 value = new(tree.Value)([value]);
306                             }
307                             return new(tree.Rule)('@' + k, value, false, 0);
308                         });
309                         frames = [new(tree.Ruleset)(null, variables)];
310                     }
311
312                     try {
313                         var css = evaluate.call(this, { frames: frames })
314                                           .toCSS([], { compress: options.compress || false });
315                     } catch (e) {
316                         lines = input.split('\n');
317                         line = getLine(e.index);
318
319                         for (var n = e.index, column = -1;
320                                  n >= 0 && input.charAt(n) !== '\n';
321                                  n--) { column++ }
322
323                         throw {
324                             type: e.type,
325                             message: e.message,
326                             filename: env.filename,
327                             index: e.index,
328                             line: typeof(line) === 'number' ? line + 1 : null,
329                             callLine: e.call && (getLine(e.call) + 1),
330                             callExtract: lines[getLine(e.call)],
331                             stack: e.stack,
332                             column: column,
333                             extract: [
334                                 lines[line - 1],
335                                 lines[line],
336                                 lines[line + 1]
337                             ]
338                         };
339                     }
340                     if (options.compress) {
341                         return css.replace(/(\s)+/g, "$1");
342                     } else {
343                         return css;
344                     }
345
346                     function getLine(index) {
347                         return index ? (input.slice(0, index).match(/\n/g) || "").length : null;
348                     }
349                 };
350             })(root.eval);
351
352             // If `i` is smaller than the `input.length - 1`,
353             // it means the parser wasn't able to parse the whole
354             // string, so we've got a parsing error.
355             //
356             // We try to extract a \n delimited string,
357             // showing the line where the parse error occured.
358             // We split it up into two parts (the part which parsed,
359             // and the part which didn't), so we can color them differently.
360             if (i < input.length - 1) {
361                 i = furthest;
362                 lines = input.split('\n');
363                 line = (input.slice(0, i).match(/\n/g) || "").length + 1;
364
365                 for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ }
366
367                 error = {
368                     name: "ParseError",
369                     message: "Syntax Error on line " + line,
370                     index: i,
371                     filename: env.filename,
372                     line: line,
373                     column: column,
374                     extract: [
375                         lines[line - 2],
376                         lines[line - 1],
377                         lines[line]
378                     ]
379                 };
380             }
381
382             if (this.imports.queue.length > 0) {
383                 finish = function () { callback(error, root) };
384             } else {
385                 callback(error, root);
386             }
387         },
388
389         //
390         // Here in, the parsing rules/functions
391         //
392         // The basic structure of the syntax tree generated is as follows:
393         //
394         //   Ruleset ->  Rule -> Value -> Expression -> Entity
395         //
396         // Here's some LESS code:
397         //
398         //    .class {
399         //      color: #fff;
400         //      border: 1px solid #000;
401         //      width: @w + 4px;
402         //      > .child {...}
403         //    }
404         //
405         // And here's what the parse tree might look like:
406         //
407         //     Ruleset (Selector '.class', [
408         //         Rule ("color",  Value ([Expression [Color #fff]]))
409         //         Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
410         //         Rule ("width",  Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
411         //         Ruleset (Selector [Element '>', '.child'], [...])
412         //     ])
413         //
414         //  In general, most rules will try to parse a token with the `$()` function, and if the return
415         //  value is truly, will return a new node, of the relevant type. Sometimes, we need to check
416         //  first, before parsing, that's when we use `peek()`.
417         //
418         parsers: {
419             //
420             // The `primary` rule is the *entry* and *exit* point of the parser.
421             // The rules here can appear at any level of the parse tree.
422             //
423             // The recursive nature of the grammar is an interplay between the `block`
424             // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
425             // as represented by this simplified grammar:
426             //
427             //     primary  â†’  (ruleset | rule)+
428             //     ruleset  â†’  selector+ block
429             //     block    â†’  '{' primary '}'
430             //
431             // Only at one point is the primary rule not called from the
432             // block rule: at the root level.
433             //
434             primary: function () {
435                 var node, root = [];
436
437                 while ((node = $(this.mixin.definition) || $(this.rule)    ||  $(this.ruleset) ||
438                                $(this.mixin.call)       || $(this.comment) ||  $(this.directive))
439                                || $(/^[\s\n]+/)) {
440                     node && root.push(node);
441                 }
442                 return root;
443             },
444
445             // We create a Comment node for CSS comments `/* */`,
446             // but keep the LeSS comments `//` silent, by just skipping
447             // over them.
448             comment: function () {
449                 var comment;
450
451                 if (input.charAt(i) !== '/') return;
452
453                 if (input.charAt(i + 1) === '/') {
454                     return new(tree.Comment)($(/^\/\/.*/), true);
455                 } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) {
456                     return new(tree.Comment)(comment);
457                 }
458             },
459
460             //
461             // Entities are tokens which can be found inside an Expression
462             //
463             entities: {
464                 //
465                 // A string, which supports escaping " and '
466                 //
467                 //     "milky way" 'he\'s the one!'
468                 //
469                 quoted: function () {
470                     var str, j = i, e;
471
472                     if (input.charAt(j) === '~') { j++, e = true } // Escaped strings
473                     if (input.charAt(j) !== '"' && input.charAt(j) !== "'") return;
474
475                     e && $('~');
476
477                     if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) {
478                         return new(tree.Quoted)(str[0], str[1] || str[2], e);
479                     }
480                 },
481
482                 //
483                 // A catch-all word, such as:
484                 //
485                 //     black border-collapse
486                 //
487                 keyword: function () {
488                     var k;
489                     if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) { return new(tree.Keyword)(k) }
490                 },
491
492                 //
493                 // A function call
494                 //
495                 //     rgb(255, 0, 255)
496                 //
497                 // We also try to catch IE's `alpha()`, but let the `alpha` parser
498                 // deal with the details.
499                 //
500                 // The arguments are parsed with the `entities.arguments` parser.
501                 //
502                 call: function () {
503                     var name, args, index = i;
504
505                     if (! (name = /^([\w-]+|%)\(/.exec(chunks[j]))) return;
506
507                     name = name[1].toLowerCase();
508
509                     if (name === 'url') { return null }
510                     else                { i += name.length }
511
512                     if (name === 'alpha') { return $(this.alpha) }
513
514                     $('('); // Parse the '(' and consume whitespace.
515
516                     args = $(this.entities.arguments);
517
518                     if (! $(')')) return;
519
520                     if (name) { return new(tree.Call)(name, args, index) }
521                 },
522                 arguments: function () {
523                     var args = [], arg;
524
525                     while (arg = $(this.expression)) {
526                         args.push(arg);
527                         if (! $(',')) { break }
528                     }
529                     return args;
530                 },
531                 literal: function () {
532                     return $(this.entities.dimension) ||
533                            $(this.entities.color) ||
534                            $(this.entities.quoted);
535                 },
536
537                 //
538                 // Parse url() tokens
539                 //
540                 // We use a specific rule for urls, because they don't really behave like
541                 // standard function calls. The difference is that the argument doesn't have
542                 // to be enclosed within a string, so it can't be parsed as an Expression.
543                 //
544                 url: function () {
545                     var value;
546
547                     if (input.charAt(i) !== 'u' || !$(/^url\(/)) return;
548                     value = $(this.entities.quoted)  || $(this.entities.variable) ||
549                             $(this.entities.dataURI) || $(/^[-\w%@$\/.&=:;#+?~]+/) || "";
550                     if (! $(')')) throw new(Error)("missing closing ) for url()");
551
552                     return new(tree.URL)((value.value || value.data || value instanceof tree.Variable)
553                                         ? value : new(tree.Anonymous)(value), imports.paths);
554                 },
555
556                 dataURI: function () {
557                     var obj;
558
559                     if ($(/^data:/)) {
560                         obj         = {};
561                         obj.mime    = $(/^[^\/]+\/[^,;)]+/)     || '';
562                         obj.charset = $(/^;\s*charset=[^,;)]+/) || '';
563                         obj.base64  = $(/^;\s*base64/)          || '';
564                         obj.data    = $(/^,\s*[^)]+/);
565
566                         if (obj.data) { return obj }
567                     }
568                 },
569
570                 //
571                 // A Variable entity, such as `@fink`, in
572                 //
573                 //     width: @fink + 2px
574                 //
575                 // We use a different parser for variable definitions,
576                 // see `parsers.variable`.
577                 //
578                 variable: function () {
579                     var name, index = i;
580
581                     if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
582                         return new(tree.Variable)(name, index);
583                     }
584                 },
585
586                 //
587                 // A Hexadecimal color
588                 //
589                 //     #4F3C2F
590                 //
591                 // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
592                 //
593                 color: function () {
594                     var rgb;
595
596                     if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) {
597                         return new(tree.Color)(rgb[1]);
598                     }
599                 },
600
601                 //
602                 // A Dimension, that is, a number and a unit
603                 //
604                 //     0.5em 95%
605                 //
606                 dimension: function () {
607                     var value, c = input.charCodeAt(i);
608                     if ((c > 57 || c < 45) || c === 47) return;
609
610                     if (value = $(/^(-?\d*\.?\d+)(px|%|em|rem|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) {
611                         return new(tree.Dimension)(value[1], value[2]);
612                     }
613                 },
614
615                 //
616                 // JavaScript code to be evaluated
617                 //
618                 //     `window.location.href`
619                 //
620                 javascript: function () {
621                     var str, j = i, e;
622
623                     if (input.charAt(j) === '~') { j++, e = true } // Escaped strings
624                     if (input.charAt(j) !== '`') { return }
625
626                     e && $('~');
627
628                     if (str = $(/^`([^`]*)`/)) {
629                         return new(tree.JavaScript)(str[1], i, e);
630                     }
631                 }
632             },
633
634             //
635             // The variable part of a variable definition. Used in the `rule` parser
636             //
637             //     @fink:
638             //
639             variable: function () {
640                 var name;
641
642                 if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1] }
643             },
644
645             //
646             // A font size/line-height shorthand
647             //
648             //     small/12px
649             //
650             // We need to peek first, or we'll match on keywords and dimensions
651             //
652             shorthand: function () {
653                 var a, b;
654
655                 if (! peek(/^[@\w.%-]+\/[@\w.-]+/)) return;
656
657                 if ((a = $(this.entity)) && $('/') && (b = $(this.entity))) {
658                     return new(tree.Shorthand)(a, b);
659                 }
660             },
661
662             //
663             // Mixins
664             //
665             mixin: {
666                 //
667                 // A Mixin call, with an optional argument list
668                 //
669                 //     #mixins > .square(#fff);
670                 //     .rounded(4px, black);
671                 //     .button;
672                 //
673                 // The `while` loop is there because mixins can be
674                 // namespaced, but we only support the child and descendant
675                 // selector for now.
676                 //
677                 call: function () {
678                     var elements = [], e, c, args, index = i, s = input.charAt(i);
679
680                     if (s !== '.' && s !== '#') { return }
681
682                     while (e = $(/^[#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/)) {
683                         elements.push(new(tree.Element)(c, e));
684                         c = $('>');
685                     }
686                     $('(') && (args = $(this.entities.arguments)) && $(')');
687
688                     if (elements.length > 0 && ($(';') || peek('}'))) {
689                         return new(tree.mixin.Call)(elements, args, index);
690                     }
691                 },
692
693                 //
694                 // A Mixin definition, with a list of parameters
695                 //
696                 //     .rounded (@radius: 2px, @color) {
697                 //        ...
698                 //     }
699                 //
700                 // Until we have a finer grained state-machine, we have to
701                 // do a look-ahead, to make sure we don't have a mixin call.
702                 // See the `rule` function for more information.
703                 //
704                 // We start by matching `.rounded (`, and then proceed on to
705                 // the argument list, which has optional default values.
706                 // We store the parameters in `params`, with a `value` key,
707                 // if there is a value, such as in the case of `@radius`.
708                 //
709                 // Once we've got our params list, and a closing `)`, we parse
710                 // the `{...}` block.
711                 //
712                 definition: function () {
713                     var name, params = [], match, ruleset, param, value;
714
715                     if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') ||
716                         peek(/^[^{]*(;|})/)) return;
717
718                     if (match = $(/^([#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+)\s*\(/)) {
719                         name = match[1];
720
721                         while (param = $(this.entities.variable) || $(this.entities.literal)
722                                                                  || $(this.entities.keyword)) {
723                             // Variable
724                             if (param instanceof tree.Variable) {
725                                 if ($(':')) {
726                                     if (value = $(this.expression)) {
727                                         params.push({ name: param.name, value: value });
728                                     } else {
729                                         throw new(Error)("Expected value");
730                                     }
731                                 } else {
732                                     params.push({ name: param.name });
733                                 }
734                             } else {
735                                 params.push({ value: param });
736                             }
737                             if (! $(',')) { break }
738                         }
739                         if (! $(')')) throw new(Error)("Expected )");
740
741                         ruleset = $(this.block);
742
743                         if (ruleset) {
744                             return new(tree.mixin.Definition)(name, params, ruleset);
745                         }
746                     }
747                 }
748             },
749
750             //
751             // Entities are the smallest recognized token,
752             // and can be found inside a rule's value.
753             //
754             entity: function () {
755                 return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) ||
756                        $(this.entities.call)    || $(this.entities.keyword)  || $(this.entities.javascript) ||
757                        $(this.comment);
758             },
759
760             //
761             // A Rule terminator. Note that we use `peek()` to check for '}',
762             // because the `block` rule will be expecting it, but we still need to make sure
763             // it's there, if ';' was ommitted.
764             //
765             end: function () {
766                 return $(';') || peek('}');
767             },
768
769             //
770             // IE's alpha function
771             //
772             //     alpha(opacity=88)
773             //
774             alpha: function () {
775                 var value;
776
777                 if (! $(/^\(opacity=/i)) return;
778                 if (value = $(/^\d+/) || $(this.entities.variable)) {
779                     if (! $(')')) throw new(Error)("missing closing ) for alpha()");
780                     return new(tree.Alpha)(value);
781                 }
782             },
783
784             //
785             // A Selector Element
786             //
787             //     div
788             //     + h1
789             //     #socks
790             //     input[type="text"]
791             //
792             // Elements are the building blocks for Selectors,
793             // they are made out of a `Combinator` (see combinator rule),
794             // and an element name, such as a tag a class, or `*`.
795             //
796             element: function () {
797                 var e, t, c;
798
799                 c = $(this.combinator);
800                 e = $(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/) || $('*') || $(this.attribute) || $(/^\([^)@]+\)/) || $(/^(?:\d*\.)?\d+%/);
801
802                 if (e) { return new(tree.Element)(c, e) }
803
804                 if (c.value && c.value[0] === '&') {
805                   return new(tree.Element)(c, null);
806                 }
807             },
808
809             //
810             // Combinators combine elements together, in a Selector.
811             //
812             // Because our parser isn't white-space sensitive, special care
813             // has to be taken, when parsing the descendant combinator, ` `,
814             // as it's an empty space. We have to check the previous character
815             // in the input, to see if it's a ` ` character. More info on how
816             // we deal with this in *combinator.js*.
817             //
818             combinator: function () {
819                 var match, c = input.charAt(i);
820
821                 if (c === '>' || c === '+' || c === '~') {
822                     i++;
823                     while (input.charAt(i) === ' ') { i++ }
824                     return new(tree.Combinator)(c);
825                 } else if (c === '&') {
826                     match = '&';
827                     i++;
828                     if(input.charAt(i) === ' ') {
829                         match = '& ';
830                     }
831                     while (input.charAt(i) === ' ') { i++ }
832                     return new(tree.Combinator)(match);
833                 } else if (c === ':' && input.charAt(i + 1) === ':') {
834                     i += 2;
835                     while (input.charAt(i) === ' ') { i++ }
836                     return new(tree.Combinator)('::');
837                 } else if (input.charAt(i - 1) === ' ') {
838                     return new(tree.Combinator)(" ");
839                 } else {
840                     return new(tree.Combinator)(null);
841                 }
842             },
843
844             //
845             // A CSS Selector
846             //
847             //     .class > div + h1
848             //     li a:hover
849             //
850             // Selectors are made out of one or more Elements, see above.
851             //
852             selector: function () {
853                 var sel, e, elements = [], c, match;
854
855                 while (e = $(this.element)) {
856                     c = input.charAt(i);
857                     elements.push(e)
858                     if (c === '{' || c === '}' || c === ';' || c === ',') { break }
859                 }
860
861                 if (elements.length > 0) { return new(tree.Selector)(elements) }
862             },
863             tag: function () {
864                 return $(/^[a-zA-Z][a-zA-Z-]*[0-9]?/) || $('*');
865             },
866             attribute: function () {
867                 var attr = '', key, val, op;
868
869                 if (! $('[')) return;
870
871                 if (key = $(/^[a-zA-Z-]+/) || $(this.entities.quoted)) {
872                     if ((op = $(/^[|~*$^]?=/)) &&
873                         (val = $(this.entities.quoted) || $(/^[\w-]+/))) {
874                         attr = [key, op, val.toCSS ? val.toCSS() : val].join('');
875                     } else { attr = key }
876                 }
877
878                 if (! $(']')) return;
879
880                 if (attr) { return "[" + attr + "]" }
881             },
882
883             //
884             // The `block` rule is used by `ruleset` and `mixin.definition`.
885             // It's a wrapper around the `primary` rule, with added `{}`.
886             //
887             block: function () {
888                 var content;
889
890                 if ($('{') && (content = $(this.primary)) && $('}')) {
891                     return content;
892                 }
893             },
894
895             //
896             // div, .class, body > p {...}
897             //
898             ruleset: function () {
899                 var selectors = [], s, rules, match;
900                 save();
901
902                 while (s = $(this.selector)) {
903                     selectors.push(s);
904                     $(this.comment);
905                     if (! $(',')) { break }
906                     $(this.comment);
907                 }
908
909                 if (selectors.length > 0 && (rules = $(this.block))) {
910                     return new(tree.Ruleset)(selectors, rules);
911                 } else {
912                     // Backtrack
913                     furthest = i;
914                     restore();
915                 }
916             },
917             rule: function () {
918                 var name, value, c = input.charAt(i), important, match;
919                 save();
920
921                 if (c === '.' || c === '#' || c === '&') { return }
922
923                 if (name = $(this.variable) || $(this.property)) {
924                     if ((name.charAt(0) != '@') && (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j]))) {
925                         i += match[0].length - 1;
926                         value = new(tree.Anonymous)(match[1]);
927                     } else if (name === "font") {
928                         value = $(this.font);
929                     } else {
930                         value = $(this.value);
931                     }
932                     important = $(this.important);
933
934                     if (value && $(this.end)) {
935                         return new(tree.Rule)(name, value, important, memo);
936                     } else {
937                         furthest = i;
938                         restore();
939                     }
940                 }
941             },
942
943             //
944             // An @import directive
945             //
946             //     @import "lib";
947             //
948             // Depending on our environemnt, importing is done differently:
949             // In the browser, it's an XHR request, in Node, it would be a
950             // file-system operation. The function used for importing is
951             // stored in `import`, which we pass to the Import constructor.
952             //
953             "import": function () {
954                 var path;
955                 if ($(/^@import\s+/) &&
956                     (path = $(this.entities.quoted) || $(this.entities.url)) &&
957                     $(';')) {
958                     return new(tree.Import)(path, imports);
959                 }
960             },
961
962             //
963             // A CSS Directive
964             //
965             //     @charset "utf-8";
966             //
967             directive: function () {
968                 var name, value, rules, types;
969
970                 if (input.charAt(i) !== '@') return;
971
972                 if (value = $(this['import'])) {
973                     return value;
974                 } else if (name = $(/^@media|@page/) || $(/^@(?:-webkit-|-moz-)?keyframes/)) {
975                     types = ($(/^[^{]+/) || '').trim();
976                     if (rules = $(this.block)) {
977                         return new(tree.Directive)(name + " " + types, rules);
978                     }
979                 } else if (name = $(/^@[-a-z]+/)) {
980                     if (name === '@font-face') {
981                         if (rules = $(this.block)) {
982                             return new(tree.Directive)(name, rules);
983                         }
984                     } else if ((value = $(this.entity)) && $(';')) {
985                         return new(tree.Directive)(name, value);
986                     }
987                 }
988             },
989             font: function () {
990                 var value = [], expression = [], weight, shorthand, font, e;
991
992                 while (e = $(this.shorthand) || $(this.entity)) {
993                     expression.push(e);
994                 }
995                 value.push(new(tree.Expression)(expression));
996
997                 if ($(',')) {
998                     while (e = $(this.expression)) {
999                         value.push(e);
1000                         if (! $(',')) { break }
1001                     }
1002                 }
1003                 return new(tree.Value)(value);
1004             },
1005
1006             //
1007             // A Value is a comma-delimited list of Expressions
1008             //
1009             //     font-family: Baskerville, Georgia, serif;
1010             //
1011             // In a Rule, a Value represents everything after the `:`,
1012             // and before the `;`.
1013             //
1014             value: function () {
1015                 var e, expressions = [], important;
1016
1017                 while (e = $(this.expression)) {
1018                     expressions.push(e);
1019                     if (! $(',')) { break }
1020                 }
1021
1022                 if (expressions.length > 0) {
1023                     return new(tree.Value)(expressions);
1024                 }
1025             },
1026             important: function () {
1027                 if (input.charAt(i) === '!') {
1028                     return $(/^! *important/);
1029                 }
1030             },
1031             sub: function () {
1032                 var e;
1033
1034                 if ($('(') && (e = $(this.expression)) && $(')')) {
1035                     return e;
1036                 }
1037             },
1038             multiplication: function () {
1039                 var m, a, op, operation;
1040                 if (m = $(this.operand)) {
1041                     while ((op = ($('/') || $('*'))) && (a = $(this.operand))) {
1042                         operation = new(tree.Operation)(op, [operation || m, a]);
1043                     }
1044                     return operation || m;
1045                 }
1046             },
1047             addition: function () {
1048                 var m, a, op, operation;
1049                 if (m = $(this.multiplication)) {
1050                     while ((op = $(/^[-+]\s+/) || (input.charAt(i - 1) != ' ' && ($('+') || $('-')))) &&
1051                            (a = $(this.multiplication))) {
1052                         operation = new(tree.Operation)(op, [operation || m, a]);
1053                     }
1054                     return operation || m;
1055                 }
1056             },
1057
1058             //
1059             // An operand is anything that can be part of an operation,
1060             // such as a Color, or a Variable
1061             //
1062             operand: function () {
1063                 var negate, p = input.charAt(i + 1);
1064
1065                 if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-') }
1066                 var o = $(this.sub) || $(this.entities.dimension) ||
1067                         $(this.entities.color) || $(this.entities.variable) ||
1068                         $(this.entities.call);
1069                 return negate ? new(tree.Operation)('*', [new(tree.Dimension)(-1), o])
1070                               : o;
1071             },
1072
1073             //
1074             // Expressions either represent mathematical operations,
1075             // or white-space delimited Entities.
1076             //
1077             //     1px solid black
1078             //     @var * 2
1079             //
1080             expression: function () {
1081                 var e, delim, entities = [], d;
1082
1083                 while (e = $(this.addition) || $(this.entity)) {
1084                     entities.push(e);
1085                 }
1086                 if (entities.length > 0) {
1087                     return new(tree.Expression)(entities);
1088                 }
1089             },
1090             property: function () {
1091                 var name;
1092
1093                 if (name = $(/^(\*?-?[-a-z_0-9]+)\s*:/)) {
1094                     return name[1];
1095                 }
1096             }
1097         }
1098     };
1099 };
1100
1101 if (less.mode === 'browser' || less.mode === 'rhino') {
1102     //
1103     // Used by `@import` directives
1104     //
1105     less.Parser.importer = function (path, paths, callback, env) {
1106         if (path.charAt(0) !== '/' && paths.length > 0) {
1107             path = paths[0] + path;
1108         }
1109         // We pass `true` as 3rd argument, to force the reload of the import.
1110         // This is so we can get the syntax tree as opposed to just the CSS output,
1111         // as we need this to evaluate the current stylesheet.
1112         loadStyleSheet({ href: path, title: path, type: env.mime }, callback, true);
1113     };
1114 }
1115