671 lines
14 KiB
D
671 lines
14 KiB
D
|
|
/**
|
||
|
|
Internal module for pushing and getting _functions and delegates.
|
||
|
|
|
||
|
|
LuaD allows for pushing of all D function or delegate types with return type and parameter types compatible with LuaD (see $(DPMODULE stack)).
|
||
|
|
|
||
|
|
For a fixed number of multiple return values, return a $(STDREF typecons,Tuple) or a static array. For a variable number of return values, return $(MREF LuaVariableReturn).
|
||
|
|
|
||
|
|
As a special case for $(D const(char)[]) parameter types in _functions pushed to Lua, no copy of the string is made when called; take care not to escape such references, they are effectively $(D scope) parameters.
|
||
|
|
When a copy is desired, use $(D char[]) or $(D string), or $(D dup) or $(D idup) the string manually.
|
||
|
|
|
||
|
|
If a function with the $(D lua_CFunction) signature is encountered, it is pushed directly with no inserted conversions or overhead.
|
||
|
|
|
||
|
|
Typesafe varargs is supported when pushing _functions to Lua, but as of DMD 2.054, compiler bugs prevent getting delegates with varargs from Lua (use $(DPREF lfunction,LuaFunction) instead).
|
||
|
|
*/
|
||
|
|
module luad.conversions.functions;
|
||
|
|
|
||
|
|
import core.memory;
|
||
|
|
import std.range;
|
||
|
|
import std.string : toStringz;
|
||
|
|
import std.traits;
|
||
|
|
import std.typetuple;
|
||
|
|
|
||
|
|
import luad.c.all;
|
||
|
|
|
||
|
|
import luad.stack;
|
||
|
|
|
||
|
|
private void argsError(lua_State* L, int nargs, ptrdiff_t expected)
|
||
|
|
{
|
||
|
|
lua_Debug debugInfo;
|
||
|
|
lua_getstack(L, 0, &debugInfo);
|
||
|
|
lua_getinfo(L, "n", &debugInfo);
|
||
|
|
luaL_error(L, "call to %s '%s': got %d arguments, expected %d",
|
||
|
|
debugInfo.namewhat, debugInfo.name, nargs, expected);
|
||
|
|
}
|
||
|
|
|
||
|
|
template StripHeadQual(T : const(T*))
|
||
|
|
{
|
||
|
|
alias const(T)* StripHeadQual;
|
||
|
|
}
|
||
|
|
|
||
|
|
template StripHeadQual(T : const(T[]))
|
||
|
|
{
|
||
|
|
alias const(T)[] StripHeadQual;
|
||
|
|
}
|
||
|
|
|
||
|
|
template StripHeadQual(T : immutable(T*))
|
||
|
|
{
|
||
|
|
alias immutable(T)* StripHeadQual;
|
||
|
|
}
|
||
|
|
|
||
|
|
template StripHeadQual(T : immutable(T[]))
|
||
|
|
{
|
||
|
|
alias immutable(T)[] StripHeadQual;
|
||
|
|
}
|
||
|
|
|
||
|
|
template StripHeadQual(T : T[])
|
||
|
|
{
|
||
|
|
alias T[] StripHeadQual;
|
||
|
|
}
|
||
|
|
|
||
|
|
template StripHeadQual(T : T*)
|
||
|
|
{
|
||
|
|
alias T* StripHeadQual;
|
||
|
|
}
|
||
|
|
|
||
|
|
template StripHeadQual(T : T[N], size_t N)
|
||
|
|
{
|
||
|
|
alias T[N] StripHeadQual;
|
||
|
|
}
|
||
|
|
|
||
|
|
template StripHeadQual(T)
|
||
|
|
{
|
||
|
|
alias T StripHeadQual;
|
||
|
|
}
|
||
|
|
|
||
|
|
template FillableParameterTypeTuple(T)
|
||
|
|
{
|
||
|
|
alias staticMap!(StripHeadQual, ParameterTypeTuple!T) FillableParameterTypeTuple;
|
||
|
|
}
|
||
|
|
|
||
|
|
template BindableReturnType(T)
|
||
|
|
{
|
||
|
|
alias StripHeadQual!(ReturnType!T) BindableReturnType;
|
||
|
|
}
|
||
|
|
|
||
|
|
//Call with or without return value, propagating Exceptions as Lua errors.
|
||
|
|
//This should rather be throwing a userdata with __tostring and a reference to
|
||
|
|
//the thrown exception, as it is now, everything but the error type and message is lost.
|
||
|
|
int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args)
|
||
|
|
if(!is(BindableReturnType!T == const) &&
|
||
|
|
!is(BindableReturnType!T == immutable))
|
||
|
|
{
|
||
|
|
alias BindableReturnType!T RetType;
|
||
|
|
enum hasReturnValue = !is(RetType == void);
|
||
|
|
|
||
|
|
static if(hasReturnValue)
|
||
|
|
RetType ret;
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
static if(hasReturnValue)
|
||
|
|
ret = func(args);
|
||
|
|
else
|
||
|
|
func(args);
|
||
|
|
}
|
||
|
|
catch(Exception e)
|
||
|
|
{
|
||
|
|
luaL_error(L, "%s", toStringz(e.toString()));
|
||
|
|
}
|
||
|
|
|
||
|
|
static if(hasReturnValue)
|
||
|
|
return pushReturnValues(L, ret);
|
||
|
|
else
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ditto, but wrap the try-catch in a nested function because the return value's
|
||
|
|
// declaration and initialization cannot be separated.
|
||
|
|
int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args)
|
||
|
|
if(is(BindableReturnType!T == const) ||
|
||
|
|
is(BindableReturnType!T == immutable))
|
||
|
|
{
|
||
|
|
auto ref call()
|
||
|
|
{
|
||
|
|
try
|
||
|
|
return func(args);
|
||
|
|
catch(Exception e)
|
||
|
|
luaL_error(L, "%s", e.toString().toStringz());
|
||
|
|
}
|
||
|
|
|
||
|
|
return pushReturnValues(L, call());
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
|
||
|
|
// TODO: right now, virtual functions on specialized classes can be called with base classes as 'self', not safe!
|
||
|
|
extern(C) int methodWrapper(T, Class, bool virtual)(lua_State* L)
|
||
|
|
{
|
||
|
|
alias ParameterTypeTuple!T Args;
|
||
|
|
|
||
|
|
static assert ((variadicFunctionStyle!T != Variadic.d && variadicFunctionStyle!T != Variadic.c),
|
||
|
|
"Non-typesafe variadic functions are not supported.");
|
||
|
|
|
||
|
|
//Check arguments
|
||
|
|
int top = lua_gettop(L);
|
||
|
|
|
||
|
|
static if (variadicFunctionStyle!T == Variadic.typesafe)
|
||
|
|
enum requiredArgs = Args.length;
|
||
|
|
else
|
||
|
|
enum requiredArgs = Args.length + 1;
|
||
|
|
|
||
|
|
if(top < requiredArgs)
|
||
|
|
argsError(L, top, requiredArgs);
|
||
|
|
|
||
|
|
Class self = *cast(Class*)luaL_checkudata(L, 1, toStringz(Class.mangleof));
|
||
|
|
|
||
|
|
static if(virtual)
|
||
|
|
{
|
||
|
|
alias ReturnType!T function(Class, Args) VirtualWrapper;
|
||
|
|
VirtualWrapper func = cast(VirtualWrapper)lua_touserdata(L, lua_upvalueindex(1));
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
T func;
|
||
|
|
func.ptr = cast(void*)self;
|
||
|
|
func.funcptr = cast(typeof(func.funcptr))lua_touserdata(L, lua_upvalueindex(1));
|
||
|
|
}
|
||
|
|
|
||
|
|
//Assemble arguments
|
||
|
|
static if(virtual)
|
||
|
|
{
|
||
|
|
ParameterTypeTuple!VirtualWrapper allArgs;
|
||
|
|
allArgs[0] = self;
|
||
|
|
alias allArgs[1..$] args;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Args allArgs;
|
||
|
|
alias allArgs args;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach(i, Arg; Args)
|
||
|
|
args[i] = getArgument!(T, i)(L, i + 2);
|
||
|
|
|
||
|
|
return callFunction!(typeof(func))(L, func, allArgs);
|
||
|
|
}
|
||
|
|
|
||
|
|
extern(C) int functionWrapper(T)(lua_State* L)
|
||
|
|
{
|
||
|
|
alias FillableParameterTypeTuple!T Args;
|
||
|
|
|
||
|
|
static assert ((variadicFunctionStyle!T != Variadic.d && variadicFunctionStyle!T != Variadic.c),
|
||
|
|
"Non-typesafe variadic functions are not supported.");
|
||
|
|
|
||
|
|
//Check arguments
|
||
|
|
int top = lua_gettop(L);
|
||
|
|
|
||
|
|
static if (variadicFunctionStyle!T == Variadic.typesafe)
|
||
|
|
enum requiredArgs = Args.length - 1;
|
||
|
|
else
|
||
|
|
enum requiredArgs = Args.length;
|
||
|
|
|
||
|
|
if(top < requiredArgs)
|
||
|
|
argsError(L, top, requiredArgs);
|
||
|
|
|
||
|
|
//Get function
|
||
|
|
static if(isFunctionPointer!T)
|
||
|
|
T func = cast(T)lua_touserdata(L, lua_upvalueindex(1));
|
||
|
|
else
|
||
|
|
T func = *cast(T*)lua_touserdata(L, lua_upvalueindex(1));
|
||
|
|
|
||
|
|
//Assemble arguments
|
||
|
|
Args args;
|
||
|
|
foreach(i, Arg; Args)
|
||
|
|
args[i] = getArgument!(T, i)(L, i + 1);
|
||
|
|
|
||
|
|
return callFunction!T(L, func, args);
|
||
|
|
}
|
||
|
|
|
||
|
|
extern(C) int functionCleaner(lua_State* L)
|
||
|
|
{
|
||
|
|
GC.removeRoot(lua_touserdata(L, 1));
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
public:
|
||
|
|
|
||
|
|
void pushFunction(T)(lua_State* L, T func) if (isSomeFunction!T)
|
||
|
|
{
|
||
|
|
static if(isFunctionPointer!T)
|
||
|
|
lua_pushlightuserdata(L, func);
|
||
|
|
else
|
||
|
|
{
|
||
|
|
T* udata = cast(T*)lua_newuserdata(L, T.sizeof);
|
||
|
|
*udata = func;
|
||
|
|
|
||
|
|
GC.addRoot(udata);
|
||
|
|
|
||
|
|
if(luaL_newmetatable(L, "__dcall") == 1)
|
||
|
|
{
|
||
|
|
lua_pushcfunction(L, &functionCleaner);
|
||
|
|
lua_setfield(L, -2, "__gc");
|
||
|
|
}
|
||
|
|
|
||
|
|
lua_setmetatable(L, -2);
|
||
|
|
}
|
||
|
|
|
||
|
|
lua_pushcclosure(L, &functionWrapper!T, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: optimize for non-virtual functions
|
||
|
|
void pushMethod(Class, string member)(lua_State* L) if (isSomeFunction!(__traits(getMember, Class, member)))
|
||
|
|
{
|
||
|
|
alias typeof(mixin("&Class.init." ~ member)) T;
|
||
|
|
|
||
|
|
// Delay vtable lookup until the right time
|
||
|
|
static ReturnType!T virtualWrapper(Class self, ParameterTypeTuple!T args)
|
||
|
|
{
|
||
|
|
return mixin("self." ~ member)(args);
|
||
|
|
}
|
||
|
|
|
||
|
|
lua_pushlightuserdata(L, &virtualWrapper);
|
||
|
|
lua_pushcclosure(L, &methodWrapper!(T, Class, true), 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Currently this function allocates a reference in the registry that is never deleted,
|
||
|
|
* one for each call... see code comments
|
||
|
|
*/
|
||
|
|
T getFunction(T)(lua_State* L, int idx) if (is(T == delegate))
|
||
|
|
{
|
||
|
|
auto func = new class
|
||
|
|
{
|
||
|
|
int lref;
|
||
|
|
this()
|
||
|
|
{
|
||
|
|
lua_pushvalue(L, idx);
|
||
|
|
lref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||
|
|
}
|
||
|
|
|
||
|
|
//Alright... how to fix this?
|
||
|
|
//The problem is that this object tends to be finalized after L is freed (by LuaState's destructor or otherwise).
|
||
|
|
//If you have a good solution to the problem of dangling references to a lua_State,
|
||
|
|
//please contact me :)
|
||
|
|
|
||
|
|
/+~this()
|
||
|
|
{
|
||
|
|
luaL_unref(L, LUA_REGISTRYINDEX, lref);
|
||
|
|
}+/
|
||
|
|
|
||
|
|
void push()
|
||
|
|
{
|
||
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, lref);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
alias ReturnType!T RetType;
|
||
|
|
alias ParameterTypeTuple!T Args;
|
||
|
|
|
||
|
|
return delegate RetType(Args args)
|
||
|
|
{
|
||
|
|
func.push();
|
||
|
|
foreach(arg; args)
|
||
|
|
pushValue(L, arg);
|
||
|
|
|
||
|
|
return callWithRet!RetType(L, args.length);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Type for efficiently returning a variable number of return values
|
||
|
|
* from a function.
|
||
|
|
*
|
||
|
|
* Use $(D variableReturn) to instantiate it.
|
||
|
|
* Params:
|
||
|
|
* Range = any input range
|
||
|
|
*/
|
||
|
|
struct LuaVariableReturn(Range) if(isInputRange!Range)
|
||
|
|
{
|
||
|
|
alias WrappedType = Range; /// The type of the wrapped input range.
|
||
|
|
Range returnValues; /// The wrapped input range.
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a LuaVariableReturn object for efficiently returning
|
||
|
|
* a variable number of values from a function.
|
||
|
|
* Params:
|
||
|
|
* returnValues = any input range
|
||
|
|
* Example:
|
||
|
|
-----------------------------
|
||
|
|
LuaVariableReturn!(uint[]) makeList(uint n)
|
||
|
|
{
|
||
|
|
uint[] list;
|
||
|
|
|
||
|
|
foreach(i; 1 .. n + 1)
|
||
|
|
{
|
||
|
|
list ~= i;
|
||
|
|
}
|
||
|
|
|
||
|
|
return variableReturn(list);
|
||
|
|
}
|
||
|
|
|
||
|
|
lua["makeList"] = &makeList;
|
||
|
|
|
||
|
|
lua.doString(`
|
||
|
|
local one, two, three, four = makeList(4)
|
||
|
|
assert(one == 1)
|
||
|
|
assert(two == 2)
|
||
|
|
assert(three == 3)
|
||
|
|
assert(four == 4)
|
||
|
|
`);
|
||
|
|
-----------------------------
|
||
|
|
*/
|
||
|
|
LuaVariableReturn!Range variableReturn(Range)(Range returnValues)
|
||
|
|
if(isInputRange!Range)
|
||
|
|
{
|
||
|
|
return typeof(return)(returnValues);
|
||
|
|
}
|
||
|
|
|
||
|
|
version(unittest)
|
||
|
|
{
|
||
|
|
import luad.testing;
|
||
|
|
import std.typecons;
|
||
|
|
private lua_State* L;
|
||
|
|
}
|
||
|
|
|
||
|
|
unittest
|
||
|
|
{
|
||
|
|
L = luaL_newstate();
|
||
|
|
luaL_openlibs(L);
|
||
|
|
|
||
|
|
//functions
|
||
|
|
static const(char)[] func(const(char)[] s)
|
||
|
|
{
|
||
|
|
return "Hello, " ~ s;
|
||
|
|
}
|
||
|
|
|
||
|
|
pushValue(L, &func);
|
||
|
|
assert(lua_isfunction(L, -1));
|
||
|
|
lua_setglobal(L, "sayHello");
|
||
|
|
|
||
|
|
unittest_lua(L, `
|
||
|
|
local ret = sayHello("foo")
|
||
|
|
local expect = "Hello, foo"
|
||
|
|
assert(ret == expect,
|
||
|
|
("sayHello return type - got '%s', expected '%s'"):format(ret, expect)
|
||
|
|
)
|
||
|
|
`);
|
||
|
|
|
||
|
|
static uint countSpaces(const(char)[] s)
|
||
|
|
{
|
||
|
|
uint n = 0;
|
||
|
|
foreach(dchar c; s)
|
||
|
|
if(c == ' ')
|
||
|
|
++n;
|
||
|
|
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
pushValue(L, &countSpaces);
|
||
|
|
assert(lua_isfunction(L, -1));
|
||
|
|
lua_setglobal(L, "countSpaces");
|
||
|
|
|
||
|
|
unittest_lua(L, `
|
||
|
|
assert(countSpaces("Hello there, world!") == 2)
|
||
|
|
`);
|
||
|
|
|
||
|
|
//delegates
|
||
|
|
double curry = 3.14 * 2;
|
||
|
|
double closure(double x)
|
||
|
|
{
|
||
|
|
return curry * x;
|
||
|
|
}
|
||
|
|
|
||
|
|
pushValue(L, &closure);
|
||
|
|
assert(lua_isfunction(L, -1));
|
||
|
|
lua_setglobal(L, "circle");
|
||
|
|
|
||
|
|
unittest_lua(L, `
|
||
|
|
assert(circle(2) == 3.14 * 4, "closure return type mismatch!")
|
||
|
|
`);
|
||
|
|
|
||
|
|
// Const parameters
|
||
|
|
static bool isEmpty(const(char[]) str) { return str.length == 0; }
|
||
|
|
static bool isEmpty2(in char[] str) { return str.length == 0; }
|
||
|
|
|
||
|
|
pushValue(L, &isEmpty);
|
||
|
|
lua_setglobal(L, "isEmpty");
|
||
|
|
|
||
|
|
pushValue(L, &isEmpty2);
|
||
|
|
lua_setglobal(L, "isEmpty2");
|
||
|
|
|
||
|
|
unittest_lua(L, `
|
||
|
|
assert(isEmpty(""))
|
||
|
|
assert(isEmpty2(""))
|
||
|
|
assert(not isEmpty("a"))
|
||
|
|
assert(not isEmpty2("a"))
|
||
|
|
`);
|
||
|
|
|
||
|
|
// Immutable parameters
|
||
|
|
static immutable(char[]) returnArg(immutable(char[]) str) { return str; }
|
||
|
|
|
||
|
|
pushValue(L, &returnArg);
|
||
|
|
lua_setglobal(L, "returnArg");
|
||
|
|
|
||
|
|
unittest_lua(L, `assert(returnArg("foo") == "foo")`);
|
||
|
|
}
|
||
|
|
|
||
|
|
version(unittest) import luad.base;
|
||
|
|
|
||
|
|
// multiple return values
|
||
|
|
unittest
|
||
|
|
{
|
||
|
|
// tuple returns
|
||
|
|
auto nameInfo = ["foo"];
|
||
|
|
auto ageInfo = [42];
|
||
|
|
|
||
|
|
alias Tuple!(string, "name", uint, "age") GetInfoResult;
|
||
|
|
GetInfoResult getInfo(int idx)
|
||
|
|
{
|
||
|
|
GetInfoResult result;
|
||
|
|
result.name = nameInfo[idx];
|
||
|
|
result.age = ageInfo[idx];
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
pushValue(L, &getInfo);
|
||
|
|
lua_setglobal(L, "getInfo");
|
||
|
|
|
||
|
|
unittest_lua(L, `
|
||
|
|
local name, age = getInfo(0)
|
||
|
|
assert(name == "foo")
|
||
|
|
assert(age == 42)
|
||
|
|
`);
|
||
|
|
|
||
|
|
// static array returns
|
||
|
|
static string[2] getName()
|
||
|
|
{
|
||
|
|
string[2] ret;
|
||
|
|
ret[0] = "Foo";
|
||
|
|
ret[1] = "Bar";
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
pushValue(L, &getName);
|
||
|
|
lua_setglobal(L, "getName");
|
||
|
|
|
||
|
|
unittest_lua(L, `
|
||
|
|
local first, last = getName()
|
||
|
|
assert(first == "Foo")
|
||
|
|
assert(last == "Bar")
|
||
|
|
`);
|
||
|
|
|
||
|
|
// variable length returns
|
||
|
|
LuaVariableReturn!(uint[]) makeList(uint n)
|
||
|
|
{
|
||
|
|
uint[] list;
|
||
|
|
|
||
|
|
foreach(i; 1 .. n + 1)
|
||
|
|
{
|
||
|
|
list ~= i;
|
||
|
|
}
|
||
|
|
|
||
|
|
return variableReturn(list);
|
||
|
|
}
|
||
|
|
|
||
|
|
auto makeList2(uint n)
|
||
|
|
{
|
||
|
|
return variableReturn(iota(1, n + 1));
|
||
|
|
}
|
||
|
|
|
||
|
|
pushValue(L, &makeList);
|
||
|
|
lua_setglobal(L, "makeList");
|
||
|
|
pushValue(L, &makeList2);
|
||
|
|
lua_setglobal(L, "makeList2");
|
||
|
|
|
||
|
|
unittest_lua(L, `
|
||
|
|
for i, f in pairs{makeList, makeList2} do
|
||
|
|
local one, two, three, four = f(4)
|
||
|
|
assert(one == 1)
|
||
|
|
assert(two == 2)
|
||
|
|
assert(three == 3)
|
||
|
|
assert(four == 4)
|
||
|
|
end
|
||
|
|
`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Variadic function arguments
|
||
|
|
unittest
|
||
|
|
{
|
||
|
|
static string concat(const(char)[][] pieces...)
|
||
|
|
{
|
||
|
|
string result;
|
||
|
|
foreach(piece; pieces)
|
||
|
|
result ~= piece;
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
pushValue(L, &concat);
|
||
|
|
lua_setglobal(L, "concat");
|
||
|
|
|
||
|
|
unittest_lua(L, `
|
||
|
|
local whole = concat("he", "llo", ", ", "world!")
|
||
|
|
assert(whole == "hello, world!")
|
||
|
|
`);
|
||
|
|
|
||
|
|
//Test with zero parameters.
|
||
|
|
unittest_lua(L, `
|
||
|
|
local blank = concat()
|
||
|
|
assert (string.len(blank) == 0)
|
||
|
|
`);
|
||
|
|
|
||
|
|
static const(char)[] concat2(char separator, const(char)[][] pieces...)
|
||
|
|
{
|
||
|
|
if(pieces.length == 0)
|
||
|
|
return "";
|
||
|
|
|
||
|
|
string result;
|
||
|
|
foreach(piece; pieces[0..$-1])
|
||
|
|
result ~= piece ~ separator;
|
||
|
|
|
||
|
|
return result ~ pieces[$-1];
|
||
|
|
}
|
||
|
|
|
||
|
|
pushValue(L, &concat2);
|
||
|
|
lua_setglobal(L, "concat2");
|
||
|
|
|
||
|
|
unittest_lua(L, `
|
||
|
|
local whole = concat2(",", "one", "two", "three", "four")
|
||
|
|
assert(whole == "one,two,three,four")
|
||
|
|
`);
|
||
|
|
|
||
|
|
//C- and D-style variadic versions of concat for
|
||
|
|
//future use if/when these are supported.
|
||
|
|
|
||
|
|
//C varargs require at least one fixed argument.
|
||
|
|
import core.vararg;
|
||
|
|
// C-style varargs broken on Linux for 2.066.1?
|
||
|
|
version(none) extern(C) static string concat_cvar (size_t count, ...)
|
||
|
|
{
|
||
|
|
string result;
|
||
|
|
|
||
|
|
va_list args;
|
||
|
|
|
||
|
|
va_start(args, count);
|
||
|
|
|
||
|
|
foreach(immutable i; 0 .. count)
|
||
|
|
{
|
||
|
|
auto arg = va_arg!LuaObject(args);
|
||
|
|
result ~= arg.toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
va_end(args);
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
//D-style variadics have an _arguments array that specifies
|
||
|
|
//the type of each passed argument.
|
||
|
|
static string concat_dvar (...) {
|
||
|
|
string result;
|
||
|
|
|
||
|
|
foreach (argtype; _arguments) {
|
||
|
|
assert (argtype == typeid(LuaObject));
|
||
|
|
auto arg = va_arg!(LuaObject)(_argptr);
|
||
|
|
|
||
|
|
result ~= arg.toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// get delegates from Lua
|
||
|
|
unittest
|
||
|
|
{
|
||
|
|
lua_getglobal(L, "string");
|
||
|
|
lua_getfield(L, -1, "match");
|
||
|
|
auto match = popValue!(string delegate(string, string))(L);
|
||
|
|
lua_pop(L, 1);
|
||
|
|
|
||
|
|
auto result = match("foobar@example.com", "([^@]+)@example.com");
|
||
|
|
assert(result == "foobar");
|
||
|
|
|
||
|
|
// multiple return values
|
||
|
|
luaL_dostring(L, `function multRet(a) return "foo", a end`);
|
||
|
|
lua_getglobal(L, "multRet");
|
||
|
|
auto multRet = popValue!(Tuple!(string, int) delegate(int))(L);
|
||
|
|
|
||
|
|
auto results = multRet(42);
|
||
|
|
assert(results[0] == "foo");
|
||
|
|
assert(results[1] == 42);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Nested call stack testing
|
||
|
|
unittest
|
||
|
|
{
|
||
|
|
alias string delegate(string) MyFun;
|
||
|
|
|
||
|
|
MyFun[string] funcs;
|
||
|
|
|
||
|
|
pushValue(L, (string name, MyFun fun) {
|
||
|
|
funcs[name] = fun;
|
||
|
|
});
|
||
|
|
lua_setglobal(L, "addFun");
|
||
|
|
|
||
|
|
pushValue(L, (string name, string arg) {
|
||
|
|
auto top = lua_gettop(L);
|
||
|
|
auto result = funcs[name](arg);
|
||
|
|
assert(lua_gettop(L) == top);
|
||
|
|
return result;
|
||
|
|
});
|
||
|
|
lua_setglobal(L, "callFun");
|
||
|
|
|
||
|
|
auto top = lua_gettop(L);
|
||
|
|
|
||
|
|
luaL_dostring(L, q{
|
||
|
|
addFun("echo", function(s) return s end)
|
||
|
|
local result = callFun("echo", "test")
|
||
|
|
assert(result == "test")
|
||
|
|
});
|
||
|
|
|
||
|
|
assert(lua_gettop(L) == top);
|
||
|
|
}
|
||
|
|
|
||
|
|
unittest
|
||
|
|
{
|
||
|
|
assert(lua_gettop(L) == 0);
|
||
|
|
lua_close(L);
|
||
|
|
}
|