text: var carto = exports,
      tree = require('./tree'),
      _ = global._ || require('underscore');
  
  //    Token matching is done with the `$` function, which either takes
  //    a terminal string or regexp, or a non-terminal function to call.
  //    It also takes care of moving all the indices forwards.
  carto.Parser = function Parser(env) {
      var input,       // LeSS input string
          i,           // current index in `input`
          j,           // current chunk
          temp,        // temporarily holds a chunk's state, for backtracking
          memo,        // temporarily holds `i`, when backtracking
          furthest,    // furthest index the parser has gone to
          chunks,      // chunkified input
          current,     // index of current chunk, in `input`
          parser;
  
      var that = this;
  
      // This function is called after all files
      // have been imported through `@import`.
      var finish = function() {};
  
      function save()    {
          temp = chunks[j];
          memo = i;
          current = i;
      }
      function restore() {
          chunks[j] = temp;
          i = memo;
          current = i;
      }
  
      function sync() {
          if (i > current) {
              chunks[j] = chunks[j].slice(i - current);
              current = i;
          }
      }
      //
      // Parse from a token, regexp or string, and move forward if match
      //
      function $(tok) {
          var match, args, length, c, index, endIndex, k;
  
          // Non-terminal
          if (tok instanceof Function) {
              return tok.call(parser.parsers);
          // Terminal
          // Either match a single character in the input,
          // or match a regexp in the current chunk (chunk[j]).
          } else if (typeof(tok) === 'string') {
              match = input.charAt(i) === tok ? tok : null;
              length = 1;
              sync();
          } else {
              sync();
  
              match = tok.exec(chunks[j]);
              if (match) {
                  length = match[0].length;
              } else {
                  return null;
              }
          }
  
          // The match is confirmed, add the match length to `i`,
          // and consume any extra white-space characters (' ' || '\n')
          // which come after that. The reason for this is that LeSS's
          // grammar is mostly white-space insensitive.
          if (match) {
              var mem = i += length;
              endIndex = i + chunks[j].length - length;
  
              while (i < endIndex) {
                  c = input.charCodeAt(i);
                  if (! (c === 32 || c === 10 || c === 9)) { break; }
                  i++;
              }
              chunks[j] = chunks[j].slice(length + (i - mem));
              current = i;
  
              if (chunks[j].length === 0 && j < chunks.length - 1) { j++; }
  
              if (typeof(match) === 'string') {
                  return match;
              } else {
                  return match.length === 1 ? match[0] : match;
              }
          }
      }
  
      // Same as $(), but don't change the state of the parser,
      // just return the match.
      function peek(tok) {
          if (typeof(tok) === 'string') {
              return input.charAt(i) === tok;
          } else {
              return !!tok.test(chunks[j]);
          }
      }
  
      function extractErrorLine(style, errorIndex) {
          return (style.slice(0, errorIndex).match(/\n/g) || '').length + 1;
      }
  
  
      // Make an error object from a passed set of properties.
      // Accepted properties:
      // - `message`: Text of the error message.
      // - `filename`: Filename where the error occurred.
      // - `index`: Char. index where the error occurred.
      function makeError(err) {
          var einput;
          var errorTemplate;
  
          _.defaults(err, {
              index: furthest,
              filename: env.filename,
              message: 'Parse error.',
              line: 0,
              column: -1
          });
  
          if (err.filename && that.env.inputs && that.env.inputs[err.filename]) {
              einput = that.env.inputs[err.filename];
          } else {
              einput = input;
          }
  
          err.line = extractErrorLine(einput, err.index);
          for (var n = err.index; n >= 0 && einput.charAt(n) !== '\n'; n--) {
              err.column++;
          }
          errorTemplate = _.template('<%=filename%>:<%=line%>:<%=column%> <%=message%>');
          return new Error(errorTemplate(err));
      }
  
      this.env = env = env || {};
      this.env.filename = this.env.filename || null;
      this.env.inputs = this.env.inputs || {};
  
      // The Parser
      parser = {
  
          extractErrorLine: extractErrorLine,
          //
          // Parse an input string into an abstract syntax tree.
          // Throws an error on parse errors.
          parse: function(str) {
              var root, start, end, zone, line, lines, buff = [], c, error = null;
  
              i = j = current = furthest = 0;
              chunks = [];
              input = str.replace(/\r\n/g, '\n');
              if (env.filename) {
                  that.env.inputs[env.filename] = input;
              }
  
              var early_exit = false;
  
              // Split the input into chunks.
              chunks = (function (chunks) {
                  var j = 0,
                      skip = /(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,
                      comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
                      string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,
                      level = 0,
                      match,
                      chunk = chunks[0],
                      inParam;
  
                  for (var i = 0, c, cc; i < input.length;) {
                      skip.lastIndex = i;
                      if (match = skip.exec(input)) {
                          if (match.index === i) {
                              i += match[0].length;
                              chunk.push(match[0]);
                          }
                      }
                      c = input.charAt(i);
                      comment.lastIndex = string.lastIndex = i;
  
                      if (match = string.exec(input)) {
                          if (match.index === i) {
                              i += match[0].length;
                              chunk.push(match[0]);
                              continue;
                          }
                      }
  
                      if (!inParam && c === '/') {
                          cc = input.charAt(i + 1);
                          if (cc === '/' || cc === '*') {
                              if (match = comment.exec(input)) {
                                  if (match.index === i) {
                                      i += match[0].length;
                                      chunk.push(match[0]);
                                      continue;
                                  }
                              }
                          }
                      }
  
                      switch (c) {
                          case '{': if (! inParam) { level ++;        chunk.push(c);                           break; }
                          case '}': if (! inParam) { level --;        chunk.push(c); chunks[++j] = chunk = []; break; }
                          case '(': if (! inParam) { inParam = true;  chunk.push(c);                           break; }
                          case ')': if (  inParam) { inParam = false; chunk.push(c);                           break; }
                          default:                                    chunk.push(c);
                      }
  
                      i++;
                  }
                  if (level !== 0) {
                      error = {
                          index: i - 1,
                          type: 'Parse',
                          message: (level > 0) ? "missing closing `}`" : "missing opening `{`"
                      };
                  }
  
                  return chunks.map(function (c) { return c.join(''); });
              })([[]]);
  
              if (error) {
                  throw makeError(error);
              }
  
              // Start with the primary rule.
              // The whole syntax tree is held under a Ruleset node,
              // with the `root` property set to true, so no `{}` are
              // output.
              root = new tree.Ruleset([], $(this.parsers.primary));
              root.root = true;
  
              // Get an array of Ruleset objects, flattened
              // and sorted according to specificitySort
              root.toList = (function() {
                  var line, lines, column;
                  return function(env) {
                      env.error = function(e) {
                          if (!env.errors) env.errors = new Error('');
                          if (env.errors.message) {
                              env.errors.message += '\n' + makeError(e).message;
                          } else {
                              env.errors.message = makeError(e).message;
                          }
                      };
                      env.frames = env.frames || [];
  
  
                      // call populates Invalid-caused errors
                      var definitions = this.flatten([], [], env);
                      definitions.sort(specificitySort);
                      return definitions;
                  };
              })();
  
              // Sort rules by specificity: this function expects selectors to be
              // split already.
              //
              // Written to be used as a .sort(Function);
              // argument.
              //
              // [1, 0, 0, 467] > [0, 0, 1, 520]
              var specificitySort = function(a, b) {
                  var as = a.specificity;
                  var bs = b.specificity;
  
                  if (as[0] != bs[0]) return bs[0] - as[0];
                  if (as[1] != bs[1]) return bs[1] - as[1];
                  if (as[2] != bs[2]) return bs[2] - as[2];
                  return bs[3] - as[3];
              };
  
              return root;
          },
  
          // Here in, the parsing rules/functions
          //
          // The basic structure of the syntax tree generated is as follows:
          //
          //   Ruleset ->  Rule -> Value -> Expression -> Entity
          //
          //  In general, most rules will try to parse a token with the `$()` function, and if the return
          //  value is truly, will return a new node, of the relevant type. Sometimes, we need to check
          //  first, before parsing, that's when we use `peek()`.
          parsers: {
              // The `primary` rule is the *entry* and *exit* point of the parser.
              // The rules here can appear at any level of the parse tree.
              //
              // The recursive nature of the grammar is an interplay between the `block`
              // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
              // as represented by this simplified grammar:
              //
              //     primary  →  (ruleset | rule)+
              //     ruleset  →  selector+ block
              //     block    →  '{' primary '}'
              //
              // Only at one point is the primary rule not called from the
              // block rule: at the root level.
              primary: function() {
                  var node, root = [];
  
                  while ((node = $(this.rule) || $(this.ruleset) ||
                                 $(this.comment)) ||
                                 $(/^[\s\n]+/) || (node = $(this.invalid))) {
                      if (node) root.push(node);
                  }
                  return root;
              },
  
              invalid: function () {
                  var chunk = $(/^[^;\n]*[;\n]/);
  
                  // To fail gracefully, match everything until a semicolon or linebreak.
                  if (chunk) {
                      return new tree.Invalid(chunk, memo);
                  }
              },
  
              // We create a Comment node for CSS comments `/* */`,
              // but keep the LeSS comments `//` silent, by just skipping
              // over them.
              comment: function() {
                  var comment;
  
                  if (input.charAt(i) !== '/') return;
  
                  if (input.charAt(i + 1) === '/') {
                      return new tree.Comment($(/^\/\/.*/), true);
                  } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) {
                      return new tree.Comment(comment);
                  }
              },
  
              // Entities are tokens which can be found inside an Expression
              entities: {
  
                  // A string, which supports escaping " and ' "milky way" 'he\'s the one!'
                  quoted: function() {
                      if (input.charAt(i) !== '"' && input.charAt(i) !== "'") return;
                      var str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/);
                      if (str) {
                          return new tree.Quoted(str[1] || str[2]);
                      }
                  },
  
                  // A reference to a Mapnik field, like [NAME]
                  // Behind the scenes, this has the same representation, but Carto
                  // needs to be careful to warn when unsupported operations are used.
                  field: function() {
                      if (! $('[')) return;
                      var field_name = $(/(^[^\]]+)/);
                      if (! $(']')) return;
                      if (field_name) return new tree.Field(field_name[1]);
                  },
  
                  // This is a comparison operator
                  comparison: function() {
                      var str = $(/^=~|=|!=|<=|>=|<|>/);
                      if (str) {
                          return str;
                      }
                  },
  
                  // A catch-all word, such as: hard-light
                  // These can start with either a letter or a dash (-),
                  // and then contain numbers, underscores, and letters.
                  keyword: function() {
                      var k = $(/^[A-Za-z-]+[A-Za-z-0-9_]*/);
                      if (k) { return new tree.Keyword(k); }
                  },
  
                  // A function call like rgb(255, 0, 255)
                  // The arguments are parsed with the `entities.arguments` parser.
                  call: function() {
                      var name, args;
  
                      if (!(name = /^([\w\-]+|%)\(/.exec(chunks[j]))) return;
  
                      name = name[1];
  
                      if (name === 'url') {
                          // url() is handled by the url parser instead
                          return null;
                      } else {
                          i += name.length;
                      }
  
                      $('('); // Parse the '(' and consume whitespace.
  
                      args = $(this.entities['arguments']);
  
                      if (!$(')')) return;
  
                      if (name) {
                          return new tree.Call(name, args, i);
                      }
                  },
                  // Arguments are comma-separated expressions
                  'arguments': function() {
                      var args = [], arg;
  
                      while (arg = $(this.expression)) {
                          args.push(arg);
                          if (! $(',')) { break; }
                      }
  
                      return args;
                  },
                  literal: function() {
                      return $(this.entities.dimension) ||
                          $(this.entities.keywordcolor) ||
                          $(this.entities.hexcolor) ||
                          $(this.entities.quoted);
                  },
  
                  // Parse url() tokens
                  //
                  // We use a specific rule for urls, because they don't really behave like
                  // standard function calls. The difference is that the argument doesn't have
                  // to be enclosed within a string, so it can't be parsed as an Expression.
                  url: function() {
                      var value;
  
                      if (input.charAt(i) !== 'u' || !$(/^url\(/)) return;
                      value = $(this.entities.quoted) || $(this.entities.variable) ||
                              $(/^[\-\w%@$\/.&=:;#+?~]+/) || '';
                      if (! $(')')) {
                          return new tree.Invalid(value, memo, 'Missing closing ) in URL.');
                      } else {
                          return new tree.URL((typeof value.value !== 'undefined' ||
                              value instanceof tree.Variable) ?
                              value : new tree.Quoted(value));
                      }
                  },
  
                  // A Variable entity, such as `@fink`, in
                  //
                  //     width: @fink + 2px
                  //
                  // We use a different parser for variable definitions,
                  // see `parsers.variable`.
                  variable: function() {
                      var name, index = i;
  
                      if (input.charAt(i) === '@' && (name = $(/^@[\w-]+/))) {
                          return new tree.Variable(name, index, env.filename);
                      }
                  },
  
                  hexcolor: function() {
                      var rgb;
                      if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) {
                          return new tree.Color(rgb[1]);
                      }
                  },
  
                  keywordcolor: function() {
                      var rgb = chunks[j].match(/^[a-z]+/);
                      if (rgb && rgb[0] in tree.Reference.data.colors) {
                          return new tree.Color(tree.Reference.data.colors[$(/^[a-z]+/)]);
                      }
                  },
  
                  // A Dimension, that is, a number and a unit. The only
                  // unit that has an effect is %
                  dimension: function() {
                      var c = input.charCodeAt(i);
                      if ((c > 57 || c < 45) || c === 47) return;
                      var value = $(/^(-?\d*\.?\d+(?:[eE][-+]?\d+)?)(\%|\w+)?/);
                      if (value) {
                          return new tree.Dimension(value[1], value[2], memo);
                      }
                  }
  
              },
  
              // The variable part of a variable definition.
              // Used in the `rule` parser. Like @fink:
              variable: function() {
                  var name;
  
                  if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) {
                      return name[1];
                  }
              },
  
              // Entities are the smallest recognized token,
              // and can be found inside a rule's value.
              entity: function() {
                  return $(this.entities.call) ||
                      $(this.entities.literal) ||
                      $(this.entities.field) ||
                      $(this.entities.variable) ||
                      $(this.entities.url) ||
                      $(this.entities.keyword);
              },
  
              // A Rule terminator. Note that we use `peek()` to check for '}',
              // because the `block` rule will be expecting it, but we still need to make sure
              // it's there, if ';' was ommitted.
              end: function() {
                  return $(';') || peek('}');
              },
  
              // Elements are the building blocks for Selectors. They consist of
              // an element name, such as a tag a class, or `*`.
              element: function() {
                  var e = $(/^(?:[.#][\w\-]+|\*|Map)/);
                  if (e) return new tree.Element(e);
              },
  
              // Attachments allow adding multiple lines, polygons etc. to an
              // object. There can only be one attachment per selector.
              attachment: function() {
                  var s = $(/^::([\w\-]+(?:\/[\w\-]+)*)/);
                  if (s) return s[1];
              },
  
              // Selectors are made out of one or more Elements, see above.
              selector: function() {
                  var a, attachment,
                      e, elements = [],
                      f, filters = new tree.Filterset(),
                      z, zooms = [], fo,
                      frame_offset = tree.FrameOffset.none,
                      segments = 0, conditions = 0;
  
                  while (
                          (e = $(this.element)) ||
                          (z = $(this.zoom)) ||
                          (fo = $(this.frame_offset)) ||
                          (f = $(this.filter)) ||
                          (a = $(this.attachment))
                      ) {
                      segments++;
                      if (e) {
                          elements.push(e);
                      } else if (z) {
                          zooms.push(z);
                          conditions++;
                      } else if (fo) {
                          frame_offset = fo;
                          conditions++;
                      } else if (f) {
                          var err = filters.add(f);
                          if (err) {
                              throw makeError({
                                  message: err,
                                  index: i - 1
                              });
                          }
                          conditions++;
                      } else if (attachment) {
                          throw makeError({
                              message: 'Encountered second attachment name.',
                              index: i - 1
                          });
                      } else {
                          attachment = a;
                      }
  
                      var c = input.charAt(i);
                      if (c === '{' || c === '}' || c === ';' || c === ',') { break; }
                  }
  
                  if (segments) {
                      return new tree.Selector(filters, zooms, frame_offset, elements, attachment, conditions, memo);
                  }
              },
  
              filter: function() {
                  save();
                  var key, op, val;
                  if (! $('[')) return;
                  if (key = $(/^[a-zA-Z0-9\-_]+/) ||
                      $(this.entities.quoted) ||
                      $(this.entities.variable) ||
                      $(this.entities.keyword) ||
                      $(this.entities.field)) {
                      // TODO: remove at 1.0.0
                      if (key instanceof tree.Quoted) {
                          key = new tree.Field(key.toString());
                      }
                      if ((op = $(this.entities.comparison)) &&
                          (val = $(this.entities.quoted) ||
                               $(this.entities.variable) ||
                               $(this.entities.dimension) ||
                               $(this.entities.keyword) ||
                               $(this.entities.field))) {
                          if (! $(']')) {
                              throw makeError({
                                  message: 'Missing closing ] of filter.',
                                  index: memo - 1
                              });
                          }
                          if (!key.is) key = new tree.Field(key);
                          return new tree.Filter(key, op, val, memo, env.filename);
                      }
                  }
              },
  
              frame_offset: function() {
                  save();
                  var op, val;
                  if ($(/^\[\s*frame-offset/g) &&
                      (op = $(this.entities.comparison)) &&
                      (val = $(/^\d+/)) &&
                      $(']'))  {
                          return tree.FrameOffset(op, val, memo);
                  }
              },
  
              zoom: function() {
                  save();
                  var op, val;
                  if ($(/^\[\s*zoom/g) &&
                      (op = $(this.entities.comparison)) &&
                      (val = $(this.entities.variable) || $(this.entities.dimension)) && $(']')) {
                          return new tree.Zoom(op, val, memo);
                  } else {
                      // backtrack
                      restore();
                  }
              },
  
              // The `block` rule is used by `ruleset`
              // It's a wrapper around the `primary` rule, with added `{}`.
              block: function() {
                  var content;
  
                  if ($('{') && (content = $(this.primary)) && $('}')) {
                      return content;
                  }
              },
  
              // div, .class, body > p {...}
              ruleset: function() {
                  var selectors = [], s, f, l, rules, filters = [];
                  save();
  
                  while (s = $(this.selector)) {
                      selectors.push(s);
                      while ($(this.comment)) {}
                      if (! $(',')) { break; }
                      while ($(this.comment)) {}
                  }
                  if (s) {
                      while ($(this.comment)) {}
                  }
  
                  if (selectors.length > 0 && (rules = $(this.block))) {
                      if (selectors.length === 1 &&
                          selectors[0].elements.length &&
                          selectors[0].elements[0].value === 'Map') {
                          var rs = new tree.Ruleset(selectors, rules);
                          rs.isMap = true;
                          return rs;
                      }
                      return new tree.Ruleset(selectors, rules);
                  } else {
                      // Backtrack
                      restore();
                  }
              },
  
              rule: function() {
                  var name, value, c = input.charAt(i);
                  save();
  
                  if (c === '.' || c === '#') { return; }
  
                  if (name = $(this.variable) || $(this.property)) {
                      value = $(this.value);
  
                      if (value && $(this.end)) {
                          return new tree.Rule(name, value, memo, env.filename);
                      } else {
                          furthest = i;
                          restore();
                      }
                  }
              },
  
              font: function() {
                  var value = [], expression = [], weight, font, e;
  
                  while (e = $(this.entity)) {
                      expression.push(e);
                  }
  
                  value.push(new tree.Expression(expression));
  
                  if ($(',')) {
                      while (e = $(this.expression)) {
                          value.push(e);
                          if (! $(',')) { break; }
                      }
                  }
                  return new tree.Value(value);
              },
  
              // A Value is a comma-delimited list of Expressions
              // In a Rule, a Value represents everything after the `:`,
              // and before the `;`.
              value: function() {
                  var e, expressions = [];
  
                  while (e = $(this.expression)) {
                      expressions.push(e);
                      if (! $(',')) { break; }
                  }
  
                  if (expressions.length > 1) {
                      return new tree.Value(expressions.map(function(e) {
                          return e.value[0];
                      }));
                  } else if (expressions.length === 1) {
                      return new tree.Value(expressions);
                  }
              },
              // A sub-expression, contained by parenthensis
              sub: function() {
                  var e, expressions = [];
  
                  if ($('(')) {
                    while (e = $(this.expression)) {
                        expressions.push(e);
                        if (! $(',')) { break; }
                    }
                    $(')');
                  }
  
                  if (expressions.length > 1) {
                      return new tree.Value(expressions.map(function(e) {
                          return e.value[0];
                      }));
                  } else if (expressions.length === 1) {
                      return new tree.Value(expressions);
                  }
              },
              // This is a misnomer because it actually handles multiplication
              // and division.
              multiplication: function() {
                  var m, a, op, operation;
                  if (m = $(this.operand)) {
                      while ((op = ($('/') || $('*') || $('%'))) && (a = $(this.operand))) {
                          operation = new tree.Operation(op, [operation || m, a], memo);
                      }
                      return operation || m;
                  }
              },
              addition: function() {
                  var m, a, op, operation;
                  if (m = $(this.multiplication)) {
                      while ((op = $(/^[-+]\s+/) || (input.charAt(i - 1) != ' ' && ($('+') || $('-')))) &&
                             (a = $(this.multiplication))) {
                          operation = new tree.Operation(op, [operation || m, a], memo);
                      }
                      return operation || m;
                  }
              },
  
              // An operand is anything that can be part of an operation,
              // such as a Color, or a Variable
              operand: function() {
                  return $(this.sub) || $(this.entity);
              },
  
              // Expressions either represent mathematical operations,
              // or white-space delimited Entities.  @var * 2
              expression: function() {
                  var e, delim, entities = [], d;
  
                  while (e = $(this.addition) || $(this.entity)) {
                      entities.push(e);
                  }
  
                  if (entities.length > 0) {
                      return new tree.Expression(entities);
                  }
              },
              property: function() {
                  var name = $(/^(([a-z][-a-z_0-9]*\/)?\*?-?[-a-z_0-9]+)\s*:/);
                  if (name) return name[1];
              }
          }
      };
      return parser;
  };
  