Lots of work on the LuaStack documentation

This commit is contained in:
2026-02-22 02:12:28 -05:00
parent 28604e7e17
commit ee94f9f3a1
2 changed files with 186 additions and 458 deletions

View File

@@ -1,11 +1,36 @@
### Our In-House Lua API
# Our In-House Lua API
Lua comes with a C API. In general, the functions in this
API use the following calling convention: push the arguments
onto the lua stack, call the function, then pop the return
values from the lua stack. I find this difficult to use: I
find it hard to remember what's on the stack at any given
time, and which stack position contains which value. It's
not how I'm used to writing code.
So instead, we created our own in-house lua API called
"LuaStack." It allows you to create C++ local variables that
contain lua values. It has most of the same operations as
the original lua API, but they use different calling
conventions: you don't push arguments, and you don't pop
return values. Instead, you pass arguments as readable
parameters, and you pass return values as writable
parameters.
There's a caveat: we don't actually store lua values in C++
local variables. That would make it difficult for the lua
garbage collector to trace values. Instead, we use a hidden
level of indirection: the C++ local variable contains the
index of a pre-allocated spot on the lua stack. The
pre-allocated spot on the lua stack actually contains the
lua data. We'll walk you through the details of exactly how
this works in the following sections.
In our codebase, you are expected to use LuaStack, not
the original API, whenever possible.
== An Example of our API
Lua comes with a C API, which makes it possible to write C
functions that manipulate Lua data. The standard API is
functional, but it has some readability issues and can be
difficult to work with. Therefore, we have created our own
replacement API, which we call "LuaStack." In our codebase,
you are expected to use LuaStack, not the standard Lua API.
This paper will explain LuaStack using the following example
function in Lua, which compares two tables to see if they
are exactly equal:
@@ -39,13 +64,18 @@ and table2 are the same table (ie, pointer equality). The
function above, by contrast, checks if two different tables
contain the same key-value pairs. The built-in function
*table.nkeys* is something that we added to lua, it returns
the number of key-value pairs in a table. Once you fully
understand the Lua code above, have a look at the following
C++ code, which does the same thing. Don't worry if you
don't understand everything yet:
the number of key-value pairs in a table, it works on any
table. Once you fully understand the Lua code above, have a
look at the following C++ code, which does the exact same
thing. Don't worry if you don't understand everything yet:
```cpp
LuaDefine(table_equal, "table1, table2", "Return true if two tables are equal") {
LuaDefine(table_equal, "table1, table2",
"|Return true if two tables are equal."
"|"
"|The values in the table are not deep-compared,"
"|they are compared using pointer comparison."
) {
LuaArg table1, table2;
LuaVar size1, size2, key, value1, value2;
LuaRet equalflag;
@@ -251,6 +281,13 @@ our codebase - the LuaDefStack is called *LS*. But unlike
*LuaDefStack LS* is a C++ style smart pointer, which has
methods.
When the function returns, the only thing
left on the stack is supposed to be the return values.
The LuaDefStack is responsible for making that happen.
In the function LS.return, the stack is rearranged
to put the return values on the stack and remove
everything else. More on that later.
## Methods of Class LuaDefStack
In our example function, you'll find this line of code:
@@ -453,21 +490,36 @@ function always consists of two steps:
## The LuaDefine Macro
Our sample function starts with this line of code:
Our sample function starts with this:
```cpp
LuaDefine(table_equal, "table1, table2", "Return true if two tables are equal")
LuaDefine(table_equal, "table1, table2",
"|Return true if two tables are equal."
"|"
"|The values in the table are not deep-compared,"
"|they are compared using pointer comparison."
)
{ ... function body here ...}
```
The two strings in this line of code are just documentation.
Neither has any effect on how the function works. They are
collected automatically by our documentation system, which
can print them out on demand.
LuaDefine is a C macro that takes three parameters: the
function name, the parameter list docstring, and the big
docstring. Note that in C++, when you put two strings right
next to each other, C++ automatically concatenates them into
one string, and we've used that in the big docstring above.
LuaDefine is a C preprocessor macro. Before I tell you what
it macro expands into, I'd like to tell you how Lua
functions are normally written using the standard Lua API.
When using the standard API, lua functions look like this:
The two strings passed to the LuaDefine are just
documentation. Neither has any effect on how the function
works. They are collected automatically by our documentation
system, which can print them out on demand.
The big docstring doesn't contain newlines. Instead, we use
the pipe character to designate where new lines should
start. I find it harder to screw up.
Before I tell you what LuaDefine macro expands into, I'd
like to tell you how you write C functions that are intended
to be inserted into Lua:
```cpp
int table_equal(lua_State *L) {
@@ -481,19 +533,18 @@ pointer to the lua stack. A C function must have this
prototype to be compatible with the lua runtime.
Next, I will show you the macro expansion of a LuaDefine to
help you understand what it does. Here is the LuaDefine that
I will macro expand:
help you understand what it does. Here is the macroexpansion
of the LuaDefine above:
```cpp
LuaDefine(table_equal, "table1, table2", "Return true if two tables are equal")
```
Here is the expansion:
```cpp
int lfn_table_equal(lua_State *L);
const char *lfnarg_table_equal = "table1, table2";
const char *lfndoc_table_equal = "Return true if two tables are equal";
const char *lfndoc_table_equal =
"|Return true if two tables are equal."
"|"
"|The values in the table are not deep-compared,"
"|they are compared using pointer comparison.";
LuaFunctionReg reg_table_equal(
"table_equal", lfnarg_table_equal, lfndoc_table_equal, false, lfn_table_equal);
int lfn_table_equal(lua_State *L)
@@ -504,34 +555,32 @@ function lfn_table_equal. As you can see, it returns *int*
and accepts *lua_State *L,* which makes it compatible with
the lua runtime. This is a forward declaration.
Next, there are the two documentation strings. I've given
these documentation strings two names: lfnarg_table_equal
and lfndoc_table_equal. That way, it's possible to access
these documentation strings in code.
Next, there are the two documentation strings.
Next, there is the "registry entry" reg_table_equal, of
class LuaFunctionReg. The constructor of reg_table_equal
runs at program initialization time. The constructor stores
its arguments: the function name, the documentation strings,
the flags, and the C function pointer. These are all stored
inside reg_table_equal. Then, the constructor links
reg_table_equal into a global linked list of LuaFunctionReg
objects. This linked list makes up the registry of all
LuaDefines.
Next, class LuaFunctionReg implements a registry of
LuaDefines. It is the means by which we can loop over all
LuaDefines and insert them into a lua environment. The
constructor of LuaFunctionReg runs at program initialization
time. The constructor stores its arguments: the function
name, the documentation strings, the flags, and the C
function pointer. These are all stored inside
reg_table_equal. Then, reg_table_equal is linked
into a global linked list of LuaFunctionReg objects. This
linked list makes up the registry of all LuaDefines.
Finally, we have a second copy of the prototype for
lfn_table_equal. This time, it's not a forward declaration:
there's no semicolon. This prototype is meant to be followed
by the function body.
by the function body. A LuaDefine must always be followed
by a function body.
The upshot of all this is that LuaDefine doesn't just
declare a function. It also links that function into a
global registry of all the LuaDefines. The registry contains
not only the function name and function pointer, but also
the function documentation. Remember also that in C++,
static objects like reg_table_equal are constructed before
*main*. So by the time *main* starts, the registry is
ready-to-use.
declare a function. It also adds that function to the
registry of all LuaDefines. The registry contains not only
the function name and function pointer, but also the
function documentation. Remember also that in C++, static
objects like reg_table_equal are constructed before *main*.
So by the time *main* starts, the registry is ready-to-use.
The registry of all LuaDefines has two primary uses:
@@ -549,28 +598,30 @@ The registry of all LuaDefines has two primary uses:
What if you want to write C++ code that manipulates Lua
data, but which *isn't* called from inside Lua? In that
case, LuaDefStack won't work, because LuaDefStack is
hardwired to assume that a caller *exists*:
hardwired to assume that a lua caller *exists*,
and that it wants to pass arguments and return values:
- It checks that the caller passed the right number of
arguments.
- It transfers the arguments from the caller to the LuaArg
slots.
- It transfers the LuaRet slots back to the caller at
function end.
For situations where there isn't any caller, we have created
an alternative to LuaDefStack called LuaExtStack. There are
several things that LuaExtStack does differently:
By contrast, class LuaExtStack is for a situation where
you're in the middle of doing some C++ processing, and
you suddenly decide you need to store some more lua
variables. Class LuaExtStack will allocate space for
the variables.
- LuaExtStack doesn't accept LuaArg or LuaRet parameters -
it only allows LuaVar parameters. That only makes sense,
since it should be used in contexts where there's no
caller.
it only allows LuaVar parameters.
- There is no code in LuaExtStack to check that the caller
passed the right number of arguments, since again, there
is no caller.
passed the right number of arguments, since again, it
is not meant to be used in that context.
- Class LuaExtStack doesn't have a method LS.result, since
there's nobody to return a result to.
@@ -715,3 +766,69 @@ is useful for copying a value from a LuaSlot to the top of
the stack. This is generally useful when you want to take a
value from a LuaSlot, and then manipulate it using the
standard API.
## Metamethod Functions missing from our API
Several API functions from the old Lua API will
automatically call metamethods. For example, consider the
function lua_less. This can be used to compare two
strings. But if you store the right metamethod, lua_less
can end up calling a closure and running lua code.
I feel strongly that this is a *very dangerous* design
decision. Imagine that you're writing code for the
difference transmitter. You *think* you're just comparing
two strings, but all of a sudden you're running untrusted
lua code. If that code generates a lua error, suddenly
you're throwing a C++ exception from inside the difference
transmitter.
Because of this, LuaStack is *strongly* discouraging the use of
lua API functions that use metamethods: they're not even
exposed through the LuaStack API. Here is a short list
of API functions that use metamethods and substitutes that
don't:
| Uses Metamethods | Safe Substitute |
|-------------------------|--------------------------|
| lua_gettable | LS.rawget |
| lua_settable | LS.rawset |
| lua_equal | LS.rawequal |
| lua_lessthan | LS.genlt |
| lua_len | LS.rawlen or LS.nkeys |
## Our Extensions to Lua
We have made many extensions to the lua interpreter:
* We have created a new lua datatype, the "token". This is
a string-like datatype, limited to holding short lua
identifiers. The entire string is actually packed in the
8 bytes of a LUA_LIGHTUSERDATA. See the markdown
document, "Tokens-a-new-Lua-Type." LuaStack is the
module that implements and supports tokens.
* We have patched the lua runtime in quite a few ways.
See the markdown "A-Summary-of-our-Lua-Patches." Some of
these patches have APIs which are accessible via LuaStack.
* Table Subtypes. One of our patches allows you to assign
table subtypes: tables can be general tables, classes,
tangibles, and several other specialized types. LuaStack
provides the enum for all the table types, and the
accessors to get and set the extended type of a table.
* Classes. We have added the concept of a "class" to lua.
The lua registry contains a table of all classes.
LuaStack implements the functions to create classes,
lookup classes, get class names, and the like.
Classes will be documented in another markdown document.
* Tangible basics. Tangibles are a complex data structure
which is in class World, which is *way* above LuaStack's
abstraction level. However, LuaStack does implement
one tiny part of the tangible infrastructure: it provides
the tangibles table in the lua registry, and functions
to look up tangibles. The rest of what tangibles are about
are implemented by the World module.

View File

@@ -1,372 +1,9 @@
/////////////////////////////////////////////////////////
//
//
// GENERAL CONCEPT
//
// The original lua C API asks you to work with a stack machine. When you
// use the original API, you're manually pushing and popping in every
// line of code. I find it hard to remember what stack position contains
// what value. This wrapper is designed to alleviate that problem.
//
// However, the lua garbage collector demands that we keep all lua values
// on the lua stack. I can't change that. I can create a wrapper, but I
// still have to keep all lua values on the lua stack. So, here's how
// this wrapper works:
//
// At the beginning of any C++ lua function, we allocate enough space on
// the lua stack to hold all the arguments, all the return values, and all
// the temporary values that we need. We give each of the stack slots
// that we allocate a human-readable name. From that point forward,
// we don't push or pop anything on the lua stack. Instead, we do all our
// lua value manipulation using the stack slots that we allocated and named
// at the beginning of the function.
//
// LUASTACK: See the markdown document "Our In-House
// Lua API" for more information about what this is.
//
/////////////////////////////////////////////////////////
//
// LUAARG, LUARET, LUAVAR, and LUADEFSTACK
//
// The best way to explain this wrapper is with an example. This
// function, 'table_removevalue', takes a table and a 'removethis'
// value. It searches the table for any (key,val) pairs
// where val==removethis, and it removes those pairs.
//
// int table_removevalue(lua_State *L) {
// LuaArg table, removethis;
// LuaRet returnvalue;
// LuaVar key, val;
// LuaDefStack LS(L, table, removethis, returnvalue, key, val);
// LS.set(key, LuaNil);
// while (LS.next(table, key, val)) {
// if (LS.rawequal(val, removethis)) {
// LS.rawset(table, key, LuaNil);
// }
// }
// LS.set(returnvalue, table);
// return LS.result();
// }
//
// Now I'll explain this function. First, you have some declarations for
// LuaArg (lua function arguments), LuaRet (lua function return values), and
// LuaVar (lua local variables). These are small structs each containing
// an 'int' indicating an absolute position on the lua stack. These are all
// collectively called lua stack slots. Class LuaArg, LuaRet, and
// LuaVar all derive from a base class LuaSlot.
//
// At construction time, all the LuaSlots are initialized to zero, which
// means they aren't ready to use yet. They don't point to anywhere on the
// lua stack. Remember, lua numbers everything starting at 1, so zero is
// not a valid lua stack position.
//
// After the LuaSlot declarations, you have the LuaDefStack constructor.
// This takes the lua stack as a parameter, and then all the LuaSlots.
// This allocates space on the lua stack for all the LuaSlots. When it's
// done, the lua stack will contain exactly five values, one for each of the
// five LuaSlots. The LuaSlot structs will contain the stack positions of
// these values. The LuaRet and LuaVar stack slots will be initialized to nil.
// The LuaArg stack slots will be initialized with the function arguments.
// After the LuaDefStack constructor, you are ready to do lua calculations.
//
// Next, you have the loop that iterates over the table. To iterate over
// a table in lua, you initialize 'key' to nil, then you call the 'next'
// operator to get the next (key,val) pair. The 'next' operator will return
// false if there is no next pair. For each (key,val) pair in the table,
// we check if the val is equal to the thing we want to remove, and if so,
// we change the table entry to nil. After the loop, we set the returnvalue
// slot to the table that was passed in.
//
// The last line, return LS.result(), is a piece of boilerplate, every lua
// C function must end with this. This function removes everything but the
// return values from the stack, and returns the number of return values.
//
//
/////////////////////////////////////////////////////////
//
// CLASS LUAEXTSTACK.
//
// Class LuaDefStack, which I showed above, is meant to be used at the
// top level of a C++ lua function.
//
// Class LuaExtStack is meant to be used when you want to allocate
// some MORE stack slots halfway through a C++ lua function.
// Class LuaExtStack is particularly useful when you want to write
// a recursive implementation. Typically you would put LuaDefStack
// in the top-level function, and LuaExtStack in the recursive
// implementation.
//
// Class LuaExtStack differs in two ways: first of all, it doesn't allow
// LuaArg and LuaRet slots, only LuaVar. Second, it has a destructor that
// automatically puts the stack back the way it was when it was constructed.
//
// You might wonder why class LuaDefStack doesn't have a destructor that
// cleans up the lua stack. It's because of return values: it can't remove
// everything from the stack, because it has to leave the return values
// on the stack.
//
// I call these two classes the 'LuaStack' classes. When I say that
// the LuaStack classes do something, I mean that both LuaDefStack
// and LuaExtStack do that thing.
//
//
/////////////////////////////////////////////////////////
//
// THE LIBRARY OF BUILTIN OPERATORS
//
// The LuaStack classes provide a whole library of methods to operate
// on lua values. Roughly speaking, there is one function in LuaStack
// for every function in the raw lua API, and they are similarly named.
//
// However, the functions in the raw lua API push and pop values on the
// lua stack. The equivalent functions in LuaStack take inputs from
// LuaSlots, and store their outputs into other LuaSlots. Nothing is
// pushed or popped.
//
// To get the complete list, you will have to scroll down to class
// LuaCoreStack, below. All the prototypes are there, and they are all
// documented.
//
//
/////////////////////////////////////////////////////////
//
// AUTOMATIC TYPE CONVERSION
//
// In some cases, LuaStack can do automatic conversions of C++ values into
// lua values. This is supported in any function where it makes sense.
// One function that supports automatic conversion is 'LuaStack::set':
//
// LS.set(val1, val2);
//
// This is actually a copy operation that copies from one lua local variable to
// another. But using auto conversions, it can also be used to assign C++
// values to lua slots. These are all valid:
//
// LS.set(value, 1); // int and int64_t can be converted.
// LS.set(value, "banana"); // char*, string, and string_view can be converted.
// LS.set(value, true); // bool can be converted.
// LS.set(value, 2.39); // float and double can be converted.
// LS.set(value, LuaNil); // This special token stores nil.
//
// Automatic conversion is generally allowed in any context where it would
// make sense. For example, these are all valid:
//
// LS.rawget(value, mytable, 1);
// LS.rawget(value, mytable, "banana");
// LS.rawget(value, mytable, true);
// LS.rawget(value, mytable, 2.39);
// LS.rawget(value, mytable, LuaNil);
//
// There's also the 'LuaNewTable' constant. This is handled by the
// auto-conversion system, but it's not really a conversion:
//
// LS.set(value, LuaNewTable);
//
// This will cause a new table to be created, and stored in value.
//
//
/////////////////////////////////////////////////////////
//
// API INTEROPERABILITY
//
// This wrapper can intentionally be mixed with the standard, raw lua API.
// You still have an explicit handle to the lua stack, and you can get the
// stack addresses out of the LuaSlot variables using LuaSlot::index. So
// if there's some tricky thing you can't do with this wrapper, you can always
// fall back to the raw API for just the section of code that you need to.
//
// I also sometimes use the raw lua API for code that is doing truly
// unusual things, that aren't easy to do with this wrapper.
//
/////////////////////////////////////////////////////////
//
//
// ON RAWGET AND METAMETHODS
//
// The raw lua API provides functions like lua_gettab and lua_equal
// which automatically call metamethods. I do not think it is wise to
// habitually use functions like that, because some of the code we write
// is not executed in a protected context. That means that a metamethod
// that generates an error would bring down the whole system rather than
// just stopping a script thread. It also means that a metamethod could
// return an invalid piece of data, corrupting a system data structure
// rather than just a script data structure.
//
// Because there are so many contexts where it is just not safe to call
// metamethods, I've made it deliberately difficult to call metamethods.
// Our API includes 'rawget' which doesn't call metamethods,
// but it omits 'gettab' which does.
//
// If someday we actually need metamethod support, we can do that,
// but I'll have to write a safe wrapper for them. I know how to do that,
// but it's a lot of work and I'm not going to bother unless it's needed.
//
//
/////////////////////////////////////////////////////////
//
// LUA TABLE TYPES
//
// We have modified the lua interpreter to allow us to assign
// table subtypes to different tables. Most lua tables are
// marked as 'general' tables. If you create a table using the
// lua newtable operator, you'll get a general table.
//
// Aside from general tables, we have special table types for the
// lua registry, for lua global environment tables, for tangibles,
// for tangible metatables, and for classes. These tables get
// handled specially in various parts of the code.
//
//
/////////////////////////////////////////////////////////
//
// LIGHTUSERDATA AND CLASS LUATOKEN
//
// Before I tell you what tokens are, I'm going to tell you what problem
// I was trying to solve that led me to create tokens.
//
// I was trying to write a JSON to LUA converter. That's mostly
// straightforward. Json tables are very similar to lua tables.
// For example, consider this JSON:
//
// {
// "name": "John Smith",
// "alive": true,
// "address": {
// "city": "New York",
// "state": "NY",
// },
// "spouse": null
// }
//
// That converts very straightforwardly to a lua table:
//
// {
// name = "John Smith",
// alive = true,
// address = {
// city = "New York",
// state = "NY",
// },
// spouse = nil
// }
//
// There's only one problem here: that "spouse" line doesn't really work.
// Setting a key to nil in lua is the same as not setting that key at all.
// That's not correct. Instead of storing json null as lua nil,
// we could store json null as the string "null". But that would be make it
// impossible to parse and store the literal string "null". That's not correct
// either.
//
// Lua has a datatype called 'lightuserdata'. A lightuserdata holds an
// int64. That gives me an option: I can store json null as a
// lightuserdata. When we see this lightuserdata value, we would know
// we have a json null.
//
// So that finally brings me to what a "token" is. A token is a lightuserdata
// containing a short string encoded as a fixed-width base38 number. Tokens
// may only contain the characters a-z, 0-9, and underscore, and can be up to
// 12 characters long (since 38^12 fits in 64 bits). In effect, it's a short
// string, but it's
// a string that's distinguishable from a normal lua string. It doesn't have
// the same type as a lua string (it shows up as a lightuserdata).
// The purpose of tokens is to represent special unique values, like json null.
//
// To make working with tokens easy, I've created a C++ struct 'LuaToken'.
// It stores an int64. You can construct a LuaToken in two different ways:
//
// LuaToken(0x559D0F68151CB900)
// LuaToken("null")
//
// Those are equivalent. The second form is just as fast as the first,
// because of C++ constexpr magic. You can use our automatic type conversion
// system to automatically convert C++ LuaToken structs into lua lightuserdata
// values, like this:
//
// LS.set(value, LuaToken("null"))
//
// When our pretty-printer is printing out lua data structures, and it
// encounters a lightuserdata, it prints it out as a token, ie, as a short
// string, but using brackets instead of quotation marks.
//
/////////////////////////////////////////////////////////
//
// LUA CLASSES
//
// This module adds the concept of a 'class' to lua. The function
// LuaStack::makeclass creates a class. This function is exposed to lua.
// For example, you could create a lua class to manipulate deques:
//
// makeclass("deque")
//
// This creates a new table, which you will find in the global
// environment under the name 'deque'. The class is a table for
// storing functions related to deques. You can now add lua functions
// to the class:
//
// function deque.insertr(...)
// end
//
// If you call makeclass a second time with the same classname, this
// is a no-op. This is useful because if you have two sourcefiles that
// both add functions to class 'deque', they can both makeclass 'deque',
// and no conflict will occur.
//
// Class tables have a distinct LuaTableType: LUA_TT_CLASS. That
// way, it is easy to tell that the table is intended as a class.
// Class tables are treated uniquely by our engine in several ways.
//
// A class also has a field "__index" which points to itself.
// C.__index = C. That makes it possible to use the class as a
// metatable, and any attempt to look up a name in the table
// that fails will then attempt to look up that name in the class.
//
//
/////////////////////////////////////////////////////////
//
// LUADEFINE
//
// LuaDefine is a macro that defines a C function which is exposed to lua.
// In addition to actually defining the C function, it also creates a global
// registry of all such functions. The registry is used to actually
// know which functions to inject into lua.
//
// Here is an example of a typical LuaDefine:
//
// LuaDefine(table_removevalue, "table, removethis",
// "|This function removes a specified value from a table."
// "|",
// "|Iterates over all the (key, val) pairs in a table and"
// "|removes the ones where val == removethis."
// ) {
// LuaArg table, removethis;
// LuaRet returnvalue;
// LuaVar key, val;
// LuaDefStack LS(L, table, removethis, returnvalue, key, val);
// LS.set(key, LuaNil);
// while (LS.next(table, key, val)) {
// if (LS.rawequal(val, removethis)) {
// LS.rawset(table, key, LuaNil);
// }
// }
// LS.set(returnvalue, table);
// return LS.result();
// }
//
// So you might notice that this is the same example function from
// earlier, but this time with LuaDefine. If you omit the LuaDefine
// and write the function as it was shown earlier, you will get a
// function that can be called from C++, and which works fine when
// called from C++, but it won't be visible from lua.
//
// The example function above will show up in lua as 'table.removevalue'.
// This lua function name is derived straightforwardly from the C++
// function name.
//
// Note that both of the string parameters to LuaDefine are part of the
// function documentation, neither has any effect on how the lua function
// behaves. The function documentation goes into the registry and from
// there is accessible through the lua documentation system.
//
/////////////////////////////////////////////////////////
#ifndef LUASTACK_HPP
#define LUASTACK_HPP
@@ -1151,31 +788,13 @@ struct LuaCountArgs<LuaExtraArgs, Ts...> {
//
// LuaDefStack
//
// This version of LuaStack should only be used inside a LuaDefine. It can
// assign stack slots to LuaArg, LuaRet, LuaVar, and LuaExtraArgs. It
// arranges for the arguments to be in the LuaArg variables, and it arranges for
// the LuaRet variables to be returned. It also makes sure that the function
// has the correct number of arguments.
//
// At the end of the LuaDefine function, you're supposed to return LS.result().
// LS.result causes the allocated stack slots to be freed except for the LuaRet
// values, which have to stay on the stack in order to pass them back as return
// values. LS.result returns the number of LuaRet variables left on the stack.
//
// If you terminate a LuaDefine by calling lua_error or lua_yield, then
// obviously, you don't get a chance to call LS.result. That's not a problem.
// The lua interpreter will clean up after an error or yield.
//
// Implementation note: LuaDefStack doesn't have a destructor to deallocate
// stack slots. That's deliberate: you shouldn't expect this class to clean up
// its stack frame, because after all, it has to leave return values on the
// stack. It would be deceptive to put a destructor, which then doesn't
// actually clean up anyway. Better to just let it be known that this class
// doesn't clean up its stack frame.
// This version of LuaStack should only be used inside a
// LuaDefine. It handles the passing of arguments from lua
// to C++, and return values from C++ to lua. See the
// markdown for more information.
//
////////////////////////////////////////////////////////////////////
class LuaDefStack : public LuaCoreStack {
private:
int nret_;
@@ -1242,15 +861,7 @@ private:
// This version of LuaStack is meant to be used in any context where
// you want to assign stack slots to some LuaVars, and then you want
// to automatically deallocate those LuaVars when the LuaExtStack
// goes out of scope.
//
// Unlike LuaDefStack, this version of LuaStack is meant to fully
// deallocate its stack frame when it goes out of scope, so it does
// have a destructor to do that. There is a special case in the
// destructor: if lua is throwing an error, the destructor leaves
// the stack alone, in order to preserve the error message that's
// on the stack. After an error throw, the lua interpreter will
// clean up the stack.
// goes out of scope. See the markdown for more information.
//
////////////////////////////////////////////////////////////////////