From e17ec038cdd2be753ae6f907a76e3a27495501fa Mon Sep 17 00:00:00 2001 From: Erica Marigold Date: Wed, 17 Jul 2024 17:09:44 +0530 Subject: [PATCH] init: initial work on luau bindings --- .editorconfig | 22 +++++ .gitattributes | 1 + .gitignore | 21 +++++ .gitmodules | 3 + .vscode/settings.json | 13 +++ examples/.gitkeep | 9 ++ go.mod | 3 + internal/clua.c | 18 ++++ internal/clua.h | 6 ++ internal/lauxlib.go | 187 ++++++++++++++++++++++++++++++++++++++++++ internal/lua.go | 186 +++++++++++++++++++++++++++++++++++++++++ internal/luau | 1 + main.go | 3 + test/.gitkeep | 9 ++ 14 files changed, 482 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .vscode/settings.json create mode 100644 examples/.gitkeep create mode 100644 go.mod create mode 100644 internal/clua.c create mode 100644 internal/clua.h create mode 100644 internal/lauxlib.go create mode 100644 internal/lua.go create mode 160000 internal/luau create mode 100644 main.go create mode 100644 test/.gitkeep diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e90e11a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[{*.go,Makefile,.gitmodules,go.mod,go.sum}] +indent_style = tab + +[*.md] +indent_style = tab +trim_trailing_whitespace = false + +[*.{yml,yaml,json}] +indent_style = space +indent_size = 2 + +[*.{js,jsx,ts,tsx,css,less,sass,scss,vue,py}] +indent_style = space +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fa1385d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f580367 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Mac OS X files +.DS_Store + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..dba9b4a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "internal/luau"] + path = internal/luau + url = https://github.com/luau-lang/luau diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bd727de --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "files.associations": { + "lobject.h": "c", + "lualib.h": "c", + "lua.h": "c", + "luaconf.h": "c", + "cstdint": "c", + "stdlib.h": "c" + }, + "go.toolsEnvVars": { + "CGO_LDFLAGS_ALLOW":".*" + } +} \ No newline at end of file diff --git a/examples/.gitkeep b/examples/.gitkeep new file mode 100644 index 0000000..ab44e5c --- /dev/null +++ b/examples/.gitkeep @@ -0,0 +1,9 @@ +# `/examples` + +Examples for your applications and/or public libraries. + +Examples: + +* https://github.com/nats-io/nats.go/tree/master/examples +* https://github.com/docker-slim/docker-slim/tree/master/examples +* https://github.com/hashicorp/packer/tree/master/examples diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cd68e81 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/CompeyDev/gluau + +go 1.19 diff --git a/internal/clua.c b/internal/clua.c new file mode 100644 index 0000000..0c39a91 --- /dev/null +++ b/internal/clua.c @@ -0,0 +1,18 @@ +#include +#include +#include <_cgo_export.h> + +void* clua_alloc(void* ud, void *ptr, size_t osize, size_t nsize) +{ + return (void*) go_allocf((GoUintptr) ud,(GoUintptr) ptr, osize, nsize); +} + +lua_State* clua_newstate(void* goallocf) +{ + return lua_newstate(&clua_alloc, goallocf); +} + +l_noret cluaL_errorL(lua_State* L, char* msg) +{ + return luaL_error(L, msg); +} diff --git a/internal/clua.h b/internal/clua.h new file mode 100644 index 0000000..5920059 --- /dev/null +++ b/internal/clua.h @@ -0,0 +1,6 @@ +#include +#include + +void* clua_alloc(void* ud, void *ptr, size_t osize, size_t nsize); +lua_State* clua_newstate(void* goallocf); +l_noret cluaL_errorL(lua_State* L, char* msg); \ No newline at end of file diff --git a/internal/lauxlib.go b/internal/lauxlib.go new file mode 100644 index 0000000..57a8b7f --- /dev/null +++ b/internal/lauxlib.go @@ -0,0 +1,187 @@ +package internal + +/* +#cgo CFLAGS: -Iluau/VM/include -I/usr/lib/gcc/x86_64-pc-linux-gnu/14.1.1/include -I${SRCDIR} +#include +#include +#include +#include + +// From https://golang-nuts.narkive.com/UsNENgyt/cgo-how-to-pass-string-to-char-array +static char** makeCharArray(int size) { + return calloc(sizeof(char*), size); +} + +static void setArrayString(char** a, char* s, int n) { + a[n] = s; +} + +static void freeCharArray(char** a, int size) { + int i; + for (i = 0; i < size; i++) + free(a[i]); + free(a); +} +*/ +import "C" +import "unsafe" + +type luaL_Reg C.luaL_Reg + +func LRegister(L *C.lua_State, libname string, l *luaL_Reg) { + clibname := C.CString(libname) + defer C.free(unsafe.Pointer(clibname)) + + C.luaL_register(L, clibname, (*C.luaL_Reg)(l)) +} + +func LGetMetaField(L *C.lua_State, obj int32, e string) int32 { + ce := C.CString(e) + defer C.free(unsafe.Pointer(ce)) + + return int32(C.luaL_getmetafield(L, C.int(obj), ce)) +} + +func LCallMeta(L *C.lua_State, obj int32, e string) int32 { + ce := C.CString(e) + defer C.free(unsafe.Pointer(ce)) + + return int32(C.luaL_callmeta(L, C.int(obj), ce)) +} + +func LTypeError(L *C.lua_State, narg int32, tname string) { + ctname := C.CString(tname) + defer C.free(unsafe.Pointer(ctname)) + + C.luaL_typeerrorL(L, C.int(narg), ctname) +} + +func LArgError(L *C.lua_State, narg int32, extramsg string) { + cextramsg := C.CString(extramsg) + defer C.free(unsafe.Pointer(cextramsg)) + + C.luaL_argerrorL(L, C.int(narg), cextramsg) +} + +func LCheckLString(L *C.lua_State, 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) +} + +func LOptLString(L *C.lua_State, narg int32, def string, l *uint64) string { + cdef := C.CString(def) + 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) +} + +func LCheckNumber(L *C.lua_State, narg int32) lua_Number { + return lua_Number(C.luaL_checknumber(L, C.int(narg))) +} + +func LOptNumber(L *C.lua_State, narg int32, def lua_Number) lua_Number { + return lua_Number(C.luaL_optnumber(L, C.int(narg), C.lua_Number(def))) +} + +func LCheckBoolean(L *C.lua_State, narg int32) bool { + return C.luaL_checkboolean(L, C.int(narg)) != 0 +} + +func LOptBoolean(L *C.lua_State, narg int32, def bool) bool { + cdef := C.int(0) + if def { + cdef = C.int(1) + } + + return C.luaL_optboolean(L, C.int(narg), cdef) != 0 +} + +func LCheckInteger(L *C.lua_State, narg int32) lua_Integer { + return lua_Integer(C.luaL_checkinteger(L, C.int(narg))) +} + +func LOptInteger(L *C.lua_State, narg int32, def lua_Integer) lua_Integer { + return lua_Integer(C.luaL_optinteger(L, C.int(narg), C.lua_Integer(def))) +} + +func LCheckUnsigned(L *C.lua_State, narg int32) lua_Unsigned { + return lua_Unsigned(C.luaL_checkunsigned(L, C.int(narg))) +} + +func LOptUnsigned(L *C.lua_State, narg int32, def lua_Unsigned) lua_Unsigned { + return lua_Unsigned(C.luaL_optunsigned(L, C.int(narg), C.lua_Unsigned(def))) +} + +func LCheckVector(L *C.lua_State, narg int32) *float32 { + return (*float32)(C.luaL_checkvector(L, C.int(narg))) +} + +func LOptVector(L *C.lua_State, narg int32, def *float32) *float32 { + return (*float32)(C.luaL_optvector(L, C.int(narg), (*C.float)(def))) +} + +func LCheckStack(L *C.lua_State, sz int32, msg string) { + cmsg := C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + + C.luaL_checkstack(L, C.int(sz), cmsg) +} + +func LCheckType(L *C.lua_State, narg int32, t int32) { + C.luaL_checktype(L, C.int(narg), C.int(t)) +} + +func LCheckAny(L *C.lua_State, narg int32) { + C.luaL_checkany(L, C.int(narg)) +} + +func LNewMetatable(L *C.lua_State, tname string) bool { + ctname := C.CString(tname) + defer C.free(unsafe.Pointer(ctname)) + + return C.luaL_newmetatable(L, ctname) != 0 +} + +func LCheckUdata(L *C.lua_State, ud int32, tname string) unsafe.Pointer { + ctname := C.CString(tname) + defer C.free(unsafe.Pointer(ctname)) + + return C.luaL_checkudata(L, C.int(ud), ctname) +} + +func LCheckBuffer(L *C.lua_State, narg int32, len *uint64) unsafe.Pointer { + return C.luaL_checkbuffer(L, C.int(narg), (*C.size_t)(len)) +} + +func LWhere(L *C.lua_State, lvl int32) { + C.luaL_where(L, C.int(lvl)) +} + +// NOTE: It's not possible to pass varargs from Go->C via cgo, so instead we +// expect the user to format the message and hand it over to us, which we +// pass to luaL_errorL. This is an inconsistency with the actual C API, but +// there isn't really anything we can do. +func LErrorL(L *C.lua_State, msg string) { + cmsg := C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + + C.cluaL_errorL(L, cmsg) +} + +func LCheckOption(L *C.lua_State, narg int32, def string, lst []string) int32 { + cdef := C.CString(def) + defer C.free(unsafe.Pointer(cdef)) + + clst := C.makeCharArray(C.int(len(lst))) + defer C.freeCharArray(clst, C.int(len(lst))) + for i, s := range lst { + C.setArrayString(clst, C.CString(s), C.int(i)) + } + + return int32(C.luaL_checkoption(L, C.int(narg), cdef, clst)) +} diff --git a/internal/lua.go b/internal/lua.go new file mode 100644 index 0000000..74d7500 --- /dev/null +++ b/internal/lua.go @@ -0,0 +1,186 @@ +package internal + +/* +#cgo CFLAGS: -Iluau/VM/include -I/usr/lib/gcc/x86_64-pc-linux-gnu/14.1.1/include -I${SRCDIR} +#include +#include +#include +#include +*/ +import "C" +import "unsafe" + +type lua_Number C.double +type lua_Integer C.int +type lua_Unsigned C.uint + +type lua_CFunction func(L *C.lua_State) C.int +type lua_Continuation func(L *C.lua_State, status C.int) C.int + +type lua_Udestructor = func(*C.void) +type lua_Destructor = func(L *C.lua_State, _ *C.void) + +type lua_Alloc = func(ud, ptr *C.void, osize, nsize C.size_t) *C.void +type clua_Alloc = func(ptr unsafe.Pointer, osize, nsize uint) unsafe.Pointer + +//export go_allocf +func go_allocf(fp uintptr, ptr uintptr, osize uint, nsize uint) uintptr { + p := ((*((*clua_Alloc)(unsafe.Pointer(fp))))(unsafe.Pointer(ptr), osize, nsize)) + return uintptr(p) +} + +// +// ================== +// VM state +// ================== +// + +func NewState(ud unsafe.Pointer) *C.lua_State { + return C.clua_newstate(unsafe.Pointer(ud)) +} + +func LuaClose(L *C.lua_State) { + C.lua_close(L) +} + +func NewThread(L *C.lua_State) *C.lua_State { + return C.lua_newthread(L) +} + +func MainThread(L *C.lua_State) *C.lua_State { + return C.lua_mainthread(L) +} + +func ResetThread(L *C.lua_State) { + C.lua_resetthread(L) +} + +func IsThreadReset(L *C.lua_State) bool { + return C.lua_isthreadreset(L) != 0 +} + +// +// ================== +// VM Stack +// ================== +// + +func AbsIndex(L *C.lua_State, idx C.int) C.int { + return C.lua_absindex(L, idx) +} + +func GetTop(L *C.lua_State) C.int { + return C.lua_gettop(L) +} + +func SetTop(L *C.lua_State, idx C.int) { + C.lua_settop(L, idx) +} + +func PushValue(L *C.lua_State, idx C.int) { + C.lua_pushvalue(L, idx) +} + +func Remove(L *C.lua_State, idx C.int) { + C.lua_remove(L, idx) +} + +func Insert(L *C.lua_State, idx C.int) { + C.lua_insert(L, idx) +} + +func Replace(L *C.lua_State, idx C.int) { + C.lua_replace(L, idx) +} + +func CheckStack(L *C.lua_State, sz C.int) bool { + return C.lua_checkstack(L, sz) != 0 +} + +func RawCheckStack(L *C.lua_State, sz C.int) { + C.lua_rawcheckstack(L, sz) +} + +func XMove(from, to *C.lua_State, n C.int) { + C.lua_xmove(from, to, n) +} + +func XPush(from, to *C.lua_State, idx C.int) { + C.lua_xpush(from, to, idx) +} + +// +// ====================== +// Stack Values +// ====================== +// + +func IsNumber(L *C.lua_State, idx C.int) bool { + return C.lua_isnumber(L, idx) != 0 +} + +func IsString(L *C.lua_State, idx C.int) bool { + return C.lua_isstring(L, idx) != 0 +} + +func IsCFunction(L *C.lua_State, idx C.int) bool { + return C.lua_iscfunction(L, idx) != 0 +} + +func IsLFunction(L *C.lua_State, idx C.int) bool { + return C.lua_isLfunction(L, idx) != 0 +} + +func IsUserData(L *C.lua_State, idx C.int) bool { + return C.lua_isuserdata(L, idx) != 0 +} + +func Type(L *C.lua_State, idx C.int) bool { + return C.lua_type(L, idx) != 0 +} + +func TypeName(L *C.lua_State, tp C.int) *C.char { + return C.lua_typename(L, tp) +} + +func Equal(L *C.lua_State, idx1, idx2 C.int) bool { + return C.lua_equal(L, idx1, idx2) != 0 +} + +func RawEqual(L *C.lua_State, idx1, idx2 C.int) bool { + return C.lua_rawequal(L, idx1, idx2) != 0 +} + +func LessThan(L *C.lua_State, idx1, idx2 C.int) bool { + return C.lua_lessthan(L, idx1, idx2) != 0 +} + +func ToNumberX(L *C.lua_State, idx C.int, isnum bool) lua_Number { + isnumInner := C.int(0) + if isnum { + isnumInner = C.int(1) + } + + return lua_Number(C.lua_tonumberx(L, idx, &isnumInner)) +} + +func ToIntegerX(L *C.lua_State, idx C.int, isnum bool) lua_Integer { + isnumInner := C.int(0) + if isnum { + isnumInner = C.int(1) + } + + return lua_Integer(C.lua_tointegerx(L, idx, &isnumInner)) +} + +func ToUnsignedX(L *C.lua_State, idx C.int, isnum bool) lua_Unsigned { + isnumInner := C.int(0) + if isnum { + isnumInner = C.int(1) + } + + return lua_Unsigned(C.lua_tounsignedx(L, idx, &isnumInner)) +} + +// TODO: Rest of it +// TODO: Convert C.* types in args to go types diff --git a/internal/luau b/internal/luau new file mode 160000 index 0000000..4f91742 --- /dev/null +++ b/internal/luau @@ -0,0 +1 @@ +Subproject commit 4f917420d74aae84912acbaeb42e86691ddea309 diff --git a/main.go b/main.go new file mode 100644 index 0000000..38dd16d --- /dev/null +++ b/main.go @@ -0,0 +1,3 @@ +package main + +func main() {} diff --git a/test/.gitkeep b/test/.gitkeep new file mode 100644 index 0000000..cdcf65f --- /dev/null +++ b/test/.gitkeep @@ -0,0 +1,9 @@ +# `/test` + +Additional external test apps and test data. Feel free to structure the `/test` directory anyway you want. For bigger projects it makes sense to have a data subdirectory. For example, you can have `/test/data` or `/test/testdata` if you need Go to ignore what's in that directory. Note that Go will also ignore directories or files that begin with "." or "_", so you have more flexibility in terms of how you name your test data directory. + +Examples: + +* https://github.com/openshift/origin/tree/master/test (test data is in the `/testdata` subdirectory) + +