Вход Регистрация
Файл: error-kitty/node_modules/mocha/node_modules/jade/lib/parser.js
Строк: 572
<?php
/*!
 * Jade - Parser
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Lexer = require('./lexer')
  , 
nodes = require('./nodes');

/**
 * Initialize `Parser` with the given input `str` and `filename`.
 *
 * @param {String} str
 * @param {String} filename
 * @param {Object} options
 * @api public
 */

var Parser exports module.exports = function Parser(strfilenameoptions){
  
this.input str;
  
this.lexer = new Lexer(stroptions);
  
this.filename filename;
  
this.blocks = {};
  
this.mixins = {};
  
this.options options;
  
this.contexts = [this];
};

/**
 * Tags that may not contain tags.
 */

var textOnly exports.textOnly = ['script''style'];

/**
 * Parser prototype.
 */

Parser.prototype = {

  
/**
   * Push `parser` onto the context stack,
   * or pop and return a `Parser`.
   */

  
context: function(parser){
    if (
parser) {
      
this.contexts.push(parser);
    } else {
      return 
this.contexts.pop();
    }
  },

  
/**
   * Return the next token object.
   *
   * @return {Object}
   * @api private
   */

  
advance: function(){
    return 
this.lexer.advance();
  },

  
/**
   * Skip `n` tokens.
   *
   * @param {Number} n
   * @api private
   */

  
skip: function(n){
    while (
n--) this.advance();
  },
  
  
/**
   * Single token lookahead.
   *
   * @return {Object}
   * @api private
   */
  
  
peek: function() {
    return 
this.lookahead(1);
  },
  
  
/**
   * Return lexer lineno.
   *
   * @return {Number}
   * @api private
   */
  
  
line: function() {
    return 
this.lexer.lineno;
  },
  
  
/**
   * `n` token lookahead.
   *
   * @param {Number} n
   * @return {Object}
   * @api private
   */
  
  
lookahead: function(n){
    return 
this.lexer.lookahead(n);
  },
  
  
/**
   * Parse input returning a string of js for evaluation.
   *
   * @return {String}
   * @api public
   */
  
  
parse: function(){
    var 
block = new nodes.Blockparser;
    
block.line this.line();

    while (
'eos' != this.peek().type) {
      if (
'newline' == this.peek().type) {
        
this.advance();
      } else {
        
block.push(this.parseExpr());
      }
    }

    if (
parser this.extending) {
      
this.context(parser);
      var 
ast parser.parse();
      
this.context();
      
// hoist mixins
      
for (var name in this.mixins)
        
ast.unshift(this.mixins[name]);
      return 
ast;
    }

    return 
block;
  },
  
  
/**
   * Expect the given type, or throw an exception.
   *
   * @param {String} type
   * @api private
   */
  
  
expect: function(type){
    if (
this.peek().type === type) {
      return 
this.advance();
    } else {
      throw new 
Error('expected "' type '", but got "' this.peek().type '"');
    }
  },
  
  
/**
   * Accept the given `type`.
   *
   * @param {String} type
   * @api private
   */
  
  
accept: function(type){
    if (
this.peek().type === type) {
      return 
this.advance();
    }
  },
  
  
/**
   *   tag
   * | doctype
   * | mixin
   * | include
   * | filter
   * | comment
   * | text
   * | each
   * | code
   * | yield
   * | id
   * | class
   * | interpolation
   */
  
  
parseExpr: function(){
    switch (
this.peek().type) {
      case 
'tag':
        return 
this.parseTag();
      case 
'mixin':
        return 
this.parseMixin();
      case 
'block':
        return 
this.parseBlock();
      case 
'case':
        return 
this.parseCase();
      case 
'when':
        return 
this.parseWhen();
      case 
'default':
        return 
this.parseDefault();
      case 
'extends':
        return 
this.parseExtends();
      case 
'include':
        return 
this.parseInclude();
      case 
'doctype':
        return 
this.parseDoctype();
      case 
'filter':
        return 
this.parseFilter();
      case 
'comment':
        return 
this.parseComment();
      case 
'text':
        return 
this.parseText();
      case 
'each':
        return 
this.parseEach();
      case 
'code':
        return 
this.parseCode();
      case 
'call':
        return 
this.parseCall();
      case 
'interpolation':
        return 
this.parseInterpolation();
      case 
'yield':
        
this.advance();
        var 
block = new nodes.Block;
        
block.yield = true;
        return 
block;
      case 
'id':
      case 
'class':
        var 
tok this.advance();
        
this.lexer.defer(this.lexer.tok('tag''div'));
        
this.lexer.defer(tok);
        return 
this.parseExpr();
      default:
        throw new 
Error('unexpected token "' this.peek().type '"');
    }
  },
  
  
/**
   * Text
   */
  
  
parseText: function(){
    var 
tok this.expect('text')
      , 
node = new nodes.Text(tok.val);
    
node.line this.line();
    return 
node;
  },

  
/**
   *   ':' expr
   * | block
   */

  
parseBlockExpansion: function(){
    if (
':' == this.peek().type) {
      
this.advance();
      return new 
nodes.Block(this.parseExpr());
    } else {
      return 
this.block();
    }
  },

  
/**
   * case
   */

  
parseCase: function(){
    var 
val this.expect('case').val
      
node = new nodes.Case(val);
    
node.line this.line();
    
node.block this.block();
    return 
node;
  },

  
/**
   * when
   */

  
parseWhen: function(){
    var 
val this.expect('when').val
    
return new nodes.Case.When(valthis.parseBlockExpansion());
  },
  
  
/**
   * default
   */

  
parseDefault: function(){
    
this.expect('default');
    return new 
nodes.Case.When('default'this.parseBlockExpansion());
  },

  
/**
   * code
   */
  
  
parseCode: function(){
    var 
tok this.expect('code')
      , 
node = new nodes.Code(tok.valtok.buffertok.escape)
      , 
block
      
1;
    
node.line this.line();
    while (
this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i;
    
block 'indent' == this.lookahead(i).type;
    if (
block) {
      
this.skip(i-1);
      
node.block this.block();
    }
    return 
node;
  },
  
  
/**
   * comment
   */
  
  
parseComment: function(){
    var 
tok this.expect('comment')
      , 
node;

    if (
'indent' == this.peek().type) {
      
node = new nodes.BlockComment(tok.valthis.block(), tok.buffer);
    } else {
      
node = new nodes.Comment(tok.valtok.buffer);
    }

    
node.line this.line();
    return 
node;
  },
  
  
/**
   * doctype
   */
  
  
parseDoctype: function(){
    var 
tok this.expect('doctype')
      , 
node = new nodes.Doctype(tok.val);
    
node.line this.line();
    return 
node;
  },
  
  
/**
   * filter attrs? text-block
   */
  
  
parseFilter: function(){
    var 
block
      
tok this.expect('filter')
      , 
attrs this.accept('attrs');

    
this.lexer.pipeless true;
    
block this.parseTextBlock();
    
this.lexer.pipeless false;

    var 
node = new nodes.Filter(tok.valblockattrs && attrs.attrs);
    
node.line this.line();
    return 
node;
  },
  
  
/**
   * tag ':' attrs? block
   */
  
  
parseASTFilter: function(){
    var 
block
      
tok this.expect('tag')
      , 
attrs this.accept('attrs');

    
this.expect(':');
    
block this.block();

    var 
node = new nodes.Filter(tok.valblockattrs && attrs.attrs);
    
node.line this.line();
    return 
node;
  },
  
  
/**
   * each block
   */
  
  
parseEach: function(){
    var 
tok this.expect('each')
      , 
node = new nodes.Each(tok.codetok.valtok.key);
    
node.line this.line();
    
node.block this.block();
    return 
node;
  },

  
/**
   * 'extends' name
   */

  
parseExtends: function(){
    var 
path = require('path')
      , 
fs = require('fs')
      , 
dirname path.dirname
      
basename path.basename
      
join path.join;

    if (!
this.filename)
      throw new 
Error('the "filename" option is required to extend templates');

    var 
path this.expect('extends').val.trim()
      , 
dir dirname(this.filename);

    var 
path join(dirpath '.jade')
      , 
str fs.readFileSync(path'utf8')
      , 
parser = new Parser(strpaththis.options);

    
parser.blocks this.blocks;
    
parser.contexts this.contexts;
    
this.extending parser;

    
// TODO: null node
    
return new nodes.Literal('');
  },

  
/**
   * 'block' name block
   */

  
parseBlock: function(){
    var 
block this.expect('block')
      , 
mode block.mode
      
name block.val.trim();

    
block 'indent' == this.peek().type
      
this.block()
      : new 
nodes.Block(new nodes.Literal(''));

    var 
prev this.blocks[name];

    if (
prev) {
      switch (
prev.mode) {
        case 
'append':
          
block.nodes block.nodes.concat(prev.nodes);
          
prev block;
          break;
        case 
'prepend':
          
block.nodes prev.nodes.concat(block.nodes);
          
prev block;
          break;
      }
    }

    
block.mode mode;
    return 
this.blocks[name] = prev || block;
  },

  
/**
   * include block?
   */

  
parseInclude: function(){
    var 
path = require('path')
      , 
fs = require('fs')
      , 
dirname path.dirname
      
basename path.basename
      
join path.join;

    var 
path this.expect('include').val.trim()
      , 
dir dirname(this.filename);

    if (!
this.filename)
      throw new 
Error('the "filename" option is required to use includes');

    
// no extension
    
if (!~basename(path).indexOf('.')) {
      
path += '.jade';
    }

    
// non-jade
    
if ('.jade' != path.substr(-5)) {
      var 
path join(dirpath)
        , 
str fs.readFileSync(path'utf8');
      return new 
nodes.Literal(str);
    }

    var 
path join(dirpath)
      , 
str fs.readFileSync(path'utf8')
     , 
parser = new Parser(strpaththis.options);
    
parser.blocks this.blocks;
    
parser.mixins this.mixins;

    
this.context(parser);
    var 
ast parser.parse();
    
this.context();
    
ast.filename path;

    if (
'indent' == this.peek().type) {
      
ast.includeBlock().push(this.block());
    }

    return 
ast;
  },

  
/**
   * call ident block
   */

  
parseCall: function(){
    var 
tok this.expect('call')
      , 
name tok.val
      
args tok.args
      
mixin = new nodes.Mixin(nameargs, new nodes.Blocktrue);

    
this.tag(mixin);
    if (
mixin.block.isEmpty()) mixin.block null;
    return 
mixin;
  },

  
/**
   * mixin block
   */

  
parseMixin: function(){
    var 
tok this.expect('mixin')
      , 
name tok.val
      
args tok.args
      
mixin;

    
// definition
    
if ('indent' == this.peek().type) {
      
mixin = new nodes.Mixin(nameargsthis.block(), false);
      
this.mixins[name] = mixin;
      return 
mixin;
    
// call
    
} else {
      return new 
nodes.Mixin(nameargsnulltrue);
    }
  },

  
/**
   * indent (text | newline)* outdent
   */

  
parseTextBlock: function(){
    var 
block = new nodes.Block;
    
block.line this.line();
    var 
spaces this.expect('indent').val;
    if (
null == this._spacesthis._spaces spaces;
    var 
indent = Array(spaces this._spaces 1).join(' ');
    while (
'outdent' != this.peek().type) {
      switch (
this.peek().type) {
        case 
'newline':
          
this.advance();
          break;
        case 
'indent':
          
this.parseTextBlock().nodes.forEach(function(node){
            
block.push(node);
          });
          break;
        default:
          var 
text = new nodes.Text(indent this.advance().val);
          
text.line this.line();
          
block.push(text);
      }
    }

    if (
spaces == this._spacesthis._spaces null;
    
this.expect('outdent');
    return 
block;
  },

  
/**
   * indent expr* outdent
   */
  
  
block: function(){
    var 
block = new nodes.Block;
    
block.line this.line();
    
this.expect('indent');
    while (
'outdent' != this.peek().type) {
      if (
'newline' == this.peek().type) {
        
this.advance();
      } else {
        
block.push(this.parseExpr());
      }
    }
    
this.expect('outdent');
    return 
block;
  },

  
/**
   * interpolation (attrs | class | id)* (text | code | ':')? newline* block?
   */
  
  
parseInterpolation: function(){
    var 
tok this.advance();
    var 
tag = new nodes.Tag(tok.val);
    
tag.buffer true;
    return 
this.tag(tag);
  },

  
/**
   * tag (attrs | class | id)* (text | code | ':')? newline* block?
   */
  
  
parseTag: function(){
    
// ast-filter look-ahead
    
var 2;
    if (
'attrs' == this.lookahead(i).type) ++i;
    if (
':' == this.lookahead(i).type) {
      if (
'indent' == this.lookahead(++i).type) {
        return 
this.parseASTFilter();
      }
    }

    var 
tok this.advance()
      , 
tag = new nodes.Tag(tok.val);

    
tag.selfClosing tok.selfClosing;

    return 
this.tag(tag);
  },

  
/**
   * Parse tag.
   */

  
tag: function(tag){
    var 
dot;

    
tag.line this.line();

    
// (attrs | class | id)*
    
out:
      while (
true) {
        switch (
this.peek().type) {
          case 
'id':
          case 
'class':
            var 
tok this.advance();
            
tag.setAttribute(tok.type"'" tok.val "'");
            continue;
          case 
'attrs':
            var 
tok this.advance()
              , 
obj tok.attrs
              
escaped tok.escaped
              
names Object.keys(obj);

            if (
tok.selfClosingtag.selfClosing true;

            for (var 
0len names.lengthlen; ++i) {
              var 
name names[i]
                , 
val obj[name];
              
tag.setAttribute(namevalescaped[name]);
            }
            continue;
          default:
            break 
out;
        }
      }

    
// check immediate '.'
    
if ('.' == this.peek().val) {
      
dot tag.textOnly true;
      
this.advance();
    }

    
// (text | code | ':')?
    
switch (this.peek().type) {
      case 
'text':
        
tag.block.push(this.parseText());
        break;
      case 
'code':
        
tag.code this.parseCode();
        break;
      case 
':':
        
this.advance();
        
tag.block = new nodes.Block;
        
tag.block.push(this.parseExpr());
        break;
    }

    
// newline*
    
while ('newline' == this.peek().typethis.advance();

    
tag.textOnly tag.textOnly || ~textOnly.indexOf(tag.name);

    
// script special-case
    
if ('script' == tag.name) {
      var 
type tag.getAttribute('type');
      if (!
dot && type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) {
        tag.textOnly = false;
      }
    }

    // block?
    if ('indent' == this.peek().type) {
      if (tag.textOnly) {
        this.lexer.pipeless = true;
        tag.block = this.parseTextBlock();
        this.lexer.pipeless = false;
      } else {
        var block = this.block();
        if (tag.block) {
          for (var i = 0, len = block.nodes.length; i < len; ++i) {
            tag.block.push(block.nodes[i]);
          }
        } else {
          tag.block = block;
        }
      }
    }
    
    return tag;
  }
};
?>
Онлайн: 1
Реклама