A Lua 5.3 lexer, parser, AST, and semantic-analysis toolkit written in pure Kotlin.
The project scope covers Lua 5.3 plus Android-Lua/LuaJava analysis, JVM reflection interop, and a JVM language-server path. Semantic analysis uses the SemanticPipeline API.
- Kotlin Multiplatform support (JVM / JS / Native)
- Parse source to AST
- Transform AST to source code
- Semantic analysis: provide type information (work in progress)
Use JDK 17 for local JVM work.
For Android framework reflection, point the JVM workspace configuration at the platform jar for the API level you want to model, via the jvm.androidJar metadata key or JvmWorkspaceConfiguration.androidJar. Keep application/library jars in jvm.classpath, and reserve jvm.androidJar for the Android platform jar. See docs/android-platform-setup.md for details.
Add the dependency to your Gradle file:
implementation("io.github.dingyi222666:luaparser:1.0.4")Requires Kotlin 2.1 or newer — the library is compiled with Kotlin 2.2.0, and older Kotlin toolchains reject its metadata.
import io.github.dingyi222666.luaparser.lexer.LuaLexer
import io.github.dingyi222666.luaparser.parser.LuaParser
import io.github.dingyi222666.luaparser.source.AST2Lua
fun main() {
val lexer = LuaLexer("print('hello world')")
val parser = LuaParser()
val root = parser.parse(lexer)
println(AST2Lua().asCode(root))
}The no-argument LuaParser() constructor defaults to LuaVersion.ANDROLUA_5_3, keeping Android-Lua/AndroLua 5.3 syntax available by default. For strict Lua 5.3 grammar:
val parser = LuaParser(luaVersion = LuaVersion.LUA_5_3)In strict Lua 5.3 mode, Android-Lua keywords such as continue, when, switch, case, default, and lambda are not enabled as parser keywords.
Use SemanticPipeline as the primary file-local semantic entry point. For workspace-level module resolution and path-based queries, use LuaWorkspaceEngine plus LuaWorkspaceQueryFacade.
import io.github.dingyi222666.luaparser.parser.LuaParser
import io.github.dingyi222666.luaparser.parser.ast.node.Position
import io.github.dingyi222666.luaparser.semantic.SemanticPipeline
fun main() {
val chunk = LuaParser().parse(
"""
---@type string
local name = "lua"
return name
""".trimIndent()
)
val result = SemanticPipeline().analyze(chunk)
val symbol = result.model.getSymbolAt(Position(2, 11))
println(symbol?.name)
println(result.summary.diagnosticCount)
}The returned SemanticAnalysisResult exposes the SemanticModel plus a lightweight summary. The former SemanticAnalyzer / AnalysisResult / legacy symbol-table API has been retired; see docs/semantic-compat.md.
JvmWorkspaceEngine can reflect JVM classes into synthetic Lua modules so require("String"), require("Arrays"), and similar patterns resolve through the workspace graph.
Metadata-driven integration keys:
jvm.classes: fully-qualified class names to mount directlyandrolua.imports: AndroLua-style simple import names resolved through JVM import prefixesjvm.classpath: newline-separated jar or directory entries used to build a reflective class loaderjvm.androidJar: optional path to an Android platformandroid.jar, appended to the effective reflective classpathjvm.importPrefixes: newline-separated prefix list used for simple-name import resolution
Or configure the JVM engine directly:
val engine = JvmWorkspaceEngine(
configuration = JvmWorkspaceConfiguration(
classes = linkedSetOf("java.util.Locale"),
androluaImports = listOf("String"),
classpathEntries = listOf("libs/example.jar"),
androidJar = "/opt/android/platforms/android-34/android.jar",
importPrefixes = listOf("java.lang", "android.widget")
)
)The structured configuration is merged with per-workspace metadata, letting embedding applications provide a default class loader or classpath while still honoring workspace-specific AndroLua imports.
Minimal workspace query example:
import io.github.dingyi222666.luaparser.interop.jvm.JvmWorkspaceConfiguration
import io.github.dingyi222666.luaparser.interop.jvm.JvmWorkspaceEngine
import io.github.dingyi222666.luaparser.parser.ast.node.Position
import io.github.dingyi222666.luaparser.semantic.workspace.LuaWorkspaceInput
import io.github.dingyi222666.luaparser.semantic.workspace.LuaWorkspaceQueryFacade
import io.github.dingyi222666.luaparser.semantic.workspace.VirtualPath
fun main() {
val path = VirtualPath.of("main.lua")
val configuration = JvmWorkspaceConfiguration(
androidJar = "/opt/android/platforms/android-35/android.jar",
androluaImports = listOf("TextView"),
importPrefixes = listOf("java.lang", "android.widget")
)
val input = LuaWorkspaceInput(
files = mapOf(
path to """
require "import"
import "android.widget.TextView"
local view = TextView(activity)
return view
""".trimIndent()
),
metadata = configuration.applyToMetadata(emptyMap())
)
val snapshot = JvmWorkspaceEngine(configuration = configuration).build(input).snapshot
val queries = LuaWorkspaceQueryFacade(snapshot)
println(queries.diagnostics(path))
println(queries.hover(path, Position(3, 15))?.typeInfo?.displayName)
}Source-level Android-Lua patterns recognized by the JVM workspace layer:
import "java.lang.String"exposesStringas a visible reflected module symbol.import "java.util.*"resolves top-level classes discoverable from that package on the active JVM classpath/runtime image.local import = require("import")followed byimport("java.util.Locale")resolves the reflected class type for the assigned local.luajava.bindClass("java.util.Locale")resolves the reflected class type for the assigned local.
A JVM lsp4j server is available under io.github.dingyi222666.luaparser.lsp and can be launched over stdio:
./gradlew runLuaLanguageServerThe server maps workspace-backed diagnostics, hover, completion, and goto-definition queries through LuaWorkspaceQueryFacade. Workspace symbol search (workspace/symbol) matches case-insensitively and ranks name-prefix matches ahead of mid-name substring matches.
Example client settings payload:
{
"androlua.imports": ["String", "LinearLayout"],
"jvm.classpath": ["build/test-libs/app.jar"],
"jvm.androidJar": "/opt/android/platforms/android-34/android.jar",
"jvm.importPrefixes": ["java.lang", "android.widget"]
}See docs/language-server-usage.md for capabilities and request examples.
- Production readiness guide: setup, usage, verification boundaries, known limitations, and extension points.
- Android-Lua architecture notes
- Android-Lua import and LuaJava behavior
- Android-Lua compatibility matrix
- Android-Lua library model notes
- Android-Lua corpus verification
- Android platform setup
- Java interop model guide
- JVM reflection and classloader design
- JVM language-server usage
- Semantic compatibility policy
- Test strategy and inventory
./gradlew checkis the default local validation path.- Native host test execution is opt-in with
-PrunNativeHostTests=true. - JVM interop coverage lives in
src/jvmTest/kotlin/interop/jvm/JvmWorkspaceEngineTest.kt; LSP coverage lives insrc/jvmTest/kotlin/lsp/LuaLanguageServiceTest.kt.
Releases go to Maven Central through the manual publish workflow (.github/workflows/publish.yml, "Run workflow" only). Bump publishVersion in build.gradle.kts before every run — Maven Central forbids re-uploading an existing version.