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