Home

Awesome

zoltan

zoltan

A Sol-inspired minimalist Lua binding for Zig.

Features

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):

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: