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