Obligatory links to pt. 1, pt. 2, and pt. 3.
The way lua tables exist on the stack can be confusing. I looked and looked for documentation explaining exactly what is going on inside the stack with respect to a table, but I couldn’t find what I was looking for, so what I’m sharing is a product solely of my own research.
What is a lua table?
Tables in lua are really nothing more than associative arrays. That’s a pretty easy concept to understand, so I’m not going to dwell on it much. That said, the way a table exists on the stack I found to be kind of strange. First, go ahead and put this piece of code into your C program.
(Using the code tag in WordPress sucks. It never works, so I’m going to use pastebin instead).
So what did we just do?
Here’s what happens in the code linked above. We create a new table on the stack using lua_newtable (at index -1 if you have nothing else on the stack). We then iterate from 1 to 5, adding a key-value pair to the stack for each iteration.
First off, notice that we’re doing lua_pushnumber twice. The first number we push is the key, the second number is the value. For now, those are just values on the stack, they have not been added to the table. The order is very important when we get to the next line though.
So again, the values are on the stack but they’re not part of a table. When we call lua_rawset, it pops those values from the stack and puts the key-value pairs into the table. The index parameter set in the lua_rawset function is the index of the table. Remember how it was -1 when we first created it? Well now it’s -3, because we’ve added 2 elements on top of it. Once we execute lua_rawset though, those values are popped from the stack, and the table once again moves up to the position of -1. The values aren’t gone though, they’ve now become part of the table.
If you’ve run this code, you’ll most likely have noticed one glaring issue. Running stackTrace after you’ve populated your table shows “table” as the sole item on the stack. Where the hell is all of our important data?!
Tables occupying the stack, and where the data actually is.
At the beginning of this series I made an analogy to a stack of papers. To understand how tables work on the stack, let’s extend that analogy a bit. Imagine that each piece of paper in our stack has something written on it. So far, every single piece of paper in our stack only ever has ONE thing written on it. One piece of paper might have a number, another piece might have a single word, but each piece only contains one piece of information each. Now imagine that we create a spreadsheet with several pieces of information on it, and we print it out on a single sheet of paper and place it on the stack. As far as we’re concerned, it’s still just one piece of paper, and therefore one item in our stack of papers, but the contents of that one piece of paper is much different than the other pieces.
When we’re viewing a list of items in the stack (here’s the code for that if you haven’t followed the other entries) what we’re going to see is nothing more than the word “table”. We’re not going to actually see all of the contents of said table. As far as the raw stack is concerned, “table” is just another piece of paper sitting with all the others. The thing is, seeing “table” isn’t very useful to us. We’re using a table because we want the data inside it, so we have to be able to access that data somehow.
So how do we use the data in our table?
We use the data inside of a given table by iterating through the table and copying individual values from the table onto the stack. Imagine taking that piece of paper with the spreadsheet on it and transcribing the values from each row onto individual sheets of paper, one sheet per value.
Understanding all of that, let’s create a quick example to demonstrate what is actually happening when we access the table. Add the following code after you’ve created your table, but before the c = getchar();
The most widely accepted way to access the data inside of a table is by using lua_next. Lua_next iterates through a table and copies a key / value pair from the table onto the stack, which is pretty easy to understand. It does some other funky stuff though, which requires a bit more abstract thought.
The first thing lua_next does is remove a key from the stack. It does this before it does anything else. The problem is, the first time it iterates, it hasn’t yet populated the stack with a key, so it can’t pop one. Therefore, to use lua_next without error, you must “prime” the stack first by adding a value to the stack right before you call lua_next. This way, the value you’ve added will be treated as the first key (we’ll call it the zero key) and it will be discarded.
Once it’s discarded a key, it then pulls the next available key / value pair from the table and copies the value, and then the key. Here’s a look at what exactly happens, it’s a bit tricky.
After we’ve created a table, the stack looks like this:
-1 —- table
Once we’ve primed the stack it looks like this:
-1 —- nil (or whatever our primer value is)
-2 —- table
Once we call lua_next, the primed value is discarded and is replaced with the key value pair:
-1 —- “hi there” (value)
-2 —- 1 (key)
-3 —- table
In the above stack, the top element is the value, but lua_next expects the key to be the top value when it performs it’s next iteration, so before we call lua_next again, we have to remove the top element (the value part of the key value pair). Lua_pop(1), will remove the top value, and our stack will look like this:
-1 —- 1 (key)
-2 —- table
Lua_next will continue to iterate, and again the first thing it will do is pop the key in element -1 and replace it with a new key / value pair.
Using the values.
In the key / value pair, the value is what we’re really after. The key is essentially an index (it can be more than that, but that’s a discussion for another day). Every time we iterate through our table, we’re actively removing the value from the stack though, and that seems counter-intuitive. Really, it’s not though. We as the programmers control when that value is removed from the stack or if it’s even removed at all. We essentially have three options for using the value: (A) We can populate a C variable with the value and now it’s not tied to the lua stack at all. (B) Instead of popping it from the stack we can simply move it to another location on the stack, out of the way of our lua_next iterations. (C) We can decide that we don’t need the value at all, and just pop it.
Putting it all together.
At this point we can create a table, populate it with some data, and then iterate through that table to retrieve the data that we need. I found the concept of lua tables to be quite tricky to wrap my mind around, but once I really got into the meat and potatoes of the concepts, it proved to be pretty straight-forward. Hopefully this helps anyone who might be struggling with the same conceptual issues I was.