1 /** 2 Wrapper module for Excel. This module contains the functionality that autowraps 3 D code for use within Excel. 4 */ 5 module xlld.wrap.wrap; 6 7 import xlld.wrap.worksheet; 8 import xlld.sdk.xlcall: XLOPER12; 9 import std.typecons: Flag, No; 10 11 12 13 14 /// 15 string wrapAll(Modules...) 16 (Flag!"onlyExports" onlyExports = No.onlyExports, 17 in string mainModule = __MODULE__) 18 { 19 20 if(!__ctfe) { 21 return ""; 22 } 23 24 import xlld.wrap.traits: implGetWorksheetFunctionsString; 25 return 26 q{import xlld;} ~ 27 "\n" ~ 28 wrapWorksheetFunctionsString!Modules(onlyExports, mainModule) ~ 29 "\n" ~ 30 implGetWorksheetFunctionsString!(mainModule) ~ 31 "\n" ~ 32 `mixin GenerateDllDef!"` ~ mainModule ~ `";` ~ 33 "\n"; 34 } 35 36 /// 37 string wrapWorksheetFunctionsString(Modules...) 38 (Flag!"onlyExports" onlyExports = No.onlyExports, string callingModule = __MODULE__) 39 { 40 if(!__ctfe) { 41 return ""; 42 } 43 44 string ret; 45 foreach(module_; Modules) { 46 ret ~= wrapModuleWorksheetFunctionsString!module_(onlyExports, callingModule); 47 } 48 49 return ret; 50 } 51 52 53 /** 54 A string to mixin that wraps all eligible functions in the 55 given module. 56 */ 57 string wrapModuleWorksheetFunctionsString(string moduleName) 58 (Flag!"onlyExports" onlyExports = No.onlyExports, string callingModule = __MODULE__) 59 { 60 if(!__ctfe) { 61 return ""; 62 } 63 64 import xlld.wrap.traits: Identity; 65 66 mixin(`import ` ~ moduleName ~ `;`); 67 alias module_ = Identity!(mixin(moduleName)); 68 69 string ret; 70 71 foreach(moduleMemberStr; __traits(allMembers, module_)) 72 ret ~= wrapModuleMember!(moduleName, moduleMemberStr)(onlyExports, callingModule); 73 74 return ret; 75 } 76 77 string wrapModuleMember(string moduleName, string moduleMemberStr) 78 (Flag!"onlyExports" onlyExports = No.onlyExports, string callingModule = __MODULE__) 79 { 80 if(!__ctfe) return ""; 81 82 import xlld.wrap.traits: Identity; 83 import std.traits: functionAttributes, FunctionAttribute; 84 85 mixin(`import ` ~ moduleName ~ `;`); 86 alias module_ = Identity!(mixin(moduleName)); 87 88 string ret; 89 90 alias moduleMember = Identity!(__traits(getMember, module_, moduleMemberStr)); 91 92 static if(isWorksheetFunction!moduleMember) { 93 enum numOverloads = __traits(getOverloads, mixin(moduleName), moduleMemberStr).length; 94 static if(numOverloads == 1) { 95 // if onlyExports is true, then only functions that are "export" are allowed 96 // Otherwise, any function will do as long as they're visible (i.e. public) 97 const shouldWrap = onlyExports ? __traits(getProtection, moduleMember) == "export" : true; 98 if(shouldWrap) 99 ret ~= wrapModuleFunctionStr!(moduleName, moduleMemberStr)(callingModule); 100 } else 101 pragma(msg, "excel-d WARNING: Not wrapping ", moduleMemberStr, " due to it having ", 102 cast(int)numOverloads, " overloads"); 103 } else { 104 /// trying to get a pointer to something is a good way of making sure we can 105 /// attempt to evaluate `isSomeFunction` - it's not always possible 106 enum canGetPointerToIt = __traits(compiles, &moduleMember); 107 static if(canGetPointerToIt) { 108 import xlld.wrap.worksheet: Register; 109 import std.traits: getUDAs; 110 alias registerAttrs = getUDAs!(moduleMember, Register); 111 static assert(registerAttrs.length == 0, 112 "excel-d ERROR: Function `" ~ moduleMemberStr ~ "` not eligible for wrapping"); 113 } 114 } 115 116 return ret; 117 } 118 119 /** 120 A string to use with `mixin` that wraps a D function 121 */ 122 string wrapModuleFunctionStr(string moduleName, string funcName) 123 (in string callingModule = __MODULE__) 124 { 125 if(!__ctfe) { 126 return ""; 127 } 128 129 assert(callingModule != moduleName, 130 "Cannot use `wrapAll` with __MODULE__"); 131 132 import xlld.wrap.traits: Async, Identity; 133 import xlld.wrap.worksheet: Register; 134 import std.array: join; 135 import std.traits: Parameters, functionAttributes, FunctionAttribute, getUDAs, hasUDA; 136 import std.conv: to; 137 import std.algorithm: map; 138 import std.range: iota; 139 import std.format: format; 140 141 mixin("import " ~ moduleName ~ ": " ~ funcName ~ ";"); 142 143 alias func = Identity!(mixin(funcName)); 144 145 const argsLength = Parameters!(mixin(funcName)).length; 146 // e.g. XLOPER12* arg0, XLOPER12* arg1, ... 147 auto argsDecl = argsLength.iota.map!(a => `XLOPER12* arg` ~ a.to!string).join(", "); 148 // e.g. arg0, arg1, ... 149 static if(!hasUDA!(func, Async)) 150 const argsCall = argsLength.iota.map!(a => `arg` ~ a.to!string).join(", "); 151 else { 152 import std.range: only, chain, iota; 153 import std.conv: text; 154 argsDecl ~= ", XLOPER12* asyncHandle"; 155 const argsCall = chain(only("*asyncHandle"), argsLength.iota.map!(a => `*arg` ~ a.text)). 156 map!(a => `cast(immutable)` ~ a) 157 .join(", "); 158 } 159 const nogc = functionAttributes!(mixin(funcName)) & FunctionAttribute.nogc 160 ? "@nogc " 161 : ""; 162 const safe = functionAttributes!(mixin(funcName)) & FunctionAttribute.safe 163 ? "@trusted " 164 : ""; 165 166 alias registerAttrs = getUDAs!(mixin(funcName), Register); 167 static assert(registerAttrs.length == 0 || registerAttrs.length == 1, 168 "Invalid number of @Register on " ~ funcName); 169 170 string register; 171 static if(registerAttrs.length) 172 register = `@` ~ registerAttrs[0].to!string; 173 string async; 174 static if(hasUDA!(func, Async)) 175 async = "@Async"; 176 177 const returnType = hasUDA!(func, Async) ? "void" : "XLOPER12*"; 178 // The function name that Excel actually calls in the binary 179 const xlFuncName = pascalCase(funcName); 180 const return_ = hasUDA!(func, Async) ? "" : "return "; 181 const wrap = hasUDA!(func, Async) 182 ? q{wrapAsync!wrappedFunc(Mallocator.instance, %s)}.format(argsCall) 183 : q{wrapModuleFunctionImpl!wrappedFunc(gTempAllocator, %s)}.format(argsCall); 184 185 return [ 186 register, 187 async, 188 q{ 189 extern(Windows) %s %s(%s) nothrow %s %s { 190 static import %s; 191 import xlld.memorymanager: gTempAllocator; 192 import std.experimental.allocator.mallocator: Mallocator; 193 alias wrappedFunc = %s.%s; 194 %s%s; 195 } 196 }.format(returnType, xlFuncName, argsDecl, nogc, safe, 197 moduleName, 198 moduleName, funcName, 199 return_, wrap), 200 ].join("\n"); 201 } 202 203 string pascalCase(in string func) @safe pure { 204 import std.uni: toUpper; 205 import std.conv: to; 206 return (func[0].toUpper ~ func[1..$].to!dstring).to!string; 207 } 208 209 void wrapAsync(alias F, A, T...)(ref A allocator, immutable XLOPER12 asyncHandle, T args) { 210 211 import xlld.sdk.xlcall: XlType, xlAsyncReturn; 212 import xlld.sdk.framework: Excel12f; 213 import std.concurrency: spawn; 214 import std.format: format; 215 216 string toDArgsStr() { 217 import std.string: join; 218 import std.conv: text; 219 220 string[] pointers; 221 foreach(i; 0 .. T.length) { 222 pointers ~= text("&args[", i, "]"); 223 } 224 225 return q{toDArgs!F(allocator, %s)}.format(pointers.join(", ")); 226 } 227 228 mixin(q{alias DArgs = typeof(%s);}.format(toDArgsStr)); 229 DArgs dArgs; 230 231 // Convert all Excel types to D types. This needs to be done here because the 232 // asynchronous part of the computation can't call back into Excel, and converting 233 // to D types requires calling coerce. 234 try { 235 mixin(q{dArgs = %s;}.format(toDArgsStr)); 236 } catch(Throwable t) { 237 static if(isGC!F) { 238 import xlld.sdk.xll: log; 239 log("ERROR: Could not convert to D args for asynchronous function " ~ 240 __traits(identifier, F)); 241 } 242 } 243 244 try 245 spawn(&wrapAsyncImpl!(F, A, DArgs), allocator, asyncHandle, cast(immutable)dArgs); 246 catch(Exception ex) { 247 XLOPER12 functionRet, ret; 248 functionRet.xltype = XlType.xltypeErr; 249 Excel12f(xlAsyncReturn, &ret, cast(XLOPER12*)&asyncHandle, &functionRet); 250 } 251 } 252 253 254 void wrapAsyncImpl(alias F, A, T)(ref A allocator, XLOPER12 asyncHandle, T dArgs) { 255 import xlld.sdk.framework: Excel12f; 256 import xlld.sdk.xlcall: xlAsyncReturn; 257 import std.traits: Unqual; 258 259 // get rid of the temporary memory allocations for the conversions 260 scope(exit) freeDArgs(allocator, cast(Unqual!T)dArgs); 261 262 auto functionRet = callWrapped!F(dArgs); 263 XLOPER12 xl12ret; 264 const errorCode = () @trusted { 265 return Excel12f(xlAsyncReturn, &xl12ret, &asyncHandle, functionRet); 266 }(); 267 } 268 269 // if a function is not @nogc, i.e. it uses the GC 270 private bool isGC(alias F)() { 271 import std.traits: functionAttributes, FunctionAttribute; 272 enum nogc = functionAttributes!F & FunctionAttribute.nogc; 273 return !nogc; 274 } 275 276 277 /** 278 Implement a wrapper for a regular D function 279 */ 280 XLOPER12* wrapModuleFunctionImpl(alias wrappedFunc, A, T...) 281 (ref A allocator, T args) 282 { 283 static XLOPER12 ret; 284 285 alias DArgs = typeof(toDArgs!wrappedFunc(allocator, args)); 286 DArgs dArgs; 287 // convert all Excel types to D types 288 try { 289 dArgs = toDArgs!wrappedFunc(allocator, args); 290 } catch(Exception ex) { 291 ret = stringOper("#ERROR converting argument to call " ~ __traits(identifier, wrappedFunc)); 292 return &ret; 293 } catch(Throwable t) { 294 ret = stringOper("#FATAL ERROR converting argument to call " ~ 295 __traits(identifier, wrappedFunc)); 296 return &ret; 297 } 298 299 // get rid of the temporary memory allocations for the conversions 300 scope(exit) freeDArgs(allocator, dArgs); 301 302 return callWrapped!wrappedFunc(dArgs); 303 } 304 305 /** 306 Converts a variadic number of XLOPER12* to their equivalent D types 307 and returns a tuple 308 */ 309 auto toDArgs(alias wrappedFunc, A, T...) 310 (ref A allocator, T args) 311 { 312 import xlld.func.xl: coerce, free; 313 import xlld.sdk.xlcall: XlType; 314 import xlld.conv.from: fromXlOper; 315 import std.traits: Parameters, ParameterDefaults, Unqual; 316 import std.typecons: Tuple; 317 import std.meta: staticMap; 318 319 static XLOPER12 ret; 320 321 XLOPER12[T.length] coercedOperArgs; 322 // must 1st convert each argument to the "real" type. 323 // 2D arrays are passed in as SRefs, for instance 324 foreach(i, InputType; Parameters!wrappedFunc) { 325 if(args[i].xltype == XlType.xltypeMissing) { 326 coercedOperArgs[i] = *args[i]; 327 continue; 328 } 329 coercedOperArgs[i] = coerce(args[i]); 330 } 331 332 // scopedCoerce doesn't work with actual Excel 333 scope(exit) { 334 static foreach(i; 0 .. args.length) { 335 if(args[i].xltype != XlType.xltypeMissing) 336 free(&coercedOperArgs[i]); 337 } 338 } 339 340 // the D types to pass to the wrapped function 341 Tuple!(staticMap!(Unqual, Parameters!wrappedFunc)) dArgs; 342 343 // convert all Excel types to D types 344 static foreach(i, InputType; Parameters!wrappedFunc) { 345 346 // here we must be careful to use a default value if it exists _and_ 347 // the oper that was passed in was xlTypeMissing 348 static if(is(ParameterDefaults!wrappedFunc[i] == void)) 349 dArgs[i] = () @trusted { return fromXlOper!InputType(&coercedOperArgs[i], allocator); }(); 350 else 351 dArgs[i] = args[i].xltype == XlType.xltypeMissing 352 ? ParameterDefaults!wrappedFunc[i] 353 : () @trusted { return fromXlOper!InputType(&coercedOperArgs[i], allocator); }(); 354 } 355 356 return dArgs; 357 } 358 359 360 // Takes a tuple returned by `toDArgs`, calls the wrapped function and returns 361 // the XLOPER12 result 362 private XLOPER12* callWrapped(alias wrappedFunc, T)(T dArgs) { 363 364 import xlld.wrap.worksheet: Dispose; 365 import xlld.sdk.xlcall: XlType; 366 import nogc.conv: text; 367 import std.traits: hasUDA, getUDAs, ReturnType; 368 369 static XLOPER12 ret; 370 371 try { 372 // call the wrapped function with D types 373 static if(is(ReturnType!wrappedFunc == void)) { 374 wrappedFunc(dArgs.expand); 375 ret.xltype = XlType.xltypeNil; 376 return &ret; 377 } else { 378 auto wrappedRet = wrappedFunc(dArgs.expand); 379 ret = excelRet(wrappedRet); 380 381 // dispose of the memory allocated in the wrapped function 382 static if(hasUDA!(wrappedFunc, Dispose)) { 383 alias disposes = getUDAs!(wrappedFunc, Dispose); 384 static assert(disposes.length == 1, "Too many @Dispose for " ~ wrappedFunc.stringof); 385 disposes[0].dispose(wrappedRet); 386 } 387 388 return &ret; 389 } 390 391 } catch(Exception ex) { 392 ret = stringOper(text("#ERROR calling ", __traits(identifier, wrappedFunc), ": ", ex.msg)); 393 return &ret; 394 } catch(Throwable t) { 395 ret = stringOper(text("#FATAL ERROR calling ", __traits(identifier, wrappedFunc), ": ", t.msg)); 396 return &ret; 397 } 398 } 399 400 401 private XLOPER12 stringOper(in string msg) @safe @nogc nothrow { 402 import xlld.conv: toAutoFreeOper; 403 import xlld.sdk.xlcall: XlType; 404 405 try 406 return () @trusted { return msg.toAutoFreeOper; }(); 407 catch(Exception _) { 408 XLOPER12 ret; 409 ret.xltype = XlType.xltypeErr; 410 return ret; 411 } 412 } 413 414 415 416 // get excel return value from D return value of wrapped function 417 XLOPER12 excelRet(T)(T wrappedRet) { 418 419 import xlld.conv: toAutoFreeOper; 420 import xlld.conv.misc: stripMemoryBitmask; 421 import xlld.sdk.xlcall: XlType; 422 import std.traits: isArray; 423 424 static if(isArray!(typeof(wrappedRet))) { 425 426 // Excel crashes if it's returned an empty array, so stop that from happening 427 if(wrappedRet.length == 0) { 428 return "#ERROR: empty result".toAutoFreeOper; 429 } 430 431 static if(isArray!(typeof(wrappedRet[0]))) { 432 if(wrappedRet[0].length == 0) { 433 return "#ERROR: empty result".toAutoFreeOper; 434 } 435 } 436 } 437 438 // convert the return value to an Excel type, tell Excel to call 439 // us back to free it afterwards 440 auto ret = toAutoFreeOper(wrappedRet); 441 442 // convert 1D arrays called from a column into a column instead of the default row 443 static if(isArray!(typeof(wrappedRet))) { 444 static if(!isArray!(typeof(wrappedRet[0]))) { // 1D array 445 import xlld.func.xlf: xlfCaller = caller; 446 import std.algorithm: swap; 447 448 try { 449 auto caller = xlfCaller; 450 if(caller.xltype.stripMemoryBitmask == XlType.xltypeSRef) { 451 const isColumnCaller = caller.val.sref.ref_.colLast == caller.val.sref.ref_.colFirst; 452 if(isColumnCaller) swap(ret.val.array.rows, ret.val.array.columns); 453 } 454 } catch(Exception _) {} 455 } 456 } 457 458 return ret; 459 } 460 461 462 463 464 private void freeDArgs(A, T)(ref A allocator, ref T dArgs) { 465 static if(__traits(compiles, allocator.deallocateAll)) 466 allocator.deallocateAll; 467 else { 468 foreach(ref dArg; dArgs) { 469 import std.traits: isPointer, isArray; 470 static if(isArray!(typeof(dArg))) 471 { 472 import std.experimental.allocator: disposeMultidimensionalArray; 473 allocator.disposeMultidimensionalArray(dArg[]); 474 } 475 else 476 static if(isPointer!(typeof(dArg))) 477 { 478 import std.experimental.allocator: dispose; 479 allocator.dispose(dArg); 480 } 481 } 482 } 483 } 484 485 // if a function can be wrapped to be baclled by Excel 486 private template isWorksheetFunction(alias F) { 487 import xlld.wrap.traits: isSupportedFunction; 488 enum isWorksheetFunction = isSupportedFunction!(F, isWantedType); 489 } 490 491 template isWantedType(T) { 492 import xlld.wrap.traits: isOneOf; 493 import xlld.any: Any; 494 import std.datetime: DateTime; 495 import std.traits: Unqual; 496 497 alias U = Unqual!T; 498 499 enum isOneOfTypes = isOneOf!( 500 U, 501 bool, 502 int, 503 double, double[], double[][], 504 string, string[], string[][], 505 Any, Any[], Any[][], 506 DateTime, DateTime[], DateTime[][], 507 ); 508 509 static if(isOneOfTypes) 510 enum isWantedType = true; 511 else static if(is(U == enum) || is(U == struct)) 512 enum isWantedType = true; 513 else static if(is(U: E[], E)) 514 enum isWantedType = isWantedType!E; 515 else 516 enum isWantedType = false; 517 } 518 519 520 version(testingExcelD) { 521 @("isWorksheetFunction") 522 @safe pure unittest { 523 static import test.d_funcs; 524 // the line below checks that the code still compiles even with a private function 525 // it might stop compiling in a future version when the deprecation rules for 526 // visibility kick in 527 static assert(!isWorksheetFunction!(test.d_funcs.shouldNotBeAProblem)); 528 static assert( isWorksheetFunction!(test.d_funcs.FuncThrows)); 529 static assert( isWorksheetFunction!(test.d_funcs.DoubleArrayToAnyArray)); 530 static assert( isWorksheetFunction!(test.d_funcs.Twice)); 531 static assert( isWorksheetFunction!(test.d_funcs.DateTimeToDouble)); 532 static assert( isWorksheetFunction!(test.d_funcs.BoolToInt)); 533 static assert( isWorksheetFunction!(test.d_funcs.FuncSimpleTupleRet)); 534 static assert( isWorksheetFunction!(test.d_funcs.FuncTupleArrayRet)); 535 static assert( isWorksheetFunction!(test.d_funcs.FuncDateAndStringRet)); 536 } 537 }