text: var _ = require("../util");
  
  var config = require("../config");
  var node = require("./node");
  var Lexer = require("./Lexer");
  var varName = _.varName;
  var ctxName = _.ctxName;
  var extName = _.extName;
  var isPath = _.makePredicate("STRING IDENT NUMBER");
  var isKeyWord = _.makePredicate("true false undefined null this Array Date JSON Math NaN RegExp decodeURI decodeURIComponent encodeURI encodeURIComponent parseFloat parseInt Object");
  var isInvalidTag = _.makePredicate("script style");
  var isLastBind = /\.bind$/;
  
  
  
  function Parser(input, opts){
    opts = opts || {};
  
    this.input = input;
    this.tokens = new Lexer(input, opts).lex();
    this.pos = 0;
    this.length = this.tokens.length;
  }
  
  
  var op = Parser.prototype;
  
  
  op.parse = function(){
    this.pos = 0;
    var res= this.program();
    if(this.ll().type === 'TAG_CLOSE'){
      this.error("You may got a unclosed Tag")
    }
    return res;
  }
  
  op.ll =  function(k){
    k = k || 1;
    if(k < 0) k = k + 1;
    var pos = this.pos + k - 1;
    if(pos > this.length - 1){
        return this.tokens[this.length-1];
    }
    return this.tokens[pos];
  }
    // lookahead
  op.la = function(k){
    return (this.ll(k) || '').type;
  }
  
  op.match = function(type, value){
    var ll;
    if(!(ll = this.eat(type, value))){
      ll  = this.ll();
      this.error('expect [' + type + (value == null? '':':'+ value) + ']" -> got "[' + ll.type + (value==null? '':':'+ll.value) + ']', ll.pos)
    }else{
      return ll;
    }
  }
  
  op.error = function(msg, pos){
    msg =  "\n【 parse failed 】 " + msg +  ':\n\n' + _.trackErrorPos(this.input, typeof pos === 'number'? pos: this.ll().pos||0);
    throw new Error(msg);
  }
  
  op.next = function(k){
    k = k || 1;
    this.pos += k;
  }
  op.eat = function(type, value){
    var ll = this.ll();
    if(typeof type !== 'string'){
      for(var len = type.length ; len--;){
        if(ll.type === type[len]) {
          this.next();
          return ll;
        }
      }
    }else{
      if( ll.type === type && (typeof value === 'undefined' || ll.value === value) ){
         this.next();
         return ll;
      }
    }
    return false;
  }
  
  // program
  //  :EOF
  //  | (statement)* EOF
  op.program = function(isAttr){
    var statements = [],  ll = this.ll();
    while(ll.type !== 'EOF' && ll.type !=='TAG_CLOSE'){
  
      statements.push(this.statement());
      ll = this.ll();
      // {~ <div></div>}
      if( isAttr && ll.type === 'END'){
        this.next();
        return node.body(statements)
      }
    }
    // if(ll.type === 'TAG_CLOSE') this.error("You may have unmatched Tag")
    return statements;
  }
  
  // statement
  //  : xml
  //  | jst
  //  | text
  var rRN = /\r\n/g;
  op.statement = function(){
    var ll = this.ll();
    switch(ll.type){
      case 'NAME':
      case 'TEXT':
        var text = ll.value;
        this.next();
        while(ll = this.eat(['NAME', 'TEXT'])){
          text += ll.value;
        }
        return node.text(text.replace(rRN, '\n'));
      case 'TAG_OPEN':
        return this.xml();
      case 'OPEN': 
        return this.directive();
      case 'EXPR_OPEN':
        return this.interplation();
      default:
        this.error('Unexpected token: '+ this.la())
    }
  }
  
  // xml 
  // stag statement* TAG_CLOSE?(if self-closed tag)
  op.xml = function(){
    var name, attrs, children, selfClosed;
    name = this.match('TAG_OPEN').value;
  
    if( isInvalidTag(name)){
      this.error('Invalid Tag: ' + name);
    }
    attrs = this.attrs();
    selfClosed = this.eat('/')
    this.match('>');
    if( !selfClosed && !_.isVoidTag(name) ){
      children = this.program();
      if(!this.eat('TAG_CLOSE', name)) this.error('expect </'+name+'> got'+ 'no matched closeTag')
    }
    return node.element(name, attrs, children);
  }
  
  // xentity
  //  -rule(wrap attribute)
  //  -attribute
  //
  // __example__
  //  name = 1 |  
  //  ng-hide |
  //  on-click={{}} | 
  //  {{#if name}}on-click={{xx}}{{#else}}on-tap={{}}{{/if}}
  
  op.xentity = function(ll){
    var name = ll.value, value, modifier;
    if(ll.type === 'NAME'){
      //@ only for test
      if(~name.indexOf('.')){
        var tmp = name.split('.');
        name = tmp[0];
        modifier = tmp[1]
  
      }
      if( this.eat("=") ) value = this.attvalue(modifier);
      return node.attribute( name, value, modifier );
    }else{
      if( name !== 'if') this.error("current version. ONLY RULE #if #else #elseif is valid in tag, the rule #" + name + ' is invalid');
      return this['if'](true);
    }
  
  }
  
  // stag     ::=    '<' Name (S attr)* S? '>'  
  // attr    ::=     Name Eq attvalue
  op.attrs = function(isAttribute){
    var eat
    if(!isAttribute){
      eat = ["NAME", "OPEN"]
    }else{
      eat = ["NAME"]
    }
  
    var attrs = [], ll;
    while (ll = this.eat(eat)){
      attrs.push(this.xentity( ll ))
    }
    return attrs;
  }
  
  // attvalue
  //  : STRING  
  //  | NAME
  op.attvalue = function(mdf){
    var ll = this.ll();
    switch(ll.type){
      case "NAME":
      case "UNQ":
      case "STRING":
        this.next();
        var value = ll.value;
        return value;
      case "EXPR_OPEN":
        return this.interplation();
      case "BODY_OPEN":
        this.next();
        return this.program(true);
      default:
        this.error('Unexpected token: '+ this.la())
    }
  }
  
  
  // {{#}}
  op.directive = function(){
    var name = this.ll().value;
    this.next();
    if(typeof this[name] === 'function'){
      return this[name]()
    }else{
      this.error('Undefined directive['+ name +']');
    }
  }
  
  
  
  
  
  // {{}}
  op.interplation = function(){
    this.match('EXPR_OPEN');
    var res = this.expression(true);
    this.match('END');
    return res;
  }
  
  // {{~}}
  op.inc = op.include = function(){
    var content = this.expression();
    this.match('END');
    return node.template(content);
  }
  
  // {{#if}}
  op["if"] = function(tag){
    var test = this.expression();
    var consequent = [], alternate=[];
  
    var container = consequent;
    var statement = !tag? "statement" : "attrs";
  
    this.match('END');
  
    var ll, close;
    while( ! (close = this.eat('CLOSE')) ){
      ll = this.ll();
      if( ll.type === 'OPEN' ){
        switch( ll.value ){
          case 'else':
            container = alternate;
            this.next();
            this.match( 'END' );
            break;
          case 'elseif':
            this.next();
            alternate.push( this["if"](tag) );
            return node['if']( test, consequent, alternate );
          default:
            container.push( this[statement](true) );
        }
      }else{
        container.push(this[statement](true));
      }
    }
    // if statement not matched
    if(close.value !== "if") this.error('Unmatched if directive')
    return node["if"](test, consequent, alternate);
  }
  
  
  // @mark   mustache syntax have natrure dis, canot with expression
  // {{#list}}
  op.list = function(){
    // sequence can be a list or hash
    var sequence = this.expression(), variable, ll, track;
    var consequent = [], alternate=[];
    var container = consequent;
  
    this.match('IDENT', 'as');
  
    variable = this.match('IDENT').value;
  
    if(this.eat('IDENT', 'by')){
      if(this.eat('IDENT', variable+'_key')){
        this.error('can not track by ' + variable + '_key in list statement');
      }
      if(this.eat('IDENT',variable + '_index')){
        track = true;
      }else{
        track = this.expression();
        if(track.constant){
          // true is means constant, we handle it just like xxx_index.
          track = true;
        }
      }
    }
  
    this.match('END');
  
    while( !(ll = this.eat('CLOSE')) ){
      if(this.eat('OPEN', 'else')){
        container =  alternate;
        this.match('END');
      }else{
        container.push(this.statement());
      }
    }
    
    if(ll.value !== 'list') this.error('expect ' + 'list got ' + '/' + ll.value + ' ', ll.pos );
    return node.list(sequence, variable, consequent, alternate, track);
  }
  
  
  op.expression = function(){
    var expression;
    if(this.eat('@(')){ //once bind
      expression = this.expr();
      expression.once = true;
      this.match(')')
    }else{
      expression = this.expr();
    }
    return expression;
  }
  
  op.expr = function(){
    this.depend = [];
  
    var buffer = this.filter()
  
    var body = buffer.get || buffer;
    var setbody = buffer.set;
    return node.expression(body, setbody, !this.depend.length, buffer.filters);
  }
  
  
  // filter
  // assign ('|' filtername[':' args]) * 
  op.filter = function(){
    var left = this.assign();
    var ll = this.eat('|');
    var buffer = [], filters,setBuffer, prefix,
      attr = "t", 
      set = left.set, get, 
      tmp = "";
  
    if(ll){
      if(set) {
        setBuffer = [];
        filters = [];
      }
  
      prefix = "(function(" + attr + "){";
  
      do{
        var filterName = this.match('IDENT').value;
        tmp = attr + " = " + ctxName + "._f_('" + filterName + "' ).get.call( "+_.ctxName +"," + attr ;
        if(this.eat(':')){
          tmp +=", "+ this.arguments("|").join(",") + ");"
        }else{
          tmp += ');'
        }
        buffer.push(tmp);
        
        if(set){
          // only in runtime ,we can detect  whether  the filter has a set function. 
          filters.push(filterName);
          setBuffer.unshift( tmp.replace(" ).get.call", " ).set.call") );
        }
  
      }while(ll = this.eat('|'));
      buffer.push("return " + attr );
      setBuffer && setBuffer.push("return " + attr);
  
      get =  prefix + buffer.join("") + "})("+left.get+")";
      // we call back to value.
      if(setBuffer){
        // change _ss__(name, _p_) to _s__(name, filterFn(_p_));
        set = set.replace(_.setName, 
          prefix + setBuffer.join("") + "})("+　_.setName　+")" );
  
      }
      // the set function is depend on the filter definition. if it have set method, the set will work
      var ret = getset(get, set);
      ret.filters = filters;
      return ret;
    }
    return left;
  }
  
  // assign
  // left-hand-expr = condition
  op.assign = function(){
    var left = this.condition(), ll;
    if(ll = this.eat(['=', '+=', '-=', '*=', '/=', '%='])){
      if(!left.set) this.error('invalid lefthand expression in assignment expression');
      return getset( left.set.replace( "," + _.setName, "," + this.condition().get ).replace("'='", "'"+ll.type+"'"), left.set);
      // return getset('(' + left.get + ll.type  + this.condition().get + ')', left.set);
    }
    return left;
  }
  
  // or
  // or ? assign : assign
  op.condition = function(){
  
    var test = this.or();
    if(this.eat('?')){
      return getset([test.get + "?", 
        this.assign().get, 
        this.match(":").type, 
        this.assign().get].join(""));
    }
  
    return test;
  }
  
  // and
  // and && or
  op.or = function(){
  
    var left = this.and();
  
    if(this.eat('||')){
      return getset(left.get + '||' + this.or().get);
    }
  
    return left;
  }
  // equal
  // equal && and
  op.and = function(){
  
    var left = this.equal();
  
    if(this.eat('&&')){
      return getset(left.get + '&&' + this.and().get);
    }
    return left;
  }
  // relation
  // 
  // equal == relation
  // equal != relation
  // equal === relation
  // equal !== relation
  op.equal = function(){
    var left = this.relation(), ll;
    // @perf;
    if( ll = this.eat(['==','!=', '===', '!=='])){
      return getset(left.get + ll.type + this.equal().get);
    }
    return left
  }
  // relation < additive
  // relation > additive
  // relation <= additive
  // relation >= additive
  // relation in additive
  op.relation = function(){
    var left = this.additive(), ll;
    // @perf
    if(ll = (this.eat(['<', '>', '>=', '<=']) || this.eat('IDENT', 'in') )){
      return getset(left.get + ll.value + this.relation().get);
    }
    return left
  }
  // additive :
  // multive
  // additive + multive
  // additive - multive
  op.additive = function(){
    var left = this.multive() ,ll;
    if(ll= this.eat(['+','-']) ){
      return getset(left.get + ll.value + this.additive().get);
    }
    return left
  }
  // multive :
  // unary
  // multive * unary
  // multive / unary
  // multive % unary
  op.multive = function(){
    var left = this.range() ,ll;
    if( ll = this.eat(['*', '/' ,'%']) ){
      return getset(left.get + ll.type + this.multive().get);
    }
    return left;
  }
  
  op.range = function(){
    var left = this.unary(), ll, right;
  
    if(ll = this.eat('..')){
      right = this.unary();
      var body = 
        "(function(start,end){var res = [],step=end>start?1:-1; for(var i = start; end>start?i <= end: i>=end; i=i+step){res.push(i); } return res })("+left.get+","+right.get+")"
      return getset(body);
    }
  
    return left;
  }
  
  
  
  // lefthand
  // + unary
  // - unary
  // ~ unary
  // ! unary
  op.unary = function(){
    var ll;
    if(ll = this.eat(['+','-','~', '!'])){
      return getset('(' + ll.type + this.unary().get + ')') ;
    }else{
      return this.member()
    }
  }
  
  // call[lefthand] :
  // member args
  // member [ expression ]
  // member . ident  
  
  op.member = function(base, last, pathes, prevBase){
    var ll, path;
  
  
    var onlySimpleAccessor = false;
    if(!base){ //first
      path = this.primary();
      var type = typeof path;
      if(type === 'string'){ 
        pathes = [];
        pathes.push( path );
        last = path;
        base = ctxName + "._sg_('" + path + "', " + varName + ", " + extName + ")";
        onlySimpleAccessor = true;
      }else{ //Primative Type
        if(path.get === 'this'){
          base = ctxName;
          pathes = ['this'];
        }else{
          pathes = null;
          base = path.get;
        }
      }
    }else{ // not first enter
      if(typeof last === 'string' && isPath( last) ){ // is valid path
        pathes.push(last);
      }else{
        if(pathes && pathes.length) this.depend.push(pathes);
        pathes = null;
      }
    }
    if(ll = this.eat(['[', '.', '('])){
      switch(ll.type){
        case '.':
            // member(object, property, computed)
          var tmpName = this.match('IDENT').value;
          prevBase = base;
          if( this.la() !== "(" ){ 
            base = ctxName + "._sg_('" + tmpName + "', " + base + ")";
          }else{
            base += "." + tmpName ;
          }
          return this.member( base, tmpName, pathes,  prevBase);
        case '[':
            // member(object, property, computed)
          path = this.assign();
          prevBase = base;
          if( this.la() !== "(" ){ 
          // means function call, we need throw undefined error when call function
          // and confirm that the function call wont lose its context
            base = ctxName + "._sg_(" + path.get + ", " + base + ")";
          }else{
            base += "[" + path.get + "]";
          }
          this.match(']')
          return this.member(base, path, pathes, prevBase);
        case '(':
          // call(callee, args)
  
          base = base.replace(isLastBind, '.__bind__')
          var args = this.arguments().join(',');
  
          base =  base+"(" + args +")";
          this.match(')')
          return this.member(base, null, pathes);
      }
    }
    if( pathes && pathes.length ) this.depend.push( pathes );
    var res =  {get: base};
    if(last){
      res.set = ctxName + "._ss_(" + 
          (last.get? last.get : "'"+ last + "'") + 
          ","+ _.setName + ","+ 
          (prevBase?prevBase:_.varName) + 
          ", '=', "+ ( onlySimpleAccessor? 1 : 0 ) + ")";
    
    }
    return res;
  }
  
  /**
   * 
   */
  op.arguments = function(end){
    end = end || ')'
    var args = [];
    do{
      if(this.la() !== end){
        args.push(this.assign().get)
      }
    }while( this.eat(','));
    return args
  }
  
  
  // primary :
  // this 
  // ident
  // literal
  // array
  // object
  // ( expression )
  
  op.primary = function(){
    var ll = this.ll();
    switch(ll.type){
      case "{":
        return this.object();
      case "[":
        return this.array();
      case "(":
        return this.paren();
      // literal or ident
      case 'STRING':
        this.next();
        var value = "" + ll.value;
        var quota = ~value.indexOf("'")? "\"": "'" ;
        return getset(quota + value + quota);
      case 'NUMBER':
        this.next();
        return getset( "" + ll.value );
      case "IDENT":
        this.next();
        if(isKeyWord(ll.value)){
          return getset( ll.value );
        }
        return ll.value;
      default: 
        this.error('Unexpected Token: ' + ll.type);
    }
  }
  
  // object
  //  {propAssign [, propAssign] * [,]}
  
  // propAssign
  //  prop : assign
  
  // prop
  //  STRING
  //  IDENT
  //  NUMBER
  
  op.object = function(){
    var code = [this.match('{').type];
  
    var ll = this.eat( ['STRING', 'IDENT', 'NUMBER'] );
    while(ll){
      code.push("'" + ll.value + "'" + this.match(':').type);
      var get = this.assign().get;
      code.push(get);
      ll = null;
      if(this.eat(",") && (ll = this.eat(['STRING', 'IDENT', 'NUMBER'])) ) code.push(",");
    }
    code.push(this.match('}').type);
    return {get: code.join("")}
  }
  
  // array
  // [ assign[,assign]*]
  op.array = function(){
    var code = [this.match('[').type], item;
    if( this.eat("]") ){
  
       code.push("]");
    } else {
      while(item = this.assign()){
        code.push(item.get);
        if(this.eat(',')) code.push(",");
        else break;
      }
      code.push(this.match(']').type);
    }
    return {get: code.join("")};
  }
  
  // '(' expression ')'
  op.paren = function(){
    this.match('(');
    var res = this.filter()
    res.get = '(' + res.get + ')';
    res.set = res.set;
    this.match(')');
    return res;
  }
  
  function getset(get, set){
    return {
      get: get,
      set: set
    }
  }
  
  
  
  module.exports = Parser;
  