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