Lots of work on the LuaStack documentation
This commit is contained in:
@@ -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) {
|
||||
@@ -476,24 +528,23 @@ int table_equal(lua_State *L) {
|
||||
```
|
||||
|
||||
The C prototype is always the same: the return value is
|
||||
always *int,* and the only argument is *lua_State*L*, a
|
||||
always *int,* and the only argument is *lua_State *L*, a
|
||||
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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
Reference in New Issue
Block a user