Skip to content

Commit b74d2cf

Browse files
committed
Add first parts of the Lua standard library
This necessited a bunch of changes: * LuaContext, functions and linker pass around the current VM * Injecting the VM to Java functions not works * Lua VM can be now configured (for now, installed libraries and stdout) A majority of the standard library is still missing!
1 parent f63b68f commit b74d2cf

33 files changed

+441
-180
lines changed

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/LuaStdLib.java

-47
This file was deleted.

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/LuaVm.java

+17-19
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,27 @@
1818

1919
public class LuaVm {
2020

21+
private final VmOptions options;
2122
private final LuaTable globals;
2223

2324
public LuaVm() {
24-
this.globals = initGlobals();
25+
this(VmOptions.DEFAULT);
2526
}
2627

27-
private static LuaTable initGlobals() {
28-
var globals = new LuaTable();
29-
30-
globals.set("_G", globals);
31-
globals.set("_VERSION", "lua4jvm DEV (Lua 5.4)");
32-
33-
globals.set("print", LuaStdLib.PRINT);
34-
globals.set("tostring", LuaStdLib.TO_STRING);
35-
globals.set("type", LuaStdLib.TYPE);
36-
globals.set("tonumber", LuaStdLib.TO_NUMBER);
37-
38-
var ourLib = new LuaTable();
39-
ourLib.set("read", LuaStdLib.READ);
40-
ourLib.set("write", LuaStdLib.WRITE);
41-
globals.set("code4jvm", ourLib);
42-
43-
return globals;
28+
public LuaVm(VmOptions options) {
29+
this.options = options;
30+
this.globals = new LuaTable();
31+
installLibraries();
32+
}
33+
34+
public VmOptions options() {
35+
return options;
36+
}
37+
38+
private void installLibraries() {
39+
for (var lib : options.libraries()) {
40+
lib.install(this);
41+
}
4442
}
4543

4644
public LuaTable globals() {
@@ -66,7 +64,7 @@ public LuaFunction load(LuaModule module, LuaTable env) {
6664
List.of(),
6765
module.root()
6866
);
69-
return new LuaFunction(type, new Object[] {env});
67+
return new LuaFunction(this, type, new Object[] {env});
7068
}
7169

7270
public Object execute(String chunk) throws Throwable {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package fi.benjami.code4jvm.lua;
2+
3+
import java.io.PrintStream;
4+
import java.util.Collection;
5+
import java.util.List;
6+
7+
import fi.benjami.code4jvm.lua.ffi.LuaLibrary;
8+
import fi.benjami.code4jvm.lua.stdlib.BasicLib;
9+
10+
/**
11+
* lua4jvm Lua VM options.
12+
*
13+
*/
14+
public class VmOptions implements Cloneable {
15+
16+
public static final VmOptions DEFAULT = new VmOptions();
17+
18+
public static final Builder builder() {
19+
return new Builder();
20+
}
21+
22+
private Collection<LuaLibrary> libraries = List.of(BasicLib.INSTANCE);
23+
private PrintStream stdOut = System.out;
24+
25+
private VmOptions() {}
26+
27+
public static class Builder {
28+
29+
private VmOptions opts;
30+
31+
private Builder() {
32+
this.opts = new VmOptions();
33+
}
34+
35+
public Builder libraries(LuaLibrary... libraries) {
36+
opts.libraries = List.of(libraries);
37+
return this;
38+
}
39+
40+
public Builder stdOut(PrintStream out) {
41+
opts.stdOut = out;
42+
return this;
43+
}
44+
45+
public VmOptions build() {
46+
try {
47+
return (VmOptions) opts.clone();
48+
} catch (CloneNotSupportedException e) {
49+
throw new AssertionError(e);
50+
}
51+
}
52+
}
53+
54+
public Collection<LuaLibrary> libraries() {
55+
return libraries;
56+
}
57+
58+
public PrintStream stdOut() {
59+
return stdOut;
60+
}
61+
}

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/FunctionCompiler.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function,
5959

6060
// Compile and load the function code, or use something that is already cached
6161
var compiledFunc = function.type().specializations().computeIfAbsent(cacheKey, t -> {
62-
var ctx = function.type().newContext(truncateReturn, argTypes);
62+
var ctx = LuaContext.forFunction(function.owner(), function.type(), truncateReturn, argTypes);
6363
var code = generateCode(ctx, function.type(), argTypes, upvalueTypes);
6464
try {
6565
var lookup = LOOKUP.defineHiddenClassWithClassData(code, ctx.allClassData(), true);

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/LuaContext.java

+47-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import fi.benjami.code4jvm.Constant;
99
import fi.benjami.code4jvm.Type;
1010
import fi.benjami.code4jvm.Variable;
11+
import fi.benjami.code4jvm.lua.LuaVm;
1112
import fi.benjami.code4jvm.lua.ir.LuaLocalVar;
1213
import fi.benjami.code4jvm.lua.ir.LuaType;
1314
import fi.benjami.code4jvm.lua.ir.LuaVariable;
@@ -16,6 +17,34 @@
1617

1718
public class LuaContext {
1819

20+
public static LuaContext forFunction(LuaVm vm, LuaType.Function type, boolean truncateReturn, LuaType... argTypes) {
21+
// Init scope with upvalue types
22+
var ctx = new LuaContext(vm, truncateReturn);
23+
for (var upvalue : type.upvalues()) {
24+
ctx.recordType(upvalue.variable(), upvalue.type());
25+
}
26+
27+
// Add types of function arguments
28+
// Missing arguments are allowed and treated as nil
29+
var normalArgs = type.acceptedArgs().size();
30+
if (type.isVarargs()) {
31+
normalArgs--;
32+
ctx.recordType(LuaLocalVar.VARARGS, LuaType.UNKNOWN); // No type analysis for these yet
33+
}
34+
var acceptedArgs = type.acceptedArgs();
35+
for (var i = 0; i < normalArgs; i++) {
36+
if (argTypes.length > i) {
37+
ctx.recordType(acceptedArgs.get(i), argTypes[i]);
38+
} else {
39+
ctx.recordType(acceptedArgs.get(i), LuaType.NIL);
40+
}
41+
}
42+
43+
// Compute types of local variables and the return type
44+
type.body().outputType(ctx);
45+
return ctx;
46+
}
47+
1948
/**
2049
* Local variable type table. When variables are written to, their types
2150
* are recorded here.
@@ -45,12 +74,21 @@ public class LuaContext {
4574

4675
private boolean allowSpread;
4776

48-
public LuaContext(boolean truncateReturn) {
77+
/**
78+
* The Lua VM that 'owns' this context.
79+
*/
80+
private final LuaVm owner;
81+
private final Constant ownerConstant;
82+
83+
public LuaContext(LuaVm owner, boolean truncateReturn) {
84+
assert owner != null;
4985
this.typeTable = new IdentityHashMap<>();
5086
this.variables = new IdentityHashMap<>();
5187
this.classData = new ArrayList<>();
5288
this.cache = new IdentityHashMap<>();
5389
this.truncateReturn = truncateReturn;
90+
this.owner = owner;
91+
this.ownerConstant = addClassData(owner);
5492
}
5593

5694
public void recordType(LuaVariable variable, LuaType type) {
@@ -187,5 +225,13 @@ public void setAllowSpread(boolean value) {
187225
public boolean allowSpread() {
188226
return allowSpread;
189227
}
228+
229+
public LuaVm owner() {
230+
return owner;
231+
}
232+
233+
public Constant ownerConstant() {
234+
return ownerConstant;
235+
}
190236

191237
}

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ffi/Inject.java

+1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
@Target(ElementType.PARAMETER)
1010
public @interface Inject {
1111

12+
String value() default "default";
1213
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package fi.benjami.code4jvm.lua.ffi;
2+
3+
import fi.benjami.code4jvm.lua.linker.LuaCallSite;
4+
5+
public interface InjectedArg {
6+
7+
Object get(LuaCallSite site);
8+
}

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ffi/JavaFunction.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public record Target(
3333
* Arguments that are injected by Lua VM at the call site.
3434
* Injected arguments must come first!
3535
*/
36-
List<Class<?>> injectedArgs,
36+
List<InjectedArg> injectedArgs,
3737

3838
/**
3939
* Function arguments. All are required, use multiple targets
@@ -65,7 +65,7 @@ public record Target(
6565
MethodHandle method
6666
) {}
6767

68-
public record Arg(String name, LuaType type) {}
68+
public record Arg(String name, LuaType type, boolean nullable) {}
6969

7070
// TODO support functions for generating errors
7171

@@ -104,8 +104,12 @@ private MatchResult checkArgs(Target target, LuaType[] argTypes) {
104104

105105
// Check types of arguments
106106
for (var i = 0; i < requiredArgs; i++) {
107-
if (!target.arguments.get(i).type.isAssignableFrom(argTypes[i])) {
108-
return MatchResult.ARG_TYPE_MISMATCH;
107+
var arg = target.arguments.get(i);
108+
if (!arg.type.isAssignableFrom(argTypes[i])) {
109+
// Allow nil instead of expected type if nullability is allowed
110+
if (!arg.nullable ||!argTypes[i].equals(LuaType.NIL)) {
111+
return MatchResult.ARG_TYPE_MISMATCH;
112+
}
109113
}
110114
}
111115

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ffi/LuaBinder.java

+19-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.HashMap;
1111
import java.util.List;
1212

13+
import fi.benjami.code4jvm.lua.LuaVm;
1314
import fi.benjami.code4jvm.lua.ir.LuaType;
1415

1516
/**
@@ -72,14 +73,15 @@ public JavaFunction bindFunction(String name, List<Method> targetMethods) {
7273
}
7374

7475
private JavaFunction.Target toFunctionTarget(Method method) {
75-
var injectedArgs = new ArrayList<Class<?>>();
76+
var injectedArgs = new ArrayList<InjectedArg>();
7677
var args = new ArrayList<JavaFunction.Arg>();
7778
for (var param : method.getParameters()) {
78-
if (param.isAnnotationPresent(Inject.class)) {
79+
var inject = param.getAnnotation(Inject.class);
80+
if (inject != null) {
7981
if (!args.isEmpty()) {
8082
throw new IllegalArgumentException("injected arguments after normal ones in method " + method.getName());
8183
}
82-
injectedArgs.add(param.getType());
84+
injectedArgs.add(toInjectedArg(param.getType(), inject.value()));
8385
} else {
8486
args.add(toArg(param));
8587
}
@@ -100,7 +102,20 @@ private JavaFunction.Target toFunctionTarget(Method method) {
100102
return new JavaFunction.Target(injectedArgs, args, method.isVarArgs(), LuaType.of(returnType), multipleReturns, handle);
101103
}
102104

105+
private InjectedArg toInjectedArg(Class<?> type, String source) {
106+
// TODO support non-default sources
107+
if (!source.equals("default")) {
108+
throw new UnsupportedOperationException();
109+
}
110+
// Handle default injected arg source
111+
if (type.equals(LuaVm.class)) {
112+
return site -> site.options.owner();
113+
} else {
114+
throw new IllegalArgumentException("unsupported type for @Inject");
115+
}
116+
}
117+
103118
private JavaFunction.Arg toArg(Parameter param) {
104-
return new JavaFunction.Arg(param.getName(), LuaType.of(param.getType()));
119+
return new JavaFunction.Arg(param.getName(), LuaType.of(param.getType()), param.isAnnotationPresent(Nullable.class));
105120
}
106121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package fi.benjami.code4jvm.lua.ffi;
2+
3+
import fi.benjami.code4jvm.lua.LuaVm;
4+
import fi.benjami.code4jvm.lua.VmOptions;
5+
6+
/**
7+
* A library of functions (or other constructs) installable to a Lua VM.
8+
* @see VmOptions
9+
*
10+
*/
11+
public interface LuaLibrary {
12+
13+
void install(LuaVm vm);
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package fi.benjami.code4jvm.lua.ffi;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target(ElementType.PARAMETER)
10+
public @interface Nullable {
11+
12+
}

0 commit comments

Comments
 (0)