Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
054ed31
feat: high-level `lua` module API
CompeyDev Nov 17, 2025
ca1fe20
chore: remove references to old `internal` module
CompeyDev Nov 17, 2025
aa33c3d
refactor(lua): rewrite allocator in go & more
CompeyDev Nov 18, 2025
7e911f1
fix(ffi/lua): `LuauLoad` segfault due to use-after-free
CompeyDev Nov 20, 2025
2b15b5b
feat(ffi): add `luacode` compiler bindings
CompeyDev Nov 20, 2025
65e206e
chore(ffi): include tests for luacode compiler bindings
CompeyDev Nov 20, 2025
1f30e08
fix(ffi/lua): zero-indexed values for `lua_Status` and `lua_CoStatus`
CompeyDev Nov 22, 2025
a1144d3
feat(ffi): minimal `Bytecode.h` bindings exporting version consts
CompeyDev Nov 22, 2025
9959310
feat(lua): add high-level execution API
CompeyDev Nov 22, 2025
cdced26
fix(lua/compiler): initialize missed empty arrays
CompeyDev Nov 22, 2025
b539fd3
refactor(ffi/lua): use cgo exports for pseudo indices
CompeyDev Nov 25, 2025
00de5eb
feat(lua): use registry to safely store index handles for types
CompeyDev Nov 25, 2025
0a53330
feat(lua/value): add annotation and exported case normalization
CompeyDev Nov 26, 2025
1a5e3a1
chore(lua): include Lua to Go reflection tests
CompeyDev Nov 26, 2025
9996bd2
feat(ffi/lua): accept ptr for `PushCClosureK` for `debugname`
CompeyDev Dec 7, 2025
942da1a
fix(ffi/lua): `luau_load` returns a boolean, not just an int
CompeyDev Dec 9, 2025
08013ba
fix(ffi/lauxlib): use-after-free due to Go-side free calls
CompeyDev Dec 9, 2025
39ffca1
feat(lua): implement `LuaChunk` for callable bodies
CompeyDev Dec 9, 2025
cadff3b
fix(lua): address noted func registry memory leak
CompeyDev Dec 9, 2025
480ee86
feat(lua): `Lua.CreateFunction` functions accept `LuaValue` args
CompeyDev Dec 9, 2025
c1a9aec
refactor(lua): add type assertions for `LuaValue` impls
CompeyDev Dec 10, 2025
434855e
feat(lua): implement `LuaNumber` type
CompeyDev Dec 10, 2025
6bbd51b
feat(ffi, lua): implement `GetRef` and instead of `RawGetI`
CompeyDev Dec 10, 2025
25cf04d
feat(lua): implement `LuaNumber` and refactor `LuaValue` interface
CompeyDev Dec 14, 2025
19f3c4f
feat(lua): support catching panics in Go functions as Lua errors
CompeyDev Dec 23, 2025
c6a29ad
fix(ffi): refer to full include path for `lua.h`
CompeyDev Dec 23, 2025
c40cc22
chore(build): replace `go` build wrapper with `go:generate` attrs
CompeyDev Dec 23, 2025
b81a5e3
fix(lua): `cgo` ignores `CFLAGS` causing include errors
CompeyDev Dec 23, 2025
3fe5bcd
fix(lua/registry): free leaked error message on C side
CompeyDev Jan 23, 2026
c65633a
refactor(lua/registry): make `GoFunction` a type alias
CompeyDev Jan 23, 2026
cb545a5
feat(lua): globals API and userdata support
CompeyDev Jan 23, 2026
40b408e
refactor(lua/compiler): use copy-on-modify pattern for `Compiler`
CompeyDev Jan 24, 2026
d0a071e
refactor(lua/number): use value receivers for `LuaNumber`
CompeyDev Jan 24, 2026
492b29c
fix(lua): return pointer in `Lua.CreateUserData`
CompeyDev Jan 24, 2026
77daa13
feat(ffi): add `luacodegen` bindings
CompeyDev Jan 25, 2026
f56d8da
feat(lua): support codegen configuration APIs
CompeyDev Jan 25, 2026
29ee5a9
fix(lua/value): unpredictable override behavior with `As`
CompeyDev Jan 26, 2026
cbb9fd3
fix(lua/stdlib): luau does not have a `PACKAGE` lib
CompeyDev Jan 26, 2026
e10e827
feat: refactor lib opener bindings and vector lib support
CompeyDev Jan 26, 2026
c384631
feat(lua/state): add sandboxing option
CompeyDev Jan 26, 2026
bb1d6a7
refactor(lua/stdlib): use links to luau docs instead of lua
CompeyDev Jan 26, 2026
379dd08
fix(ffi): return the actual vector components in `ToVector`
CompeyDev Jan 27, 2026
f75e364
feat(lua): implement `LuaVector` type
CompeyDev Jan 27, 2026
2e4a4e3
fix(lua/value): check that none of the coordinates are nil
CompeyDev Jan 27, 2026
a31975b
fix(lua/value): check not required, we already know the type
CompeyDev Jan 27, 2026
c45a8a2
fix(lua): memory leak due to `LuaUserData` never being freed
CompeyDev Jan 27, 2026
2c6958a
feat(lua): implement `LuaBuffer` type
CompeyDev Jan 27, 2026
f4078c7
chore(ffi): remove unused vector stubs
CompeyDev Jan 27, 2026
ce826a4
fix(ffi/lua): move `PushVector` into `lua.go`
CompeyDev Jan 27, 2026
97fea66
fix(ffi/lua): remove "Lua" prefix from coroutine methods
CompeyDev Feb 4, 2026
0716720
feat(lua/value): implement reflection and conversion for `LuaBuffer`
CompeyDev Feb 4, 2026
261d29e
feat(lua): implement `LuaThread` type
CompeyDev Feb 4, 2026
b604a16
feat(lua/value): convert `LuaNumber` into any numeric type
CompeyDev Feb 4, 2026
ce4bdd1
chore(lua/value_test): use numeric types instead of string
CompeyDev Feb 4, 2026
02de14b
chore(lua/chunk): remove resolved TODO comment
CompeyDev Feb 4, 2026
e026826
fix(lua/value): add missed `LuaThread` value conversion case
CompeyDev Feb 4, 2026
c0294eb
refactor(lua): make `LuaThread.state` private, rearrange
CompeyDev Feb 5, 2026
447dcd7
feat(lua/buffer): implement `IsEmpty` and `Size` methods
CompeyDev Feb 5, 2026
bbd713d
feat(lua): support providing debug name for C functions
CompeyDev Feb 5, 2026
5bb16f4
fix(ffi/lua): `lua_setfenv` returns a boolean-like value
CompeyDev Feb 21, 2026
6ae8a7f
feat(lua/chunk): more convenience methods for `LuaChunk`
CompeyDev Feb 21, 2026
21df350
fix(lua/chunk): export `ChunkMode` variants as correct type
CompeyDev Feb 25, 2026
5260d81
fix(ffi/lua): `lua_getmetatable` returns bool-like value
CompeyDev Mar 12, 2026
164e751
feat(lua/table): implement more methods for `LuaTable`
CompeyDev Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,8 @@
# Build script compiled data
.lei/

# Cgo generated files
_obj/

# Dependency directories (remove the comment below to include it)
# vendor/
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
[submodule "internal/luau"]
path = internal/luau
url = https://github.com/luau-lang/luau
[submodule "ffi/luau"]
path = ffi/luau
url = https://github.com/luau-lang/luau.git
163 changes: 30 additions & 133 deletions build/build.go
Original file line number Diff line number Diff line change
@@ -1,152 +1,49 @@
package main

import (
"fmt"
"log"
"os"
"path"
"strings"

"github.com/gookit/color"
"golang.org/x/term"
)

const LUAU_VERSION = "0.634"
const ARTIFACT_NAME = "libLuau.VM.a"

func bail(err error) {
if err != nil {
panic(err)
}
}

func cloneSrc() string {
color.Blue.Println("> Cloning luau-lang/luau")

dir, tempDirErr := os.MkdirTemp("", "lei-build")
bail(tempDirErr)

// Clone down the Luau repo and checkout the required tag
Exec("git", "", "clone", "https://github.com/luau-lang/luau.git", dir)
Exec("git", dir, "checkout", LUAU_VERSION)

color.Green.Printf("> Cloned repo to%s\n\n", dir)
return dir
}

func buildVm(srcPath string, artifactPath string, includesDir string, cmakeFlags ...string) {
color.Blue.Println("> Compile libLuau.VM.a")

// Build the Luau VM using CMake
buildDir := path.Join(srcPath, "cmake")
buildDirErr := os.Mkdir(buildDir, os.ModePerm)
if !os.IsExist(buildDirErr) {
bail(buildDirErr)
}

defaultCmakeFlags := []string{"..", "-DCMAKE_BUILD_TYPE=RelWithDebInfo", "-DLUAU_EXTERN_C=ON", "-DCMAKE_POLICY_VERSION_MINIMUM=3.5"}
Exec("cmake", buildDir, append(defaultCmakeFlags, cmakeFlags...)...)
Exec("cmake", buildDir, "--build", ".", "--target Luau.VM", "--config", "RelWithDebInfo")

color.Green.Println("> Successfully compiled!\n")

// Copy the artifact to the artifact directory
artifactFile, artifactErr := os.ReadFile(path.Join(buildDir, ARTIFACT_NAME))
bail(artifactErr)
bail(os.WriteFile(artifactPath, artifactFile, os.ModePerm))

// Copy the header files into the includes directory
headerDir := path.Join(srcPath, "VM", "include")
headerFiles, headerErr := os.ReadDir(headerDir)
bail(headerErr)
for _, file := range headerFiles {
src := path.Join(headerDir, file.Name())
dest := path.Join(includesDir, file.Name())

headerContents, headerReadErr := os.ReadFile(src)
bail(headerReadErr)

os.WriteFile(dest, headerContents, os.ModePerm)
}
}

func main() {
workDir, workDirErr := os.Getwd()
bail(workDirErr)

artifactDir := path.Join(workDir, ".lei")
artifactPath := path.Join(artifactDir, ARTIFACT_NAME)
lockfilePath := path.Join(artifactDir, ".lock")
includesDir := path.Join(artifactDir, "includes")

bail(os.MkdirAll(includesDir, os.ModePerm)) // includesDir is the deepest dir, creates all

gitignore, gitignoreErr := os.ReadFile(".gitignore")
if gitignoreErr == nil && !strings.Contains(string(gitignore), ".lei") {
color.Yellow.Println("> WARN: The gitignore in the CWD does not include `.lei`, consider adding it")
usage := func() { log.Fatal("Usage: buildProject <projects...>") }
if len(os.Args) < 2 {
usage()
}

// TODO: Args for clean build
args := os.Args[1:]

goArgs := []string{}
cmakeFlags := []string{}
features := []string{}
switch os.Args[1] {
case "buildProject":
for _, project := range os.Args[2:] {
if !strings.HasPrefix(project, "Luau.") {
log.Fatalf("Invalid project name: %s", project)
}

// TODO: maybe use env vars for this config instead
for _, arg := range args {
if arg == "--enable-vector4" {
features = append(features, "LUAU_VECTOR4")
// FIXME: This flag apparently isn't recognized by cmake for some reason
cmakeFlags = append(cmakeFlags, "-DLUAU_VECTOR_SIZE=4")

} else {
goArgs = append(goArgs, arg)
compileLuauProject(project)
}
}

lockfileContents, err := os.ReadFile(lockfilePath)
if !os.IsNotExist(err) {
bail(err)
// Display usage menu
case "-h", "--help":
fallthrough
default:
usage()
}
}

serFeatures := fmt.Sprintf("%v", features)
toCleanBuild := (string(lockfileContents) != serFeatures) || os.Getenv("LEI_CLEAN_BUILD") == "true"
if _, err := os.Stat(artifactPath); err == nil && !toCleanBuild {
fmt.Printf("[build] Using existing artifact at %s\n", artifactPath)
} else {
srcPath, notUnset := os.LookupEnv("LEI_LUAU_SRC")
if !notUnset {
srcPath = cloneSrc()
defer os.RemoveAll(srcPath)
}

buildVm(srcPath, artifactPath, includesDir, cmakeFlags...)
bail(os.WriteFile(lockfilePath, []byte(serFeatures), os.ModePerm))
}
func compileLuauProject(project string) {
if err := os.Mkdir("_obj", os.ModePerm); err == nil || !os.IsExist(err) {
// Directory already exists, i.e., config files generated
Exec(
"cmake",
"-S", "luau",
"-B", "_obj",
"-G", "Ninja",

buildTags := []string{}
if len(features) > 0 {
buildTags = append(buildTags, []string{"-tags", strings.Join(features, ",")}...)
// Flags
"-DCMAKE_BUILD_TYPE=RelWithDebInfo",
"-DLUAU_EXTERN_C=ON",
)
}

w, _, termErr := term.GetSize(int(os.Stdout.Fd()))
bail(termErr)
fmt.Println(strings.Repeat("=", w))

subcommand := goArgs[0]
goArgs = goArgs[1:]
combinedArgs := append(buildTags, goArgs...)
cmd, _, _, _ := Command("go").
WithArgs(append([]string{subcommand}, combinedArgs...)...).
WithVar(
"CGO_LDFLAGS",
fmt.Sprintf("-L %s -lLuau.VM -lm -lstdc++", artifactDir),
).
WithVar("CGO_CFLAGS", fmt.Sprintf("-I%s", includesDir)).
WithVar("CGO_ENABLED", "1").
PipeAll(Forward).
ToCommand()

bail(cmd.Start())
bail(cmd.Wait())
Exec("cmake", "--build", "_obj", "-t", project, "--config", "RelWithDebInfo")
}
28 changes: 18 additions & 10 deletions build/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package main
import (
"bytes"
"io"
"log"
"os"
"os/exec"
"strings"
)

type CommandPipeMode int
Expand Down Expand Up @@ -127,17 +129,23 @@ func pipeModeToWriter(mode CommandPipeMode, def io.Writer) io.Writer {
}
}

func Exec(name string, dir string, args ...string) {
cmd, _, _, _ := Command(name).WithArgs(args...).Dir(dir).PipeAll(Forward).ToCommand()
startErr := cmd.Start()
if startErr != nil {
panic(startErr)
func Exec(exe string, args ...string) {
cmd, _, _, _ := Command(exe).
WithArgs(args...).
WithVar("CLICOLOR_FORCE", "1").
PipeAll(Forward).
ToCommand()

if err := cmd.Start(); err != nil {
log.Fatalf("Failed to start command %s: %v", exe, err)
}

cmdErr := cmd.Wait()
if cmdErr != nil {
panic(cmdErr)
// err := cmdErr.(*exec.ExitError)
// os.Exit(err.ExitCode())
if err := cmd.Wait(); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
commandStr := strings.Join(append([]string{exe}, args...), " ")
log.Fatalf("'%s' exited with %d", commandStr, exitErr.ExitCode())
}

log.Fatalf("%s command failed: %v", exe, err)
}
}
3 changes: 3 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Root package. Use `ffi` or `lua` modules for low-level bindings
// or high-level APIs respectively.
package main
23 changes: 23 additions & 0 deletions ffi/bytecode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ffi

/*
#cgo CFLAGS: -Iluau/Common/include
#include "Luau/Bytecode.h"

enum LuauBytecodeTag LuauBytecodeTag;
*/
import "C"

//
// Version Constants
//

const (
LBC_VERSION_MIN = C.LBC_VERSION_MIN
LBC_VERSION_MAX = C.LBC_VERSION_MAX
LBC_VERSION_TARGET = C.LBC_VERSION_TARGET

LBC_TYPE_VERSION_MIN = C.LBC_TYPE_VERSION_MIN
LBC_TYPE_VERSION_MAX = C.LBC_TYPE_VERSION_MAX
LBC_TYPE_VERSION_TARGET = C.LBC_TYPE_VERSION_TARGET
)
3 changes: 1 addition & 2 deletions ffi/clua.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include <stdio.h>
#include <lua.h>
#include <_cgo_export.h>
#include "luau/VM/include/lua.h"

// void* clua_alloc(void* ud, void *ptr, size_t osize, size_t nsize)
// {
Expand Down
2 changes: 1 addition & 1 deletion ffi/clua.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include <stdlib.h>
#include <lua.h>
#include "luau/VM/include/lua.h"

lua_State* clua_newstate(void* f, void* ud);
l_noret cluaL_errorL(lua_State* L, char* msg);
Expand Down
56 changes: 26 additions & 30 deletions ffi/lauxlib.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package ffi

//go:generate go run ../build buildProject Luau.VM

/*
#cgo CFLAGS: -Iluau/VM/include -I/usr/lib/gcc/x86_64-pc-linux-gnu/14.1.1/include
#cgo LDFLAGS: -Lluau/cmake -lLuau.VM -lm -lstdc++
#cgo CFLAGS: -Iluau/VM/include
#cgo LDFLAGS: -L_obj -lLuau.VM -lm -lstdc++
#include <lua.h>
#include <lualib.h>
#include <stdlib.h>
Expand Down Expand Up @@ -66,8 +68,6 @@ func LArgError(L *LuaState, narg int32, extramsg string) {

func LCheckLString(L *LuaState, narg int32, l *uint64) string {
p := C.luaL_checklstring(L, C.int(narg), (*C.size_t)(l))
defer C.free(unsafe.Pointer(p))

return C.GoString(p)
}

Expand All @@ -76,8 +76,6 @@ func LOptLString(L *LuaState, narg int32, def string, l *uint64) string {
defer C.free(unsafe.Pointer(cdef))

p := C.luaL_optlstring(L, C.int(narg), cdef, (*C.ulong)(l))
defer C.free(unsafe.Pointer(p))

return C.GoString(p)
}

Expand Down Expand Up @@ -238,31 +236,29 @@ func LOptString(L *LuaState, n int32, d string) string {
}

const (
LUA_COLIBNAME = "coroutine"
LUA_TABLIBNAME = "table"
LUA_OSLIBNAME = "os"
LUA_STRLIBNAME = "string"
LUA_BITLIBNAME = "bit32"
LUA_BUFFERLIBNAME = "buffer"
LUA_UTF8LIBNAME = "utf8"
LUA_MATHLIBNAME = "math"
LUA_DBLIBNAME = "debug"
LUA_COLIBNAME = C.LUA_COLIBNAME
LUA_TABLIBNAME = C.LUA_TABLIBNAME
LUA_OSLIBNAME = C.LUA_OSLIBNAME
LUA_STRLIBNAME = C.LUA_STRLIBNAME
LUA_BITLIBNAME = C.LUA_BITLIBNAME
LUA_BUFFERLIBNAME = C.LUA_BUFFERLIBNAME
LUA_UTF8LIBNAME = C.LUA_UTF8LIBNAME
LUA_MATHLIBNAME = C.LUA_MATHLIBNAME
LUA_DBLIBNAME = C.LUA_DBLIBNAME
LUA_VECLIBNAME = C.LUA_VECLIBNAME
)

// DIVERGENCE: We cannot export wrapper functions around C functions if we want to
// pass them to API functions, we preserve the real C pointer by having 'opener'
// functions

func CoroutineOpener() C.lua_CFunction { return C.lua_CFunction(C.luaopen_base) }
func BaseOpener() C.lua_CFunction { return C.lua_CFunction(C.luaopen_base) }
func TableOpener() C.lua_CFunction { return C.lua_CFunction(C.luaopen_table) }
func OsOpener() C.lua_CFunction { return C.lua_CFunction(C.luaopen_os) }
func StringOpener() C.lua_CFunction { return C.lua_CFunction(C.luaopen_string) }
func Bit32Opener() C.lua_CFunction { return C.lua_CFunction(C.luaopen_bit32) }
func BufferOpener() C.lua_CFunction { return C.lua_CFunction(C.luaopen_buffer) }
func Utf8Opener() C.lua_CFunction { return C.lua_CFunction(C.luaopen_utf8) }
func MathOpener() C.lua_CFunction { return C.lua_CFunction(C.luaopen_math) }
func DebugOpener() C.lua_CFunction { return C.lua_CFunction(C.luaopen_debug) }
func LibsOpener() C.lua_CFunction { return C.lua_CFunction(C.luaL_openlibs) }
func OpenBase(L *LuaState) { C.luaopen_base(L) }
func OpenCoroutine(L *LuaState) { C.luaopen_coroutine(L) }
func OpenTable(L *LuaState) { C.luaopen_table(L) }
func OpenOs(L *LuaState) { C.luaopen_os(L) }
func OpenString(L *LuaState) { C.luaopen_string(L) }
func OpenBit32(L *LuaState) { C.luaopen_bit32(L) }
func OpenBuffer(L *LuaState) { C.luaopen_buffer(L) }
func OpenUtf8(L *LuaState) { C.luaopen_utf8(L) }
func OpenMath(L *LuaState) { C.luaopen_math(L) }
func OpenDebug(L *LuaState) { C.luaopen_debug(L) }
func OpenVector(L *LuaState) { C.luaopen_vector(L) }
func LOpenLibs(L *LuaState) { C.luaL_openlibs(L) }

// TODO: More utility functions, buffer bindings
Loading