ucode-mod-lua: support prototype lookups and method calls on ucode values

Expose ucode arrays and objects with prototypes as userdata proxy objects
to Lua and extend the userdata metadatable with an __index metamethod to
lookup not found properties in the ucode values prototype chain.

Also extend the __call metamethod implementation to infer method call
status from the activation record in order to invoke ucode functions with
the correct `this` context when called as method from Lua.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
Jo-Philipp Wich 2022-08-21 01:32:12 +02:00
parent 7e4c087f86
commit 42201e336d

View file

@ -155,6 +155,21 @@ ucv_to_lua(uc_vm_t *vm, uc_value_t *uv, lua_State *L, struct lh_table *visited)
case UC_ARRAY: case UC_ARRAY:
case UC_OBJECT: case UC_OBJECT:
if (ucv_prototype_get(uv)) {
ud = lua_newuserdata(L, sizeof(*ud));
if (ud) {
ud->vm = vm;
ud->uv = ucv_get(uv);
luaL_getmetatable(L, "ucode.value");
lua_setmetatable(L, -2);
}
else {
lua_pushnil(L);
}
}
else {
if (!visited) { if (!visited) {
freetbl = true; freetbl = true;
visited = lh_kptr_table_new(16, NULL); visited = lh_kptr_table_new(16, NULL);
@ -180,6 +195,7 @@ ucv_to_lua(uc_vm_t *vm, uc_value_t *uv, lua_State *L, struct lh_table *visited)
else { else {
lua_pushnil(L); lua_pushnil(L);
} }
}
break; break;
@ -401,17 +417,28 @@ lua_uv_call(lua_State *L)
ucv_userdata_t *ud = luaL_checkudata(L, 1, "ucode.value"); ucv_userdata_t *ud = luaL_checkudata(L, 1, "ucode.value");
int nargs = lua_gettop(L), i; int nargs = lua_gettop(L), i;
uc_value_t *rv; uc_value_t *rv;
lua_Debug ar;
bool mcall;
if (!ucv_is_callable(ud->uv)) if (!ucv_is_callable(ud->uv))
return luaL_error(L, "%s: Invoked value is not a function", return luaL_error(L, "%s: Invoked value is not a function",
uc_exception_type_name(EXCEPTION_TYPE)); uc_exception_type_name(EXCEPTION_TYPE));
if (!lua_getstack(L, 0, &ar) || !lua_getinfo(L, "n", &ar))
return luaL_error(L, "%s: Unable to obtain stackframe information",
uc_exception_type_name(EXCEPTION_RUNTIME));
mcall = !strcmp(ar.namewhat, "method");
if (mcall)
uc_vm_stack_push(ud->vm, lua_to_ucv(L, 2, ud->vm, NULL));
uc_vm_stack_push(ud->vm, ucv_get(ud->uv)); uc_vm_stack_push(ud->vm, ucv_get(ud->uv));
for (i = 2; i <= nargs; i++) for (i = 2 + mcall; i <= nargs; i++)
uc_vm_stack_push(ud->vm, lua_to_ucv(L, i, ud->vm, NULL)); uc_vm_stack_push(ud->vm, lua_to_ucv(L, i, ud->vm, NULL));
if (uc_vm_call(ud->vm, false, nargs - 1)) { if (uc_vm_call(ud->vm, mcall, nargs - 1 - mcall)) {
rv = ucv_object_get(ucv_array_get(ud->vm->exception.stacktrace, 0), "context", NULL); rv = ucv_object_get(ucv_array_get(ud->vm->exception.stacktrace, 0), "context", NULL);
return luaL_error(L, "%s: %s%s%s", return luaL_error(L, "%s: %s%s%s",
@ -428,6 +455,26 @@ lua_uv_call(lua_State *L)
return 1; return 1;
} }
static int
lua_uv_index(lua_State *L)
{
ucv_userdata_t *ud = luaL_checkudata(L, 1, "ucode.value");
const char *key = luaL_checkstring(L, 2);
uc_value_t *proto, *rv;
bool found;
proto = ucv_prototype_get(ud->uv);
rv = ucv_object_get(proto, key, &found);
if (!found)
return 0;
ucv_to_lua(ud->vm, rv, L, NULL);
ucv_put(rv);
return 1;
}
static int static int
lua_uv_tostring(lua_State *L) lua_uv_tostring(lua_State *L)
{ {
@ -443,6 +490,7 @@ lua_uv_tostring(lua_State *L)
static const luaL_reg ucode_ud_methods[] = { static const luaL_reg ucode_ud_methods[] = {
{ "__gc", lua_uv_gc }, { "__gc", lua_uv_gc },
{ "__call", lua_uv_call }, { "__call", lua_uv_call },
{ "__index", lua_uv_index },
{ "__tostring", lua_uv_tostring }, { "__tostring", lua_uv_tostring },
{ } { }
@ -899,8 +947,6 @@ uc_lua_create(uc_vm_t *vm, size_t nargs)
luaL_newmetatable(L, "ucode.value"); luaL_newmetatable(L, "ucode.value");
luaL_register(L, NULL, ucode_ud_methods); luaL_register(L, NULL, ucode_ud_methods);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
lua_pop(L, 1); lua_pop(L, 1);
return uc_resource_new(vm_type, L); return uc_resource_new(vm_type, L);