text: module.exports = function(Velocity, utils) {
  
    'use strict';
  
    function getSize(obj) {
  
      if (utils.isArray(obj)) {
        return obj.length;
      } else if (utils.isObject(obj)) {
        return utils.keys(obj).length;
      }
  
      return undefined;
    }
  
    /**
     * unicode encode
     */
    function convert(str) {
  
      if (typeof str !== 'string') return str;
  
      var result = ""
      var escape = false
      var i, c, cstr;
  
      for (i = 0 ; i < str.length ; i++) {
        c = str.charAt(i);
        if ((' ' <= c && c <= '~') || (c === '\r') || (c === '\n')) {
          if (c === '&') {
            cstr = "&amp;"
            escape = true
          } else if (c === '"') {
            cstr = "&quot;"
            escape = true
          } else if (c === '<') {
            cstr = "&lt;"
            escape = true
          } else if (c === '>') {
            cstr = "&gt;"
            escape = true
          } else {
            cstr = c.toString()
          }
        } else {
          cstr = "&#" + c.charCodeAt().toString() + ";"
        }
  
        result = result + cstr
      }
  
      return escape ? result : str
    }
  
    function getter(base, property) {
      // get(1)
      if (typeof property === 'number') {
        return base[property];
      }
  
      var letter = property.charCodeAt(0);
      var isUpper = letter < 91;
      var ret = base[property];
  
      if (ret !== undefined) {
        return ret;
      }
  
      if (isUpper) {
        // Address => address
        property = String.fromCharCode(letter).toLowerCase() + property.slice(1);
      }
  
      if (!isUpper) {
        // address => Address
        property = String.fromCharCode(letter).toUpperCase() + property.slice(1);
      }
  
      return base[property];
    }
  
    utils.mixin(Velocity.prototype, {
      // 增加某些函数，不需要执行html转义
      addIgnoreEscpape: function(key) {
  
        if (!utils.isArray(key)) key = [key]
  
        utils.forEach(key, function(key) {
          this.config.unescape[key] = true
        }, this)
  
      },
  
      /**
       * 引用求值
       * @param {object} ast 结构来自velocity.yy
       * @param {bool} isVal 取值还是获取字符串，两者的区别在于，求值返回结果，求
       * 字符串，如果没有返回变量自身，比如$foo
       */
      getReferences: function(ast, isVal) {
  
        if (ast.prue) {
          var define = this.defines[ast.id];
          if (utils.isArray(define)) {
            return this._render(define);
          }
          if (ast.id in this.config.unescape) ast.prue = false;
        }
        var escape = this.config.escape;
  
        var isSilent = this.silence || ast.leader === "$!";
        var isfn     = ast.args !== undefined;
        var context  = this.context;
        var ret      = context[ast.id];
        var local    = this.getLocal(ast);
  
        var text = Velocity.Helper.getRefText(ast);
  
        if (text in context) {
          return (ast.prue && escape) ? convert(context[text]) : context[text];
        }
  
  
        if (ret !== undefined && isfn) {
          ret = this.getPropMethod(ast, context, ast);
        }
  
        if (local.isLocaled) ret = local['value'];
  
        if (ast.path) {
  
          utils.some(ast.path, function(property, i, len) {
  
            if (ret === undefined) {
              this._throw(ast, property);
            }
  
            // 第三个参数，返回后面的参数ast
            ret = this.getAttributes(property, ret, ast);
  
          }, this);
        }
  
        if (isVal && ret === undefined) {
          ret = isSilent ? '' : Velocity.Helper.getRefText(ast);
        }
  
        ret = (ast.prue && escape) ? convert(ret) : ret;
  
        return ret;
      },
  
      /**
       * 获取局部变量，在macro和foreach循环中使用
       */
      getLocal: function(ast) {
  
        var id = ast.id;
        var local = this.local;
        var ret = false;
  
        var isLocaled = utils.some(this.conditions, function(contextId) {
          var _local = local[contextId];
          if (id in _local) {
            ret = _local[id];
            return true;
          }
  
          return false;
        }, this);
  
        return {
          value: ret,
          isLocaled: isLocaled
        };
      },
      /**
       * $foo.bar 属性求值，最后面两个参数在用户传递的函数中用到
       * @param {object} property 属性描述，一个对象，主要包括id，type等定义
       * @param {object} baseRef 当前执行链结果，比如$a.b.c，第一次baseRef是$a,
       * 第二次是$a.b返回值
       * @private
       */
      getAttributes: function(property, baseRef, ast) {
        // fix #54
        if (baseRef === null || baseRef === undefined) {
          return undefined;
        }
  
        /**
         * type对应着velocity.yy中的attribute，三种类型: method, index, property
         */
        var type = property.type;
        var ret;
        var id = property.id;
        if (type === 'method') {
          ret = this.getPropMethod(property, baseRef, ast);
        } else if (type === 'property') {
          ret = baseRef[id];
        } else {
          ret = this.getPropIndex(property, baseRef);
        }
        return ret;
      },
  
      /**
       * $foo.bar[1] index求值
       * @private
       */
      getPropIndex: function(property, baseRef) {
        var ast = property.id;
        var key;
        if (ast.type === 'references') {
          key = this.getReferences(ast);
        } else if (ast.type === 'integer') {
          key = ast.value;
        } else {
          key = ast.value;
        }
  
        return baseRef[key];
      },
  
      /**
       * $foo.bar()求值
       */
      getPropMethod: function(property, baseRef, ast) {
  
        var id = property.id;
        var ret = '';
  
        // getter 处理
        if (id.indexOf('get') === 0 && !(id in baseRef)) {
          if (id.length === 3) {
            // get('address')
            ret = getter(baseRef, this.getLiteral(property.args[0]));
          } else {
            // getAddress()
            ret = getter(baseRef, id.slice(3));
          }
  
          return ret;
  
        // setter 处理
        } else if (id.indexOf('set') === 0 && !baseRef[id]) {
  
          baseRef[id.slice(3)] = this.getLiteral(property.args[0]);
          // $page.setName(123)
          baseRef.toString = function() { return ''; };
          return baseRef;
  
        } else if (id.indexOf('is') === 0 && !(id in baseRef)) {
  
          return getter(baseRef, id.slice(2));
        } else if (id === 'keySet' && !baseRef[id]) {
  
          return utils.keys(baseRef);
  
        } else if (id === 'entrySet' && !baseRef[id]) {
  
          ret = [];
          utils.forEach(baseRef, function(value, key) {
            ret.push({key: key, value: value});
          });
  
          return ret;
  
        } else if (id === 'size' && !baseRef[id]) {
  
          return getSize(baseRef);
        } else if (id === 'put' && !baseRef[id]) {
          return baseRef[this.getLiteral(property.args[0])] = this.getLiteral(property.args[1]);
        } else if (id === 'add' && !baseRef[id] && typeof baseRef.push === 'function') {
          return baseRef.push(this.getLiteral(property.args[0]));
        } else if (id === 'remove') {
  
          if (utils.isArray(baseRef)) {
            
            if (typeof index === 'number') {
              var index = this.getLiteral(property.args[0]);
            } else {
              var index = baseRef.indexOf(this.getLiteral(property.args[0]));
            }
  
            ret = baseRef[index];
            baseRef.splice(index, 1);
            return ret;
  
          } else if (utils.isObject(baseRef)) {
  
            ret = baseRef[this.getLiteral(property.args[0])];
            delete baseRef[this.getLiteral(property.args[0])];
            return ret; 
  
          }
          
          return undefined;
  
        } else if (id === 'subList' && !baseRef[id]) {
          return baseRef.slice(this.getLiteral(property.args[0]), this.getLiteral(property.args[1]));
        } else {
  
          ret = baseRef[id];
          var args = [];
  
          utils.forEach(property.args, function(exp) {
            args.push(this.getLiteral(exp));
          }, this);
  
          if (ret && ret.call) {
  
            var that = this;
  
            if(typeof baseRef === 'object' && baseRef){
              baseRef.eval = function() {
                return that.eval.apply(that, arguments);
              };
            }
  
            try {
              ret = ret.apply(baseRef, args);
            } catch (e) {
              var pos = ast.pos;
              var text = Velocity.Helper.getRefText(ast);
              var err = ' on ' + text + ' at L/N ' +
                pos.first_line + ':' + pos.first_column;
              e.name = '';
              e.message += err;
              throw new Error(e);
            }
  
          } else {
            this._throw(ast, property, 'TypeError');
            ret = undefined;
          }
        }
  
        return ret;
      },
  
      _throw: function(ast, property, errorName) {
        if (this.config.env !== 'development') {
          return;
        }
  
        var text = Velocity.Helper.getRefText(ast);
        var pos = ast.pos;
        var propertyName = property.type === 'index' ? property.id.value : property.id;
        var errorMsg = 'get property ' + propertyName + ' of undefined';
        if (errorName === 'TypeError') {
          errorMsg = propertyName + ' is not method';
        }
  
        errorMsg += '\n  at L/N ' + text + ' ' + pos.first_line + ':' + pos.first_column;
        var e = new Error(errorMsg);
        e.name = errorName || 'ReferenceError';
        throw e;
      }
    })
  
  }
  