State
State
is the owner of the Lua state. This is a standalone type, practically unrelated to the rest of the library. In addition to controlling the creation and destruction of the state, it provides only the means to execute files, strings, and Lua-compatible functions. Errors that occur during their work are converted to exceptions.LFunction
LFunction
, functions of a special format, compatible with Lua API ++. This is an analogue of Lua-compatible functions, which in turn was given the name CFunction
. The special format of the function was needed mainly in order to let in our next character:Context
Context
is the context of the function, as well as the access center to all Lua features. With it, you can access global variables, function arguments, the registry, and upvalues . You can manage the garbage collector, signal errors, pass multiple return values, and create closures. Simply put, everything that is not directly related to operations on values that are the prerogative of our closing is done through Context
.Value
class, of course, exists). First of all, it is connected with the policy of “open borders”, which allows free migration of native values in Lua and vice versa. Wherever Lua values are expected, you can substitute the native values of the supported types and they will automatically “move” to the Lua stack. Operators of implicit type conversion will help to move the values in the opposite direction, and in case of incompatibility of the real and expected type, they will notify us with an exception.Valref
Valref
does not Valref
with placing or deleting values on the stack, but focuses solely on value operations. In the Lua API ++ Valref
serves as a model that follows an interface of other types representing values.Valref
interface. Using them is easy, simple and convenient, but make a mistake, and the compiler will “delight” you with a voluminous work full of angle brackets.Value
is a universal “anchor” for one value, Table
specialized for tables, and Valset
stores several values.State
State
has a default constructor that performs all the actions necessary to initialize the context. An alternative constructor allows you to use a custom memory management function . You can query the “raw” pointer to the state object used in the Lua API by getRawState
function.runFile
, runString
and call
functions that allow you to make the simplest interpreter: #include <iostream> #include <luapp/lua.hpp> using namespace std; using namespace lua; void interpretLine(State& state, const string& line) { try { state.runString(line); // } catch(std::exception& e) { // cerr << e.what() << endl; } } void interpretStream(State& state, istream& in) { string currentLine; while(!in.eof()) { // getline(in, currentLine); interpretLine(state, currentLine); } } int main() { State state; interpretStream(state, cin); }
Table
not from a table, or those that need to be (possibly) intercepted in user code , like type conversion errors. The library does not attempt to diagnose in advance those errors that may appear when calling the Lua API. Therefore, an attempt, for example, to use a function call on a value that is actually a number, will not cause an exception. It will be detected inside the lua_call
call and will cause a Lua-style error (abort execution and return to the closest point of the secure call).LFunction
Retval myFunc(Context& c);
Context
, and Retval
is a special type that helps to conveniently return an arbitrary number of values through the function Context::ret
.mkcf
template allows mkcf
to make from LFunction
what Lua will make friends with: int (*myCfunc)(lua_State*) = mkcf<myFunc>;
mkcf
will create a separate wrapper function in each case.Context
object, pass it to our function, and upon completion of the work, return the values returned via Retval
to Lua. All exceptions exceeding the limits of the wrapped function will be intercepted and converted to a Lua error. Retval retSelf(Context& c) { return c.ret(retSelf, mkcf<retSelf>); // , - }
Context
Context
. I will not reject hints of an obvious resemblance to the god object , but in this case, this decision is dictated by the architecture of the Lua API. Through Context
you can manage the garbage collector, you can find out the version number and the number of values placed on the stack. It is implicitly converted to lua_State*
in case you need to conjure the Lua API directly. On the same case, a magic word (more precisely, a static constant of the signal type) is provided initializeExplicitly
, which allows you to create a Context
explicitly, outside of the LFunction
.LFunction
requires you to return Retval
, which can only be created by Retval
the Context
method with the modest name ret
. This is a special function: after its call, work with the stack stops, so as not to throw off our values from it, therefore, it should be used only directly in the return statement . In a ret
call, you can list as many return values as you need. return ctx.ret(1, "two", three);
lua_pushinteger(ctx, 1); lua_pushstring(ctx, "two"); lua_pushvalue(ctx, three); return 3;
Retval
is to access the ret
function, I did not sin against the truth, but there is one nuance ... From a formal point of view, there is also an error
function, which also returns this type. Only in fact, Retval
does not reach the creation of the Retval
, because no return occurs from this function. The maximum you can count on is to pass your message to the Lua error handling mechanism. The Lua API documentation recommends using the lua_error
call in the return statement to indicate the fact that the execution of the function is interrupted during the call. The same approach is applied in the Lua API ++, that is why the error
declared as returning Retval
.where
function, which creates a string describing the current function. The same value is used if the message is not specified at all. if(verbose) return ctx.error(ctx.where() & " intentional error " & 42); else return ctx.error(); // , return ctx.error(ctx.where());
if(verbose) { luaL_where(ctx, 0); lua_pushstring(ctx, " intentional error "); lua_pushinteger(ctx, 42); lua_concat(ctx, 3); return lua_error(ctx); } else { luaL_where(ctx, 0); return lua_error(ctx); }
Context
is obviously the primary source of the values. In fact, where else would they come from?Context
class, allowing us to reach various interesting places of the environment. All of them allow both reading and writing values.args
, function arguments. Unlike other objects of access, for each of which a special type inaccessible to the user was created, here the usual constant Valset
. Its constancy means only that we cannot change its size, but to rewrite the values of the arguments for health. Since Valset was created as an STL-compatible container, the numbering of elements in it starts from 0. In other cases, the library follows the rules of Lua and implies that indexing starts from 1. if(ctx.args.size() > 1 && ctx.args[0].is<string>()) {...};
if(nArgs > 1 && lua_isstring(ctx, 1) ) {...};
global
object is indexed by strings. ctx.global["answer"] = 42; // ,
lua_pushinteger(ctx, 42); lua_setglobal(ctx, "answer");
upvalues
with an integer index (starting with 1, everything is correct). There is no way to find out the number of stored values: it is assumed that this is already known.registry
, is used in two ways. For string keys, metatables for user data are stored there. Integer keys are used when using the registry as a repository of values. The key is created by calling registry.store
and is subsequently used to read and write to the registry
, erasing the value and releasing the key occurs when writing nil
. auto k = ctx.registry.store(ctx.upvalues[1]); // decltype(k) == int ctx.registry [k] = nil; // k store
lua_pushvalue(ctx, lua_upvalueindex(1)); auto k = luaL_ref(ctx, LUA_REGISTRYINDEX); luaL_unref(ctx, LUA_REGISTRYINDEX, k);
Context
object, the closure
function is used for this, which receives the CFunction
and the values that will be stored in the closure. The result is a temporary object, that is, a full Lua value.CFunction
we can specify LFunction
at once, but this lightness has its price. In the resulting closure, the first upvalue will be reserved (the address of the function is stored there, since the wrapper is the same for any LFunction). The same function is used for transparent migration of LFunction
with the same consequences. This is different from the mkcf
template, which does not reserve anything, but it creates a separate wrapper function for each function.chunk
method, and the contents of the file using load
. For the cases of “done and forgotten” there is a runString
and runFile
, exactly the same as in State
. In terms of using a chunk, this is a common function.wrap
method. It automatically creates a wrapper that takes the arguments from the Lua stack, converts them to the values accepted by our function, makes the call and places the result on the Lua stack as the return value. By default, this works with all supported types, including user data. And if this is not enough (for example, we need to get something up with strings stored in a vector
, then we can also specify the conversion to one side or the other using special macros. wrap
works when implicit migration functions. The fraternal vwrap
method does almost everything the same, only ignores the return value of the function being wrapped. Numeric |
---|
int |
unsigned int |
long long |
unsigned long long |
float |
double |
String |
const char* |
std::string |
Functions |
CFunction: int (*) (lua_State*) |
LFunction: Retval (*) (Context&) |
Arbitrary functions |
Member functions |
miscellanea |
Nil |
bool |
LightUserData: void* |
registered user types |
Nil
and “wrapped” functions, which remain pointers to wrappers). cast
template function. If the Lua value contains data that cannot be converted to what we are trying to, an exception will be thrown. The optcast
function instead of exceptions will return a “spare” value. int a = val; auto b = val.cast<int>(); auto c = val.optcast<int>(42);
if(!lua_isnumber(ctx, valIdx)){ lua_pushstring(ctx, "Conversion error"); return lua_error(ctx); } int a = lua_tointeger(ctx, valIdx); if(!lua_isnumber(ctx, valIdx)){ lua_pushstring(ctx, "Conversion error"); return lua_error(ctx); } auto b = lua_tointeger(ctx, valIdx); auto c = lua_isnumber(ctx, valIdx) ? lua_tointeger(ctx, valIdx) : 42;
type
to find out the type of the stored value directly. if(val.is<double>()) ...; if(val.type() == ValueTypes::Number) ...;
=
sign, they will give a tricky error. But other temporary values, such as indexing or metatable, assignment is fully admissible. According to the meaning of the action performed, it is easy to guess what can be appropriated and what cannot. mt
method gives access to the metatable of a value that can be read and written. { Table mt = val.mt(); val.mt() = nil; }
if(!lua_getmetatable(ctx, valIdx)){ lua_pushstring(ctx, "The value has no metatable"); return lua_error(ctx); } int mtIdx = lua_gettop(ctx); lua_pushnil(ctx); lua_setmetatable(ctx, valIdx); lua_pop(ctx, 1);
len
function differs in different versions of Lua: in compatibility mode with 5.1, it returns the native size_t
, and in mode 5.2, a temporary value. void myFunc(); Retval example(Context& c) { Table t(c); t[myFunc] = 42; // myFunc ... assert(t[myFunc].is<Nil>()); // - , . t[mkcf<example>] = 42.42; // CFunction, "" assert(t[mkcf<example>] == 42.42); }
void myFunc(); int wrapped_void_void(lua_State* s) { if(!lua_islightuserdata(ctx, lua_upvalueindex(1))) { lua_pushstring(ctx, "Conversion error"); return lua_error(ctx); } void (*fptr) () = reinterpret_cast<void(*)()>(lua_touserdata(ctx, lua_upvalueindex(1))); fptr(); return 0; } int mkcf_myFunc(lua_State* s) { myFunc(); return 0; } int example(lua_State* ctx) { lua_createtable(ctx, 0, 0); int t = lua_gettop(ctx); lua_pushlightuserdata(ctx, reinterpret_cast<void*>(&myFunc)); lua_pushcclosure(ctx, &wrapped_void_void, 1); lua_pushinteger(ctx, 42); lua_settable(ctx, t); lua_pushlightuserdata(ctx, reinterpret_cast<void*>(&myFunc)); lua_pushcclosure(ctx, &wrapped_void_void, 1); lua_gettable(ctx, t); assert(lua_isnil(ctx, -1)); lua_pushcfunction(ctx, &mkcf_myFunc); lua_pushnumber(ctx, 42.42); lua_settable(ctx, t); lua_pushcfunction(ctx, &mkcf_myFunc); lua_gettable(ctx, t); lua_pushnumber(ctx, 42.42); int result = lua_compare(ctx, -1, -2, LUA_OPEQ); lua_pop(ctx, 2); assert(result == 1); }
call
, , - . pcall
. int x = fn(1); int y = fn.call(1); // int z = fn.pcall(1); // ,
function mrv() return 2, 3, 4; end
mrv();
lua_pushvalue(ctx, mrvIdx); lua_call(ctx, 0, 0);
nil
, ), . Value x = mrv(); // x == 2
lua_pushvalue(ctx, mrvIdx); lua_call(ctx, 0, 1); int x = lua_gettop(ctx);
print(1, mrv(), 5); // 1 2 3 4 5
lua_pushvalue(ctx, printIdx); int oldtop = lua_gettop(ctx); lua_pushinteger(ctx, 1); lua_pushvalue(ctx, mrvIdx); lua_call(ctx, 0, LUA_MULTRET); lua_pushinteger(ctx, 5); int nArgs = lua_gettop(ctx) - oldtop; lua_call(ctx, nArgs, 0);
Valset
, . Valset vs = mrv.pcall(); // vs.size() == 3, vs.success() == true
int vsBegin = lua_gettop(ctx) + 1; lua_pushvalue(ctx, mrvIdx); bool vsSuccess = lua_pcall(ctx, 0, LUA_MULTRET) == LUA_OK; int vsSize = lua_gettop(ctx) + 1 - vsBegin;
Valset
, ( ). . , Valset
, . print(1, vs, 5); // 1 2 3 4 5
lua_pushvalue(ctx, printIdx); int oldTop = lua_gettop(ctx); lua_pushInteger(ctx, 1); lua_pushvalue(ctx, mrvIdx); for(auto i = 0; i < vsSize; ++i) lua_pushvalue(ctx, vsBegin + i); lua_pushinteger(ctx, 5); int nArgs = lua_gettop(ctx) - oldtop; lua_call(ctx, nArgs, 0);
Valset
. STL- STL, «» Valref
. Valset
, push_back
pop_back
. Valref
, ( Valset
), . , . string s = "The answer to question " & val & " is " & 42;
lua_pushstring(ctx, "The answer to question "); lua_pushvalue(ctx, valIdx); lua_pushstring(ctx, " is "); lua_pushinteger(ctx, 42); lua_concat(ctx, 4); string s = lua_tostring(ctx, -1); lua_pop(ctx, 1);
Valset
.^
.Table
, Valref
. , , , . raw
, , , iterate
, for_each
. , iterate
( , , ), -. Valref
true
, false
, . : Table t = ctx.global["myTable"]; t.iterate([] (Valref k, Valref v) { cout << int(k) << int(v); });
lua_getglobal(ctx, "myTable"); if(!lua_istable(ctx, -1)){ lua_pushstring(ctx, "Conversion error"); return lua_error(ctx); } int t = lua_gettop(ctx); lua_pushnil(ctx); size_t visited = 0; while(lua_next(ctx, t)) { ++ visited; if(!lua_isnumber(ctx, -2) || !lua_isnumber(ctx, -1)){ lua_pushstring(ctx, "Conversion error"); return lua_error(ctx); } cout << lua_tointeger(ctx, -2) << lua_tointeger(ctx, -1); lua_pop(ctx, 1); }
iterate
.Table
— array
records
. . fn(Table::array(ctx, "one", 42, Table::array(ctx, 1, 2, 3))); // ? !
lua_pushvalue(ctx, fn); lua_createtable(ctx, 3, 0); lua_pushinteger(ctx, 1); lua_pushstring(ctx, "one"); lua_settable(ctx, -3); lua_pushinteger(ctx, 2); lua_pushinteger(ctx, 42); lua_settable(ctx, -3); lua_pushinteger(ctx, 3); lua_createtable(ctx, 3, 0); lua_pushinteger(ctx, 1); lua_pushinteger(ctx, 1); lua_settable(ctx, -3); lua_pushinteger(ctx, 2); lua_pushinteger(ctx, 2); lua_settable(ctx, -3); lua_pushinteger(ctx, 3); lua_pushinteger(ctx, 3); lua_settable(ctx, -3); lua_settable(ctx, -3); lua_call(ctx, 1, 0);
array
, 1. , Valset
.records
, -. . x.mt() = Table::records(ctx, "__index", xRead, "__newindex", xWrite, "__gc", xDestroy );
lua_createtable(ctx, 0, 3); lua_pushstring(ctx, "__index"); lua_pushlightuserdata(ctx, reinterpret_cast<void*>(&xRead)); lua_pushcclosure(ctx, &wrapped_signature_1, 1); lua_settable(ctx, -3); lua_pushstring(ctx, "__newindex"); lua_pushlightuserdata(ctx, reinterpret_cast<void*>(&xWrite)); lua_pushcclosure(ctx, &wrapped_signature_2, 1); lua_settable(ctx, -3); lua_pushstring(ctx, "__gc"); lua_pushlightuserdata(ctx, reinterpret_cast<void*>(&xDestroy)); lua_pushcclosure(ctx, &wrapped_signature_3, 1); lua_settable(ctx, -3); lua_setmetatable(ctx, x);
cast
, .LUAPP_USERDATA
. , , . , registry
-, : LUAPP_USERDATA(MyType, "MyType Lua ID") Retval setup(Context& ctx) { ctx.mt<MyType>() = Table::records(ctx); // , ctx.registry["MyType Lua ID"] }
#include <vector> using dvec = std::vector<double>; // LUAPP_USERDATA(dvec, "Number array") // dvec aCreate(size_t size) // . { // - . return dvec(size); // RVO } void aDestroy(dvec& self) // - . { self.~dvec(); } void aWrite(dvec& self, size_t index, double val) // __newindex { self.at(index) = val; // at, Lua } Retval setup(Context& c) { // c.mt<dvec>() = Table::records(c, // "__index", static_cast<double& (dvec::*)(size_t)> (&dvec::at), // at // (const -const), "__newindex", aWrite, "__len", dvec::size, // size vector , "__gc", aDestroy ); c.global["createArray"] = aCreate; // return c.ret(); }
inline
, Lua API , , Link time code generation (LTO GCC). header-only. inline
Lua.Source: https://habr.com/ru/post/201860/
All Articles