639 lines
15 KiB
D
639 lines
15 KiB
D
/++
|
|
This internal module, with the help of the luad.conversions package, takes care of converting between D and Lua types.
|
|
|
|
The conversion rules are as follows, where conversion goes both ways:
|
|
$(DL
|
|
$(DT boolean
|
|
$(DD $(D bool))
|
|
)
|
|
$(DT number
|
|
$(DD $(D lua_Integer) (default $(D int)))
|
|
$(DD $(D lua_Number) (default $(D double)))
|
|
)
|
|
$(DT string
|
|
$(DD $(D string), $(D const(char)[]), $(D char[]))
|
|
$(DD $(D const(char)*))
|
|
$(DD $(D char))
|
|
$(DD $(D immutable(void)[]), $(D const(void)[]), $(D void[]) (binary data))
|
|
)
|
|
$(DT table
|
|
$(DD associative arrays (see $(DPMODULE2 conversions,assocarrays)))
|
|
$(DD arrays (see $(DPMODULE2 conversions,arrays)))
|
|
$(DD structs (see $(DPMODULE2 conversions,structs)))
|
|
$(DD $(DPREF table,LuaTable))
|
|
)
|
|
$(DT function (see $(DPMODULE2 conversions,functions))
|
|
$(DD function pointers)
|
|
$(DD delegates)
|
|
$(DD $(DPREF lfunction,LuaFunction))
|
|
)
|
|
$(DT userdata
|
|
$(DD classes (see $(DPMODULE2 conversions,classes)))
|
|
)
|
|
$(DT nil
|
|
$(DD the special identifier $(D nil))
|
|
$(DD $(D null) class references)
|
|
)
|
|
$(DT any of the above
|
|
$(DD $(DPREF base,LuaObject))
|
|
$(DD $(DPREF dynamic,LuaDynamic))
|
|
$(DD $(D Algebraic), when given a compatible value (see $(DPMODULE2 conversions,variant)))
|
|
)
|
|
)
|
|
|
|
The conversions are checked in the specified order. For example, even though $(D bool) is implicitly convertible
|
|
to $(D lua_Integer), it will be converted to a boolean because boolean has precedence.
|
|
|
|
$(D wchar) and $(D dchar) are explicitly disallowed. Lua strings consist of 8-bit characters, if you want to push UTF-16 or UTF-32 strings, convert to UTF-8 first.
|
|
|
|
Additionally, the following types are pushable to Lua, but can't be retrieved back:
|
|
$(DL
|
|
$(DT function
|
|
$(DD $(D lua_CFunction))
|
|
)
|
|
)
|
|
+/
|
|
module luad.stack;
|
|
|
|
import std.range;
|
|
import std.traits;
|
|
import std.typecons;
|
|
|
|
import luad.c.all;
|
|
|
|
import luad.base;
|
|
import luad.table;
|
|
import luad.lfunction;
|
|
import luad.dynamic;
|
|
|
|
import luad.conversions.functions;
|
|
import luad.conversions.arrays;
|
|
import luad.conversions.structs;
|
|
import luad.conversions.assocarrays;
|
|
import luad.conversions.classes;
|
|
import luad.conversions.variant;
|
|
|
|
/**
|
|
* Push a value of any type to the stack.
|
|
* Params:
|
|
* L = stack to push to
|
|
* value = value to push
|
|
*/
|
|
void pushValue(T)(lua_State* L, T value)
|
|
{
|
|
static if(is(T : LuaObject))
|
|
value.push();
|
|
|
|
else static if(is(T == LuaDynamic))
|
|
value.object.push();
|
|
|
|
else static if(is(T == Nil))
|
|
lua_pushnil(L);
|
|
|
|
else static if(is(T == bool))
|
|
lua_pushboolean(L, cast(bool)value);
|
|
|
|
else static if(is(T == char))
|
|
lua_pushlstring(L, &value, 1);
|
|
|
|
else static if(is(T : lua_Integer))
|
|
lua_pushinteger(L, value);
|
|
|
|
else static if(is(T : lua_Number))
|
|
lua_pushnumber(L, value);
|
|
|
|
else static if(is(T : const(char)[]))
|
|
lua_pushlstring(L, value.ptr, value.length);
|
|
|
|
else static if(isVoidArray!T)
|
|
lua_pushlstring(L, cast(const(char)*)value.ptr, value.length);
|
|
|
|
else static if(is(T : const(char)*))
|
|
lua_pushstring(L, value);
|
|
|
|
else static if(isVariant!T)
|
|
pushVariant(L, value);
|
|
|
|
else static if(isAssociativeArray!T)
|
|
pushAssocArray(L, value);
|
|
|
|
else static if(isArray!T)
|
|
pushArray(L, value);
|
|
|
|
else static if(is(T == struct))
|
|
pushStruct(L, value);
|
|
|
|
// luaCFunction's are directly pushed
|
|
else static if(is(T == lua_CFunction) && functionLinkage!T == "C")
|
|
lua_pushcfunction(L, value);
|
|
|
|
// other functions are wrapped
|
|
else static if(isSomeFunction!T)
|
|
pushFunction(L, value);
|
|
|
|
else static if(is(T == class))
|
|
{
|
|
if(value is null)
|
|
lua_pushnil(L);
|
|
else
|
|
pushClassInstance(L, value);
|
|
}
|
|
else
|
|
static assert(false, "Unsupported type `" ~ T.stringof ~ "` in stack push operation");
|
|
}
|
|
|
|
template isVoidArray(T)
|
|
{
|
|
enum isVoidArray = is(T == void[]) ||
|
|
is(T == const(void)[]) ||
|
|
is(T == const(void[])) ||
|
|
is(T == immutable(void)[]) ||
|
|
is(T == immutable(void[]));
|
|
}
|
|
|
|
/**
|
|
* Get the associated Lua type for T.
|
|
* Returns: Lua type for T
|
|
*/
|
|
template luaTypeOf(T)
|
|
{
|
|
static if(is(T == bool))
|
|
enum luaTypeOf = LUA_TBOOLEAN;
|
|
|
|
else static if(is(T == Nil))
|
|
enum luaTypeOf = LUA_TNIL;
|
|
|
|
else static if(is(T : const(char)[]) || is(T : const(char)*) || is(T == char) || isVoidArray!T)
|
|
enum luaTypeOf = LUA_TSTRING;
|
|
|
|
else static if(is(T : lua_Integer) || is(T : lua_Number))
|
|
enum luaTypeOf = LUA_TNUMBER;
|
|
|
|
else static if(isSomeFunction!T || is(T == LuaFunction))
|
|
enum luaTypeOf = LUA_TFUNCTION;
|
|
|
|
else static if(isArray!T || isAssociativeArray!T || is(T == struct) || is(T == LuaTable))
|
|
enum luaTypeOf = LUA_TTABLE;
|
|
|
|
else static if(is(T : Object))
|
|
enum luaTypeOf = LUA_TUSERDATA;
|
|
|
|
else
|
|
static assert(false, "No Lua type defined for `" ~ T.stringof ~ "`");
|
|
}
|
|
|
|
// generic type mismatch message
|
|
private void defaultTypeMismatch(lua_State* L, int idx, int expectedType)
|
|
{
|
|
luaL_error(L, "expected %s, got %s", lua_typename(L, expectedType), luaL_typename(L, idx));
|
|
}
|
|
|
|
// type mismatch for function arguments of unexpected type
|
|
private void argumentTypeMismatch(lua_State* L, int idx, int expectedType)
|
|
{
|
|
luaL_typerror(L, idx, lua_typename(L, expectedType));
|
|
}
|
|
|
|
/**
|
|
* Get a value of any type from the stack.
|
|
* Params:
|
|
* T = type of value
|
|
* typeMismatchHandler = function called to produce an error in case of an invalid conversion.
|
|
* L = stack to get from
|
|
* idx = value stack index
|
|
*/
|
|
T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int idx)
|
|
{
|
|
debug //ensure unchanged stack
|
|
{
|
|
int _top = lua_gettop(L);
|
|
scope(success) assert(lua_gettop(L) == _top);
|
|
}
|
|
|
|
//ambiguous types
|
|
static if(is(T == wchar) || is(T : const(wchar)[]) ||
|
|
is(T == dchar) || is(T : const(dchar)[]))
|
|
{
|
|
static assert("Ambiguous type " ~ T.stringof ~ " in stack push operation. Consider converting before pushing.");
|
|
}
|
|
|
|
static if(!is(T == LuaObject) && !is(T == LuaDynamic) && !isVariant!T)
|
|
{
|
|
int type = lua_type(L, idx);
|
|
enum expectedType = luaTypeOf!T;
|
|
|
|
//if a class reference, return null for nil values
|
|
static if(is(T : Object))
|
|
{
|
|
if(type == LuaType.Nil)
|
|
return null;
|
|
}
|
|
|
|
if(type != expectedType)
|
|
typeMismatchHandler(L, idx, expectedType);
|
|
}
|
|
|
|
static if(is(T == LuaFunction)) // WORKAROUND: bug #6036
|
|
{
|
|
LuaFunction func;
|
|
func.object = LuaObject(L, idx);
|
|
return func;
|
|
}
|
|
else static if(is(T == LuaDynamic)) // ditto
|
|
{
|
|
LuaDynamic obj;
|
|
obj.object = LuaObject(L, idx);
|
|
return obj;
|
|
}
|
|
else static if(is(T : LuaObject))
|
|
return T(L, idx);
|
|
|
|
else static if(is(T == Nil))
|
|
return nil;
|
|
|
|
else static if(is(T == bool))
|
|
return lua_toboolean(L, idx);
|
|
|
|
else static if(is(T == char))
|
|
return *lua_tostring(L, idx); // TODO: better define this
|
|
|
|
else static if(is(T : lua_Integer))
|
|
return cast(T)lua_tointeger(L, idx);
|
|
|
|
else static if(is(T : lua_Number))
|
|
return cast(T)lua_tonumber(L, idx);
|
|
|
|
else static if(is(T : const(char)[]) || isVoidArray!T)
|
|
{
|
|
size_t len;
|
|
const(char)* str = lua_tolstring(L, idx, &len);
|
|
static if(is(T == char[]) || is(T == void[]))
|
|
return str[0 .. len].dup;
|
|
else
|
|
return str[0 .. len].idup;
|
|
}
|
|
else static if(is(T : const(char)*))
|
|
return lua_tostring(L, idx);
|
|
|
|
else static if(isAssociativeArray!T)
|
|
return getAssocArray!T(L, idx);
|
|
|
|
else static if(isArray!T)
|
|
return getArray!T(L, idx);
|
|
|
|
else static if(isVariant!T)
|
|
{
|
|
if(!isAllowedType!T(L, idx))
|
|
luaL_error(L, "Type not allowed in Variant: %s", luaL_typename(L, idx));
|
|
|
|
return getVariant!T(L, idx);
|
|
}
|
|
else static if(is(T == struct))
|
|
return getStruct!T(L, idx);
|
|
|
|
else static if(isSomeFunction!T)
|
|
return getFunction!T(L, idx);
|
|
|
|
else static if(is(T : Object))
|
|
return getClassInstance!T(L, idx);
|
|
|
|
else
|
|
{
|
|
static assert(false, "Unsupported type `" ~ T.stringof ~ "` in stack read operation");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Same as calling getValue!(T, typeMismatchHandler)(L, -1), then popping one value from the stack.
|
|
* See_Also: $(MREF getValue)
|
|
*/
|
|
T popValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L)
|
|
{
|
|
scope(success) lua_pop(L, 1);
|
|
return getValue!(T, typeMismatchHandler)(L, -1);
|
|
}
|
|
|
|
/**
|
|
* Pop a number of elements from the stack.
|
|
* Params:
|
|
* T = element type
|
|
* L = stack to pop from
|
|
* n = number of elements to pop
|
|
* Returns:
|
|
* array of popped elements, or a $(D null) array if n = 0
|
|
*/
|
|
T[] popStack(T = LuaObject)(lua_State* L, size_t n)
|
|
{
|
|
if(n == 0) // Don't allocate an array in this case
|
|
return null;
|
|
|
|
auto stack = new T[n];
|
|
foreach(i; 0 .. n)
|
|
{
|
|
stack[i] = getValue!T(L, cast(int)(-n + i));
|
|
}
|
|
|
|
lua_pop(L, cast(int)n);
|
|
return stack;
|
|
}
|
|
|
|
/// Get a function argument from the stack.
|
|
auto getArgument(T, int narg)(lua_State* L, int idx)
|
|
{
|
|
alias ParameterTypeTuple!T Args;
|
|
|
|
static if(narg == -1) // varargs causes this
|
|
alias ForeachType!(Args[$-1]) Arg;
|
|
else
|
|
alias Args[narg] Arg;
|
|
|
|
enum isVarargs = variadicFunctionStyle!T == Variadic.typesafe;
|
|
|
|
static if(isVarargs && narg == Args.length-1)
|
|
{
|
|
alias Args[narg] LastArg;
|
|
alias ForeachType!LastArg ElemType;
|
|
|
|
auto top = lua_gettop(L);
|
|
auto size = top - idx + 1;
|
|
LastArg result = new LastArg(size);
|
|
foreach(i; 0 .. size)
|
|
{
|
|
result[i] = getArgument!(T, -1)(L, idx + i);
|
|
}
|
|
return result;
|
|
}
|
|
else static if(is(Arg == const(char)[]) || is(Arg == const(void)[]) ||
|
|
is(Arg == const(char[])) || is(Arg == const(void[])))
|
|
{
|
|
if(lua_type(L, idx) != LUA_TSTRING)
|
|
argumentTypeMismatch(L, idx, LUA_TSTRING);
|
|
|
|
size_t len;
|
|
const(char)* cstr = lua_tolstring(L, idx, &len);
|
|
return cstr[0 .. len];
|
|
}
|
|
else
|
|
return getValue!(Arg, argumentTypeMismatch)(L, idx);
|
|
}
|
|
|
|
template isVariableReturnType(T : LuaVariableReturn!U, U)
|
|
{
|
|
enum isVariableReturnType = true;
|
|
}
|
|
|
|
template isVariableReturnType(T)
|
|
{
|
|
enum isVariableReturnType = false;
|
|
}
|
|
|
|
/// Used for getting a suitable nresults argument to $(D lua_call) or $(D lua_pcall).
|
|
template returnTypeSize(T)
|
|
{
|
|
static if(isVariableReturnType!T)
|
|
enum returnTypeSize = LUA_MULTRET;
|
|
|
|
else static if(isTuple!T)
|
|
enum returnTypeSize = T.Types.length;
|
|
|
|
else static if(isStaticArray!T)
|
|
enum returnTypeSize = T.length;
|
|
|
|
else static if(is(T == void))
|
|
enum returnTypeSize = 0;
|
|
|
|
else
|
|
enum returnTypeSize = 1;
|
|
}
|
|
|
|
/**
|
|
* Pop return values from stack.
|
|
* Defaults to $(MREF popValue), but has special handling for $(DPREF2 conversions, functions, LuaVariableReturn),
|
|
* $(STDREF typecons, Tuple), static arrays and $(D void).
|
|
* Params:
|
|
* nret = number of return values
|
|
* Returns:
|
|
* Return value, collection of return values, or nothing
|
|
*/
|
|
T popReturnValues(T)(lua_State* L, size_t nret)
|
|
{
|
|
static if(isVariableReturnType!T)
|
|
return variableReturn(popStack!(ElementType!(T.WrappedType))(L, nret));
|
|
|
|
else static if(isTuple!T)
|
|
{
|
|
if(nret < T.Types.length)
|
|
luaL_error(L, "expected %d return values, got %d", T.Types.length, nret);
|
|
|
|
return popTuple!T(L);
|
|
}
|
|
else static if(isStaticArray!T)
|
|
{
|
|
T ret;
|
|
fillStaticArray(L, ret);
|
|
return ret;
|
|
}
|
|
else static if(is(T == void))
|
|
return;
|
|
|
|
else
|
|
{
|
|
if(nret < 1)
|
|
luaL_error(L, "expected return value of type %s, got nil", lua_typename(L, luaTypeOf!T));
|
|
|
|
return popValue!T(L);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Push return values to the stack.
|
|
* Defaults to $(MREF pushValue), but has special handling for $(DPREF2 conversions, functions, LuaVariableReturn),
|
|
* $(STDREF typecons, Tuple) and static arrays.
|
|
*/
|
|
int pushReturnValues(T)(lua_State* L, T value)
|
|
{
|
|
static if(isVariableReturnType!T)
|
|
{
|
|
enum calculateLength = !hasLength!(typeof(value.returnValues));
|
|
|
|
static if(calculateLength)
|
|
int length;
|
|
|
|
foreach(obj; value.returnValues)
|
|
{
|
|
pushValue(L, obj);
|
|
|
|
static if(calculateLength)
|
|
++length;
|
|
}
|
|
|
|
static if(calculateLength)
|
|
return length;
|
|
else
|
|
return cast(int)value.returnValues.length;
|
|
}
|
|
else static if(isTuple!T)
|
|
{
|
|
pushTuple(L, value);
|
|
return cast(int)T.Types.length;
|
|
}
|
|
else static if(isStaticArray!T)
|
|
{
|
|
pushStaticArray(L, value);
|
|
return cast(int)value.length;
|
|
}
|
|
else
|
|
{
|
|
pushValue(L, value);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/// Pops a $(STDREF typecons, Tuple) from the values at the top of the stack.
|
|
T popTuple(T)(lua_State* L) if(isTuple!T)
|
|
{
|
|
T tup;
|
|
foreach(i, Elem; T.Types)
|
|
tup[i] = getValue!Elem(L, cast(int)(-T.Types.length + i));
|
|
|
|
lua_pop(L, T.Types.length);
|
|
return tup;
|
|
}
|
|
|
|
/// Pushes all the values in a $(STDREF typecons, Tuple) to the stack.
|
|
void pushTuple(T)(lua_State* L, ref T tup) if(isTuple!T)
|
|
{
|
|
foreach(i, Elem; T.Types)
|
|
pushValue(L, tup[i]);
|
|
}
|
|
|
|
/**
|
|
* Call a Lua function and handle its return values.
|
|
* Params:
|
|
* T = type of return value or container of return values
|
|
* nargs = number of arguments
|
|
* Returns:
|
|
* Zero, one or all return values as $(D T), taking into account $(D void),
|
|
* $(DPREF2 conversions, functions, LuaVariableReturn) and $(STDREF typecons, Tuple) returns
|
|
*/
|
|
T callWithRet(T)(lua_State* L, int nargs)
|
|
{
|
|
static if(isVariableReturnType!T)
|
|
auto frame = lua_gettop(L) - nargs - 1; // the size of the stack before arguments and the function
|
|
|
|
lua_call(L, nargs, returnTypeSize!T);
|
|
|
|
static if(isVariableReturnType!T)
|
|
auto nret = lua_gettop(L) - frame;
|
|
else
|
|
auto nret = returnTypeSize!T;
|
|
|
|
return popReturnValues!T(L, nret);
|
|
}
|
|
|
|
private extern(C) int printf(const(char)* fmt, ...);
|
|
|
|
/// Print the Lua stack to $(D stdout).
|
|
void printStack(lua_State* L)
|
|
{
|
|
auto top = lua_gettop(L);
|
|
|
|
foreach(n; 0 .. top)
|
|
{
|
|
auto str = luaL_tolstring(L, n + 1, null);
|
|
printf("\t[%d] %s (%s)\r\n", n + 1, str, luaL_typename(L, n + 1));
|
|
}
|
|
|
|
lua_pop(L, top); // luaL_tolstring always pushes one
|
|
}
|
|
|
|
version(unittest) import luad.testing;
|
|
|
|
unittest
|
|
{
|
|
lua_State* L = luaL_newstate();
|
|
scope(success) lua_close(L);
|
|
|
|
// pushValue and popValue
|
|
//number
|
|
pushValue(L, cast(ubyte)123);
|
|
assert(lua_isnumber(L, -1) && (popValue!ubyte(L) == 123));
|
|
|
|
pushValue(L, cast(short)123);
|
|
assert(lua_isnumber(L, -1) && (popValue!short(L) == 123));
|
|
|
|
pushValue(L, 123);
|
|
assert(lua_isnumber(L, -1) && (popValue!int(L) == 123));
|
|
|
|
pushValue(L, 123UL);
|
|
assert(lua_isnumber(L, -1) && (popValue!ulong(L) == 123));
|
|
|
|
pushValue(L, 1.2f);
|
|
assert(lua_isnumber(L, -1) && (popValue!float(L) == 1.2f));
|
|
|
|
pushValue(L, 1.23);
|
|
assert(lua_isnumber(L, -1) && (popValue!double(L) == 1.23));
|
|
|
|
//string
|
|
string istr = "foobar";
|
|
pushValue(L, istr);
|
|
assert(lua_isstring(L, -1) && (popValue!string(L) == "foobar"));
|
|
|
|
char[] str = "baz".dup;
|
|
pushValue(L, str);
|
|
assert(lua_isstring(L, -1) && (popValue!(char[])(L) == "baz"));
|
|
|
|
const(char)* cstr = "hi";
|
|
pushValue(L, cstr);
|
|
assert(lua_isstring(L, -1) && (strcmp(cstr, popValue!(const(char)*)(L)) == 0));
|
|
|
|
//char
|
|
pushValue(L, '\t');
|
|
assert(lua_isstring(L, -1) && getValue!string(L, -1) == "\t");
|
|
assert(popValue!char(L) == '\t');
|
|
|
|
//boolean
|
|
pushValue(L, true);
|
|
assert(lua_isboolean(L, -1) && (popValue!bool(L) == true));
|
|
|
|
assert(lua_gettop(L) == 0, "bad popValue semantics for primitives");
|
|
|
|
// arrays
|
|
static int[] arr = [3, 2, 1];
|
|
pushValue(L, arr);
|
|
assert(lua_istable(L, -1) && popValue!(int[])(L) == arr);
|
|
|
|
immutable int[] iarr = [1, 2, 3];
|
|
pushValue(L, iarr);
|
|
assert(lua_istable(L, -1) && popValue!(typeof(arr))(L) == iarr);
|
|
|
|
//void arrays
|
|
immutable void[] voidiarr = "foobar";
|
|
pushValue(L, voidiarr);
|
|
assert(lua_isstring(L, -1) && popValue!(typeof(voidiarr))(L) == "foobar");
|
|
|
|
void[] voidarr ="baz".dup;
|
|
pushValue(L, voidarr);
|
|
assert(lua_isstring(L, -1) && popValue!(void[])(L) == "baz");
|
|
|
|
//popStack
|
|
extern(C) static int luacfunc(lua_State* L)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
pushValue(L, &luacfunc);
|
|
pushValue(L, "test");
|
|
pushValue(L, 123);
|
|
pushValue(L, true);
|
|
|
|
assert(lua_gettop(L) == 4);
|
|
|
|
auto stack = popStack(L, lua_gettop(L));
|
|
assert(lua_gettop(L) == 0);
|
|
assert(stack[0].type == LuaType.Function);
|
|
assert(stack[1].type == LuaType.String);
|
|
assert(stack[2].type == LuaType.Number);
|
|
assert(stack[3].type == LuaType.Boolean);
|
|
}
|