Files
lunch-games/source/luad/lfunction.d
2025-04-29 01:59:15 +02:00

191 lines
4.5 KiB
D

module luad.lfunction;
import luad.base;
import luad.table;
import luad.stack;
import luad.conversions.functions;
import luad.c.all;
/// Represents a Lua function.
struct LuaFunction
{
/// LuaFunction sub-types $(DPREF base, LuaObject) through this reference.
LuaObject object;
alias object this;
version(none) package this(lua_State* L, int idx)
{
LuaObject.checkType(L, idx, LUA_TFUNCTION, "LuaFunction");
object = LuaObject(L, idx);
}
/**
* Call this function and collect all return values as
* an array of $(DPREF base, LuaObject) references.
* Examples:
-----------------------
lua.doString(`function f(...) return ... end`);
auto f = lua.get!LuaFunction("f");
LuaObject[] ret = f(1.2, "hello!", true);
assert(ret[0].to!double() == 1.2);
assert(ret[1].to!string() == "hello!");
assert(ret[2].to!bool());
-----------------------
*/
LuaObject[] opCall(U...)(U args)
{
return call!(LuaVariableReturn!(LuaObject[]))(args).returnValues;
}
/**
* Call this function.
* Params:
* T = expected return type.
* args = list of arguments.
* Returns:
* Return value of type $(D T), or nothing if $(D T) was unspecified.
* See $(DPMODULE2 conversions,functions) for how to
* catch multiple return values.
* Examples:
* ------------------
lua.doString(`function ask(question) return 42 end`);
auto ask = lua.get!LuaFunction("ask");
auto answer = ask.call!int("What's the answer to life, the universe and everything?");
assert(answer == 42);
* ------------------
*/
T call(T = void, U...)(U args)
{
this.push();
foreach(arg; args)
pushValue(this.state, arg);
return callWithRet!T(this.state, args.length);
}
/**
* Set a new environment for this function.
*
* The environment of a function is the table used for looking up non-local (global) variables.
* Params:
* env = new environment
* Examples:
* -------------------
* lua["foo"] = "bar";
* auto func = lua.loadString(`return foo`);
* assert(func.call!string() == "bar");
*
* auto env = lua.wrap(["foo": "test"]);
* func.setEnvironment(env);
* assert(func.call!string() == "test");
* -------------------
*/
void setEnvironment(ref LuaTable env)
in { assert(this.state == env.state); }
body
{
this.push();
env.push();
lua_setfenv(this.state, -2);
lua_pop(this.state, 1);
}
/**
* Dump this function as a binary chunk of Lua bytecode to the specified
* writer delegate. Multiple chunks may be produced to dump a single
* function.
*
* Params:
* writer = delegate to forward writing calls to
*
* If the delegate returns $(D false) for any of the chunks,
* the _dump process ends, and the writer won't be called again.
*/
bool dump(scope bool delegate(in void[]) writer)
{
alias typeof(writer) LuaWriter;
extern(C) static int luaCWriter(lua_State* L, const void* p, size_t sz, void* ud)
{
auto writer = *cast(LuaWriter*)ud;
return writer(p[0..sz]) ? 0 : 1;
}
this.push();
auto ret = lua_dump(this.state, &luaCWriter, &writer);
lua_pop(this.state, 1);
return ret == 0;
}
}
version(unittest)
{
import luad.testing;
import std.variant;
import std.typecons;
}
unittest
{
lua_State* L = luaL_newstate();
scope(success) lua_close(L);
luaL_openlibs(L);
lua_getglobal(L, "tostring");
auto tostring = popValue!LuaFunction(L);
LuaObject[] ret = tostring(123);
assert(ret[0].to!string() == "123");
assert(tostring.call!string(123) == "123");
tostring.call(321);
// Multiple return values
luaL_dostring(L, "function singleRet() return 42 end");
lua_getglobal(L, "singleRet");
auto singleRet = popValue!LuaFunction(L);
auto singleRetResult = singleRet.call!(Tuple!int)();
assert(singleRetResult[0] == 42);
alias Algebraic!(string, double) BasicLuaType;
BasicLuaType a = "foo";
BasicLuaType b = 1.5;
pushValue(L, [a, b]);
lua_setglobal(L, "test");
luaL_dostring(L, "function multRet() return unpack(test) end");
lua_getglobal(L, "multRet");
auto multRet = popValue!LuaFunction(L);
auto result = multRet.call!(Tuple!(string, double))();
assert(result[0] == a);
assert(result[1] == b);
unittest_lua(L, `function getName() return "Foo", "Bar" end`);
lua_getglobal(L, "getName");
auto getName = popValue!LuaFunction(L);
string[2] arrayRet = getName.call!(string[2])();
assert(arrayRet[0] == "Foo");
assert(arrayRet[1] == "Bar");
// setEnvironment
pushValue(L, ["test": [42]]);
auto env = popValue!LuaTable(L);
lua_getglobal(L, "unpack");
env["unpack"] = popValue!LuaObject(L);
multRet.setEnvironment(env);
assert(multRet.call!int() == 42);
}