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 }