Skip to content

Commit

Permalink
lua4jvm: Add pcall(...) to standard library
Browse files Browse the repository at this point in the history
With some MethodHandle magic, the arguments are passed as-is, return values
intercepted and caught exceptions returned. Linkage of the underlying call is
done as if it was a normal function call.
  • Loading branch information
bensku committed Oct 6, 2024
1 parent a23120e commit 3e5933e
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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);
}

}
60 changes: 56 additions & 4 deletions lua4jvm/src/main/java/fi/benjami/code4jvm/lua/stdlib/BasicLib.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
}
Expand All @@ -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

Expand Down Expand Up @@ -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"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

0 comments on commit 3e5933e

Please sign in to comment.