PrevUpHomeNext

function read

primer::read is a template function used to read a C++ value from the lua stack.

Examples
Synopsis
template <typename T>
expected<T> read(lua_State * L, int index);

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.

Supported Types

The following core types are supported:

Type

primer::read

bool

Checks lua_isboolean, returns value of lua_toboolean.

int

Checks lua_isinteger, returns value of lua_tointeger.

long

long long

unsigned int

Checks lua_isinteger, returns value of lua_tointeger. Fails if the value is negative.

unsigned long

unsigned long long

float

Checks lua_isnumber, returns value of lua_tonumber.

double

long double

const char *

Checks lua_isstring, returns value of lua_tostring.

std::string

In the above table, "Checks" means that, the checked function must report "true" or an error is reported.

[Note] Note

In the above table, when an integral type is read, if the target type is narrower than LUA_INTEGER, then an overflow check is performed, and an overflow error will be reported if it occurs.

To avoid the overhead of overflow checks, consider using the type LUA_INTEGER in your code, or only using primer::read with integral types known to be as large as LUA_INTEGER.

Or, consider compiling lua using -DLUA_32BITS to force lua to use a 32 bit type for integers and floating point numbers internally.

Alternatively, see "customization" below and specialize reading for int, unsigned int, etc. as you like.

[Caution] Caution

In the above table, when a floating point type is read, if the target type is narrower than LUA_NUMBER, no overflow check is performed, and the conversion is performed using a static_cast. This gives you (the / a) "closest possible" matching float value.

In most applications this is what you want. If this conversion is a concern for you, consider specializing the read trait for float, double, etc., or using LUA_NUMBER in your code for portability.

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

primer::read

primer::truthy

Returns value of lua_toboolean. Does not fail.

primer::stringy

Returns value of lua_tostring if argument is string or number. Returns result of __tostring if metamethod is present and produces a string. Otherwise fails.

primer::nil_t

Checks lua_isnoneornil.

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.

Primer also supports the ability to read references to userdata types. (See userdata reference page.)

Customization

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:

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);
Stack Space

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.


PrevUpHomeNext