From 6823e9c451b58a9b2334fb543d2b7c00cb4b1cf2 Mon Sep 17 00:00:00 2001 From: MrMugame <40832361+MrMugame@users.noreply.github.com> Date: Sat, 26 Jul 2025 20:37:32 +0200 Subject: [PATCH] Add `__call` (#1471) * Add support for `__call` method * Add small test for `__call` method * Remove unecessary error --- lib/space_lua/eval.ts | 16 +++------- lib/space_lua/language_core_test.lua | 17 +++++++++++ lib/space_lua/runtime.ts | 44 ++++++++++++++++++++++------ 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/lib/space_lua/eval.ts b/lib/space_lua/eval.ts index 2c21fd2b..a95689ad 100644 --- a/lib/space_lua/eval.ts +++ b/lib/space_lua/eval.ts @@ -345,22 +345,14 @@ function evalPrefixExpression( if (e.name) { selfArgs = [prefixValue]; prefixValue = luaIndexValue(prefixValue, e.name, sf); - if (prefixValue === null) { - throw new LuaRuntimeError( - `Attempting to index a non-table: ${prefixValue}`, - sf.withCtx(e.prefix.ctx), - ); - } + if (prefixValue instanceof Promise) { return prefixValue.then(handleFunctionCall); } } - if (!prefixValue.call) { - throw new LuaRuntimeError( - `Attempting to call ${prefixValue} as a function`, - sf.withCtx(e.prefix.ctx), - ); - } + + // Unsure if part of the spec, but it seems to be common for lua implementations + // to evaluate all args before evaluating the callee const args = evalExpressions(e.args, env, sf); if (args instanceof Promise) { diff --git a/lib/space_lua/language_core_test.lua b/lib/space_lua/language_core_test.lua index cea759f1..75f06a0f 100644 --- a/lib/space_lua/language_core_test.lua +++ b/lib/space_lua/language_core_test.lua @@ -258,6 +258,23 @@ assert(added[1] == 5 and added[2] == 7 and added[3] == 9) local muliplied = t * 2 assert(muliplied[1] == 2 and muliplied[2] == 4 and muliplied[3] == 6) +-- __call Metamethod + +local ts = { + foo = "ARG 2" +} +local mt = {} + +function mt.__call(table, arg1) + assert(arg1 == "ARG 1") + assert(table.foo == "ARG 2") + return "return from metatable" +end + +setmetatable(ts, mt) + +assert(ts("ARG 1") == "return from metatable") + -- Let's try somethings lightly more complicated, like a deep comparison function implemented in Lua function deepCompare(t1, t2) if t1 == t2 then return true end diff --git a/lib/space_lua/runtime.ts b/lib/space_lua/runtime.ts index 8f5821d9..dff9adb7 100644 --- a/lib/space_lua/runtime.ts +++ b/lib/space_lua/runtime.ts @@ -584,34 +584,60 @@ export function luaLen(obj: any): number { } export function luaCall( - fn: any, + callee: any, args: any[], ctx: ASTCtx, sf?: LuaStackFrame, ): any { - if (!fn) { + if (!callee) { throw new LuaRuntimeError( `Attempting to call a nil value`, (sf || LuaStackFrame.lostFrame).withCtx(ctx), ); } - if (typeof fn === "function") { + + if (typeof callee === "function") { const jsArgs = evalPromiseValues( args.map((v) => luaValueToJS(v, sf || LuaStackFrame.lostFrame)), ); + if (jsArgs instanceof Promise) { - return jsArgs.then((jsArgs) => fn(...jsArgs)); + return jsArgs.then((jsArgs) => callee(...jsArgs)); } else { - return fn(...jsArgs); + return callee(...jsArgs); } - } - if (!fn.call) { + } else if (callee instanceof LuaTable) { + const metatable = getMetatable(callee, sf); + + if (metatable && metatable.has("__call")) { + // Invoke the meta table + const metaValue = metatable.get("__call", sf); + + const callMetaValue = (value: any) => { + if (metaValue.call) { + return luaCall(value, [callee, ...args], ctx, sf); + } else { + throw new Error("Meta table __call must be a function"); + } + }; + + if (metaValue.then) { + // Got a promise, we need to wait for it + return metaValue.then((value: any) => { + callMetaValue(value); + }); + } else { + return callMetaValue(metaValue); + } + } + } else if (callee.call) { + return callee.call((sf || LuaStackFrame.lostFrame).withCtx(ctx), ...args); + } else { throw new LuaRuntimeError( - `Attempting to call a non-callable value`, + `Attempting to call a non-callable value of type: ${luaTypeOf(callee)}`, (sf || LuaStackFrame.lostFrame).withCtx(ctx), ); } - return fn.call((sf || LuaStackFrame.lostFrame).withCtx(ctx), ...args); } export function luaEquals(a: any, b: any): boolean {