1 module zua.vm.reflection; 2 import zua.vm.engine; 3 import zua.vm.engine : ValueType; 4 import std.traits; 5 import std.typecons; 6 import std.variant; 7 import std.meta; 8 import std.conv; 9 import std.uuid; 10 11 // this interop library was created mostly for the standard library; it is dirty and low-level and should not be used in user code 12 // use zua.interop instead 13 package: 14 15 private U fromLua(U, alias Default)(lazy string errorMsg, Nullable!Value nval) { 16 alias T = Unqual!U; 17 18 void error(const string expected) { 19 if (expected == "") { 20 throw new LuaError(Value(errorMsg ~ " (value expected)")); 21 } 22 else { 23 const string got = nval.isNull ? "no value" : nval.get.typeStr; 24 throw new LuaError(Value(errorMsg ~ " (" ~ expected ~ " expected, got " ~ got ~ ")")); 25 } 26 } 27 28 static if (!is(Default == void)) { 29 if (nval.isNull) { 30 return cast(U) Default; 31 } 32 } 33 34 static if (is(T == Nullable!K, K)) { 35 if (nval.isNull) 36 return cast(U) Nullable!K(); 37 38 const Value val = nval.get; 39 40 if (val.type == ValueType.Nil) 41 return cast(U) Nullable!K(); 42 43 return cast(U) fromLua!(K, Default)(errorMsg, nval).nullable; 44 } 45 else static if (is(T == VariantN!(n, K), size_t n, K...)) { 46 static foreach (i; 0 .. K.length - 1) { // @suppress(dscanner.suspicious.length_subtraction) 47 try { 48 return cast(U)T(fromLua!(K[i], Default)(errorMsg, nval)); 49 } 50 catch (LuaError e) { 51 } 52 } 53 54 return cast(U) T(fromLua!(K[$ - 1], Default)(errorMsg, nval)); 55 } 56 else static if (isNumeric!T) { 57 if (nval.isNull) 58 error("number"); 59 60 const Value val = nval.get; 61 62 double num; 63 if (val.type == ValueType.String) { 64 try { 65 num = val.str.to!double; 66 } 67 catch (ConvException) { 68 error("number"); 69 } 70 } 71 else if (val.type == ValueType.Number) { 72 num = val.num; 73 } 74 else 75 error("number"); 76 77 return cast(U) num; 78 } 79 else static if (is(T == char)) { 80 if (nval.isNull) 81 error("string"); 82 83 const Value val = nval.get; 84 85 string str; 86 if (val.type == ValueType.String) { 87 str = val.str; 88 } 89 else if (val.type == ValueType.Number) { 90 str = val.num.to!string; 91 } 92 else 93 error("string"); 94 95 if (str.length != 1) { 96 throw new LuaError(Value(errorMsg ~ " (string of length 1 expected)")); 97 } 98 99 return cast(U) str[0]; 100 } 101 else static if (is(T == string) || is(T == wstring) || is(T == dstring)) { 102 if (nval.isNull) 103 error("string"); 104 105 const Value val = nval.get; 106 107 T str; 108 if (val.type == ValueType.String) { 109 str = val.str.to!T; 110 } 111 else if (val.type == ValueType.Number) { 112 str = val.num.to!T; 113 } 114 else 115 error("string"); 116 117 return cast(U) str; 118 } 119 else static if (is(T == bool)) { 120 if (nval.isNull) 121 return cast(U)false; 122 123 return cast(U) nval.get.toBool; 124 } 125 else static if (is(T == TableValue)) { 126 if (nval.isNull) 127 error("table"); 128 129 Value val = nval.get; 130 131 if (val.type == ValueType.Table) { 132 return cast(U) val.table; 133 } 134 135 error("table"); 136 } 137 else static if (is(T == FunctionValue)) { 138 if (nval.isNull) 139 error("function"); 140 141 Value val = nval.get; 142 143 if (val.type == ValueType.Function) { 144 return cast(U) val.func; 145 } 146 147 error("function"); 148 } 149 else static if (is(T == ThreadValue)) { 150 if (nval.isNull) 151 error("thread"); 152 153 Value val = nval.get; 154 155 if (val.type == ValueType.Thread) { 156 return cast(U) val.thread; 157 } 158 159 error("thread"); 160 } 161 else static if (is(T == Value)) { 162 if (nval.isNull) 163 error(""); 164 165 return cast(U) nval.get; 166 } 167 else 168 static assert(0, "Unsupported type '" ~ fullyQualifiedName!T ~ "'"); 169 assert(0); 170 } 171 172 private T fromLua(T, string func, alias Default)(size_t arg, Nullable!Value val) { 173 return fromLua!(T, Default)("bad argument #" ~ (arg + 1).to!string ~ " to '" ~ func ~ "'", val); 174 } 175 176 private Value toLua(U)(U val) { 177 alias T = Unqual!U; 178 179 static if (is(T == Nullable!K, K)) { 180 if (val.isNull) { 181 return Value(); 182 } 183 else { 184 return val.get.toLua; 185 } 186 } 187 else static if (is(T == VariantN!(n, K), size_t n, K...)) { 188 static foreach (i; 0 .. K.length) { 189 if (auto ptr = val.peek!(K[i])) { 190 static if (is(K[i] == void)) 191 return Value(); 192 else 193 return toLua(*ptr); 194 } 195 } 196 return Value(); 197 } 198 else static if (isNumeric!T) { 199 return Value(cast(double) val); 200 } 201 else static if (is(T == char)) { 202 return Value([cast(T) val]); 203 } 204 else static if (is(T == string) || is(T == wstring) || is(T == dstring)) { 205 return Value(val.to!string); 206 } 207 else static if (is(T == bool)) { 208 return Value(cast(T) val); 209 } 210 else static if (is(T == TableValue) || is(T == FunctionValue) 211 || is(T == ThreadValue)) { 212 if (val is null) return Value(); 213 return Value(cast(T) val); 214 } 215 else static if (is(T == Tuple!K, K...)) { 216 Value[] res; 217 static foreach (i; 0 .. T.fieldNames.length) { 218 res ~= val[i].toLua; 219 } 220 return Value.makeTuple(res); 221 } 222 else static if (is(T == Value[])) { 223 return Value.makeTuple(val); 224 } 225 else static if (is(T == K[], K)) { 226 Value[] res; 227 foreach (v; val) { 228 res ~= v.toLua; 229 } 230 return Value.makeTuple(res); 231 } 232 else static if (is(T == Value)) { 233 return cast(T) val; 234 } 235 else 236 static assert(0, "Unsupported type '" ~ fullyQualifiedName!T ~ "'"); 237 } 238 239 private string getPrologue(params...)() { 240 string res = ""; 241 static foreach (i; 0 .. params.length) { 242 res ~= fullyQualifiedName!(params[i]) ~ " param_" ~ i.to!string ~ ";\n"; 243 } 244 return res; 245 } 246 247 private string getArgs(bool trail, params...)() { 248 string res = ""; 249 static foreach (i; 0 .. params.length) { 250 res ~= "param_" ~ i.to!string ~ ", "; 251 } 252 if (res == "") 253 return ""; 254 return trail ? res : res[0 .. $ - 2]; 255 } 256 257 /** Make a Lua function from a D function */ 258 Value exposeFunction(alias dfunc, string funcname)() { 259 alias STC = ParameterStorageClass; 260 alias pstc = ParameterStorageClassTuple!dfunc; 261 static foreach (i; 0 .. pstc.length) { 262 static if (pstc[i] & STC.lazy_) 263 static assert(0, "Lazy parameters cannot be used from Lua"); 264 static if (pstc[i] & STC.ref_) 265 static assert(0, "Reference parameters cannot be used from Lua"); 266 static if (pstc[i] & STC.out_) 267 static assert(0, "Out parameters cannot be used from Lua"); 268 } 269 270 FunctionValue func = new FunctionValue; 271 func.env = null; 272 func.engine = new class Engine { 273 274 override Value[] callf(FunctionValue, Value[] args) { 275 alias variadic = variadicFunctionStyle!dfunc; 276 assert(variadicFunctionStyle!dfunc == Variadic.typesafe || variadicFunctionStyle!dfunc == Variadic.no, 277 "Variadic functions should be made using `T[] args...` syntax"); 278 static if (variadic == Variadic.typesafe) { 279 alias params = Parameters!dfunc[0 .. $ - 1]; 280 } 281 else { 282 alias params = Parameters!dfunc; 283 } 284 mixin(getPrologue!params); 285 alias ParamDefaults = ParameterDefaults!dfunc; 286 static foreach (i; 0 .. params.length) { 287 if (i >= args.length) { 288 mixin("param_", i.to!string, 289 " = fromLua!(params[i], funcname, ParamDefaults[i])(i, Nullable!Value());"); 290 } 291 else { 292 mixin("param_", i.to!string, 293 " = fromLua!(params[i], funcname, ParamDefaults[i])(i, args[i].nullable);"); 294 } 295 } 296 297 static if (is(ReturnType!dfunc == void)) { 298 alias ResType = typeof(null); 299 } 300 else { 301 alias ResType = ReturnType!dfunc; 302 } 303 304 ResType res; 305 306 static if (variadic == Variadic.typesafe) { 307 alias Last = Parameters!dfunc[$ - 1]; 308 Last tuple; 309 if (args.length > params.length) { 310 foreach (i; params.length .. args.length) { 311 tuple ~= fromLua!(ForeachType!Last, funcname, void)(i, args[i].nullable); 312 } 313 } 314 try { 315 static if (is(ReturnType!dfunc == void)) 316 mixin("dfunc(", getArgs!(true, params), "tuple);"); 317 else 318 res = mixin("dfunc(", getArgs!(true, params), "tuple)"); 319 } 320 catch (LuaError e) { 321 throw e; 322 } 323 catch (Exception e) { 324 throw new LuaError(Value(e.msg)); 325 } 326 } 327 else { 328 try { 329 static if (is(ReturnType!dfunc == void)) 330 mixin("dfunc(", getArgs!(false, params), ");"); 331 else 332 res = mixin("dfunc(", getArgs!(false, params), ")"); 333 } 334 catch (LuaError e) { 335 throw e; 336 } 337 catch (Exception e) { 338 throw new LuaError(Value(e.msg)); 339 } 340 } 341 static if (is(ReturnType!dfunc == void)) { 342 return []; 343 } 344 else { 345 auto luaRes = res.toLua; 346 if (luaRes.type == ValueType.Tuple) { 347 return luaRes.tuple; 348 } 349 else { 350 return [luaRes]; 351 } 352 } 353 } 354 355 }; 356 return Value(func); 357 } 358 359 unittest { 360 import std.stdio : writeln; 361 362 int add2(Nullable!int i) { 363 if (i.isNull) 364 return -1; 365 return i.get + 2; 366 } 367 368 string sumAll(string pre, int[] args...) { 369 int res = 0; 370 foreach (i; args) { 371 res += i; 372 } 373 return pre ~ res.to!string; 374 } 375 376 alias Mystery = Algebraic!(int, string); 377 378 Mystery mystery(int x) { 379 if (x < 0) 380 return Mystery("invalid"); 381 return Mystery(x + 3); 382 } 383 384 int mystery2(Algebraic!(int, bool) val) { 385 if (val.peek!int) { 386 return 0; 387 } 388 else { 389 return 1; 390 } 391 } 392 393 Value m = exposeFunction!(mystery, "mystery"); 394 assert(m.call([Value("-6e10")]) == [Value("invalid")]); 395 assert(m.call([Value("7")]) == [Value(10)]); 396 397 Value m2 = exposeFunction!(mystery2, "mystery"); 398 assert(m2.call([Value("-6e10")]) == [Value(0)]); 399 assert(m2.call([Value(false)]) == [Value(1)]); 400 401 Value v = exposeFunction!(sumAll, "sumAll"); 402 assert(v.call([Value("3 + 5 = "), Value("3"), Value("5")]) == [ 403 Value("3 + 5 = 8") 404 ]); 405 406 Value a = exposeFunction!(add2, "add2"); 407 assert(a.call([Value(5)]) == [Value(7)]); 408 assert(a.call([Value("8.674")]) == [Value(10)]); 409 assert(a.call([Value()]) == [Value(-1)]); 410 assert(a.call([]) == [Value(-1)]); 411 try { 412 a.call([Value("a")]); 413 assert(0); 414 } 415 catch (LuaError e) { 416 assert(e.data == Value("bad argument #1 to 'add2' (number expected, got string)")); 417 } 418 }