text: 
  /*!
   * Stylus - Lexer
   * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
   * MIT Licensed
   */
  
  /**
   * Module dependencies.
   */
  
  var Token = require('./token')
    , nodes = require('./nodes')
    , errors = require('./errors')
    , units = require('./units');
  
  /**
   * Expose `Lexer`.
   */
  
  exports = module.exports = Lexer;
  
  /**
   * Operator aliases.
   */
  
  var alias = {
      'and': '&&'
    , 'or': '||'
    , 'is': '=='
    , 'isnt': '!='
    , 'is not': '!='
    , ':=': '?='
  };
  
  /**
   * Units.
   */
  
  units = units.join('|');
  
  /**
   * Unit RegExp.
   */
  
  var unit = new RegExp('^(-)?(\\d+\\.\\d+|\\d+|\\.\\d+)(' + units + ')?[ \\t]*');
  
  /**
   * Initialize a new `Lexer` with the given `str` and `options`.
   *
   * @param {String} str
   * @param {Object} options
   * @api private
   */
  
  function Lexer(str, options) {
    var self = this;
    options = options || {};
    this.stash = [];
    this.indentStack = [];
    this.indentRe = null;
    this.lineno = 1;
  
    function comment(str, val, offset, s) {
      var inComment = s.lastIndexOf('/*', offset) > s.lastIndexOf('*/', offset)
        || s.lastIndexOf('//', offset) > s.lastIndexOf('\n', offset);
      ++self.lineno;
      return inComment
        ? str
        : val;
    };
  
    // Remove UTF-8 BOM.
    if ('\uFEFF' == str.charAt(0)) str = str.slice(1);
  
    this.str = str
      .replace(/\s+$/, '\n')
      .replace(/\r\n?/g, '\n')
      .replace(/\\ *\n/g, ' ')
      .replace(/([,(]|:(?!\/\/[^ ])) *(?:\/\/[^\n]*)?\n\s*/g, comment)
      .replace(/\s*\n *([,)])/g, comment);
  };
  
  /**
   * Lexer prototype.
   */
  
  Lexer.prototype = {
    
    /**
     * Custom inspect.
     */
    
    inspect: function(){
      var tok
        , tmp = this.str
        , buf = [];
      while ('eos' != (tok = this.next()).type) {
        buf.push(tok.inspect());
      }
      this.str = tmp;
      this.prevIndents = 0;
      return buf.concat(tok.inspect()).join('\n');
    },
  
    /**
     * Lookahead `n` tokens.
     *
     * @param {Number} n
     * @return {Object}
     * @api private
     */
    
    lookahead: function(n){
      var fetch = n - this.stash.length;
      while (fetch-- > 0) this.stash.push(this.advance());
      return this.stash[--n];
    },
    
    /**
     * Consume the given `len`.
     *
     * @param {Number|Array} len
     * @api private
     */
  
    skip: function(len){
      this.str = this.str.substr(Array.isArray(len)
        ? len[0].length
        : len);
    },
  
    /**
     * Fetch next token including those stashed by peek.
     *
     * @return {Token}
     * @api private
     */
  
    next: function() {
      var tok = this.stashed() || this.advance();
      switch (tok.type) {
        case 'newline':
        case 'indent':
          ++this.lineno;
          break;
        case 'outdent':
          if ('outdent' != this.prev.type) ++this.lineno;
      }
      this.prev = tok;
      tok.lineno = this.lineno;
      return tok;
    },
  
    /**
     * Check if the current token is a part of selector.
     *
     * @return {Boolean}
     * @api private
     */
  
    isPartOfSelector: function() {
      var tok = this.stash[this.stash.length - 1] || this.prev;
      switch (tok && tok.type) {
        // #for
        case 'color':
          return 2 == tok.val.raw.length;
        // .or
        case '.':
        // [is]
        case '[':
          return true;
      }
      return false;
    },
  
    /**
     * Fetch next token.
     *
     * @return {Token}
     * @api private
     */
  
    advance: function() {
      return this.eos()
        || this.null()
        || this.sep()
        || this.keyword()
        || this.urlchars()
        || this.atrule()
        || this.scope()
        || this.extends()
        || this.media()
        || this.mozdocument()
        || this.comment()
        || this.newline()
        || this.escaped()
        || this.important()
        || this.literal()
        || this.function()
        || this.brace()
        || this.paren()
        || this.color()
        || this.string()
        || this.unit()
        || this.namedop()
        || this.boolean()
        || this.atblock()
        || this.ident()
        || this.op()
        || this.space()
        || this.selector();
    },
  
    /**
     * Lookahead a single token.
     *
     * @return {Token}
     * @api private
     */
    
    peek: function() {
      return this.lookahead(1);
    },
    
    /**
     * Return the next possibly stashed token.
     *
     * @return {Token}
     * @api private
     */
  
    stashed: function() {
      return this.stash.shift();
    },
  
    /**
     * EOS | trailing outdents.
     */
  
    eos: function() {
      if (this.str.length) return;
      if (this.indentStack.length) {
        this.indentStack.shift();
        return new Token('outdent');
      } else {
        return new Token('eos');
      }
    },
  
    /**
     * url char
     */
  
    urlchars: function() {
      var captures;
      if (!this.isURL) return;
      if (captures = /^[\/:@.;?&=*!,<>#%0-9]+/.exec(this.str)) {
        this.skip(captures);
        return new Token('literal', new nodes.Literal(captures[0]));
      }
    },
  
    /**
     * ';' [ \t]*
     */
  
    sep: function() {
      var captures;
      if (captures = /^;[ \t]*/.exec(this.str)) {
        this.skip(captures);
        return new Token(';');
      }
    },
    
    /**
     * ' '+
     */
  
    space: function() {
      var captures;
      if (captures = /^([ \t]+)/.exec(this.str)) {
        this.skip(captures);
        return new Token('space');
      }
    },
    
    /**
     * '\\' . ' '*
     */
     
    escaped: function() {
      var captures;
      if (captures = /^\\(.)[ \t]*/.exec(this.str)) {
        var c = captures[1];
        this.skip(captures);
        return new Token('ident', new nodes.Literal(c));
      }
    },
    
    /**
     * '@css' ' '* '{' .* '}' ' '*
     */
    
    literal: function() {
      // HACK attack !!!
      var captures;
      if (captures = /^@css[ \t]*\{/.exec(this.str)) {
        this.skip(captures);
        var c
          , braces = 1
          , css = '';
        while (c = this.str[0]) {
          this.str = this.str.substr(1);
          switch (c) {
            case '{': ++braces; break;
            case '}': --braces; break;
          }
          css += c;
          if (!braces) break;
        }
        css = css.replace(/\s*}$/, '');
        return new Token('literal', new nodes.Literal(css));
      }
    },
    
    /**
     * '!important' ' '*
     */
    
    important: function() {
      var captures;
      if (captures = /^!important[ \t]*/.exec(this.str)) {
        this.skip(captures);
        return new Token('ident', new nodes.Literal('!important'));
      }
    },
    
    /**
     * '{' | '}'
     */
    
    brace: function() {
      var captures;
      if (captures = /^([{}])/.exec(this.str)) {
        this.skip(1);
        var brace = captures[1];
        return new Token(brace, brace);
      }
    },
    
    /**
     * '(' | ')' ' '*
     */
    
    paren: function() {
      var captures;
      if (captures = /^([()])([ \t]*)/.exec(this.str)) {
        var paren = captures[1];
        this.skip(captures);
        if (')' == paren) this.isURL = false;
        var tok = new Token(paren, paren);
        tok.space = captures[2];
        return tok;
      }
    },
    
    /**
     * 'null'
     */
    
    null: function() {
      var captures
        , tok;
      if (captures = /^(null)\b[ \t]*/.exec(this.str)) {
        this.skip(captures);
        if (this.isPartOfSelector()) {
          tok = new Token('ident', new nodes.Ident(captures[0]));
        } else {
          tok = new Token('null', nodes.null);
        }
        return tok;
      }
    },
    
    /**
     *   'if'
     * | 'else'
     * | 'unless'
     * | 'return'
     * | 'for'
     * | 'in'
     */
    
    keyword: function() {
      var captures
        , tok;
      if (captures = /^(return|if|else|unless|for|in)\b[ \t]*/.exec(this.str)) {
        var keyword = captures[1];
        this.skip(captures);
        if (this.isPartOfSelector()) {
          tok = new Token('ident', new nodes.Ident(captures[0]));
        } else {
          tok = new Token(keyword, keyword);
        }
        return tok;
      }
    },
    
    /**
     *   'not'
     * | 'and'
     * | 'only'
     * | 'or'
     * | 'is'
     * | 'is not'
     * | 'isnt'
     * | 'is a'
     * | 'is defined'
     */
    
    namedop: function() {
      var captures
        , tok;
      if (captures = /^(not|and|only|or|is a|is defined|isnt|is not|is)(?!-)\b([ \t]*)/.exec(this.str)) {
        var op = captures[1];
        this.skip(captures);
        if (this.isPartOfSelector()) {
          tok = new Token('ident', new nodes.Ident(captures[0]));
        } else {
          op = alias[op] || op;
          tok = new Token(op, op);
        }
        tok.space = captures[2];
        return tok;
      }
    },
    
    /**
     *   ','
     * | '+'
     * | '+='
     * | '-'
     * | '-='
     * | '*'
     * | '*='
     * | '/'
     * | '/='
     * | '%'
     * | '%='
     * | '**'
     * | '!'
     * | '&'
     * | '&&'
     * | '||'
     * | '>'
     * | '>='
     * | '<'
     * | '<='
     * | '='
     * | '=='
     * | '!='
     * | '!'
     * | '~'
     * | '?='
     * | ':='
     * | '?'
     * | ':'
     * | '['
     * | ']'
     * | '.'
     * | '..'
     * | '...'
     */
    
    op: function() {
      var captures;
      if (captures = /^([.]{1,3}|&&|\|\||[!<>=?:]=|\*\*|[-+*\/%]=?|[,=?:!~<>&\[\]])([ \t]*)/.exec(this.str)) {
        var op = captures[1];
        this.skip(captures);
        op = alias[op] || op;
        var tok = new Token(op, op);
        tok.space = captures[2];
        this.isURL = false;
        return tok;
      }
    },
  
    /**
     * '@extends'
     */
    
    extends: function() {
      var captures;
      if (captures = /^@extends?[ \t]*/.exec(this.str)) {
        this.skip(captures);
        return new Token('extend');
      }
    },
  
    /**
     * '@media'
     */
    
    media: function() {
      var captures;
      if (captures = /^@media[ \t]*/.exec(this.str)) {
        this.skip(captures);
        return new Token('media');
      }
    },
  
    /**
     * '@-moz-document' ([^{\n]+)
     */
  
    mozdocument: function() {
      var captures;
      if (captures = /^@-moz-document[ \t]*([^\/{\n]+)/.exec(this.str)) {
        this.skip(captures);
        return new Token('-moz-document', captures[1].trim());
      }
    },
  
    /**
     * '@block'
     */
  
    atblock: function() {
      var captures;
      if (captures = /^@block[ \t]*/.exec(this.str)) {
        this.skip(captures);
        return new Token('atblock');
      }
    },
  
    /**
     * '@scope' ([^{\n]+)
     */
    
    scope: function() {
      var captures;
      if (captures = /^@scope[ \t]*([^\/{\n]+)/.exec(this.str)) {
        this.skip(captures);
        return new Token('scope', captures[1].trim());
      }
    },
  
    /**
     * '@' ('import' | 'require' | 'keyframes' | 'charset' | 'page' | 'font-face')
     */
    
    atrule: function() {
      var captures;
      if (captures = /^@(import|require|(?:-(\w+)-)?keyframes|charset|font-face|page)[ \t]*/.exec(this.str)) {
        this.skip(captures);
        var vendor = captures[2]
          , type = captures[1];
        if (vendor) type = 'keyframes';
        return new Token(type, vendor);
      }
    },
  
    /**
     * '//' *
     */
    
    comment: function() {
      // Single line
      if ('/' == this.str[0] && '/' == this.str[1]) {
        var end = this.str.indexOf('\n');
        if (-1 == end) end = this.str.length;
        this.skip(end);
        return this.advance();
      }
  
      // Multi-line
      if ('/' == this.str[0] && '*' == this.str[1]) {
        var end = this.str.indexOf('*/');
        if (-1 == end) end = this.str.length;
        var str = this.str.substr(0, end + 2)
          , lines = str.split('\n').length - 1
          , suppress = true;
        this.lineno += lines;
        this.skip(end + 2);
        // output
        if ('!' == str[2]) {
          str = str.replace('*!', '*');
          suppress = false;
        }
        return new Token('comment', new nodes.Comment(str, suppress));
      }
    },
  
    /**
     * 'true' | 'false'
     */
    
    boolean: function() {
      var captures;
      if (captures = /^(true|false)\b([ \t]*)/.exec(this.str)) {
        var val = nodes.Boolean('true' == captures[1]);
        this.skip(captures);
        var tok = new Token('boolean', val);
        tok.space = captures[2];
        return tok;
      }
    },
  
    /**
     * -*[_a-zA-Z$] [-\w\d$]* '('
     */
    
    function: function() {
      var captures;
      if (captures = /^(-*[_a-zA-Z$][-\w\d$]*)\(([ \t]*)/.exec(this.str)) {
        var name = captures[1];
        this.skip(captures);
        this.isURL = 'url' == name;
        var tok = new Token('function', new nodes.Ident(name));
        tok.space = captures[2];
        return tok;
      } 
    },
  
    /**
     * -*[_a-zA-Z$] [-\w\d$]*
     */
    
    ident: function() {
      var captures;
      if (captures = /^(@)?(-*[_a-zA-Z$][-\w\d$]*)/.exec(this.str)) {
        var at = captures[1]
          , name = captures[2]
          , id = new nodes.Ident(name);
        this.skip(captures);
        id.property = !! at;
        return new Token('ident', id);
      }
    },
    
    /**
     * '\n' ' '+
     */
  
    newline: function() {
      var captures, re;
  
      // we have established the indentation regexp
      if (this.indentRe){
        captures = this.indentRe.exec(this.str);
      // figure out if we are using tabs or spaces
      } else {
        // try tabs
        re = /^\n([\t]*)[ \t]*/;
        captures = re.exec(this.str);
  
        // nope, try spaces
        if (captures && !captures[1].length) {
          re = /^\n([ \t]*)/;
          captures = re.exec(this.str);
        }
  
        // established
        if (captures && captures[1].length) this.indentRe = re;
      }
  
  
      if (captures) {
        var tok
          , indents = captures[1].length;
  
        this.skip(captures);
        if (this.str[0] === ' ' || this.str[0] === '\t') {
          throw new errors.SyntaxError('Invalid indentation. You can use tabs or spaces to indent, but not both.');
        }
  
        // Reset state
        this.isVariable = false;
  
        // Blank line
        if ('\n' == this.str[0]) {
          ++this.lineno;
          return this.advance();
        }
  
        // Outdent
        if (this.indentStack.length && indents < this.indentStack[0]) {
          while (this.indentStack.length && this.indentStack[0] > indents) {
            this.stash.push(new Token('outdent'));
            this.indentStack.shift();
          }
          tok = this.stash.pop();
        // Indent
        } else if (indents && indents != this.indentStack[0]) {
          this.indentStack.unshift(indents);
          tok = new Token('indent');
        // Newline
        } else {
          tok = new Token('newline');
        }
  
        return tok;
      }
    },
  
    /**
     * '-'? (digit+ | digit* '.' digit+) unit
     */
  
    unit: function() {
      var captures;
      if (captures = unit.exec(this.str)) {
        this.skip(captures);
        var n = parseFloat(captures[2]);
        if ('-' == captures[1]) n = -n;
        var node = new nodes.Unit(n, captures[3]);
        node.raw = captures[0];
        return new Token('unit', node);
      }
    },
  
    /**
     * '"' [^"]+ '"' | "'"" [^']+ "'"
     */
  
    string: function() {
      var captures;
      if (captures = /^("[^"]*"|'[^']*')[ \t]*/.exec(this.str)) {
        var str = captures[1]
          , quote = captures[0][0];
        this.skip(captures);
        str = str.slice(1,-1).replace(/\\n/g, '\n');
        return new Token('string', new nodes.String(str, quote));
      }
    },
  
    /**
     * #rrggbbaa | #rrggbb | #rgba | #rgb | #nn | #n
     */
  
    color: function() {
      return this.rrggbbaa()
        || this.rrggbb()
        || this.rgba()
        || this.rgb()
        || this.nn()
        || this.n()
    },
  
    /**
     * #n
     */
    
    n: function() {
      var captures;
      if (captures = /^#([a-fA-F0-9]{1})[ \t]*/.exec(this.str)) {
        this.skip(captures);
        var n = parseInt(captures[1] + captures[1], 16)
          , color = new nodes.RGBA(n, n, n, 1);
        color.raw = captures[0];
        return new Token('color', color); 
      }
    },
  
    /**
     * #nn
     */
    
    nn: function() {
      var captures;
      if (captures = /^#([a-fA-F0-9]{2})[ \t]*/.exec(this.str)) {
        this.skip(captures);
        var n = parseInt(captures[1], 16)
          , color = new nodes.RGBA(n, n, n, 1);
        color.raw = captures[0];
        return new Token('color', color); 
      }
    },
  
    /**
     * #rgb
     */
    
    rgb: function() {
      var captures;
      if (captures = /^#([a-fA-F0-9]{3})[ \t]*/.exec(this.str)) {
        this.skip(captures);
        var rgb = captures[1]
          , r = parseInt(rgb[0] + rgb[0], 16)
          , g = parseInt(rgb[1] + rgb[1], 16)
          , b = parseInt(rgb[2] + rgb[2], 16)
          , color = new nodes.RGBA(r, g, b, 1);
        color.raw = captures[0];
        return new Token('color', color); 
      }
    },
    
    /**
     * #rgba
     */
    
    rgba: function() {
      var captures;
      if (captures = /^#([a-fA-F0-9]{4})[ \t]*/.exec(this.str)) {
        this.skip(captures);
        var rgb = captures[1]
          , r = parseInt(rgb[0] + rgb[0], 16)
          , g = parseInt(rgb[1] + rgb[1], 16)
          , b = parseInt(rgb[2] + rgb[2], 16)
          , a = parseInt(rgb[3] + rgb[3], 16)
          , color = new nodes.RGBA(r, g, b, a/255);
        color.raw = captures[0];
        return new Token('color', color); 
      }
    },
    
    /**
     * #rrggbb
     */
    
    rrggbb: function() {
      var captures;
      if (captures = /^#([a-fA-F0-9]{6})[ \t]*/.exec(this.str)) {
        this.skip(captures);
        var rgb = captures[1]
          , r = parseInt(rgb.substr(0, 2), 16)
          , g = parseInt(rgb.substr(2, 2), 16)
          , b = parseInt(rgb.substr(4, 2), 16)
          , color = new nodes.RGBA(r, g, b, 1);
        color.raw = captures[0];
        return new Token('color', color); 
      }
    },
    
    /**
     * #rrggbbaa
     */
    
    rrggbbaa: function() {
      var captures;
      if (captures = /^#([a-fA-F0-9]{8})[ \t]*/.exec(this.str)) {
        this.skip(captures);
        var rgb = captures[1]
          , r = parseInt(rgb.substr(0, 2), 16)
          , g = parseInt(rgb.substr(2, 2), 16)
          , b = parseInt(rgb.substr(4, 2), 16)
          , a = parseInt(rgb.substr(6, 2), 16)
          , color = new nodes.RGBA(r, g, b, a/255);
        color.raw = captures[0];
        return new Token('color', color); 
      }
    },
    
    /**
     * [^\n,;]+
     */
    
    selector: function() {
      var captures;
      if (captures = /^.*?(?=\/\/(?![^\[]*\])|[,\n{])/.exec(this.str)) {
        var selector = captures[0];
        this.skip(captures);
        return new Token('selector', selector);
      }
    }
  };
  