1 module luad.state;
2 
3 import std.array, std.range;
4 
5 import std.string : toStringz;
6 import std.typecons : isTuple;
7 
8 import luad.c.all;
9 import luad.stack;
10 import luad.conversions.classes;
11 
12 import luad.base, luad.table, luad.lfunction, luad.dynamic, luad.error;
13 
14 /// Specify error handling scheme for $(MREF LuaState.doString) and $(MREF LuaState.doFile).
15 enum LuaErrorHandler
16 {
17 	None, /// No extra error handler.
18 	Traceback /// Append a stack traceback to the error message.
19 }
20 
21 /**
22  * Represents a Lua state instance.
23  */
24 class LuaState
25 {
26 private:
27 	lua_State* L; // underlying state
28 	LuaTable _G, _R; // global and registry tables
29 	LuaFunction traceback; // debug.traceback, set in openLibs()
30 	bool owner = false; // whether or not to close the underlying state in the finalizer
31 
32 public:
33 	/**
34 	 * Create a new, empty Lua state. The standard library is not loaded.
35 	 *
36 	 * If an uncaught error for any operation on this state
37 	 * causes a Lua panic for the underlying state,
38 	 * an exception of type $(DPREF error, LuaErrorException) is thrown.
39 	 *
40 	 * See_Also: $(MREF LuaState.openLibs)
41 	 */
42 	this()
43 	{
44 		lua_State* L = luaL_newstate();
45 		owner = true;
46 
47 		extern(C) static int panic(lua_State* L)
48 		{
49 			size_t len;
50 			const(char)* cMessage = lua_tolstring(L, -1, &len);
51 			string message = cMessage[0 .. len].idup;
52 
53 			lua_pop(L, 1);
54 
55 			throw new LuaErrorException(message);
56 		}
57 
58 		lua_atpanic(L, &panic);
59 
60 		this(L);
61 	}
62 
63 	/**
64 	 * Create a D wrapper for an existing Lua state.
65 	 *
66 	 * The new $(D LuaState) object does not assume ownership of the state.
67 	 * Params:
68 	 *	 L = state to wrap
69 	 * Note:
70 	 *	 The panic function is not changed - a Lua panic will not throw a D exception!
71 	 * See_Also:
72 		$(MREF LuaState.setPanicHandler)
73 	 */
74 	this(lua_State* L)
75 	{
76 		this.L = L;
77 		_G = LuaTable(L, LUA_GLOBALSINDEX);
78 		_R = LuaTable(L, LUA_REGISTRYINDEX);
79 
80 		lua_pushlightuserdata(L, cast(void*)this);
81 		lua_setfield(L, LUA_REGISTRYINDEX, "__dstate");
82 	}
83 
84 	~this()
85 	{
86 		if(owner)
87 		{
88 			_R.release();
89 			_G.release();
90 			traceback.release();
91 			lua_close(L);
92 		}
93 		else // Unregister state
94 		{
95 			lua_pushnil(L);
96 			lua_setfield(L, LUA_REGISTRYINDEX, "__dstate");
97 		}
98 	}
99 
100 	/// The underlying $(D lua_State) pointer for interfacing with C.
101 	@property lua_State* state() nothrow pure @safe
102 	{
103 		return L;
104 	}
105 
106 	/**
107 	 * Get the $(D LuaState) instance for a Lua state.
108 	 * Params:
109 	 *	 L = Lua state
110 	 * Returns:
111 	 *	 $(D LuaState) for the given $(D lua_State*), or $(D null) if a $(D LuaState) is not currently attached to the state
112 	 */
113 	static LuaState fromPointer(lua_State* L) @trusted
114 	{
115 		lua_getfield(L, LUA_REGISTRYINDEX, "__dstate");
116 		auto lua = cast(LuaState)lua_touserdata(L, -1);
117 		lua_pop(L, 1);
118 		return lua;
119 	}
120 
121 	/// Open the standard library.
122 	void openLibs() @trusted
123 	{
124 		luaL_openlibs(L);
125 		traceback = _G.get!LuaFunction("debug", "traceback");
126 	}
127 
128 	/// The global table for this instance.
129 	@property LuaTable globals() @trusted
130 	{
131 		return _G;
132 	}
133 
134 	/// The _registry table for this instance.
135 	@property LuaTable registry() @trusted
136 	{
137 		return _R;
138 	}
139 
140 	/**
141 	 * Set a new panic handler.
142 	 * Params:
143 	 *	 onPanic = new panic handler
144 	 * Examples:
145 	 * ----------------------
146 	auto L = luaL_newstate(); // found in luad.c.all
147 	auto lua = new LuaState(L);
148 
149 	static void panic(LuaState lua, in char[] error)
150 	{
151 		throw new LuaErrorException(error.idup);
152 	}
153 
154 	lua.setPanicHandler(&panic);
155 	 * ----------------------
156 	 */
157 	void setPanicHandler(void function(LuaState, in char[]) onPanic) @trusted
158 	{
159 		extern(C) static int panic(lua_State* L)
160 		{
161 			size_t len;
162 			const(char)* message = lua_tolstring(L, -1, &len);
163 			auto error = message[0 .. len];
164 
165 			lua_getfield(L, LUA_REGISTRYINDEX, "__dpanic");
166 			auto callback = cast(void function(LuaState, in char[]))lua_touserdata(L, -1);
167 			assert(callback);
168 
169 			scope(exit) lua_pop(L, 2);
170 
171 			callback(LuaState.fromPointer(L), error);
172 			return 0;
173 		}
174 
175 		lua_pushlightuserdata(L, onPanic);
176 		lua_setfield(L, LUA_REGISTRYINDEX, "__dpanic");
177 
178 		lua_atpanic(L, &panic);
179 	}
180 
181 	/*
182 	 * push debug.traceback error handler to the stack
183 	 */
184 	private void pushErrorHandler()
185 	{
186 		if(traceback.isNil)
187 			throw new Exception("LuaErrorHandler.Traceback requires openLibs()");
188 		traceback.push();
189 	}
190 
191 	/*
192 	 * a variant of luaL_do(string|file) with advanced error handling
193 	 */
194 	private void doChunk(alias loader)(in char[] s, LuaErrorHandler handler)
195 	{
196 		if(handler == LuaErrorHandler.Traceback)
197 			pushErrorHandler();
198 
199 		if(loader(L, toStringz(s)) || lua_pcall(L, 0, LUA_MULTRET, handler == LuaErrorHandler.Traceback? -2 : 0))
200 			lua_error(L);
201 
202 		if(handler == LuaErrorHandler.Traceback)
203 			lua_remove(L, 1);
204 	}
205 
206 	/**
207 	 * Compile a string of Lua _code.
208 	 * Params:
209 	 *	 code = _code to compile
210 	 * Returns:
211 	 *   Loaded _code as a function.
212 	 */
213 	LuaFunction loadString(in char[] code) @trusted
214 	{
215 		if(luaL_loadstring(L, toStringz(code)) != 0)
216 			lua_error(L);
217 
218 		return popValue!LuaFunction(L);
219 	}
220 
221 	/**
222 	 * Compile a file of Lua code.
223 	 * Params:
224 	 *	 path = _path to file
225 	 * Returns:
226 	 *   Loaded code as a function.
227 	 */
228 	LuaFunction loadFile(in char[] path) @trusted
229 	{
230 		if(luaL_loadfile(L, toStringz(path)) != 0)
231 			lua_error(L);
232 
233 		return popValue!LuaFunction(L);
234 	}
235 
236 	/**
237 	 * Execute a string of Lua _code.
238 	 * Params:
239 	 *	 code = _code to run
240 	 *   handler = error handling scheme
241 	 * Returns:
242 	 *	 Any _code return values
243 	 * See_Also:
244 	 *   $(MREF LuaErrorHandler)
245 	 */
246 	LuaObject[] doString(in char[] code, LuaErrorHandler handler = LuaErrorHandler.None) @trusted
247 	{
248 		auto top = lua_gettop(L);
249 
250 		doChunk!(luaL_loadstring)(code, handler);
251 
252 		auto nret = lua_gettop(L) - top;
253 
254 		return popStack(L, nret);
255 	}
256 
257 	/**
258 	 * Execute a file of Lua code.
259 	 * Params:
260 	 *	 path = _path to file
261 	 *   handler = error handling scheme
262 	 * Returns:
263 	 *	 Any script return values
264 	 * See_Also:
265 	 *   $(MREF LuaErrorHandler)
266 	 */
267 	LuaObject[] doFile(in char[] path, LuaErrorHandler handler = LuaErrorHandler.None) @trusted
268 	{
269 		auto top = lua_gettop(L);
270 
271 		doChunk!(luaL_loadfile)(path, handler);
272 
273 		auto nret = lua_gettop(L) - top;
274 
275 		return popStack(L, nret);
276 	}
277 
278 	/**
279 	 * Create a new, empty table.
280 	 * Returns:
281 	 *	 The new table
282 	 */
283 	LuaTable newTable()() @trusted
284 	{
285 		return newTable(0, 0);
286 	}
287 
288 	/**
289 	 * Create a new, empty table with pre-allocated space for members.
290 	 * Params:
291 	 *	 narr = number of pre-allocated array slots
292 	 *	 nrec = number of pre-allocated non-array slots
293 	 * Returns:
294 	 *	 The new table
295 	 */
296 	LuaTable newTable()(uint narr, uint nrec) @trusted
297 	{
298 		lua_createtable(L, narr, nrec);
299 		return popValue!LuaTable(L);
300 	}
301 
302 	/**
303 	 * Create a new table from an $(D InputRange).
304 	 * If the element type of the range is $(D Tuple!(T, U)),
305 	 * then each element makes up a key-value pair, where
306 	 * $(D T) is the key and $(D U) is the value of the pair.
307 	 * For any other element type $(D T), a table with sequential numeric
308 	 * keys is created (an array).
309 	 * Params:
310 	 *   range = $(D InputRange) of key-value pairs or elements
311 	 * Returns:
312 	 *	 The new table
313 	 */
314 	LuaTable newTable(Range)(Range range) @trusted if(isInputRange!Range)
315 	{
316 		alias ElementType!Range Elem;
317 
318 		static if(hasLength!Range)
319 		{
320 			immutable numElements = range.length;
321 			assert(numElements < int.max, "lua_createtable only supports int.max many elements");
322 		}
323 		else
324 		{
325 			immutable numElements = 0;
326 		}
327 
328 		static if(isTuple!Elem) // Key-value pairs
329 		{
330 			static assert(range.front.length == 2, "key-value tuple must have exactly 2 values.");
331 
332 			lua_createtable(L, 0, cast(int)numElements);
333 
334 			foreach(pair; range)
335 			{
336 				pushValue(L, pair[0]);
337 				pushValue(L, pair[1]);
338 				lua_rawset(L, -3);
339 			}
340 		}
341 		else // Sequential table
342 		{
343 			lua_createtable(L, cast(int)numElements, 0);
344 
345 			int i = 1;
346 
347 			foreach(value; range)
348 			{
349 				pushValue(L, value);
350 				lua_rawseti(L, -2, i);
351 				++i;
352 			}
353 		}
354 
355 		return popValue!LuaTable(L);
356 	}
357 
358 	/**
359 	 * Wrap a D value in a Lua reference.
360 	 *
361 	 * Note that using this method is only necessary in certain situations,
362 	 * such as when you want to act on the reference before fully exposing it to Lua.
363 	 * Params:
364 	 *   T = type of reference. Must be $(D LuaObject), $(D LuaTable), $(D LuaFunction) or $(D LuaDynamic).
365 	 *   Defaults to $(D LuaObject).
366 	 *	 value = D value to _wrap
367 	 * Returns:
368 	 *	 A Lua reference to value
369 	 */
370 	T wrap(T = LuaObject, U)(U value) @trusted if(is(T : LuaObject) || is(T == LuaDynamic))
371 	{
372 		pushValue(L, value);
373 		return popValue!T(L);
374 	}
375 
376 	/**
377 	 * Register a D class or struct with Lua.
378 	 *
379 	 * This method exposes a type's constructors and static interface to Lua.
380 	 * Params:
381 	 *    T = class or struct to register
382 	 * Returns:
383 	 *    Reference to the registered type in Lua
384 	 */
385 	LuaObject registerType(T)() @trusted
386 	{
387 		pushStaticTypeInterface!T(L);
388 		return popValue!LuaObject(L);
389 	}
390 
391 	/**
392 	 * Same as calling $(D globals._get) with the same arguments.
393 	 * See_Also:
394 	 *	 $(DPREF table, LuaTable._get)
395 	 */
396 	T get(T, U...)(U args)
397 	{
398 		return globals.get!T(args);
399 	}
400 
401 	/**
402 	 * Same as calling $(D globals.get!LuaObject) with the same arguments.
403 	 * See_Also:
404 	 *	 $(DPREF table, LuaTable._opIndex)
405 	 */
406 	LuaObject opIndex(T...)(T args)
407 	{
408 		return globals.get!LuaObject(args);
409 	}
410 
411 	/**
412 	 * Same as calling $(D globals._set) with the same arguments.
413 	 * See_Also:
414 	 *	 $(DPREF table, LuaTable._set)
415 	 */
416 	void set(T, U)(T key, U value)
417 	{
418 		globals.set(key, value);
419 	}
420 
421 	/**
422 	 * Same as calling $(D globals._opIndexAssign) with the same arguments.
423 	 * See_Also:
424 	 *	 $(DPREF table, LuaTable._opIndexAssign)
425 	 */
426 	void opIndexAssign(T, U...)(T value, U args)
427 	{
428 		globals()[args] = value;
429 	}
430 }
431 
432 version(unittest)
433 {
434 	import luad.testing;
435 	import std.string : splitLines;
436 	private LuaState lua;
437 }
438 
439 unittest
440 {
441 	lua = new LuaState;
442 	assert(LuaState.fromPointer(lua.state) == lua);
443 
444 	lua.openLibs();
445 
446 	//default panic handler
447 	try
448 	{
449 		lua.doString(`error("Hello, D!")`, LuaErrorHandler.Traceback);
450 		assert(false);
451 	}
452 	catch(LuaErrorException e)
453 	{
454 		auto lines = splitLines(e.msg);
455 		assert(lines.length > 1);
456 		assert(lines[0] == `[string "error("Hello, D!")"]:1: Hello, D!`);
457 	}
458 
459 	lua.set("success", false);
460 	assert(!lua.get!bool("success"));
461 
462 	lua.doString(`success = true`);
463 	assert(lua.get!bool("success"));
464 
465 	auto foo = lua.wrap!LuaTable([1, 2, 3]);
466 	foo[4] = "test"; // Lua tables start at 1
467 	lua["foo"] = foo;
468 	unittest_lua(lua.state, `
469 		for i = 1, 3 do
470 			assert(foo[i] == i)
471 		end
472 		assert(foo[4] == "test")
473 	`);
474 
475 	LuaFunction multipleReturns = lua.loadString(`return 1, "two", 3`);
476 	LuaObject[] results = multipleReturns();
477 
478 	assert(results.length == 3);
479 	assert(results[0].type == LuaType.Number);
480 	assert(results[1].type == LuaType.String);
481 	assert(results[2].type == LuaType.Number);
482 }
483 
484 unittest // LuaState.newTable(range)
485 {
486 	import std.algorithm;
487 
488 	auto input = [1, 2, 3];
489 
490 	lua["tab"] = lua.newTable(input);
491 
492 	unittest_lua(lua.state, `
493 		assert(#tab == 3)
494 		for i = 1, 3 do
495 			assert(tab[i] == i)
496 		end
497 	`);
498 
499 	lua["tab"] = lua.newTable(filter!(i => i == 2)(input));
500 
501 	unittest_lua(lua.state, `
502 		assert(#tab == 1)
503 		assert(tab[1] == 2)
504 	`);
505 
506 	auto keys = iota(7, 14);
507 	auto values = repeat(42);
508 
509 	lua["tab"] = lua.newTable(zip(keys, values));
510 
511 	unittest_lua(lua.state, `
512 		assert(not tab[1])
513 		assert(not tab[6])
514 		for i = 7, 13 do
515 			assert(tab[i] == 42)
516 		end
517 		assert(not tab[14])
518 	`);
519 }
520 
521 unittest
522 {
523 	static class Test
524 	{
525 		private:
526 		/+ Not working as of 2.062
527 		static int priv;
528 		static void priv_fun() {}
529 		+/
530 
531 		public:
532 		static int pub = 123;
533 
534 		static string foo() { return "bar"; }
535 
536 		this(int i)
537 		{
538 			_bar = i;
539 		}
540 
541 		int bar(){ return _bar; }
542 		int _bar;
543 	}
544 
545 	lua["Test"] = lua.registerType!Test();
546 
547 	unittest_lua(lua.state, `
548 		assert(type(Test) == "table")
549 		-- TODO: private members are currently pushed too...
550 		--assert(Test.priv == nil)
551 		--assert(Test.priv_fun == nil)
552 		assert(Test._foo == nil)
553 		assert(Test._bar == nil)
554 
555 		local test = Test(42)
556 		assert(test:bar() == 42)
557 
558 		assert(Test.pub == 123)
559 		assert(Test.foo() == "bar")
560 	`);
561 }
562 
563 version(Windows) unittest // Enable carefully, see issue #35
564 {
565 	// setPanicHandler, keep this test last
566 	static void panic(LuaState lua, in char[] error)
567 	{
568 		throw new Exception("hijacked error!");
569 	}
570 
571 	lua.setPanicHandler(&panic);
572 
573 	try
574 	{
575 		lua.doString(`error("test")`);
576 	}
577 	catch(Exception e)
578 	{
579 		assert(e.msg == "hijacked error!");
580 	}
581 }