1 module zua.interop; 2 import zua.interop.table; 3 import zua.interop.userdata; 4 import zua.interop.functions; 5 import zua.interop.classwrapper; 6 import zua.vm.engine; 7 import zua.vm.engine : ValueType; 8 import std.variant; 9 import std.traits; 10 import std.typecons; 11 import std.uni; 12 import std.conv; 13 import std.algorithm.iteration; 14 import std.range; 15 16 alias DConsumableFunction = DConsumable[] delegate(DConsumable[] args); 17 18 /** Returns true if the type is convertible to/from a DConsumable; false otherwise */ 19 enum bool isConvertible(T) = 20 is(Unqual!T == typeof(null)) 21 || is(Unqual!T == void) 22 || isNumeric!T 23 || is(Unqual!T == string) 24 || is(Unqual!T == wstring) 25 || is(Unqual!T == dstring) 26 || is(Unqual!T == bool) 27 || is(Unqual!T == Table) 28 || is(Unqual!T == Userdata) 29 || is(Unqual!T == DConsumable) 30 || is(Unqual!T == Value) 31 || isSomeFunction!(Unqual!T) 32 || is(Unqual!T == class); 33 34 /** Returns the ValueType that corresponds to the given D-side type, or null if the given D-side type is dynamic */ 35 Nullable!ValueType getValueType(T)() if (isConvertible!T) { 36 alias U = Unqual!T; 37 static if (is(U == typeof(null))) { 38 return Nullable!ValueType(ValueType.Nil); 39 } 40 else static if (is(U == void)) { 41 return Nullable!ValueType(ValueType.Nil); 42 } 43 else static if (isNumeric!T) { 44 return Nullable!ValueType(ValueType.Number); 45 } 46 else static if (is(U == string) || is(U == wstring) || is(U == dstring)) { 47 return Nullable!ValueType(ValueType.String); 48 } 49 else static if (is(U == bool)) { 50 return Nullable!ValueType(ValueType.Boolean); 51 } 52 else static if (is(U == Table)) { 53 return Nullable!ValueType(ValueType.Table); 54 } 55 else static if (is(U == Userdata) || is(U == class)) { 56 return Nullable!ValueType(ValueType.Userdata); 57 } 58 else static if (is(U == DConsumableFunction)) { 59 return Nullable!ValueType(ValueType.Function); 60 } 61 else static if (is(U == DConsumable) || is(U == Value)) { 62 return Nullable!ValueType(); 63 } 64 else static assert(0); 65 } 66 67 /** Convert a ValueType to its canonical string form */ 68 string valueTypeToString(ValueType type) { 69 return type.to!string.toLower; 70 } 71 72 /** A type that is consumable by D code without much fuss */ 73 struct DConsumable { 74 75 private Value internalValue; 76 77 /** The value of this DConsumable */ 78 Algebraic!( 79 typeof(null), 80 double, 81 string, 82 bool, 83 Table, 84 Userdata, 85 DConsumableFunction, 86 ) value; 87 88 alias value this; 89 90 /** Create a new DConsumable from the given type */ 91 this(T, string preferredName = "?")(T rawValue) if (isConvertible!T) { 92 alias U = Unqual!T; 93 U v = cast(U)rawValue; 94 95 static if (is(U == typeof(null))) { 96 this(Value(), cast(typeof(value))null); 97 } 98 else static if (isNumeric!T) { 99 double dvalue = v.to!double; 100 this(Value(dvalue), cast(typeof(value))dvalue); 101 } 102 else static if (is(U == string) || is(U == wstring) || is(U == dstring)) { 103 string svalue = v.to!string; 104 this(Value(svalue), cast(typeof(value))svalue); 105 } 106 else static if (is(U == bool)) { 107 this(Value(v), cast(typeof(value))v); 108 } 109 else static if (is(U == Table)) { 110 this(v._internalTable, cast(typeof(value))v); 111 } 112 else static if (is(U == Userdata)) { 113 this(v._internalUserdata, cast(typeof(value))v); 114 } 115 else static if (is(U == DConsumable)) { 116 this(v.internalValue, cast(typeof(value))v.value); 117 } 118 else static if (is(U == Value)) { 119 DConsumable dvalue; 120 121 switch (v.type) { 122 case ValueType.Nil: dvalue = DConsumable(v, cast(typeof(DConsumable.value))null); break; 123 case ValueType.Number: dvalue = DConsumable(v, cast(typeof(DConsumable.value))v.num); break; 124 case ValueType.String: dvalue = DConsumable(v, cast(typeof(DConsumable.value))v.str); break; 125 case ValueType.Boolean: dvalue = DConsumable(v, cast(typeof(DConsumable.value))v.boolean); break; 126 case ValueType.Table: dvalue = DConsumable(v, cast(typeof(DConsumable.value))Table(v)); break; 127 case ValueType.Userdata: dvalue = DConsumable(v, cast(typeof(DConsumable.value))Userdata(v)); break; 128 case ValueType.Function: 129 dvalue = DConsumable(v, cast(typeof(DConsumable.value))delegate(DConsumable[] args) { 130 Value[] luaArgs; 131 luaArgs.reserve(luaArgs.length); 132 foreach (i; 0..args.length) { 133 luaArgs ~= makeInternalValue(args[i]); 134 } 135 Value[] luaRes = v.func.ccall(luaArgs); 136 DConsumable[] res; 137 res.reserve(luaRes.length); 138 foreach (i; 0..luaRes.length) { 139 res ~= DConsumable(luaRes[i]); 140 } 141 return res; 142 }); 143 break; 144 // case ValueType.Thread: 145 // TODO: this 146 default: assert(0); 147 } 148 149 this(dvalue); 150 } 151 else static if (isSomeFunction!U) { 152 DConsumable[] luaFunction(DConsumable[] consumableArgs) { 153 try { 154 auto args = convertParameters!(U, preferredName)(consumableArgs); 155 static if (is(ReturnType!U == void)) { 156 v(args.expand); 157 return []; 158 } 159 else { 160 return v(args.expand).convertReturn!U; 161 } 162 } 163 catch (Exception e) { 164 if (cast(LuaError)e) { 165 throw e; 166 } 167 else { 168 throw new LuaError(Value(e.msg)); 169 } 170 } 171 } 172 173 FunctionValue funcValue = new FunctionValue; 174 funcValue.env = null; 175 funcValue.engine = new class Engine { 176 override Value[] callf(FunctionValue, Value[] args) { 177 DConsumable[] consumableArgs; 178 consumableArgs.reserve(args.length); 179 foreach (v; args) { 180 consumableArgs ~= DConsumable(v); 181 } 182 DConsumable[] consumableResult = luaFunction(consumableArgs); 183 Value[] res; 184 res.reserve(consumableResult.length); 185 foreach (v; consumableResult) { 186 res ~= v.internalValue; 187 } 188 return res; 189 } 190 }; 191 192 this(Value(funcValue), cast(typeof(DConsumable.value))&luaFunction); 193 } 194 else static if (is(U == class)) { 195 Tuple!(DConsumable, ClassConverter!T) classWrapper = makeClassWrapper!T; 196 Userdata uv = classWrapper[1](v); 197 this(uv._internalUserdata, cast(typeof(DConsumable.value))uv); 198 } 199 else static assert(0); 200 } 201 202 private this(Value internalValue, typeof(value) value) { 203 this.internalValue = internalValue; 204 this.value = value; 205 } 206 207 /** Convert the DConsumable to a native D type, possibly failing; if exact, type coercion is not performed */ 208 Nullable!T convert(T, bool exact = false)() const { 209 alias U = Unqual!T; 210 static if (is(U == typeof(null))) { 211 if (internalValue.type == ValueType.Nil) return cast(Nullable!T)null; 212 else return Nullable!T(); 213 } 214 else static if (isNumeric!T) { 215 if (internalValue.type == ValueType.Number) return cast(Nullable!T)(internalValue.num.to!U); 216 else if (internalValue.type == ValueType.String && !exact) { 217 try { 218 return cast(Nullable!T)(internalValue.str.to!U); 219 } 220 catch (ConvException e) { 221 return Nullable!T(); 222 } 223 } 224 else return Nullable!T(); 225 } 226 else static if (is(U == string) || is(U == wstring) || is(U == dstring)) { 227 if (internalValue.type == ValueType.String) return cast(Nullable!T)(internalValue.str.to!U); 228 else if (internalValue.type == ValueType.Number && !exact) return cast(Nullable!T)(internalValue.num.to!U); 229 else return Nullable!T(); 230 } 231 else static if (is(U == bool)) { 232 static if (exact) { 233 if (internalValue.type == ValueType.Boolean) { 234 return Nullable!T(internalValue.boolean); 235 } 236 else { 237 return Nullable!T(); 238 } 239 } 240 else { 241 if (internalValue.type == ValueType.Nil || (internalValue.type == ValueType.Boolean && !internalValue.boolean)) { 242 return Nullable!T(false); 243 } 244 else { 245 return Nullable!T(true); 246 } 247 } 248 } 249 else static if (is(U == Table)) { 250 if (internalValue.type == ValueType.Table) return cast(Nullable!T)value.get!Table; 251 else return Nullable!T(); 252 } 253 else static if (is(U == Userdata)) { 254 if (internalValue.type == ValueType.Userdata) return cast(Nullable!T)value.get!Userdata; 255 else return Nullable!T(); 256 } 257 else static if (is(U == DConsumable)) { 258 return Nullable!T(this); 259 } 260 else static if (is(U == Value)) { 261 return Nullable!T(internalValue); 262 } 263 else static if (is(U == DConsumableFunction)) { 264 return Nullable!T(delegate DConsumable[](DConsumable[] args) { 265 return (cast(Value)internalValue).func.ccall(args.map!makeInternalValue.array).makeConsumable; 266 }); 267 } 268 else static if (is(U == class)) { 269 if (internalValue.type == ValueType.Userdata) { 270 Userdata data = value.get!Userdata; 271 if (isClassWrapper(data)) { 272 Object obj = cast(Object)data.data; 273 if (cast(T)obj) { 274 return Nullable!T(cast(T)obj); 275 } 276 } 277 } 278 279 return Nullable!T(); 280 } 281 else static assert(0, "Unable to convert to this type"); 282 } 283 284 /** Cast operation */ 285 T opCast(T)() const { 286 Nullable!T res = convert!T; 287 if (res.isNull) { 288 throw new LuaError(Value("bad argument #1 to '?' (" ~ getValueType!T.get.valueTypeToString ~ " expected, got " 289 ~ internalValue.type.valueTypeToString ~ ")")); 290 } 291 else { 292 return res.get; 293 } 294 } 295 296 /** Cast operation */ 297 T opCast(T, int position)() const { 298 Nullable!T res = convert!T; 299 if (res.isNull) { 300 throw new LuaError(Value("bad argument #" ~ position.to!string ~ " to '?' (" ~ getValueType!T.get.valueTypeToString 301 ~ " expected, got " ~ internalValue.type.valueTypeToString ~ ")")); 302 } 303 else { 304 return res.get; 305 } 306 } 307 308 } 309 310 /** Convert the DConsumable type into an internal-representation Value */ 311 pragma(inline) Value makeInternalValue(DConsumable value) { 312 return value.internalValue; 313 } 314 315 /** Convert an internal-representation Value array into a DConsumable array */ 316 pragma(inline) DConsumable[] makeConsumable(Value[] value) { 317 DConsumable[] res; 318 res.reserve(value.length); 319 foreach (i; 0..value.length) { 320 res ~= DConsumable(value[i]); 321 } 322 return res; 323 } 324 325 unittest { 326 import zua; 327 328 static assert(isConvertible!double); 329 static assert(isConvertible!int); 330 331 import std.stdio; 332 333 Common c = new Common(GlobalOptions.FullAccess); 334 335 c.env["callD1"] = delegate(int a, int b) { 336 return (a + b) * 1000 + a * b; 337 }; 338 339 c.env["callD2"] = delegate(string a, string b) { 340 return a ~ b; 341 }; 342 343 c.env.expose!"callD3"(delegate(string a, string b) { 344 return a ~ b; 345 }); 346 347 string cs; 348 349 c.env.expose!"callD4"(delegate(string a, string b) { 350 cs = a ~ b; 351 }); 352 353 c.env.expose!"callD5"(delegate() { 354 return cs; 355 }); 356 357 try { 358 c.run("file.lua", q"{ 359 assert(callD1("2", 3) == 5006) 360 assert(callD2(2, 7) == "27") 361 assert(select(2, pcall(callD2, 2, {})) == "bad argument #2 to '?' (string expected, got table)") 362 assert(select(2, pcall(callD3, {}, 3)) == "bad argument #1 to 'callD3' (string expected, got table)") 363 assert(callD5() == "") 364 assert(not callD4("a", "b")) 365 assert(callD5() == "ab") 366 }"); 367 } 368 catch (LuaError e) { 369 stderr.writeln("Error: " ~ e.data.toString); 370 assert(0); 371 } 372 }