IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    lua metatable使用和源码分析(二)

    hoterran发表于 2012-02-05 15:09:30
    love 0

    表是如何调度到元表的呢,上篇博文的例子我们看到当键值未能被查找到之后会调用__index事件对应的函数.现在举一个”__add”事件来遍历重要环节的源码.当然从luaV_execute开始.

    lvm.c
    =============
    void luaV_execute (lua_State *L, int nexeccalls) {
        ....
        switch (GET_OPCODE(i)) {
            case OP_ADD: {
                arith_op(luai_numadd, TM_ADD);
                continue;
            }
    ....

    arith_op是个宏,会针对操作数进行类型判断,如果是数字类型就会立即进行相加,这就是为什么基本类型即便设置了加减等事件函数后还是没有效果,例如你为数字的加法设置了__add事件后数字相加不会掉用这个元表方法,后面会举一个例子.
    继续当发现操作数字为非数字类型后,就会调用Airth函数.

    #define arith_op(op,tm) { \
            TValue *rb = RKB(i); \
            TValue *rc = RKC(i); \
            if (ttisnumber(rb) && ttisnumber(rc)) { \
              lua_Number nb = nvalue(rb), nc = nvalue(rc); \
              setnvalue(ra, op(nb, nc)); \
            } \
            else \
              Protect(Arith(L, ra, rb, rc, tm)); \
          }

    Airth会对操作数再进行一次努力试图把其转化为数字,这样某些字符串也会被转换成数字相加.如果无法转化成数字则会尝试元表调用call_binTM函数.

    static void Arith (lua_State *L, StkId ra, const TValue *rb,
                       const TValue *rc, TMS op) {
      TValue tempb, tempc;
      const TValue *b, *c;
      if ((b = luaV_tonumber(rb, &tempb;)) != NULL &&
          (c = luaV_tonumber(rc, &tempc;)) != NULL) {
        lua_Number nb = nvalue(b), nc = nvalue(c);
        switch (op) {
          case TM_ADD: setnvalue(ra, luai_numadd(nb, nc)); break;
          case TM_SUB: setnvalue(ra, luai_numsub(nb, nc)); break;
          case TM_MUL: setnvalue(ra, luai_nummul(nb, nc)); break;
          case TM_DIV: setnvalue(ra, luai_numdiv(nb, nc)); break;
          case TM_MOD: setnvalue(ra, luai_nummod(nb, nc)); break;
          case TM_POW: setnvalue(ra, luai_numpow(nb, nc)); break;
          case TM_UNM: setnvalue(ra, luai_numunm(nb)); break;
          default: lua_assert(0); break;
        }
      }
      else if (!call_binTM(L, rb, rc, ra, op))
        luaG_aritherror(L, rb, rc);
    }

    call_binTM函数会对操作数进行尝试获取获取元表再根据事件名字(这里是__add)获取事件函数,如果获取成功则把操作数字压栈并调用这个事件函数.

    static int call_binTM (lua_State *L, const TValue *p1, const TValue *p2,
                           StkId res, TMS event) {
      const TValue *tm = luaT_gettmbyobj(L, p1, event);  /* try first operand */
      if (ttisnil(tm))
        tm = luaT_gettmbyobj(L, p2, event);  /* try second operand */
      if (ttisnil(tm)) return 0;
      callTMres(L, res, tm, p1, p2);
      return 1;
    }

    好了以上就是调度元表函数的过程.

    我们再来看看最常见的__index,__new_index函数是怎么被调用的,还是luaV_execute.

    lvm.c
    =====================
    
          case OP_GETTABLE: {
            Protect(luaV_gettable(L, RB(i), RKC(i), ra));
            continue;
          }
    ....
          case OP_SETTABLE: {
            Protect(luaV_settable(L, ra, RKB(i), RKC(i)));
            continue;
          }

    我们来看查找表,先进行常规的键值查找,如果找到则返回,如果键值未被发现则会查找TM_INDEX对应的元表事件函数.

    void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) {
      int loop;
      for (loop = 0; loop < MAXTAGLOOP; loop++) {     const TValue *tm;     if (ttistable(t)) {  /* `t' is a table? */       Table *h = hvalue(t);       const TValue *res = luaH_get(h, key); /* do a primitive get */       if (!ttisnil(res) ||  /* result is no nil? */           (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */
            setobj2s(L, val, res);
            return;
          }
          /* else will try the tag method, cant find so try metamethod */
        }
        else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX)))
          luaG_typeerror(L, t, "index");
        if (ttisfunction(tm)) {
          callTMres(L, val, tm, t, key);
          return;
        }
        t = tm;  /* else repeat with `tm' */
      }
      luaG_runerror(L, "loop in gettable");
    }

    luaT_gettmbyobj先查找到元表再查找对应的事件函数

    const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event) {
      Table *mt;
      switch (ttype(o)) {
        case LUA_TTABLE:
          mt = hvalue(o)->metatable;
          break;
        case LUA_TUSERDATA:
          mt = uvalue(o)->metatable;
          break;
        default:
          mt = G(L)->mt[ttype(o)];
      }
      return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject);
    }

    回到luaV_gettable找到这个事件函数后就调用callTMres,参数压栈执行函数.

    这就是元表事件函数的调用过程,接下来会讲解元表的其它用法,未完待续.



沪ICP备19023445号-2号
友情链接