adapt is a mechanism that
takes a user function with a complex signature and adapts it to be pushed
to lua.
It is used via the macro PRIMER_ADAPT.
You can think of this (roughly) as a function which takes a function pointer,
and yields a new function pointer of signature int
(lua_State
*). However, PRIMER_ADAPT
happens at compile-time, and the function pointer which you give it must
be a constant expression -- usually, just some specific, named function
in your program. You can't pass it an "unknown" function pointer
which you obtained at run-time as a function parameter or something.
lua C API:
int f(lua_State * L) { int i1 = luaL_checkinteger(L, 1); int i2 = luaL_checkinteger(L, 2); if (i1 == i2) { lua_pushinteger(L, i1); return 1; } else { lua_pushinteger(L, i2); lua_pushinteger(L, i1 % i2); return 2; } } void init(lua_State * L) { lua_pushcfunction(L, f); lua_setglobal(L, "f"); }
Using primer:
primer::result f(lua_State * L, int i1, int i2) { if (i1 == i2) { primer::push(L, i1); return 1; } else { primer::push(L, i2); primer::push(L, i1 % i2); return 2; } } void init(lua_State * L) { lua_pushcfunction(L, PRIMER_ADAPT(&f)); lua_setglobal(L, "f"); }
There are two things that adapt
accomplishes:
longjmp
and trashing objects on the stack without calling their destructors.
(This is only an issue if lua is compiled as C.)
adapt handles the first
one using primer::read. adapt
handles the second one using primer::result.
The core object of adapt
is a class template:
// adapt is what does the actual work. // The PRIMER_ADAPT macro provides a suitable interface to it template <typename T, T> class adapt;
When given a function pointer f,
adapt is expected to provide
a static member function of signature int
(lua_State
*) which is named adapted.
The PRIMER_ADAPT macro
is defined:
// Simplified interface to the helper structure template // (This seems to be necessary since we can't deduce non-type template parameter // types, at least prior to C++17.) // #define PRIMER_ADAPT(F) &::primer::adapt<decltype(F), (F)>::adapted
so that PRIMER_ADAPT can
be used to push complex functions directly to lua, e.g.:
lua_pushcfunction(L, PRIMER_ADAPT(&my_func));
Given a function pointer of the form,
primer::result my_func(lua_State * L, std::string foo, bool bar, int baz);
PRIMER_ADAPT(&my_func)
produces a function a somewhat similar to my_func_adapted below:
primer::result my_func_helper(lua_State * L) { auto arg1 = primer::read<std::string>(L, 1); if (!arg1) { return arg1.err(); } auto arg2 = primer::read<bool>(L, 2); if (!arg2) { return arg2.err(); } auto arg3 = primer::read<int>(L, 3); if (!arg3) { return arg3.err(); } return my_func(L, *arg1, *arg2, *arg3); } int my_func_adapted(lua_State * L) { if (auto r = my_func_helper(L)) { if (r->is_return_) { return r->n_; } else { return lua_yield(L, r->n_); } } else { lua_pushstring(L, r.err_c_str()); } return lua_error(L); }
Each parameter is read from the stack in succession using primer::read. If any cannot be read, then the
input is invalid and an error is returned. If all can be read, then the
user function is invoked, and its result returned.
![]() |
Note |
|---|---|
|
An important principle of
This is also important because lua values may be destroyed by the garbage
collector once they are removed from the stack. For instance, if your
callback takes a |
primer::result is implemented by a simple return in the case of a return signal, by
calling lua_yield in case
of a yield, and by raising a lua error in case of an error. The only tricky
part is making sure that it works with no leaks, whether lua is compiled
as C or C++. (Technically, the above example will create a small leak upon
lua_yield call. In the
actual code we do something different.)
![]() |
Caution |
|---|---|
|
When using the
If it does not, then a lua error triggers a call to
A similar situation occurs if you call
The |
primer::adapt also has a trivial specialization
for functions which are already lua_CFunction:
// Traditional "raw" C-style lua callbacks. // We don't have to do any work template <lua_CFunction target_func> class adapt<lua_CFunction, target_func> { public: static int adapted(lua_State * L) { return target_func(L); } };
If you would like to implement a custom parameter reading / error handling
mechanism, you can do that by introducing a new return type for such functions,
and specializing the adapt
class template for function pointers with your given return type.
Here's an example:
In this code, an exception "raise_lua_error" is defined, and
a try-catch block is setup for functions which return my_int.
Otherwise, it just defers to the regular adapt mechanism.
// This is a custom exception type, which is supposed to be handled by raising // a lua error with this error message. struct raise_lua_error : std::runtime_error { raise_lua_error(const std::string & str) : std::runtime_error(str) {} }; // To make our own partial specialization of adapt, we make our own return type, // separate from primer::result. Since we are throwing exceptions rather than // returning error objects, the return can just be an int basically. struct my_int { int value; }; // Now we specialize adapt within the primer namespace // In order to make it as simple as possible, we implement it by translating the // `my_int + exception` return signal to primer::result, and adapt that version // using PRIMER_ADAPT. // // Because PRIMER_ADAPT takes place at compile-time, this is all transparent to // the optimizer and all of this can be inlined. // // Of course, you could also implement it without reference to `primer::result` // at all, but you'd likely have to dig into the implementation details for the // argument parsing off of the stack, or reimplement that yourself. namespace primer { template <typename... Args, my_int (*target_func)(lua_State *, Args...)> class adapt<my_int (*)(lua_State *, Args...), target_func> { static primer::result adapt_target(lua_State * L, Args... args) { try { my_int r = target_func(L, std::forward<Args>(args)...); return r.value; } catch (raise_lua_error & e) { return primer::error{e.what()}; } } public: // This is the `output`, that is, the function pointer which we produce. static int adapted(lua_State * L) { return (PRIMER_ADAPT(&adapt_target))(L); } }; } // end namespace primer
With this specialization installed, any function passed to PRIMER_ADAPT with return value my_int gets the automatic argument parsing
feature, but it now can signal lua errors by throwing an exception derived
from raise_lua_error rather
than in the default way that Primer does it.
my_int reverse_palindrome(lua_State * L, std::string p) { auto it = p.begin(); auto it2 = p.end() - 1; while (it < it2) { if (*it != *it2) { throw raise_lua_error("not a palidrome"); } ++it; --it2; } if (it == it2) { std::string s{it, p.end()}; s += std::string{p.begin() + 1, it + 1}; primer::push(L, s); } else { std::string s{it, p.end()}; s += std::string{p.begin(), it}; primer::push(L, s); } return {1}; } void test_adapt_example() { lua_raii L; lua_CFunction f = PRIMER_ADAPT(&reverse_palindrome); lua_pushcfunction(L, f); lua_pushstring(L, "amanapanama"); TEST_LUA_OK(L, lua_pcall(L, 1, 1, 0)); TEST(lua_isstring(L, 1), "expected a string"); TEST_EQ(lua_tostring(L, 1), std::string{"panamamanap"}); lua_pushcfunction(L, f); lua_insert(L, 1); TEST_LUA_OK(L, lua_pcall(L, 1, 1, 0)); TEST(lua_isstring(L, 1), "expected a string"); TEST_EQ(lua_tostring(L, 1), std::string{"amanapanama"}); lua_pushcfunction(L, f); lua_pushstring(L, "abfxxxx"); TEST(LUA_OK != lua_pcall(L, 1, 1, 0), "expected failure"); }
Your functions using the new return type would then be automatically recognized
also by PRIMER_ADAPT_USERDATA
and USE_LUA_CALLBACK in
an api::base object, see later in the docs.
Another option for specialization is to use tag-dispatch to select different behaviors.