├── .github └── workflows │ ├── docs.yml │ ├── linux.yml │ └── macos.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── build.zig.zon ├── src ├── Buffer.zig ├── Function.zig ├── State.zig ├── Table.zig ├── Value.zig ├── arithmetic.zig ├── lunaro.zig └── safety.zig └── test └── test.zig /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: Setup Zig 16 | uses: goto-bus-stop/setup-zig@v2 17 | with: 18 | version: master 19 | 20 | - name: Install Lua 21 | run: sudo apt-get install liblua5.4-dev 22 | 23 | - name: Build Documentation 24 | run: zig build autodoc 25 | 26 | - name: Upload artifact 27 | uses: actions/upload-pages-artifact@v2 28 | with: 29 | path: zig-out/docs 30 | 31 | deploy: 32 | needs: build 33 | permissions: 34 | pages: write 35 | id-token: write 36 | 37 | environment: 38 | name: github-pages 39 | url: ${{ steps.deployment.outputs.page_url }} 40 | 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Deploy to Pages 44 | id: deployment 45 | uses: actions/deploy-pages@v2 -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**.zig" 7 | pull_request: 8 | paths: 9 | - "**.zig" 10 | workflow_dispatch: 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Zig 20 | uses: goto-bus-stop/setup-zig@v2 21 | with: 22 | version: master 23 | 24 | - name: Install Lua 25 | run: sudo apt-get install liblua5.1-dev liblua5.2-dev liblua5.3-dev liblua5.4-dev libluajit-5.1-dev 26 | 27 | - name: Test Lua 5.1 28 | run: zig build test -Dlua=lua51 29 | 30 | - name: Test Lua 5.2 31 | run: zig build test -Dlua=lua52 32 | 33 | - name: Test Lua 5.3 34 | run: zig build test -Dlua=lua53 35 | 36 | - name: Test Lua 5.4 37 | run: zig build test -Dlua=lua54 38 | 39 | - name: Test Luajit 40 | run: zig build test -Dlua=luajit -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: Macos 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**.zig" 7 | pull_request: 8 | paths: 9 | - "**.zig" 10 | workflow_dispatch: 11 | 12 | jobs: 13 | test: 14 | runs-on: macos-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Zig 20 | uses: goto-bus-stop/setup-zig@v2 21 | with: 22 | version: master 23 | 24 | - name: Install Lua 25 | run: brew install luajit 26 | 27 | - name: Test Lua 5.1 28 | run: zig build test -Dlua=lua51 -Dtest-system-lib=false 29 | 30 | - name: Test Lua 5.2 31 | run: zig build test -Dlua=lua52 -Dtest-system-lib=false 32 | 33 | - name: Test Lua 5.3 34 | run: zig build test -Dlua=lua53 -Dtest-system-lib=false 35 | 36 | - name: Test Lua 5.4 37 | run: zig build test -Dlua=lua54 -Dtest-system-lib=false 38 | 39 | - name: Test Luajit 40 | run: zig build test -Dlua=luajit -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-out 2 | .zig-cache -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2023 truemedian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Lunaro 3 | 4 | [![Linux Workflow Status](https://img.shields.io/github/actions/workflow/status/truemedian/lunaro/linux.yml?style=for-the-badge&label=Linux)](https://github.com/truemedian/hzzp/actions/workflows/linux.yml) 5 | 6 | Bindings to the [Lua](https://www.lua.org/) C API. 7 | 8 | The primary goal of Lunaro to provide a stable, agnostic, and idiomatic interface to the Lua C API. 9 | Code using Lunaro can link against any Lua >= 5.1 library and work without issue. 10 | 11 | ## Documentation 12 | 13 | Documentation is available at [pages.truemedian.me/lunaro](https://pages.truemedian.me/lunaro/#test.lunaro). 14 | 15 | ## Installation 16 | 17 | Add lunaro to your list of dependencies in `build.zig.zon` by adding it to the list of dependencies. 18 | 19 | ```zig 20 | .{ 21 | .name = "project", 22 | .version = "0.0.1", 23 | .dependencies = .{ 24 | .lunaro = .{ 25 | .url = "https://github.com/truemedian/lunaro/archive/master.tar.gz", 26 | }, 27 | }, 28 | } 29 | ``` 30 | 31 | The following sections contain code for a `build.zig` for the different ways to link against Lua. 32 | 33 | ### As a Library 34 | 35 | First, link against Lua dynamically (as seen in the [Linking](#linking) section). 36 | 37 | Then add the following to your library: 38 | 39 | ```zig 40 | const lunaro = @import("lunaro"); 41 | 42 | ... 43 | 44 | fn mylibrary(L: *lunaro.State) c_int { 45 | ... 46 | } 47 | 48 | comptime { 49 | _ = lunaro.exportAs(mylibrary, "mylibrary"); 50 | } 51 | ``` 52 | 53 | This exports the `mylibrary` function (following `lunaro.wrapFn` rules) as `luaopen_mylibrary` so that it can be required from lua. 54 | 55 | ### Linking 56 | 57 | #### Using System Libraries 58 | 59 | ```zig 60 | const lunaro = b.dependency("lunaro", .{}); 61 | const lunaro_module = lunaro.module("lunaro"); 62 | 63 | lunaro_module.linkSystemLibrary("lua", .{ .use_pkg_config = .force }); // or whatever the name of the lua library is under pkg-config 64 | 65 | // // if pkg-config isn't available, you'll need to add the include path and library path manually 66 | // lunaro_module.addIncludePath(.{ .cwd_relative = "/usr/include/lua5.3" }); // this directory should contain lua.h 67 | // lunaro_module.addLibraryPath(.{ .cwd_relative = "/usr/lib/lua5.3" }); // this directory should contain the required liblua.so 68 | // lunaro_module.linkSystemLibrary("lua5.3", .{}); // this should be the name of the lua library to link against 69 | 70 | exe.root_module.addImport("lunaro", lunaro_module); 71 | ``` 72 | 73 | #### Using a compiled dynamic library 74 | 75 | ```zig 76 | const lunaro = b.dependency("lunaro", .{ 77 | .lua = .lua51, // request the version of lua here, valid values are: lua51, lua52, lua53, lua54, luajit 78 | .shared = true, // build a shared library 79 | // .strip = true, // strip all debug information from the lua library 80 | // .target = ... // build lua for a non-native target 81 | }); 82 | 83 | const liblua = lunaro.artifact("liblua"); 84 | const lunaro_module = lunaro.module("lunaro"); 85 | lunaro_module.linkLibrary(liblua); 86 | 87 | exe.root_module.addImport("lunaro", lunaro_module); 88 | ``` 89 | 90 | ### Using a compiled static library 91 | 92 | ```zig 93 | const lunaro = b.dependency("lunaro", .{ 94 | .lua = .lua51, // request the version of lua here, valid values are: lua51, lua52, lua53, lua54, luajit 95 | .shared = false, // build a static library 96 | // .strip = true, // strip all debug information from the lua library 97 | // .target = ... // build lua for a non-native target 98 | }); 99 | 100 | const liblua = lunaro.artifact("liblua"); 101 | const lunaro_module = lunaro.module("lunaro"); 102 | lunaro_module.linkLibrary(liblua); 103 | 104 | exe.root_module.addImport("lunaro", lunaro_module); 105 | ``` 106 | 107 | ## Differences 108 | 109 | For the most part, Lunaro's API is close to the Lua C API with a few exceptions. 110 | The Lua 5.3 API was the base for Lunaro, any function that has a direct replacement in 5.3 has been replaced with that function (for example: `lua_equal` is replaced with `lua_compare`). 111 | 112 | ### Naming 113 | 114 | All functions have been stripped of their `lua_` and `luaL_` prefixes. These functions are all in the `State` or `Buffer` namespaces. 115 | Most functions have been slightly renamed, or removed in favor of more idiomatic Zig. 116 | 117 | However, function names still follow the lua convention of all lowercase with no separation between words. 118 | 119 | | Lua C API | Lunaro | 120 | |---------------------|--------------------------| 121 | | `lua_newstate` | `State.initWithAlloc` | 122 | | `lua_type` | `State.typeof` | 123 | | `lua_error` | `State.throw` | 124 | | `luaL_checktype` | `State.ensuretype` | 125 | | `luaL_checkany` | `State.ensureexists` | 126 | | `luaL_checkstack` | `State.ensurestack` | 127 | | `luaL_newmetatable` | `State.newmetattablefor` | 128 | | `luaL_setmetatable` | `State.setmetatablefor` | 129 | | `luaL_error` | `State.raise` | 130 | | `luaL_loadbuffer` | `State.loadstring` | 131 | | `luaL_newstate` | `State.init` | 132 | | `luaL_len` | `State.lenof` | 133 | | `luaL_typename` | `State.typenameof` | 134 | | `luaL_getmetatable` | `State.getmetablefor` | 135 | | `luaL_checkudata` | `State.checkResource` | 136 | 137 | ### Missing Functions 138 | 139 | `lua_tolstring` has been replaced with `tostring` 140 | `lua_pushlstring` has been replaced with `pushstring` and `pushstringExtra` 141 | `luaL_loadstring` has been replaced with `State.loadstring` 142 | 143 | `lua_gc` is not yet implemented. 144 | 145 | `lua_isyieldable` is not implementable in Lua 5.1 and 5.2. 146 | `lua_setwarnf` is not implementable in Lua 5.1, 5.2 and 5.3 without polyfilling the entire warning API. 147 | `lua_warning` is not implementable in Lua 5.1, 5.2 and 5.3 without polyfilling the entire warning API. 148 | `lua_upvalueid` is not implementable in Lua 5.1, 5.2 and 5.3. 149 | `lua_upvaluejoin` is not implementable in Lua 5.1, 5.2 and 5.3. 150 | 151 | `luaL_argerror` replaced with `State.check`. 152 | `luaL_typeerror` replaced with `State.check`. 153 | `luaL_checknumber` replaced with `State.check`. 154 | `luaL_optnumber` replaced with `State.check`. 155 | `luaL_checkinteger` replaced with `State.check`. 156 | `luaL_optinteger` replaced with `State.check`. 157 | `luaL_checklstring` replaced with `State.check`. 158 | `luaL_optlstring` replaced with `State.check`. 159 | `luaL_checkoption` replaced with `State.check`. 160 | `luaL_testudata` replaced with `State.checkResource`. 161 | 162 | ### Additions 163 | 164 | To deal with the change in `LUA_GLOBALSINDEX` and `LUA_RIDX_GLOBALS`, `State.pushglobaltable` is provided to push the global table onto the stack. 165 | 166 | A generic `State.push` function is provided to push any type onto the stack, include structs as a table. 167 | A generic `State.check` and `State.checkAlloc` function is provided to check the type of any value on the stack. Usually used to check arguments of a function. 168 | 169 | ### Resource Mechanism 170 | 171 | Lunaro provides a mechanism to manage userdata resources in Lua. 172 | 173 | `State.registerResource` registers a type as a resource and creates a metatable for it. 174 | `State.resource` creates a resource (from a registered type) and pushes it onto the stack and returns a pointer to it. 175 | `State.checkResource` checks if the value on the stack is a resource of the given type and returns a pointer to it. 176 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const LuaVersion = enum { 2 | lua51, 3 | lua52, 4 | lua53, 5 | lua54, 6 | luajit, 7 | }; 8 | 9 | fn findLiblua(b: *Build) ?*Build.Step.Compile { 10 | var found: ?*Build.Step.Compile = null; 11 | for (b.install_tls.step.dependencies.items) |dep_step| { 12 | const inst = dep_step.cast(Build.Step.InstallArtifact) orelse continue; 13 | if (std.mem.eql(u8, inst.artifact.name, "liblua")) { 14 | found = inst.artifact; 15 | } 16 | } 17 | return found; 18 | } 19 | 20 | pub fn build(b: *Build) void { 21 | const requested_lua = b.option(LuaVersion, "lua", "Version of lua to build against") orelse .lua54; 22 | 23 | const module = b.addModule("lunaro", .{ .root_source_file = b.path("src/lunaro.zig") }); 24 | 25 | switch (requested_lua) { 26 | .lua51 => buildLua51(b), 27 | .lua52 => buildLua52(b), 28 | .lua53 => buildLua53(b), 29 | .lua54 => buildLua54(b), 30 | .luajit => buildLuajit(b), 31 | } 32 | 33 | // Lunaro Tests 34 | 35 | const test_step = b.step("test", "Run tests"); 36 | const test_system = b.option(bool, "test-system", "Run the lunaro tests against the system lua library") orelse false; 37 | 38 | const test_library = b.addTest(.{ 39 | .root_source_file = b.path("src/lunaro.zig"), 40 | }); 41 | 42 | const example_shared_exe = b.addExecutable(.{ 43 | .target = b.host, 44 | .name = "example", 45 | .root_source_file = b.path("test/test.zig"), 46 | }); 47 | 48 | example_shared_exe.root_module.addImport("lunaro", module); 49 | 50 | const run_example_exe = b.addRunArtifact(example_shared_exe); 51 | run_example_exe.expectExitCode(0); 52 | 53 | const install_example_exe = b.addInstallArtifact(example_shared_exe, .{}); 54 | run_example_exe.step.dependOn(&install_example_exe.step); 55 | 56 | test_step.dependOn(&test_library.step); 57 | test_step.dependOn(&run_example_exe.step); 58 | 59 | const autodoc_test = b.addTest(.{ 60 | .root_source_file = b.path("src/lunaro.zig"), 61 | }); 62 | 63 | const install_docs = b.addInstallDirectory(.{ 64 | .source_dir = autodoc_test.getEmittedDocs(), 65 | .install_dir = .prefix, 66 | .install_subdir = "docs", 67 | }); 68 | 69 | const autodoc_step = b.step("autodoc", "Generate documentation"); 70 | autodoc_step.dependOn(&install_docs.step); 71 | 72 | if (test_system) { 73 | module.linkSystemLibrary(@tagName(requested_lua), .{}); 74 | 75 | test_library.linkLibC(); 76 | test_library.root_module.linkSystemLibrary(@tagName(requested_lua), .{}); 77 | 78 | autodoc_test.linkLibC(); 79 | autodoc_test.root_module.linkSystemLibrary(@tagName(requested_lua), .{}); 80 | } else if (findLiblua(b)) |liblua| { 81 | module.linkLibrary(liblua); 82 | test_library.linkLibrary(liblua); 83 | 84 | autodoc_test.root_module.linkLibrary(liblua); 85 | } 86 | } 87 | 88 | // https://github.com/truemedian/allmycodebase/blob/master/lua51/build.zig 89 | pub fn buildLua51(b: *Build) void { 90 | const target = b.standardTargetOptions(.{}); 91 | const optimize = b.standardOptimizeOption(.{}); 92 | 93 | const shared = b.option(bool, "shared", "Build Lua as a shared library.") orelse false; 94 | const strip = b.option(bool, "strip", "Strip debug information from lua builds") orelse true; 95 | const upstream = b.lazyDependency("lua51", .{}) orelse return; 96 | 97 | const liblua = if (shared) b.addSharedLibrary(.{ 98 | .name = "liblua", 99 | .target = target, 100 | .optimize = optimize, 101 | .link_libc = true, 102 | .strip = strip, 103 | 104 | .version = .{ .major = 5, .minor = 1, .patch = 5 }, 105 | }) else b.addStaticLibrary(.{ 106 | .name = "liblua", 107 | .target = target, 108 | .optimize = optimize, 109 | .link_libc = true, 110 | .strip = strip, 111 | 112 | .version = .{ .major = 5, .minor = 1, .patch = 5 }, 113 | }); 114 | 115 | liblua.root_module.sanitize_c = false; 116 | liblua.root_module.linkSystemLibrary("m", .{}); 117 | 118 | liblua.addCSourceFiles(.{ 119 | .root = upstream.path("src"), 120 | .files = &.{ 121 | "lapi.c", "lcode.c", "ldebug.c", "ldo.c", "ldump.c", 122 | "lfunc.c", "lgc.c", "llex.c", "lmem.c", "lobject.c", 123 | "lopcodes.c", "lparser.c", "lstate.c", "lstring.c", "ltable.c", 124 | "ltm.c", "lundump.c", "lvm.c", "lzio.c", "lauxlib.c", 125 | "lbaselib.c", "ldblib.c", "liolib.c", "lmathlib.c", "loslib.c", 126 | "ltablib.c", "lstrlib.c", "loadlib.c", "linit.c", 127 | }, 128 | }); 129 | 130 | liblua.installHeader(upstream.path("src/lua.h"), "lua.h"); 131 | liblua.installHeader(upstream.path("etc/lua.hpp"), "lua.hpp"); 132 | liblua.installHeader(upstream.path("src/luaconf.h"), "luaconf.h"); 133 | liblua.installHeader(upstream.path("src/lualib.h"), "lualib.h"); 134 | liblua.installHeader(upstream.path("src/lauxlib.h"), "lauxlib.h"); 135 | 136 | const luac = b.addExecutable(.{ 137 | .name = "luac", 138 | .target = target, 139 | .optimize = optimize, 140 | .link_libc = true, 141 | .strip = strip, 142 | }); 143 | 144 | luac.root_module.sanitize_c = false; 145 | luac.linkLibrary(liblua); 146 | luac.addCSourceFiles(.{ 147 | .root = upstream.path("src"), 148 | .files = &.{ "luac.c", "print.c" }, 149 | }); 150 | 151 | const lua = b.addExecutable(.{ 152 | .name = "lua", 153 | .target = target, 154 | .optimize = optimize, 155 | .link_libc = true, 156 | .strip = strip, 157 | }); 158 | 159 | lua.root_module.sanitize_c = false; 160 | lua.linkLibrary(liblua); 161 | lua.addCSourceFiles(.{ 162 | .root = upstream.path("src"), 163 | .files = &.{"lua.c"}, 164 | }); 165 | 166 | if (target.result.isDarwin()) { 167 | liblua.root_module.addCMacro("LUA_USE_MACOSX", "1"); 168 | luac.root_module.addCMacro("LUA_USE_MACOSX", "1"); 169 | lua.root_module.addCMacro("LUA_USE_MACOSX", "1"); 170 | } else if (target.result.os.tag != .windows) { 171 | liblua.root_module.addCMacro("LUA_USE_POSIX", "1"); 172 | luac.root_module.addCMacro("LUA_USE_POSIX", "1"); 173 | lua.root_module.addCMacro("LUA_USE_POSIX", "1"); 174 | 175 | liblua.root_module.linkSystemLibrary("dl", .{}); 176 | liblua.root_module.addCMacro("LUA_USE_DLOPEN", "1"); 177 | } 178 | 179 | if (optimize == .Debug) { 180 | liblua.root_module.addCMacro("LUA_USE_APICHECK", "1"); 181 | } 182 | 183 | b.installArtifact(lua); 184 | if (!shared) // we can only provide luac when static linking 185 | b.installArtifact(luac); 186 | 187 | b.getInstallStep().dependOn(&b.addInstallArtifact(liblua, .{ 188 | .dest_sub_path = b.fmt("liblua{s}", .{ 189 | if (shared) 190 | target.result.dynamicLibSuffix() 191 | else 192 | target.result.staticLibSuffix(), 193 | }), 194 | }).step); 195 | } 196 | 197 | // https://github.com/truemedian/allmycodebase/blob/master/lua52/build.zig 198 | pub fn buildLua52(b: *Build) void { 199 | const target = b.standardTargetOptions(.{}); 200 | const optimize = b.standardOptimizeOption(.{}); 201 | 202 | const shared = b.option(bool, "shared", "Build Lua as a shared library.") orelse false; 203 | const strip = b.option(bool, "strip", "Strip debug information from lua builds") orelse true; 204 | const upstream = b.lazyDependency("lua52", .{}) orelse return; 205 | 206 | const liblua = if (shared) b.addSharedLibrary(.{ 207 | .name = "liblua", 208 | .target = target, 209 | .optimize = optimize, 210 | .link_libc = true, 211 | .strip = strip, 212 | 213 | .version = .{ .major = 5, .minor = 2, .patch = 4 }, 214 | }) else b.addStaticLibrary(.{ 215 | .name = "liblua", 216 | .target = target, 217 | .optimize = optimize, 218 | .link_libc = true, 219 | .strip = strip, 220 | 221 | .version = .{ .major = 5, .minor = 2, .patch = 4 }, 222 | }); 223 | 224 | liblua.root_module.sanitize_c = false; 225 | liblua.root_module.linkSystemLibrary("m", .{}); 226 | 227 | liblua.addCSourceFiles(.{ 228 | .root = upstream.path("src"), 229 | .files = &.{ 230 | "lapi.c", "lcode.c", "lctype.c", "ldebug.c", "ldo.c", 231 | "ldump.c", "lfunc.c", "lgc.c", "llex.c", "lmem.c", 232 | "lobject.c", "lopcodes.c", "lparser.c", "lstate.c", "lstring.c", 233 | "ltable.c", "ltm.c", "lundump.c", "lvm.c", "lzio.c", 234 | "lauxlib.c", "lbaselib.c", "lbitlib.c", "lcorolib.c", "ldblib.c", 235 | "liolib.c", "lmathlib.c", "loslib.c", "ltablib.c", "lstrlib.c", 236 | "loadlib.c", "linit.c", 237 | }, 238 | }); 239 | 240 | liblua.installHeader(upstream.path("src/lua.h"), "lua.h"); 241 | liblua.installHeader(upstream.path("src/lua.hpp"), "lua.hpp"); 242 | liblua.installHeader(upstream.path("src/luaconf.h"), "luaconf.h"); 243 | liblua.installHeader(upstream.path("src/lualib.h"), "lualib.h"); 244 | liblua.installHeader(upstream.path("src/lauxlib.h"), "lauxlib.h"); 245 | 246 | const luac = b.addExecutable(.{ 247 | .name = "luac", 248 | .target = target, 249 | .optimize = optimize, 250 | .link_libc = true, 251 | .strip = strip, 252 | }); 253 | 254 | luac.root_module.sanitize_c = false; 255 | luac.linkLibrary(liblua); 256 | luac.addCSourceFiles(.{ 257 | .root = upstream.path("src"), 258 | .files = &.{"luac.c"}, 259 | }); 260 | 261 | const lua = b.addExecutable(.{ 262 | .name = "lua", 263 | .target = target, 264 | .optimize = optimize, 265 | .link_libc = true, 266 | .strip = strip, 267 | }); 268 | 269 | lua.root_module.sanitize_c = false; 270 | lua.linkLibrary(liblua); 271 | lua.addCSourceFiles(.{ 272 | .root = upstream.path("src"), 273 | .files = &.{"lua.c"}, 274 | }); 275 | 276 | liblua.root_module.addCMacro("LUA_COMPAT_ALL", "1"); 277 | luac.root_module.addCMacro("LUA_COMPAT_ALL", "1"); 278 | lua.root_module.addCMacro("LUA_COMPAT_ALL", "1"); 279 | 280 | if (target.result.isDarwin()) { 281 | liblua.root_module.addCMacro("LUA_USE_MACOSX", "1"); 282 | luac.root_module.addCMacro("LUA_USE_MACOSX", "1"); 283 | lua.root_module.addCMacro("LUA_USE_MACOSX", "1"); 284 | 285 | liblua.root_module.linkSystemLibrary("readline", .{}); 286 | } else if (target.result.os.tag != .windows) { 287 | liblua.root_module.addCMacro("LUA_USE_POSIX", "1"); 288 | luac.root_module.addCMacro("LUA_USE_POSIX", "1"); 289 | lua.root_module.addCMacro("LUA_USE_POSIX", "1"); 290 | 291 | liblua.root_module.linkSystemLibrary("dl", .{}); 292 | liblua.root_module.addCMacro("LUA_USE_DLOPEN", "1"); 293 | liblua.root_module.addCMacro("LUA_USE_STRTODHEX", "1"); 294 | liblua.root_module.addCMacro("LUA_USE_AFORMAT", "1"); 295 | liblua.root_module.addCMacro("LUA_USE_LONGLONG", "1"); 296 | } 297 | 298 | if (optimize == .Debug) { 299 | liblua.root_module.addCMacro("LUA_USE_APICHECK", "1"); 300 | } 301 | 302 | b.installArtifact(lua); 303 | if (!shared) // we can only provide luac when static linking 304 | b.installArtifact(luac); 305 | 306 | b.getInstallStep().dependOn(&b.addInstallArtifact(liblua, .{ 307 | .dest_sub_path = b.fmt("liblua{s}", .{ 308 | if (shared) 309 | target.result.dynamicLibSuffix() 310 | else 311 | target.result.staticLibSuffix(), 312 | }), 313 | }).step); 314 | } 315 | 316 | // https://github.com/truemedian/allmycodebase/blob/master/lua53/build.zig 317 | pub fn buildLua53(b: *Build) void { 318 | const target = b.standardTargetOptions(.{}); 319 | const optimize = b.standardOptimizeOption(.{}); 320 | 321 | const shared = b.option(bool, "shared", "Build Lua as a shared library.") orelse false; 322 | const strip = b.option(bool, "strip", "Strip debug information from lua builds") orelse true; 323 | const upstream = b.lazyDependency("lua53", .{}) orelse return; 324 | 325 | const liblua = if (shared) b.addSharedLibrary(.{ 326 | .name = "liblua", 327 | .target = target, 328 | .optimize = optimize, 329 | .link_libc = true, 330 | .strip = strip, 331 | 332 | .version = .{ .major = 5, .minor = 3, .patch = 6 }, 333 | }) else b.addStaticLibrary(.{ 334 | .name = "liblua", 335 | .target = target, 336 | .optimize = optimize, 337 | .link_libc = true, 338 | .strip = strip, 339 | 340 | .version = .{ .major = 5, .minor = 3, .patch = 6 }, 341 | }); 342 | 343 | liblua.root_module.sanitize_c = false; 344 | liblua.root_module.linkSystemLibrary("m", .{}); 345 | 346 | liblua.addCSourceFiles(.{ 347 | .root = upstream.path("src"), 348 | .files = &.{ 349 | "lapi.c", "lcode.c", "lctype.c", "ldebug.c", "ldo.c", 350 | "ldump.c", "lfunc.c", "lgc.c", "llex.c", "lmem.c", 351 | "lobject.c", "lopcodes.c", "lparser.c", "lstate.c", "lstring.c", 352 | "ltable.c", "ltm.c", "lundump.c", "lvm.c", "lzio.c", 353 | "lauxlib.c", "lbaselib.c", "lbitlib.c", "lcorolib.c", "ldblib.c", 354 | "liolib.c", "lmathlib.c", "loslib.c", "ltablib.c", "lutf8lib.c", 355 | "lstrlib.c", "loadlib.c", "linit.c", 356 | }, 357 | }); 358 | 359 | liblua.installHeader(upstream.path("src/lua.h"), "lua.h"); 360 | liblua.installHeader(upstream.path("src/lua.hpp"), "lua.hpp"); 361 | liblua.installHeader(upstream.path("src/luaconf.h"), "luaconf.h"); 362 | liblua.installHeader(upstream.path("src/lualib.h"), "lualib.h"); 363 | liblua.installHeader(upstream.path("src/lauxlib.h"), "lauxlib.h"); 364 | 365 | const luac = b.addExecutable(.{ 366 | .name = "luac", 367 | .target = target, 368 | .optimize = optimize, 369 | .link_libc = true, 370 | .strip = strip, 371 | }); 372 | 373 | luac.root_module.sanitize_c = false; 374 | luac.linkLibrary(liblua); 375 | luac.addCSourceFiles(.{ 376 | .root = upstream.path("src"), 377 | .files = &.{"luac.c"}, 378 | }); 379 | 380 | const lua = b.addExecutable(.{ 381 | .name = "lua", 382 | .target = target, 383 | .optimize = optimize, 384 | .link_libc = true, 385 | .strip = strip, 386 | }); 387 | 388 | lua.root_module.sanitize_c = false; 389 | lua.linkLibrary(liblua); 390 | lua.addCSourceFiles(.{ 391 | .root = upstream.path("src"), 392 | .files = &.{"lua.c"}, 393 | }); 394 | 395 | liblua.root_module.addCMacro("LUA_COMPAT_5_2", "1"); 396 | luac.root_module.addCMacro("LUA_COMPAT_5_2", "1"); 397 | lua.root_module.addCMacro("LUA_COMPAT_5_2", "1"); 398 | 399 | if (target.result.os.tag != .windows) { 400 | liblua.root_module.addCMacro("LUA_USE_POSIX", "1"); 401 | luac.root_module.addCMacro("LUA_USE_POSIX", "1"); 402 | lua.root_module.addCMacro("LUA_USE_POSIX", "1"); 403 | 404 | liblua.root_module.linkSystemLibrary("dl", .{}); 405 | liblua.root_module.addCMacro("LUA_USE_DLOPEN", "1"); 406 | } 407 | 408 | if (optimize == .Debug) { 409 | liblua.root_module.addCMacro("LUA_USE_APICHECK", "1"); 410 | } 411 | 412 | b.installArtifact(lua); 413 | if (!shared) // we can only provide luac when static linking 414 | b.installArtifact(luac); 415 | 416 | b.getInstallStep().dependOn(&b.addInstallArtifact(liblua, .{ 417 | .dest_sub_path = b.fmt("liblua{s}", .{ 418 | if (shared) 419 | target.result.dynamicLibSuffix() 420 | else 421 | target.result.staticLibSuffix(), 422 | }), 423 | }).step); 424 | } 425 | 426 | // https://github.com/truemedian/allmycodebase/blob/master/lua54/build.zig 427 | pub fn buildLua54(b: *Build) void { 428 | const target = b.standardTargetOptions(.{}); 429 | const optimize = b.standardOptimizeOption(.{}); 430 | 431 | const shared = b.option(bool, "shared", "Build Lua as a shared library.") orelse false; 432 | const strip = b.option(bool, "strip", "Strip debug information from lua builds") orelse true; 433 | const upstream = b.lazyDependency("lua54", .{}) orelse return; 434 | 435 | const liblua = if (shared) b.addSharedLibrary(.{ 436 | .name = "liblua", 437 | .target = target, 438 | .optimize = optimize, 439 | .link_libc = true, 440 | .strip = strip, 441 | 442 | .version = .{ .major = 5, .minor = 4, .patch = 7 }, 443 | }) else b.addStaticLibrary(.{ 444 | .name = "liblua", 445 | .target = target, 446 | .optimize = optimize, 447 | .link_libc = true, 448 | .strip = strip, 449 | 450 | .version = .{ .major = 5, .minor = 4, .patch = 7 }, 451 | }); 452 | 453 | liblua.root_module.sanitize_c = false; 454 | liblua.root_module.linkSystemLibrary("m", .{}); 455 | 456 | liblua.addCSourceFiles(.{ 457 | .root = upstream.path("src"), 458 | .files = &.{ 459 | "lapi.c", "lcode.c", "lctype.c", "ldebug.c", "ldo.c", 460 | "ldump.c", "lfunc.c", "lgc.c", "llex.c", "lmem.c", 461 | "lobject.c", "lopcodes.c", "lparser.c", "lstate.c", "lstring.c", 462 | "ltable.c", "ltm.c", "lundump.c", "lvm.c", "lzio.c", 463 | "lauxlib.c", "lbaselib.c", "lcorolib.c", "ldblib.c", "liolib.c", 464 | "lmathlib.c", "loslib.c", "ltablib.c", "lutf8lib.c", "lstrlib.c", 465 | "loadlib.c", "linit.c", 466 | }, 467 | }); 468 | 469 | liblua.installHeader(upstream.path("src/lua.h"), "lua.h"); 470 | liblua.installHeader(upstream.path("src/lua.hpp"), "lua.hpp"); 471 | liblua.installHeader(upstream.path("src/luaconf.h"), "luaconf.h"); 472 | liblua.installHeader(upstream.path("src/lualib.h"), "lualib.h"); 473 | liblua.installHeader(upstream.path("src/lauxlib.h"), "lauxlib.h"); 474 | 475 | const luac = b.addExecutable(.{ 476 | .name = "luac", 477 | .target = target, 478 | .optimize = optimize, 479 | .link_libc = true, 480 | .strip = strip, 481 | }); 482 | 483 | luac.root_module.sanitize_c = false; 484 | luac.linkLibrary(liblua); 485 | luac.addCSourceFiles(.{ 486 | .root = upstream.path("src"), 487 | .files = &.{"luac.c"}, 488 | }); 489 | 490 | const lua = b.addExecutable(.{ 491 | .name = "lua", 492 | .target = target, 493 | .optimize = optimize, 494 | .link_libc = true, 495 | .strip = strip, 496 | }); 497 | 498 | lua.root_module.sanitize_c = false; 499 | lua.linkLibrary(liblua); 500 | lua.addCSourceFiles(.{ 501 | .root = upstream.path("src"), 502 | .files = &.{"lua.c"}, 503 | }); 504 | 505 | liblua.root_module.addCMacro("LUA_COMPAT_5_3", "1"); 506 | luac.root_module.addCMacro("LUA_COMPAT_5_3", "1"); 507 | lua.root_module.addCMacro("LUA_COMPAT_5_3", "1"); 508 | 509 | if (target.result.os.tag != .windows) { 510 | liblua.root_module.addCMacro("LUA_USE_POSIX", "1"); 511 | luac.root_module.addCMacro("LUA_USE_POSIX", "1"); 512 | lua.root_module.addCMacro("LUA_USE_POSIX", "1"); 513 | 514 | liblua.root_module.linkSystemLibrary("dl", .{}); 515 | liblua.root_module.addCMacro("LUA_USE_DLOPEN", "1"); 516 | } 517 | 518 | if (optimize == .Debug) { 519 | liblua.root_module.addCMacro("LUA_USE_APICHECK", "1"); 520 | } 521 | 522 | b.installArtifact(lua); 523 | if (!shared) // we can only provide luac when static linking 524 | b.installArtifact(luac); 525 | 526 | b.getInstallStep().dependOn(&b.addInstallArtifact(liblua, .{ 527 | .dest_sub_path = b.fmt("liblua{s}", .{ 528 | if (shared) 529 | target.result.dynamicLibSuffix() 530 | else 531 | target.result.staticLibSuffix(), 532 | }), 533 | }).step); 534 | } 535 | 536 | // https://github.com/truemedian/allmycodebase/blob/master/luajit/build.zig 537 | pub fn buildLuajit(b: *Build) void { 538 | const target = b.standardTargetOptions(.{}); 539 | const optimize = b.standardOptimizeOption(.{}); 540 | 541 | const target_cpu = target.result.cpu; 542 | const target_os = target.result.os; 543 | 544 | const disable_compat = b.option(bool, "enable-compat52", "Enable potentially breaking Lua 5.2 compatibility features") orelse true; 545 | const disable_jit = b.option(bool, "disable-jit", "Disable JIT compilation") orelse false; 546 | const disable_ffi = b.option(bool, "disable-ffi", "Disable FFI support") orelse false; 547 | const disable_gc64 = b.option(bool, "disable-gc64", "Disable 64-bit GC") orelse false; 548 | const enable_dualnum = b.option(bool, "enable-dualnum", "Enable dual-number mode when possible") orelse false; 549 | 550 | const shared = b.option(bool, "shared", "Build Lua as a shared library.") orelse false; 551 | const strip = b.option(bool, "strip", "Strip debug information from lua builds") orelse true; 552 | const upstream = b.lazyDependency("luajit", .{}) orelse return; 553 | 554 | const liblua = if (shared) b.addSharedLibrary(.{ 555 | .name = "liblua", 556 | .target = target, 557 | .optimize = optimize, 558 | .link_libc = true, 559 | .strip = strip, 560 | 561 | .version = .{ .major = 5, .minor = 1, .patch = 0 }, 562 | .omit_frame_pointer = optimize != .Debug, 563 | }) else b.addStaticLibrary(.{ 564 | .name = "liblua", 565 | .target = target, 566 | .optimize = optimize, 567 | .link_libc = true, 568 | .strip = strip, 569 | 570 | .version = .{ .major = 5, .minor = 1, .patch = 0 }, 571 | .omit_frame_pointer = optimize != .Debug, 572 | }); 573 | 574 | const luajit = b.addExecutable(.{ 575 | .name = "luajit", 576 | .target = target, 577 | .optimize = optimize, 578 | .link_libc = true, 579 | .strip = strip, 580 | 581 | .omit_frame_pointer = optimize != .Debug, 582 | }); 583 | 584 | b.getInstallStep().dependOn(&b.addInstallArtifact(liblua, .{ 585 | .dest_sub_path = b.fmt("liblua{s}", .{ 586 | if (shared) 587 | target.result.dynamicLibSuffix() 588 | else 589 | target.result.staticLibSuffix(), 590 | }), 591 | }).step); 592 | b.installArtifact(luajit); 593 | 594 | // Build minilua for the host and run dynasm to generate buildvm_arch.h 595 | const minilua = b.addExecutable(.{ 596 | .name = "minilua", 597 | .target = b.graph.host, 598 | .optimize = optimize, 599 | .link_libc = true, 600 | }); 601 | 602 | minilua.root_module.sanitize_c = false; 603 | minilua.addCSourceFiles(.{ 604 | .root = upstream.path("src/host"), 605 | .files = &.{"minilua.c"}, 606 | }); 607 | 608 | // Generate luajit.h from luajit_rolling.h 609 | const generate_luajit_h = b.addRunArtifact(minilua); 610 | generate_luajit_h.addFileArg(upstream.path("src/host/genversion.lua")); 611 | generate_luajit_h.addFileArg(upstream.path("src/luajit_rolling.h")); 612 | generate_luajit_h.addFileArg(upstream.path(".relver")); 613 | const luajit_h = generate_luajit_h.addOutputFileArg("luajit.h"); 614 | 615 | // buildvm needs to have the same pointer size as the target 616 | var buildvm_target = b.graph.host; 617 | if (buildvm_target.result.ptrBitWidth() != target.result.ptrBitWidth()) { 618 | switch (buildvm_target.result.cpu.arch) { 619 | .x86 => buildvm_target.query.cpu_arch = .x86_64, 620 | .x86_64 => buildvm_target.query.cpu_arch = .x86, 621 | else => std.debug.panic("cross-build with mismatched pointer sizes: {s}", .{@tagName(buildvm_target.result.cpu.arch)}), 622 | } 623 | 624 | buildvm_target = b.resolveTargetQuery(.{ 625 | .cpu_arch = buildvm_target.query.cpu_arch, 626 | }); 627 | } 628 | 629 | // Build buildvm for the host to generate the target-specific headers 630 | const buildvm = b.addExecutable(.{ 631 | .name = "buildvm", 632 | .target = buildvm_target, 633 | .optimize = optimize, 634 | .link_libc = true, 635 | }); 636 | 637 | buildvm.root_module.sanitize_c = false; 638 | buildvm.addCSourceFiles(.{ 639 | .root = upstream.path("src/host"), 640 | .files = &.{ "buildvm_asm.c", "buildvm_fold.c", "buildvm_lib.c", "buildvm_peobj.c", "buildvm.c" }, 641 | }); 642 | 643 | // Generate buildvm_arch.h from the dynasm source files 644 | const dynasm_run = b.addRunArtifact(minilua); 645 | dynasm_run.addFileArg(upstream.path("dynasm/dynasm.lua")); 646 | 647 | if (target_cpu.arch.endian() == .little) { 648 | dynasm_run.addArgs(&.{ "-D", "ENDIAN_LE" }); 649 | } else { 650 | dynasm_run.addArgs(&.{ "-D", "ENDIAN_BE" }); 651 | } 652 | 653 | if (target.result.ptrBitWidth() == 64) 654 | dynasm_run.addArgs(&.{ "-D", "P64" }); 655 | 656 | // not (ios or console or disable_jit) 657 | if (target_os.tag != .ios and // LJ_TARGET_IOS 658 | target_os.tag != .ps3 and // LJ_TARGET_CONSOLE 659 | target_os.tag != .ps4 and // LJ_TARGET_CONSOLE 660 | target_os.tag != .ps5 and // LJ_TARGET_CONSOLE 661 | !disable_jit) 662 | { 663 | dynasm_run.addArgs(&.{ "-D", "JIT" }); 664 | } 665 | 666 | // not ((ppc and console) or disable_ffi) 667 | if ((!target_cpu.arch.isPowerPC() or 668 | (target_os.tag != .ps3 and 669 | target_os.tag != .ps4 and 670 | target_os.tag != .ps5)) and 671 | !disable_ffi) 672 | { 673 | dynasm_run.addArgs(&.{ "-D", "FFI" }); 674 | } 675 | 676 | if ((target_cpu.arch.isX86() and enable_dualnum) or 677 | target_cpu.arch.isARM() or 678 | target_cpu.arch.isAARCH64() or 679 | target_cpu.arch.isPowerPC() or 680 | target_cpu.arch.isMIPS()) 681 | { 682 | dynasm_run.addArgs(&.{ "-D", "DUALNUM" }); 683 | } 684 | 685 | // Check for floating point emulation 686 | if ((target_cpu.arch.isARM() and isFeatureEnabled(target_cpu, std.Target.arm, "soft_float")) or // ARM 687 | ((target_cpu.arch.isPowerPC()) and !isFeatureEnabled(target_cpu, std.Target.powerpc, "hard_float")) or // PPC 688 | (target_cpu.arch.isMIPS() and isFeatureEnabled(target_cpu, std.Target.mips, "soft_float"))) // MIPS 689 | { 690 | buildvm.root_module.addCMacro("LJ_ARCH_HASFPU", "0"); 691 | buildvm.root_module.addCMacro("LJ_ABI_SOFTFP", "1"); 692 | } else { 693 | dynasm_run.addArgs(&.{ "-D", "FPU" }); 694 | dynasm_run.addArgs(&.{ "-D", "HFABI" }); 695 | 696 | buildvm.root_module.addCMacro("LJ_ARCH_HASFPU", "1"); 697 | buildvm.root_module.addCMacro("LJ_ABI_SOFTFP", "0"); 698 | } 699 | 700 | if (target_os.tag == .ios or // LJ_TARGET_IOS 701 | target_os.tag == .ps3 or // LJ_TARGET_CONSOLE 702 | target_os.tag == .ps4 or // LJ_TARGET_CONSOLE 703 | target_os.tag == .ps5) // LJ_TARGET_CONSOLE 704 | { 705 | dynasm_run.addArgs(&.{ "-D", "NO_UNWIND" }); 706 | buildvm.root_module.addCMacro("LUAJIT_NO_UNWIND", "1"); 707 | } 708 | 709 | if (target_cpu.arch.isAARCH64() and isFeatureEnabled(target_cpu, std.Target.aarch64, "pauth")) { 710 | dynasm_run.addArgs(&.{ "-D", "PAUTH" }); 711 | buildvm.root_module.addCMacro("LJ_ABI_PAUTH", "1"); 712 | } 713 | 714 | if (target_cpu.arch.isARM()) { 715 | if (isFeatureEnabled(target_cpu, std.Target.arm, "has_v8")) { 716 | dynasm_run.addArgs(&.{ "-D", "VER=80" }); 717 | } else if (isFeatureEnabled(target_cpu, std.Target.arm, "has_v7")) { 718 | dynasm_run.addArgs(&.{ "-D", "VER=70" }); 719 | } else if (isFeatureEnabled(target_cpu, std.Target.arm, "has_v6t2")) { 720 | dynasm_run.addArgs(&.{ "-D", "VER=61" }); 721 | } else if (isFeatureEnabled(target_cpu, std.Target.arm, "has_v6")) { 722 | dynasm_run.addArgs(&.{ "-D", "VER=60" }); 723 | } else { 724 | dynasm_run.addArgs(&.{ "-D", "VER=50" }); 725 | } 726 | } else if (target_cpu.arch.isAARCH64()) { 727 | dynasm_run.addArgs(&.{ "-D", "VER=80" }); 728 | } else if (target_cpu.arch.isPowerPC()) { 729 | if (target_cpu.features.isSuperSetOf(std.Target.powerpc.cpu.pwr7.features)) { 730 | dynasm_run.addArgs(&.{ "-D", "VER=70" }); 731 | } else if (target_cpu.features.isSuperSetOf(std.Target.powerpc.cpu.pwr6.features)) { 732 | dynasm_run.addArgs(&.{ "-D", "VER=60" }); 733 | } else if (target_cpu.features.isSuperSetOf(std.Target.powerpc.cpu.pwr5x.features)) { 734 | dynasm_run.addArgs(&.{ "-D", "VER=51" }); 735 | dynasm_run.addArgs(&.{ "-D", "ROUND" }); 736 | } else if (target_cpu.features.isSuperSetOf(std.Target.powerpc.cpu.pwr5.features)) { 737 | dynasm_run.addArgs(&.{ "-D", "VER=50" }); 738 | } else if (target_cpu.features.isSuperSetOf(std.Target.powerpc.cpu.pwr4.features)) { 739 | dynasm_run.addArgs(&.{ "-D", "VER=40" }); 740 | } else { 741 | dynasm_run.addArgs(&.{ "-D", "VER=0" }); 742 | } 743 | } 744 | 745 | if (target_os.tag == .windows) { 746 | dynasm_run.addArgs(&.{ "-D", "WIN" }); 747 | } else if (target_os.tag == .ios) { 748 | dynasm_run.addArgs(&.{ "-D", "IOS" }); 749 | } 750 | 751 | if (target_cpu.features.isSuperSetOf(std.Target.mips.cpu.mips32r6.features) or 752 | target_cpu.features.isSuperSetOf(std.Target.mips.cpu.mips64r6.features)) 753 | { 754 | dynasm_run.addArgs(&.{ "-D", "MIPSR6" }); 755 | } 756 | 757 | if (target_cpu.arch.isPowerPC()) { 758 | if (isFeatureEnabled(target_cpu, std.Target.powerpc, "fsqrt")) 759 | dynasm_run.addArgs(&.{ "-D", "SQRT" }); 760 | 761 | if (target_os.tag == .ps3) { 762 | dynasm_run.addArgs(&.{ "-D", "PPE", "-D", "TOC" }); 763 | buildvm.root_module.addCMacro("__CELLOS_LV2__", "1"); 764 | } 765 | } 766 | 767 | if (target_cpu.arch.isPowerPC() and (target_os.tag == .ps3 or target_os.tag == .ps4 or target_os.tag == .ps5)) 768 | dynasm_run.addArgs(&.{ "-D", "GPR64" }); 769 | 770 | dynasm_run.addArg("-o"); 771 | const buildvm_arch_h = dynasm_run.addOutputFileArg("buildvm_arch.h"); 772 | dynasm_run.addArg("-L"); // dynasm produces bad output with windows file paths for line numbers, so disable them 773 | 774 | switch (target_cpu.arch) { 775 | .x86 => dynasm_run.addFileArg(upstream.path("src/vm_x86.dasc")), 776 | .x86_64 => dynasm_run.addFileArg(upstream.path("src/vm_x64.dasc")), 777 | .arm, .armeb => dynasm_run.addFileArg(upstream.path("src/vm_arm.dasc")), 778 | .aarch64, .aarch64_be => dynasm_run.addFileArg(upstream.path("src/vm_arm64.dasc")), 779 | .powerpc, .powerpcle, .powerpc64, .powerpc64le => dynasm_run.addFileArg(upstream.path("src/vm_ppc.dasc")), 780 | .mips, .mipsel => dynasm_run.addFileArg(upstream.path("src/vm_mips.dasc")), 781 | .mips64, .mips64el => dynasm_run.addFileArg(upstream.path("src/vm_mips64.dasc")), 782 | else => std.debug.panic("unsupported target architecture: {s}", .{@tagName(target_cpu.arch)}), 783 | } 784 | 785 | switch (target_cpu.arch) { 786 | .x86 => buildvm.root_module.addCMacro("LUAJIT_TARGET", "LUAJIT_ARCH_x86"), 787 | .x86_64 => buildvm.root_module.addCMacro("LUAJIT_TARGET", "LUAJIT_ARCH_x64"), 788 | .arm, .armeb => buildvm.root_module.addCMacro("LUAJIT_TARGET", "LUAJIT_ARCH_arm"), 789 | .aarch64, .aarch64_be => buildvm.root_module.addCMacro("LUAJIT_TARGET", "LUAJIT_ARCH_arm64"), 790 | .powerpc, .powerpcle, .powerpc64, .powerpc64le => buildvm.root_module.addCMacro("LUAJIT_TARGET", "LUAJIT_ARCH_ppc"), 791 | .mips, .mipsel => buildvm.root_module.addCMacro("LUAJIT_TARGET", "LUAJIT_ARCH_mips"), 792 | .mips64, .mips64el => buildvm.root_module.addCMacro("LUAJIT_TARGET", "LUAJIT_ARCH_mips64"), 793 | else => unreachable, 794 | } 795 | 796 | // Prepare to run buildvm generations 797 | if (!disable_compat) 798 | buildvm.root_module.addCMacro("LUAJIT_ENABLE_LUA52COMPAT", "1"); 799 | 800 | if (disable_jit) 801 | buildvm.root_module.addCMacro("LUAJIT_DISABLE_JIT", "1"); 802 | 803 | if (disable_ffi) 804 | buildvm.root_module.addCMacro("LUAJIT_DISABLE_FFI", "1"); 805 | 806 | if (disable_gc64) 807 | buildvm.root_module.addCMacro("LUAJIT_DISABLE_GC64", "1"); 808 | 809 | if (target_cpu.arch == .aarch64_be) { 810 | buildvm.root_module.addCMacro("__AARCH64EB__", "1"); 811 | } else if (target_cpu.arch.isPowerPC()) { 812 | if (target_cpu.arch.endian() == .little) { 813 | buildvm.root_module.addCMacro("LJ_ARCH_ENDIAN", "LUAJIT_LE"); 814 | } else { 815 | buildvm.root_module.addCMacro("LJ_ARCH_ENDIAN", "LUAJIT_BE"); 816 | } 817 | } else if (target_cpu.arch.isMIPS() and target_cpu.arch.endian() == .little) { 818 | buildvm.root_module.addCMacro("__MIPSEL__", "1"); 819 | } 820 | 821 | if (target_os.tag != b.graph.host.result.os.tag) { 822 | if (target_os.tag == .windows) { 823 | buildvm.root_module.addCMacro("LUAJIT_OS", "LUAJIT_OS_WINDOWS"); 824 | } else if (target_os.tag == .linux) { 825 | buildvm.root_module.addCMacro("LUAJIT_OS", "LUAJIT_OS_LINUX"); 826 | } else if (target_os.tag.isDarwin()) { 827 | buildvm.root_module.addCMacro("LUAJIT_OS", "LUAJIT_OS_OSX"); 828 | 829 | if (target_os.tag == .ios) 830 | buildvm.root_module.addCMacro("TARGET_OS_IPHONE", "1"); 831 | } else { 832 | buildvm.root_module.addCMacro("LUAJIT_OS", "LUAJIT_OS_OTHER"); 833 | } 834 | } 835 | 836 | buildvm.addIncludePath(luajit_h.dirname()); 837 | buildvm.addIncludePath(buildvm_arch_h.dirname()); 838 | buildvm.addIncludePath(upstream.path("src")); 839 | 840 | const lib_sources: []const []const u8 = &.{ 841 | "lib_base.c", "lib_math.c", "lib_bit.c", "lib_string.c", "lib_table.c", "lib_io.c", "lib_os.c", 842 | "lib_package.c", "lib_debug.c", "lib_jit.c", "lib_ffi.c", "lib_buffer.c", 843 | }; 844 | 845 | const buildvm_bcdef = b.addRunArtifact(buildvm); 846 | buildvm_bcdef.addArgs(&.{ "-m", "bcdef", "-o" }); 847 | const lj_bcdef_h = buildvm_bcdef.addOutputFileArg("lj_bcdef.h"); 848 | for (lib_sources) |source| { 849 | buildvm_bcdef.addFileArg(upstream.path("src").path(b, source)); 850 | } 851 | 852 | const buildvm_ffdef = b.addRunArtifact(buildvm); 853 | buildvm_ffdef.addArgs(&.{ "-m", "ffdef", "-o" }); 854 | const lj_ffdef_h = buildvm_ffdef.addOutputFileArg("lj_ffdef.h"); 855 | for (lib_sources) |source| { 856 | buildvm_ffdef.addFileArg(upstream.path("src").path(b, source)); 857 | } 858 | 859 | const buildvm_libdef = b.addRunArtifact(buildvm); 860 | buildvm_libdef.addArgs(&.{ "-m", "libdef", "-o" }); 861 | const lj_libdef_h = buildvm_libdef.addOutputFileArg("lj_libdef.h"); 862 | for (lib_sources) |source| { 863 | buildvm_libdef.addFileArg(upstream.path("src").path(b, source)); 864 | } 865 | 866 | const buildvm_recdef = b.addRunArtifact(buildvm); 867 | buildvm_recdef.addArgs(&.{ "-m", "recdef", "-o" }); 868 | const lj_recdef_h = buildvm_recdef.addOutputFileArg("lj_recdef.h"); 869 | for (lib_sources) |source| { 870 | buildvm_recdef.addFileArg(upstream.path("src").path(b, source)); 871 | } 872 | 873 | const buildvm_vmdef = b.addRunArtifact(buildvm); 874 | buildvm_vmdef.addArgs(&.{ "-m", "vmdef", "-o" }); 875 | const vmdef_lua = buildvm_vmdef.addOutputFileArg("vmdef.lua"); 876 | for (lib_sources) |source| { 877 | buildvm_vmdef.addFileArg(upstream.path("src").path(b, source)); 878 | } 879 | 880 | const buildvm_folddef = b.addRunArtifact(buildvm); 881 | buildvm_folddef.addArgs(&.{ "-m", "folddef", "-o" }); 882 | const lj_folddef_h = buildvm_folddef.addOutputFileArg("lj_folddef.h"); 883 | buildvm_folddef.addFileArg(upstream.path("src").path(b, "lj_opt_fold.c")); 884 | 885 | const buildvm_ljvm = b.addRunArtifact(buildvm); 886 | if (target_os.tag == .windows) { 887 | buildvm_ljvm.addArgs(&.{ "-m", "peobj", "-o" }); 888 | } else if (target_os.tag.isDarwin()) { 889 | buildvm_ljvm.addArgs(&.{ "-m", "machasm", "-o" }); 890 | } else { 891 | buildvm_ljvm.addArgs(&.{ "-m", "elfasm", "-o" }); 892 | } 893 | const lj_vm_s = if (target_os.tag == .windows) 894 | buildvm_ljvm.addOutputFileArg("lj_vm.obj") 895 | else 896 | buildvm_ljvm.addOutputFileArg("lj_vm.S"); 897 | 898 | // Build LuaJIT 899 | 900 | const install_jit = b.addInstallDirectory(.{ 901 | .source_dir = upstream.path("src/jit"), 902 | .install_dir = .prefix, 903 | .install_subdir = "jit", 904 | .exclude_extensions = &.{".gitignore"}, 905 | }); 906 | 907 | const install_vmdef = b.addInstallFileWithDir(vmdef_lua, .{ .custom = "jit" }, "vmdef.lua"); 908 | 909 | liblua.root_module.stack_protector = false; 910 | liblua.root_module.addCMacro("LUAJIT_UNWIND_EXTERNAL", "1"); 911 | liblua.root_module.linkSystemLibrary("unwind", .{ .needed = true }); 912 | 913 | liblua.addIncludePath(upstream.path("src")); 914 | liblua.addIncludePath(luajit_h.dirname()); 915 | liblua.addIncludePath(lj_bcdef_h.dirname()); 916 | liblua.addIncludePath(lj_ffdef_h.dirname()); 917 | liblua.addIncludePath(lj_libdef_h.dirname()); 918 | liblua.addIncludePath(lj_recdef_h.dirname()); 919 | liblua.addIncludePath(lj_folddef_h.dirname()); 920 | 921 | liblua.installHeader(upstream.path("src/lua.h"), "lua.h"); 922 | liblua.installHeader(upstream.path("src/luaconf.h"), "luaconf.h"); 923 | liblua.installHeader(upstream.path("src/lualib.h"), "lualib.h"); 924 | liblua.installHeader(upstream.path("src/lauxlib.h"), "lauxlib.h"); 925 | liblua.installHeader(luajit_h, "luajit.h"); 926 | 927 | if (target_os.tag == .windows) { 928 | liblua.addObjectFile(lj_vm_s); 929 | } else { 930 | liblua.addAssemblyFile(lj_vm_s); 931 | } 932 | 933 | liblua.root_module.sanitize_c = false; 934 | liblua.addCSourceFiles(.{ 935 | .root = upstream.path("src"), 936 | .files = &.{ 937 | "lj_assert.c", "lj_gc.c", "lj_char.c", "lj_bc.c", "lj_obj.c", "lj_buf.c", 938 | "lj_str.c", "lj_tab.c", "lj_func.c", "lj_udata.c", "lj_meta.c", "lj_debug.c", 939 | "lj_prng.c", "lj_state.c", "lj_dispatch.c", "lj_vmevent.c", "lj_vmmath.c", "lj_strscan.c", 940 | "lj_strfmt.c", "lj_strfmt_num.c", "lj_serialize.c", "lj_api.c", "lj_profile.c", "lj_lex.c", 941 | "lj_parse.c", "lj_bcread.c", "lj_bcwrite.c", "lj_load.c", "lj_ir.c", "lj_opt_mem.c", 942 | "lj_opt_fold.c", "lj_opt_narrow.c", "lj_opt_dce.c", "lj_opt_loop.c", "lj_opt_split.c", "lj_opt_sink.c", 943 | "lj_mcode.c", "lj_snap.c", "lj_record.c", "lj_crecord.c", "lj_ffrecord.c", "lj_asm.c", 944 | "lj_trace.c", "lj_gdbjit.c", "lj_ctype.c", "lj_cdata.c", "lj_cconv.c", "lj_ccall.c", 945 | "lj_ccallback.c", "lj_carith.c", "lj_clib.c", "lj_cparse.c", "lj_lib.c", "lj_alloc.c", 946 | "lib_aux.c", "lib_init.c", 947 | }, 948 | }); 949 | 950 | liblua.addCSourceFiles(.{ 951 | .root = upstream.path("src"), 952 | .files = lib_sources, 953 | }); 954 | 955 | if (target_os.tag == .windows) { 956 | liblua.addCSourceFile(.{ .file = upstream.path("src/lj_err.c") }); 957 | } else { 958 | // This is a hack that needs to be verified to work every time lj_err changes. Zig's libunwind matches the Apple libunwind 959 | liblua.addCSourceFile(.{ .file = upstream.path("src/lj_err.c"), .flags = &.{"-DLUAJIT_OS=LUAJIT_OS_OSX"} }); 960 | } 961 | 962 | if (!disable_compat) 963 | liblua.root_module.addCMacro("LUAJIT_ENABLE_LUA52COMPAT", "1"); 964 | 965 | if (disable_jit) 966 | liblua.root_module.addCMacro("LUAJIT_DISABLE_JIT", "1"); 967 | 968 | if (disable_ffi) 969 | liblua.root_module.addCMacro("LUAJIT_DISABLE_FFI", "1"); 970 | 971 | if (disable_gc64) 972 | liblua.root_module.addCMacro("LUAJIT_DISABLE_GC64", "1"); 973 | 974 | if (optimize == .Debug) 975 | liblua.root_module.addCMacro("LUA_USE_APICHECK", "1"); 976 | 977 | liblua.step.dependOn(&install_vmdef.step); 978 | liblua.step.dependOn(&install_jit.step); 979 | 980 | luajit.addCSourceFiles(.{ 981 | .root = upstream.path("src"), 982 | .files = &.{"luajit.c"}, 983 | }); 984 | 985 | luajit.rdynamic = true; 986 | luajit.linkLibrary(liblua); 987 | } 988 | 989 | /// Helper function to check if a feature is enabled for a given CPU. 990 | fn isFeatureEnabled(cpu: std.Target.Cpu, comptime arch: type, comptime feature: []const u8) bool { 991 | return cpu.features.isEnabled(@intFromEnum(@field(arch.Feature, feature))); 992 | } 993 | 994 | const Build = std.Build; 995 | const std = @import("std"); 996 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = "lunaro", 3 | .description = "Bindings to the Lua C API", 4 | .version = "0.1.0", 5 | .paths = .{ 6 | "src", 7 | "pkg", 8 | "build.zig", 9 | "build.zig.zon", 10 | "README.md", 11 | "LICENSE", 12 | }, 13 | .dependencies = .{ 14 | .luajit = .{ 15 | .url = "https://github.com/Luajit/Luajit/archive/fe71d0fb54ceadfb5b5f3b6baf29e486d97f6059.tar.gz", 16 | .hash = "12202c64ab920cbebafd26f6e88c96595ff232db7ed6f01d335bbfda96c1025be4a4", 17 | .lazy = true, 18 | }, 19 | .lua54 = .{ 20 | .url = "https://www.lua.org/ftp/lua-5.4.6.tar.gz", 21 | .hash = "1220f93ada1fa077ab096bf88a5b159ad421dbf6a478edec78ddb186d0c21d3476d9", 22 | .lazy = true, 23 | }, 24 | .lua53 = .{ 25 | .url = "https://www.lua.org/ftp/lua-5.3.6.tar.gz", 26 | .hash = "1220937a223531ef6b3fea8f653dc135310b0e84805e7efa148870191f5ab915c828", 27 | .lazy = true, 28 | }, 29 | .lua52 = .{ 30 | .url = "https://www.lua.org/ftp/lua-5.2.4.tar.gz", 31 | .hash = "1220d5b2b39738f0644d9ed5b7431973f1a16b937ef86d4cf85887ef3e9fda7a3379", 32 | .lazy = true, 33 | }, 34 | .lua51 = .{ 35 | .url = "https://www.lua.org/ftp/lua-5.1.5.tar.gz", 36 | .hash = "1220089572fb380fb4679b16421fc53851a8226bcebc9ce44463a0f4ada5c9bd737f", 37 | .lazy = true, 38 | }, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /src/Buffer.zig: -------------------------------------------------------------------------------- 1 | //! A Lua string buffer 2 | //! 3 | //! During its normal operation, a string buffer uses a variable number of stack slots. So, while using a buffer, 4 | //! you cannot assume that you know where the top of the stack is. You can use the stack between successive calls to 5 | //! buffer operations as long as that use is balanced; that is, when you call a buffer operation, the stack is at 6 | //! the same level it was immediately after the previous buffer operation. 7 | //! 8 | //! This struct must be pinned, it cannot be moved or copied after initialization. 9 | 10 | const std = @import("std"); 11 | const lunaro = @import("lunaro.zig"); 12 | 13 | const State = lunaro.State; 14 | const Buffer = @This(); 15 | 16 | const c = lunaro.c; 17 | const lua_version = lunaro.lua_version; 18 | 19 | const assert = std.debug.assert; 20 | 21 | state: *State, 22 | buf: c.luaL_Buffer, 23 | 24 | pin_check: if (std.debug.runtime_safety) *const Buffer else void, 25 | 26 | /// [-0, +0, -] Initializes a buffer B. This function does not allocate any space. 27 | pub fn init(buffer: *Buffer, L: *State) void { 28 | buffer.state = L; 29 | if (std.debug.runtime_safety) buffer.pin_check = buffer; 30 | 31 | c.luaL_buffinit(L.to(), &buffer.buf); 32 | } 33 | 34 | /// [-?, +?, m] Returns a slice of memory of at *most* `max_size` bytes where you can copy a string to be added 35 | /// to the buffer (see `commit`). 36 | pub fn reserve(buffer: *Buffer, max_size: usize) []u8 { 37 | if (std.debug.runtime_safety) assert(buffer == buffer.pin_check); 38 | 39 | const ptr = if (lua_version >= 502) 40 | c.luaL_prepbuffsize(&buffer.buf, max_size) 41 | else 42 | c.luaL_prepbuffer(&buffer.buf); 43 | 44 | const clamped_len = if (lua_version >= 502) 45 | max_size 46 | else 47 | @min(max_size, c.LUAL_BUFFERSIZE); 48 | 49 | return ptr[0..clamped_len]; 50 | } 51 | 52 | /// [-?, +?, -] Adds to the buffer a string of length `size` that had previously been copied into the buffer 53 | /// area provided by `reserve`. 54 | pub fn commit(buffer: *Buffer, size: usize) void { 55 | if (std.debug.runtime_safety) assert(buffer == buffer.pin_check); 56 | 57 | // TODO: translate-c bug: c.luaL_addsize(&buffer.buf, size); 58 | if (lua_version >= 502) { 59 | buffer.buf.n += size; 60 | } else { 61 | buffer.buf.p += size; 62 | } 63 | } 64 | 65 | /// [-?, +?, m] Adds the byte `char` to the buffer. 66 | pub fn addchar(buffer: *Buffer, char: u8) void { 67 | const str = buffer.reserve(1); 68 | str[0] = char; 69 | buffer.commit(1); 70 | } 71 | 72 | /// [-?, +?, m] Adds the string `str` to the buffer. 73 | pub fn addstring(buffer: *Buffer, str: []const u8) void { 74 | if (std.debug.runtime_safety) assert(buffer == buffer.pin_check); 75 | 76 | c.luaL_addlstring(&buffer.buf, str.ptr, str.len); 77 | } 78 | 79 | /// [-1, +?, m] Adds the value at the top of the stack to the buffer. Pops the value. 80 | pub fn addvalue(buffer: *Buffer) void { 81 | if (std.debug.runtime_safety) assert(buffer == buffer.pin_check); 82 | 83 | c.luaL_addvalue(&buffer.buf); 84 | } 85 | 86 | /// [-?, +1, m] Finishes the use of buffer B leaving the final string on the top of the stack. 87 | pub fn final(buffer: *Buffer) void { 88 | if (std.debug.runtime_safety) assert(buffer == buffer.pin_check); 89 | 90 | c.luaL_pushresult(&buffer.buf); 91 | } 92 | 93 | /// A Lua writer function that can be used to write to a string buffer. 94 | pub fn luaWrite(L_opt: ?*c.lua_State, p: ?[*]const u8, sz: usize, ud: ?*anyopaque) callconv(.C) c_int { 95 | _ = L_opt; 96 | assert(ud != null); 97 | assert(p != null); 98 | 99 | const buf: *Buffer = @ptrCast(@alignCast(ud.?)); 100 | buf.addstring(p.?[0..sz]); 101 | 102 | return 0; 103 | } 104 | 105 | pub fn write(buffer: *Buffer, bytes: []const u8) error{}!usize { 106 | buffer.addstring(bytes); 107 | return bytes.len; 108 | } 109 | 110 | pub const Writer = std.io.GenericWriter(*Buffer, error{}, write); 111 | 112 | pub fn writer(buffer: *Buffer) Writer { 113 | return Writer{ .context = buffer }; 114 | } 115 | 116 | comptime { 117 | if (@import("builtin").is_test) 118 | std.testing.refAllDecls(Buffer); 119 | } 120 | -------------------------------------------------------------------------------- /src/Function.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const lunaro = @import("lunaro.zig"); 3 | 4 | const State = lunaro.State; 5 | const Value = lunaro.Value; 6 | const Function = @This(); 7 | 8 | const assert = std.debug.assert; 9 | 10 | state: *State, 11 | ref: c_int, 12 | 13 | /// [-0, +0, -] Initializes a function from the value at the given index. This stores a reference to the function. 14 | /// 15 | /// Asserts that the value at the given index is a function. 16 | pub fn init(L: *State, index: lunaro.Index) Function { 17 | assert(L.typeof(index) == .function); 18 | 19 | L.pushvalue(index); 20 | return .{ .ref = L.ref(lunaro.REGISTRYINDEX), .state = L }; 21 | } 22 | 23 | /// [-0, +0, -] Deinitializes this representation and dereferences the function. 24 | pub fn deinit(func: Function) void { 25 | func.state.unref(lunaro.REGISTRYINDEX, func.ref); 26 | } 27 | 28 | /// [-0, +1, m] Pushes this function onto the stack of `to`. The `to` thread must be in the same state as this function. 29 | pub fn push(func: Function, to: *State) void { 30 | assert(func.state.geti(lunaro.REGISTRYINDEX, func.ref) == .function); 31 | 32 | func.state.xmove(to, 1); 33 | } 34 | 35 | pub const ReturnType = union(enum) { 36 | /// Drop all return values. 37 | none, 38 | 39 | /// Return a single Value of the first return. 40 | /// The value is left on the stack. 41 | value, 42 | 43 | /// Return the number of return values left on the stack. 44 | all, 45 | 46 | /// Return a tuple of the given types. 47 | /// The values are left on the stack. 48 | many: []const type, 49 | }; 50 | 51 | fn MakeCallReturn(comptime ret: ReturnType) type { 52 | switch (ret) { 53 | .none => return void, 54 | .value => return Value, 55 | .all => return lunaro.Size, 56 | .many => |v| return std.meta.Tuple(v), 57 | } 58 | } 59 | 60 | /// [-0, +nargs, e] Calls this function with the given arguments and returns the result. 61 | pub fn call(func: Function, args: anytype, comptime returns: ReturnType) MakeCallReturn(returns) { 62 | const prev_top = func.state.gettop(); 63 | 64 | assert(func.state.geti(lunaro.REGISTRYINDEX, func.ref) == .function); 65 | 66 | inline for (args) |arg| { 67 | func.state.push(arg); 68 | } 69 | 70 | var ret: MakeCallReturn(returns) = undefined; 71 | const num_returns = switch (returns) { 72 | .none => 0, 73 | .value => 1, 74 | .all => null, 75 | .many => returns.many.len, 76 | }; 77 | 78 | func.state.call(args.len, num_returns); 79 | 80 | switch (returns) { 81 | .none => return, 82 | .value => return Value.init(func.state, -1), 83 | .all => return @intCast(func.state.gettop() - prev_top), 84 | .many => { 85 | inline for (returns.many, 0..) |T, i| { 86 | ret[i] = func.state.check(T, prev_top + i + 1, .{ 87 | .source = @src(), 88 | .label_name = "return", 89 | .label = i, 90 | }); 91 | } 92 | 93 | return ret; 94 | }, 95 | } 96 | } 97 | 98 | comptime { 99 | if (@import("builtin").is_test) 100 | std.testing.refAllDecls(Function); 101 | } 102 | -------------------------------------------------------------------------------- /src/State.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const lunaro = @import("lunaro.zig"); 3 | 4 | const arithmetic = @import("arithmetic.zig"); 5 | 6 | const c = lunaro.c; 7 | const lua_version = lunaro.lua_version; 8 | 9 | const Number = lunaro.Number; 10 | const Integer = lunaro.Integer; 11 | const Index = lunaro.Index; 12 | const Size = lunaro.Size; 13 | 14 | const CFn = lunaro.CFn; 15 | const ReaderFn = lunaro.ReaderFn; 16 | const WriterFn = lunaro.WriterFn; 17 | const AllocFn = lunaro.AllocFn; 18 | const HookFn = lunaro.HookFn; 19 | 20 | const REGISTRYINDEX = lunaro.REGISTRYINDEX; 21 | 22 | const DebugInfo = lunaro.DebugInfo; 23 | const ThreadStatus = lunaro.ThreadStatus; 24 | const Type = lunaro.Type; 25 | const ArithOp = lunaro.ArithOp; 26 | const CompareOp = lunaro.CompareOp; 27 | const LoadMode = lunaro.LoadMode; 28 | 29 | const Value = lunaro.Value; 30 | const Table = lunaro.Table; 31 | const Buffer = lunaro.Buffer; 32 | const Function = lunaro.Function; 33 | 34 | const safety = lunaro.safety; 35 | 36 | const assert = std.debug.assert; 37 | 38 | fn literal(comptime str: []const u8) [:0]const u8 { 39 | return (str ++ "\x00")[0..str.len :0]; 40 | } 41 | 42 | /// An opaque type representing a Lua thread. This is the only way to access or manipulate the Lua state. 43 | /// 44 | /// Stack Documentation follows the Lua format: [-o, +p, x] 45 | /// - The first field, o, is how many elements the function pops from the stack. 46 | /// - The second field, p, is how many elements the function pushes onto the stack. 47 | /// - Any function always pushes its results after popping its arguments. 48 | /// - A field in the form x|y means the function can push (or pop) x or y elements, depending on the situation 49 | /// - an interrogation mark '?' means that we cannot know how many elements the function pops/pushes by looking 50 | /// only at its arguments (e.g., they may depend on what is on the stack). 51 | /// - The third field, x, tells whether the function may raise errors: 52 | /// - '-' means the function never raises any error 53 | /// - 'm' means the function may raise out-of-memory errors and errors running a __gc metamethod 54 | /// - 'e' means the function may raise any errors (it can run arbitrary Lua code, either directly or through 55 | /// metamethods) 56 | /// - 'v' means the function may raise an error on purpose. 57 | pub const State = opaque { 58 | pub inline fn to(ptr: *State) *c.lua_State { 59 | return @ptrCast(ptr); 60 | } 61 | 62 | // state manipulation 63 | 64 | /// [-0, +0, -] Creates a new Lua state. Allows for custom allocation functions. 65 | /// 66 | /// This function **WILL NOT** work on Luajit on a 64 bit target. 67 | pub fn initWithAlloc(f: AllocFn, ud: ?*anyopaque) !*State { 68 | const ret = c.lua_newstate(f, ud); 69 | if (ret == null) return error.OutOfMemory; 70 | return @ptrCast(ret.?); 71 | } 72 | 73 | /// [-0, +0, -] Destroys all objects in the given state and frees all dynamic memory used by this state. 74 | pub fn close(L: *State) void { 75 | return c.lua_close(to(L)); 76 | } 77 | 78 | /// [-0, +1, m] Creates a new Lua thread, pushes it onto the stack, and returns a pointer to it. This thread 79 | /// shares its global environment with the given thread. 80 | /// There is no explicit function to close or to destroy a thread. Threads are subject to garbage collection, 81 | /// like any Lua object. 82 | pub fn createcoroutine(L: *State) !*State { 83 | const ptr = c.lua_newthread(to(L)); 84 | if (ptr == null) return error.OutOfMemory; 85 | return @ptrCast(ptr.?); 86 | } 87 | 88 | /// [-0, +0, -] Sets a new panic function and returns the old one. 89 | pub fn atpanic(L: *State, panicf: anytype) CFn { 90 | return c.lua_atpanic(to(L), lunaro.helpers.wrapAnyFn(panicf)); 91 | } 92 | 93 | // basic stack manipulation 94 | 95 | /// [-0, +0, -] Returns the pseudo-index that represents the i-th upvalue of the running function. 96 | pub fn upvalueindex(index: Index) Index { 97 | return c.lua_upvalueindex(index); 98 | } 99 | 100 | /// [-0, +0, -] Converts the acceptable index `index` into an absolute index (that is, one that does not depend 101 | /// on the stack top). 102 | pub fn absindex(L: *State, index: Index) Index { 103 | if (lua_version >= 502) { 104 | return c.lua_absindex(to(L), index); 105 | } 106 | 107 | if (index < 0 and index > REGISTRYINDEX) 108 | return L.gettop() + 1 + index; 109 | return index; 110 | } 111 | 112 | /// [-0, +0, -] Returns the index of the top element in the stack. This also represents the number of elements 113 | /// in the stack. 114 | pub fn gettop(L: *State) Index { 115 | return c.lua_gettop(to(L)); 116 | } 117 | 118 | /// [-?, +?, -] Sets the stack top to the given index. If the new top is larger than the old one, then the new 119 | /// elements are filled with nil. 120 | pub fn settop(L: *State, index: Index) void { 121 | return c.lua_settop(to(L), index); 122 | } 123 | 124 | /// [-0, +1, -] Pushes a copy of the element at the given index onto the stack. 125 | pub fn pushvalue(L: *State, index: Index) void { 126 | return c.lua_pushvalue(to(L), index); 127 | } 128 | 129 | /// [-1, +0, -] Removes the element at the given valid index, shifting down the elements above this index to 130 | /// fill the gap. 131 | /// 132 | /// Cannot be called with a pseudo-index, because a pseudo-index is not an actual stack position. 133 | pub fn remove(L: *State, index: Index) void { 134 | if (lua_version >= 503) { 135 | L.rotate(index, -1); 136 | return L.pop(1); 137 | } 138 | 139 | return c.lua_remove(to(L), index); 140 | } 141 | 142 | /// [-1, +1, -] Moves the top element into the given valid index, shifting up the elements above this index to 143 | /// open space. 144 | /// 145 | /// This function cannot be called with a pseudo-index, because a pseudo-index is not an actual stack position. 146 | pub fn insert(L: *State, index: Index) void { 147 | if (lua_version >= 503) { 148 | return L.rotate(index, 1); 149 | } 150 | 151 | return c.lua_insert(to(L), index); 152 | } 153 | 154 | /// [-1, +0, -] Moves the top element into the given valid index without shifting any element (therefore 155 | /// replacing the value at that given index), 156 | /// and then pops the top element. 157 | pub fn replace(L: *State, index: Index) void { 158 | if (lua_version >= 503) { 159 | L.copy(-1, index); 160 | return L.pop(1); 161 | } 162 | 163 | return c.lua_replace(to(L), index); 164 | } 165 | 166 | fn rotate_reverse(L: *State, start: Index, end: Index) void { 167 | var a = start; 168 | var b = end; 169 | 170 | while (a < b) : ({ 171 | a += 1; 172 | b -= 1; 173 | }) { 174 | L.pushvalue(a); 175 | L.pushvalue(b); 176 | L.replace(a); 177 | L.replace(b); 178 | } 179 | } 180 | 181 | /// [-0, +0, -] Rotates the stack elements between the valid index idx and the top of the stack. 182 | /// 183 | /// The elements are rotated n positions in the direction of the top, for a positive n, or -n positions in the 184 | /// direction of the bottom, for a negative n. 185 | /// The absolute value of n must not be greater than the size of the slice being rotated. 186 | /// 187 | /// This function cannot be called with a pseudo-index, because a pseudo-index is not an actual stack position. 188 | pub fn rotate(L: *State, index: Index, amount: Index) void { 189 | if (lua_version >= 503) { 190 | return c.lua_rotate(to(L), index, amount); 191 | } else { 192 | const idx = L.absindex(index); 193 | const elems = L.gettop() - idx + 1; 194 | var n = amount; 195 | if (n < 0) n += elems; 196 | 197 | if (n > 0 and n < elems) { 198 | L.ensurestack(2, "not enough stack slots available"); 199 | n = elems - n; 200 | rotate_reverse(L, idx, idx + n - 1); 201 | rotate_reverse(L, idx + n, idx + elems - 1); 202 | rotate_reverse(L, idx, idx + elems - 1); 203 | } 204 | } 205 | } 206 | 207 | /// [-0, +0, -] Copies the element at index `src` into the valid index `dest`, replacing the value at that 208 | /// position. Values at other positions are not affected. 209 | pub fn copy(L: *State, src: Index, dest: Index) void { 210 | if (lua_version >= 502) { 211 | return c.lua_copy(to(L), src, dest); 212 | } 213 | 214 | const abs_dest = L.absindex(dest); 215 | L.ensurestack(1, "not enough stack slots"); 216 | L.pushvalue(src); 217 | return L.replace(abs_dest); 218 | } 219 | 220 | /// [-0, +0, -] Ensures that the stack has space for at least n extra slots (that is, that you can safely push 221 | /// up to n values into it). 222 | /// It returns false if it cannot fulfill the request, either because it would cause the stack to be larger than 223 | /// a fixed maximum size(typically at least several thousand elements) or because it cannot allocate memory for 224 | /// the extra space. 225 | /// 226 | /// This function never shrinks the stack; if the stack already has space for the extra slots, it is left 227 | /// unchanged. 228 | pub fn checkstack(L: *State, extra: Size) bool { 229 | return c.lua_checkstack(to(L), extra) == 0; 230 | } 231 | 232 | /// [-?, +?, -] Exchange values between different threads of the same state. 233 | /// 234 | /// This function pops `n` values from the stack `src`, and pushes them onto the stack `dest`. 235 | pub fn xmove(src: *State, dest: *State, n: Size) void { 236 | return c.lua_xmove(to(src), to(dest), n); 237 | } 238 | 239 | /// [-n, +0, -] Pops n elements from the stack. 240 | pub fn pop(L: *State, n: Size) void { 241 | return c.lua_settop(to(L), -@as(Index, n) - 1); 242 | } 243 | 244 | // access functions (stack -> zig) 245 | 246 | /// [-0, +0, -] Returns true if the value at the given index is a number or a string convertible to a number. 247 | pub fn isnumber(L: *State, index: Index) bool { 248 | return c.lua_isnumber(to(L), index) != 0; 249 | } 250 | 251 | /// [-0, +0, -] Returns true if the value at the given index is a string or a number (which is always 252 | /// convertible to a string). 253 | pub fn isstring(L: *State, index: Index) bool { 254 | return c.lua_isstring(to(L), index) != 0; 255 | } 256 | 257 | /// [-0, +0, -] Returns true if the value at the given index is a C function. 258 | pub fn iscfunction(L: *State, index: Index) bool { 259 | return c.lua_iscfunction(to(L), index) != 0; 260 | } 261 | 262 | /// [-0, +0, -] Returns true if the value at the given index is an integer (that is, the value is a number and 263 | /// is represented as an integer). 264 | pub fn isinteger(L: *State, index: Index) bool { 265 | if (lua_version >= 503) { 266 | return c.lua_isinteger(to(L), index) != 0; 267 | } 268 | 269 | if (!L.isnumber(index)) return false; 270 | 271 | const value = L.tonumber(index); 272 | return @floor(value) == value; 273 | } 274 | 275 | /// [-0, +0, -] Returns true if the value at the given index is a userdata (either full or light). 276 | pub fn isuserdata(L: *State, index: Index) bool { 277 | return c.lua_isuserdata(to(L), index) != 0; 278 | } 279 | 280 | /// [-0, +0, -] Returns the type of the value in the given valid index. 281 | pub fn typeof(L: *State, index: Index) Type { 282 | return @enumFromInt(c.lua_type(to(L), index)); 283 | } 284 | 285 | /// [-0, +0, -] Converts the Lua value at the given index to `Number`. The Lua value must be a number or a 286 | /// string convertible to a number; otherwise, returns 0. 287 | pub fn tonumber(L: *State, index: Index) Number { 288 | if (lua_version >= 502) { 289 | var isnum: c_int = 0; 290 | const value = c.lua_tonumberx(to(L), index, &isnum); 291 | 292 | if (isnum == 0) return 0; 293 | return value; 294 | } 295 | 296 | return c.lua_tonumber(to(L), index); 297 | } 298 | 299 | /// [-0, +0, -] Converts the Lua value at the given index to `Integer`. The Lua value must be an integer or a 300 | /// number or string convertible to an integer; otherwise, returns 0. 301 | pub fn tointeger(L: *State, index: Index) Integer { 302 | if (lua_version >= 502) { 303 | var isnum: c_int = 0; 304 | const value = c.lua_tointegerx(to(L), index, &isnum); 305 | 306 | if (isnum == 0) return 0; 307 | return value; 308 | } 309 | 310 | return c.lua_tointeger(to(L), index); 311 | } 312 | 313 | /// [-0, +0, -] Converts the Lua value at the given index to `bool`. 314 | /// 315 | /// If the value is not `false` or `nil`, returns `true`; otherwise, returns `false`. 316 | pub fn toboolean(L: *State, index: Index) bool { 317 | return c.lua_toboolean(to(L), index) != 0; 318 | } 319 | 320 | /// [-0, +0, m] Converts the Lua value at the given index to a string. If the value is a number, then tostring 321 | /// also changes the actual value in the stack to a string (which will confuse `next`). 322 | pub fn tostring(L: *State, index: Index) ?[:0]const u8 { 323 | var ptr_len: usize = undefined; 324 | const ptr = c.lua_tolstring(to(L), index, &ptr_len); 325 | if (ptr == null) return null; 326 | 327 | return ptr[0..ptr_len :0]; 328 | } 329 | 330 | /// [-0, +0, -] Returns the raw "length" of the value at the given index. 331 | /// 332 | /// For strings, this is the string length. 333 | /// For tables, this is the result of the length operator ('#') with no metamethods. 334 | /// For userdata, this is the size of the block of memory allocated for the userdata. 335 | /// For other values, it is 0. 336 | pub fn rawlen(L: *State, index: Index) usize { 337 | if (lua_version >= 502) { 338 | return c.lua_rawlen(to(L), index); 339 | } 340 | 341 | return c.lua_objlen(to(L), index); 342 | } 343 | 344 | /// [-0, +0, -] Converts the Lua value at the given index to a C function (or null). 345 | pub fn tocfunction(L: *State, index: Index) CFn { 346 | return c.lua_tocfunction(to(L), index); 347 | } 348 | 349 | /// [-0, +0, -] If the value at the given index is a full userdata, returns its block address. If the value is a 350 | /// light userdata, returns its pointer. 351 | pub fn touserdata(L: *State, comptime T: type, index: Index) ?*align(@alignOf(usize)) T { 352 | return @ptrCast(@alignCast(c.lua_touserdata(to(L), index))); 353 | } 354 | 355 | /// [-0, +0, -] Converts the value at the given index to a Lua thread (represented as `State`) or null. 356 | pub fn tothread(L: *State, index: Index) ?*State { 357 | return @ptrCast(c.lua_tothread(to(L), index)); 358 | } 359 | 360 | /// [-0, +0, -] Converts the value at the given index to a pointer or null. Different objects give different 361 | /// pointers. 362 | /// 363 | /// There is no way to convert the pointer back to its original value. 364 | pub fn topointer(L: *State, index: Index) ?*const anyopaque { 365 | return c.lua_topointer(to(L), index); 366 | } 367 | 368 | /// [-(2|1), +1, e] Performs an arithmetic or bitwise operation over the two values (or one, in the case of 369 | /// negations) at the top of the stack. The function pops these values, performs the operation, and pushes the 370 | /// result back onto the stack. 371 | /// 372 | /// This function follows the semantics of the corresponding Lua operator (that is, it may call metamethods). 373 | pub fn arith(L: *State, op: ArithOp) void { 374 | if (lua_version >= 502) { 375 | if (@intFromEnum(op) >= 0) { 376 | return c.lua_arith(to(L), @intFromEnum(op)); 377 | } 378 | } 379 | 380 | switch (op) { 381 | inline else => |value| if (@intFromEnum(value) < 0) 382 | return @field(arithmetic, @tagName(value))(L), 383 | } 384 | } 385 | 386 | /// [-0, +0, -] Returns true if the values at indices `a` and `b` are primitively equal (that is, without 387 | /// calling metamethods). 388 | pub fn rawequal(L: *State, a: Index, b: Index) bool { 389 | return c.lua_rawequal(to(L), a, b) != 0; 390 | } 391 | 392 | /// [-0, +0, e] Compares two Lua values. Returns true if the value at index `a` satisfies op when compared with 393 | /// the value at index `b`, following the semantics of the corresponding Lua operator (that is, it may call 394 | /// metamethods). 395 | pub fn compare(L: *State, a: Index, b: Index, op: CompareOp) bool { 396 | if (lua_version >= 502) { 397 | return c.lua_compare(to(L), a, b, @intFromEnum(op)) != 0; 398 | } 399 | 400 | switch (op) { 401 | .eq => return c.lua_equal(L.to(), a, b) != 0, 402 | .lt => return c.lua_lessthan(L.to(), a, b) != 0, 403 | .le => return arithmetic.le(L, a, b), 404 | } 405 | } 406 | 407 | /// [-0, +0, -] Returns true if the value at the given index is a function (either C or Lua). 408 | pub fn isfunction(L: *State, index: Index) bool { 409 | return c.lua_type(to(L), index) == c.LUA_TFUNCTION; 410 | } 411 | 412 | /// [-0, +0, -] Returns true if the value at the given index is a table. 413 | pub fn istable(L: *State, index: Index) bool { 414 | return c.lua_type(to(L), index) == c.LUA_TTABLE; 415 | } 416 | 417 | /// [-0, +0, -] Returns true if the value at the given index is a full userdata. 418 | pub fn isfulluserdata(L: *State, index: Index) bool { 419 | return c.lua_type(to(L), index) == c.LUA_TUSERDATA; 420 | } 421 | 422 | /// [-0, +0, -] Returns true if the value at the given index is a light userdata. 423 | pub fn islightuserdata(L: *State, index: Index) bool { 424 | return c.lua_type(to(L), index) == c.LUA_TLIGHTUSERDATA; 425 | } 426 | 427 | /// [-0, +0, -] Returns true if the value at the given index is nil. 428 | pub fn isnil(L: *State, index: Index) bool { 429 | return c.lua_type(to(L), index) == c.LUA_TNIL; 430 | } 431 | 432 | /// [-0, +0, -] Returns true if the value at the given index is a boolean. 433 | pub fn isboolean(L: *State, index: Index) bool { 434 | return c.lua_type(to(L), index) == c.LUA_TBOOLEAN; 435 | } 436 | 437 | /// [-0, +0, -] Returns true if the value at the given index is a thread. 438 | pub fn isthread(L: *State, index: Index) bool { 439 | return c.lua_type(to(L), index) == c.LUA_TTHREAD; 440 | } 441 | 442 | /// [-0, +0, -] Returns true if the value at the given index is not valid. 443 | pub fn isnone(L: *State, index: Index) bool { 444 | return c.lua_type(to(L), index) == c.LUA_TNONE; 445 | } 446 | 447 | /// [-0, +0, -] Returns true if the value at the given index is nil or not valid. 448 | pub fn isnoneornil(L: *State, index: Index) bool { 449 | const t = c.lua_type(to(L), index); 450 | return t == c.LUA_TNONE or t == c.LUA_TNIL; 451 | } 452 | 453 | // push functions (zig -> stack) 454 | 455 | /// [-0, +1, -] Pushes a nil value onto the stack. 456 | pub fn pushnil(L: *State) void { 457 | return c.lua_pushnil(to(L)); 458 | } 459 | 460 | /// [-0, +1, -] Pushes a float with value `value` onto the stack. 461 | pub fn pushnumber(L: *State, value: Number) void { 462 | return c.lua_pushnumber(to(L), value); 463 | } 464 | 465 | /// [-0, +1, -] Pushes an integer with value `value` onto the stack. 466 | pub fn pushinteger(L: *State, value: Integer) void { 467 | return c.lua_pushinteger(to(L), value); 468 | } 469 | 470 | /// [-0, +1, m] Pushes a copy of the string `value` onto the stack. 471 | pub fn pushstring(L: *State, value: []const u8) void { 472 | _ = c.lua_pushlstring(to(L), value.ptr, value.len); 473 | } 474 | 475 | /// [-0, +1, m] Pushes a copy of the string `value` onto the stack. Returns a pointer to the internal copy, but 476 | /// does NOT reference it. 477 | pub fn pushstringExtra(L: *State, value: []const u8) [*:0]const u8 { 478 | if (lua_version >= 502) { 479 | return c.lua_pushlstring(to(L), value.ptr, value.len)[0..value.len :0]; 480 | } 481 | 482 | c.lua_pushlstring(to(L), value.ptr, value.len); 483 | return L.tostring(-1).?; 484 | } 485 | 486 | /// [-0, +1, e] Pushes onto the stack a formatted string and returns a pointer to this string. 487 | /// 488 | /// It is similar to the C function sprintf, but the conversion specifiers are quite restricted: 489 | /// - There are no flags, widths, or precisions, and only the following conversion specifiers are allowed: 490 | /// - '%%' (inserts the character '%') 491 | /// - '%s' (inserts a zero-terminated string, with no size restrictions) 492 | /// - '%f' (inserts a Number) 493 | /// - '%I' (inserts a Integer) 494 | /// - '%p' (inserts a pointer as a hexadecimal numeral) 495 | /// - '%d' (inserts an c_int) 496 | /// - '%c' (inserts an c_int as a one-byte character) 497 | /// - '%U' (inserts a long int as a UTF-8 byte sequence) [Lua 5.3+] 498 | pub fn pushfstring(L: *State, fmt: [:0]const u8, args: anytype) [:0]const u8 { 499 | const ptr = @call(.auto, c.lua_pushfstring, .{ to(L), fmt.ptr } ++ args); 500 | return std.mem.sliceTo(ptr, 0); 501 | } 502 | 503 | /// [-n, +1, m] Pushes a new C closure onto the stack. Pops `n` values from the stack and sets the new closure's 504 | /// upvalues from the popped values. 505 | /// 506 | /// Does not wrap zig functions, only accepts CFn. 507 | pub fn pushclosure_unwrapped(L: *State, func: CFn, n: Size) void { 508 | return c.lua_pushcclosure(to(L), func, n); 509 | } 510 | 511 | /// [-n, +1, m] Pushes a new C closure onto the stack. Pops `n` values from the stack and sets the new closure's 512 | /// upvalues from the popped values. 513 | pub fn pushclosure(L: *State, comptime func: anytype, n: Size) void { 514 | return c.lua_pushcclosure(to(L), lunaro.helpers.wrapAnyFn(func), n); 515 | } 516 | 517 | /// [-0, +1, -] Pushes a boolean value with value `value` onto the stack. 518 | pub fn pushboolean(L: *State, value: bool) void { 519 | return c.lua_pushboolean(to(L), @intFromBool(value)); 520 | } 521 | 522 | /// [-0, +1, -] Pushes a light userdata onto the stack. 523 | pub fn pushlightuserdata(L: *State, ptr: anytype) void { 524 | return c.lua_pushlightuserdata(to(L), @ptrCast(@constCast(ptr))); 525 | } 526 | 527 | /// [-0, +1, -] Pushes the thread represented by `L` onto the stack. 528 | pub fn pushthread(L: *State) bool { 529 | return c.lua_pushthread(to(L)) != 0; 530 | } 531 | 532 | // get functions (Lua -> stack) 533 | 534 | /// [-0, +1, e] Pushes onto the stack the value of the global `name`. Returns the type of that value. 535 | pub fn getglobal(L: *State, name: [:0]const u8) Type { 536 | if (lua_version >= 503) { 537 | return @enumFromInt(c.lua_getglobal(to(L), name.ptr)); 538 | } 539 | 540 | c.lua_getglobal(to(L), name.ptr); 541 | return L.typeof(-1); 542 | } 543 | 544 | /// [-1, +1, e] Pushes onto the stack the value t[k], where t is the value at the given index and k is the value 545 | /// at the top of the stack. Returns the type of the pushed value. 546 | /// 547 | /// This function pops the key from the stack (putting the resulting value in its place). As in Lua, this 548 | /// function may trigger a metamethod for the "index" event. 549 | pub fn gettable(L: *State, index: Index) Type { 550 | if (lua_version >= 503) { 551 | return @enumFromInt(c.lua_gettable(to(L), index)); 552 | } 553 | 554 | c.lua_gettable(to(L), index); 555 | return L.typeof(-1); 556 | } 557 | 558 | /// [-0, +1, e] Pushes onto the stack the value t[k], where t is the value at the given index. 559 | /// 560 | /// As in Lua, this function may trigger a metamethod for the "index" event. 561 | pub fn getfield(L: *State, index: Index, name: [:0]const u8) Type { 562 | if (lua_version >= 503) { 563 | return @enumFromInt(c.lua_getfield(to(L), index, name.ptr)); 564 | } 565 | 566 | c.lua_getfield(to(L), index, name.ptr); 567 | return L.typeof(-1); 568 | } 569 | 570 | /// [-0, +1, e] Pushes onto the stack the value t[n], where t is the value at the given index. 571 | /// 572 | /// As in Lua, this function may trigger a metamethod for the "index" event. 573 | pub fn geti(L: *State, index: Index, n: Integer) Type { 574 | if (lua_version >= 503) { 575 | return @enumFromInt(c.lua_geti(to(L), index, n)); 576 | } 577 | 578 | const abs = L.absindex(index); 579 | L.pushinteger(n); 580 | return L.gettable(abs); 581 | } 582 | 583 | /// [-1, +1, -] Pushes onto the stack the value t[i], where t is the value at the given index and i is the value 584 | /// at the top of the stack. 585 | /// 586 | /// This function pops the key from the stack (putting the resulting value in its place). This access is "raw" 587 | /// and does not invoke metamethods. 588 | pub fn rawget(L: *State, index: Index) Type { 589 | if (lua_version >= 503) { 590 | return @enumFromInt(c.lua_rawget(to(L), index)); 591 | } 592 | 593 | c.lua_rawget(to(L), index); 594 | return L.typeof(-1); 595 | } 596 | 597 | /// [-0, +1, -] Pushes onto the stack the value t[n], where t is the value at the given index. 598 | /// 599 | /// The access is raw; that is, it does not invoke metamethods. 600 | pub fn rawgeti(L: *State, index: Index, n: Integer) Type { 601 | if (lua_version >= 503) { 602 | return @enumFromInt(c.lua_rawgeti(to(L), index, n)); 603 | } 604 | 605 | // lua_rawgeti takes a c_int pre-5.3, so handle the edge case where n is too large or small 606 | if (n > std.math.maxInt(c_int) or n < std.math.minInt(c_int)) { 607 | L.pushinteger(n); 608 | return L.rawget(index); 609 | } else { 610 | c.lua_rawgeti(to(L), index, @as(c_int, @intCast(n))); 611 | return L.typeof(-1); 612 | } 613 | } 614 | 615 | /// [-0, +1, -] Pushes onto the stack the value t[p], where t is the value at the given index and p is any 616 | /// pointer. 617 | /// 618 | /// The access is raw; that is, it does not invoke metamethods. 619 | pub fn rawgetp(L: *State, index: Index, ptr: anytype) Type { 620 | assert(@typeInfo(@TypeOf(ptr)) == .Pointer); 621 | if (lua_version >= 503) { 622 | return @enumFromInt(c.lua_rawgetp(to(L), index, @ptrCast(ptr))); 623 | } 624 | 625 | const abs = L.absindex(index); 626 | L.pushlightuserdata(ptr); 627 | return L.rawget(abs); 628 | } 629 | 630 | /// [-0, +1, m] Creates a new empty table and pushes it onto the stack. Parameter `narr` is a hint for how many 631 | /// elements the table will have as a sequence; parameter `nrec` is a hint for how many other elements the table 632 | /// will have. 633 | /// 634 | /// Lua may use these hints to preallocate memory for the new table. This preallocation is useful for 635 | /// performance when you know in advance how many elements the table will have. 636 | pub fn createtable(L: *State, narr: Size, nrec: Size) void { 637 | return c.lua_createtable(to(L), narr, nrec); 638 | } 639 | 640 | /// [-0, +1, m] This function allocates a new block of memory with the given size, pushes onto the stack a new 641 | /// full userdata with the block address, and returns this address. 642 | /// 643 | /// The host program can freely use this memory. 644 | pub fn newuserdata(L: *State, size: Size) *align(@alignOf(usize)) anyopaque { 645 | if (lua_version >= 504) { 646 | return @alignCast(c.lua_newuserdatauv(to(L), size, 1).?); 647 | } 648 | 649 | return @alignCast(c.lua_newuserdata(to(L), size).?); 650 | } 651 | 652 | /// [-0, +(0|1), -] If the value at the given index has a metatable, the function pushes that metatable onto the 653 | /// stack and returns true. 654 | /// Otherwise, the function returns false and pushes nothing on the stack. 655 | pub fn getmetatable(L: *State, index: Index) bool { 656 | if (lua_version >= 504) { 657 | return c.lua_getmetatable(to(L), index) != 0; 658 | } 659 | 660 | return c.lua_getmetatable(to(L), index) != 0; 661 | } 662 | 663 | /// [-0, +1, m] Creates a new empty table and pushes it onto the stack. 664 | pub fn newtable(L: *State) void { 665 | return c.lua_newtable(to(L)); 666 | } 667 | 668 | /// [-0, +1, -] Pushes onto the stack the global environment. 669 | pub fn pushglobaltable(L: *State) void { 670 | if (lua_version >= 502) { 671 | _ = L.rawgeti(REGISTRYINDEX, c.LUA_RIDX_GLOBALS); 672 | return; 673 | } 674 | 675 | return c.lua_pushvalue(to(L), c.LUA_GLOBALSINDEX); 676 | } 677 | 678 | /// [-0, +1, -] Pushes onto the stack the Lua value associated with the full userdata at the given index. 679 | /// 680 | /// Returns the type of the pushed value. 681 | pub fn getuservalue(L: *State, index: Index) Type { 682 | if (lua_version >= 503) { 683 | return @enumFromInt(c.lua_getuservalue(to(L), index)); 684 | } 685 | 686 | if (lua_version >= 502) { 687 | c.lua_getuservalue(to(L), index); 688 | 689 | if (L.istable(-1)) { 690 | const typ = L.rawgeti(-1, 1); 691 | L.remove(-2); 692 | 693 | return typ; 694 | } 695 | 696 | return .nil; 697 | } 698 | 699 | if (!L.isfulluserdata(index)) 700 | L.raise("full userdata expected", .{}); 701 | 702 | const ptr = L.topointer(index).?; 703 | return L.rawgetp(REGISTRYINDEX, ptr); 704 | } 705 | 706 | // set functions (stack -> Lua) 707 | 708 | /// [-1, +0, e] Pops a value from the stack and sets it as the new value of global name. 709 | pub fn setglobal(L: *State, name: [:0]const u8) void { 710 | return c.lua_setglobal(to(L), name.ptr); 711 | } 712 | 713 | /// [-2, +0, e] Does the equivalent to t[k] = v, where t is the value at the given index, v is the value at the 714 | /// top of the stack, and k is the value just below the top. 715 | /// 716 | /// This function pops both the key and the value from the stack. As in Lua, this function may trigger a 717 | /// metamethod for the "newindex" event. 718 | pub fn settable(L: *State, index: Index) void { 719 | return c.lua_settable(to(L), index); 720 | } 721 | 722 | /// [-1, +0, e] Does the equivalent to t[k] = v, where t is the value at the given index and v is the value at 723 | /// the top of the stack. 724 | /// 725 | /// This function pops the value from the stack. As in Lua, this function may trigger a metamethod for the 726 | /// "newindex" event. 727 | pub fn setfield(L: *State, index: Index, name: [:0]const u8) void { 728 | return c.lua_setfield(to(L), index, name.ptr); 729 | } 730 | 731 | /// [-1, +0, e] Does the equivalent to t[n] = v, where t is the value at the given index and v is the value at 732 | /// the top of the stack. 733 | /// 734 | /// This function pops the value from the stack. As in Lua, this function may trigger a metamethod for the 735 | /// "newindex" event. 736 | pub fn seti(L: *State, index: Index, n: Integer) void { 737 | if (lua_version >= 503) { 738 | return c.lua_seti(to(L), index, n); 739 | } 740 | 741 | const abs = L.absindex(index); 742 | L.pushinteger(n); 743 | return L.settable(abs); 744 | } 745 | 746 | /// [-2, +0, m] Does the equivalent to t[k] = v, where t is the value at the given index, v is the value at the 747 | /// top of the stack, and k is the value just below the top. 748 | /// 749 | /// This function pops the both the key and value from the stack. The assignment is raw; that is, it does not 750 | /// invoke metamethods. 751 | pub fn rawset(L: *State, index: Index) void { 752 | return c.lua_rawset(to(L), index); 753 | } 754 | 755 | /// [-1, +0, m] Does the equivalent of t[n] = v, where t is the value at the given index and v is the value at 756 | /// the top of the stack. 757 | /// 758 | /// This function pops the value from the stack. The assignment is raw; that is, it does not invoke metamethods. 759 | pub fn rawseti(L: *State, index: Index, n: Size) void { 760 | if (lua_version >= 503) { 761 | return c.lua_rawseti(to(L), index, n); 762 | } 763 | 764 | return c.lua_rawseti(to(L), index, n); 765 | } 766 | 767 | /// [-1, +0, m] Does the equivalent of t[p] = v, where t is the value at the given index, v is the value at the 768 | /// top of the stack, and p is any pointer (which will become lightuserdata). 769 | /// 770 | /// The assignment is raw; that is, it does not invoke metamethods. 771 | pub fn rawsetp(L: *State, index: Index, ptr: anytype) void { 772 | assert(@typeInfo(@TypeOf(ptr)) == .Pointer); 773 | if (lua_version >= 503) { 774 | return c.lua_rawsetp(to(L), index, @ptrCast(ptr)); 775 | } 776 | 777 | const abs = L.absindex(index); 778 | L.pushlightuserdata(ptr); 779 | L.insert(-2); 780 | return L.rawset(abs); 781 | } 782 | 783 | /// [-1, +0, -] Pops a table from the stack and sets it as the new metatable for the value at the given index. 784 | pub fn setmetatable(L: *State, index: Index) void { 785 | _ = c.lua_setmetatable(to(L), index); 786 | return; 787 | } 788 | 789 | /// [-1, +0, -] Pops a value from the stack and sets it as the new value associated to the full userdata at the 790 | /// given index. 791 | pub fn setuservalue(L: *State, index: Index) void { 792 | if (lua_version >= 503) { 793 | _ = c.lua_setuservalue(to(L), index); 794 | return; 795 | } 796 | 797 | if (lua_version >= 502) { 798 | c.lua_getuservalue(to(L), index); 799 | if (!L.istable(-1)) { 800 | L.pop(1); 801 | L.newtable(); 802 | } 803 | 804 | L.insert(-2); 805 | L.rawseti(-2, 1); 806 | return c.lua_setuservalue(to(L), index); 807 | } 808 | 809 | if (!L.isfulluserdata(index)) 810 | L.raise("full userdata expected", .{}); 811 | 812 | const ptr = L.topointer(index).?; 813 | return L.rawsetp(REGISTRYINDEX, ptr); 814 | } 815 | 816 | // load and call functions 817 | 818 | /// [-(nargs+1), +nresults, e] Calls a function. The following protocol must be followed: 819 | /// 820 | /// - The function to be called is pushed onto the stack. 821 | /// - The arguments are pushed in direct order (the first argument is pushed first). 822 | /// - Then call `State.call`, the function and all arguments are popped from the stack, and the function's 823 | /// results are pushed onto the stack. 824 | /// - The number of results is adjusted to `nresults`, unless `nresults` is `null` (indicating you want all 825 | /// returned values). 826 | /// - The function results are pushed in direct order (the first result is pushed first), such that the last 827 | /// result is on the top of the stack. 828 | pub fn call(L: *State, nargs: Size, nresults: ?Size) void { 829 | const nres: Index = nresults orelse c.LUA_MULTRET; 830 | 831 | if (lua_version >= 502) { 832 | return c.lua_callk(to(L), nargs, nres, 0, null); 833 | } 834 | 835 | return c.lua_call(to(L), nargs, nres); 836 | } 837 | 838 | /// [-(nargs + 1), +(nresults|1), -] Calls a function in protected mode. Both `nargs` and `nresults` have the 839 | /// same meaning as in `call`. And like `call`, the function and all arguments are popped from the stack when 840 | /// the function is called. 841 | /// 842 | /// If there are no errors during the call, `State.pcall` behaves exactly like `State.call`. However, if there 843 | /// is any error, `State.pcall` catches it, pushes a single value on the stack (the error message), and returns 844 | /// an error code. 845 | /// 846 | /// If `handler_index` is zero, the error object on the stack is exactly the original error object. Otherwise 847 | /// `handler_index` is the stack index of a message handler. In case of runtime errors, this function will be 848 | /// called with the error object and its return value will be the object pushed on the stack by `State.pcall`. 849 | pub fn pcall(L: *State, nargs: Size, nresults: ?Size, handler_index: Index) ThreadStatus { 850 | const nres: Index = nresults orelse c.LUA_MULTRET; 851 | 852 | if (lua_version >= 502) { 853 | return @enumFromInt(c.lua_pcallk(to(L), nargs, nres, handler_index, 0, null)); 854 | } 855 | 856 | return @enumFromInt(c.lua_pcall(to(L), nargs, nres, handler_index)); 857 | } 858 | 859 | /// [-0, +1, -] Loads a Lua chunk without running it. If there are no errors, `load` pushes the compiled chunk 860 | /// as a Lua function on top of the stack. Otherwise, it pushes an error message. 861 | /// 862 | /// `load` uses the stack internally, so the reader function must always leave the stack unmodified when 863 | /// returning. 864 | /// 865 | /// If the resulting function has upvalues, its first upvalue is set to the value of the global environment. 866 | /// When loading main chunks, this upvalue will be the _ENV variable. Other upvalues are initialized with nil. 867 | pub fn load(L: *State, reader: anytype, chunkname: [:0]const u8, mode: LoadMode) ThreadStatus { 868 | if (@typeInfo(@TypeOf(reader)) != .Pointer) 869 | @compileError("expected *LuaReader, got " ++ @typeName(@TypeOf(reader))); 870 | const read = @typeInfo(@TypeOf(reader)).Pointer.child.read; 871 | 872 | if (lua_version >= 502) { 873 | return @enumFromInt(c.lua_load( 874 | to(L), 875 | read, 876 | reader, 877 | chunkname, 878 | switch (mode) { 879 | .binary => "b", 880 | .text => "t", 881 | .either => null, 882 | }, 883 | )); 884 | } 885 | 886 | if (reader.mode == .binary and mode == .text) 887 | L.raise("attempt to load a binary chunk (mode is 'text')", .{}); 888 | 889 | if (reader.mode == .text and mode == .binary) 890 | L.raise("attempt to load a text chunk (mode is 'binary')", .{}); 891 | 892 | return @enumFromInt(c.lua_load(to(L), read, reader, chunkname)); 893 | } 894 | 895 | /// [-0, +0, -] Dumps a function as a binary chunk. Receives a Lua function on the top of the stack and produces 896 | /// a binary chunk that, if loaded again, results in a function equivalent to the one dumped. 897 | /// 898 | /// If `strip` is true, the binary representation may not include all debug information about the function, to 899 | /// save space. The value returned is the error code returned by the last call to the writer; `ok` means no 900 | /// errors. This function does not pop the Lua function from the stack. 901 | pub fn dump(L: *State, writer: anytype, strip: bool) ThreadStatus { 902 | if (@typeInfo(@TypeOf(writer)) != .Pointer) 903 | @compileError("expected *LuaWriter, got " ++ @typeName(@TypeOf(writer))); 904 | const write = @typeInfo(@TypeOf(writer)).Pointer.child.write; 905 | 906 | if (lua_version >= 503) { 907 | return @enumFromInt(c.lua_dump(to(L), write, writer, @intFromBool(strip))); 908 | } 909 | 910 | return @enumFromInt(c.lua_dump(to(L), write, writer)); 911 | } 912 | 913 | // coroutine functions 914 | 915 | /// [-?, +?, -] Yields a coroutine. This function MUST only be used as a tailcall. 916 | /// 917 | /// The running coroutine suspends it's execution and the call to `State.resume` that started this coroutine 918 | /// returns. The parameter `nresults` is the number of values from the stack that are passed as results to 919 | /// `State.resume`. 920 | pub fn yield(L: *State, nresults: Size) c_int { 921 | if (lua_version >= 503) { 922 | _ = c.lua_yieldk(to(L), nresults, 0, null); 923 | unreachable; 924 | } 925 | 926 | // before 5.3, lua_yield returns a magic value that MUST be returned to Lua's core 927 | if (lua_version >= 502) { 928 | return c.lua_yieldk(to(L), nresults, 0, null); 929 | } 930 | 931 | return c.lua_yield(to(L), nresults); 932 | } 933 | 934 | /// [-?, +?, -] Starts or resumes a coroutine in the given thread. 935 | /// 936 | /// This call returns when the coroutine suspends or finishes its execution. When it returns, the stack contains 937 | /// all values passed to `yield`, or all values returned by the body function. This function returns `.yield` if 938 | /// the coroutine yields, `.ok` if the coroutine finishes its execution without errors, or an error code in case 939 | /// of errors (see `ThreadStatus`). 940 | /// 941 | /// In case of errors, the stack is not unwound, so you can use the debug API over it. The error object is on the top of the stack. 942 | /// 943 | /// To resume a coroutine, you remove any results from the last `yield`, put on its stack only the values to be passed as results from yield, and then call `resume. 944 | pub fn @"resume"(L: *State, nargs: Size) ThreadStatus { 945 | if (lua_version >= 504) { 946 | var res: c_int = 0; 947 | return @enumFromInt(c.lua_resume(to(L), null, nargs, &res)); 948 | } 949 | 950 | if (lua_version >= 502) { 951 | return @enumFromInt(c.lua_resume(to(L), null, nargs)); 952 | } 953 | 954 | return @enumFromInt(c.lua_resume(to(L), nargs)); 955 | } 956 | 957 | /// [-0, +0, -] Returns the status of the thread `L`. 958 | /// 959 | /// You can only call functions in threads with status `.ok`. 960 | /// You can only resume threads with status `.ok` or `.yield`. 961 | pub fn status(L: *State) ThreadStatus { 962 | return @enumFromInt(c.lua_status(to(L))); 963 | } 964 | 965 | // isyieldable unimplementable in 5.2 and 5.1 966 | // setwarnf unimplementable in 5.3 and 5.2 and 5.1 967 | // warning unimplementable in 5.3 and 5.2 and 5.1 968 | 969 | // TODO: gc 970 | 971 | // miscellaneous functions 972 | 973 | /// [-1, +0, v] Generates a Lua error, using the value at the top of the stack as the error object. 974 | pub fn throw(L: *State) noreturn { 975 | _ = c.lua_error(to(L)); 976 | unreachable; 977 | } 978 | 979 | /// [-1, +(2|0), e] Pops a key from the stack, and pushes a key–value pair from the table at the given index 980 | /// (the "next" pair after the given key). If there are no more elements in the table, then `next` returns 0 and 981 | /// pushes nothing. 982 | pub fn next(L: *State, index: Index) bool { 983 | return c.lua_next(to(L), index) != 0; 984 | } 985 | 986 | /// [-n, +1, e] Concatenates the `n` values at the top of the stack, pops them, and leaves the result at the top. 987 | /// 988 | /// Concatenation is performed following the usual semantics of Lua. 989 | pub fn concat(L: *State, items: Size) void { 990 | return c.lua_concat(to(L), items); 991 | } 992 | 993 | /// [-0, +1, -] Pushes the length of the value at the given index onto the stack. It is equivalent to the '#' 994 | /// operator in Lua. 995 | pub fn len(L: *State, index: Index) void { 996 | if (lua_version >= 502) { 997 | return c.lua_len(to(L), index); 998 | } 999 | 1000 | switch (L.typeof(index)) { 1001 | .string => { 1002 | return L.pushinteger(@intCast(c.lua_objlen(to(L), index))); 1003 | }, 1004 | .table => if (!L.callmeta(index, "__len")) { 1005 | return L.pushinteger(@intCast(c.lua_objlen(to(L), index))); 1006 | }, 1007 | .userdata => if (!L.callmeta(index, "__len")) { 1008 | L.raise("attempt to get length of a userdata value", .{}); 1009 | }, 1010 | else => L.raise("attempt to get length of a %s value", .{L.typenameof(index)}), 1011 | } 1012 | } 1013 | 1014 | // debug api 1015 | 1016 | /// [-0, +0, -] Gets information about the interpreter runtime stack. 1017 | /// 1018 | /// This function fills parts of a `DebugInfo` struct with an identification of the activation record of the 1019 | /// function executing at a given level. 1020 | /// 1021 | /// Level 0 is the current running function, whereas level `n+1` is the function that has called level `n`. 1022 | /// Returns false if the given level is greater than the current stack depth. 1023 | pub fn getstack(L: *State, level: Size, ar: *DebugInfo) bool { 1024 | return c.lua_getstack(to(L), level, ar) != 0; 1025 | } 1026 | 1027 | /// [-(0|1), +(0|1|2), e] Gets information about a specific function or function invocation. 1028 | /// 1029 | /// To get information about a function invokation, `ar` must be a valid activation record that was filled by a 1030 | /// previous call to `State.getstack` or given as argument to a hook. 1031 | /// 1032 | /// If the first character of `what` is `>`, the function is popped from the stack. 1033 | /// 1034 | /// The following values of `what` are valid: 1035 | /// - 'n': fills in the field `name` and `namewhat`. 1036 | /// - 'S': fills in the fields `source`, `short_src`, `linedefined`, `lastlinedefined`, and `what`. 1037 | /// - 'l': fills in the field `currentline`. 1038 | /// - 't': fills in the field `istailcall` [Lua 5.2+]. 1039 | /// - 'u': fills in the field `nups`, `nparams` [Lua 5.2+], and `isvararg` [Lua 5.2+]. 1040 | /// - 'f': pushes onto the stack the function that is running at the given level. 1041 | /// - 'L': pushes onto the stack a table whose indices are the numbers of the lines that are valid on the 1042 | /// function. (A valid line is a line with some associated code, that is, a line where you can put a break 1043 | /// point. Non-valid lines include empty lines and comments.). 1044 | /// 1045 | /// If `f` and `L` are provided, the function is pushed first.` 1046 | /// 1047 | /// Returns `false` on error (such as an invalid option in `what`). 1048 | pub fn getinfo(L: *State, what: [:0]const u8, ar: *DebugInfo) bool { 1049 | return c.lua_getinfo(to(L), what.ptr, ar) != 0; 1050 | } 1051 | 1052 | /// [-0, +(1|0), -] Gets information about a local variable of a given activation record. 1053 | /// 1054 | /// Pushes the value on the stack and return's it's name. 1055 | /// 1056 | /// The parameter `ar` must be a valid activation record that was filled by a previous call to `State.getstack` 1057 | /// or given as argument to a hook. The index `n` selects which local variable to inspect (1 is the first parameter or active local variable). 1058 | /// 1059 | /// Returns null and pushes nothing if the index is greater than the number of active local variables. 1060 | pub fn getlocal(L: *State, ar: *DebugInfo, n: Size) ?[:0]const u8 { 1061 | const ptr = c.lua_getlocal(to(L), ar, n); 1062 | if (ptr == null) return null; 1063 | return std.mem.sliceTo(ptr, 0); 1064 | } 1065 | 1066 | /// [-(1|0), +0, -] Sets the value of a local variable of a given activation record. 1067 | /// 1068 | /// Pops the value from the stack and sets it as the new value of the local variable. 1069 | /// 1070 | /// The parameter `ar` must be a valid activation record that was filled by a previous call to `State.getstack` 1071 | /// or given as argument to a hook. The index `n` selects which local variable to inspect (1 is the first parameter or active local variable). 1072 | /// 1073 | /// Returns null and pops nothing if the index is greater than the number of active local variables. 1074 | pub fn setlocal(L: *State, ar: *DebugInfo, n: Size) ?[:0]const u8 { 1075 | const ptr = c.lua_setlocal(to(L), ar, n); 1076 | if (ptr == null) return null; 1077 | return std.mem.sliceTo(ptr, 0); 1078 | } 1079 | 1080 | /// [-0, +(0|1), -] Gets information about the n-th upvalues of the closure at index `funcindex`. It pushes the 1081 | /// upvalue's value onto the stack and returns its name. Returns null and pushes nothing if there is no upvalue with the given index. 1082 | /// 1083 | /// Upvalues are numbered in an arbitrary order. Upvalues for C closures all have a name of empty string `""`. 1084 | pub fn getupvalue(L: *State, funcindex: Index, n: Size) ?[:0]const u8 { 1085 | const ptr = c.lua_getupvalue(to(L), funcindex, n); 1086 | if (ptr == null) return null; 1087 | return std.mem.sliceTo(ptr, 0); 1088 | } 1089 | 1090 | /// [-(0|1), +0, -] Sets the value of the n-th upvalue of the closure at index `funcindex`. It assigns the value 1091 | /// at the top of the stack to the upvalue and returns its name. It also pops the value from the stack. 1092 | /// 1093 | /// Returns null and pops nothing if there is no upvalue with the given index. 1094 | pub fn setupvalue(L: *State, funcindex: Index, n: Size) ?[:0]const u8 { 1095 | const ptr = c.lua_setupvalue(to(L), funcindex, n); 1096 | if (ptr == null) return null; 1097 | return std.mem.sliceTo(ptr, 0); 1098 | } 1099 | 1100 | // upvalueid unimplementable in 5.3 and 5.2 and 5.1 1101 | // upvaluejoin unimplementable in 5.3 and 5.2 and 5.1 1102 | 1103 | // TODO: better binding for hook 1104 | /// [-0, +0, -] Sets the debugging hook function. `func` is the function to be called. `mask` specifies on which 1105 | /// events the hook will be called, and `count` is the only used with LUA_MASKCOUNT. 1106 | /// 1107 | /// Each event is described below: 1108 | /// - call: called when the interpreter calls a functions, just after Lua enters the function, but before the 1109 | /// function gets it's arguments. 1110 | /// - return: called when the interpreter returns from a function. The hook is called just before Lua leaves the 1111 | /// function. You have no access to the values to be returned by the function. 1112 | /// - line: called when the interpreter is about to start the execution of a new line of code, or when it jumps 1113 | /// back in the code (even to the same line). (This event only happens while Lua is executing a Lua function.) 1114 | /// - count: called after the interpreter executes every `count` instructions. (This event only happens while Lua 1115 | /// is executing a Lua function.) 1116 | /// 1117 | /// If `mask` is zero, the debug hook is disabled. 1118 | pub fn sethook(L: *State, func: HookFn, mask: c_int, count: Size) void { 1119 | _ = c.lua_sethook(to(L), func, mask, count); 1120 | } 1121 | 1122 | /// [-0, +0, -] Returns the current hook function. 1123 | pub fn gethook(L: *State) HookFn { 1124 | return c.lua_gethook(to(L)); 1125 | } 1126 | 1127 | /// [-0, +0, -] Returns the current hook mask. 1128 | pub fn gethookmask(L: *State) c_int { 1129 | return c.lua_gethookmask(to(L)); 1130 | } 1131 | 1132 | /// [-0, +0, -] Returns the current hook count. 1133 | pub fn gethookcount(L: *State) Size { 1134 | return @intCast(c.lua_gethookcount(to(L))); 1135 | } 1136 | 1137 | // auxiliary library 1138 | 1139 | /// [-0, +0, v] Checks whether the code making the call and the Lua library being called are using the same version 1140 | /// of Lua and the same numeric types. 1141 | pub fn checkversion(L: *State) void { 1142 | if (lua_version >= 502) { 1143 | return c.luaL_checkversion(to(L)); 1144 | } 1145 | 1146 | if (L.loadstring("return _VERSION", "lunaro/checkversion", .either) != .ok or L.pcall(0, 1, 0) != .ok) { 1147 | L.raise("lua core does not expose version information, cannot assume compatability", .{}); 1148 | } 1149 | 1150 | if (L.tostring(-1)) |core_version| { 1151 | if (!std.mem.eql(u8, core_version, c.LUA_VERSION)) { 1152 | L.raise("version mismatch: app needs %s, Lua core provides %s", .{ c.LUA_VERSION, core_version }); 1153 | } 1154 | } 1155 | 1156 | L.pop(1); 1157 | } 1158 | 1159 | /// [-0, +(0|1), m] Pushes onto the stack the field `event` from the metatable of the object at index `obj` and 1160 | /// returns the type of the pushed value. If the object does not have a metatable, or if the metatable does not 1161 | /// have this field, pushes nothing and returns `.nil`. 1162 | pub fn getmetafield(L: *State, obj: Index, event: [:0]const u8) Type { 1163 | if (lua_version >= 503) { 1164 | return @enumFromInt(c.luaL_getmetafield(to(L), obj, event.ptr)); 1165 | } 1166 | 1167 | if (c.luaL_getmetafield(to(L), obj, event.ptr) == 0) { 1168 | return .nil; 1169 | } 1170 | 1171 | return L.typeof(-1); 1172 | } 1173 | 1174 | /// [-0, +(0|1), e] Calls a metamethod. If the object at index `obj` has a metatable and this metatable has a 1175 | /// field `event`, this function calls this field passing the object as its only argument. In this case this 1176 | /// function returns true and pushes onto the stack the value returned by the call. If there is no metatable or 1177 | /// no metamethod, this function returns false (without pushing any value on the stack). 1178 | pub fn callmeta(L: *State, obj: Index, event: [:0]const u8) bool { 1179 | return c.luaL_callmeta(to(L), obj, event.ptr) != 0; 1180 | } 1181 | 1182 | // argerror replaced with check mechanism 1183 | // typeerror replaced with check mechanism 1184 | // checklstring replaced with check mechanism 1185 | // optlstring replaced with check mechanism 1186 | // checknumber replaced with check mechanism 1187 | // optnumber replaced with check mechanism 1188 | // checkinteger replaced with check mechanism 1189 | // optinteger replaced with check mechanism 1190 | // checktype replaced with check mechanism 1191 | // checkany replaced with check mechanism 1192 | 1193 | /// [-0, +0, v] Checks whether the function argument `arg` has type `typ`. 1194 | pub fn ensuretype(L: *State, arg: Size, typ: Type) void { 1195 | c.luaL_checktype(to(L), arg, @intFromEnum(typ)); 1196 | } 1197 | 1198 | /// [-0, +0, v] Checks whether the function argument `arg` exists, even if it is nil. 1199 | pub fn ensureexists(L: *State, arg: Size) void { 1200 | c.luaL_checkany(to(L), arg); 1201 | } 1202 | 1203 | /// [-0, +0, v] Grows the stack size to `top + sz` elements, raising an error if the stack cannot grow to that 1204 | /// size. msg is an additional text to go into the error message. 1205 | pub fn ensurestack(L: *State, sz: Size, msg: [:0]const u8) void { 1206 | c.luaL_checkstack(to(L), sz, msg.ptr); 1207 | } 1208 | 1209 | /// [-0, +1, m] If the registry already has the key `tname`, return false. Otherwise creates a new table to be 1210 | /// used as a metatable for userdata, adds to this new table the pair `__name = tname`, adds the table to the 1211 | /// registry under the key `tname` and returns true. 1212 | /// 1213 | /// In both cases pushes the final value associated with `tname` in the registry. 1214 | pub fn newmetatablefor(L: *State, tname: [:0]const u8) bool { 1215 | return c.luaL_newmetatable(to(L), tname.ptr) != 0; 1216 | } 1217 | 1218 | /// [-0, +0, -] Sets the metatable of the object at the top of the stack as the metatable associated with name 1219 | /// `tname` in the registry 1220 | pub fn setmetatablefor(L: *State, tname: [:0]const u8) void { 1221 | if (lua_version >= 502) { 1222 | c.luaL_setmetatable(to(L), tname.ptr); 1223 | } 1224 | 1225 | _ = L.getmetatablefor(tname); 1226 | L.setmetatable(-2); 1227 | } 1228 | 1229 | // testudata replaced with userdata mechanism 1230 | // checkudata replaced with userdata mechanism 1231 | 1232 | /// [-0, +1, m] Pushes onto the stack a string identifying the current position of the control at level lvl in the call stack. Typically this string has the following format: 1233 | /// 1234 | /// chunkname:currentline: 1235 | /// 1236 | /// Level 0 is the running function, level 1 is the function that called the running function, etc. 1237 | /// This function is used to build a prefix for error messages. 1238 | pub fn where(L: *State, lvl: Size) void { 1239 | c.luaL_where(to(L), lvl); 1240 | } 1241 | 1242 | /// [-0, +0, v] Raises an error. The error message format is given by fmt plus any extra arguments, following 1243 | /// the same rules of `pushfstring`. It also adds at the beginning of the message the file name and the line 1244 | /// number where the error occurred, if this information is available. 1245 | pub fn raise(L: *State, msg: [:0]const u8, args: anytype) noreturn { 1246 | if (args.len == 0) { 1247 | L.where(1); 1248 | L.pushstring(msg); 1249 | L.concat(2); 1250 | L.throw(); 1251 | } else { 1252 | const ArgsTuple = std.meta.Tuple(blk: { 1253 | var types: [args.len]type = undefined; 1254 | 1255 | inline for (@typeInfo(@TypeOf(args)).@"struct".fields, 0..) |field, i| { 1256 | if (field.type == [:0]const u8 or field.type == []const u8) { 1257 | types[i] = [*:0]const u8; 1258 | } else { 1259 | types[i] = field.type; 1260 | } 1261 | } 1262 | 1263 | break :blk types[0..]; 1264 | }); 1265 | 1266 | var new_args: ArgsTuple = undefined; 1267 | inline for (args, 0..) |arg, i| { 1268 | if (@TypeOf(arg) == [:0]const u8) { 1269 | new_args[i] = arg.ptr; 1270 | } else if (@TypeOf(arg) == []const u8) { 1271 | new_args[i] = L.pushstringExtra(arg); 1272 | } else { 1273 | new_args[i] = arg; 1274 | } 1275 | } 1276 | 1277 | _ = @call(.auto, c.luaL_error, .{ to(L), msg.ptr } ++ new_args); 1278 | unreachable; 1279 | } 1280 | } 1281 | 1282 | // checkoption replaced with check mechanism 1283 | 1284 | /// [-1, +0, m] Creates and returns a reference, in the table at index t, for the object at the top of the stack 1285 | /// (and pops the object). 1286 | /// 1287 | /// A reference is a unique integer key. As long as you do not manually add integer keys into table t, `ref` 1288 | /// ensures the uniqueness of the key it returns. You can retrieve an object referred by reference r by calling 1289 | /// `L.rawgeti(t, r)`. Function `unref` frees a reference and its associated object. 1290 | /// 1291 | /// If the object at the top of the stack is nil, luaL_ref returns the constant `State.refnil`. The constant 1292 | /// `State.noref` is guaranteed to be different from any reference returned by `ref`. 1293 | pub fn ref(L: *State, t: Index) c_int { 1294 | return c.luaL_ref(to(L), t); 1295 | } 1296 | 1297 | /// [-0, +0, -] Releases reference refi from the table at index t (see `ref`). The entry is removed from the 1298 | /// table, so that the referred object can be collected. The reference refi is also freed to be used again. 1299 | pub fn unref(L: *State, t: Index, refi: c_int) void { 1300 | c.luaL_unref(to(L), t, refi); 1301 | } 1302 | 1303 | // TODO: loadfile 1304 | 1305 | /// [-0, +1, -] Loads a string as a Lua chunk. This function uses `load` to load the chunk, so all caveats about 1306 | /// that function apply. 1307 | pub fn loadstring(L: *State, str: []const u8, chunkname: [:0]const u8, mode: LoadMode) ThreadStatus { 1308 | if (lua_version >= 502) { 1309 | return @enumFromInt(c.luaL_loadbufferx( 1310 | to(L), 1311 | str.ptr, 1312 | str.len, 1313 | chunkname, 1314 | switch (mode) { 1315 | .binary => "b", 1316 | .text => "t", 1317 | .either => null, 1318 | }, 1319 | )); 1320 | } 1321 | 1322 | return @enumFromInt(c.luaL_loadbuffer(to(L), str.ptr, str.len, chunkname)); 1323 | } 1324 | 1325 | /// [-0, +0, -] Creates a new Lua state with a default allocator function and panic function. 1326 | pub fn init() !*State { 1327 | const ret = c.luaL_newstate(); 1328 | if (ret == null) return error.OutOfMemory; 1329 | return @ptrCast(ret.?); 1330 | } 1331 | 1332 | /// [-0, +0, e] Returns the "length" of the value at the given index as a number; it is equivalent to the '#' 1333 | /// operator in Lua. Raises an error if the result of the operation is not an integer. (This case only can 1334 | /// happen through metamethods.) 1335 | pub fn lenof(L: *State, obj: Index) Size { 1336 | if (lua_version >= 502) { 1337 | return @intCast(c.luaL_len(to(L), obj)); 1338 | } 1339 | 1340 | L.len(obj); 1341 | const n = L.tointeger(-1); 1342 | L.pop(1); 1343 | return @intCast(n); 1344 | } 1345 | 1346 | /// [-0, +1, m] Creates a copy of string `s` by replacing any occurrence of the string `p` with the string `r`. 1347 | /// Pushes the resulting string on the stack and returns it. 1348 | pub fn gsub(L: *State, s: [:0]const u8, p: [:0]const u8, r: [:0]const u8) [:0]const u8 { 1349 | return std.mem.sliceTo(c.luaL_gsub(to(L), s.ptr, p.ptr, r.ptr), 0); 1350 | } 1351 | 1352 | // TODO: setfuncs 1353 | 1354 | /// [-0, +1, e] Ensures that the value t[fname], where t is the value at index idx, is a table, and pushes that 1355 | /// table onto the stack. Returns true if it finds a previous table there and false if it creates a new table. 1356 | pub fn getsubtable(L: *State, t: Index, fname: [:0]const u8) bool { 1357 | if (lua_version >= 502) { 1358 | return c.luaL_getsubtable(to(L), t, fname.ptr) != 0; 1359 | } 1360 | 1361 | if (L.getfield(t, fname) != .table) { 1362 | L.pop(1); 1363 | L.newtable(); 1364 | L.pushvalue(-1); 1365 | L.setfield(t, fname); 1366 | return false; 1367 | } 1368 | 1369 | return true; 1370 | } 1371 | 1372 | /// [-0, +1, m] Creates and pushes a traceback of the stack `target`. If msg is not null it is appended at the 1373 | /// beginning of the traceback. The level parameter tells at which level to start the traceback. 1374 | pub fn traceback(L: *State, target: *State, msg: ?[:0]const u8, level: Size) void { 1375 | if (lua_version >= 502) { 1376 | c.luaL_traceback(to(L), to(target), if (msg) |m| m.ptr else null, level); 1377 | } 1378 | 1379 | var ar: DebugInfo = undefined; 1380 | var buffer: Buffer = undefined; 1381 | buffer.init(L); 1382 | 1383 | if (msg) |m| { 1384 | buffer.addstring(m); 1385 | buffer.addchar('\n'); 1386 | } 1387 | 1388 | buffer.addstring("stack traceback:"); 1389 | var this_level = level; 1390 | while (target.getstack(this_level, &ar)) : (this_level += 1) { 1391 | _ = target.getinfo("Slnt", &ar); 1392 | if (ar.currentline <= 0) { 1393 | _ = L.pushfstring("\n\t%s: in ", .{&ar.short_src}); 1394 | } else { 1395 | _ = L.pushfstring("\n\t%s:%d: in ", .{ &ar.short_src, ar.currentline }); 1396 | } 1397 | buffer.addvalue(); 1398 | 1399 | if (ar.namewhat[0] != 0) { 1400 | _ = L.pushfstring("%s '%s'", .{ ar.namewhat, ar.name }); 1401 | } else if (ar.what[0] == 'm') { 1402 | L.pushstring("main chunk"); 1403 | } else if (ar.what[0] != 'C') { 1404 | _ = L.pushfstring("function <%s:%d>", .{ &ar.short_src, ar.linedefined }); 1405 | } else { 1406 | L.pushstring("?"); 1407 | } 1408 | 1409 | buffer.addvalue(); 1410 | if (@hasField(DebugInfo, "istailcall") and ar.istailcall != 0) 1411 | buffer.addstring("\n\t(...tail calls...)"); 1412 | } 1413 | 1414 | buffer.final(); 1415 | } 1416 | 1417 | /// [-0, +1, e] If `module` is not already present in package.loaded, calls function openf with string `module` 1418 | /// as an argument and sets the call result in package.loaded[module], as if that function has been called 1419 | /// through require. 1420 | /// 1421 | /// If `global` is true, also stores the module into global `module`. 1422 | /// 1423 | /// Leaves a copy of the module on the stack. 1424 | pub fn requiref(L: *State, module: [:0]const u8, openf: CFn, global: bool) void { 1425 | if (lua_version >= 503) { 1426 | c.luaL_requiref(to(L), module, openf, @intFromBool(global)); 1427 | } 1428 | 1429 | const scheck = safety.StackCheck.init(L); 1430 | defer _ = scheck.check(requiref, L, 1); 1431 | 1432 | if (L.getglobal("package") != .table) return; 1433 | if (L.getfield(-1, "loaded") != .table) return; 1434 | _ = L.getfield(-1, module); 1435 | if (!L.toboolean(-1)) { 1436 | L.pop(1); 1437 | L.pushclosure_unwrapped(openf, 0); 1438 | L.pushstring(module); 1439 | L.call(1, 1); 1440 | L.pushvalue(-1); 1441 | L.setfield(-3, module); 1442 | } 1443 | 1444 | if (global) { 1445 | L.pushvalue(-1); 1446 | L.setglobal(module); 1447 | } 1448 | 1449 | L.insert(-3); 1450 | L.pop(2); 1451 | } 1452 | 1453 | /// [-0, +0, -] Returns the name of the type of the value at the given index. 1454 | pub fn typenameof(L: *State, idx: Index) [:0]const u8 { 1455 | return @tagName(L.typeof(idx)); 1456 | } 1457 | 1458 | /// [-0, +1, m] Pushes onto the stack the metatable associated with name tname in the registry (see 1459 | /// `newmetatableFor`) or nil if there is no metatable associated with that name. Returns the type of the pushed 1460 | /// value. 1461 | pub fn getmetatablefor(L: *State, tname: [:0]const u8) Type { 1462 | return L.getfield(REGISTRYINDEX, tname); 1463 | } 1464 | 1465 | /// [-0, +0, e] Opens all standard Lua libraries into the given state. 1466 | pub fn openlibs(L: *State) void { 1467 | c.luaL_openlibs(to(L)); 1468 | } 1469 | 1470 | // convienience functions 1471 | 1472 | /// [-0, +1, m] Pushes the given value onto the stack. 1473 | /// 1474 | /// Strings and null terminated byte arrays are pushed as strings. 1475 | /// Slices, arrays, vectors and tuples are pushed as a sequence (a table with sequential integer keys). 1476 | /// Packed structs are pushed as their integer backed value. 1477 | /// Normal and extern structs are pushed as a table. 1478 | /// Enums are pushed as their integer value. 1479 | /// Errors and enum literals are pushed as their name as a string. 1480 | /// Unions are pushed as a table with a single key-value pair. 1481 | /// Functions are pushed as a closure. 1482 | /// Enums and ErrorSet *types* are pushed as a sequence of their fields. 1483 | /// Struct *types* are pushed as a table with their public declarations as key-value pairs. 1484 | pub fn push(L: *State, value: anytype) void { 1485 | const T = @TypeOf(value); 1486 | if (T == Value) 1487 | return value.push(L); 1488 | 1489 | switch (@typeInfo(T)) { 1490 | .void, .null => L.pushnil(), 1491 | .bool => L.pushboolean(value), 1492 | .int, .comptime_int => L.pushinteger(@intCast(value)), 1493 | .float, .comptime_float => L.pushnumber(@floatCast(value)), 1494 | .pointer => |info| { 1495 | if (comptime isZigString(T)) { 1496 | return L.pushstring(value); 1497 | } 1498 | 1499 | switch (T) { 1500 | [*c]const u8, [*c]u8, [*:0]const u8, [*:0]u8 => c.lua_pushstring(to(L), value), 1501 | else => switch (info.size) { 1502 | .One, .Many, .C => L.pushlightuserdata(value), 1503 | .Slice => { 1504 | L.createtable(@intCast(value.len), 0); 1505 | 1506 | for (value, 0..) |item, i| { 1507 | const idx = i + 1; 1508 | L.push(item); 1509 | L.rawseti(-2, @intCast(idx)); 1510 | } 1511 | }, 1512 | }, 1513 | } 1514 | }, 1515 | .array, .vector => { 1516 | L.createtable(@intCast(value.len), 0); 1517 | 1518 | for (value, 0..) |item, i| { 1519 | L.push(item); 1520 | L.rawseti(-2, i + 1); 1521 | } 1522 | }, 1523 | .@"struct" => |info| if (info.is_tuple) { 1524 | L.createtable(@intCast(info.fields.len), 0); 1525 | 1526 | inline for (value, 0..) |item, i| { 1527 | L.push(item); 1528 | L.rawseti(-2, i + 1); 1529 | } 1530 | } else if (info.backing_integer) |int_t| { 1531 | L.pushinteger(@intCast(@as(int_t, @bitCast(value)))); 1532 | } else { 1533 | L.createtable(0, @intCast(info.fields.len)); 1534 | 1535 | inline for (info.fields) |field| { 1536 | L.push(@field(value, field.name)); 1537 | L.setfield(-2, literal(field.name)); 1538 | } 1539 | }, 1540 | .optional => if (value) |u_value| { 1541 | L.push(u_value); 1542 | } else { 1543 | L.pushnil(); 1544 | }, 1545 | .error_set => L.pushstring(@errorName(value)), 1546 | .@"enum" => L.pushinteger(@intFromEnum(value)), 1547 | .@"union" => { 1548 | L.createtable(0, 1); 1549 | 1550 | switch (value) { 1551 | inline else => |u_value| { 1552 | L.push(u_value); 1553 | L.setfield(-2, @tagName(value)); 1554 | }, 1555 | } 1556 | }, 1557 | .@"fn" => L.pushclosure_unwrapped(lunaro.helpers.wrapAnyFn(value), 0), 1558 | .enum_literal => L.pushstring(@tagName(value)), 1559 | .type => switch (@typeInfo(value)) { 1560 | .@"struct" => |info| { 1561 | L.createtable(0, @intCast(info.decls.len)); 1562 | 1563 | inline for (info.decls) |decl| { 1564 | if (decl.is_pub) { 1565 | L.push(@field(value, decl.name)); 1566 | L.setfield(-2, literal(decl.name)); 1567 | } 1568 | } 1569 | }, 1570 | .@"enum" => |info| { 1571 | L.createtable(0, @intCast(info.fields.len)); 1572 | 1573 | inline for (info.fields) |field| { 1574 | L.push(field.value); 1575 | L.setfield(-2, literal(field.name)); 1576 | } 1577 | }, 1578 | .error_set => |info| if (info) |fields| { 1579 | L.createtable(@intCast(fields.len), 0); 1580 | 1581 | inline for (fields, 0..) |field, i| { 1582 | L.push(field.name); 1583 | L.rawseti(-2, i + 1); 1584 | } 1585 | } else @compileError("cannot push anyerror"), 1586 | else => @compileError("unable to push container '" ++ @typeName(value) ++ "'"), 1587 | }, 1588 | else => @compileError("unable to push value '" ++ @typeName(T) ++ "'"), 1589 | } 1590 | } 1591 | 1592 | fn resource__tostring(L: *State) c_int { 1593 | _ = L.getfield(upvalueindex(1), "__name"); 1594 | _ = L.pushfstring(": %p", .{L.topointer(1)}); 1595 | L.concat(2); 1596 | 1597 | return 1; 1598 | } 1599 | 1600 | /// [-0, +1, m] Registers a resource type with the given metatable. 1601 | pub fn registerResource(L: *State, comptime T: type, comptime metatable: ?type) void { 1602 | const tname = if (@hasDecl(T, "lunaro_typename")) 1603 | literal(T.lunaro_typename) 1604 | else 1605 | literal(@typeName(T)); 1606 | 1607 | if (L.getmetatablefor(tname) != .table) { 1608 | L.pop(1); 1609 | 1610 | if (metatable) |mt| { 1611 | L.push(mt); 1612 | } else { 1613 | L.createtable(0, 1); 1614 | } 1615 | 1616 | if (metatable == null or metatable != null and !@hasDecl(metatable.?, "__name")) { 1617 | L.push(tname); 1618 | L.setfield(-2, "__name"); 1619 | } 1620 | 1621 | if (lua_version <= 503 and (metatable == null or metatable != null and !@hasDecl(metatable.?, "__tostring"))) { 1622 | L.pushvalue(-1); 1623 | L.pushclosure_unwrapped(lunaro.wrapCFn(resource__tostring), 1); 1624 | L.setfield(-2, "__tostring"); 1625 | } 1626 | 1627 | L.pushvalue(-1); 1628 | L.setfield(c.LUA_REGISTRYINDEX, tname); 1629 | } 1630 | 1631 | L.pop(1); 1632 | } 1633 | 1634 | /// [-0, +1, m] Creates a new resource of the given type. 1635 | /// 1636 | /// `registerResource` must be called (with this type) before this function. 1637 | pub fn resource(L: *State, comptime T: type) *align(@alignOf(usize)) T { 1638 | const tname = if (@hasDecl(T, "lunaro_typename")) 1639 | literal(T.lunaro_typename) 1640 | else 1641 | literal(@typeName(T)); 1642 | 1643 | const size = @sizeOf(T); 1644 | const ptr = L.newuserdata(size); 1645 | 1646 | assert(L.getmetatablefor(tname) == .table); 1647 | L.setmetatable(-2); 1648 | 1649 | return @ptrCast(@alignCast(ptr)); 1650 | } 1651 | 1652 | /// [-0, +2, m] Pushes a `nil, string` pair onto the stack representing the given zig error. 1653 | pub fn pusherror(L: *State, err: anyerror) void { 1654 | L.pushnil(); 1655 | L.pushstring(@errorName(err)); 1656 | } 1657 | 1658 | const CheckTraceback = struct { 1659 | stack: [3][]const u8, 1660 | extra: usize, 1661 | len: u8, 1662 | 1663 | pub fn push(tb: *CheckTraceback, label: []const u8) void { 1664 | if (tb.len < tb.stack.len) { 1665 | tb.stack[tb.len] = label; 1666 | tb.len += 1; 1667 | } else { 1668 | tb.extra += 1; 1669 | } 1670 | } 1671 | 1672 | pub fn pop(tb: *CheckTraceback) void { 1673 | if (tb.extra > 0) { 1674 | tb.extra -= 1; 1675 | } else if (tb.len > 0) { 1676 | tb.len -= 1; 1677 | } 1678 | } 1679 | 1680 | pub fn print(tb: *const CheckTraceback, writer: anytype) !void { 1681 | for (tb.stack[0..tb.len]) |label| { 1682 | try writer.writeAll(label); 1683 | } 1684 | 1685 | if (tb.extra > 0) { 1686 | try writer.print("...({d} more)", .{tb.extra}); 1687 | } 1688 | } 1689 | }; 1690 | 1691 | pub const CheckOptions = struct { 1692 | /// The allocator that will be used to allocate any non-const strings and slices. 1693 | allocator: ?std.mem.Allocator = null, 1694 | 1695 | /// The source location that will be used to print the source of the error. 1696 | source: ?std.builtin.SourceLocation = null, 1697 | 1698 | /// The label that will be used to identify where on the stack the error occurred. 1699 | label_name: []const u8 = "argument", 1700 | 1701 | /// The numeric label that will be used to identify where on the stack the error occurred. 1702 | /// 1703 | /// A value of 0 means use the index of the original argument. 1704 | label: c_int = 0, 1705 | }; 1706 | 1707 | fn check_throw(L: *State, options: CheckOptions, tb: ?*const CheckTraceback, comptime fmt: []const u8, args: anytype) noreturn { 1708 | var buffer: Buffer = undefined; 1709 | buffer.init(L); 1710 | 1711 | var buffered = std.io.bufferedWriter(buffer.writer()); 1712 | const writer = buffered.writer(); 1713 | 1714 | if (options.source) |srcinfo| { 1715 | writer.print("{s}:{d}:{d}: in {s}: ", .{ srcinfo.file, srcinfo.line, srcinfo.column, srcinfo.fn_name }) catch unreachable; 1716 | } else { 1717 | writer.writeAll("?:?:?: in ?: ") catch unreachable; 1718 | } 1719 | 1720 | if (options.label > 0) { 1721 | writer.print("({s} #{d}) ", .{ options.label_name, options.label }) catch unreachable; 1722 | } 1723 | 1724 | if (tb) |trace| { 1725 | trace.print(writer) catch unreachable; 1726 | } 1727 | 1728 | writer.writeByte(' ') catch unreachable; 1729 | writer.print(fmt, args) catch unreachable; 1730 | 1731 | buffered.flush() catch unreachable; 1732 | buffer.final(); 1733 | 1734 | L.throw(); 1735 | } 1736 | 1737 | fn checkInternal(L: *State, comptime T: type, idx: Index, options: CheckOptions, tb: *CheckTraceback) T { 1738 | switch (@typeInfo(T)) { 1739 | .bool => { 1740 | if (!L.isboolean(idx)) 1741 | check_throw(L, options, tb, "expected boolean, got {s}", .{L.typenameof(idx)}); 1742 | 1743 | return L.toboolean(idx); 1744 | }, 1745 | .int => { 1746 | if (!L.isinteger(idx)) 1747 | check_throw(L, options, tb, "expected integer, got {s}", .{L.typenameof(idx)}); 1748 | 1749 | const num = L.tointeger(idx); 1750 | return std.math.cast(T, num) orelse 1751 | check_throw(L, options, tb, "expected number in range [{d}, {d}], got {d}", .{ std.math.minInt(T), std.math.maxInt(T), num }); 1752 | }, 1753 | .float => { 1754 | if (!L.isnumber(idx)) 1755 | check_throw(L, options, tb, "expected number, got {s}", .{L.typenameof(idx)}); 1756 | 1757 | return @floatCast(L.tonumber(idx)); 1758 | }, 1759 | .array => |info| { 1760 | if (info.child == u8 and L.isstring(idx)) { 1761 | const str = L.tostring(idx).?; 1762 | if (str.len != info.len) 1763 | check_throw(L, options, tb, "expected table of length {d}, got {d}", .{ info.len, str.len }); 1764 | 1765 | return str[0..info.len].*; 1766 | } 1767 | 1768 | if (!L.istable(idx)) 1769 | check_throw(L, options, tb, "expected table, got {s}", .{L.typenameof(idx)}); 1770 | 1771 | const tlen = L.lenof(idx); 1772 | if (tlen != info.len) 1773 | check_throw(L, options, tb, "expected table of length {d}, got {d}", .{ info.len, tlen }); 1774 | 1775 | var res: T = undefined; 1776 | 1777 | for (res[0..], 0..) |*slot, i| { 1778 | _ = L.rawgeti(idx, @as(Integer, @intCast(i)) + 1); 1779 | 1780 | tb.push("[]"); 1781 | slot.* = L.checkInternal(info.child, -1, options, tb); 1782 | tb.pop(); 1783 | } 1784 | 1785 | L.pop(info.len); 1786 | return res; 1787 | }, 1788 | .@"struct" => |info| { 1789 | if (!L.istable(idx)) 1790 | check_throw(L, options, tb, "expected table, got {s}", .{L.typenameof(idx)}); 1791 | 1792 | var res: T = undefined; 1793 | 1794 | inline for (info.fields) |field| { 1795 | _ = L.getfield(idx, literal(field.name)); 1796 | 1797 | tb.push("." ++ field.name); 1798 | @field(res, field.name) = L.checkInternal(field.type, -1, options, tb); 1799 | tb.pop(); 1800 | } 1801 | 1802 | L.pop(info.fields.len); 1803 | return res; 1804 | }, 1805 | .pointer => |info| { 1806 | if (comptime isZigString(T)) { 1807 | if (!L.isstring(idx)) 1808 | check_throw(L, options, tb, "expected string, got {s}", .{L.typenameof(idx)}); 1809 | 1810 | if (!info.is_const) { 1811 | if (options.allocator == null) 1812 | check_throw(L, options, tb, "cannot allocate non-const string, missing allocator", .{L.typenameof(idx)}); 1813 | 1814 | const str = L.tostring(idx) orelse unreachable; 1815 | return options.allocator.dupe(str) catch 1816 | L.raise("out of memory", .{}); 1817 | } 1818 | 1819 | return L.tostring(idx) orelse unreachable; 1820 | } 1821 | 1822 | switch (T) { 1823 | [*c]const u8, [*:0]const u8 => { 1824 | if (!L.isstring(idx)) 1825 | check_throw(L, options, tb, "expected string, got {s}", .{L.typenameof(idx)}); 1826 | 1827 | return c.lua_tolstring(to(L), idx, null).?; 1828 | }, 1829 | else => switch (info.size) { 1830 | .One, .Many, .C => { 1831 | if (!L.isuserdata(idx)) 1832 | check_throw(L, options, tb, "expected userdata, got {s}", .{L.typenameof(idx)}); 1833 | 1834 | return @ptrCast(L.touserdata(anyopaque, idx).?); 1835 | }, 1836 | .Slice => { 1837 | if (!L.istable(idx)) 1838 | check_throw(L, options, tb, "expected table, got {s}", .{L.typenameof(idx)}); 1839 | 1840 | if (options.allocator == null) 1841 | check_throw(L, options, tb, "cannot allocate slice, missing allocator", .{L.typenameof(idx)}); 1842 | 1843 | const sentinel = if (info.sentinel) |ptr| @as(*const info.child, @ptrCast(ptr)).* else null; 1844 | 1845 | const slen = L.lenof(idx); 1846 | const ptr = options.allocator.allocWithOptions(info.child, slen, info.alignment, sentinel) catch 1847 | L.raise("out of memory", .{}); 1848 | 1849 | for (ptr[0..], 0..) |*slot, i| { 1850 | _ = L.rawgeti(idx, @as(Integer, @intCast(i)) + 1); 1851 | 1852 | tb.push("[]"); 1853 | slot.* = L.checkInternal(info.child, -1, options, tb); 1854 | tb.pop(); 1855 | } 1856 | 1857 | L.pop(slen); 1858 | return ptr; 1859 | }, 1860 | }, 1861 | } 1862 | }, 1863 | .optional => |info| { 1864 | if (L.isnoneornil(idx)) return null; 1865 | 1866 | tb.push(".?"); 1867 | defer tb.pop(); 1868 | 1869 | return L.checkInternal(info.child, idx, options, tb); 1870 | }, 1871 | .@"enum" => |info| { 1872 | if (L.isnumber(idx)) { 1873 | const value = @as(info.tag_type, @intCast(L.tointeger(idx))); 1874 | 1875 | return std.meta.intToEnum(T, value) catch 1876 | check_throw(L, options, tb, "invalid enum value '{d}' for {s}", .{ value, @typeName(T) }); 1877 | } else if (L.isstring(idx)) { 1878 | const value = L.tostring(idx) orelse unreachable; 1879 | 1880 | return std.meta.stringToEnum(T, value) orelse 1881 | check_throw(L, options, tb, "invalid enum value '{s}' for {s}", .{ value, @typeName(T) }); 1882 | } else { 1883 | check_throw(L, options, tb, "expected number or string, got {s}", .{L.typenameof(idx)}); 1884 | } 1885 | }, 1886 | else => @compileError("check not implemented for " ++ @typeName(T)), 1887 | } 1888 | } 1889 | 1890 | /// [-0, +0, v] Checks that the value at the given index is of the given type. Returns the value. 1891 | pub fn check(L: *State, comptime T: type, idx: Index, options: CheckOptions) T { 1892 | var tb = CheckTraceback{ 1893 | .stack = undefined, 1894 | .extra = 0, 1895 | .len = 0, 1896 | }; 1897 | defer assert(tb.len == 0); 1898 | 1899 | if (options.label == 0) { 1900 | var new_options = options; 1901 | new_options.label = idx; 1902 | 1903 | return L.checkInternal(T, idx, new_options, &tb); 1904 | } else { 1905 | return L.checkInternal(T, idx, options, &tb); 1906 | } 1907 | } 1908 | 1909 | /// [-0, +0, v] Checks that the value at the given index is of the given resource type. Returns a pointer to the 1910 | /// resource. 1911 | /// 1912 | /// If the resource type has a `lunaro_typename` field, it will be used as the resource name. Otherwise, the 1913 | /// type name will be used. 1914 | /// 1915 | /// If the resource type has a `lunaro_raw` field and it is true, the resource will be treated as a raw resource 1916 | /// and the metatable will not be checked. 1917 | pub fn checkResource(L: *State, comptime T: type, idx: Index, options: CheckOptions) *align(@alignOf(usize)) T { 1918 | const resource_name = if (@hasDecl(T, "lunaro_typename")) 1919 | literal(T.lunaro_typename) 1920 | else 1921 | literal(@typeName(T)); 1922 | 1923 | const resource_is_raw = @hasDecl(T, "lunaro_raw") and T.lunaro_raw; 1924 | 1925 | var new_options = options; 1926 | if (options.label == 0) 1927 | new_options.label = idx; 1928 | 1929 | if (!L.isuserdata(idx)) 1930 | check_throw(L, new_options, null, "expected resource '{s}', got {s}", .{ resource_name, L.typenameof(idx) }); 1931 | 1932 | if (resource_is_raw) 1933 | return L.touserdata(T, idx).?; 1934 | 1935 | if (!L.getmetatable(idx) or L.typeof(-1) != .table) 1936 | check_throw(L, new_options, null, "expected resource '{s}', got userdata", .{resource_name}); 1937 | 1938 | if (L.getmetatablefor(resource_name) != .table) 1939 | check_throw(L, new_options, null, "attempt to check non-existent resource: '{s}'", .{resource_name}); 1940 | 1941 | if (!L.rawequal(-1, -2)) { 1942 | if (L.getfield(-2, "__name") == .string) { 1943 | check_throw(L, new_options, null, "expected resource '{s}', got '{s}'", .{ resource_name, L.tostring(-1).? }); 1944 | } else { 1945 | check_throw(L, new_options, null, "expected resource '{s}', got userdata", .{resource_name}); 1946 | } 1947 | } 1948 | 1949 | L.pop(2); 1950 | return L.touserdata(T, idx).?; 1951 | } 1952 | 1953 | // [-0, +0, e] Checks that the value at the given index is a function or callable table 1954 | pub fn checkCallable(L: *State, idx: Index, options: CheckOptions) void { 1955 | const typ = L.typeof(idx); 1956 | 1957 | switch (typ) { 1958 | .function => return, 1959 | .table, .userdata => if (L.getmetatable(idx) and L.typeof(-1) == .table) { 1960 | defer L.pop(2); 1961 | 1962 | if (L.getfield(-1, "__call") == .function) return; 1963 | }, 1964 | else => {}, 1965 | } 1966 | 1967 | var new_options = options; 1968 | if (options.label == 0) 1969 | new_options.label = idx; 1970 | 1971 | check_throw(L, new_options, null, "expected function, got {s}", .{L.typenameof(idx)}); 1972 | } 1973 | 1974 | /// [-0, +0, -] Returns a value representation of the value at an index, and stores a reference to that value if necessary 1975 | pub fn valueAt(L: *State, index: Index) !Value { 1976 | return Value.init(L, index); 1977 | } 1978 | 1979 | /// [-0, +0, -] Returns a table representation of the value at an index, and stores a reference to that value 1980 | /// 1981 | /// Throws an error if the value is not a table 1982 | pub fn tableAt(L: *State, index: Index) !Table { 1983 | return Table.init(L, index); 1984 | } 1985 | 1986 | /// [-0, +0, -] Returns a function representation of the value at an index, and stores a reference to that value 1987 | pub fn functionAt(L: *State, index: Index) !Function { 1988 | return Function.init(L, index); 1989 | } 1990 | }; 1991 | 1992 | /// Returns true if the passed type will coerce to []const u8. 1993 | /// Any of the following are considered strings: 1994 | /// ``` 1995 | /// []const u8, [:S]const u8, *const [N]u8, *const [N:S]u8, 1996 | /// []u8, [:S]u8, *[:S]u8, *[N:S]u8. 1997 | /// ``` 1998 | /// These types are not considered strings: 1999 | /// ``` 2000 | /// u8, [N]u8, [*]const u8, [*:0]const u8, 2001 | /// [*]const [N]u8, []const u16, []const i8, 2002 | /// *const u8, ?[]const u8, ?*const [N]u8. 2003 | /// ``` 2004 | inline fn isZigString(comptime T: type) bool { 2005 | return blk: { 2006 | // Only pointer types can be strings, no optionals 2007 | const info = @typeInfo(T); 2008 | if (info != .pointer) break :blk false; 2009 | 2010 | const ptr = &info.pointer; 2011 | // Check for CV qualifiers that would prevent coerction to []const u8 2012 | if (ptr.is_volatile or ptr.is_allowzero) break :blk false; 2013 | 2014 | // If it's already a slice, simple check. 2015 | if (ptr.size == .Slice) { 2016 | break :blk ptr.child == u8; 2017 | } 2018 | 2019 | // Otherwise check if it's an array type that coerces to slice. 2020 | if (ptr.size == .One) { 2021 | const child = @typeInfo(ptr.child); 2022 | if (child == .array) { 2023 | const arr = &child.array; 2024 | break :blk arr.child == u8; 2025 | } 2026 | } 2027 | 2028 | break :blk false; 2029 | }; 2030 | } 2031 | 2032 | test isZigString { 2033 | try std.testing.expect(isZigString([]const u8)); 2034 | try std.testing.expect(isZigString([]u8)); 2035 | try std.testing.expect(isZigString([:0]const u8)); 2036 | try std.testing.expect(isZigString([:0]u8)); 2037 | try std.testing.expect(isZigString([:5]const u8)); 2038 | try std.testing.expect(isZigString([:5]u8)); 2039 | try std.testing.expect(isZigString(*const [0]u8)); 2040 | try std.testing.expect(isZigString(*[0]u8)); 2041 | try std.testing.expect(isZigString(*const [0:0]u8)); 2042 | try std.testing.expect(isZigString(*[0:0]u8)); 2043 | try std.testing.expect(isZigString(*const [0:5]u8)); 2044 | try std.testing.expect(isZigString(*[0:5]u8)); 2045 | try std.testing.expect(isZigString(*const [10]u8)); 2046 | try std.testing.expect(isZigString(*[10]u8)); 2047 | try std.testing.expect(isZigString(*const [10:0]u8)); 2048 | try std.testing.expect(isZigString(*[10:0]u8)); 2049 | try std.testing.expect(isZigString(*const [10:5]u8)); 2050 | try std.testing.expect(isZigString(*[10:5]u8)); 2051 | 2052 | try std.testing.expect(!isZigString(u8)); 2053 | try std.testing.expect(!isZigString([4]u8)); 2054 | try std.testing.expect(!isZigString([4:0]u8)); 2055 | try std.testing.expect(!isZigString([*]const u8)); 2056 | try std.testing.expect(!isZigString([*]const [4]u8)); 2057 | try std.testing.expect(!isZigString([*c]const u8)); 2058 | try std.testing.expect(!isZigString([*c]const [4]u8)); 2059 | try std.testing.expect(!isZigString([*:0]const u8)); 2060 | try std.testing.expect(!isZigString([*:0]const u8)); 2061 | try std.testing.expect(!isZigString(*[]const u8)); 2062 | try std.testing.expect(!isZigString(?[]const u8)); 2063 | try std.testing.expect(!isZigString(?*const [4]u8)); 2064 | try std.testing.expect(!isZigString([]allowzero u8)); 2065 | try std.testing.expect(!isZigString([]volatile u8)); 2066 | try std.testing.expect(!isZigString(*allowzero [4]u8)); 2067 | try std.testing.expect(!isZigString(*volatile [4]u8)); 2068 | } 2069 | 2070 | comptime { 2071 | if (@import("builtin").is_test) 2072 | std.testing.refAllDecls(State); 2073 | } 2074 | -------------------------------------------------------------------------------- /src/Table.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const lunaro = @import("lunaro.zig"); 3 | 4 | const State = lunaro.State; 5 | const Index = lunaro.Index; 6 | 7 | const Value = lunaro.Value; 8 | const Table = @This(); 9 | 10 | const assert = std.debug.assert; 11 | state: *State, 12 | ref: Index, 13 | 14 | /// [-0, +0, -] Initializes a table from the value at the given index. This stores a reference to the table. 15 | /// 16 | /// Asserts that the value at the given index is a table. 17 | pub fn init(L: *State, index: Index) Table { 18 | assert(L.typeof(index) == .table); 19 | 20 | L.pushvalue(index); 21 | return .{ .ref = L.ref(lunaro.REGISTRYINDEX), .state = L }; 22 | } 23 | 24 | /// [-0, +0, -] Deinitializes this representation and dereferences the table. 25 | pub fn deinit(table: Table) void { 26 | table.state.unref(lunaro.REGISTRYINDEX, table.ref); 27 | } 28 | 29 | /// [-0, +1, m] Pushes this table onto the stack of `to`. The `to` thread must be in the same state as this table. 30 | pub fn push(table: Table, to: *State) void { 31 | assert(table.state.geti(lunaro.REGISTRYINDEX, table.ref) == .table); 32 | 33 | if (to != table.state) 34 | table.state.xmove(to, 1); 35 | } 36 | 37 | /// [-0, +1, e] Gets the value at the given key in this table and pushes it onto the stack. 38 | pub fn get(table: Table, key: anytype) lunaro.Type { 39 | assert(table.state.geti(lunaro.REGISTRYINDEX, table.ref) == .table); 40 | defer table.state.pop(1); 41 | table.state.push(key); 42 | return table.state.gettable(-2); 43 | } 44 | 45 | /// [-0, +0, e] Gets the value at the given key in this table as a Value. 46 | pub fn getValue(table: Table, key: anytype) Value { 47 | table.get(key); 48 | defer table.state.pop(1); 49 | 50 | return Value.init(table.state, -1); 51 | } 52 | 53 | /// [-1, +0, e] Sets the value at the given key in this table with the value at the top of the stack. 54 | pub fn set(table: Table, key: anytype) void { 55 | assert(table.state.geti(lunaro.REGISTRYINDEX, table.ref) == .table); 56 | table.state.push(key); 57 | table.state.pushvalue(-3); 58 | table.state.settable(-3); 59 | table.state.pop(2); 60 | } 61 | 62 | /// [-0, +0, e] Sets the value at the given key in this table. 63 | pub fn setValue(table: Table, key: anytype, value: anytype) void { 64 | assert(table.state.geti(lunaro.REGISTRYINDEX, table.ref) == .table); 65 | table.state.push(key); 66 | table.state.push(value); 67 | table.state.settable(-3); 68 | table.state.pop(1); 69 | } 70 | 71 | comptime { 72 | if (@import("builtin").is_test) 73 | std.testing.refAllDecls(Table); 74 | } 75 | -------------------------------------------------------------------------------- /src/Value.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const lunaro = @import("lunaro.zig"); 3 | 4 | const State = lunaro.State; 5 | 6 | /// A union of the possible Lua types, mostly used for debugging. 7 | pub const Value = union(enum) { 8 | nil, 9 | boolean: bool, 10 | number: lunaro.Number, 11 | integer: lunaro.Integer, 12 | string: [:0]const u8, 13 | table: lunaro.Table, 14 | function: lunaro.Function, 15 | userdata: *anyopaque, 16 | lightuserdata: *anyopaque, 17 | 18 | pub fn format(value: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 19 | _ = fmt; 20 | 21 | switch (value) { 22 | .nil => return std.fmt.formatBuf("nil", options, writer), 23 | .boolean => return std.fmt.format(writer, "{}", .{value.boolean}), 24 | .number, .integer => |num| return std.fmt.format(writer, "{d}", .{num}), 25 | .string => return std.fmt.format(writer, "'{'}'", .{std.zig.fmtEscapes(value.string)}), 26 | .function => return std.fmt.format(writer, "function: 0x{?x}", .{@intFromPtr(value.function)}), 27 | .table => return std.fmt.format(writer, "table: 0x{x}", .{@intFromPtr(value.table)}), 28 | .lightuserdata, .userdata => |ptr| return std.fmt.format(writer, "userdata: 0x{x}", .{@intFromPtr(ptr)}), 29 | } 30 | } 31 | 32 | /// [-0, +0, -] Returns a value representation of the current value, and stores a reference to that value if necessary 33 | pub fn init(L: *State, index: lunaro.Index) !Value { 34 | const T = L.typeof(-1); 35 | 36 | switch (T) { 37 | .none, .nil => return .{ .nil = {} }, 38 | .boolean => return .{ .boolean = L.toboolean(index) }, 39 | .lightuserdata => return .{ .lightuserdata = L.touserdata(anyopaque, index).? }, 40 | .number => if (L.isinteger(index)) 41 | return .{ .integer = L.tointeger(index) } 42 | else 43 | return .{ .number = L.tonumber(index) }, 44 | .string => return .{ .string = L.tostring(index).? }, 45 | .table => return .{ .table = lunaro.Table.init(L, index) }, 46 | .function => return .{ .function = lunaro.Function.init(L, index) }, 47 | .userdata => return .{ .userdata = L.touserdata(anyopaque, index).? }, 48 | .thread => return error.NotImplemented, 49 | } 50 | } 51 | 52 | /// [-0, +1, -] Pushes this value onto the stack. 53 | pub fn push(value: Value, L: *State) void { 54 | switch (value) { 55 | .nil => L.pushnil(), 56 | .boolean => |v| L.pushboolean(v), 57 | .number => |v| L.pushnumber(v), 58 | .integer => |v| L.pushinteger(v), 59 | .string => |v| L.pushstring(v), 60 | .table => |v| v.push(L), 61 | .function => |v| v.push(L), 62 | .userdata => |v| L.pushlightuserdata(v), // FIXME: should this copy the userdata? 63 | .lightuserdata => |v| L.pushlightuserdata(v), 64 | } 65 | } 66 | }; 67 | 68 | comptime { 69 | if (@import("builtin").is_test) 70 | std.testing.refAllDecls(Value); 71 | } 72 | -------------------------------------------------------------------------------- /src/arithmetic.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const lunaro = @import("lunaro.zig"); 3 | 4 | const State = lunaro.State; 5 | 6 | const lua_version = lunaro.lua_version; 7 | 8 | pub fn le(L: *State, A: lunaro.Index, B: lunaro.Index) bool { 9 | const Ta = L.typeof(A); 10 | const Tb = L.typeof(B); 11 | 12 | if (Ta == .number and Tb == .number) { 13 | const a = L.tonumber(A); 14 | const b = L.tonumber(B); 15 | 16 | return a <= b; 17 | } else if (Ta == .string and Tb == .string) { 18 | const a = L.tostring(A).?; 19 | const b = L.tostring(B).?; 20 | 21 | return std.mem.order(u8, a, b) != .gt; 22 | } 23 | 24 | const absA = L.absindex(A); 25 | const absB = L.absindex(B); 26 | 27 | if (L.getmetafield(absA, "__le") != .nil or L.getmetafield(absB, "__le") != .nil) { 28 | L.pushvalue(absA); 29 | L.pushvalue(absB); 30 | L.call(2, 1); 31 | 32 | const res = L.toboolean(-1); 33 | L.pop(1); 34 | return res; 35 | } 36 | 37 | if (L.getmetafield(absA, "__lt") != .nil or L.getmetafield(absB, "__lt") != .nil) { 38 | L.pushvalue(absB); 39 | L.pushvalue(absA); 40 | L.call(2, 1); 41 | 42 | const res = L.toboolean(-1); 43 | L.pop(1); 44 | return !res; 45 | } 46 | 47 | L.raise("attempt to compare %s with %s", .{ L.typenameof(absA), L.typenameof(absB) }); 48 | } 49 | 50 | pub fn add(L: *State) void { 51 | if (L.isnumber(-2) and L.isnumber(-1)) { 52 | if (lua_version >= 503) { 53 | if (L.isinteger(-2) and L.isinteger(-1)) { 54 | const a = L.tointeger(-2); 55 | const b = L.tointeger(-1); 56 | L.pop(2); 57 | 58 | return L.push(a +% b); 59 | } 60 | } 61 | 62 | const a = L.tonumber(-2); 63 | const b = L.tonumber(-1); 64 | L.pop(2); 65 | 66 | return L.push(a + b); 67 | } 68 | 69 | if (L.getmetafield(-2, "__add") != .nil or L.getmetafield(-1, "__add") != .nil) { 70 | L.pushvalue(-2); 71 | L.pushvalue(-1); 72 | L.call(2, 1); 73 | return; 74 | } 75 | 76 | if (!L.isnumber(-2)) 77 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 78 | 79 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 80 | } 81 | 82 | pub fn sub(L: *State) void { 83 | if (L.isnumber(-2) and L.isnumber(-1)) { 84 | if (lua_version >= 503) { 85 | if (L.isinteger(-2) and L.isinteger(-1)) { 86 | const a = L.tointeger(-2); 87 | const b = L.tointeger(-1); 88 | L.pop(2); 89 | 90 | return L.push(a -% b); 91 | } 92 | } 93 | 94 | const a = L.tonumber(-2); 95 | const b = L.tonumber(-1); 96 | L.pop(2); 97 | 98 | return L.push(a - b); 99 | } 100 | 101 | if (L.getmetafield(-2, "__sub") != .nil or L.getmetafield(-1, "__sub") != .nil) { 102 | L.pushvalue(-2); 103 | L.pushvalue(-1); 104 | L.call(2, 1); 105 | return; 106 | } 107 | 108 | if (!L.isnumber(-2)) 109 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 110 | 111 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 112 | } 113 | 114 | pub fn mul(L: *State) void { 115 | if (L.isnumber(-2) and L.isnumber(-1)) { 116 | if (lua_version >= 503) { 117 | if (L.isinteger(-2) and L.isinteger(-1)) { 118 | const a = L.tointeger(-2); 119 | const b = L.tointeger(-1); 120 | L.pop(2); 121 | 122 | return L.push(a *% b); 123 | } 124 | } 125 | 126 | const a = L.tonumber(-2); 127 | const b = L.tonumber(-1); 128 | L.pop(2); 129 | 130 | return L.push(a * b); 131 | } 132 | 133 | if (L.getmetafield(-2, "__mul") != .nil or L.getmetafield(-1, "__mul") != .nil) { 134 | L.pushvalue(-2); 135 | L.pushvalue(-1); 136 | L.call(2, 1); 137 | return; 138 | } 139 | 140 | if (!L.isnumber(-2)) 141 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 142 | 143 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 144 | } 145 | 146 | pub fn div(L: *State) void { 147 | if (L.isnumber(-2) and L.isnumber(-1)) { 148 | if (lua_version >= 503) { 149 | if (L.isinteger(-2) and L.isinteger(-1)) { 150 | const a = L.tointeger(-2); 151 | const b = L.tointeger(-1); 152 | L.pop(2); 153 | 154 | return L.push(@divTrunc(a, b)); 155 | } 156 | } 157 | 158 | const a = L.tonumber(-2); 159 | const b = L.tonumber(-1); 160 | L.pop(2); 161 | 162 | return L.push(a / b); 163 | } 164 | 165 | if (L.getmetafield(-2, "__div") != .nil or L.getmetafield(-1, "__div") != .nil) { 166 | L.pushvalue(-2); 167 | L.pushvalue(-1); 168 | L.call(2, 1); 169 | return; 170 | } 171 | 172 | if (!L.isnumber(-2)) 173 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 174 | 175 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 176 | } 177 | 178 | pub fn idiv(L: *State) void { 179 | if (L.isnumber(-2) and L.isnumber(-1)) { 180 | const a = L.tonumber(-2); 181 | const b = L.tonumber(-1); 182 | L.pop(2); 183 | 184 | return L.push(@divFloor(a, b)); 185 | } 186 | 187 | if (L.getmetafield(-2, "__idiv") != .nil or L.getmetafield(-1, "__idiv") != .nil) { 188 | L.pushvalue(-2); 189 | L.pushvalue(-1); 190 | L.call(2, 1); 191 | return; 192 | } 193 | 194 | if (!L.isnumber(-2)) 195 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 196 | 197 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 198 | } 199 | 200 | pub fn mod(L: *State) void { 201 | if (L.isnumber(-2) and L.isnumber(-1)) { 202 | if (lua_version >= 503) { 203 | if (L.isinteger(-2) and L.isinteger(-1)) { 204 | const a = L.tointeger(-2); 205 | const b = L.tointeger(-1); 206 | L.pop(2); 207 | 208 | return L.push(std.zig.c_translation.signedRemainder(a, b)); 209 | } 210 | } 211 | 212 | const a = L.tonumber(-2); 213 | const b = L.tonumber(-1); 214 | L.pop(2); 215 | 216 | return L.push(@mod(a, b)); 217 | } 218 | 219 | if (L.getmetafield(-2, "__mod") != .nil or L.getmetafield(-1, "__mod") != .nil) { 220 | L.pushvalue(-2); 221 | L.pushvalue(-1); 222 | L.call(2, 1); 223 | return; 224 | } 225 | 226 | if (!L.isnumber(-2)) 227 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 228 | 229 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 230 | } 231 | 232 | pub fn pow(L: *State) void { 233 | if (L.isnumber(-2) and L.isnumber(-1)) { 234 | if (lua_version >= 503) { 235 | if (L.isinteger(-2) and L.isinteger(-1)) { 236 | const a = L.tointeger(-2); 237 | const b = L.tointeger(-1); 238 | L.pop(2); 239 | 240 | return L.push(std.math.pow(@TypeOf(a, b), a, b)); 241 | } 242 | } 243 | 244 | const a = L.tonumber(-2); 245 | const b = L.tonumber(-1); 246 | L.pop(2); 247 | 248 | return L.push(std.math.pow(@TypeOf(a, b), a, b)); 249 | } 250 | 251 | if (L.getmetafield(-2, "__pow") != .nil or L.getmetafield(-1, "__pow") != .nil) { 252 | L.pushvalue(-2); 253 | L.pushvalue(-1); 254 | L.call(2, 1); 255 | return; 256 | } 257 | 258 | if (!L.isnumber(-2)) 259 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 260 | 261 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 262 | } 263 | 264 | pub fn unm(L: *State) void { 265 | if (L.isnumber(-1)) { 266 | if (lua_version >= 503) { 267 | if (L.isinteger(-1)) { 268 | const a = L.tointeger(-1); 269 | L.pop(1); 270 | 271 | return L.push(-a); 272 | } 273 | } 274 | 275 | const a = L.tonumber(-1); 276 | L.pop(1); 277 | 278 | return L.push(-a); 279 | } 280 | 281 | if (L.getmetafield(-1, "__unm") != .nil) { 282 | L.pushvalue(-1); 283 | L.call(1, 1); 284 | return; 285 | } 286 | 287 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 288 | } 289 | 290 | pub fn bnot(L: *State) void { 291 | if (L.isnumber(-1)) { 292 | const a = L.tointeger(-1); 293 | L.pop(1); 294 | 295 | return L.push(~a); 296 | } 297 | 298 | if (L.getmetafield(-1, "__bnot") != .nil) { 299 | L.pushvalue(-1); 300 | L.call(1, 1); 301 | return; 302 | } 303 | 304 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 305 | } 306 | 307 | pub fn band(L: *State) void { 308 | if (L.isnumber(-2) and L.isnumber(-1)) { 309 | const a = L.tointeger(-2); 310 | const b = L.tointeger(-1); 311 | L.pop(2); 312 | 313 | return L.push(a & b); 314 | } 315 | 316 | if (L.getmetafield(-2, "__band") != .nil or L.getmetafield(-1, "__band") != .nil) { 317 | L.pushvalue(-2); 318 | L.pushvalue(-1); 319 | L.call(2, 1); 320 | return; 321 | } 322 | 323 | if (!L.isnumber(-2)) 324 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 325 | 326 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 327 | } 328 | 329 | pub fn bor(L: *State) void { 330 | if (L.isnumber(-2) and L.isnumber(-1)) { 331 | const a = L.tointeger(-2); 332 | const b = L.tointeger(-1); 333 | L.pop(2); 334 | 335 | return L.push(a | b); 336 | } 337 | 338 | if (L.getmetafield(-2, "__bor") != .nil or L.getmetafield(-1, "__bor") != .nil) { 339 | L.pushvalue(-2); 340 | L.pushvalue(-1); 341 | L.call(2, 1); 342 | return; 343 | } 344 | 345 | if (!L.isnumber(-2)) 346 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 347 | 348 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 349 | } 350 | 351 | pub fn bxor(L: *State) void { 352 | if (L.isnumber(-2) and L.isnumber(-1)) { 353 | const a = L.tointeger(-2); 354 | const b = L.tointeger(-1); 355 | L.pop(2); 356 | 357 | return L.push(a ^ b); 358 | } 359 | 360 | if (L.getmetafield(-2, "__bxor") != .nil or L.getmetafield(-1, "__bxor") != .nil) { 361 | L.pushvalue(-2); 362 | L.pushvalue(-1); 363 | L.call(2, 1); 364 | return; 365 | } 366 | 367 | if (!L.isnumber(-2)) 368 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 369 | 370 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 371 | } 372 | 373 | pub fn shl(L: *State) void { 374 | if (L.isnumber(-2) and L.isnumber(-1)) { 375 | const a = L.tointeger(-2); 376 | const amt = L.tointeger(-1); 377 | L.pop(2); 378 | 379 | if (amt >= @bitSizeOf(@TypeOf(a))) return L.push(0); 380 | return L.push(a << @intCast(amt)); 381 | } 382 | 383 | if (L.getmetafield(-2, "__shl") != .nil or L.getmetafield(-1, "__shl") != .nil) { 384 | L.pushvalue(-2); 385 | L.pushvalue(-1); 386 | L.call(2, 1); 387 | return; 388 | } 389 | 390 | if (!L.isnumber(-2)) 391 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 392 | 393 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 394 | } 395 | 396 | pub fn shr(L: *State) void { 397 | if (L.isnumber(-2) and L.isnumber(-1)) { 398 | const a = L.tointeger(-2); 399 | const amt = L.tointeger(-1); 400 | L.pop(2); 401 | 402 | if (amt >= @bitSizeOf(@TypeOf(a))) return L.push(0); 403 | return L.push(a >> @intCast(amt)); 404 | } 405 | 406 | if (L.getmetafield(-2, "__shr") != .nil or L.getmetafield(-1, "__shr") != .nil) { 407 | L.pushvalue(-2); 408 | L.pushvalue(-1); 409 | L.call(2, 1); 410 | return; 411 | } 412 | 413 | if (!L.isnumber(-2)) 414 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-2)}); 415 | 416 | L.raise("attempt to perform arithmetic on a %s value", .{L.typenameof(-1)}); 417 | } 418 | -------------------------------------------------------------------------------- /src/lunaro.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const assert = std.debug.assert; 4 | const comptimePrint = std.fmt.comptimePrint; 5 | 6 | const luaconf = @cImport({ 7 | @cDefine("luajit_c", "1"); 8 | @cInclude("luaconf.h"); 9 | }); 10 | 11 | pub const is_luajit = @hasDecl(luaconf, "LUA_PROGNAME") and std.mem.eql(u8, luaconf.LUA_PROGNAME, "luajit"); 12 | 13 | pub const c = @cImport({ 14 | @cInclude("lua.h"); 15 | @cInclude("lauxlib.h"); 16 | @cInclude("lualib.h"); 17 | 18 | if (is_luajit) 19 | @cInclude("luajit.h"); 20 | }); 21 | 22 | pub const safety = @import("safety.zig"); 23 | 24 | pub const State = @import("State.zig").State; 25 | pub const Value = @import("Value.zig").Value; 26 | pub const Table = @import("Table.zig"); 27 | pub const Buffer = @import("Buffer.zig"); 28 | pub const Function = @import("Function.zig"); 29 | 30 | pub const lua_version = c.LUA_VERSION_NUM; 31 | 32 | /// The type of floating point numbers in Lua. By default this is `f64`. 33 | /// In Lua 5.1 and 5.2, it is possible that this may instead be an integer. 34 | pub const Number = c.lua_Number; 35 | 36 | /// The type of integers in Lua. By default this is `i64`. 37 | pub const Integer = c.lua_Integer; 38 | 39 | /// The type of indexes into the Lua stack. By default this is `i32`. 40 | pub const Index = c_int; 41 | 42 | /// The type of sizes used by Lua. By default this is `u31` and can be implicitly converted to `Index`. 43 | pub const Size = std.meta.Int(.unsigned, @bitSizeOf(Index) - 1); 44 | 45 | /// The type of external functions used by Lua. 46 | pub const CFn = c.lua_CFunction; 47 | 48 | /// The type of reader functions used by Lua. 49 | pub const ReaderFn = c.lua_Reader; 50 | 51 | /// The type of writer functions used by Lua. 52 | pub const WriterFn = c.lua_Writer; 53 | 54 | /// The type of allocation functions used by Lua. 55 | pub const AllocFn = c.lua_Alloc; 56 | 57 | /// The type of debug hook functions used by Lua. 58 | pub const HookFn = c.lua_Hook; 59 | 60 | /// The structure used to hold debug information. Some fields may not exist in some versions of Lua. 61 | pub const DebugInfo = c.lua_Debug; 62 | 63 | /// The pseudo-index used to refer to the registry. 64 | pub const REGISTRYINDEX = c.LUA_REGISTRYINDEX; 65 | 66 | fn lookup(comptime field: []const u8, comptime default: anytype) @TypeOf(default) { 67 | if (@hasDecl(c, field)) return @field(c, field); 68 | return default; 69 | } 70 | 71 | /// The enum used to represent the status of a thread. 72 | pub const ThreadStatus = enum(c_int) { 73 | ok = lookup("LUA_OK", 0), 74 | yield = c.LUA_YIELD, 75 | err_runtime = c.LUA_ERRRUN, 76 | err_syntax = c.LUA_ERRSYNTAX, 77 | err_memory = c.LUA_ERRMEM, 78 | err_handler = c.LUA_ERRERR, 79 | err_file = c.LUA_ERRFILE, 80 | }; 81 | 82 | /// The enum of different possible types of a Lua value. 83 | pub const Type = enum(c_int) { 84 | none = c.LUA_TNONE, 85 | nil = c.LUA_TNIL, 86 | boolean = c.LUA_TBOOLEAN, 87 | lightuserdata = c.LUA_TLIGHTUSERDATA, 88 | number = c.LUA_TNUMBER, 89 | string = c.LUA_TSTRING, 90 | table = c.LUA_TTABLE, 91 | function = c.LUA_TFUNCTION, 92 | userdata = c.LUA_TUSERDATA, 93 | thread = c.LUA_TTHREAD, 94 | }; 95 | 96 | /// The enum of different possible arithmetic operations to be passed to `State.arith`. 97 | /// A negative value indicates that lunaro will be forced to polyfill the operation. 98 | pub const ArithOp = enum(c_int) { 99 | add = lookup("LUA_OPADD", -1), 100 | sub = lookup("LUA_OPSUB", -2), 101 | mul = lookup("LUA_OPMUL", -3), 102 | div = lookup("LUA_OPDIV", -4), 103 | idiv = lookup("LUA_OPIDIV", -5), 104 | mod = lookup("LUA_OPMOD", -6), 105 | pow = lookup("LUA_OPPOW", -7), 106 | unm = lookup("LUA_OPUNM", -8), 107 | bnot = lookup("LUA_OPBNOT", -9), 108 | band = lookup("LUA_OPBAND", -10), 109 | bor = lookup("LUA_OPBOR", -11), 110 | bxor = lookup("LUA_OPBXOR", -12), 111 | shl = lookup("LUA_OPSHL", -13), 112 | shr = lookup("LUA_OPSHR", -14), 113 | }; 114 | 115 | /// The enum of different possible comparison operations to be passed to `State.compare`. 116 | pub const CompareOp = enum(c_int) { 117 | eq = lookup("LUA_OPEQ", -1), 118 | lt = lookup("LUA_OPLT", -2), 119 | le = lookup("LUA_OPLE", -3), 120 | }; 121 | 122 | /// The different possible modes of loading a Lua chunk. 123 | pub const LoadMode = enum { 124 | binary, 125 | text, 126 | either, 127 | }; 128 | 129 | pub const helpers = struct { 130 | /// Wraps any zig function to be used as a Lua C function. 131 | /// 132 | /// Arguments will be checked using `State.check`. Follows the same rules as `wrapCFn`. 133 | pub fn wrapAnyFn(func: anytype) CFn { 134 | if (@TypeOf(func) == CFn) return func; 135 | 136 | const info = switch (@typeInfo(@TypeOf(func))) { 137 | .@"fn" => |info_Fn| info_Fn, 138 | .pointer => |info_Pointer| switch (@typeInfo(info_Pointer.child)) { 139 | .@"fn" => |info_Fn| info_Fn, 140 | else => @compileError("expected a `fn(X...) Y` or `*const fn(X...) Y`"), 141 | }, 142 | else => @compileError("expected a `fn(X...) Y` or `*const fn(X...) Y`"), 143 | }; 144 | 145 | if (info.params.len == 1 and info.params[0].type.? == *State) { 146 | return wrapCFn(func); 147 | } 148 | 149 | if (info.is_generic) @compileError("cannot wrap generic functions as Lua C functions"); 150 | 151 | return wrapCFn(struct { 152 | fn wrapped(L: *State) info.return_type.? { 153 | var args: std.meta.ArgsTuple(@TypeOf(func)) = undefined; 154 | 155 | inline for (&args, 0..) |*slot, i| { 156 | if (i == 0 and @TypeOf(slot) == *State) { 157 | slot.* = L; 158 | } else { 159 | slot.* = L.check(@TypeOf(slot), i + 1, @src()); 160 | } 161 | } 162 | 163 | return @call(.auto, func, args); 164 | } 165 | }.wrapped); 166 | } 167 | 168 | /// Wraps a zig-like Lua function (with a `*State` as its first argument) to be used as a Lua C function. 169 | /// 170 | /// If the function returns `c_int`, it will be returned unmodified. 171 | /// Return values will be pushed using `State.push`. 172 | pub fn wrapCFn(func: anytype) CFn { 173 | if (@TypeOf(func) == CFn) return func; 174 | 175 | const info = switch (@typeInfo(@TypeOf(func))) { 176 | .@"fn" => |info_Fn| info_Fn, 177 | .pointer => |info_Pointer| switch (@typeInfo(info_Pointer.child)) { 178 | .@"fn" => |info_Fn| info_Fn, 179 | else => @compileError("expected a `fn(*State) X` or `*const fn(*State) X`"), 180 | }, 181 | else => @compileError("expected a `fn(*State) X` or `*const fn(*State) X`"), 182 | }; 183 | 184 | return struct { 185 | fn wrapped(L_opt: ?*c.lua_State) callconv(.C) c_int { 186 | const L: *State = @ptrCast(L_opt.?); 187 | const T = info.return_type.?; 188 | 189 | const top = switch (@typeInfo(T)) { 190 | .error_union => L.gettop(), 191 | else => {}, 192 | }; 193 | 194 | const scheck = safety.StackCheck.init(L); 195 | const result = @call(.auto, func, .{L}); 196 | 197 | if (T == c_int) 198 | return scheck.check(func, L, result); 199 | 200 | switch (@typeInfo(T)) { 201 | .void => return scheck.check(func, L, 0), 202 | .error_union => |err_info| { 203 | const actual_result = result catch |err| { 204 | L.settop(top); 205 | L.pusherror(err); 206 | 207 | return scheck.check(func, L, 2); 208 | }; 209 | 210 | if (err_info.payload == c_int) 211 | return scheck.check(func, L, actual_result); 212 | 213 | L.push(actual_result); 214 | return scheck.check(func, L, 1); 215 | }, 216 | else => { 217 | L.push(result); 218 | return scheck.check(func, L, 1); 219 | }, 220 | } 221 | } 222 | }.wrapped; 223 | } 224 | 225 | /// Wraps a zig allocator to be used as a Lua allocator. This function should be used as the allocator function 226 | /// to `initWithAlloc`. The `std.mem.Allocator` should be passed as the `ud` argument to `initWithAlloc`. 227 | /// 228 | /// Usage: `L.initWithAlloc(helpers.alloc, allocator)`. 229 | pub fn alloc(ud: ?*anyopaque, ptr: ?*anyopaque, oldsize: usize, newsize: usize) callconv(.C) ?*anyopaque { 230 | assert(ud != null); 231 | 232 | const allocator: *std.mem.Allocator = @ptrCast(@alignCast(ud.?)); 233 | const alignment = @alignOf(c.max_align_t); 234 | 235 | const ptr_aligned: ?[*]align(alignment) u8 = @ptrCast(@alignCast(ptr)); 236 | 237 | if (ptr_aligned) |prev_ptr| { 238 | const prev_slice = prev_ptr[0..oldsize]; 239 | 240 | if (newsize == 0) { 241 | allocator.free(prev_slice); 242 | return null; 243 | } 244 | 245 | if (newsize <= oldsize) { 246 | assert(allocator.resize(prev_slice, newsize)); 247 | 248 | return prev_slice.ptr; 249 | } 250 | 251 | const new_slice = allocator.realloc(prev_slice, newsize) catch return null; 252 | return new_slice.ptr; 253 | } 254 | 255 | if (newsize == 0) return null; 256 | 257 | const new_ptr = allocator.alignedAlloc(u8, alignment, newsize) catch return null; 258 | return new_ptr.ptr; 259 | } 260 | 261 | pub const ReaderState = struct { 262 | reader: std.io.AnyReader, 263 | buffer: [1024]u8 = undefined, 264 | 265 | /// A `ReaderFn` that accepts a `helpers.ReaderState` as its user data. This provides a simple way 266 | /// to read from a `std.io.AnyReader` in a Lua chunk. 267 | /// 268 | /// This is the function that will be used when a `helpers.ReaderState` is passed to any function that requires 269 | /// a `ReaderFn`. 270 | pub fn read(L_opt: ?*c.lua_State, ud: ?*anyopaque, size: ?*usize) callconv(.C) [*c]const u8 { 271 | assert(L_opt != null); 272 | assert(ud != null); 273 | assert(size != null); 274 | 275 | const L: *State = @ptrCast(L_opt.?); 276 | const state: *ReaderState = @ptrCast(@alignCast(ud.?)); 277 | 278 | size.?.* = state.reader.read(state.buffer[0..]) catch |err| { 279 | L.raise("wrapped lunaro reader returned an error: {s}", .{@errorName(err)}); 280 | }; 281 | 282 | return &state.buffer; 283 | } 284 | }; 285 | 286 | pub const WriterState = struct { 287 | writer: std.io.AnyWriter, 288 | 289 | /// A `WriterFn` that accepts a `helpers.WriterState` as its user data. This provides a simple way 290 | /// to write to a `std.io.AnyWriter` in a Lua chunk. 291 | /// 292 | /// This is the function that will be used when a `helpers.WriterState` is passed to any function that requires 293 | /// a `WriterFn`. 294 | pub fn write(L_opt: ?*c.lua_State, p: ?*const anyopaque, sz: usize, ud: ?*anyopaque) callconv(.C) c_int { 295 | assert(L_opt != null); 296 | assert(ud != null); 297 | assert(p != null); 298 | 299 | const L: *State = @ptrCast(L_opt.?); 300 | const wrapper: *WriterState = @ptrCast(@alignCast(ud.?)); 301 | const ptr: [*]const u8 = @ptrCast(p.?); 302 | 303 | wrapper.writer.writeAll(ptr[0..sz]) catch |err| { 304 | L.raise("wrapped lunaro writer returned an error: {s}", .{@errorName(err)}); 305 | }; 306 | 307 | return 0; 308 | } 309 | }; 310 | }; 311 | 312 | /// Export a zig function as the entry point of a Lua module. This wraps the function and exports it as 313 | /// `luaopen_{name}`. 314 | pub fn exportAs(comptime func: anytype, comptime name: []const u8) CFn { 315 | return struct { 316 | fn luaopen(L: ?*c.lua_State) callconv(.C) c_int { 317 | const fnc = comptime helpers.wrapCFn(func) orelse unreachable; 318 | 319 | return @call(.always_inline, fnc, .{L}); 320 | } 321 | 322 | comptime { 323 | @export(luaopen, .{ .name = "luaopen_" ++ name }); 324 | } 325 | }.luaopen; 326 | } 327 | 328 | test { 329 | std.testing.refAllDecls(@This()); 330 | } 331 | -------------------------------------------------------------------------------- /src/safety.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const lunaro = @import("lunaro.zig"); 3 | 4 | const State = lunaro.State; 5 | const Index = lunaro.Index; 6 | 7 | /// A debug utility to ensure that the stack is in the expected state after a function call. 8 | /// 9 | /// Does nothing when `std.debug.runtime_safety` is false. 10 | pub const StackCheck = struct { 11 | top: if (std.debug.runtime_safety) Index else void, 12 | 13 | /// [-0, +0, -] Initializes a stack check. The top of the stack will be saved as the "base". 14 | pub fn init(L: *State) StackCheck { 15 | return .{ .top = if (std.debug.runtime_safety) L.gettop() else {} }; 16 | } 17 | 18 | /// [-0, +0, v] Checks that the stack is in the expected state. If it is not, an error is thrown with debug 19 | /// information if available from the given function (by probing for debug info in the binary). 20 | /// 21 | /// A negative value for `pushed` means that `abs(pushed)` items have been popped. 22 | pub fn check(self: StackCheck, comptime func: anytype, L: *State, pushed: c_int) c_int { 23 | if (!std.debug.runtime_safety) return; 24 | 25 | const new_top = L.gettop(); 26 | if (new_top != self.top + pushed) { 27 | debuginfo: { 28 | const address = @intFromPtr(&func); 29 | 30 | const debug_info = std.debug.getSelfDebugInfo() catch break :debuginfo; 31 | const module = debug_info.getModuleForAddress(address) catch break :debuginfo; 32 | const symbol_info: std.debug.Symbol = module.getSymbolAtAddress(debug_info.allocator, address) catch break :debuginfo; 33 | defer symbol_info.deinit(debug_info.allocator); 34 | 35 | if (symbol_info.source_location) |info| { 36 | L.raise("stack check failed in %s at %s:%d (expected %d items but %d were pushed)", .{ symbol_info.name, info.file_name, info.line, pushed, new_top - self.top }); 37 | } 38 | 39 | L.raise("stack check failed in %s (expected %d items but %d were pushed)", .{ symbol_info.name, pushed, new_top - self.top }); 40 | } 41 | 42 | L.raise("stack check failed (expected %d items but %d were pushed)", .{ pushed, new_top - self.top }); 43 | } 44 | 45 | return pushed; 46 | } 47 | }; 48 | 49 | comptime { 50 | if (@import("builtin").is_test) 51 | std.testing.refAllDecls(StackCheck); 52 | } 53 | -------------------------------------------------------------------------------- /test/test.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const lunaro = @import("lunaro"); 3 | 4 | const expect = std.testing.expect; 5 | 6 | pub fn main() !void { 7 | const L = try lunaro.State.init(); 8 | defer L.close(); 9 | 10 | L.checkversion(); 11 | L.openlibs(); 12 | 13 | L.push(testfn); 14 | 15 | try expect(L.getglobal("print") == .function); 16 | const print = try L.functionAt(-1); 17 | 18 | L.push("Hello, "); 19 | L.push("World!"); 20 | L.concat(2); 21 | 22 | L.pushvalue(-1); 23 | L.insert(-3); 24 | 25 | L.call(1, 0); 26 | 27 | const value = try L.valueAt(-1); 28 | try expect(value == .string); 29 | try expect(std.mem.eql(u8, value.string, "Hello, World!")); 30 | 31 | print.call(.{"This is a print() call!"}, .none); 32 | 33 | L.call(1, 1); 34 | 35 | const value1 = try L.valueAt(-1); 36 | try expect(value1 == .boolean and value1.boolean == true); 37 | 38 | print.push(L); 39 | L.push(does_error); 40 | try expect(L.pcall(0, 1, 0) != .ok); 41 | 42 | L.call(1, 0); 43 | } 44 | 45 | pub fn testfn(L: *lunaro.State) bool { 46 | const value = L.check([]const u8, 1, .{ .source = @src() }); 47 | 48 | expect(std.mem.eql(u8, value, "Hello, World!")) catch return false; 49 | 50 | return true; 51 | } 52 | 53 | pub fn does_error(L: *lunaro.State) bool { 54 | const value = L.check(bool, 1, .{ .source = @src() }); 55 | return value; 56 | } 57 | --------------------------------------------------------------------------------