At this point, most functions have been LuaStack refactored

This commit is contained in:
2023-04-07 14:20:45 -04:00
parent 44c5a56172
commit 4f0713c9cb
9 changed files with 240 additions and 161 deletions

View File

@@ -1,28 +1,28 @@
/////////////////////////////////////////////////////////
//
//
// LuaOldStack
// LuaStack
//
// The standard lua C API asks you to work with a stack machine. You're supposed
// to manually push and pop values on the lua stack. I find this difficult, I
// find it hard to remember what stack position contains what value.
//
// To make it easier, I've created this module, "LuaOldStack." This module
// creates the illusion that you're working with local variables that contain
// lua values.
// To make it easier, I've created this module, "LuaStack." This module creates
// the illusion that you're working with local variables that contain lua
// values.
//
// Of course, this is all using the lua stack under the covers. Lua
// local variables are actually just lua stack addresses. But that's
// all kept fairly well hidden. When you use Lua local variables, and
// the accessors inside class LuaOldStack, it appears that you're
// manipulating data using local variables instead of using a stack.
// For people like me, that's easier to think about.
// Of course, this is all using the lua stack under the covers. Lua local
// variables are actually just lua stack addresses. But that's all kept fairly
// well hidden. When you use Lua local variables, and the accessors inside
// class LuaStack, it appears that you're manipulating data using local
// variables instead of using a stack. For people like me, that's easier to
// think about.
//
// Here's an example.
//
// let's say you have a function that takes two arguments
// ARG1 and ARG2, has a single return value RET1, and needs two local
// variables LOC1 and LOC2. We would declare it like this:
// let's say you have a function that takes two arguments ARG1 and ARG2, has a
// single return value RET1, and needs two local variables LOC1 and LOC2. We
// would declare it like this:
//
// int myfunc(lua_State *L) {
//
@@ -31,128 +31,117 @@
// LuaVar loc1, loc2, loc3; // Declare local variables for other purposes.
//
// // Assign every local var a stack index.
// LuaOldStack LS(L, arg1, arg2, ret1, loc1, loc2, loc3);
// LuaDefStack LS(L, arg1, arg2, ret1, loc1, loc2, loc3);
//
// // manipulate the data in the lua local variables...
// LS.rawget(loc1, arg1, arg2);
// ... etc ...
// }
//
// Class LuaArg, LuaRet, and LuaVar are all lua local variables.
// The LuaOldStack constructor assigns each one of them a position on
// the lua stack. It also makes sure that the arguments are in
// the LuaArg variables, and it makes sure that the LuaRet values
// are the only thing left on the stack at return time.
// Class LuaArg, LuaRet, and LuaVar are all lua local variables. The LuaDefStack
// constructor assigns each one of them a position on the lua stack. It also
// makes sure that the arguments are in the LuaArg variables, and it makes sure
// that the LuaRet values are the only thing left on the stack at return time.
//
// Class LuaOldStack provides a complete catalog of accessors
// like 'rawget' - roughly speaking, it provides equivalents to
// every major accessor in the lua API. However, the accessors
// provided by LuaOldStack take input and output from lua locals, not
// from the stack. For example, consider this:
// Class LuaDefStack provides a complete catalog of accessors like 'rawget' -
// roughly speaking, it provides equivalents to every major accessor in the lua
// API. However, the accessors provided by LuaDefStack take input and output
// from lua locals, not from the stack. For example, consider this:
//
// LS.rawget(value, tab, key);
//
// In the above, value, tab, and key should be lua local variables.
// This does a rawget on 'table', with the specified 'key', and
// stores the result in 'value'. Nothing is added to or removed
// from the lua stack. In general, none of the accessors in class
// LuaOldStack add anything to the stack, or pop anything from the
// stack.
// In the above, value, tab, and key should be lua local variables. This does a
// rawget on 'table', with the specified 'key', and stores the result in
// 'value'. Nothing is added to or removed from the lua stack. In general,
// none of the accessors in class LuaDefStack add anything to the stack, or pop
// anything from the stack.
//
// Class LuaOldStack can also do automatic type conversions. For
// example, suppose you do this:
// Class LuaDefStack can also do automatic type conversions. For example,
// suppose you do this:
//
// LS.rawget(value, tab, key);
//
// Nominally, you would expect value, tab, and key to be lua local
// variables. But if you pass a eng::string for key, then LuaOldStack will
// automatically convert it. In general, class LuaOldStack can
// convert lua_Integer, lua_Number, eng::string, bool, and LuaNil.
// Nominally, you would expect value, tab, and key to be lua local variables.
// But if you pass a eng::string for key, then LuaDefStack will automatically
// convert it.
//
// On output, LuaOldStack can convert lua_Integers, lua_Numbers, and
// eng::strings. In this case, strict type checking is done. If
// there is a type mismatch, a lua error is thrown.
// On output, LuaDefStack can convert lua_Integers, lua_Numbers, and
// eng::strings. In this case, strict type checking is done. If there is a
// type mismatch, a lua error is thrown.
//
// You can use the operator 'set' to assign a value to a lua local
// variable:
// You can use the operator 'set' to assign a value to a lua local variable:
//
// LS.set(val1, val2);
//
// This is actually a copy operation that copies from one lua local
// variable to another. But using type conversions, it can also be
// used to assign arbitrary values to lua local variables, or to
// get values from lua local variables.
//
// Passing LuaNewTable as an input will cause a new table to be
// created before calling the specified operation.
// This is actually a copy operation that copies from one lua local variable to
// another. But using type conversions, it can also be used to assign arbitrary
// values to lua local variables, or to get values from lua local variables.
//
// Passing LuaNewTable as an input will cause a new table to be created before
// calling the specified operation.
//
//
/////////////////////////////////////////////////////////
//
//
// LuaOldStack type checking
// LuaDefStack type checking
//
// LuaOldStack contains accessors for type checking. These include:
// LuaDefStack contains accessors for type checking. These include:
//
// bool LuaOldStack::isnumber(LuaSlot s)
// bool LuaOldStack::isinteger(LuaSlot s)
// bool LuaOldStack::isstring(LuaSlot s)
// bool LuaDefStack::isnumber(LuaSlot s)
// bool LuaDefStack::isinteger(LuaSlot s)
// bool LuaDefStack::isstring(LuaSlot s)
// etc...
//
// And it also contains operations that throw errors:
//
// void LuaOldStack::checknumber(LuaSlot s)
// void LuaOldStack::checkinteger(LuaSlot s)
// void LuaOldStack::checkstring(LuaSlot s)
// void LuaDefStack::checknumber(LuaSlot s)
// void LuaDefStack::checkinteger(LuaSlot s)
// void LuaDefStack::checkstring(LuaSlot s)
// etc...
//
// These are different from the lua builtins in that they are strict.
// For example, 'isnumber' only returns true if the value in the
// lua local variable is already a number. No conversions are done.
// These are different from the lua builtins in that they are strict. For
// example, 'isnumber' only returns true if the value in the lua local variable
// is already a number. No conversions are done.
//
// These functions do checking and also conversion at the same time:
//
// lua_Integer LuaOldStack::ckinteger(LuaSlot s)
// lua_Number LuaOldStack::cknumber(LuaSlot s)
// eng::string LuaOldStack::ckstring(LuaSlot s)
// lua_State *LuaOldStack::ckthread(LuaSlot s)
// lua_Integer LuaDefStack::ckinteger(LuaSlot s)
// lua_Number LuaDefStack::cknumber(LuaSlot s)
// eng::string LuaDefStack::ckstring(LuaSlot s)
// lua_State *LuaDefStack::ckthread(LuaSlot s)
//
// Like the other operations, they are strict.
//
//
// LUADEFINE
//
// LuaDefine is a macro that defines a C function which is
// exposed to lua. It creates a global registry of functions
// created with LuaDefine. You use it like so:
// LuaDefine is a macro that defines a C function which is exposed to lua. It
// creates a global registry of functions created with LuaDefine. You use it
// like so:
//
// LuaDefine(function_name, "arguments", "documentation") {
// ...
// }
//
// This macroexpands into a function definition and a function
// registration. The function definition looks like this:
// This macroexpands into a function definition and a function registration.
// The function definition looks like this:
//
// int function_name(lua_State *L) {
// ...
// }
//
// The macro expansion generates this function definition, but it
// also generates a "registration object" whose constructor puts
// this function into a global registry of lua-callable C functions.
// This global registry is later used to inject these C functions
// into the lua intepreter. The mode is a string that contain
// the following characters:
//
// c - create a class, and put a function into it.
// f - create a global function not inside a class.
// The macro expansion generates this function definition, but it also generates
// a "registration object" whose constructor puts this function into a global
// registry of lua-callable C functions. This global registry is later used to
// inject these C functions into the lua intepreter.
//
//
/////////////////////////////////////////////////////////
#ifndef LuaOldStack_HPP
#define LuaOldStack_HPP
#ifndef LUASTACK_HPP
#define LUASTACK_HPP
#include "wrap-string.hpp"
#include "wrap-set.hpp"
@@ -272,12 +261,22 @@ public:
eng::string str() const;
};
////////////////////////////////////////////////////////////////////
//
// LuaCoreStack
//
// This class is not meant to be used directly: it doesn't contain any code to
// allocate a stack frame on the lua stack and allocate slots within the frame
// to lua local variables. That code is in derived classes, below. The only
// time this class is used directly is in the extremely rare case that you want
// to manually allocate stack slots yourself.
//
////////////////////////////////////////////////////////////////////
class LuaCoreStack : public eng::nevernew {
protected:
lua_State *L_;
LuaCoreStack(lua_State *L) : L_(L) {}
private:
// Push any value on the stack, by type.
void push_any_value(LuaNewTableMarker s) const { lua_newtable(L_); }
@@ -306,6 +305,8 @@ private:
void argerr(const char *arg, const char *tp) const;
public:
LuaCoreStack(lua_State *L) : L_(L) {}
lua_State *state() const { return L_; }
// This is the largest integer that can be stored in a lua_Number.
@@ -479,6 +480,17 @@ public:
static bool int64_storable(int64_t v) { return (v <= MAXINT) && (v >= -MAXINT); }
};
////////////////////////////////////////////////////////////////////
//
// LuaOldStack
//
// This class is deprecated, we're phasing it out. This was the first piece of
// code we wrote to allocate stack slots, and it has issues. It is
// still in use in the difference transmitter and the source database,
// and a few other small spots.
//
////////////////////////////////////////////////////////////////////
class LuaOldStack : public LuaCoreStack {
private:
int narg_;
@@ -578,6 +590,33 @@ public:
~LuaOldStack() {};
};
////////////////////////////////////////////////////////////////////
//
// LuaDefStack
//
// This version of LuaStack is meant to be used at the top level of a LuaDefine.
// It can assign stack slots to LuaArg, LuaRet, and LuaVar locals. 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.
//
////////////////////////////////////////////////////////////////////
class LuaDefStack : public LuaCoreStack {
private:
@@ -651,6 +690,26 @@ public:
~LuaDefStack() { }
};
////////////////////////////////////////////////////////////////////
//
// LuaExtStack
//
// 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.
//
////////////////////////////////////////////////////////////////////
class LuaExtStack : public LuaCoreStack {
private:
int oldtop_;
@@ -700,14 +759,28 @@ public:
if (!lua_isthrowing(L_)) {
lua_settop(L_, oldtop_);
}
}
void forcediscard() {
lua_settop(L_, oldtop_);
}
};
////////////////////////////////////////////////////////////////////
//
// LuaKeywordParser
//
// This is a helper class to help parse tables full of keywords.
// It is meant to make it easier to write LuaDefine functions that
// accept keyword arguments. It helps with the following tasks:
//
// * It makes sure the keyword table actually is a table.
//
// * It makes sure that you didn't put an unrecognized keyword
// into the keyword table. Unrecognized keywords are defined
// as keywords that are never checked using 'parse'.
//
// * It makes sure that you didn't put anything that isn't a
// keyword into the keyword table.
//
////////////////////////////////////////////////////////////////////
class LuaKeywordParser {
struct cmp_char {
bool operator () (const char *s1, const char *s2) const {
@@ -742,6 +815,12 @@ public:
lua_State *state() const { return L_; }
};
////////////////////////////////////////////////////////////////////
//
// The Lua Constant Registry
//
////////////////////////////////////////////////////////////////////
class LuaConstantReg : public eng::nevernew {
private:
const char *name_;
@@ -761,6 +840,12 @@ public:
LuaConstantReg *next() const { return next_; }
};
////////////////////////////////////////////////////////////////////
//
// The Lua Function Registry
//
////////////////////////////////////////////////////////////////////
class LuaFunctionReg : public eng::nevernew {
private:
const char *name_;
@@ -784,6 +869,12 @@ public:
void set_func(lua_CFunction fn) { func_ = fn; }
};
////////////////////////////////////////////////////////////////////
//
// LuaDefine and friends.
//
////////////////////////////////////////////////////////////////////
#define LuaTokenConstant(name, tvalue, docs) \
LuaToken ltoken_##name(tvalue); \
LuaConstantReg reg_##name(#name, docs, LuaToken(tvalue), 0);
@@ -812,4 +903,4 @@ public:
#define LuaAssert(L, x) if (!(x)) { luaL_error((L), "Assert failed: %s (file %s line %d)", LuaStringify(x), __FILE__, __LINE__); }
#define LuaAssertStrEq(L, x, y) { eng::string _s1_(x); eng::string _s2_(y); if (_s1_ != _s2_) luaL_error((L), "Assert failed: value=%s (file %s line %d)", _s1_.c_str(), __FILE__, __LINE__); }
#endif // LuaOldStack_HPP
#endif // LUASTACK_HPP