1 module ws.script.lua;
2 
3 import
4 	ws.list,
5 	ws.io,
6 	ws.exception,
7 	ws.string,
8 	ws.sys.library;
9 
10 __gshared:
11 
12 
13 class Lua {
14 
15 	class Var {
16 
17 		enum: int { Number, String, Table, Reference };
18 
19 		int reference;
20 
21 		this(int t){
22 			if(t == Number)
23 				push(0);
24 			else if(t == String)
25 				push("");
26 			else if(t == Table)
27 				lua_createtable(state, 0, 0);
28 			reference = luaL_ref(state, LUA_REGISTRYINDEX);
29 		}
30 
31 		void setTop(){
32 			lua_rawgeti(state, LUA_REGISTRYINDEX, reference);
33 		}
34 
35 		~this(){
36 			luaL_unref(state, LUA_REGISTRYINDEX, reference);
37 		}
38 
39 		bool valid(){
40 			setTop();
41 			scope(exit)
42 				lua_pop(state, 1);
43 			return lua_type(state, -1) != 0;
44 		}
45 
46 		Var opIndex(string s){
47 			mixin(checkstack);
48 			setTop();
49 			scope(exit)
50 				lua_pop(state, 1);
51 			lua_getfield(state, LUA_REGISTRYINDEX, s.toStringz());
52 			return new Var(Reference);
53 		}
54 
55 		void opIndexAssign(T)(T value, string key){
56 			mixin(checkstack);
57 			setTop();
58 			push(value);
59 			lua_setfield(state, -2, key.toStringz());
60 			lua_pop(state, 1);
61 		}
62 
63 		void opIndexAssign(T)(T value, int key){
64 			opIndexAssign(value, tostring(key));
65 		}
66 
67 		int opApply(int delegate(Var) dg){
68 			mixin(checkstack);
69 			setTop();
70 			if(lua_type(state, -1) != 5)
71 				throw new Exception("Lua Var is not a table, cannot iterate");
72 			int result = 0;
73 			lua_pushnil(state);
74 			Var[] list;
75 			while(lua_next(state, -2))
76 				list ~= new Var(Reference);
77 			foreach(v; list){
78 				result = dg(v);
79 				if(result)
80 					break;
81 			}
82 			return result;
83 		}
84 
85 
86 		int opApply(int delegate(string, Var) dg){
87 			mixin(checkstack);
88 			setTop();
89 			if(lua_type(state, -1) != 5)
90 				throw new Exception("Lua Var is not a table, cannot iterate");
91 			int result = 0;
92 			lua_pushnil(state);
93 			Var[string] list;
94 			while(lua_next(state, -2)){
95 				bool n = false;
96 				if(lua_isnumber(state, -2)){
97 					setTop();
98 					lua_pushvalue(state, -3);
99 					lua_pushvalue(state, -3);
100 					n = true;
101 				}
102 				list[Lua.toString(-2)] = new Var(Reference);
103 				if(n)
104 					lua_settop(state, -(3)-1);
105 			}
106 			lua_pop(state, 1);
107 			foreach(k, e; list){
108 				result = dg(k, e);
109 				if(result)
110 					break;
111 			}
112 			return result;
113 		}
114 
115 
116 		Var opCall(Args...)(Args args){
117 			mixin(checkstack);
118 			int i=lua_gettop(state);
119 			scope(exit)
120 				assert(i == lua_gettop(state));
121 			setTop();
122 			foreach(a; args)
123 				push(a);
124 			check(lua_pcall(state, args.length, 1, 0));
125 			return new Var(Var.Reference);
126 		}
127 
128 		override string toString(){
129 			mixin(checkstack);
130 			setTop();
131 			scope(exit)
132 				lua_pop(state, 1);
133 			if(!valid()){
134 				return "nil";
135 			}else if(getType() == "table"){
136 				if(true)
137 					return "Table: 0x" ~ tostring(lua_topointer(state, -1));
138 				else{
139 					string s = "{\n";
140 					foreach(k, e; this)
141 						s ~= indent(k ~ " = " ~ e.toString()) ~ "\n";
142 					s ~= "}";
143 					return s;
144 				}
145 			}else if(getType() == "userdata"){
146 				if(this["__tostring"].valid()){
147 					return this["__tostring"]().toString();
148 				}else{
149 					PtrContainer!(void*) container = *cast(PtrContainer!(void*)*)lua_touserdata(state, -1);
150 					return "Userdata: 0x" ~ tostring(cast(void*)container.reference);
151 				}
152 			}else if(getType() == "function"){
153 				return "Function: 0x" ~ tostring(lua_topointer(state, -1));
154 			}else{
155 				return tostring(luaL_checklstring(state, -1, null));
156 			}
157 		}
158 
159 
160 		T userdata(T)(){
161 			mixin(checkstack);
162 			setTop();
163 			scope(exit)
164 				lua_pop(state, 1);
165 			return getUserdata!T("", -1);
166 		}
167 
168 
169 		void invalidate(){
170 			setTop();
171 			lua_getfield(state, LUA_REGISTRYINDEX, "nil");
172 			lua_setmetatable(state, -2);
173 		}
174 
175 		void setMetatable(string s){
176 			mixin(checkstack);
177 			setTop();
178 			luaL_getmetatable(state, s.toStringz());
179 			if(lua_type(state, -1) == 0)
180 				writeln("Warning: " ~ s ~ " is nil");
181 			//metatable(s).setTop();
182 			lua_setmetatable(state, -2);
183 			lua_pop(state, 1);
184 		}
185 
186 		Var getMetatable(){
187 			mixin(checkstack);
188 			setTop();
189 			scope(exit)
190 				lua_pop(state, 1);
191 			lua_getmetatable(state, -1);
192 			return new Var(Var.Reference);
193 		}
194 
195 		string getType(){
196 			mixin(checkstack);
197 			setTop();
198 			scope(exit)
199 				lua_pop(state, 1);
200 			int t = lua_type(state, -1);
201 			return tostring(lua_typename(state, t));
202 		}
203 
204 		private string indent(string s){
205 			string n = "\t";
206 			foreach(c; s){
207 				n ~= c;
208 				if(c == '\n')
209 					n ~= '\t';
210 			}
211 			return n;
212 		}
213 
214 	}
215 	
216 	
217 	void run(string s){
218 		check(luaL_loadstring(state, s.toStringz()) || lua_pcall(state, 0, -1, 0));
219 	}
220 	
221 
222 	void runFile(string path){
223 		fileStack ~= path;
224 		check(luaL_loadfile(state, path.toStringz()) || lua_pcall(state, 0, -1, 0));
225 		fileStack.popBack();
226 	}
227 
228 
229 	Var opIndex(string key){
230 		mixin(checkstack);
231 		lua_getfield(state, LUA_GLOBALSINDEX, key.toStringz());
232 		return new Var(Var.Reference);
233 	}
234 
235 
236 	void opIndexAssign(T)(T t, string key){
237 		mixin(checkstack);
238 		push(t);
239 		lua_setfield(state, LUA_GLOBALSINDEX, key.toStringz());
240 	}
241 	
242 
243 	Var table(Args...)(Args args){
244 		mixin(checkstack);
245 		lua_createtable(state, 0, 0);
246 		pushField(args);
247 		return new Var(Var.Reference);
248 	}
249 
250 
251 	Var metatable(string n){
252 		mixin(checkstack);
253 		luaL_newmetatable(state, n.toStringz());
254 		return new Var(Var.Reference);
255 	}
256 
257 
258 	/+
259 	void startMetatable(string name){
260 		luaL_newmetatable(state, name.toStringz());
261 	}
262 	+/
263 
264 	Var newUserdata(T)(T e, string n=""){
265 		mixin(checkstack);
266 		assert(e);
267 		//n = (n.length ? n : typeid(T).toString());
268 		auto container = cast(PtrContainer!T*)lua_newuserdata(state, (PtrContainer!T).sizeof);
269 		//lua_getfield(state, LUA_GLOBALSINDEX, n.toStringz());
270 		//lua_pushstring(state, "__index");
271 		//lua_pushvalue(state, -2);
272 		//lua_settable(state, -3); // metatable.__index = metatable
273 		//lua_setmetatable(state, -2);
274 		container.reference = e;
275 		return new Var(Var.Reference);
276 	}
277 
278 
279 	protected {
280 
281 		string printStack(){
282 			mixin(checkstack);
283 			string s = "Lua Stack {\n";
284 			for(int i=1; i<=lua_gettop(state); i++){
285 				lua_pushvalue(state, i);
286 				auto v = new Var(Var.Reference);
287 				s ~= "\t%s = %s\n".format(i, v);
288 			}
289 			s ~= "}\n";
290 			return s;
291 		}
292 	
293 		string toString(int i){
294 			return tostring(luaL_checklstring(state, i, null));
295 		}
296 	
297 	
298 		T get(T)(int idx){
299 			static if(is(T == string))
300 				return toString(idx);
301 			else static if(is(T == double) || is(T==long) || is(T==int))
302 				return cast(T)luaL_checknumber(state, idx);
303 			else static if(is(T == Var)){
304 				lua_pushvalue(state, idx);
305 				return new Var(Var.Reference);
306 			}else
307 				return getUserdata!T();
308 			//else static assert(0, "Return type not implemented");
309 		}
310 		
311 		void push(Ret, Args...)(Ret function(Args) f){
312 			push(delegate Ret(Args args){ return f(args); });
313 		}
314 		
315 		void push(Ret, Args...)(Ret delegate(Args) f){
316 			push(delegate int(int argc) nothrow {
317 				try {
318 					Args args;
319 					foreach(i, T; Args)
320 						args[i] = get!T(i+1);
321 					static if(is(Ret==void)){
322 						f(args);
323 						return 0;
324 					}else{
325 						push(f(args));
326 						return 1;
327 					}
328 				}catch(Exception e)
329 					writeln("Error in delegate: ", e);
330 				return 0;
331 			});
332 		}
333 		
334 		void push()(double n){
335 			lua_pushnumber(state, n);
336 		}
337 		
338 		void push()(string s){
339 			lua_pushstring(state, s.toStringz());
340 		}
341 		
342 		void push()(Var v){
343 			v.setTop();
344 		}
345 		
346 		void push()(Function f){
347 			closures ~= f;
348 			lua_pushnumber(state, closures.length-1);
349 			lua_pushcclosure(state, &staticClosure, 1);
350 		}
351 		
352 	
353 		void pushField()(){}
354 	
355 	
356 		void pushField(Args...)(string c, string g, Args args){
357 			lua_pushstring(state, n.toStringz);
358 			lua_getfield(state, LUA_GLOBALSINDEX, n.toStringz);
359 			lua_settable(state, -3);
360 			pushField(args);
361 		}
362 	
363 	
364 		void pushField(T, Args...)(string n, T t, Args args){
365 			push(t);
366 			lua_setfield(state, -2, n.toStringz());
367 			pushField(args);
368 		}
369 
370 
371 		private struct PtrContainer(T) {
372 			T reference;
373 		}
374 
375 
376 		/// return reference to userdata on stack
377 		T getUserdata(T)(string n="", int i = 1){
378 			mixin(checkstack);
379 			n = (n.length ? n : typeid(T).toString());
380 			auto container = cast(PtrContainer!T*)lua_touserdata(state, i);
381 			if(!container)
382 				luaL_argerror(state, i, "'%s' expected".format(n).toStringz());
383 			return container.reference;
384 			/+ Checks if ud's metatable is the same as luaL_getmetatable(state,n)
385 			n = (n.length ? n : typeid(T).toString());
386 			auto container = cast(PtrContainer!T*)luaL_checkudata(state, i, n.toStringz());
387 			if(!container)
388 				luaL_argerror(state, i, "'%' expected".format(n).toStringz());
389 			return container.reference;
390 			+/
391 		}
392 
393 	}
394 
395 
396 	alias nothrow int delegate(int) Function;
397 
398 	protected {
399 		/+
400 		const static string checkstack = "
401 			int checkstacksize=lua_gettop(state);
402 			scope(exit)
403 				assert(checkstacksize == lua_gettop(state), \"Before: \" ~ tostring(checkstacksize) ~ \"\nNow: \" ~ printStack());
404 		";
405 		+/
406 		const static string checkstack = ""; 
407 		Library library;
408 		state_ptr state;
409 		List!string fileStack;
410 		Function[] closures;
411 		size_t closureCurrent = 0;
412 	}
413 
414 	this(){
415 		library = luaLib.load();
416 		state = luaL_newstate();
417 		//luaL_openlibs(state);
418 		luaopen_base(state);
419 		luaopen_table(state);
420 
421 		/*lua_pushcclosure(state, luaopen_table, 0);
422 		pushLiteral("table");
423 		lua_call(state, 1, 0);*/
424 		fileStack = new List!string;
425 		states[state] = this;
426 	}
427 
428 
429 	~this(){
430 		lua_close(state);
431 	}
432 
433 
434 	state_ptr getState(){
435 		return state;
436 	}
437 	
438 
439 	private void check(int e){
440 		if(e){
441 			writeln("Lua error: ", lua_tolstring(state, -1, null));
442 			lua_settop(state, -2);
443 		}
444 	}
445 
446 
447 	static Lua[state_ptr] states;
448 
449 
450 	extern(C) static int staticClosure(state_ptr state){
451 		return states[state].closures[cast(size_t)lua_tonumber(state,-10003)](lua_gettop(state));
452 	}
453 
454 
455 }
456 
457 
458 extern(C){
459 	const int LUA_REGISTRYINDEX	= -10000;
460 	const int LUA_ENVIRONINDEX = -10001;
461 	const int LUA_GLOBALSINDEX = -10002;
462 
463 	alias void* state_ptr;
464 
465 	alias int function(state_ptr) lua_CFunction;
466 
467 	version(Windows)
468 		private const string LibraryFile = "lua51";
469 	version(Posix)
470 		private const string LibraryFile = "lua5.1";
471 
472 	void lua_pop(state_ptr state, int n){
473 		lua_settop(state, -(n)-1);
474 	}
475 
476 	void luaL_getmetatable(state_ptr s, const(char)* n){
477 		lua_getfield(s, LUA_REGISTRYINDEX, n);
478 	}
479 
480 	mixin library!(
481 		"luaLib", LibraryFile,
482 		"luaL_newstate", state_ptr function(),
483 		"luaL_openlibs", void function(state_ptr),
484 		"luaL_newmetatable", void function(state_ptr, const(char)*),
485 		"luaL_checknumber", double function(state_ptr, int),
486 		"luaL_loadstring", int function(state_ptr, const(char)*),
487 		"luaL_loadfile", int function(state_ptr, const(char)*),
488 		"luaL_checkudata", void* function(state_ptr, int, const(char)*),
489 		"luaL_argerror", int function(state_ptr, int, const(char)*),
490 		"luaL_ref", int function(state_ptr, int),
491 		"luaL_unref", void function(state_ptr, int, int),
492 		"lua_setfield", void function(state_ptr, int, const(char)*),
493 		"lua_getfield", void function(state_ptr, int, const(char)*),
494 		"lua_pcall", int function(state_ptr, int, int, int),
495 		"lua_call", void function(state_ptr, int, int),
496 		"luaL_checklstring", const(char*) function(state_ptr, int, size_t*),
497 		"luaopen_base", int function(state_ptr),
498 		"luaopen_table", int function(state_ptr),
499 		"lua_close", void function(state_ptr),
500 		"lua_createtable", void function(state_ptr, int, int),
501 		"lua_setmetatable", int function(state_ptr, int),
502 		"lua_getmetatable", int function(state_ptr, int),
503 		"lua_settable", void function(state_ptr, int),
504 		"lua_pushvalue", void function(state_ptr, int),
505 		"lua_isstring", int function(state_ptr, int),
506 		"lua_isnumber", int function(state_ptr, int),
507 		"lua_settop", void function(state_ptr, int),
508 		"lua_gettop", int function(state_ptr),
509 		"lua_tonumber", double function(state_ptr, int),
510 		"lua_tolstring", const(char)* function(state_ptr, int, size_t*),
511 		"lua_touserdata", void* function(state_ptr, int),
512 		"lua_topointer", void* function(state_ptr, int),
513 		"lua_pushstring", void function(state_ptr, const char*),
514 		"lua_pushnumber", void function(state_ptr, double),
515 		"lua_pushcclosure", "void function(state_ptr, lua_CFunction, int)",
516 		"lua_newuserdata", void* function(state_ptr, size_t),
517 		"lua_rawseti", void function(state_ptr, int, int),
518 		"lua_rawgeti", void function(state_ptr, int, int),
519 		"lua_pushnil", void function(state_ptr),
520 		"lua_next", int function(state_ptr, int),
521 		"lua_type", int function(state_ptr, int),
522 		"lua_typename", const(char)* function(state_ptr, int)
523 	);
524 	
525 }