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); }