Доступ к массиву пользовательских данных Lua и методы

Я пишу в C тип userdata для использования в Lua. Он также имеет некоторые свойства типа массива и различные методы. Прямо сейчас, если вы такого типа, я использую u:set(k,v) соответственно u:get(k) для доступа к данным и, например, u:sort() как метод. Для этого я установил __index к таблице, содержащей эти методы. Теперь, если я хочу получить доступ к данным с помощью u[k] = v или же u[k]Мне нужно установить __newindex а также __index в set соответственно get, Но тогда другие методы больше не доступны...

Какой лучший способ справиться с этим в C? Я предполагаю, что мне нужно написать функцию в C, чтобы зарегистрироваться как __index и как-то с этим справиться. Может быть, проверить, принадлежит ли ключ таблице методов Lua, и если это так, вызвать его.

Любая помощь / советы будут оценены. Я не нашел таких примеров, хотя это кажется очень естественным (для меня).

edit: добавлена ​​моя C версия решения в Lua, опубликованная в ответе ниже. Это более или менее прямой перевод, поэтому вся заслуга принадлежит @ gilles-gregoire.

Следующая функция C зарегистрирована как метаметод __index.

static int permL_index(lua_State *L) {
  struct perm **pp = luaL_checkudata(L, 1, PERM_MT);
  int i;

  luaL_getmetatable(L, PERM_MT);
  lua_pushvalue(L, 2);
  lua_rawget(L, -2);

  if ( lua_isnil(L, -1) ) {
    /* found no method, so get value from userdata. */
    i = luaL_checkint(L, 2);
    luaL_argcheck(L, 1 <= i && i <= (*pp)->n, 2, "index out of range");

    lua_pushinteger(L, (*pp)->v[i-1]);
  };

  return 1;
};

Это код, который делает это,

int luaopen_perm(lua_State *L) {

  luaL_newmetatable(L, PERM_MT);
  luaL_setfuncs(L, permL_methods, 0);
  luaL_setfuncs(L, permL_functions, 0);
  lua_pop(L, 1);

  luaL_newlib(L, permL_functions);

  return 1;
};

где permL_methods является

static const struct luaL_Reg permL_methods[] = {
  { "__index",      permL_index           },
  { "__eq",         permL_equal           },
  { "__tostring",   permL_tostring        },
  { "__gc",         permL_destroy         },
  [...]
  { NULL,           NULL                  }
};

а также permL_functions является

static const struct luaL_Reg permL_functions[] = {
  { "inverse",      permL_new_inverse     },
  { "product",      permL_new_product     },
  { "composition",  permL_new_composition },
  [...]
  { NULL,           NULL                  }
};

1 ответ

Решение

Это похоже на проблему, которую можно решить с помощью вложенных метатаблиц. Вам нужен один метатабль для методов (например, метод sort()), а второй - для операций с индексами. Эта вторая метатабельная на самом деле является метатабельной из метатабельных методов.

Позвольте мне написать это как код Lua. Вам нужно 3 таблицы:

-- the userdata object. I'm using a table here,
-- but it will work the same with a C userdata
u = {}

-- the "methods" metatable:
mt = {sort = function() print('sorting...') end}

-- the "operators" metatable:
op_mt = {__index = function() print('get') end}

Теперь сложная часть: Луа будет первым поиском u когда вы будете вызывать метод. Если он не найдет его, он будет искать в таблице, указанной полем __index uЭто метатабельно... И Луа повторит процесс для этой таблицы!

-- first level metatable
mt.__index = mt
setmetatable(u, mt)

-- second level metatable
setmetatable(mt, op_mt)

Теперь вы можете использовать свой u как это:

> u:sort()
sorting...
> = u[1]
get
nil

РЕДАКТИРОВАТЬ: лучшее решение с помощью функции для метаметода __index

Использование функции для метаметода __index, вероятно, является правильным способом для этого:

u = {}
mt = {sort = function() print('sorting...') end}
setmetatable(u, mt)
mt.__index = function(t, key)
    -- use rawget to avoid recursion
    local mt_val = rawget(mt, key)
    if mt_val ~=nil then
        return mt_val
    else
        print('this is a get on object', t)
    end
end

Использование:

> print(u)
table: 0x7fb1eb601c30
> u:sort()
sorting...
> = u[1]
this is a get on object    table: 0x7fb1eb601c30
nil
> 
Другие вопросы по тегам