KLua is a work-in-progress pure Kotlin Lua runtime for JVM 17+. It aims to provide a Lua 5.5 source runtime, a C-Lua-like low-level stack API for Java embedders, Kotlin-friendly convenience APIs, and an interpreter-first runtime architecture that can grow into debugging, tooling, conformance hardening, and benchmark-driven optimization.
KLua is pre-1.0 and not production-ready. Public APIs may change while the runtime moves toward a complete Lua implementation.
The repository currently includes a multi-module Gradle project, lexer/parser/compiler/VM pieces, internal KLua bytecode and disassembly support, KLua bytecode package compile/load APIs including packaged resource loading, a single Lua 5.5 runtime target, basic globals and native function calls, a Java-friendly LuaState API, a higher-level Lua facade, Kotlin extension helpers, partial base/math/string/table/utf8/package/coroutine/os/debug standard library support, initial debug/DAP/tooling foundations, instruction-limit enforcement, standard-library whitelisting, tests, and a JMH benchmark module. Broader standard library conformance, debug tooling integration, sandboxing, Lua 5.5 conformance hardening, and performance work are still roadmap items.
- Build a Lua 5.5 source runtime for JVM 17+.
- Provide a Java-friendly low-level
LuaStatestack API. - Provide a Kotlin convenience layer for common embedding workflows.
- Keep the runtime interpreter-first: Lua source -> parser -> AST -> compiler -> KLua bytecode -> VM.
- Keep public API modules separate from compiler, VM, bytecode, parser, and AST internals.
- Add source-level debugging support as a runtime feature, not just as a wrapper around the JVM debugger.
- Optimize only after correctness and benchmark baselines exist.
klua-core: internal lexer, parser, AST, compiler, bytecode, value model, and VM runtime.klua-api: stable Java-friendly public API surface, includingLuaState,Lua,LuaChunk, and host function types.klua-kotlin: Kotlin extension helpers for the public API.klua-stdlib: partial standard library integration, currently covering base, math, string, table, utf8, package, coroutine, os, and debug functions.klua-debug: initial runtime debugging internals.klua-dap: initial Debug Adapter Protocol integration.klua-tools: initial command-line tools.klua-jmh: JMH benchmarks.klua-tests: cross-module foundation, integration, and conformance tests.
- JVM 17 or newer.
- The checked-in Gradle wrapper.
The verified test command for this checkout is:
sh gradlew testOnce gradlew has executable permissions in the local checkout, this should also work:
./gradlew testimport io.github.realmlabs.klua.api.Lua
import io.github.realmlabs.klua.kotlin.function
import io.github.realmlabs.klua.kotlin.globals
import io.github.realmlabs.klua.kotlin.set
fun main() {
val lua = Lua.create()
val answer = lua.load("return 40 + 2", "answer.lua").evalLong()
println(answer)
lua.globals["base"] = 20
lua.globals["add"] = lua.function { left: Long, right: Long ->
left + right
}
val result = lua.load("return add(base, 22)", "native-call.lua").evalLong()
println(result)
}import io.github.realmlabs.klua.api.Lua;
import io.github.realmlabs.klua.api.LuaReturn;
public final class Example {
public static void main(String[] args) {
Lua lua = Lua.create();
long answer = lua.load("return 40 + 2", "answer.lua").evalLong();
System.out.println(answer);
lua.globals().set("base", 20L);
lua.globals().setFunction("add", context -> {
Long left = context.toInteger(1);
Long right = context.toInteger(2);
if (left == null || right == null) {
throw new IllegalArgumentException("add expects integer arguments");
}
return LuaReturn.of(left + right);
});
long result = lua.load("return add(base, 22)", "native-call.lua").evalLong();
System.out.println(result);
}
}KLua bytecode packages can be compiled with the tools module command:
klua --compile scripts/main.lua build/klua/main.kluacPackaged bytecode can be loaded from bytes or classpath resources through the public API:
val result = Lua.create()
.loadBytecodeResource("scripts/main.kluac")
.call("arg")LuaConfig.instructionLimit can cap VM bytecode instructions for chunk execution, exported Lua function calls, and coroutine resumes. A value of 0 keeps execution unlimited.
val lua = Lua.create(LuaConfig(instructionLimit = 100_000))
lua.load(script, "sandboxed.lua").exec()os.exit never calls System.exit directly. It maps Lua's boolean or integer status argument to a JVM integer status, invokes LuaConfig.exitHandler, then throws LuaExitException so embedders can decide how to terminate or report the script.
val state = LuaState.create(
LuaConfig(
exitHandler = LuaExitHandler { status, closeState ->
println("Lua requested exit $status")
},
),
)LuaConfig.standardLibraries can whitelist which standard libraries LuaStdlib.openLibs installs. The default installs every available library, while debugEnabled = false still suppresses the debug library even when it is listed.
val state = LuaState.create(
LuaConfig(
instructionLimit = 100_000,
standardLibraries = setOf(
LuaStandardLibrary.BASE,
LuaStandardLibrary.MATH,
LuaStandardLibrary.STRING,
LuaStandardLibrary.TABLE,
),
),
)
LuaStdlib.openLibs(state)For untrusted or game-server scripts, start with LuaConfig.production(). The preset disables debugger attachment, applies a default one-million-instruction limit, selects the base/math/string/table/utf8/coroutine libraries, and disables unsafe standard-library host access. That last policy removes base dofile/loadfile and prevents package, I/O, and OS libraries from opening even if they are accidentally included in standardLibraries or opened directly. Explicit host globals and registered userdata types remain available.
val state = LuaState.create(LuaConfig.production(instructionLimit = 250_000))
state.register("hostTick") { LuaReturn.of(42L) }
LuaStdlib.openLibs(state)Filesystem, module-path, process, environment, and OS access require an explicit compatibility opt-in:
val config = LuaConfig(
instructionLimit = 250_000,
standardLibraries = LuaStandardLibrary.all(),
unsafeStandardLibraryAccessEnabled = true,
)The general-purpose LuaConfig() default keeps unsafeStandardLibraryAccessEnabled = true for backward compatibility; use the production preset when executing untrusted scripts.
KLua is designed as a Lua-compatible runtime on the JVM, not as a direct port of PUC Lua internals. The intended runtime path is:
Lua source
-> lexer/parser
-> AST
-> KLua compiler
-> custom register bytecode
-> Kotlin/JVM interpreter
-> optional JVM bytecode compiler later
The klua-jmh module contains JMH baselines for deliberately separate costs:
CompileBenchmark.compileNumericLooprecompiles a fixed numeric-loop source through parsing, compilation, and KLua bytecode-package encoding.VmExecutionBenchmark.executeNumericLoopreuses a compiled top-level function and measures creation and execution of a fresh VM coroutine running 10,000 numeric-loop iterations.RuntimeWorkloadBenchmarkcompares 10,000 Lua calls, 10,000 host calls, and 10,000 table writes followed by 10,000 reads using the same fresh-coroutine boundary.
Build and list the generated benchmark jar:
./gradlew :klua-jmh:jmhJar
java -jar klua-jmh/build/libs/klua-jmh-0.1.0-SNAPSHOT-jmh.jar -l
Run the configured warmup, measurement, and fork settings with:
./gradlew :klua-jmh:jmh
Benchmark numbers are only comparable when the JVM, hardware, operating system, power settings, and JMH arguments are held constant. The short smoke commands used during development prove that benchmarks execute; they are not optimization evidence.
Recorded checkpoints and the evidence used to select optimization targets live in docs/KLua_Benchmark_Baseline.md.
See docs/KLua_Architecture.md for the full architecture notes.
The project is organized around small, testable milestones: foundation, lexer/parser, compiler skeleton, minimal VM, expressions and control flow, functions, tables, closures, metatables, public APIs, userdata and JVM interop, standard libraries, coroutines, debugging, DAP tooling, packaging, sandboxing, performance work, Lua 5.5 conformance hardening, v1.0, and optional JVM bytecode generation.
See docs/KLua_Implementation_Milestones.md for the milestone plan.
Before making implementation changes, read docs/KLua_Codex_Goal.md. It defines the module boundaries, delivery rules, language target policy, implementation order, testing requirements, and definition of done for this repository.
For behavior-sensitive Lua 5.5 work, inspect the local official Lua 5.5 source tree at ~/Downloads/lua-lua-a5522f0 before deciding semantics. Use that source as the primary reference for actual logic behavior; manuals and local lua5.5 probes are supporting checks.
KLua is available under the MIT License.