1 module luad.lfunction;
2 
3 import luad.base;
4 import luad.table;
5 import luad.stack;
6 import luad.conversions.functions;
7 
8 import luad.c.all;
9 
10 /// Represents a Lua function.
11 struct LuaFunction
12 {
13 	/// LuaFunction sub-types $(DPREF base, LuaObject) through this reference.
14 	LuaObject object;
15 	alias object this;
16 
17 	version(none) package this(lua_State* L, int idx)
18 	{
19 		LuaObject.checkType(L, idx, LUA_TFUNCTION, "LuaFunction");
20 		object = LuaObject(L, idx);
21 	}
22 
23 	/**
24 	 * Call this function and collect all return values as
25 	 * an array of $(DPREF base, LuaObject) references.
26 	 * Examples:
27 	 -----------------------
28 	lua.doString(`function f(...) return ... end`);
29 	auto f = lua.get!LuaFunction("f");
30 
31 	LuaObject[] ret = f(1.2, "hello!", true);
32 
33 	assert(ret[0].to!double() == 1.2);
34 	assert(ret[1].to!string() == "hello!");
35 	assert(ret[2].to!bool());
36 	 -----------------------
37 	 */
38 	LuaObject[] opCall(U...)(U args)
39 	{
40 		return call!(LuaVariableReturn!(LuaObject[]))(args).returnValues;
41 	}
42 
43 	/**
44 	 * Call this function.
45 	 * Params:
46 	 *	 T = expected return type.
47 	 *	 args = list of arguments.
48 	 * Returns:
49 	 *	 Return value of type $(D T), or nothing if $(D T) was unspecified.
50 	 *   See $(DPMODULE2 conversions,functions) for how to
51 	 *   catch multiple return values.
52 	 * Examples:
53 	 * ------------------
54 	lua.doString(`function ask(question) return 42 end`);
55 	auto ask = lua.get!LuaFunction("ask");
56 
57 	auto answer = ask.call!int("What's the answer to life, the universe and everything?");
58 	assert(answer == 42);
59 	 * ------------------
60 	 */
61 	T call(T = void, U...)(U args)
62 	{
63 		this.push();
64 		foreach(arg; args)
65 			pushValue(this.state, arg);
66 
67 		return callWithRet!T(this.state, args.length);
68 	}
69 
70 	/**
71 	 * Set a new environment for this function.
72 	 *
73 	 * The environment of a function is the table used for looking up non-local (global) variables.
74 	 * Params:
75 	 *    env = new environment
76 	 * Examples:
77 	 * -------------------
78 	 * lua["foo"] = "bar";
79 	 * auto func = lua.loadString(`return foo`);
80 	 * assert(func.call!string() == "bar");
81 	 *
82 	 * auto env = lua.wrap(["foo": "test"]);
83 	 * func.setEnvironment(env);
84 	 * assert(func.call!string() == "test");
85 	 * -------------------
86 	 */
87 	void setEnvironment(ref LuaTable env)
88 	in { assert(this.state == env.state); }
89 	body
90 	{
91 		this.push();
92 		env.push();
93 		lua_setfenv(this.state, -2);
94 		lua_pop(this.state, 1);
95 	}
96 
97 	/**
98 	 * Dump this function as a binary chunk of Lua bytecode to the specified
99 	 * writer delegate.  Multiple chunks may be produced to dump a single
100 	 * function.
101 	 *
102 	 * Params:
103 	 *    writer = delegate to forward writing calls to
104 	 *
105 	 *  If the delegate returns $(D false) for any of the chunks,
106 	 *  the _dump process ends, and the writer won't be called again.
107 	 */
108 	bool dump(scope bool delegate(in void[]) writer)
109 	{
110 		alias typeof(writer) LuaWriter;
111 
112 		extern(C) static int luaCWriter(lua_State* L, const void* p, size_t sz, void* ud)
113 		{
114 			auto writer = *cast(LuaWriter*)ud;
115 			return writer(p[0..sz]) ? 0 : 1;
116 		}
117 
118 		this.push();
119 		auto ret = lua_dump(this.state, &luaCWriter, &writer);
120 		lua_pop(this.state, 1);
121 		return ret == 0;
122 	}
123 }
124 
125 version(unittest)
126 {
127 	import luad.testing;
128 	import std.variant;
129 	import std.typecons;
130 }
131 
132 unittest
133 {
134 	lua_State* L = luaL_newstate();
135 	scope(success) lua_close(L);
136 	luaL_openlibs(L);
137 
138 	lua_getglobal(L, "tostring");
139 	auto tostring = popValue!LuaFunction(L);
140 
141 	LuaObject[] ret = tostring(123);
142 	assert(ret[0].to!string() == "123");
143 
144 	assert(tostring.call!string(123) == "123");
145 
146 	tostring.call(321);
147 
148 	// Multiple return values
149 	luaL_dostring(L, "function singleRet() return 42 end");
150 	lua_getglobal(L, "singleRet");
151 	auto singleRet = popValue!LuaFunction(L);
152 
153 	auto singleRetResult = singleRet.call!(Tuple!int)();
154 	assert(singleRetResult[0] == 42);
155 
156 	alias Algebraic!(string, double) BasicLuaType;
157 	BasicLuaType a = "foo";
158 	BasicLuaType b = 1.5;
159 
160 	pushValue(L, [a, b]);
161 	lua_setglobal(L, "test");
162 
163 	luaL_dostring(L, "function multRet() return unpack(test) end");
164 	lua_getglobal(L, "multRet");
165 	auto multRet = popValue!LuaFunction(L);
166 
167 	auto result = multRet.call!(Tuple!(string, double))();
168 	assert(result[0] == a);
169 	assert(result[1] == b);
170 
171 	unittest_lua(L, `function getName() return "Foo", "Bar" end`);
172 
173 	lua_getglobal(L, "getName");
174 	auto getName = popValue!LuaFunction(L);
175 
176 	string[2] arrayRet = getName.call!(string[2])();
177 	assert(arrayRet[0] == "Foo");
178 	assert(arrayRet[1] == "Bar");
179 
180 	// setEnvironment
181 	pushValue(L, ["test": [42]]);
182 	auto env = popValue!LuaTable(L);
183 
184 	lua_getglobal(L, "unpack");
185 	env["unpack"] = popValue!LuaObject(L);
186 
187 	multRet.setEnvironment(env);
188 	assert(multRet.call!int() == 42);
189 
190 }