Wednesday, March 04, 2015
Mimicking C APIs in Lua
Last Sunday, the latest announcement of luaposix lead me to state the following observation on the Lua mailing list:
- From
- Sean Conner <sean@conman.org>
- To
- Lua mailing list <lua-l@lists.lua.org>
- Subject
- Literal mimicking of C APIs in Lua (split from Re: [ANN] luaposix 33.3.0 released)
- Date
- Sat, 28 Feb 2015 23:16:51 -0500
This isn't about luaposix per se, but this is prompting this observation about Lua wrappers for C APIs: they tend to mimic it quite literally (to the point where I think I've said this before: if I wanted to code in C, I know where to find it).
I checked, and sure enough, the Lua wrapper around syslog()
was
what I expected (and had found in several other syslog()
wrappers for Lua)—a very thin wrapper over
syslog()
leading to this in Lua:
syslog.syslog(syslog.LOG_WARNING,string.format("foobar %d is at %d capacity",fooid,foocap))
I seem to be the only one who make it easy to use
syslog()
in Lua:
syslog('warning',"foobar %d is at %d capcaity",fooid,foocap)
I went to the trouble of ma
king the module itself callable (but you can still call
syslog.log()
if you want) and handle the call to
string.format()
be
half of the caller because in 90% of the cases I call
syslog()
, I need formatted output.
It just struck me as odd that not many writers of C-based Lua modules bother to make it easy to use their modules and I was about the thought process behind it (or the lack of thought process). The best answer came from Gary Vaughan, author of luaposix:
- From
- "Gary V. Vaughan" <XXXXXXXXXXXXXXX>
- To
- Lua mailing list <lua-l@lists.lua.org>
- Subject
- Re: Literal mimicking of C APIs in Lua (split from Re: [ANN] luaposix 33.3.0 released)
- Date
- Sun, 1 Mar 2015 09:02:00 +0000
It's precisely because I don't want to code in C either that the low- level API of luaposix aspires to be as thin a wrapper for the C API as possible. It's easier, faster and less error-prone to write the Luaish API on top of the thin C wrappers in Lua than it is to write the fancy stuff in C.
Not only that, this leaves the door open to replace the C bindings with an FFI binding that the Luaish layer can equally sit on top of and ultimately shipping no C code at all in luaposix… as long as a dependency on LuaJIT and/or LuaFFI is an acceptable compromise. When the low-level C code implements the user- facing API, all of this is a lot more difficult.
He's got a good point—make the C layer as thin as possible:
static int syslog_syslog(lua_State *L) { syslog(luaL_checkinteger(L,1),"%s",luaL_checkstring(L,2)); return 0; }
and leave the fancy stuff up to Lua:
local m_priority = { emerg = 0, emergency = 0, alert = 1, crit = 2, critical = 2, err = 3, error = 3, warn = 4, warning = 4, notice = 5, info = 6, information = 6, debug = 7 } function syslog(priority,...) syslogcore.syslog(m_priority[priority],string.format(...)) end
But there is a flaw when he metions LuaJIT (and LuaFFI in general):
C declarations are not passed through a C pre-processor, yet. No pre- processor tokens are allowed, except for
#pragma pack
. Replace#define
in existing C header files withenum
,static const
ortypedef
and/or pass the files through an external C pre-processor (once). Be careful not to include unneeded or redundant declarations from unrelated header files.
In the case of syslog()
, it's not that big an issue—the
levels are standardized so it's easy
to supply the proper values, but it's different for a function like
socket()
. A thin C wrapper is trivial:
static int socket_socket(lua_State *L) { lua_pushinteger( L, socket( luaL_checkinteger(L,1), luaL_checkinteger(L,2), luaL_checkinteger(L,3) ) ); lua_pushinteger(L,errno); return 2; }
Not so easy is defining the values for those three parameters. The first is an arbitrary number defining the “family” the socket belongs to, ether IP, IPv6, Unix domain, etc. The second parameter further defines the socket type, whether it's a stream based socket, or you want actual packets. The last parameter can, in 99% of the cases, be 0, so we shall ignore it. In C, it's typically called like:
s = socket(AF_INET,SOCK_STREAM,0);
But the actual value of AF_INET
may vary from operating
system to operating system (they do—I checked a few systems to make sure) so
it's not always as straightforward to skip C entirely when wrapping a C API with LuaJIT.
Overall though, the idea is sound and I do find it intriguing, but not enough to stop the approach I've been taking.
Mind blown
When you bite into a Thin Mint, you probably aren't wondering where it comes from. (The Girl Scouts, of course.) But wait, there are two bakers. And they make two very different Thin Mints: One is crunchier, more minty. The other is richer with a smooth chocolate coating.
Via Jeff Cuscutis at FaceGoogleMyBookPlusSpace, 6 Girl Scout cookies you thought you were getting but aren't - Los Angeles Times
Wait … what?