From 06b57fdb3da729ed3b63be4a030805b7e7099693 Mon Sep 17 00:00:00 2001 From: terminaldweller Date: Sat, 27 Jan 2024 23:10:04 -0500 Subject: added new blog entry --- mds/cstruct2luatable.md | 69 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 17 deletions(-) (limited to 'mds/cstruct2luatable.md') diff --git a/mds/cstruct2luatable.md b/mds/cstruct2luatable.md index a8eb92c..8e5bdf5 100644 --- a/mds/cstruct2luatable.md +++ b/mds/cstruct2luatable.md @@ -1,22 +1,27 @@ # C Struct to Lua table ## Overview + For this tutorial we'll change a C struct into a Lua table. The structure we'll be using won't be the simplest structure you'll come across in the wild so hopefully the tutorial will do a little more than just cover the basics.
We'll add the structures as `userdata` and not as `lightuserdata`. Because of that, we won't have to manage the memory ourselves, instead we will let Lua's GC handle it for us.
Disclaimer: -* This turotial is not supposed to be a full dive into lua tables, metatables and their implementation or behavior. The tutorial is meant as an entry point into implementing custom Lua tables.
+ +- This turotial is not supposed to be a full dive into lua tables, metatables and their implementation or behavior. The tutorial is meant as an entry point into implementing custom Lua tables.
### Yet Another One? + There are already a couple of tutorials on this, yes, but the ones I managed to find were all targeting older versions of lua and as the Lua devs have clearly stated, different Lua version are really different. The other reason I wrote this is I needed a structures that had structure members themselves and I couldn't find a tutorial for that.
This tutorial will be targeting Lua 5.3.
We'll also be using a not-so-simple structure to turn into a Lua table.
### What you'll need -* A working C compiler(I'll be using clang) -* Make -* you can get the repo [here](https://github.com/bloodstalker/blogstuff/tree/master/src/cstruct2luatbale).
+ +- A working C compiler(I'll be using clang) +- Make +- you can get the repo [here](https://github.com/bloodstalker/blogstuff/tree/master/src/cstruct2luatbale).
## C Structs + First let's take a look at the C structures we'll be using.
The primary structure is called `a_t` which has, inside it, two more structures `b_t` and `c_t`: @@ -29,30 +34,36 @@ typedef struct { c_t** a_pp; } a_t; ``` + ```c typedef struct { uint32_t b_int; double b_float; } b_t; ``` + ```c typedef struct { char* c_string; uint32_t c_int; } c_t; ``` + The structures are purely artificial.
## First Step: Lua Types + First let's take a look at `a_t` and decide how we want to do this.
`a_t` has five members:
-* `a_int` which in Lua we can turn into an `integer`. -* `a_float` which we can turn into a `number`. -* `a_string` which will be a Lua `string`. -* `a_p` which is a pointer to another structure. As previously stated, we will turn this into a `userdata`.
-* `a_pp` which is a double pointer. We will turn this into a table of `userdata`.
+ +- `a_int` which in Lua we can turn into an `integer`. +- `a_float` which we can turn into a `number`. +- `a_string` which will be a Lua `string`. +- `a_p` which is a pointer to another structure. As previously stated, we will turn this into a `userdata`.
+- `a_pp` which is a double pointer. We will turn this into a table of `userdata`.
## Second Step: Helper Functions + Now let's think about what we need to do. First we need to think about how we will be using our structures. For this example we will go with a pointer, i.e., our library code will get a pointer to the structure so we need to turn the table into `userdata`.
Next, we want to be able to push and pop our new table from the Lua stack.
We can also use Lua's type check to make sure our library code complains when someone passes a bad type.
@@ -60,6 +71,7 @@ We will also add functions for pushing the structure arguments onto the stack, a Let's start: First we will write a function that checks the type and returns the C structure:
+ ```c static a_t* pop_a_t(lua_State* ls, int index) { a_t* dummy; @@ -68,6 +80,7 @@ static a_t* pop_a_t(lua_State* ls, int index) { return dummy; } ``` + We check to see if the stack index we are getting is actually a userdata type and then check the type of the userdata we get to make sure we get the right userdata type. We check the type of the userdata by checking its metatable. We will get into that later.
This amounts to our "pop" functionality for our new type.
Now let's write a "push":
@@ -97,6 +110,7 @@ We will need a key. for simplicity's sake we use the pointer that `lua_newuserda Lastly we just set our key-value pair with `LUA_REGISTRYINDEX`.
Next we will write a function that pushes the fields of the structure onto the stack:
+ ```c int a_t_push_args(lua_State* ls, a_t* a) { if (!lua_checkstack(ls, 5)) { @@ -111,9 +125,11 @@ int a_t_push_args(lua_State* ls, a_t* a) { return 5; } ``` + Notice that we are returning 5, since our new next function which is the new function expects to see the 5 fields on top of the stack.
Next up is our new function:
+ ```c int new_a_t(lua_State* ls) { if (!lua_checkstack(ls, 6)) { @@ -135,10 +151,12 @@ int new_a_t(lua_State* ls) { return 1; } ``` + We just push an `a_t` on top of stack and then populate the fields with the values already on top of stack.
The fact that we wrote tha two separate functions for pushing the arguments and returning a new table instance means we can use `new_a_t` as a constructor from lua as well. We'll later talk about that.
## Third Step: Setters and Getters + Now lets move onto writing our setter and getter functions.
For the non-userdata types its fairly straightforward:
@@ -155,6 +173,7 @@ static int getter_a_string(lua_State* ls) { return 1; } ``` + As for the setters:
```c @@ -166,6 +185,7 @@ static int setter_a_int(lua_State* ls) { ``` Now for the 4th and 5th fields:
+ ```c static int getter_a_p(lua_State *ls) { a_t* dummy = pop_a_t(ls, 1); @@ -176,7 +196,7 @@ static int getter_a_p(lua_State *ls) { } ``` -For the sake of laziness, let's assume `a_t->a_int` denotes the number of entries in `a_t->a_pp`.
+For the sake of laziness, let's assume `a_t->a_int` denotes the number of entries in `a_t->a_pp`.
```c static int getter_a_pp(lua_State* ls) { @@ -206,16 +226,19 @@ Since we register all our tables with `LUA_REGISTRYINDEX` we just retreive the k As you can see, for setters we are assuming that the table itself is being passed as the first argument(the `pop_a_t` line assumes that).
Our setters methods would be called like this in Lua:
+ ```lua local a = a_t() a:set_a_int(my_int) ``` + The `:` operator in Lua is syntactic sugar. The second line from the above snippet is equivalent to `a.set_a_int(self, my_int)`.
As you can see, the table itself will always be our first argument. That's why our assumption above will always be true if the lua code is well-formed.
We do the same steps above for `b_t` and `c_t` getter functions.
Now let's look at our setters:
+ ```c static int setter_a_string(lua_State *ls) { a_t* dummy = pop_a_t(ls, 1); @@ -254,7 +277,9 @@ static int setter_a_pp(lua_State* ls) { We are all done with the functions we needed for our new table. Now we need to register the metatable we kept using:
# Fourth Step: Metatable + First, if you haven't already, take a look at the chapter on metatable and metamethods on pil [here](https://www.lua.org/pil/13.html).
+ ```c static const luaL_Reg a_t_methods[] = { {"new", new_a_t}, @@ -272,11 +297,13 @@ static const luaL_Reg a_t_methods[] = { static const luaL_Reg a_t_meta[] = {{0, 0}}; ``` + We just list the functions we want to be accessible inside Lua code.
Lua expects the C functions that we register with Lua to have the form `(int)(func_ptr*)(lua_State*)`.
Also, it's a good idea to take a look at the metatable events that Lua 5.3 supports [here](http://lua-users.org/wiki/MetatableEvents). They provide customization options for our new table type(as an example we get the same functionality as C++ where we get to define what an operator does for our table type).
Now we move on to registering our metatable with Lua:
+ ```c int a_t_register(lua_State *ls) { lua_checkstack(ls, 4); @@ -295,6 +322,7 @@ int a_t_register(lua_State *ls) { return 0; } ``` + Please note that we are registering the metatable as a global. It is generally not recommended to do so.Why you ask?
Adding a new enrty to the global table in Lua means you are already reserving that keyword, so if another library also needs that key, you are going to have lots of fun(the term `fun` here is borrowed from the Dwarf Fortress literature).
Entries in the global table will require Lua to look things up in the global table so it slows things down a bit, though whether the slow-down is signifacant enough really depends on you and your requirements.
@@ -306,21 +334,25 @@ Remember metatable events?
That's what we'll use.
Lua metatables support something called metatable events. Eeach event has a string key and the value is whatever you put as the value.
The values are used whenever that event happens. Some the events are: -* `__call` -* `__pairs` -* `__sub` -* `__add` -* `__gc` -The `__sub` event is triggered when your table is the operand of a suntraction operator. `__gc` is used when lua want to dispose of the table so if you are handling the memory yourself, in contrast to letting Lua handle it for you, here's where you free memory.
-The events are a powerful tool that help us customize how our new table behaves.
+ +- `__call` +- `__pairs` +- `__sub` +- `__add` +- `__gc` + The `__sub` event is triggered when your table is the operand of a suntraction operator. `__gc` is used when lua want to dispose of the table so if you are handling the memory yourself, in contrast to letting Lua handle it for you, here's where you free memory.
+ The events are a powerful tool that help us customize how our new table behaves.
For a constructor, we will use the `__call` event.
That means when someone calls our metatable in Lua, like this(call event is triggered when our table is called, syntactically speaking):
+ ```lua local a = a_t() ``` + `a` will become a new instance of our table.
We can add a value for our metatable's `__call` key from either Lua or C. Since we are talking about Lua and haven't almost written anything in Lua, let's do it in Lua:
+ ```lua setmetatable(a_t, {__call = function(self, arg1, arg2, arg3, arg4, arg5) @@ -330,13 +362,16 @@ setmetatable(a_t, {__call = } ) ``` + We use our `new` method which we previously registered for our metatable. Note that Lua will pass `nil` for the argument if we don't provide any. That's how our cozy constructor works.
## Final Words + The tutorial's goal is to show you one way of doing the task and not necessarily the best way of doing it. Besides, depending on your situation, you might want to do things differently so by no means is this tutorial enough. It's an entry level tutorial.
Any feedback, suggestions and/or fixes to the tutorial is much appreciated.
## Shameless Plug + I needed to turn a C struct into a lua table for an application I'm working [on](https://github.com/bloodstalker/mutator/tree/master/bruiser). Further down the line, I needed to do the same for a lot more C structs with the possibility of me having to do the same for a lot more C structs. I just couldn't bring myself to do it manually for that many C structs so I decided to work on a code generator that does that for me. The result is [luatablegen](https://github.com/bloodstalker/luatablegen).
`luatablegen` is a simple script that takes the description of your C structures in an XML file and generates the C code for your new tables and metatables. It does everything we did by hand automatically for us.
`lautablegen` is in its early stages, so again, any feedback or help will be appreciated.
-- cgit v1.2.3