A Sol-inspired minimalist Lua binding for Zig.

Overview

zoltan

zoltan

A Sol-inspired minimalist Lua binding for Zig.

Features

  • Supports Zig 0.9.0
  • Lua tables
    • table creation from Zig
    • get/set/create methods
    • possible key types: numerical, string
  • Scalars
    • int, float, bool
    • Lua string (equals [] const u8)
  • Functions
    • calling Zig function from Lua and vice-versa
    • Zig functions can accept
      • scalars, Lua types (table, functions, custom types)
  • Custom types
    • registered types could be instantiated from Lua
    • public functions are registered in Lua
    • supports self
  • Scalar array support (equals Lua tables without metatables + numeric keys)
  • All of the supported use-cases are intensively tested & leak-free

Tutorial

Installing

In addition to the binding facilities, zoltan contains the vanilla Lua source (v5.4.3) and the necessary code to compile it. If zoltan is installed in the third_party library in your application's root, then you have to add to your build.zig the following:

// At the beginning of the file
const addLuaLib = @import("third_party/zoltan/build.zig").addLuaLibrary;

pub fn build(b: *std.build.Builder) void {
...
  // Exe part
  exe.addPackage(.{ .name = "lua", .path = .{ .path="third_party/zoltan/src/lua.zig" }});
  addLuaLib(exe, "third_party/zoltan/");
...
  // Test part
  const lua_tests = b.addTest("third_party/zoltan/src/tests.zig");
  addLuaLib(lua_tests, "third_party/zoltan/");
  lua_tests.setBuildMode(mode);

You can found an example integration here.

Instantiating and destroying Lua engine

const Lua = @import("lua").Lua;
...
pub fn main() anyerror!void {
  ...
  var lua = try Lua.init(std.testing.allocator);
  defer lua.destroy();

  lua.openLibs();  // Open common standard libraries

Running Lua code

_ = lua.run("print('Hello World!')");

Getting/setting Lua global varibles

lua.set("int32", 42);
var int = lua.get(i32, "int32");
std.log.info("Int: {}", .{int});  // 42

lua.set("string", "I'm a string");
const str = lua.get([] const u8, "string");
std.log.info("String: {s}", .{str});  // I'm a string

Resource handling

The following functions acquire some kind of resource (heap, Lua reference):

  • getResource
  • createTable
  • createUserType

You have to release the acquired resources by calling the release method:

var tbl = try lua.createTable();
....
lua.release(tbl);         // You have to release

Lua tables

var tbl = try lua.createTable();
defer lua.release(tbl);

lua.set("tbl", tbl);

var inTbl = try lua.createTable();

// Set, integer key
inTbl.set(1, "string");
inTbl.set(2, 3.1415);
inTbl.set(3, 42);

var tst1 = inTbl.get([]const u8, 1);
var tst2 = inTbl.get(f32, 2);
var tst3 = inTbl.get(i32, 3);

try std.testing.expect(std.mem.eql(u8, test1, "string"));
try std.testing.expect(tst2 == 3.1415);
try std.testing.expect(tst3 == 42);

// Set, string key
inTbl.set("bool", true);

var tst4 = inTbl.get(bool, "bool");
try std.testing.expect(tst4 == true);

// Set table in parent
tbl.set("inner", inTbl);
// Now we can release the inTbl directly (tbl refers it)
lua.release(inTbl);

Calling Lua function from Zig

_ = lua.run("function double_me(a) return 2*a; end");

var doubleMe = lua.get(Lua.Function(fn(a: i32) i32), "double_me");
// As Zig doesn't handle variable args, one should pass the arguments as anonymous struct
var res = doubleMe.call(.{42});

std.log.info("Result: {}", .{res});   // 84

Calling Zig function from Lua

var testResult: i32 = 0;

fn test_fun(a: i32, b: i32) void {
    std.log.info("I'm a test: {}", .{a*b});
    testResult = a*b;
}
...
lua.set("test_fun", test_fun);

lua.run("test_fun(3,15)");
try std.testing.expect(testResult == 45);

Passing Lua function to Zig function

fn testLuaInnerFun(fun: Lua.Function(fn(a: i32) i32)) i32 {
    var res = fun.call(.{42}) catch unreachable;
    std.log.warn("Result: {}", .{res});
    return res;
}
...

Mechanism on Zig side

lua.run("function getInt(a) print(a); return a+1; end");
var luafun = try lua.getResource(Lua.Function(fn(a: i32) i32), "getInt");
defer lua.release(luafun);

var result = testLuaInnerFun(luafun);
std.log.info("Zig Result: {}", .{result});

Mechanism on Lua side

lua.set("zigFunction", testLuaInnerFun);

const lua_command =
    \\function getInt(a) print(a); return a+1; end
    \\print("Preppare");
    \\zigFunction(getInt);
    \\print("Oppare");
;
lua.run(lua_command);

Custom types

Registering Zig structs in Lua

const TestCustomType = struct {
    a: i32,
    b: f32,
    c: []const u8,
    d: bool,

    pub fn init(_a: i32, _b: f32, _c: []const u8, _d: bool) TestCustomType {
        return TestCustomType{ ... };
    }

    pub fn destroy(_: *TestCustomType) void {}

    pub fn getA(self: *TestCustomType) i32 { return self.a; }
    pub fn getB(self: *TestCustomType) f32 { return self.b; }
    pub fn getC(self: *TestCustomType) []const u8 { return self.c; }
    pub fn getD(self: *TestCustomType) bool { return self.d; }

    pub fn reset(self: *TestCustomType) void {
        self.a = 0;
        self.b = 0;
        self.c = "";
        self.d = false;
    }

    pub fn store(self: *TestCustomType, _a: i32, _b: f32, _c: []const u8, _d: bool) void {
        self.a = _a;
        self.b = _b;
        self.c = _c;
        self.d = _d;
    }
};
...
// ******************************************
try lua.newUserType(TestCustomType);
// ******************************************

Instantiating custom type in Zig

var obj = try lua.createUserType(TestCustomType, .{42, 42.0, "life", true});
defer lua.release(obj);

// One can access the inner struct via the ptr field
std.testing.expect(obj.ptr.getA() == 42);

// One can set as global
lua.set("zigObj", obj);

// And then use it
lua.run("zigObj:reset()");

std.testing.expect(obj.ptr.getA() == 0);

Instantiating custom type in Lua

lua.run("obj = TestCustomType.new(42, 42.0, 'life', true)");

// Get as a reference (it doesn't hold reference to the inner object,
// therefore the lifetime is managed totally by the Lua engine
// => storing is dangerous)
var ptr = try lua.get(*TestCustomType, "obj");
std.testing.expect(ptr.getA() == 42);

TODO

In order of importance:

  • The current error handling is a little bit rustic, sometimes rough :) A proper error handling strategy would be better.
  • Run Lua code from file
  • zigmod support
  • LuaJIT support
  • Lua.Table should deep-copy between table and user structs
  • Lua Coroutine support
  • Lua.Table should support JSON
  • Option for building without libc (if possible)
  • Performance benchmarks
Comments
  • Am I using getResource incorrectly?

    Am I using getResource incorrectly?

    Hi there, thanks for making Zoltan!

    I have been using Zoltan in my project but have come across an error under specific circumstances. I'm not entirely sure what the problem is, but I'll post a simple example that reproduces the issue (taken from the readme).

    When the loop limit is 501, the code runs, when it's 502, there's an illegal instruction error.

    const std = @import("std");
    const Lua = @import("lua").Lua;
    const assert = std.debug.assert;
    
    pub fn main() !void {
        var lua = try Lua.init(std.testing.allocator);
        defer lua.destroy();
    
        lua.openLibs();
    
        var tbl = try lua.createTable();
        defer lua.release(tbl);
    
        lua.set("tbl", tbl);
    
        var inTbl = try lua.createTable();
    
        // Set, integer key
        inTbl.set(1, "string");
        inTbl.set(2, 3.1415);
        inTbl.set(3, 42);
    
        var tst1 = try inTbl.get([]const u8, 1);
        var tst2 = try inTbl.get(f32, 2);
        var tst3 = try inTbl.get(i32, 3);
    
        try std.testing.expect(std.mem.eql(u8, tst1, "string"));
        try std.testing.expect(tst2 == 3.1415);
        try std.testing.expect(tst3 == 42);
    
        // Set, string key
        inTbl.set("bool", true);
    
        var tst4 = try inTbl.get(bool, "bool");
        try std.testing.expect(tst4 == true);
    
        // Set table in parent
        tbl.set("inner", inTbl);
        // Now we can release the inTbl directly (tbl refers it)
        lua.release(inTbl);
    
        // Somewhere else, later.
        {
            var retTbl = try lua.getResource(Lua.Table, "tbl");
            defer lua.release(retTbl);
    
            var i: usize = 0;
            while (i < 502) : (i += 1) {
                var retInTbl = try retTbl.getResource(Lua.Table, "inner");
                lua.release(retInTbl);
            }
        }
    
        std.debug.print("Success.\n", .{});
    }
    
    

    The error:

    Illegal instruction at address 0x20bdad
    zoltan/src/lua-5.4.3/src/lapi.c:0:0: 0x20bdad in finishrawget (/home/falconerd/work/zoltan_test/zoltan/src/lua-5.4.3/src/lapi.c)
    zoltan/src/lua-5.4.3/src/lauxlib.c:667:7: 0x21004f in luaL_ref (/home/falconerd/work/zoltan_test/zoltan/src/lua-5.4.3/src/lauxlib.c)
      if (lua_rawgeti(L, t, freelist) == LUA_TNIL) {  /* first access? */
          ^
    /home/falconerd/work/zoltan_test/zoltan/src/lua.zig:296:41: 0x253c4e in .lua.Table.init (zoltan_app)
                const _ref = lualib.luaL_ref(_L, lualib.LUA_REGISTRYINDEX);
                                            ^
    /home/falconerd/work/zoltan_test/zoltan/src/lua.zig:547:38: 0x253bdd in .lua.Lua.popResource (zoltan_app)
                            return T.init(L);
                                         ^
    /home/falconerd/work/zoltan_test/zoltan/src/lua.zig:341:39: 0x24f261 in .lua.Table.getResource (zoltan_app)
                return try Lua.popResource(T, self.L);
                                          ^
    /home/falconerd/work/zoltan_test/main.zig:49:50: 0x24e4c6 in main (zoltan_app)
                var retInTbl = try retTbl.getResource(Lua.Table, "inner");
                                                     ^
    /home/falconerd/bin/lib/std/start.zig:561:37: 0x259377 in std.start.callMain (zoltan_app)
                const result = root.main() catch |err| {
                                        ^
    /home/falconerd/bin/lib/std/start.zig:495:12: 0x24f7e7 in std.start.callMainWithArgs (zoltan_app)
        return @call(.{ .modifier = .always_inline }, callMain, .{});
               ^
    /home/falconerd/bin/lib/std/start.zig:460:12: 0x24f592 in std.start.main (zoltan_app)
        return @call(.{ .modifier = .always_inline }, callMainWithArgs, .{ @intCast(usize, c_argc), c_argv, envp });
               ^
    The following command terminated unexpectedly:
    cd /home/falconerd/work/zoltan_test && /home/falconerd/work/zoltan_test/zig-out/bin/zoltan_app
    error: the following build command failed with exit code 1:
    /home/falconerd/work/zoltan_test/zig-cache/o/86d0b49472420f3a37b88ab0c72eee03/build /home/falconerd/bin/zig /home/falconerd/work/zoltan_test /home/falconerd/work/zoltan_test/zig-cache /home/falconerd/.cache/zig run
    

    While this example is contrived, I ran into this error in my project while looping through a sub-table.

    Any help would be greatly appreciated!

    opened by Falconerd 0
  • Added support for luajit

    Added support for luajit

    Hello,

    Here is an attempt at compiling zoltan with luajit.

    I did not really know how to do conditional compilation, so I used that approach of having different files loaded conditionally through addPackage.

    I had to work around a few missing features from luajit and the most questionable one is avoiding to call lua_setuservalue. I'm no Lua expert so bear with me here. I do not know what is the purpose of this function and looking at other code, especially Lua5.1 binding code, it did not seem necessary.

    Anyway, let me know if there is anything you'd like to change.

    Also, I only tested on osx and Linux. I do not have a windows to try it over there.

    opened by bcareil 0
  • Wrapper type fixups regarding string terminators

    Wrapper type fixups regarding string terminators

    (based on #2 , but if that is rejected it's trivial to rebase on main)

    The Lua API explicitly states it expects 0-terminated strings ("null-terminated") at several points. These changes express this using Zig's sentinel termination syntax ([:0] for 0-terminated slice, [*:0] for pointer-to-many with 0-termination).

    I also removed all @ptrCast to [*c] types, as they are non-helpful. All pointers already coerce to these types where sensible, so they can only hide errors. These types only exist for translate-c and @cImport to be less explicit about pointer sizes. When writing code manually, it's preferred to be explicit about what size pointers are (i.e. *T vs [*]T vs optional, and terminator info).

    I had to adjust one test a bit, because it included pushing strings without information about their 0-terminator. I've adapted the case with [*]const u8 to instead use the type [*:0]const u8 (which string literals are already compatible with). On [*c] const u8 I've used std.mem.span, just to showcase it, which looks for the 0-terminator we know is there. The [*c] const u8 type in general doesn't guarantee that it's a safe operation however.

    opened by rohlem 0
  • run `zig fmt`

    run `zig fmt`

    I ran the zig fmt formatting command (using newest zig version 0.10.0-dev390+0866fa9d1) in a local project for consistency, and thought you might want to have the repo be in canonical form too. If not, feel free to close this PR.

    It's really just whitespace changes, though I inserted a trailing comma at one point to make it preserve a line break.

    opened by rohlem 0
  • Support LuaJIT

    Support LuaJIT

    Amazing project. I love the idea of getting more things into Zig.

    I saw you are hosting Lua 5.4 inside the project. Is there anything that would prevent running LuaJIT as an alternative? LuaJIT is a significantly faster runtime (admittedly, if you care about speed you don't use an interpreter language) which would make this even more appealing to me.

    Downsides of LuaJIT: no real integers and no built-in UTF8 support, so that could make the interface more complicated.

    opened by dbready 2
Owner
null
Linguagem de programação Simple SOL

Simple SOL Simple SOL, ou Simple Stack-Oriented Language, é uma linguagem de programação procedural baseada em stack. Tem como inspirações Forth, Port

blum_dev 1 Jan 7, 2022
C++ binding to Lua

bLua C++与Lua的胶水层,b代表着bridge 特性 依赖C++17 只有一个头文件 接口简单轻量 userdata的方式管理c++指针生命周期 用法 lua调用c++ 首先注册类及需要的成员函数 // 注册全局函数 bLua::reg_global_func(L, "newA", newA

null 3 Sep 30, 2022
libmagic binding for lua.

lua-libmagic libmagic binding for lua. see man libmagic for more details. Dependencies libmagic: https://github.com/file/file Installation luarocks in

Masatoshi Fukunaga 2 Jan 14, 2022
A binding between C++11 and Lua language

lua-intf lua-intf is a binding between C++11 and Lua language, it provides three different set of API in one package: LuaBinding, Export C++ class or

Steve K. Chiu 405 Nov 16, 2022
Advanced version of lutok C++/Lua binding

Lutok2 C++/Lua binding helper for Lua 5.1 and LuaJIT 2.x+. Dependencies To use Lutok2 successfully you'll need: A standards-compliant C++11 complier L

Mário Kašuba 9 Jul 19, 2022
C++ binding to Lua

Kaguya C++ binding to Lua Licensed under Boost Software License Requirements Lua 5.1 to 5.3 (recommended: 5.3) C++03 compiler with boost library or C+

null 317 Dec 2, 2022
lua binding for Software implementation in C of the FIPS 198 Keyed-Hash Message Authentication Code HMAC

lua-hmac Compute the SHA-224, SHA-256, SHA-384, and SHA-512 message digests and the Hash-based Message Authentication Code (HMAC). this module is Lua

Masatoshi Fukunaga 2 Jul 1, 2022
A dependency free, embeddable debugger for Lua in a single file (.lua or .c)

debugger.lua A simple, embedabble debugger for Lua 5.x, and LuaJIT 2.x. debugger.lua is a simple, single file, pure Lua debugger that is easy to integ

Scott Lembcke 593 Nov 29, 2022
The Lua development repository, as seen by the Lua team. Mirrored irregularly

The Lua development repository, as seen by the Lua team. Mirrored irregularly

Lua 6.3k Dec 4, 2022
Simple Pacman clone written in Zig.

pacman.zig Like https://github.com/floooh/pacman.c, but in Zig Zig bindings for the sokol headers are here: https://github.com/floooh/sokol-zig Build

Andre Weissflog 128 Dec 1, 2022
Zig bindings for the excellent CRoaring library

Zig-Roaring This library implements Zig bindings for the CRoaring library. Naming Any C function that begins with roaring_bitmap_ is a method of the B

Justin Whear 4 Feb 23, 2022
A KC85 emulator written in Zig

A simple KC85/2, /3 and /4 emulator for Windows, macOS and Linux, written in Zig. Uses the sokol headers for platform abstraction. Read more about the

Andre Weissflog 32 Nov 4, 2022
PAC-MAN emulator written in Zig

zig-pacman A PAC-MAN emulator written in Zig to showcase the zig80 library. Requires Zig, zigmod, and SDL2. Usage usage: pacman [-htda] [-c <ratio>] [

Matthew 3 Jan 24, 2022
C, C++, D, Fortran, Rust and Zig for your z80

z80 Babel: C, C++, D, Fortran, Rust and Zig for your z80 This is a proof of concept for a multi-language pipeline for the Z80 CPU, and its correspondi

Manuel Martinez Torres 14 Nov 2, 2022
isabel - Simple, minimalist note manager.

isabel isabel - Simple, minimalist note manager. Usage Type name and body of note and press Ctrl+s for save note. Press Tab to open notes list. Press

null 1 Oct 2, 2021
Second life for famous JPEGView - fast and tiny viewer/editor for JPEG, BMP, PNG, WEBP, TGA, GIF and TIFF images with a minimalist GUI and base image processing.

JPEGView-Image-Viewer-and-Editor Updated Dec 07 2021. Version 1.1.1.0 has been released. Download link1, link2 added. Second life for famous JPEGView

Ann Hatt 36 Nov 23, 2022
Gfx - A minimalist and easy to use graphics API.

gfx gfx is a minimalist and easy to use graphics API built on top of Direct3D12/HLSL intended for rapid prototyping. It supports: Full shader reflecti

Guillaume Boissé 324 Nov 27, 2022
Legacy stepper motor analyzer - A DYI minimalist hardware stepper motor analyzer with graphical touch screen.

Simple Stepper Motor Analyzer NOTE: This is the legacy STM32 based design which was replaced by the single board, Raspberry Pi Pico design at https://

Zapta 159 Oct 23, 2022
Minimalist protocol buffer decoder and encoder in C++

protozero Minimalistic protocol buffer decoder and encoder in C++. Designed for high performance. Suitable for writing zero copy parsers and encoders

Mapbox 236 Nov 25, 2022