I'm using the nim-lua bindings to make a dll that can be loaded from lua, but I can't figure out how to bind this library to a lua table.
In C, you could do this (took this code partially from a tutorial):
int luaopen_testlib(lua_State *L) {
luaL_Reg fns[] = { // make an array like this
{"foo", cfoo},
{"bar", cbar},
{NULL, NULL} // sentinel marking the end of the array
};
// register all those functions in a lua namespace 'time' (a lua table)
luaL_register(L, "time", fns);
return 1;
}
This is my current test code in Nim:
proc luaopen_testlib(L:PState):cint {.cdecl, exportc, dynlib.} =
register(L, "foo", nimfoo)
register(L, "bar", nimbar)
The problem there is that each these procs get registered in lua as a global function, instead of functions in one table, as the C code does. I tried registring a nim table containing those procs, but it gave me a type mismatch error on register.
Is there some way to do this?
I have used this template:
let L = createLuaState()
openlibs(L)
# overload the heck out of it
proc dsSetValue*(key: string; value: string) =
proc dsSetValue*(key: string; value: byte) =
proc dsSetValue*(key: string; value: seq[byte]) =
proc dsSetValue*(key: string; value: int) =
proc implStoreSet*(L: PState): cint {.cdecl.} =
let key = $tostring(L,1) # this is cstring
let val = $tostring(L,2)
dsSetValue(key, val) # generic entrypoint for various `val` data types
result = 0
var regs = [
# data store elements handling and helpers
luaL_Reg( name: "storeSet", fn: implStoreSet), # implementation for myLib.storeSet in Lua space
luaL_Reg( name: "storeGet", fn: implStoreGet),
# add whatever functions you need in Lua scripts
# NULL terminator is required to mark end of the table
luaL_Reg( name: nil, fn: nil)
]
L.newlib(regs)
L.setglobal("myLib") # Lua domain space for your API/s
in Lua call
myLib.storeSet("key1", 15)
myLib.storeSet("key1", "hello world")
myLib.storeSet("key1", string.byte('A'))
Sadly, luaL_Reg isn't working for me. Probably because I'm using lua 5.1. luaL_Reg is defined in the lua 5.2 file
I have to use 5.1, since I'm using Love2d.
Yea, that means building love2d, and I'm not really willing to do that. Either way I think luaJIT is a nice thing to keep.
How can I build a lua table in Nim? I was thinking maybe I could get away with doing that, if I can get those procs in it and push it to the lua stack, I might be able to retrieve it on the lua side when require-ing it.
Hey Skaruts, I'm not sure what you are trying to do with Love2D, but I thought this might interest you so I'll paste it here.
It's a demo using the Love API via the Lua VM:
# main.nim
# -- a bunch of glue code to get lua working --
type
luacfunc = proc(luaState: pointer): cint
luaL_reg = object
name: cstring
function: luacfunc
luaL_reg_ref = ref luaL_reg
luaL_reg_list = array[3, luaL_reg]
proc reg(name: cstring, function: luacfunc): luaL_reg_ref =
new result
result.name = name
result.function = function
const luaGlobalsIndex = -10002
const luaRegistryIndex = -10000
var luaState: pointer
proc luaL_ref(lua_State: pointer, t: cint): cint {.importc: "luaL_ref".}
proc luaL_openlib(luaState: pointer, name: cstring, lib: luaL_reg_list, someval: cint) {.importc: "luaL_openlib".}
proc lua_settop(luaState: pointer, index: cint) {.importc: "lua_settop".}
proc rawgeti(luaState: pointer, index1, index2: cint) {.importc: "lua_rawgeti".}
proc pop(luaState: pointer, index: cint) = lua_settop(luaState,-(index)-1)
proc getfield(luaState: pointer, a: cint, name: cstring) {.importc: "lua_getfield".}
proc setfield(luaState: pointer, a: cint, name: cstring) {.importc: "lua_setfield".}
proc luaPcall(luaState: pointer, a, b, c: cint): cint {.importc: "lua_pcall".}
proc luaToString(luaState: pointer, index: cint, size: cint): cstring {.importc: "lua_tolstring".}
proc luaToNumber(luaState: pointer, index: cint): cdouble {.importc: "lua_tonumber".}
proc luaToBool(luaState: pointer, index: cint): cint {.importc: "lua_toboolean".}
proc pushNumber(luaState: pointer, number: cdouble) {.importc: "lua_pushnumber".}
proc pushString(luaState: pointer, s: cstring) {.importc: "lua_pushstring".}
proc pushCClosure(luaState: pointer, function: pointer, a: cint) {.importc: "lua_pushcclosure".}
proc pushFunction(luaState: pointer, function: pointer) = luaState.pushCClosure(function, 0)
proc pullString(luaState: pointer, index: cint): string = $luaToString(luaState, index, 0)
proc pullNumber(luaState: pointer, index: cint): float = luaToNumber(luaState, index)
proc pullBool(luaState: pointer, index: cint): bool = return luaToBool(luaState, index) == 1
proc loveFunc(moduleName, funcName: string) =
getfield(luaState, luaGlobalsIndex, "love")
getfield(luaState, -1, moduleName)
getfield(luaState, -1, funcName)
# -- api wrapper --
type Image = distinct cint
proc newImage(filename: string): Image =
loveFunc("graphics", "newImage")
luaState.pushString(filename)
if luaPcall(luaState, 1, 1, 0) != 0:
echo "proc newImage failed on the lua side ->", luaState.pullString(-1)
quit 1
let res = luaState.luaL_ref(luaRegistryIndex).Image
luaState.pop(1)
return res
proc getWidth(self: Image): float =
luaState.rawgeti(luaRegistryIndex, self.cint)
getfield(luaState, -1, "getWidth")
luaState.rawgeti(luaRegistryIndex, self.cint)
if luaPcall(luaState, 1, 1, 0) != 0:
echo "proc getWidth failed on the lua side ->", luaState.pullString(-1)
quit 1
let res = luaState.pullNumber(-1)
luaState.pop(1)
return res
proc getHeight(self: Image): float =
luaState.rawgeti(luaRegistryIndex, self.cint)
getfield(luaState, -1, "getHeight")
luaState.rawgeti(luaRegistryIndex, self.cint)
if luaPcall(luaState, 1, 1, 0) != 0:
echo "proc getHeight failed on the lua side ->", luaState.pullString(-1)
quit 1
let res = luaState.pullNumber(-1)
luaState.pop(1)
return res
proc draw(
image: Image,
x: float, # The position to draw the object (x-axis).
y: float, # The position to draw the object (y-axis).
r: float, # Orientation (radians).
sx: float, # Scale factor (x-axis). Can be negative.
sy: float, # Scale factor (y-axis). Can be negative.
ox: float, # Origin offset (x-axis). (A value of 20 would effectively move your drawable object 20 pixels to the left.)
oy: float, # Origin offset (y-axis). (A value of 20 would effectively move your drawable object 20 pixels up.)
kx: float, # Shearing factor (x-axis).
ky: float # Shearing factor (y-axis).
) =
loveFunc("graphics", "draw")
luaState.rawgeti(luaRegistryIndex, image.cint)
luaState.pushNumber(x)
luaState.pushNumber(y)
luaState.pushNumber(r)
luaState.pushNumber(sx)
luaState.pushNumber(sy)
luaState.pushNumber(ox)
luaState.pushNumber(oy)
luaState.pushNumber(kx)
luaState.pushNumber(ky)
if luaPcall(luaState, 10, 0, 0) != 0:
echo "proc draw failed on the lua side ->", luaState.pullString(-1)
quit 1
proc quitLove =
loveFunc("event", "quit")
if luaPcall(luaState, 0, 0, 0) != 0:
echo "proc quit failed on the lua side ->", luaState.pullString(-1)
quit 1
# -- app code below --
var image: Image
proc loadCallback =
image = newImage("sprite.png")
echo "Image width (should be 300): ", image.getWidth()
echo "Image height (should be 205): ", image.getHeight()
proc drawCallback =
draw(image, 10, 10, 0, 1, 1, 0, 0, 0, 0)
proc keypressedCallback(key, scancode: string, isrepeat: bool) =
if key == "escape":
quitLove()
# -- init --
proc drawWrapper(state: pointer): cint =
luaState = state
drawCallback()
proc loadWrapper(state: pointer): cint =
luaState = state
loadCallback()
proc keypressedWrapper(state: pointer): cint =
luaState = state
let key = state.pullString(1)
let scancode = state.pullString(2)
let isrepeat = state.pullBool(3)
keypressedCallback(key, scancode, isrepeat)
proc load(L: pointer): cint {.dynlib, exportc: "luaopen_libmain".} =
{.emit: """NimMain();""".}
luaState = L
echo "lua opened nim lib"
var mylib: luaL_reg_list = [
reg(nil, nil)[],
reg(nil, nil)[],
reg(nil, nil)[]
]
luaL_openlib(L, "libmain", mylib, 0)
pushFunction(L, drawWrapper)
setfield(L, luaGlobalsIndex, "draw")
pushFunction(L, keypressedWrapper)
setfield(L, luaGlobalsIndex, "keypressed")
pushFunction(L, loadWrapper)
setfield(L, luaGlobalsIndex, "load")
return 1
-- main.lua
require "libmain"
function love.load ()
load()
end
function love.draw ()
draw()
end
function love.keypressed (key, scanCode, isRepeat)
keypressed(key, scanCode, isRepeat)
end
Compile and run:
nim c --app:lib main.nim && love .
Result:
You would think that interacting with the engine by going [Nim]-->[Lua]-->[C++] would be slow but it actually doesn't seem to incur much overhead compared to just [Lua]-->[C++]
I may have mislead everyone about the bindings I'm using (edited the OP for clarity). It's the official lua bindings, and not the nimLua ones. I just adapted @lucian's code to my lib and it compiled using nimLua (although the lua app crashes on startup for some reason).
As for @geotre's code, I got a whole bunch of undefined references compiling that with both bindings. I adapted the table building code at the end to my lib (and the glue code) and I still get a undeclared identifier: 'luaL_openlib'.
I'm not sure what's going on...
As for what I'm doing, it's a little framework for me to make roguelikes in lua, and I'm exploring the possibility to use Nim for the heavy lifting.
My test lib worked fine, though. I implemented a whole FOV algorithm in Nim and used it from lua without issues. The only problem I have is that each of the functions are exposed as globals, and not within a local lib in lua. I'd like to do local mylib = require mylib and have it all as fields of the local mylib.
I don't really know anything about Lua, but I needed to do some luaJIT stuff, including reading arbitrary Lua data into Nim objects, so I wrote this thing:
https://github.com/disruptek/lunacy/
Maybe it will help. The docs are fairly skimpy, but I use it like this:
var L = newState()
L.doString(readFile "some.lua")
var data = L.popStack
echo data
# or you might do something like this...
var tree = TTable.newLuaStack(L.last)
read tree
echo tree
That module may come in handy. Gonna keep it in mind.
I'm doing the opposite of that, though. Love2D already handles the whole backend lua C api stuff, and creates the lua state, then lua itself sends it to my lib when calling its functions.
(love2d <--> lua <--> dll)
This is my test code from when I got it working.
although the lua app crashes on startup for some reason
This may be due to using Nim code standalone library without doing some presetup required. Look-up threads in forum for creating a Nim dll to be linked in C projects. AFAIK it requires some code to init the GC or alike.
In terms of registering under a table, there are two "register" calls. The one for accepting a table name requires a pointer:
type
Treg*{.final.} = object
name*: cstring
`func`*: CFunction
Preg* = ptr Treg
proc register*(state: PState, libname: cstring, lr: Preg)
I tried registring a nim table containing those procs, but it gave me a type mismatch error on register.
When you said you passed a Nim table, there is something not quite right. You may want to place your functions in an 'api' array of Treg and pass the address of api[0], so you get a real pointer. I hope it compiles.
Not sure how to do it. This gives me an error:
let fns = [
Preg(name:"foo", `func`:nimfoo), # Error: object constructor needs an object type
Preg(name:"bar", `func`:nimbar)
]
register(L, "sllib", fns[0])
let fns = [
Treg(name:"foo", `func`:nimfoo),
Treg(name:"bar", `func`:nimbar)
]
register(L, "sllib", unsafeAddr fns[0])
this compiles with official Nim's Lua 5.1 package, but from there on you're on your own.@Skaruts you don’t need either lua binding to use my demo code, just what I’ve included. Tested on nim devel.
I see now that you are trying to do something different though
@geotre, then maybe I can't compile it for the same reason I can't compile C code for lua... I may not have my lua libraries properly setup for that, or something. I'm never figured out what the problem was.
@lucian, that code worked! And my lib compiled fine too and worked as intended now. Thank you! :)
No problem, I'm glad it is helpful.
Maybe I can help figure out what's wrong. You shouldn't need to do anything with lua libraries to get it working, what versions of Nim and Love2D are you running? Did you copy the code exactly as it is above? What error message do you get?
It was a whole bunch of "undefined reference to" a lot of the functions from the lua api. I think I was getting that too when trying to compile a C code version of my first test (that's linker errors, iirc, but my C is too rusty to be sure). I ended up giving up on C, since I couldn't figure it out...
I'm not sure I have the system path properly set up for lua either. And maybe that's the problem. I couldn't even make luarocks work... The only thing I have set and working is the lua interpreter itself. I have to sit down and try to figure all that out one of these days.