ucode-mod-lua: support prototype lookups and method calls on ucode values
authorJo-Philipp Wich <jo@mein.io>
Sat, 20 Aug 2022 23:32:12 +0000 (01:32 +0200)
committerJo-Philipp Wich <jo@mein.io>
Fri, 26 Aug 2022 08:11:17 +0000 (10:11 +0200)
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>
contrib/package/ucode-mod-lua/src/lua.c

index 679425fae516435e1f64d601b2bd9a627a327274..241f4a92ff7ed008b72c5d5083a9e534df99a75e 100644 (file)
@@ -155,30 +155,46 @@ ucv_to_lua(uc_vm_t *vm, uc_value_t *uv, lua_State *L, struct lh_table *visited)
 
        case UC_ARRAY:
        case UC_OBJECT:
-               if (!visited) {
-                       freetbl = true;
-                       visited = lh_kptr_table_new(16, NULL);
+               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) {
+                               freetbl = true;
+                               visited = lh_kptr_table_new(16, NULL);
+                       }
 
-               if (visited) {
-                       if (lua_table_new_or_ref(L, visited, uv)) {
-                               if (ucv_type(uv) == UC_ARRAY) {
-                                       for (i = 0; i < ucv_array_length(uv); i++) {
-                                               e = ucv_array_get(uv, i);
-                                               ucv_to_lua(vm, e, L, visited);
-                                               lua_rawseti(L, -2, (int)i + 1);
+                       if (visited) {
+                               if (lua_table_new_or_ref(L, visited, uv)) {
+                                       if (ucv_type(uv) == UC_ARRAY) {
+                                               for (i = 0; i < ucv_array_length(uv); i++) {
+                                                       e = ucv_array_get(uv, i);
+                                                       ucv_to_lua(vm, e, L, visited);
+                                                       lua_rawseti(L, -2, (int)i + 1);
+                                               }
                                        }
-                               }
-                               else {
-                                       ucv_object_foreach(uv, key, val) {
-                                               ucv_to_lua(vm, val, L, visited);
-                                               lua_setfield(L, -2, key);
+                                       else {
+                                               ucv_object_foreach(uv, key, val) {
+                                                       ucv_to_lua(vm, val, L, visited);
+                                                       lua_setfield(L, -2, key);
+                                               }
                                        }
                                }
                        }
-               }
-               else {
-                       lua_pushnil(L);
+                       else {
+                               lua_pushnil(L);
+                       }
                }
 
                break;
@@ -401,17 +417,28 @@ lua_uv_call(lua_State *L)
        ucv_userdata_t *ud = luaL_checkudata(L, 1, "ucode.value");
        int nargs = lua_gettop(L), i;
        uc_value_t *rv;
+       lua_Debug ar;
+       bool mcall;
 
        if (!ucv_is_callable(ud->uv))
                return luaL_error(L, "%s: Invoked value is not a function",
                        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));
 
-       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));
 
-       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);
 
                return luaL_error(L, "%s: %s%s%s",
@@ -428,6 +455,26 @@ lua_uv_call(lua_State *L)
        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
 lua_uv_tostring(lua_State *L)
 {
@@ -443,6 +490,7 @@ lua_uv_tostring(lua_State *L)
 static const luaL_reg ucode_ud_methods[] = {
        { "__gc",                       lua_uv_gc         },
        { "__call",                     lua_uv_call       },
+       { "__index",            lua_uv_index      },
        { "__tostring",         lua_uv_tostring   },
 
        { }
@@ -899,8 +947,6 @@ uc_lua_create(uc_vm_t *vm, size_t nargs)
 
        luaL_newmetatable(L, "ucode.value");
        luaL_register(L, NULL, ucode_ud_methods);
-       lua_pushvalue(L, -1);
-       lua_setfield(L, -2, "__index");
        lua_pop(L, 1);
 
        return uc_resource_new(vm_type, L);