primer::read is a template function used to read
a C++ value from the lua stack.
lua C API:
if (lua_isinteger(L, 2)) { foo(lua_tointeger(L, 2)); } if (lua_isstring(L, 3)) { bar(lua_tostring(L, 3)); }
Using primer:
if (auto i = primer::read<int>(L, 2)) { foo(*i); } if (auto s = primer::read<std::string>(L, 3)) { bar(*s); }
template <typename T> expected<T> read(lua_State * L, int index);
read returns a value
of type T, or an error
message explaining why the value on the stack could not be converted
to T.
read is not permitted
to leave the stack in a different state than it was found.
You should always test the result of primer::read
for safety, and not assume that it will succeed in any given case. If the
result is in an error state, then operator
* will invoke undefined behavior.
The following core types are supported:
|
Type |
|
|---|---|
|
|
Checks |
|
|
Checks |
|
|
|
|
|
|
|
|
Checks |
|
|
|
|
|
|
|
|
Checks |
|
|
|
|
|
|
|
|
Checks |
|
|
In the above table, "Checks" means that, the checked function must report "true" or an error is reported.
![]() |
Note |
|---|---|
|
In the above table, when an integral type is read, if the target type
is narrower than
To avoid the overhead of overflow checks, consider using the type
Or, consider compiling lua using
Alternatively, see "customization" below and specialize reading
for |
![]() |
Caution |
|---|---|
|
In the above table, when a floating point type is read, if the target
type is narrower than
In most applications this is what you want. If this conversion is a concern
for you, consider specializing the |
The following extra types are supported, which allow "flexible" reads.
This is useful for writing functions which allow in their parameters certain implicit conversions typical of lua.
namespace primer { // Use this type if you want to push nil onto the stack using primer::push // interface. struct nil_t {}; // Use this type if you want primer to convert given type to a boolean, even if // it was not originally a boolean struct truthy { bool value; }; // Use this type if you want primer to convert given type to a string, even if // it was not originally a string struct stringy { std::string value; }; } // end namespace primer
|
Type |
|
|---|---|
|
|
Returns value of |
|
|
Returns value of |
|
|
Checks |
Primer includes additional headers to support some C++ standard containers and and boost containers, which tables may be converted to. See the containers section for details.
std::vector
std::array
std::set
std::map
std::unordered_map
boost::vector
Primer also supports the ability to read references to userdata types. (See userdata reference page.)
primer::read's implementation is quite simple
-- it delegates work to a type trait.
template <typename T> expected<T> read(lua_State * L, int index) { PRIMER_ASSERT_STACK_NEUTRAL(L); return ::primer::traits::read<T>::from_stack(L, index); }
This trait, named primer::traits::read,
can be specialized to override primer's behavior for certain types, or
to add new behavior for these custom types.
For example, suppose we have a simple vector type:
struct vec2i { int x; int y; };
Primer could be taught to read vec2i
objects, represented in lua as a table with entries t[1]
and t[2] using
the following code:
namespace primer { namespace traits { template <> struct read<vec2i> { static expected<vec2i> from_stack(lua_State * L, int index) { expected<vec2i> result; if (!lua_istable(L, index)) { result = primer::error("Expected a table, found ", primer::describe_lua_value(L, index)); } else { lua_rawgeti(L, index, 1); expected<int> t1 = read<int>::from_stack(L, -1); lua_pop(L, 1); if (!t1) { t1.err().prepend_error_line("In position [1]"); result = std::move(t1.err()); } else { lua_rawgeti(L, index, 2); expected<int> t2 = read<int>::from_stack(L, -1); lua_pop(L, 1); if (!t2) { t2.err().prepend_error_line("In position [2]"); result = std::move(t2.err()); } else { result = vec2i{*t1, *t2}; } } } return result; } static constexpr int stack_space_needed = 1; }; } // end namespace traits } // end namespace primer
A few things to note about this code:
lua_rawgeti,
we read it immediately and then pop it. This means we need only one
extra stack space rather than two.
index is negative.
In general, one can use lua_absindex
to convert negative indices to positive indices. If the top of the
stack is changing, then this may be necessary for correctness.
primer::error constructor can take any number
of strings, and concatenates them together. primer::describe_lua_value
is used to generate a diagnostic message describing a value on the
stack.
primer::error member function prepend_error_line is used to give
context to errors reported by subsidiary operations.
The following code snippet shows how this looks from lua's point of view
luaL_loadstring(L, "return {7, 4}"); assert(LUA_OK == lua_pcall(L, 0, 1, 0)); assert(lua_gettop(L) == 1); // the table is now on top of the stack assert(lua_istable(L, 1)); auto result = primer::read<vec2i>(L, 1); assert(result); assert(result->x == 7); assert(result->y == 4);
Similar to primer::push, specializations of primer::read must declare how much stack space
they assume that they will have available.
If your specialization performs it's own calls to lua_checkstack,
then the stack_space_needed
value can be zero.
The stack space needed for a given type can be calculated at compile-time using
template <typename T> constexpr int stack_space_for_read();
It is important to declare a stack_space_needed
value for any customization of primer::read,
it can protect you from certain obscure problems. For instance, when lua
calls a C++ function passed by you, the lua API guarantees that there will
be at least LUA_MINSTACK
stack positions free for you to work with, by default, 20 in current versions.
However, if one day your function requires 21 stack spaces to read its
parameters off of the stack, then you can get have undefined behavior when
that function is called if there is no call to lua_checkstack.
When a C++ function is adapted for lua using primer::adapt
(see docs there) Primer computes at compile-time how much space is needed
to read the parameters, using the values associated with primer::read. It compares this with LUA_MINSTACK, and insert a single call
to lua_checkstack if necessary,
and omits it otherwise. So, you pay the minimum possible at runtime for
this protection, and usually nothing. But it only works if the stack_space_needed values are accurate,
or at least conservative.