diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/linker/CallSiteOptions.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/linker/CallSiteOptions.java index acda176..f7d16de 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/linker/CallSiteOptions.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/linker/CallSiteOptions.java @@ -1,5 +1,7 @@ package fi.benjami.code4jvm.lua.linker; +import java.util.Arrays; + import fi.benjami.code4jvm.lua.LuaVm; import fi.benjami.code4jvm.lua.ir.LuaType; @@ -51,5 +53,10 @@ public CallSiteOptions(LuaVm owner, LuaType[] types, boolean spreadResults, bool public static CallSiteOptions nonFunction(LuaVm vm, LuaType... types) { return new CallSiteOptions(vm, types, false, false); } + + public CallSiteOptions wrappedCall(int popArgs) { + var innerTypes = Arrays.copyOfRange(types, popArgs, types.length - popArgs + 1); + return new CallSiteOptions(owner, innerTypes, spreadResults, spreadArguments); + } } diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/stdlib/BasicLib.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/stdlib/BasicLib.java index 98eb2e8..0e06d46 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/stdlib/BasicLib.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/stdlib/BasicLib.java @@ -10,12 +10,15 @@ import java.util.Objects; import java.util.stream.Collectors; +import fi.benjami.code4jvm.call.CallTarget; import fi.benjami.code4jvm.lua.LuaVm; import fi.benjami.code4jvm.lua.ffi.LuaLibrary; import fi.benjami.code4jvm.lua.ffi.Nullable; import fi.benjami.code4jvm.lua.ir.LuaType; import fi.benjami.code4jvm.lua.linker.CallSiteOptions; +import fi.benjami.code4jvm.lua.linker.DynamicTarget; import fi.benjami.code4jvm.lua.linker.LuaCallSite; +import fi.benjami.code4jvm.lua.linker.LuaCallTarget; import fi.benjami.code4jvm.lua.linker.LuaLinker; import fi.benjami.code4jvm.lua.ffi.Inject; import fi.benjami.code4jvm.lua.ffi.JavaFunction; @@ -41,6 +44,7 @@ public void install(LuaVm vm) { globals.set(func.name(), func); } globals.set("error", makeError()); + globals.set("pcall", makePcall()); globals.set("_G", globals); globals.set("_VERSION", "lua4jvm 0.1 (Lua 5.4)"); // TODO derive from build config } @@ -64,6 +68,58 @@ private static JavaFunction makeError() { throw new AssertionError(); } } + + private static DynamicTarget makePcall() { + return (meta, args) -> { + // Link call to first argument, passing rest of the arguments to it + // If the call site is multival, we'll need to handle that + var site = LuaLinker.linkCall(new LuaCallSite(meta.site, meta.options.wrappedCall(1)), + args[0], Arrays.copyOfRange(args, 1, args.length)) + .withGuards(LuaLinker.TARGET_HAS_CHANGED); + var target = site.target(); + + // Make the target compatible with call site that has itself as first argument + target = MethodHandles.dropArguments(target, 0, Object.class); + + try { + // Handle success multival return + if (target.type().returnType() == void.class) { + // Target returns nothing, but we'll need to return true! + target = MethodHandles.filterReturnValue(target, MethodHandles.constant(Object[].class, new Object[] {true})); + } else { + var filter = LOOKUP.findStatic(BasicLib.class, "handleSuccess", + MethodType.methodType(Object[].class, Object.class)); + target = MethodHandles.filterReturnValue(target, + filter.asType(MethodType.methodType(Object[].class, target.type().returnType()))); + } + + // Handle exception error return + target = MethodHandles.catchException(target, Exception.class, + LOOKUP.findStatic(BasicLib.class, "handleError", MethodType.methodType(Object[].class, Exception.class))); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new AssertionError(e); + } + return new LuaCallTarget(target, site.guards()); + }; + } + + @SuppressWarnings("unused") // MethodHandle + private static Object[] handleSuccess(Object value) { + if (value instanceof Object[] array) { + var result = new Object[1 + array.length]; + result[0] = true; + System.arraycopy(array, 0, result, 1, array.length); + return result; + } else { + return new Object[] {true, value}; + } + } + + @SuppressWarnings("unused") // MethodHandle + private static Object[] handleError(Exception e) { + var errorObj = e instanceof LuaException luaEx ? luaEx.getLuaMessage() : e; + return new Object[] {false, errorObj}; + } // TODO assert @@ -174,10 +230,6 @@ private static LuaTable setmetatable(LuaTable table, @Nullable LuaTable metatabl return table; } - // TODO iteration: ipairs, pairs - - // TODO pcall - but should this be a normal function? - @LuaExport("print") private static void print(@Inject LuaVm vm, Object... args) { var stdout = vm.options().stdOut().orElseThrow(() -> new LuaException("stdout is not available")); diff --git a/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/BasicLibTest.java b/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/BasicLibTest.java index 732ee5e..6b0e8ae 100644 --- a/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/BasicLibTest.java +++ b/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/BasicLibTest.java @@ -168,4 +168,34 @@ public void ipairs() throws Throwable { assertEquals(null, out.get("test")); assertEquals(null, out.get("second")); } + + @Test + public void pcallTest() throws Throwable { + { + var result = (Object[]) vm.execute("return pcall(error, \"foo\")"); + assertArrayEquals(new Object[] {false, "foo"}, result); + } + + { + var result = (Object[]) vm.execute(""" + local function raiseError() + error("foo123") + end + + return pcall(raiseError) + """); + assertArrayEquals(new Object[] {false, "foo123"}, result); + } + + { + var result = (Object[]) vm.execute(""" + local function noError(arg) + return "ok", arg, "bar" + end + + return pcall(noError, "foo") + """); + assertArrayEquals(new Object[] {true, "ok", "foo", "bar"}, result); + } + } }