primer::push is a template function used to push
a C++ value to a lua Stack.
lua C API:
lua_pushinteger(L, 5); lua_pushstring(L, "ten");
Using primer:
primer::push(L, 5); primer::push(L, "ten");
template <typename T> void push(lua_State * L, const T & t);
In the case of primitive values, it is always a direct call to the corresponding
lua_pushX function.
In the case of containers, it usually creates a table, and calls push recursively in order to populate the table.
push is not permitted
to fail, except in the case of lua memory allocation failure. In this
case, it raises a lua error.
push always pushes
exactly one object onto the stack.
The following core types are supported:
|
Type |
|
|---|---|
|
|
Calls |
|
|
Calls |
|
|
|
|
|
|
|
|
Calls |
|
|
|
|
|
|
|
|
Calls |
|
|
|
|
|
|
|
|
Calls |
|
|
![]() |
Caution |
|---|---|
|
When pushing a signed integral type, integer overflow is not permitted to occur, and primer will not perform overflow checks.
If a push operation requires a narrowing conversion of a signed
integer type to |
![]() |
Caution |
|---|---|
|
When pushing a floating point type, Primer will not check for overflow or loss of precision.
If a push operation requires a narrowing conversion of a floating
point type to |
![]() |
Note |
|---|---|
When pushing an unsigned integral type which is larger than |
![]() |
Note |
|---|---|
|
If you want your program to be portable no matter how lua is compiled, consider:
- Only pushing 32 bit types, e.g.,
- Using If you just want to compile and don't care about overflow, you can do so with syntax like primer::push(L, static_cast<LUA_INTEGER>(i));
Or, install custom handling for integer types like
Or, define |
The following ancillary types are supported (see primer::read docs for more info).
|
Type |
|
|---|---|
|
|
Calls |
|
|
Calls |
|
|
Calls |
Primer includes additional headers to support some C++ standard containers and and boost containers, which are pushed as tables. See the containers section for details.
std::vector
std::array
std::set
std::map
std::unordered_map
boost::flat_set
primer::push is defined in terms of a type trait,
primer::traits::push.
template <typename T> void push(lua_State * L, const T & t) { ::primer::traits::push<T>::to_stack(L, t); }
The trait can be specialized to override primer's behavior for certain types, or to add new behavior for custom types.
For example, suppose we have a simple vector type:
struct vec2i { int x; int y; };
Primer could be taught to push vec2i
objects as a table with entries t[1]
and t[2] using
the following code:
namespace primer { namespace traits { template <> struct push<vec2i> { static void to_stack(lua_State * L, const vec2i & v) { lua_newtable(L); lua_pushinteger(L, v.x); lua_rawseti(L, -2, 1); lua_pushinteger(L, v.y); lua_rawseti(L, -2, 2); } static constexpr int stack_space_needed = 2; }; } // end namespace traits } // end namespace primer
The following code snippet shows how the pushed object looks to lua
primer::push(L, vec2i{5, 3}); luaL_loadstring(L, "v = ... \n" "assert(type(v) == 'table') \n" "assert(v[1] == 5) \n" "assert(v[2] == 3) \n" "assert(#v == 2) \n"); lua_insert(L, -2); // put script beneath argument when using pcall assert(LUA_OK == lua_pcall(L, 1, 0, 0));
One aspect of lua that you may easily miss on your first pass through the manual is the note about stack overflow.
The lua stack is dynamically sized. When a lua state is created, it is relatively small. It can grow to a quite large size (many thousands of elements).
Some of the more complex lua calls, like lua_pcall,
will manage the checking of stack bounds and ensure that they have enough
space.
Most of the primitive lua calls, for efficiency, do not do any such bounds checking.
The function lua_checkstack(lua_State
*, int) ensures that the stack has a given amount
of extra spaces, and enlarges it if needed. It is similar to std::vector::reserve, except that the input refers
to additional spaces from the current
top of the stack. (lua_checkstack
can conceivably fail, if lua runs out of memory.)
lua leaves it up to you to manage this -- if you don't do it, simple programs will work (because of a small built-in minimum stack size) but large programs may give undefined behavior.
Most primer::push operations are primitive operations
and we don't really want to call lua_checkstack
for each one. It's more economical to gather such checks into large groups,
so that the checks are performed before a large operation.
So, a custom push specialization has two options -- it can call lua_checkstack itself, or it can assume
that the caller will do it. To tell the caller how much space it needs,
it must declare the amount using
static constexpr int stack_space_needed = ...
More complex operations in Primer use this information to calculate at compile-time how much stack space they need.
For instance, Primer computes how much space is needed to push std::map<std::string, int>
based on it knowledge of how much space is needed for std::string,
and int. When a primer::bound_function is called, it computes
at compile-time how much stack space it needs to push all of the arguments
and calls lua_checkstack
to make sure it has enough space.
The space needed for any individual type can be obtained as a constexpr using
template <typename T> constexpr int stack_space_for_push();
You can use such functions to get the correct lua_checkstack
values into your program without hard-coding them -- invariably such values
tend to become inaccurate after a refactor at a later date.
![]() |
Note |
|---|---|
|
If your type is complex enough that no fixed estimate of the stack space
is possible, then you should simply define it to be one, and your implementation
must call |
push_each
primer::push_each is an extension which pushes
a sequence of objects.
primer::push_each(L, a, b, c);
is equivalent to
primer::push(L, a); primer::push(L, b); primer::push(L, c);