1 module xlld.wrap; 2 3 import xlld.xlcall; 4 import xlld.traits: isSupportedFunction; 5 import xlld.framework: FreeXLOper; 6 import xlld.worksheet; 7 import std.traits: isArray; 8 9 version(unittest) { 10 import unit_threaded; 11 12 /// emulates SRef types by storing what the referenced type actually is 13 XlType gReferencedType; 14 15 // tracks calls to `coerce` and `free` to make sure memory allocations/deallocations match 16 int gNumXlCoerce; 17 int gNumXlFree; 18 enum maxCoerce = 1000; 19 void*[maxCoerce] gCoerced; 20 void*[maxCoerce] gFreed; 21 22 // automatically converts from oper to compare with a D type 23 void shouldEqualDlang(U)(LPXLOPER12 actual, U expected, string file = __FILE__, size_t line = __LINE__) { 24 if(actual.xltype == xltypeErr) 25 fail("XLOPER is of error type", file, line); 26 actual.fromXlOper!U.shouldEqual(expected, file, line); 27 } 28 29 // automatically converts from oper to compare with a D type 30 void shouldEqualDlang(U)(ref XLOPER12 actual, U expected, string file = __FILE__, size_t line = __LINE__) { 31 shouldEqualDlang(&actual, expected, file, line); 32 } 33 34 XLOPER12 toSRef(T)(T val) { 35 auto ret = toXlOper(val); 36 //hide real type somewhere to retrieve it 37 gReferencedType = ret.xltype; 38 ret.xltype = XlType.xltypeSRef; 39 return ret; 40 } 41 42 // tracks allocations and throws in the destructor if there is a memory leak 43 // it also throws when there is an attempt to deallocate memory that wasn't 44 // allocated 45 struct TestAllocator { 46 import std.experimental.allocator.common: platformAlignment; 47 import std.experimental.allocator.mallocator: Mallocator; 48 49 alias allocator = Mallocator.instance; 50 51 private static struct ByteRange { 52 void* ptr; 53 size_t length; 54 } 55 private ByteRange[] _allocations; 56 private int _numAllocations; 57 58 enum uint alignment = platformAlignment; 59 60 void[] allocate(size_t numBytes) { 61 ++_numAllocations; 62 auto ret = allocator.allocate(numBytes); 63 writelnUt("+ Allocated ptr ", ret.ptr, " of ", ret.length, " bytes length"); 64 _allocations ~= ByteRange(ret.ptr, ret.length); 65 return ret; 66 } 67 68 bool deallocate(void[] bytes) { 69 import std.algorithm: remove, canFind; 70 import std.exception: enforce; 71 import std.conv: text; 72 73 writelnUt("- Deallocate ptr ", bytes.ptr, " of ", bytes.length, " bytes length"); 74 75 bool pred(ByteRange other) { return other.ptr == bytes.ptr && other.length == bytes.length; } 76 77 enforce(_allocations.canFind!pred, 78 text("Unknown deallocate byte range. Ptr: ", bytes.ptr, " length: ", bytes.length, 79 " allocations: ", _allocations)); 80 _allocations = _allocations.remove!pred; 81 return allocator.deallocate(bytes); 82 } 83 84 auto numAllocations() @safe pure nothrow const { 85 return _numAllocations; 86 } 87 88 ~this() { 89 import std.exception: enforce; 90 import std.conv: text; 91 enforce(!_allocations.length, text("Memory leak in TestAllocator. Allocations: ", _allocations)); 92 } 93 } 94 } 95 96 // this shouldn't be needed IMHO and is a bug in std.experimental.allocator that dispose 97 // doesn't handle 2D arrays correctly 98 void dispose(A, T)(auto ref A allocator, T[] array) { 99 static import std.experimental.allocator; 100 import std.traits: Unqual; 101 102 static if(isArray!T) { 103 foreach(ref e; array) { 104 dispose(allocator, e); 105 } 106 } 107 108 alias U = Unqual!T; 109 std.experimental.allocator.dispose(allocator, cast(U[])array); 110 } 111 112 void dispose(A, T)(auto ref A allocator, T value) if(!isArray!T) { 113 static import std.experimental.allocator; 114 std.experimental.allocator.dispose(allocator, value); 115 } 116 117 XLOPER12 toXlOper(T, A)(T val, ref A allocator) if(is(T == double)) { 118 return toXlOper(val); 119 } 120 121 XLOPER12 toXlOper(T)(T val) if(is(T == double)) { 122 auto ret = XLOPER12(); 123 ret.xltype = XlType.xltypeNum; 124 ret.val.num = val; 125 return ret; 126 } 127 128 129 XLOPER12 toXlOper(T)(T val) if(is(T == string)) { 130 import xlld.memorymanager: allocator; 131 return toXlOper(val, allocator); 132 } 133 134 XLOPER12 toXlOper(T, A)(T val, ref A allocator) if(is(T == string)) { 135 import std.utf: byWchar; 136 import std.stdio; 137 138 // extra space for the length 139 auto wval = cast(wchar*)allocator.allocate((val.length + 1) * wchar.sizeof).ptr; 140 wval[0] = cast(wchar)val.length; 141 142 int i = 1; 143 foreach(ch; val.byWchar) { 144 wval[i++] = ch; 145 } 146 147 auto ret = XLOPER12(); 148 ret.xltype = XlType.xltypeStr; 149 ret.val.str = cast(XCHAR*)wval; 150 151 return ret; 152 } 153 154 155 @("toXlOper!string ascii") 156 @system unittest { 157 import std.conv: to; 158 159 const str = "foo"; 160 auto oper = str.toXlOper; 161 scope(exit)FreeXLOper(&oper); 162 163 oper.xltype.shouldEqual(xltypeStr); 164 (cast(int)oper.val.str[0]).shouldEqual(str.length); 165 (cast(wchar*)oper.val.str)[1 .. str.length + 1].to!string.shouldEqual("foo"); 166 } 167 168 @("toXlOper!string allocator") 169 @system unittest { 170 // should throw unless allocations match deallocations 171 TestAllocator allocator; 172 auto oper = "foo".toXlOper(allocator); 173 allocator.numAllocations.shouldEqual(1); 174 FreeXLOper(&oper, allocator); 175 } 176 177 XLOPER12 toXlOper(T)(T[][] values) if(is(T == double) || is(T == string)) 178 { 179 import xlld.memorymanager: allocator; 180 return toXlOper(values, allocator); 181 } 182 183 XLOPER12 toXlOper(T, A)(T[][] values, ref A allocator) 184 if(is(T == double) || is(T == string)) 185 { 186 import std.algorithm: map, all; 187 import std.array: array; 188 import std.exception: enforce; 189 import std.conv: text; 190 191 static const exception = new Exception("# of columns must all be the same and aren't"); 192 if(!values.all!(a => a.length == values[0].length)) 193 throw exception; 194 195 auto ret = XLOPER12(); 196 ret.xltype = XlType.xltypeMulti; 197 const rows = cast(int)values.length; 198 ret.val.array.rows = rows; 199 const cols = cast(int)values[0].length; 200 ret.val.array.columns = cols; 201 202 ret.val.array.lparray = cast(XLOPER12*)allocator.allocate(rows * cols * ret.sizeof).ptr; 203 auto opers = ret.val.array.lparray[0 .. rows*cols]; 204 205 int i; 206 foreach(ref row; values) 207 foreach(ref val; row) { 208 opers[i++] = val.toXlOper(allocator); 209 } 210 211 return ret; 212 } 213 214 215 @("toXlOper string[][]") 216 @system unittest { 217 auto oper = [["foo", "bar", "baz"], ["toto", "titi", "quux"]].toXlOper; 218 scope(exit) FreeXLOper(&oper); 219 220 oper.xltype.shouldEqual(xltypeMulti); 221 oper.val.array.rows.shouldEqual(2); 222 oper.val.array.columns.shouldEqual(3); 223 auto opers = oper.val.array.lparray[0 .. oper.val.array.rows * oper.val.array.columns]; 224 225 opers[0].shouldEqualDlang("foo"); 226 opers[3].shouldEqualDlang("toto"); 227 opers[5].shouldEqualDlang("quux"); 228 } 229 230 @("toXlOper string[][] allocator") 231 @system unittest { 232 TestAllocator allocator; 233 auto oper = [["foo", "bar", "baz"], ["toto", "titi", "quux"]].toXlOper(allocator); 234 allocator.numAllocations.shouldEqual(7); 235 FreeXLOper(&oper, allocator); 236 } 237 238 @("toXlOper double[][] allocator") 239 @system unittest { 240 TestAllocator allocator; 241 auto oper = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]].toXlOper(allocator); 242 allocator.numAllocations.shouldEqual(1); 243 FreeXLOper(&oper, allocator); 244 } 245 246 247 XLOPER12 toXlOper(T)(T values) if(is(T == string[]) || is(T == double[])) { 248 import xlld.memorymanager: allocator; 249 return toXlOper(values, allocator); 250 } 251 252 XLOPER12 toXlOper(T, A)(T values, ref A allocator) if(is(T == string[]) || is(T == double[])) { 253 T[1] realValues = [values]; 254 return realValues.toXlOper(allocator); 255 } 256 257 258 @("toXlOper string[] allocator") 259 @system unittest { 260 TestAllocator allocator; 261 auto oper = ["foo", "bar", "baz", "toto", "titi", "quux"].toXlOper(allocator); 262 allocator.numAllocations.shouldEqual(7); 263 FreeXLOper(&oper, allocator); 264 } 265 266 auto fromXlOper(T)(ref XLOPER12 val) { 267 return (&val).fromXlOper!T; 268 } 269 270 auto fromXlOper(T, A)(ref XLOPER12 val, ref A allocator) { 271 return (&val).fromXlOper!T(allocator); 272 } 273 274 275 auto fromXlOper(T, A)(LPXLOPER12 val, ref A allocator) if(is(T == double)) { 276 return fromXlOper!T(val); 277 } 278 279 auto fromXlOper(T)(LPXLOPER12 val) if(is(T == double)) { 280 if(val.xltype == xltypeMissing) 281 return double.init; 282 283 return val.val.num; 284 } 285 286 @("fromXlOper double allocator") 287 @system unittest { 288 TestAllocator allocator; 289 auto num = 4.0; 290 auto oper = num.toXlOper(allocator); 291 auto back = oper.fromXlOper!double(allocator); 292 back.shouldEqual(num); 293 294 FreeXLOper(&oper); 295 } 296 297 298 @("isNan for fromXlOper!double") 299 @system unittest { 300 import std.math: isNaN; 301 XLOPER12 oper; 302 oper.xltype = XlType.xltypeMissing; 303 fromXlOper!double(&oper).isNaN.shouldBeTrue; 304 } 305 306 // 2D slices 307 auto fromXlOper(T)(LPXLOPER12 val) if(is(T: E[][], E) && (is(E == string) || is(E == double))) 308 { 309 import xlld.memorymanager: allocator; 310 return fromXlOper!T(val, allocator); 311 } 312 313 314 auto fromXlOper(T, A)(LPXLOPER12 val, ref A allocator) 315 if(is(T: E[][], E) && (is(E == string) || is(E == double))) 316 { 317 return val.fromXlOperMulti!(Dimensions.Two, typeof(T.init[0][0]))(allocator); 318 } 319 320 @("fromXlOper!string[][]") 321 unittest { 322 auto strings = [["foo", "bar", "baz"], ["toto", "titi", "quux"]]; 323 auto oper = strings.toXlOper; 324 scope(exit) FreeXLOper(&oper); 325 oper.fromXlOper!(string[][]).shouldEqual(strings); 326 } 327 328 @("fromXlOper!double[][]") 329 unittest { 330 auto doubles = [[1.0, 2.0], [3.0, 4.0]]; 331 auto oper = doubles.toXlOper; 332 scope(exit) FreeXLOper(&oper); 333 oper.fromXlOper!(double[][]).shouldEqual(doubles); 334 } 335 336 @("fromXlOper!string[][] allocator") 337 unittest { 338 TestAllocator allocator; 339 auto strings = [["foo", "bar", "baz"], ["toto", "titi", "quux"]]; 340 auto oper = strings.toXlOper(allocator); 341 auto backAgain = oper.fromXlOper!(string[][])(allocator); 342 343 allocator.numAllocations.shouldEqual(16); 344 345 FreeXLOper(&oper, allocator); 346 backAgain.shouldEqual(strings); 347 allocator.dispose(backAgain); 348 } 349 350 @("fromXlOper!double[][] allocator") 351 unittest { 352 TestAllocator allocator; 353 auto doubles = [[1.0, 2.0], [3.0, 4.0]]; 354 auto oper = doubles.toXlOper(allocator); 355 auto backAgain = oper.fromXlOper!(double[][])(allocator); 356 357 allocator.numAllocations.shouldEqual(4); 358 359 FreeXLOper(&oper, allocator); 360 backAgain.shouldEqual(doubles); 361 allocator.dispose(backAgain); 362 } 363 364 365 private enum Dimensions { 366 One, 367 Two, 368 } 369 370 // 1D slices 371 auto fromXlOper(T)(LPXLOPER12 val) 372 if(is(T: E[], E) && (is(E == string) || is(E == double))) 373 { 374 import xlld.memorymanager: allocator; 375 return fromXlOper!T(val, allocator); 376 } 377 378 // 1D slices 379 auto fromXlOper(T, A)(LPXLOPER12 val, ref A allocator) 380 if(is(T: E[], E) && (is(E == string) || is(E == double))) 381 { 382 return val.fromXlOperMulti!(Dimensions.One, typeof(T.init[0]))(allocator); 383 } 384 385 386 @("fromXlOper!string[]") 387 unittest { 388 auto strings = ["foo", "bar", "baz", "toto", "titi", "quux"]; 389 auto oper = strings.toXlOper; 390 scope(exit) FreeXLOper(&oper); 391 oper.fromXlOper!(string[]).shouldEqual(strings); 392 } 393 394 @("fromXlOper!double[]") 395 unittest { 396 auto doubles = [1.0, 2.0, 3.0, 4.0]; 397 auto oper = doubles.toXlOper; 398 scope(exit) FreeXLOper(&oper); 399 oper.fromXlOper!(double[]).shouldEqual(doubles); 400 } 401 402 @("fromXlOper!string[] allocator") 403 unittest { 404 TestAllocator allocator; 405 auto strings = ["foo", "bar", "baz", "toto", "titi", "quux"]; 406 auto oper = strings.toXlOper(allocator); 407 auto backAgain = oper.fromXlOper!(string[])(allocator); 408 409 allocator.numAllocations.shouldEqual(14); 410 411 backAgain.shouldEqual(strings); 412 FreeXLOper(&oper, allocator); 413 allocator.dispose(backAgain); 414 } 415 416 @("fromXlOper!double[] allocator") 417 unittest { 418 TestAllocator allocator; 419 auto doubles = [1.0, 2.0, 3.0, 4.0]; 420 auto oper = doubles.toXlOper(allocator); 421 auto backAgain = oper.fromXlOper!(double[])(allocator); 422 423 allocator.numAllocations.shouldEqual(2); 424 425 backAgain.shouldEqual(doubles); 426 FreeXLOper(&oper, allocator); 427 allocator.dispose(backAgain); 428 } 429 430 431 private auto fromXlOperMulti(Dimensions dim, T, A)(LPXLOPER12 val, ref A allocator) { 432 import xlld.xl: coerce, free; 433 import std.exception: enforce; 434 import std.experimental.allocator: makeArray; 435 436 static const exception = new Exception("XL oper not of multi type"); 437 438 const realType = val.xltype & ~xlbitDLLFree; 439 if(realType != xltypeMulti) 440 throw exception; 441 442 const rows = val.val.array.rows; 443 const cols = val.val.array.columns; 444 445 static if(dim == Dimensions.Two) { 446 auto ret = allocator.makeArray!(T[])(rows); 447 foreach(ref row; ret) 448 row = allocator.makeArray!T(cols); 449 } else static if(dim == Dimensions.One) { 450 451 auto ret = allocator.makeArray!T(rows * cols); 452 } else 453 static assert(0); 454 455 auto values = val.val.array.lparray[0 .. (rows * cols)]; 456 457 foreach(const row; 0 .. rows) { 458 foreach(const col; 0 .. cols) { 459 auto cellVal = coerce(&values[row * cols + col]); 460 scope(exit) free(&cellVal); 461 462 auto value = cellVal.xltype == dlangToXlOperType!T.Type ? cellVal.fromXlOper!T(allocator) : T.init; 463 static if(dim == Dimensions.Two) 464 ret[row][col] = value; 465 else 466 ret[row * cols + col] = value; 467 } 468 } 469 470 return ret; 471 } 472 473 474 auto fromXlOper(T)(LPXLOPER12 val) if(is(T == string)) { 475 import xlld.memorymanager: allocator; 476 return fromXlOper!T(val, allocator); 477 } 478 479 auto fromXlOper(T, A)(LPXLOPER12 val, ref A allocator) if(is(T == string)) { 480 481 import std.experimental.allocator: makeArray; 482 import std.utf; 483 484 const stripType = val.xltype & ~(xlbitXLFree | xlbitDLLFree); 485 if(stripType != xltypeStr) 486 return null; 487 488 auto ret = allocator.makeArray!char(val.val.str[0]); 489 int i; 490 foreach(ch; val.val.str[1 .. ret.length + 1].byChar) 491 ret[i++] = ch; 492 493 return cast(string)ret; 494 } 495 496 @("fromXlOper missing") 497 @system unittest { 498 XLOPER12 oper; 499 oper.xltype = XlType.xltypeMissing; 500 fromXlOper!string(&oper).shouldBeNull; 501 } 502 503 @("fromXlOper string allocator") 504 @system unittest { 505 TestAllocator allocator; 506 auto oper = "foo".toXlOper(allocator); 507 auto str = fromXlOper!string(&oper, allocator); 508 allocator.numAllocations.shouldEqual(2); 509 510 FreeXLOper(&oper, allocator); 511 str.shouldEqual("foo"); 512 allocator.dispose(cast(void[])str); 513 } 514 515 private enum isWorksheetFunction(alias F) = 516 isSupportedFunction!(F, double, double[][], string[][], string[], double[], string); 517 518 @safe pure unittest { 519 import xlld.test_d_funcs; 520 // the line below checks that the code still compiles even with a private function 521 // it might stop compiling in a future version when the deprecation rules for 522 // visibility kick in 523 static assert(!isWorksheetFunction!shouldNotBeAProblem); 524 static assert(!isWorksheetFunction!FuncThrows); 525 } 526 527 /** 528 A string to mixin that wraps all eligible functions in the 529 given module. 530 */ 531 string wrapModuleWorksheetFunctionsString(string moduleName)() { 532 if(!__ctfe) { 533 return ""; 534 } 535 536 import xlld.traits: Identity; 537 import std.array: join; 538 import std.traits: ReturnType, Parameters; 539 540 mixin(`import ` ~ moduleName ~ `;`); 541 alias module_ = Identity!(mixin(moduleName)); 542 543 string ret = `static import ` ~ moduleName ~ ";\n\n"; 544 545 foreach(moduleMemberStr; __traits(allMembers, module_)) { 546 alias moduleMember = Identity!(__traits(getMember, module_, moduleMemberStr)); 547 548 static if(isWorksheetFunction!moduleMember) { 549 ret ~= wrapModuleFunctionStr!(moduleName, moduleMemberStr); 550 } 551 } 552 553 return ret; 554 } 555 556 557 @("Wrap double[][] -> double") 558 @system unittest { 559 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 560 561 auto arg = toSRef(cast(double[][])[[1, 2, 3, 4], [11, 12, 13, 14]]); 562 FuncAddEverything(&arg).shouldEqualDlang(60.0); 563 564 arg = toSRef(cast(double[][])[[0, 1, 2, 3], [4, 5, 6, 7]]); 565 FuncAddEverything(&arg).shouldEqualDlang(28.0); 566 } 567 568 @("Wrap double[][] -> double[][]") 569 @system unittest { 570 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 571 572 auto arg = toSRef(cast(double[][])[[1, 2, 3, 4], [11, 12, 13, 14]]); 573 FuncTripleEverything(&arg).shouldEqualDlang(cast(double[][])[[3, 6, 9, 12], [33, 36, 39, 42]]); 574 575 arg = toSRef(cast(double[][])[[0, 1, 2, 3], [4, 5, 6, 7]]); 576 FuncTripleEverything(&arg).shouldEqualDlang(cast(double[][])[[0, 3, 6, 9], [12, 15, 18, 21]]); 577 } 578 579 580 @("Wrap string[][] -> double") 581 @system unittest { 582 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 583 584 auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]]); 585 FuncAllLengths(&arg).shouldEqualDlang(29.0); 586 587 arg = toSRef([["", "", "", ""], ["", "", "", ""]]); 588 FuncAllLengths(&arg).shouldEqualDlang(0.0); 589 } 590 591 @("Wrap string[][] -> double[][]") 592 @system unittest { 593 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 594 595 auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]]); 596 FuncLengths(&arg).shouldEqualDlang(cast(double[][])[[3, 3, 3, 4], [4, 4, 4, 4]]); 597 598 arg = toSRef([["", "", ""], ["", "", "huh"]]); 599 FuncLengths(&arg).shouldEqualDlang(cast(double[][])[[0, 0, 0], [0, 0, 3]]); 600 } 601 602 @("Wrap string[][] -> string[][]") 603 @system unittest { 604 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 605 606 auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]]); 607 FuncBob(&arg).shouldEqualDlang([["foobob", "barbob", "bazbob", "quuxbob"], 608 ["totobob", "titibob", "tutubob", "tetebob"]]); 609 } 610 611 @("Wrap string[] -> double") 612 @system unittest { 613 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 614 auto arg = toSRef([["foo", "bar"], ["baz", "quux"]]); 615 FuncStringSlice(&arg).shouldEqualDlang(4.0); 616 } 617 618 @("Wrap double[] -> double") 619 @system unittest { 620 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 621 auto arg = toSRef([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]); 622 FuncDoubleSlice(&arg).shouldEqualDlang(6.0); 623 } 624 625 @("Wrap double[] -> double[]") 626 @system unittest { 627 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 628 auto arg = toSRef([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]); 629 FuncSliceTimes3(&arg).shouldEqualDlang([3.0, 6.0, 9.0, 12.0, 15.0, 18.0]); 630 } 631 632 @("Wrap string[] -> string[]") 633 @system unittest { 634 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 635 auto arg = toSRef(["quux", "toto"]); 636 StringsToStrings(&arg).shouldEqualDlang(["quuxfoo", "totofoo"]); 637 } 638 639 @("Wrap string[] -> string") 640 @system unittest { 641 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 642 auto arg = toSRef(["quux", "toto"]); 643 StringsToString(&arg).shouldEqualDlang("quux, toto"); 644 } 645 646 @("Wrap string -> string") 647 @system unittest { 648 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 649 auto arg = toXlOper("foo"); 650 StringToString(&arg).shouldEqualDlang("foobar"); 651 } 652 653 @("Wrap string, string, string -> string") 654 @system unittest { 655 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 656 auto arg0 = toXlOper("foo"); 657 auto arg1 = toXlOper("bar"); 658 auto arg2 = toXlOper("baz"); 659 ManyToString(&arg0, &arg1, &arg2).shouldEqualDlang("foobarbaz"); 660 } 661 662 @("Only look at nothrow functions") 663 @system unittest { 664 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 665 auto arg = toXlOper(2.0); 666 static assert(!__traits(compiles, FuncThrows(&arg))); 667 } 668 669 @("FuncAddEverything wrapper is @nogc") 670 @system @nogc unittest { 671 import std.experimental.allocator.mallocator: Mallocator; 672 673 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 674 auto arg = toXlOper(2.0, Mallocator.instance); 675 scope(exit) FreeXLOper(&arg, Mallocator.instance); 676 FuncAddEverything(&arg); 677 } 678 679 private enum invalidXlOperType = 0xdeadbeef; 680 681 /** 682 Maps a D type to two integer xltypes from XLOPER12. 683 InputType is the type actually passed in by the spreadsheet, 684 whilst Type is the Type that it gets coerced to. 685 */ 686 template dlangToXlOperType(T) { 687 static if(is(T == double[][]) || is(T == string[][]) || is(T == double[]) || is(T == string[])) { 688 enum InputType = xltypeSRef; 689 enum Type= xltypeMulti; 690 } else static if(is(T == double)) { 691 enum InputType = xltypeNum; 692 enum Type = xltypeNum; 693 } else static if(is(T == string)) { 694 enum InputType = xltypeStr; 695 enum Type = xltypeStr; 696 } else { 697 enum InputType = invalidXlOperType; 698 enum Type = invalidXlOperType; 699 } 700 } 701 702 /** 703 A string to use with `mixin` that wraps a D function 704 */ 705 string wrapModuleFunctionStr(string moduleName, string funcName)() { 706 if(!__ctfe) { 707 return ""; 708 } 709 710 import std.array: join; 711 import std.traits: Parameters, functionAttributes, FunctionAttribute, getUDAs; 712 import std.conv: to; 713 import std.algorithm: map; 714 import std.range: iota; 715 mixin("import " ~ moduleName ~ ": " ~ funcName ~ ";"); 716 717 const argsLength = Parameters!(mixin(funcName)).length; 718 // e.g. LPXLOPER12 arg0, LPXLOPER12 arg1, ... 719 const argsDecl = argsLength.iota.map!(a => `LPXLOPER12 arg` ~ a.to!string).join(", "); 720 // e.g. arg0, arg1, ... 721 const argsCall = argsLength.iota.map!(a => `arg` ~ a.to!string).join(", "); 722 const nogc = functionAttributes!(mixin(funcName)) & FunctionAttribute.nogc 723 ? "@nogc " 724 : ""; 725 726 alias registerAttrs = getUDAs!(mixin(funcName), Register); 727 static assert(registerAttrs.length == 0 || registerAttrs.length == 1, 728 "Invalid number of @Register on " ~ funcName); 729 730 string register; 731 static if(registerAttrs.length) 732 register = `@` ~ registerAttrs[0].to!string; 733 734 return [ 735 register, 736 `extern(Windows) LPXLOPER12 ` ~ funcName ~ `(` ~ argsDecl ~ `) nothrow ` ~ nogc ~ `{`, 737 ` static import ` ~ moduleName ~ `;`, 738 ` alias wrappedFunc = ` ~ moduleName ~ `.` ~ funcName ~ `;`, 739 ` return wrapModuleFunctionImpl!wrappedFunc(` ~ argsCall ~ `);`, 740 `}`, 741 ].join("\n"); 742 } 743 744 @system unittest { 745 import xlld.worksheet; 746 import std.traits: getUDAs; 747 748 mixin(wrapModuleFunctionStr!("xlld.test_d_funcs", "FuncAddEverything")); 749 alias registerAttrs = getUDAs!(FuncAddEverything, Register); 750 static assert(registerAttrs[0].argumentText.value == "Array to add"); 751 } 752 753 /** 754 Implement a wrapper for a regular D function 755 */ 756 LPXLOPER12 wrapModuleFunctionImpl(alias wrappedFunc, T...)(T args) { 757 import xlld.memorymanager: allocator; 758 return wrapModuleFunctionImplAllocator!wrappedFunc(allocator, args); 759 } 760 761 LPXLOPER12 wrapModuleFunctionImplAllocator(alias wrappedFunc, A, T...) 762 (ref A allocator, T args) { 763 import xlld.xl: free; 764 import std.traits: Parameters; 765 import std.typecons: Tuple; 766 767 static XLOPER12 ret; 768 769 XLOPER12[T.length] realArgs; 770 // must 1st convert each argument to the "real" type. 771 // 2D arrays are passed in as SRefs, for instance 772 foreach(i, InputType; Parameters!wrappedFunc) { 773 if(args[i].xltype == xltypeMissing) { 774 realArgs[i] = *args[i]; 775 continue; 776 } 777 try 778 realArgs[i] = convertInput!InputType(args[i]); 779 catch(Exception ex) { 780 ret.xltype = XlType.xltypeErr; 781 ret.val.err = -1; 782 return &ret; 783 } 784 } 785 786 scope(exit) 787 foreach(ref arg; realArgs) 788 free(&arg); 789 790 Tuple!(Parameters!wrappedFunc) dArgs; // the D types to pass to the wrapped function 791 792 // next call the wrapped function with D types 793 foreach(i, InputType; Parameters!wrappedFunc) { 794 try { 795 dArgs[i] = fromXlOper!InputType(&realArgs[i], allocator); 796 } catch(Exception ex) { 797 ret.xltype = XlType.xltypeErr; 798 ret.val.err = -1; 799 return &ret; 800 } 801 } 802 803 try 804 ret = toXlOper(wrappedFunc(dArgs.expand), allocator); 805 catch(Exception ex) 806 return null; 807 808 foreach(ref dArg; dArgs) { 809 import std.traits: isPointer; 810 static if(isArray!(typeof(dArg)) || isPointer!(typeof(dArg))) 811 allocator.dispose(dArg); 812 } 813 814 ret.xltype |= xlbitDLLFree; 815 816 return &ret; 817 } 818 819 @("No memory allocation bugs in wrapModuleFunctionImplAllocator for double return") 820 @system unittest { 821 import xlld.test_d_funcs: FuncAddEverything; 822 823 TestAllocator allocator; 824 auto arg = toSRef([1.0, 2.0]); 825 auto oper = wrapModuleFunctionImplAllocator!FuncAddEverything(allocator, &arg); 826 (oper.xltype & xlbitDLLFree).shouldBeTrue; 827 allocator.numAllocations.shouldEqual(2); 828 oper.shouldEqualDlang(3.0); 829 FreeXLOper(oper, allocator); // normally this is done by Excel 830 } 831 832 @("No memory allocation bugs in wrapModuleFunctionImplAllocator for double[][] return") 833 @system unittest { 834 import xlld.test_d_funcs: FuncTripleEverything; 835 836 TestAllocator allocator; 837 auto arg = toSRef([1.0, 2.0, 3.0]); 838 auto oper = wrapModuleFunctionImplAllocator!FuncTripleEverything(allocator, &arg); 839 (oper.xltype & xlbitDLLFree).shouldBeTrue; 840 (oper.xltype & ~xlbitDLLFree).shouldEqual(xltypeMulti); 841 allocator.numAllocations.shouldEqual(3); 842 oper.shouldEqualDlang([[3.0, 6.0, 9.0]]); 843 FreeXLOper(oper, allocator); // normally this is done by Excel 844 } 845 846 string wrapWorksheetFunctionsString(Modules...)() { 847 848 if(!__ctfe) { 849 return ""; 850 } 851 852 string ret; 853 foreach(module_; Modules) { 854 ret ~= wrapModuleWorksheetFunctionsString!module_; 855 } 856 857 return ret; 858 } 859 860 861 string wrapAll(Modules...)(in string mainModule = __MODULE__) { 862 863 if(!__ctfe) { 864 return ""; 865 } 866 867 import xlld.traits: implGetWorksheetFunctionsString; 868 return 869 wrapWorksheetFunctionsString!Modules ~ 870 "\n" ~ 871 implGetWorksheetFunctionsString!(mainModule) ~ 872 "\n" ~ 873 `mixin GenerateDllDef!"` ~ mainModule ~ `";` ~ 874 "\n"; 875 } 876 877 @("wrapAll") 878 unittest { 879 import xlld.traits: getAllWorksheetFunctions, GenerateDllDef; 880 mixin(wrapAll!("xlld.test_d_funcs")); 881 auto arg = toSRef(cast(double[][])[[1, 2, 3, 4], [11, 12, 13, 14]]); 882 FuncAddEverything(&arg).shouldEqualDlang(60.0); 883 } 884 885 886 XLOPER12 convertInput(T)(LPXLOPER12 arg) { 887 import xlld.xl: coerce, free; 888 889 static exception = new const Exception("Error converting input"); 890 891 if(arg.xltype != dlangToXlOperType!T.InputType) 892 throw exception; 893 894 auto realArg = coerce(arg); 895 896 if(realArg.xltype != dlangToXlOperType!T.Type) { 897 free(&realArg); 898 throw exception; 899 } 900 901 return realArg; 902 } 903 904 /** 905 creates an XLOPER12 that can be returned to Excel which 906 will be freed by Excel itself 907 */ 908 XLOPER12 toAutoFreeOper(T)(T value) { 909 import xlld.memorymanager: autoFreeAllocator = allocator; 910 import xlld.xlcall: XlType; 911 912 auto result = value.toXlOper(autoFreeAllocator); 913 result.xltype |= XlType.xlbitDLLFree; 914 return result; 915 } 916 917 ushort operStringLength(T)(T value) { 918 import std.exception: enforce; 919 import std.conv: text; 920 921 enforce(value.xltype == xltypeStr, 922 text("Cannot calculate string length for oper of type ", value.xltype)); 923 924 return cast(ushort)value.val.str[0]; 925 } 926 927 auto fromXlOperCoerce(T)(LPXLOPER12 val) { 928 return fromXlOperCoerce(*val); 929 } 930 931 auto fromXlOperCoerce(T, A)(LPXLOPER12 val, auto ref A allocator) { 932 return fromXlOperCoerce!T(*val, allocator); 933 } 934 935 936 auto fromXlOperCoerce(T)(ref XLOPER12 val) { 937 import xlld.memorymanager: allocator; 938 return fromXlOperCoerce!T(val, allocator); 939 } 940 941 942 auto fromXlOperCoerce(T, A)(ref XLOPER12 val, auto ref A allocator) { 943 import std.experimental.allocator: dispose; 944 import xlld.xl: coerce, free; 945 946 auto coerced = coerce(&val); 947 scope(exit) free(&coerced); 948 949 return coerced.fromXlOper!T(allocator); 950 } 951 952 953 @("fromXlOperCoerce") 954 unittest { 955 double[][] doubles = [[1, 2, 3, 4], [11, 12, 13, 14]]; 956 auto doublesOper = toSRef(doubles); 957 doublesOper.fromXlOper!(double[][]).shouldThrowWithMessage("XL oper not of multi type"); 958 doublesOper.fromXlOperCoerce!(double[][]).shouldEqual(doubles); 959 }