{"id":51851809,"url":"https://github.com/sproctor/lua-kmp","last_synced_at":"2026-07-23T20:30:55.544Z","repository":{"id":372545156,"uuid":"1307875186","full_name":"sproctor/lua-kmp","owner":"sproctor","description":"A Kotlin Multiplatform library that embeds the reference Lua 5.5 interpreter and exposes an idiomatic Kotlin API across JVM, Android, and Apple/Linux/Windows native targets.","archived":false,"fork":false,"pushed_at":"2026-07-21T23:28:29.000Z","size":396,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-22T00:21:27.945Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sproctor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-21T15:56:46.000Z","updated_at":"2026-07-21T23:27:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sproctor/lua-kmp","commit_stats":null,"previous_names":["sproctor/lua-kmp"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/sproctor/lua-kmp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sproctor%2Flua-kmp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sproctor%2Flua-kmp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sproctor%2Flua-kmp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sproctor%2Flua-kmp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sproctor","download_url":"https://codeload.github.com/sproctor/lua-kmp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sproctor%2Flua-kmp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35816503,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-23T02:00:06.683Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-23T20:30:54.957Z","updated_at":"2026-07-23T20:30:55.538Z","avatar_url":"https://github.com/sproctor.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lua-kmp\n\nKotlin Multiplatform bindings for the **reference Lua 5.5 interpreter** — one\ninterpreter, one language version, on every target. The vendored Lua C sources\nare compiled into each artifact (via cinterop on Kotlin/Native, via JNI on the\nJVM and Android), so scripts behave identically everywhere, including iOS.\n\n```kotlin\nLuaState().use { lua -\u003e\n    lua.eval(\"return 1 + 1\")   // [LuaValue.Integer(2)]\n}\n```\n\n## Supported platforms\n\n| Target | Backend | Native code |\n| --- | --- | --- |\n| `jvm` (macOS arm64/x64, Linux x64/arm64, Windows x64) | JNI | bundled in the jar, extracted at runtime |\n| `androidTarget` (arm64-v8a, armeabi-v7a, x86_64; minSdk 24) | JNI | built by the NDK, packaged in the AAR |\n| `iosArm64`, `iosSimulatorArm64` | cinterop | compiled into the klib |\n| `macosArm64`, `macosX64` | cinterop | compiled into the klib |\n| `linuxX64` | cinterop | compiled into the klib |\n| `mingwX64` | cinterop | compiled into the klib |\n\nNot planned for v1: LuaJIT (cannot follow 5.5 semantics), JS/wasm targets, and\nautomatic reflective binding of Kotlin objects — host functions are explicit.\n\n## Installation\n\n```kotlin\nkotlin {\n    sourceSets {\n        commonMain.dependencies {\n            implementation(\"com.seanproctor:lua-kmp:\u003cversion\u003e\")\n        }\n    }\n}\n```\n\n## Usage\n\n### Evaluating scripts\n\n```kotlin\nimport com.seanproctor.lua.*\n\nLuaState().use { lua -\u003e\n    // eval returns everything the chunk returns, marshalled to LuaValue.\n    val results = lua.eval(\"return 1 + 1, 'hello', 3.0\")\n    // [Integer(2), Str(\"hello\"), Number(3.0)] — the 5.5 integer/float\n    // distinction is preserved: 3 is Integer, 3.0 is Number.\n\n    lua.setGlobal(\"greeting\", LuaValue.Str(\"hi\"))\n    lua.eval(\"assert(greeting == 'hi')\")\n\n    // load compiles without running; call it later, with arguments.\n    val chunk = lua.load(\"local a, b = ... ; return a + b\")\n    lua.call(chunk, listOf(LuaValue.Integer(2), LuaValue.Integer(3)))  // [Integer(5)]\n}\n```\n\n### Tables\n\nTables cross the boundary as opaque handles backed by the Lua registry, so\nreads and writes are always live — no copying unless you ask for it.\n\n```kotlin\nval t = lua.eval(\"return {greeting = 'hello'}\").single() as LuaValue.Table\nt[LuaValue.Str(\"greeting\")]                      // Str(\"hello\")\nt[LuaValue.Str(\"count\")] = LuaValue.Integer(1)   // visible to Lua immediately\nt.size                                           // the # operator\nt.toMap()                                        // shallow copy as a Kotlin Map\n```\n\n### Host functions\n\nRegister Kotlin functions and call them from Lua. Arguments arrive already\nmarshalled; returned values are pushed back. Handlers may throw — the\nexception becomes a regular Lua error (catchable with `pcall`), and if the\nscript does not catch it, it surfaces to the caller as `LuaRuntimeError`.\n\n```kotlin\nlua.register(\"greet\") { args -\u003e\n    val name = (args.first() as LuaValue.Str).value\n    listOf(LuaValue.Str(\"hello, $name\"))\n}\nlua.eval(\"print(greet('world'))\")\n\n// wrap creates a function value without naming it, e.g. to put in a table:\nval api = lua.eval(\"api = {} ; return api\").single() as LuaValue.Table\napi[LuaValue.Str(\"double\")] = lua.wrap { args -\u003e\n    listOf(LuaValue.Integer((args.single() as LuaValue.Integer).value * 2))\n}\n```\n\nHost functions work inside Lua coroutines too.\n\n## Sandboxing\n\nBy default only a safe subset of the standard library is opened. Selection\nuses Lua 5.5's `luaL_openselectedlibs` — unselected libraries are never\nloaded, not loaded-then-hidden.\n\n```kotlin\n// Default: BASE, COROUTINE, TABLE, STRING, MATH, UTF8 (no io/os/package/debug).\nLuaState()\n\n// Everything, including io and os:\nLuaState(LuaConfig(stdlibs = StdLib.ALL))\n\n// Or any explicit subset:\nLuaState(LuaConfig(stdlibs = setOf(StdLib.BASE, StdLib.MATH)))\n```\n\nCaveat: the base library itself includes `dofile` and `loadfile`, which read\nfrom the filesystem. If your sandbox must forbid all file access, clear them\nafter construction:\n\n```kotlin\nlua.eval(\"dofile = nil ; loadfile = nil\")\n```\n\n## Errors\n\nAll failures surface as one hierarchy:\n\n| Exception | Meaning |\n| --- | --- |\n| `LuaSyntaxError` | chunk failed to compile |\n| `LuaRuntimeError` | script/function raised at run time (incl. uncaught host-function exceptions) |\n| `LuaMemoryError` | allocation failure in the interpreter |\n| `LuaException` | base class (also: unsupported value marshalling) |\n\nUsing a closed state, or touching it from the wrong thread, throws\n`IllegalStateException`. Passing another state's handle throws\n`IllegalArgumentException`.\n\n## Threading and lifetime\n\n- A `LuaState` is **confined to the thread that created it**; there is no\n  internal locking. Cross-thread use throws immediately.\n- `close()` frees the interpreter and every pinned host-function reference.\n  Idempotent. All `Table`/`Function` handles die with their state.\n- Handles are registry-backed: they stay valid across stack operations but\n  pin their Lua values until `close()`. Holding very many handles delays\n  collection of those values; an explicit per-handle `release()` is planned\n  post-v1.\n\n## Lua 5.5 notes for script authors\n\nlua-kmp embeds Lua 5.5 (currently 5.5.0, vendored and pinned by checksum;\nmoving to 5.5.1 once it leaves release candidate is tracked as a follow-up).\nIf your scripts come from 5.4, the main script-facing changes are:\n\n- **Declarations for global variables** — chunks can declare globals\n  explicitly (`global x`); see §3.3.9 of the manual.\n- **For-loop control variables are read-only** — assigning to them is now a\n  compile-time error.\n- **Float printing changed** — floats print in decimal with enough digits to\n  read back exactly (`0.1` no longer prints as `0.1` rounded to 14 digits).\n- Also new: deeper table constructors, `table.create`, extended\n  `utf8.offset`, and incremental major GC. Binary chunks are refused by this\n  binding (`load` accepts text only).\n\n## Sample\n\nA runnable feature tour lives in [`sample/`](sample/): the same `commonMain`\ndemo runs on the JVM (`./gradlew :sample:runJvm`) and as a native executable\n(`./gradlew :sample:runDebugExecutableLinuxX64`, `...MacosArm64`, …),\nprinting identical output on every backend.\n\n## Building from source\n\nRequirements: JDK 17+, a C toolchain (`cc`/gcc/clang; MinGW gcc on Windows),\nand for the Android target an SDK with NDK (set `ANDROID_HOME` or\n`local.properties`; without one, the Android target is skipped).\n\n```sh\n./gradlew build                # everything buildable on this host, plus tests\n./gradlew linuxX64Test jvmTest # the local test matrix on Linux\n./gradlew assembleRelease      # the Android AAR\n```\n\n- Apple targets build and test on macOS hosts only.\n- `native/revendor.sh` re-downloads and verifies the pinned Lua release into\n  `native/lua/` (update `LUA_VERSION`/`LUA_SHA256` there to move versions).\n- The vendored sources compile once per target: cinterop packs them into each\n  Kotlin/Native klib (see `src/nativeInterop/cinterop/lua.def`), CMake builds\n  the Android `.so`, and a per-host Gradle task builds the JVM JNI library\n  into `build/jniStaging/`, which is bundled into the jvm jar under\n  `native/\u003cos\u003e-\u003carch\u003e/`.\n\n## Releasing\n\nTag `v*` and CI publishes every target to Maven Central (portal API) from a\nmacOS runner, merging the JVM native libraries staged by the Linux, macOS,\nand Windows jobs. Required repository secrets: `MAVEN_CENTRAL_USERNAME`,\n`MAVEN_CENTRAL_PASSWORD`, `SIGNING_IN_MEMORY_KEY` (base64 of the binary\nsecret keyring, e.g. `base64 -w0 \u003c secring.gpg`), `SIGNING_IN_MEMORY_KEY_ID`\n(the short 8-char key id), and `SIGNING_IN_MEMORY_KEY_PASSWORD`.\n\n## License\n\nMIT. Bundles the Lua 5.5 sources, © 1994–2025 Lua.org, PUC-Rio, also MIT;\nthe Lua copyright notice ships in all distributions (`native/lua/LICENSE`).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsproctor%2Flua-kmp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsproctor%2Flua-kmp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsproctor%2Flua-kmp/lists"}