1 module zua.vm.std.stdlib;
2 import zua.vm.std.math;
3 import zua.vm.std.table;
4 import zua.vm.std.string;
5 import zua.vm.std.coroutine;
6 import zua.vm.std.os;
7 import zua.vm.std.bit32;
8 import zua.vm.engine;
9 import zua.vm.reflection;
10 import std.typecons;
11 import std.variant;
12 import std.conv;
13 import std.string;
14 
15 /** Flags that may be used for controlling which libraries are accessible by Lua code; can be ORed together */
16 enum GlobalOptions {
17 	FullAccess = 0,
18 
19 	/** Do not provide IO access (this includes functions such as `print` and the entire `io` library) */
20 	NoIO = 1,
21 
22 	/** Do not provide access to debug facilities */
23 	NoDebug = 2,
24 
25 	/** Do not add backported features from Lua 5.2, such as the bit32 library */
26 	NoBackport = 4,
27 
28 	/** Do not give users access to the `collectgarbage` function */
29 	NoGC = 8,
30 }
31 
32 private void lua_assert(bool v, Nullable!string message) {
33 	if (!v)
34 		throw new Exception(message.isNull ? "assertion failed!" : message.get);
35 }
36 
37 private Algebraic!(void, bool, double) lua_collectgarbage(Nullable!string optn, Nullable!double) {
38 	import core.memory : GC;
39 
40 	string opt = optn.isNull ? "collect" : optn.get;
41 	if (opt == "collect") {
42 		GC.enable();
43 		GC.collect();
44 	}
45 	else if (opt == "stop") {
46 		GC.disable();
47 	}
48 	else if (opt == "restart") {
49 		GC.enable();
50 	}
51 	else if (opt == "count") {
52 		return Algebraic!(void, bool, double)(GC.stats().usedSize / 1024.0);
53 	}
54 	else if (opt == "step") {
55 		GC.collect();
56 		return Algebraic!(void, bool, double)(true);
57 	}
58 	else if (opt == "setpause" || opt == "setstepmul") {
59 		return Algebraic!(void, bool, double)(200.0);
60 	}
61 	else {
62 		throw new Exception("bad argument #1 to 'collectgarbage' (invalid option '" ~ opt ~ "')");
63 	}
64 
65 	return Algebraic!(void, bool, double)();
66 }
67 
68 private void lua_error(Value message, Nullable!int) {
69 	if (message.type == ValueType.Number) {
70 		throw new LuaError(message.luaToString);
71 	}
72 	else {
73 		throw new LuaError(message);
74 	}
75 }
76 
77 private TableValue lua_getfenv(Nullable!(Algebraic!(FunctionValue, long)) f) {
78 	FunctionValue caller = callstack[$ - 1].func;
79 	FunctionValue func;
80 	if (f.isNull) {
81 		func = caller;
82 	}
83 	else if (FunctionValue* mfunc = f.get.peek!FunctionValue) {
84 		func = *mfunc;
85 	}
86 	else {
87 		const long lvl = f.get.get!long;
88 		if (lvl < 0) {
89 			throw new Exception("bad argument #1 to 'getfenv' (level must be non-negative)");
90 		}
91 		else if (lvl == 0) {
92 			return *getGlobalEnvPtr;
93 		}
94 		else if (lvl > callstack.length) {
95 			throw new Exception("bad argument #1 to 'getfenv' (invalid level)");
96 		}
97 		else {
98 			func = callstack[$ - lvl].func;
99 		}
100 	}
101 
102 	TableValue res = func.env;
103 	if (res is null) {
104 		return *getGlobalEnvPtr;
105 	}
106 	else {
107 		return res;
108 	}
109 }
110 
111 private Value lua_getmetatable(Value val) {
112 	TableValue metatable = val.metatable;
113 
114 	if (metatable is null)
115 		return Value();
116 
117 	Value fake = metatable.get(Value("__metatable"));
118 	if (!fake.isNil)
119 		return fake;
120 
121 	return Value(metatable);
122 }
123 
124 private Value[] ipairsIterator(TableValue t, long key) {
125 	key++;
126 	Value* v = t.rawget(Value(key));
127 	if (v == null || v.isNil) {
128 		return [];
129 	}
130 	else {
131 		return [Value(key), *v];
132 	}
133 }
134 
135 private Value ipairsIteratorValue = exposeFunction!(ipairsIterator, "?");
136 
137 private Tuple!(Value, TableValue, double) lua_ipairs(TableValue input) {
138 	return tuple(ipairsIteratorValue, input, 0.0);
139 }
140 
141 private Value lua_newproxy(Nullable!Value basen) {
142 	Value base = basen.isNull ? Value() : basen.get;
143 	if (base.type == ValueType.Userdata && base.metatable !is null && base.userdata.data is null) {
144 		return Value(new UserdataValue(base.metatable));
145 	}
146 	else if (base.type == ValueType.Boolean || base.type == ValueType.Nil) {
147 		return Value(new UserdataValue(base.toBool ? new TableValue : null));
148 	}
149 	else {
150 		throw new Exception("bad argument #1 to 'newproxy' (boolean or proxy expected)");
151 	}
152 }
153 
154 private Value[] lua_next(TableValue table, Nullable!Value indexn) {
155 	Value[] res;
156 
157 	if (indexn.isNull || indexn.get.isNil) {
158 		if (table.array.length > 0) {
159 			res = [Value(1), table.array[0]];
160 		}
161 		else if (table.hash.length > 0) {
162 			auto f = table.hash.iterator.front;
163 			res = [f[0], f[1]];
164 		}
165 	}
166 	else {
167 		Value key = indexn.get;
168 		if (key.type == ValueType.Number && key.num > 0) {
169 			ulong index = cast(ulong) key.num;
170 			if (cast(double) index == key.num) {
171 				index--;
172 				for (; index < table.array.length; ++index) {
173 					if (index == table.array.length - 1) { // if it's the last array element, return the first hash element
174 						if (table.hash.length > 0) {
175 							auto f = table.hash.iterator.front;
176 							res = [f[0], f[1]];
177 						}
178 						goto leave;
179 					}
180 					else { // if it's in the array, return the next element
181 						if (table.array[index + 1].isNil) continue;
182 						res = [Value(index + 2), table.array[index + 1]];
183 						goto leave;
184 					}
185 				}
186 			}
187 		}
188 
189 		// if it's in the hash part:
190 		auto iter = table.hash.find(key);
191 		iter.popFront();
192 		if (!iter.empty) {
193 			auto f = iter.front;
194 			res = [f[0], f[1]];
195 		}
196 	}
197 
198 leave:
199 	if (res.length == 0 || res[1].isNil) return [Value()];
200 	else return res;
201 }
202 
203 private Value pairsIteratorValue = exposeFunction!(lua_next, "?");
204 
205 private Value[] lua_pairs(TableValue input) {
206 	return [pairsIteratorValue, Value(input), Value()];
207 }
208 
209 private Tuple!(bool, Value[]) lua_pcall(FunctionValue f, Value[] args...) {
210 	auto saveStack = callstack;
211 	auto save = running;
212 	callstack = [];
213 	scope(exit) {
214 		callstack = saveStack;
215 		running = save;
216 	}
217 	try {
218 		return tuple(true, f.ccall(args));
219 	}
220 	catch (LuaError e) {
221 		return tuple(false, [e.data]);
222 	}
223 }
224 
225 private void lua_print(Value[] args...) {
226 	import std.stdio : writeln;
227 
228 	string s;
229 	foreach (v; args) {
230 		Value strv = v.luaToString;
231 		if (strv.type != ValueType.String) {
232 			throw new Exception("'tostring' must return a string to 'print'");
233 		}
234 		s ~= "\t" ~ strv.str;
235 	}
236 	writeln(s == "" ? s : s[1 .. $]);
237 }
238 
239 private bool lua_rawequal(Value a, Value b) {
240 	return a == b;
241 }
242 
243 private Value lua_rawget(TableValue table, Value index) {
244 	Value* res = table.rawget(index);
245 	if (res == null)
246 		return Value();
247 	return *res;
248 }
249 
250 private TableValue lua_rawset(TableValue table, Value index, Value value) {
251 	if (index.isNil)
252 		throw new Exception("table index is nil");
253 	table.rawset(index, value);
254 	return table;
255 }
256 
257 private Algebraic!(Value[], long) lua_select(Algebraic!(long, string) opt, Value[] args...) {
258 	if (opt.peek!string && opt.get!string == "#") {
259 		return args.length
260 			.to!long
261 			.Algebraic!(Value[], long);
262 	}
263 	else if (opt.peek!long) {
264 		const long index = opt.get!long;
265 		if (index > cast(long)args.length)
266 			return (cast(Value[])[]).Algebraic!(Value[], long);
267 		else if (index < 0) {
268 			if (-index > cast(long)args.length) {
269 				throw new Exception("bad argument #1 to 'select' (index out of range)");
270 			}
271 			return args[$ + index .. $].Algebraic!(Value[], long);
272 		}
273 		else if (index == 0)
274 			throw new Exception("bad argument #1 to 'select' (index out of range)");
275 		else
276 			return args[index - 1 .. $].Algebraic!(Value[], long);
277 	}
278 	else {
279 		throw new Exception("bad argument #1 to 'select' (number expected, got string)");
280 	}
281 }
282 
283 private Algebraic!(FunctionValue, Tuple!()) lua_setfenv(Algebraic!(FunctionValue, long) f, TableValue env) {
284 	FunctionValue func;
285 	if (FunctionValue* mfunc = f.peek!FunctionValue) {
286 		func = *mfunc;
287 	}
288 	else {
289 		const long lvl = f.get!long;
290 		if (lvl < 0) {
291 			throw new Exception("bad argument #1 to 'setfenv' (level must be non-negative)");
292 		}
293 		else if (lvl == 0) {
294 			*getGlobalEnvPtr = env;
295 			return Algebraic!(FunctionValue, Tuple!())(tuple());
296 		}
297 		else if (lvl > callstack.length) {
298 			throw new Exception("'setfenv' cannot change environment of given object");
299 		}
300 		else {
301 			func = callstack[$ - lvl].func;
302 		}
303 	}
304 
305 	if (func.env is null) {
306 		throw new Exception("'setfenv' cannot change environment of given object");
307 	}
308 	else {
309 		func.env = env;
310 		return Algebraic!(FunctionValue, Tuple!())(func);
311 	}
312 }
313 
314 private TableValue lua_setmetatable(TableValue table, Nullable!Value meta) {
315 	TableValue newMetatable;
316 	immutable string errorMsg = "bad argument #2 to 'setmetatable' (nil or table expected)";
317 	if (meta.isNull)
318 		throw new Exception(errorMsg);
319 	else {
320 		if (meta.get.type == ValueType.Nil) {
321 			newMetatable = null;
322 		}
323 		else if (meta.get.type == ValueType.Table) {
324 			newMetatable = meta.get.table;
325 		}
326 		else {
327 			throw new Exception(errorMsg);
328 		}
329 	}
330 
331 	TableValue metatable = table.metatable;
332 
333 	if (metatable !is null && !metatable.get(Value("__metatable")).isNil) {
334 		throw new Exception("cannot change a protected metatable");
335 	}
336 
337 	table.metatable = newMetatable;
338 	return table;
339 }
340 
341 private Nullable!double lua_tonumber(Value arg, Nullable!int b) {
342 	const int base = b.isNull ? 10 : b.get;
343 	if (base == 10) {
344 		if (arg.type == ValueType.Number) {
345 			return arg.num.nullable;
346 		}
347 		else if (arg.type == ValueType.String) {
348 			string str = arg.str.strip;
349 			try {
350 				double res = parse!double(str);
351 				if (str != "")
352 					return Nullable!double();
353 				return res.nullable;
354 			}
355 			catch (ConvException e) {
356 				return Nullable!double();
357 			}
358 		}
359 		else {
360 			return Nullable!double();
361 		}
362 	}
363 	else {
364 		if (base < 2 || base > 36) {
365 			throw new Exception("bad argument #2 to 'tonumber' (base out of range)");
366 		}
367 
368 		string str;
369 		if (arg.type == ValueType.String) {
370 			str = arg.str;
371 		}
372 		else if (arg.type == ValueType.Number) {
373 			str = arg.num.to!string;
374 		}
375 		else {
376 			throw new Exception(
377 					"bad argument #1 to 'tonumber' (string expected, got " ~ arg.typeStr ~ ")");
378 		}
379 
380 		str = str.strip;
381 
382 		try {
383 			double res = cast(double) parse!long(str, cast(uint) base);
384 			if (str != "")
385 				return Nullable!double();
386 			return res.nullable;
387 		}
388 		catch (ConvException e) {
389 			return Nullable!double();
390 		}
391 	}
392 }
393 
394 private Value lua_tostring(Value arg) {
395 	return arg.luaToString;
396 }
397 
398 private string lua_type(Value arg) {
399 	return arg.typeStr;
400 }
401 
402 private Value[] lua_unpack(TableValue arg, Nullable!size_t ni, Nullable!size_t nj) {
403 	size_t i, j;
404 
405 	if (ni.isNull)
406 		i = 1;
407 	else
408 		i = ni.get;
409 
410 	if (nj.isNull)
411 		j = cast(size_t) arg.length.num;
412 	else
413 		j = nj.get;
414 
415 	Value[] res;
416 	res.reserve(j - i + 1);
417 	foreach (idx; i .. j + 1) {
418 		Value* val = arg.rawget(Value(idx));
419 		if (val) {
420 			res ~= *val;
421 		}
422 		else {
423 			res ~= Value();
424 		}
425 	}
426 	return res;
427 }
428 
429 private Tuple!(bool, Value[]) lua_xpcall(FunctionValue f, FunctionValue err) {
430 	auto saveStack = callstack;
431 	auto save = running;
432 	callstack = [];
433 	scope(exit) {
434 		callstack = saveStack;
435 		running = save;
436 	}
437 	try {
438 		return tuple(true, f.ccall([]));
439 	}
440 	catch (LuaError e) {
441 		try {
442 			callstack = e.fullstack[0 .. $ - 1];
443 			running = e.fullstack[$ - 1].func;
444 			return tuple(false, err.rawcall([e.data]));
445 		}
446 		catch (LuaError e2) {
447 			return tuple(false, [Value("error in error handling")]);
448 		}
449 	}
450 }
451 
452 /** Create a new environment with standard functions */
453 TableValue stdenv(GlobalOptions context) {
454 	TableValue env = new TableValue;
455 	env.set(Value("_G"), Value(env));
456 	env.set(Value("_VERSION"), Value("Lua 5.1"));
457 	env.set(Value("_ZUAVERSION"), Value("Zua 1.0"));
458 
459 	env.set(Value("assert"), exposeFunction!(lua_assert, "assert"));
460 
461 	if ((context & GlobalOptions.NoGC) == 0) {
462 		env.set(Value("collectgarbage"), exposeFunction!(lua_collectgarbage, "collectgarbage"));
463 	}
464 
465 	env.set(Value("error"), exposeFunction!(lua_error, "error"));
466 	env.set(Value("getfenv"), exposeFunction!(lua_getfenv, "getfenv"));
467 	env.set(Value("getmetatable"), exposeFunction!(lua_getmetatable, "getmetatable"));
468 	env.set(Value("ipairs"), exposeFunction!(lua_ipairs, "ipairs"));
469 	env.set(Value("newproxy"), exposeFunction!(lua_newproxy, "newproxy"));
470 	env.set(Value("next"), exposeFunction!(lua_next, "next"));
471 	env.set(Value("pairs"), exposeFunction!(lua_pairs, "pairs"));
472 	env.set(Value("pcall"), exposeFunction!(lua_pcall, "pcall"));
473 
474 	if ((context & GlobalOptions.NoIO) == 0) {
475 		env.set(Value("print"), exposeFunction!(lua_print, "print"));
476 	}
477 
478 	env.set(Value("rawequal"), exposeFunction!(lua_rawequal, "rawequal"));
479 	env.set(Value("rawset"), exposeFunction!(lua_rawset, "rawset"));
480 	env.set(Value("rawget"), exposeFunction!(lua_rawget, "rawget"));
481 	env.set(Value("select"), exposeFunction!(lua_select, "select"));
482 	env.set(Value("setfenv"), exposeFunction!(lua_setfenv, "setfenv"));
483 	env.set(Value("setmetatable"), exposeFunction!(lua_setmetatable, "setmetatable"));
484 	env.set(Value("tonumber"), exposeFunction!(lua_tonumber, "tonumber"));
485 	env.set(Value("tostring"), exposeFunction!(lua_tostring, "tostring"));
486 	env.set(Value("type"), exposeFunction!(lua_type, "type"));
487 	env.set(Value("unpack"), exposeFunction!(lua_unpack, "unpack"));
488 	env.set(Value("xpcall"), exposeFunction!(lua_xpcall, "xpcall"));
489 
490 	env.set(Value("math"), mathlib);
491 	env.set(Value("table"), tablelib);
492 	env.set(Value("string"), stringlib);
493 	env.set(Value("coroutine"), coroutinelib);
494 	env.set(Value("os"), oslib);
495 
496 	if ((context & GlobalOptions.NoBackport) == 0) {
497 		env.set(Value("bit32"), bit32lib);
498 	}
499 
500 	return env;
501 }