docs: function (str, callback) {
              var root, start, end, zone, line, lines, buff = [], c, error = null;
  
              i = j = current = furthest = 0;
              input = str.replace(/\r\n/g, '\n');
  
              // Remove potential UTF Byte Order Mark
              input = input.replace(/^\uFEFF/, '');
  
              // 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 = new(LessError)({
                          index: i-1,
                          type: 'Parse',
                          message: (level > 0) ? "missing closing `}`" : "missing opening `{`",
                          filename: env.filename
                      }, env);
                  }
  
                  return chunks.map(function (c) { return c.join('') });;
              })([[]]);
  
              if (error) {
                  return callback(error, env);
              }
  
              // 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. The callback is called when the input is parsed.
              try {
                  root = new(tree.Ruleset)([], $(this.parsers.primary));
                  root.root = true;
              } catch (e) {
                  return callback(new(LessError)(e, env));
              }
  
              root.toCSS = (function (evaluate) {
                  var line, lines, column;
  
                  return function (options, variables) {
                      var frames = [], importError;
  
                      options = options || {};
                      //
                      // Allows setting variables with a hash, so:
                      //
                      //   `{ color: new(tree.Color)('#f01') }` will become:
                      //
                      //   new(tree.Rule)('@color',
                      //     new(tree.Value)([
                      //       new(tree.Expression)([
                      //         new(tree.Color)('#f01')
                      //       ])
                      //     ])
                      //   )
                      //
                      if (typeof(variables) === 'object' && !Array.isArray(variables)) {
                          variables = Object.keys(variables).map(function (k) {
                              var value = variables[k];
  
                              if (! (value instanceof tree.Value)) {
                                  if (! (value instanceof tree.Expression)) {
                                      value = new(tree.Expression)([value]);
                                  }
                                  value = new(tree.Value)([value]);
                              }
                              return new(tree.Rule)('@' + k, value, false, 0);
                          });
                          frames = [new(tree.Ruleset)(null, variables)];
                      }
  
                      try {
                          var css = evaluate.call(this, { frames: frames })
                                            .toCSS([], { compress: options.compress || false, dumpLineNumbers: env.dumpLineNumbers });
                      } catch (e) {
                          throw new(LessError)(e, env);
                      }
  
                      if ((importError = parser.imports.error)) { // Check if there was an error during importing
                          if (importError instanceof LessError) throw importError;
                          else                                  throw new(LessError)(importError, env);
                      }
  
                      if (options.yuicompress && less.mode === 'node') {
                          return require('ycssmin').cssmin(css);
                      } else if (options.compress) {
                          return css.replace(/(\s)+/g, "$1");
                      } else {
                          return css;
                      }
                  };
              })(root.eval);
  
              // If `i` is smaller than the `input.length - 1`,
              // it means the parser wasn't able to parse the whole
              // string, so we've got a parsing error.
              //
              // We try to extract a \n delimited string,
              // showing the line where the parse error occured.
              // We split it up into two parts (the part which parsed,
              // and the part which didn't), so we can color them differently.
              if (i < input.length - 1) {
                  i = furthest;
                  lines = input.split('\n');
                  line = (input.slice(0, i).match(/\n/g) || "").length + 1;
  
                  for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ }
  
                  error = {
                      type: "Parse",
                      message: "Syntax Error on line " + line,
                      index: i,
                      filename: env.filename,
                      line: line,
                      column: column,
                      extract: [
                          lines[line - 2],
                          lines[line - 1],
                          lines[line]
                      ]
                  };
              }
  
              if (this.imports.queue.length > 0) {
                  finish = function (e) {
                      e = error || e;
                      if (e) callback(e);
                      else callback(null, root);
                  };
              } else {
                  callback(error, root);
              }
          }