1 module xlld.wrap; 2 3 import xlld.xlcall; 4 import xlld.traits: isSupportedFunction; 5 import xlld.memorymanager: autoFree; 6 import xlld.framework: freeXLOper; 7 import xlld.worksheet; 8 import xlld.any: Any; 9 import std.traits: Unqual; 10 11 12 13 version(unittest) { 14 import unit_threaded; 15 import xlld.test_util: TestAllocator, shouldEqualDlang, toSRef; 16 import std.experimental.allocator.mallocator: Mallocator; 17 import std.experimental.allocator.gc_allocator: GCAllocator; 18 import xlld.any: any; 19 alias theMallocator = Mallocator.instance; 20 alias theGC = GCAllocator.instance; 21 } 22 23 XLOPER12 toXlOper(T, A)(in T val, ref A allocator) if(is(T == int)) { 24 auto ret = XLOPER12(); 25 ret.xltype = XlType.xltypeInt; 26 ret.val.w = val; 27 return ret; 28 } 29 30 31 XLOPER12 toXlOper(T, A)(in T val, ref A allocator) if(is(T == double)) { 32 auto ret = XLOPER12(); 33 ret.xltype = XlType.xltypeNum; 34 ret.val.num = val; 35 return ret; 36 } 37 38 __gshared immutable toXlOperMemoryException = new Exception("Failed to allocate memory for string oper"); 39 __gshared immutable toXlOperShapeException = new Exception("# of columns must all be the same and aren't"); 40 41 XLOPER12 toXlOper(T, A)(in T val, ref A allocator) 42 if(is(T == string) || is(T == wstring)) 43 { 44 import std.utf: byWchar; 45 46 auto wval = cast(wchar*)allocator.allocate(numOperStringBytes(val)).ptr; 47 if(wval is null) 48 throw toXlOperMemoryException; 49 50 int i = 1; 51 foreach(ch; val.byWchar) { 52 wval[i++] = ch; 53 } 54 55 wval[0] = cast(ushort)(i - 1); 56 57 auto ret = XLOPER12(); 58 ret.xltype = XlType.xltypeStr; 59 ret.val.str = cast(XCHAR*)wval; 60 61 return ret; 62 } 63 64 @("toXlOper!string utf8") 65 @system unittest { 66 import std.conv: to; 67 import xlld.memorymanager: allocator; 68 69 const str = "foo"; 70 auto oper = str.toXlOper(allocator); 71 scope(exit) freeXLOper(&oper, allocator); 72 73 oper.xltype.shouldEqual(xltypeStr); 74 (cast(int)oper.val.str[0]).shouldEqual(str.length); 75 (cast(wchar*)oper.val.str)[1 .. str.length + 1].to!string.shouldEqual(str); 76 } 77 78 @("toXlOper!string utf16") 79 @system unittest { 80 import xlld.memorymanager: allocator; 81 82 const str = "foo"w; 83 auto oper = str.toXlOper(allocator); 84 scope(exit) freeXLOper(&oper, allocator); 85 86 oper.xltype.shouldEqual(xltypeStr); 87 (cast(int)oper.val.str[0]).shouldEqual(str.length); 88 (cast(wchar*)oper.val.str)[1 .. str.length + 1].shouldEqual(str); 89 } 90 91 @("toXlOper!string TestAllocator") 92 @system unittest { 93 auto allocator = TestAllocator(); 94 auto oper = "foo".toXlOper(allocator); 95 allocator.numAllocations.shouldEqual(1); 96 freeXLOper(&oper, allocator); 97 } 98 99 @("toXlOper!string unicode") 100 @system unittest { 101 import std.utf: byWchar; 102 import std.array: array; 103 104 "é".byWchar.array.length.shouldEqual(1); 105 "é"w.byWchar.array.length.shouldEqual(1); 106 107 auto oper = "é".toXlOper(theGC); 108 const ushort length = oper.val.str[0]; 109 length.shouldEqual("é"w.length); 110 } 111 112 // the number of bytes required to store `str` as an XLOPER12 string 113 package size_t numOperStringBytes(T)(in T str) if(is(T == string) || is(T == wstring)) { 114 // XLOPER12 strings are wide strings where index 0 is the length 115 // and [1 .. $] is the actual string 116 return (str.length + 1) * wchar.sizeof; 117 } 118 119 package size_t numOperStringBytes(ref const(XLOPER12) oper) @trusted @nogc pure nothrow { 120 // XLOPER12 strings are wide strings where index 0 is the length 121 // and [1 .. $] is the actual string 122 if(oper.xltype != XlType.xltypeStr) return 0; 123 return (oper.val.str[0] + 1) * wchar.sizeof; 124 } 125 126 127 XLOPER12 toXlOper(T, A)(T[][] values, ref A allocator) 128 if(is(T == double) || is(T == string) || is(Unqual!T == Any)) 129 { 130 import std.algorithm: map, all; 131 import std.array: array; 132 133 if(!values.all!(a => a.length == values[0].length)) 134 throw toXlOperShapeException; 135 136 const rows = cast(int)values.length; 137 const cols = values.length ? cast(int)values[0].length : 0; 138 auto ret = multi(rows, cols, allocator); 139 auto opers = ret.val.array.lparray[0 .. rows*cols]; 140 141 int i; 142 foreach(ref row; values) { 143 foreach(ref val; row) { 144 opers[i++] = val.toXlOper(allocator); 145 } 146 } 147 148 return ret; 149 } 150 151 152 @("toXlOper string[][]") 153 @system unittest { 154 import xlld.memorymanager: allocator; 155 156 auto oper = [["foo", "bar", "baz"], ["toto", "titi", "quux"]].toXlOper(allocator); 157 scope(exit) freeXLOper(&oper, allocator); 158 159 oper.xltype.shouldEqual(xltypeMulti); 160 oper.val.array.rows.shouldEqual(2); 161 oper.val.array.columns.shouldEqual(3); 162 auto opers = oper.val.array.lparray[0 .. oper.val.array.rows * oper.val.array.columns]; 163 164 opers[0].shouldEqualDlang("foo"); 165 opers[3].shouldEqualDlang("toto"); 166 opers[5].shouldEqualDlang("quux"); 167 } 168 169 @("toXlOper string[][]") 170 @system unittest { 171 TestAllocator allocator; 172 auto oper = [["foo", "bar", "baz"], ["toto", "titi", "quux"]].toXlOper(allocator); 173 allocator.numAllocations.shouldEqual(7); 174 freeXLOper(&oper, allocator); 175 } 176 177 @("toXlOper double[][]") 178 @system unittest { 179 TestAllocator allocator; 180 auto oper = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]].toXlOper(allocator); 181 allocator.numAllocations.shouldEqual(1); 182 freeXLOper(&oper, allocator); 183 } 184 185 __gshared immutable multiMemoryException = new Exception("Failed to allocate memory for multi oper"); 186 187 private XLOPER12 multi(A)(int rows, int cols, ref A allocator) { 188 auto ret = XLOPER12(); 189 190 ret.xltype = XlType.xltypeMulti; 191 ret.val.array.rows = rows; 192 ret.val.array.columns = cols; 193 194 ret.val.array.lparray = cast(XLOPER12*)allocator.allocate(rows * cols * ret.sizeof).ptr; 195 if(ret.val.array.lparray is null) 196 throw multiMemoryException; 197 198 return ret; 199 } 200 201 202 XLOPER12 toXlOper(T, A)(T values, ref A allocator) if(is(T == string[]) || is(T == double[])) { 203 T[1] realValues = [values]; 204 return realValues.toXlOper(allocator); 205 } 206 207 208 @("toXlOper string[]") 209 @system unittest { 210 TestAllocator allocator; 211 auto oper = ["foo", "bar", "baz", "toto", "titi", "quux"].toXlOper(allocator); 212 allocator.numAllocations.shouldEqual(7); 213 freeXLOper(&oper, allocator); 214 } 215 216 XLOPER12 toXlOper(T, A)(T value, ref A allocator) if(is(Unqual!T == Any)) { 217 return value._impl; 218 } 219 220 @("toXlOper any double") 221 unittest { 222 any(5.0, Mallocator.instance).toXlOper(theMallocator).shouldEqualDlang(5.0); 223 } 224 225 @("toXlOper any string") 226 unittest { 227 any("foo", Mallocator.instance).toXlOper(theMallocator).shouldEqualDlang("foo"); 228 } 229 230 @("toXlOper any double[][]") 231 unittest { 232 any([[1.0, 2.0], [3.0, 4.0]], Mallocator.instance) 233 .toXlOper(theMallocator).shouldEqualDlang([[1.0, 2.0], [3.0, 4.0]]); 234 } 235 236 @("toXlOper any string[][]") 237 unittest { 238 any([["foo", "bar"], ["quux", "toto"]], Mallocator.instance) 239 .toXlOper(theMallocator).shouldEqualDlang([["foo", "bar"], ["quux", "toto"]]); 240 } 241 242 243 XLOPER12 toXlOper(T, A)(T value, ref A allocator) if(is(Unqual!T == Any[])) { 244 return [value].toXlOper(allocator); 245 } 246 247 @("toXlOper any[]") 248 unittest { 249 import xlld.memorymanager: allocatorContext; 250 251 with(allocatorContext(Mallocator.instance)) { 252 auto oper = toXlOper([any(42.0), any("foo")]); 253 oper.xltype.shouldEqual(XlType.xltypeMulti); 254 oper.val.array.lparray[0].shouldEqualDlang(42.0); 255 oper.val.array.lparray[1].shouldEqualDlang("foo"); 256 } 257 } 258 259 260 @("toXlOper mixed 1D array of any") 261 unittest { 262 const a = any([any(1.0, theMallocator), any("foo", theMallocator)], 263 theMallocator); 264 auto oper = a.toXlOper(theMallocator); 265 oper.xltype.shouldEqual(XlType.xltypeMulti); 266 267 const rows = oper.val.array.rows; 268 const cols = oper.val.array.columns; 269 auto opers = oper.val.array.lparray[0 .. rows * cols]; 270 opers[0].shouldEqualDlang(1.0); 271 opers[1].shouldEqualDlang("foo"); 272 autoFree(&oper); // normally this is done by Excel 273 } 274 275 @("toXlOper any[][]") 276 unittest { 277 import xlld.memorymanager: allocatorContext; 278 279 with(allocatorContext(Mallocator.instance)) { 280 auto oper = toXlOper([[any(42.0), any("foo"), any("quux")], [any("bar"), any(7.0), any("toto")]]); 281 oper.xltype.shouldEqual(XlType.xltypeMulti); 282 oper.val.array.rows.shouldEqual(2); 283 oper.val.array.columns.shouldEqual(3); 284 oper.val.array.lparray[0].shouldEqualDlang(42.0); 285 oper.val.array.lparray[1].shouldEqualDlang("foo"); 286 oper.val.array.lparray[2].shouldEqualDlang("quux"); 287 oper.val.array.lparray[3].shouldEqualDlang("bar"); 288 oper.val.array.lparray[4].shouldEqualDlang(7.0); 289 oper.val.array.lparray[5].shouldEqualDlang("toto"); 290 } 291 } 292 293 294 @("toXlOper mixed 2D array of any") 295 unittest { 296 const a = any([ 297 [any(1.0, theMallocator), any(2.0, theMallocator)], 298 [any("foo", theMallocator), any("bar", theMallocator)] 299 ], 300 theMallocator); 301 auto oper = a.toXlOper(theMallocator); 302 oper.xltype.shouldEqual(XlType.xltypeMulti); 303 304 const rows = oper.val.array.rows; 305 const cols = oper.val.array.columns; 306 auto opers = oper.val.array.lparray[0 .. rows * cols]; 307 opers[0].shouldEqualDlang(1.0); 308 opers[1].shouldEqualDlang(2.0); 309 opers[2].shouldEqualDlang("foo"); 310 opers[3].shouldEqualDlang("bar"); 311 autoFree(&oper); // normally this is done by Excel 312 } 313 314 XLOPER12 toXlOper(T, A)(T value, ref A allocator) if(is(T == int)) { 315 XLOPER12 ret; 316 ret.xltype = XlType.xltypeInt; 317 ret.val.w = value; 318 return ret; 319 } 320 321 @("toExcelOper!int") 322 unittest { 323 auto oper = 42.toXlOper(theMallocator); 324 oper.xltype.shouldEqual(XlType.xltypeInt); 325 oper.val.w.shouldEqual(42); 326 } 327 328 auto fromXlOper(T, A)(ref XLOPER12 val, ref A allocator) { 329 return (&val).fromXlOper!T(allocator); 330 } 331 332 // RValue overload 333 auto fromXlOper(T, A)(XLOPER12 val, ref A allocator) { 334 return fromXlOper!T(val, allocator); 335 } 336 337 auto fromXlOper(T, A)(LPXLOPER12 val, ref A allocator) if(is(T == double)) { 338 if(val.xltype == xltypeMissing) 339 return double.init; 340 341 return val.val.num; 342 } 343 344 @("fromXlOper!double") 345 @system unittest { 346 347 TestAllocator allocator; 348 auto num = 4.0; 349 auto oper = num.toXlOper(allocator); 350 auto back = oper.fromXlOper!double(allocator); 351 back.shouldEqual(num); 352 353 freeXLOper(&oper, allocator); 354 } 355 356 357 @("isNan for fromXlOper!double") 358 @system unittest { 359 import std.math: isNaN; 360 import xlld.memorymanager: allocator; 361 XLOPER12 oper; 362 oper.xltype = XlType.xltypeMissing; 363 fromXlOper!double(&oper, allocator).isNaN.shouldBeTrue; 364 } 365 366 auto fromXlOper(T, A)(LPXLOPER12 val, ref A allocator) if(is(T == int)) { 367 if(val.xltype == xltypeMissing) 368 return int.init; 369 370 return val.val.w; 371 } 372 373 @system unittest { 374 42.toXlOper(theGC).fromXlOper!int(theGC).shouldEqual(42); 375 } 376 377 @("0 for fromXlOper!int missing oper") 378 @system unittest { 379 XLOPER12 oper; 380 oper.xltype = XlType.xltypeMissing; 381 oper.fromXlOper!int(theGC).shouldEqual(0); 382 } 383 384 __gshared immutable fromXlOperMemoryException = new Exception("Could not allocate memory for array of char"); 385 __gshared immutable fromXlOperConvException = new Exception("Could not convert double to string"); 386 387 auto fromXlOper(T, A)(LPXLOPER12 val, ref A allocator) if(is(T == string)) { 388 389 import std.experimental.allocator: makeArray; 390 import std.utf: byChar; 391 import std.range: walkLength; 392 393 const stripType = stripMemoryBitmask(val.xltype); 394 if(stripType != XlType.xltypeStr && stripType != XlType.xltypeNum) 395 return null; 396 397 398 if(stripType == XlType.xltypeStr) { 399 400 auto chars = val.val.str[1 .. val.val.str[0] + 1].byChar; 401 const length = chars.save.walkLength; 402 auto ret = allocator.makeArray!char(length); 403 404 if(ret is null && length > 0) 405 throw fromXlOperMemoryException; 406 407 int i; 408 foreach(ch; val.val.str[1 .. val.val.str[0] + 1].byChar) 409 ret[i++] = ch; 410 411 return cast(string)ret; 412 } else { 413 // if a double, try to convert it to a string 414 import core.stdc.stdio: snprintf; 415 char[1024] buffer; 416 const numChars = snprintf(&buffer[0], buffer.length, "%lf", val.val.num); 417 if(numChars > buffer.length - 1) 418 throw fromXlOperConvException; 419 auto ret = allocator.makeArray!char(numChars); 420 421 if(ret is null && numChars > 0) 422 throw fromXlOperMemoryException; 423 424 ret[] = buffer[0 .. numChars]; 425 return cast(string)ret; 426 } 427 } 428 429 @("fromXlOper!string missing") 430 @system unittest { 431 import xlld.memorymanager: allocator; 432 XLOPER12 oper; 433 oper.xltype = XlType.xltypeMissing; 434 fromXlOper!string(&oper, allocator).shouldBeNull; 435 } 436 437 @("fromXlOper!string") 438 @system unittest { 439 import std.experimental.allocator: dispose; 440 TestAllocator allocator; 441 auto oper = "foo".toXlOper(allocator); 442 auto str = fromXlOper!string(&oper, allocator); 443 allocator.numAllocations.shouldEqual(2); 444 445 freeXLOper(&oper, allocator); 446 str.shouldEqual("foo"); 447 allocator.dispose(cast(void[])str); 448 } 449 450 @("fromXlOper!string unicode") 451 @system unittest { 452 auto oper = "é".toXlOper(theGC); 453 auto str = fromXlOper!string(&oper, theGC); 454 str.shouldEqual("é"); 455 } 456 457 package XlType stripMemoryBitmask(in XlType type) @safe @nogc pure nothrow { 458 return cast(XlType)(type & ~(xlbitXLFree | xlbitDLLFree)); 459 } 460 461 T fromXlOper(T, A)(LPXLOPER12 oper, ref A allocator) if(is(T == Any)) { 462 // FIXME: deep copy 463 return Any(*oper); 464 } 465 466 @("fromXlOper any double") 467 @system unittest { 468 any(5.0, theMallocator).fromXlOper!Any(theMallocator).shouldEqual(any(5.0, theMallocator)); 469 } 470 471 @("fromXlOper any string") 472 @system unittest { 473 any("foo", theMallocator).fromXlOper!Any(theMallocator)._impl 474 .fromXlOper!string(theMallocator).shouldEqual("foo"); 475 } 476 477 auto fromXlOper(T, A)(LPXLOPER12 val, ref A allocator) 478 if(is(T: E[][], E) && (is(E == string) || is(E == double))) 479 { 480 return val.fromXlOperMulti!(Dimensions.Two, typeof(T.init[0][0]))(allocator); 481 } 482 483 @("fromXlOper!string[][]") 484 unittest { 485 import xlld.memorymanager: allocator; 486 487 auto strings = [["foo", "bar", "baz"], ["toto", "titi", "quux"]]; 488 auto oper = strings.toXlOper(allocator); 489 scope(exit) freeXLOper(&oper, allocator); 490 oper.fromXlOper!(string[][])(allocator).shouldEqual(strings); 491 } 492 493 @("fromXlOper!double[][]") 494 unittest { 495 import xlld.memorymanager: allocator; 496 497 auto doubles = [[1.0, 2.0], [3.0, 4.0]]; 498 auto oper = doubles.toXlOper(allocator); 499 scope(exit) freeXLOper(&oper, allocator); 500 oper.fromXlOper!(double[][])(allocator).shouldEqual(doubles); 501 } 502 503 @("fromXlOper!string[][] TestAllocator") 504 unittest { 505 import std.experimental.allocator: disposeMultidimensionalArray; 506 TestAllocator allocator; 507 auto strings = [["foo", "bar", "baz"], ["toto", "titi", "quux"]]; 508 auto oper = strings.toXlOper(allocator); 509 auto backAgain = oper.fromXlOper!(string[][])(allocator); 510 511 allocator.numAllocations.shouldEqual(16); 512 513 freeXLOper(&oper, allocator); 514 backAgain.shouldEqual(strings); 515 allocator.disposeMultidimensionalArray(cast(void[][][])backAgain); 516 } 517 518 @("fromXlOper!string[][] when not all opers are strings") 519 unittest { 520 import std.experimental.allocator.mallocator: Mallocator; 521 alias allocator = Mallocator.instance; 522 523 const rows = 2; 524 const cols = 3; 525 auto array = multi(rows, cols, allocator); 526 auto opers = array.val.array.lparray[0 .. rows*cols]; 527 const strings = ["foo", "bar", "baz"]; 528 const numbers = [1.0, 2.0, 3.0]; 529 530 int i; 531 foreach(r; 0 .. rows) { 532 foreach(c; 0 .. cols) { 533 if(r == 0) 534 opers[i++] = strings[c].toXlOper(allocator); 535 else 536 opers[i++] = numbers[c].toXlOper(allocator); 537 } 538 } 539 540 opers[3].fromXlOper!string(allocator).shouldEqual("1.000000"); 541 // sanity checks 542 opers[0].fromXlOper!string(allocator).shouldEqual("foo"); 543 opers[3].fromXlOper!double(allocator).shouldEqual(1.0); 544 // the actual assertion 545 array.fromXlOper!(string[][])(allocator).shouldEqual([["foo", "bar", "baz"], 546 ["1.000000", "2.000000", "3.000000"]]); 547 } 548 549 550 @("fromXlOper!double[][] TestAllocator") 551 unittest { 552 import std.experimental.allocator: disposeMultidimensionalArray; 553 TestAllocator allocator; 554 auto doubles = [[1.0, 2.0], [3.0, 4.0]]; 555 auto oper = doubles.toXlOper(allocator); 556 auto backAgain = oper.fromXlOper!(double[][])(allocator); 557 558 allocator.numAllocations.shouldEqual(4); 559 560 freeXLOper(&oper, allocator); 561 backAgain.shouldEqual(doubles); 562 allocator.disposeMultidimensionalArray(backAgain); 563 } 564 565 566 private enum Dimensions { 567 One, 568 Two, 569 } 570 571 572 // 1D slices 573 auto fromXlOper(T, A)(LPXLOPER12 val, ref A allocator) 574 if(is(T: E[], E) && (is(E == string) || is(E == double))) 575 { 576 return val.fromXlOperMulti!(Dimensions.One, typeof(T.init[0]))(allocator); 577 } 578 579 580 @("fromXlOper!string[]") 581 unittest { 582 import xlld.memorymanager: allocator; 583 584 auto strings = ["foo", "bar", "baz", "toto", "titi", "quux"]; 585 auto oper = strings.toXlOper(allocator); 586 scope(exit) freeXLOper(&oper, allocator); 587 oper.fromXlOper!(string[])(allocator).shouldEqual(strings); 588 } 589 590 @("fromXlOper!double[]") 591 unittest { 592 import xlld.memorymanager: allocator; 593 594 auto doubles = [1.0, 2.0, 3.0, 4.0]; 595 auto oper = doubles.toXlOper(allocator); 596 scope(exit) freeXLOper(&oper, allocator); 597 oper.fromXlOper!(double[])(allocator).shouldEqual(doubles); 598 } 599 600 @("fromXlOper!string[] TestAllocator") 601 unittest { 602 import std.experimental.allocator: disposeMultidimensionalArray; 603 TestAllocator allocator; 604 auto strings = ["foo", "bar", "baz", "toto", "titi", "quux"]; 605 auto oper = strings.toXlOper(allocator); 606 auto backAgain = oper.fromXlOper!(string[])(allocator); 607 608 allocator.numAllocations.shouldEqual(14); 609 610 backAgain.shouldEqual(strings); 611 freeXLOper(&oper, allocator); 612 allocator.disposeMultidimensionalArray(cast(void[][])backAgain); 613 } 614 615 @("fromXlOper!double[] TestAllocator") 616 unittest { 617 import std.experimental.allocator: dispose; 618 TestAllocator allocator; 619 auto doubles = [1.0, 2.0, 3.0, 4.0]; 620 auto oper = doubles.toXlOper(allocator); 621 auto backAgain = oper.fromXlOper!(double[])(allocator); 622 623 allocator.numAllocations.shouldEqual(2); 624 625 backAgain.shouldEqual(doubles); 626 freeXLOper(&oper, allocator); 627 allocator.dispose(backAgain); 628 } 629 630 __gshared immutable fromXlOperMultiOperException = new Exception("oper not of multi type"); 631 __gshared immutable fromXlOperMultiMemoryException = new Exception("Could not allocate memory in fromXlOperMulti"); 632 633 private auto fromXlOperMulti(Dimensions dim, T, A)(LPXLOPER12 val, ref A allocator) { 634 import xlld.xl: coerce, free; 635 import xlld.memorymanager: makeArray2D; 636 import std.experimental.allocator: makeArray; 637 638 if(!isMulti(*val)) { 639 throw fromXlOperMultiOperException; 640 } 641 642 const rows = val.val.array.rows; 643 const cols = val.val.array.columns; 644 645 assert(rows > 0 && cols > 0, "Multi opers may not have 0 rows or columns"); 646 647 static if(dim == Dimensions.Two) { 648 auto ret = allocator.makeArray2D!T(*val); 649 } else static if(dim == Dimensions.One) { 650 auto ret = allocator.makeArray!T(rows * cols); 651 } else 652 static assert(0, "Unknown number of dimensions in fromXlOperMulti"); 653 654 if(&ret[0] is null) 655 throw fromXlOperMultiMemoryException; 656 657 (*val).apply!(T, (shouldConvert, row, col, cellVal) { 658 659 auto value = shouldConvert ? cellVal.fromXlOper!T(allocator) : T.init; 660 661 static if(dim == Dimensions.Two) 662 ret[row][col] = value; 663 else 664 ret[row * cols + col] = value; 665 }); 666 667 return ret; 668 } 669 670 __gshared immutable applyTypeException = new Exception("apply failed - oper not of multi type"); 671 672 // apply a function to an oper of type xltypeMulti 673 // the function must take a boolean value indicating if the cell value 674 // is to be converted or not, and a reference to the cell value itself 675 package void apply(T, alias F)(ref XLOPER12 oper) { 676 import xlld.xlcall: XlType; 677 import xlld.xl: coerce, free; 678 import xlld.wrap: dlangToXlOperType, isMulti, numOperStringBytes; 679 import xlld.any: Any; 680 version(unittest) import xlld.test_util: gNumXlCoerce, gNumXlFree; 681 682 if(!isMulti(oper)) 683 throw applyTypeException; 684 685 const rows = oper.val.array.rows; 686 const cols = oper.val.array.columns; 687 auto values = oper.val.array.lparray[0 .. (rows * cols)]; 688 689 foreach(const row; 0 .. rows) { 690 foreach(const col; 0 .. cols) { 691 692 auto cellVal = coerce(&values[row * cols + col]); 693 694 // Issue 22's unittest ends up coercing more than test_util can handle 695 // so we undo the side-effect here 696 version(unittest) --gNumXlCoerce; // ignore this for testing 697 698 scope(exit) { 699 free(&cellVal); 700 // see comment above about gNumXlCoerce 701 version(unittest) --gNumXlFree; 702 } 703 704 // try to convert doubles to string if trying to convert everything to an 705 // array of strings 706 const shouldConvert = 707 (cellVal.xltype == dlangToXlOperType!T.Type) || 708 (cellVal.xltype == XlType.xltypeNum && dlangToXlOperType!T.Type == XlType.xltypeStr) || 709 is(T == Any); 710 711 F(shouldConvert, row, col, cellVal); 712 } 713 } 714 } 715 716 717 package bool isMulti(ref const(XLOPER12) oper) @safe @nogc pure nothrow { 718 const realType = stripMemoryBitmask(oper.xltype); 719 return realType == XlType.xltypeMulti; 720 } 721 722 723 T fromXlOper(T, A)(LPXLOPER12 oper, ref A allocator) if(is(T == Any[])) { 724 return oper.fromXlOperMulti!(Dimensions.One, Any)(allocator); 725 } 726 727 728 @("fromXlOper any 1D array") 729 @system unittest { 730 import xlld.memorymanager: allocatorContext; 731 with(allocatorContext(theMallocator)) { 732 auto array = [any(1.0), any("foo")]; 733 auto oper = toXlOper(array); 734 auto back = fromXlOper!(Any[])(oper); 735 back.shouldEqual(array); 736 } 737 } 738 739 740 T fromXlOper(T, A)(LPXLOPER12 oper, ref A allocator) if(is(T == Any[][])) { 741 return oper.fromXlOperMulti!(Dimensions.Two, typeof(T.init[0][0]))(allocator); 742 } 743 744 745 @("fromXlOper Any 2D array") 746 @system unittest { 747 import xlld.memorymanager: allocatorContext; 748 with(allocatorContext(theMallocator)) { 749 auto array = [[any(1.0), any(2.0)], [any("foo"), any("bar")]]; 750 auto oper = toXlOper(array); 751 auto back = fromXlOper!(Any[][])(oper); 752 back.shouldEqual(array); 753 } 754 } 755 756 757 private enum isWorksheetFunction(alias F) = 758 isSupportedFunction!(F, double, double[][], string[][], string[], double[], string, Any, Any[], Any[][], int); 759 760 @safe pure unittest { 761 import xlld.test_d_funcs; 762 // the line below checks that the code still compiles even with a private function 763 // it might stop compiling in a future version when the deprecation rules for 764 // visibility kick in 765 static assert(!isWorksheetFunction!shouldNotBeAProblem); 766 static assert(!isWorksheetFunction!FuncThrows); 767 static assert(isWorksheetFunction!DoubleArrayToAnyArray); 768 static assert(isWorksheetFunction!Twice); 769 } 770 771 /** 772 A string to mixin that wraps all eligible functions in the 773 given module. 774 */ 775 string wrapModuleWorksheetFunctionsString(string moduleName)(string callingModule = __MODULE__) { 776 if(!__ctfe) { 777 return ""; 778 } 779 780 import xlld.traits: Identity; 781 import std.array: join; 782 import std.traits: ReturnType, Parameters; 783 784 mixin(`import ` ~ moduleName ~ `;`); 785 alias module_ = Identity!(mixin(moduleName)); 786 787 string ret = `static import ` ~ moduleName ~ ";\n\n"; 788 789 foreach(moduleMemberStr; __traits(allMembers, module_)) { 790 alias moduleMember = Identity!(__traits(getMember, module_, moduleMemberStr)); 791 792 static if(isWorksheetFunction!moduleMember) { 793 ret ~= wrapModuleFunctionStr!(moduleName, moduleMemberStr)(callingModule); 794 } 795 } 796 797 return ret; 798 } 799 800 801 @("Wrap double[][] -> double") 802 @system unittest { 803 import xlld.memorymanager: allocator; 804 805 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 806 807 auto arg = toSRef(cast(double[][])[[1, 2, 3, 4], [11, 12, 13, 14]], allocator); 808 FuncAddEverything(&arg).shouldEqualDlang(60.0); 809 810 arg = toSRef(cast(double[][])[[0, 1, 2, 3], [4, 5, 6, 7]], allocator); 811 FuncAddEverything(&arg).shouldEqualDlang(28.0); 812 } 813 814 @("Wrap double[][] -> double[][]") 815 @system unittest { 816 import xlld.memorymanager: allocator; 817 818 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 819 820 auto arg = toSRef(cast(double[][])[[1, 2, 3, 4], [11, 12, 13, 14]], allocator); 821 FuncTripleEverything(&arg).shouldEqualDlang(cast(double[][])[[3, 6, 9, 12], [33, 36, 39, 42]]); 822 823 arg = toSRef(cast(double[][])[[0, 1, 2, 3], [4, 5, 6, 7]], allocator); 824 FuncTripleEverything(&arg).shouldEqualDlang(cast(double[][])[[0, 3, 6, 9], [12, 15, 18, 21]]); 825 } 826 827 828 @("Wrap string[][] -> double") 829 @system unittest { 830 831 import xlld.memorymanager: allocator; 832 833 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 834 835 auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]], allocator); 836 FuncAllLengths(&arg).shouldEqualDlang(29.0); 837 838 arg = toSRef([["", "", "", ""], ["", "", "", ""]], allocator); 839 FuncAllLengths(&arg).shouldEqualDlang(0.0); 840 } 841 842 @("Wrap string[][] -> double[][]") 843 @system unittest { 844 845 import xlld.memorymanager: allocator; 846 847 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 848 849 auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]], allocator); 850 FuncLengths(&arg).shouldEqualDlang(cast(double[][])[[3, 3, 3, 4], [4, 4, 4, 4]]); 851 852 arg = toSRef([["", "", ""], ["", "", "huh"]], allocator); 853 FuncLengths(&arg).shouldEqualDlang(cast(double[][])[[0, 0, 0], [0, 0, 3]]); 854 } 855 856 @("Wrap string[][] -> string[][]") 857 @system unittest { 858 859 import xlld.memorymanager: allocator; 860 861 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 862 863 auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]], allocator); 864 FuncBob(&arg).shouldEqualDlang([["foobob", "barbob", "bazbob", "quuxbob"], 865 ["totobob", "titibob", "tutubob", "tetebob"]]); 866 } 867 868 @("Wrap string[] -> double") 869 @system unittest { 870 import xlld.memorymanager: allocator; 871 872 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 873 auto arg = toSRef([["foo", "bar"], ["baz", "quux"]], allocator); 874 FuncStringSlice(&arg).shouldEqualDlang(4.0); 875 } 876 877 @("Wrap double[] -> double") 878 @system unittest { 879 import xlld.memorymanager: allocator; 880 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 881 auto arg = toSRef([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], allocator); 882 FuncDoubleSlice(&arg).shouldEqualDlang(6.0); 883 } 884 885 @("Wrap double[] -> double[]") 886 @system unittest { 887 import xlld.memorymanager: allocator; 888 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 889 auto arg = toSRef([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], allocator); 890 FuncSliceTimes3(&arg).shouldEqualDlang([3.0, 6.0, 9.0, 12.0, 15.0, 18.0]); 891 } 892 893 @("Wrap string[] -> string[]") 894 @system unittest { 895 import xlld.memorymanager: allocator; 896 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 897 auto arg = toSRef(["quux", "toto"], allocator); 898 StringsToStrings(&arg).shouldEqualDlang(["quuxfoo", "totofoo"]); 899 } 900 901 @("Wrap string[] -> string") 902 @system unittest { 903 import xlld.memorymanager: allocator; 904 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 905 auto arg = toSRef(["quux", "toto"], allocator); 906 StringsToString(&arg).shouldEqualDlang("quux, toto"); 907 } 908 909 @("Wrap string -> string") 910 @system unittest { 911 import xlld.memorymanager: allocator; 912 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 913 auto arg = toXlOper("foo", allocator); 914 StringToString(&arg).shouldEqualDlang("foobar"); 915 } 916 917 @("Wrap string, string, string -> string") 918 @system unittest { 919 import xlld.memorymanager: allocator; 920 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 921 auto arg0 = toXlOper("foo", allocator); 922 auto arg1 = toXlOper("bar", allocator); 923 auto arg2 = toXlOper("baz", allocator); 924 ManyToString(&arg0, &arg1, &arg2).shouldEqualDlang("foobarbaz"); 925 } 926 927 @("Only look at nothrow functions") 928 @system unittest { 929 import xlld.memorymanager: allocator; 930 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 931 auto arg = toXlOper(2.0, allocator); 932 static assert(!__traits(compiles, FuncThrows(&arg))); 933 } 934 935 @("FuncAddEverything wrapper is @nogc") 936 @system @nogc unittest { 937 import std.experimental.allocator.mallocator: Mallocator; 938 import xlld.framework: freeXLOper; 939 940 mixin(wrapModuleWorksheetFunctionsString!"xlld.test_d_funcs"); 941 auto arg = toXlOper(2.0, Mallocator.instance); 942 scope(exit) freeXLOper(&arg, Mallocator.instance); 943 FuncAddEverything(&arg); 944 } 945 946 private enum invalidXlOperType = 0xdeadbeef; 947 948 /** 949 Maps a D type to two integer xltypes from XLOPER12. 950 InputType is the type actually passed in by the spreadsheet, 951 whilst Type is the Type that it gets coerced to. 952 */ 953 template dlangToXlOperType(T) { 954 static if(is(T == double[][]) || is(T == string[][]) || is(T == double[]) || is(T == string[])) { 955 enum InputType = XlType.xltypeSRef; 956 enum Type = XlType.xltypeMulti; 957 } else static if(is(T == double)) { 958 enum InputType = XlType.xltypeNum; 959 enum Type = XlType.xltypeNum; 960 } else static if(is(T == string)) { 961 enum InputType = XlType.xltypeStr; 962 enum Type = XlType.xltypeStr; 963 } else { 964 enum InputType = invalidXlOperType; 965 enum Type = invalidXlOperType; 966 } 967 } 968 969 /** 970 A string to use with `mixin` that wraps a D function 971 */ 972 string wrapModuleFunctionStr(string moduleName, string funcName)(in string callingModule = __MODULE__) { 973 974 if(!__ctfe) { 975 return ""; 976 } 977 978 assert(callingModule != moduleName, 979 "Cannot use `wrapAll` with __MODULE__"); 980 981 import std.array: join; 982 import std.traits: Parameters, functionAttributes, FunctionAttribute, getUDAs; 983 import std.conv: to; 984 import std.algorithm: map; 985 import std.range: iota; 986 import std.format: format; 987 988 mixin("import " ~ moduleName ~ ": " ~ funcName ~ ";"); 989 990 const argsLength = Parameters!(mixin(funcName)).length; 991 // e.g. LPXLOPER12 arg0, LPXLOPER12 arg1, ... 992 const argsDecl = argsLength.iota.map!(a => `LPXLOPER12 arg` ~ a.to!string).join(", "); 993 // e.g. arg0, arg1, ... 994 const argsCall = argsLength.iota.map!(a => `arg` ~ a.to!string).join(", "); 995 const nogc = functionAttributes!(mixin(funcName)) & FunctionAttribute.nogc 996 ? "@nogc " 997 : ""; 998 const safe = functionAttributes!(mixin(funcName)) & FunctionAttribute.safe 999 ? "@trusted " 1000 : ""; 1001 1002 alias registerAttrs = getUDAs!(mixin(funcName), Register); 1003 static assert(registerAttrs.length == 0 || registerAttrs.length == 1, 1004 "Invalid number of @Register on " ~ funcName); 1005 1006 string register; 1007 static if(registerAttrs.length) 1008 register = `@` ~ registerAttrs[0].to!string; 1009 1010 return [ 1011 register, 1012 q{ 1013 extern(Windows) LPXLOPER12 %s(%s) nothrow %s %s { 1014 static import %s; 1015 import xlld.memorymanager: gTempAllocator; 1016 alias wrappedFunc = %s.%s; 1017 return wrapModuleFunctionImpl!wrappedFunc(gTempAllocator, %s); 1018 } 1019 }.format(funcName, argsDecl, nogc, safe, moduleName, moduleName, funcName, argsCall), 1020 ].join("\n"); 1021 } 1022 1023 @system unittest { 1024 import xlld.worksheet; 1025 import std.traits: getUDAs; 1026 1027 mixin(wrapModuleFunctionStr!("xlld.test_d_funcs", "FuncAddEverything")); 1028 alias registerAttrs = getUDAs!(FuncAddEverything, Register); 1029 static assert(registerAttrs[0].argumentText.value == "Array to add"); 1030 } 1031 1032 /** 1033 Implement a wrapper for a regular D function 1034 */ 1035 LPXLOPER12 wrapModuleFunctionImpl(alias wrappedFunc, A, T...) 1036 (ref A tempAllocator, auto ref T args) { 1037 import xlld.xl: coerce, free; 1038 import xlld.worksheet: Dispose; 1039 import std.traits: Parameters; 1040 import std.typecons: Tuple; 1041 import std.traits: hasUDA, getUDAs; 1042 1043 static XLOPER12 ret; 1044 1045 XLOPER12[T.length] realArgs; 1046 1047 // must 1st convert each argument to the "real" type. 1048 // 2D arrays are passed in as SRefs, for instance 1049 foreach(i, InputType; Parameters!wrappedFunc) { 1050 if(args[i].xltype == xltypeMissing) { 1051 realArgs[i] = *args[i]; 1052 continue; 1053 } 1054 realArgs[i] = coerce(args[i]); 1055 } 1056 1057 // scopedCoerce doesn't work with actual Excel 1058 scope(exit) { 1059 foreach(ref arg; realArgs) 1060 free(&arg); 1061 } 1062 1063 Tuple!(Parameters!wrappedFunc) dArgs; // the D types to pass to the wrapped function 1064 1065 void setRetToError(in string msg) { 1066 try 1067 ret = msg.toAutoFreeOper; 1068 catch(Exception _) { 1069 ret.xltype = XlType.xltypeErr; 1070 } 1071 } 1072 1073 void freeAll() { 1074 static if(__traits(compiles, tempAllocator.deallocateAll)) 1075 tempAllocator.deallocateAll; 1076 else { 1077 foreach(ref dArg; dArgs) { 1078 import std.traits: isPointer, isArray; 1079 static if(isArray!(typeof(dArg))) 1080 { 1081 import std.experimental.allocator: disposeMultidimensionalArray; 1082 tempAllocator.disposeMultidimensionalArray(dArg[]); 1083 } 1084 else 1085 static if(isPointer!(typeof(dArg))) 1086 { 1087 import std.experimental.allocator: dispose; 1088 tempAllocator.dispose(dArg); 1089 } 1090 } 1091 } 1092 } 1093 1094 // get rid of the temporary memory allocations for the conversions 1095 scope(exit) freeAll; 1096 1097 // convert all Excel types to D types 1098 foreach(i, InputType; Parameters!wrappedFunc) { 1099 try { 1100 dArgs[i] = () @trusted { return fromXlOper!InputType(&realArgs[i], tempAllocator); }(); 1101 } catch(Exception ex) { 1102 setRetToError("#ERROR converting argument to call " ~ __traits(identifier, wrappedFunc)); 1103 return &ret; 1104 } catch(Throwable t) { 1105 setRetToError("#FATAL ERROR converting argument to call " ~ __traits(identifier, wrappedFunc)); 1106 return &ret; 1107 } 1108 } 1109 1110 try { 1111 1112 // call the wrapped function with D types 1113 auto wrappedRet = wrappedFunc(dArgs.expand); 1114 ret = excelRet(wrappedRet); 1115 1116 // dispose of the memory allocated in the wrapped function 1117 static if(hasUDA!(wrappedFunc, Dispose)) { 1118 alias disposes = getUDAs!(wrappedFunc, Dispose); 1119 static assert(disposes.length == 1, "Too many @Dispose for " ~ wrappedFunc.stringof); 1120 disposes[0].dispose(wrappedRet); 1121 } 1122 1123 } catch(Exception ex) { 1124 1125 version(unittest) { 1126 import core.stdc.stdio: printf; 1127 static char[1024] buffer; 1128 buffer[0 .. ex.msg.length] = ex.msg[]; 1129 buffer[ex.msg.length + 1] = 0; 1130 () @trusted { printf("Could not call wrapped function: %s\n", &buffer[0]); }(); 1131 } 1132 1133 setRetToError("#ERROR calling " ~ __traits(identifier, wrappedFunc)); 1134 return &ret; 1135 } catch(Throwable t) { 1136 setRetToError("#FATAL ERROR calling " ~ __traits(identifier, wrappedFunc)); 1137 return &ret; 1138 } 1139 1140 return &ret; 1141 } 1142 1143 // get excel return value from D return value of wrapped function 1144 private XLOPER12 excelRet(T)(T wrappedRet) { 1145 1146 import std.traits: isArray; 1147 1148 // Excel crashes if it's returned an empty array, so stop that from happening 1149 static if(isArray!(typeof(wrappedRet))) { 1150 if(wrappedRet.length == 0) { 1151 return "#ERROR: empty result".toAutoFreeOper; 1152 } 1153 1154 static if(isArray!(typeof(wrappedRet[0]))) { 1155 if(wrappedRet[0].length == 0) { 1156 return "#ERROR: empty result".toAutoFreeOper; 1157 } 1158 } 1159 } 1160 1161 // convert the return value to an Excel type, tell Excel to call 1162 // us back to free it afterwards 1163 return toAutoFreeOper(wrappedRet); 1164 } 1165 1166 @("No memory allocation bugs in wrapModuleFunctionImpl for double return Mallocator") 1167 @system unittest { 1168 import std.experimental.allocator.mallocator: Mallocator; 1169 import xlld.test_d_funcs: FuncAddEverything; 1170 1171 TestAllocator allocator; 1172 auto arg = toSRef([1.0, 2.0], Mallocator.instance); 1173 auto oper = wrapModuleFunctionImpl!FuncAddEverything(allocator, &arg); 1174 (oper.xltype & xlbitDLLFree).shouldBeTrue; 1175 allocator.numAllocations.shouldEqual(2); 1176 oper.shouldEqualDlang(3.0); 1177 autoFree(oper); // normally this is done by Excel 1178 } 1179 1180 @("No memory allocation bugs in wrapModuleFunctionImpl for double[][] return Mallocator") 1181 @system unittest { 1182 import std.experimental.allocator.mallocator: Mallocator; 1183 import xlld.test_d_funcs: FuncTripleEverything; 1184 1185 TestAllocator allocator; 1186 auto arg = toSRef([1.0, 2.0, 3.0], Mallocator.instance); 1187 auto oper = wrapModuleFunctionImpl!FuncTripleEverything(allocator, &arg); 1188 (oper.xltype & xlbitDLLFree).shouldBeTrue; 1189 (oper.xltype & ~xlbitDLLFree).shouldEqual(xltypeMulti); 1190 allocator.numAllocations.shouldEqual(2); 1191 oper.shouldEqualDlang([[3.0, 6.0, 9.0]]); 1192 autoFree(oper); // normally this is done by Excel 1193 } 1194 1195 @("No memory allocation bugs in wrapModuleFunctionImpl for double[][] return pool") 1196 @system unittest { 1197 import std.typecons: Ternary; 1198 import xlld.memorymanager: gTempAllocator; 1199 import xlld.test_d_funcs: FuncTripleEverything; 1200 1201 auto arg = toSRef([1.0, 2.0, 3.0], gTempAllocator); 1202 auto oper = wrapModuleFunctionImpl!FuncTripleEverything(gTempAllocator, &arg); 1203 gTempAllocator.empty.shouldEqual(Ternary.yes); 1204 oper.shouldEqualDlang([[3.0, 6.0, 9.0]]); 1205 autoFree(oper); // normally this is done by Excel 1206 } 1207 1208 @("No memory allocation bugs in wrapModuleFunctionImpl for string") 1209 @system unittest { 1210 import std.typecons: Ternary; 1211 import xlld.memorymanager: gTempAllocator; 1212 import xlld.test_d_funcs: StringToString; 1213 1214 auto arg = "foo".toSRef(gTempAllocator); 1215 auto oper = wrapModuleFunctionImpl!StringToString(gTempAllocator, &arg); 1216 gTempAllocator.empty.shouldEqual(Ternary.yes); 1217 oper.shouldEqualDlang("foobar"); 1218 } 1219 1220 @("No memory allocation bugs in wrapModuleFunctionImpl for Any[][] -> Any[][] -> Any[][] mallocator") 1221 @system unittest { 1222 import xlld.memorymanager: allocatorContext; 1223 import xlld.test_d_funcs: FirstOfTwoAnyArrays; 1224 1225 with(allocatorContext(theGC)) { 1226 auto dArg = [[any(1.0), any("foo"), any(3.0)], [any(4.0), any(5.0), any(6.0)]]; 1227 auto arg = toXlOper(dArg); 1228 auto oper = wrapModuleFunctionImpl!FirstOfTwoAnyArrays(theMallocator, &arg, &arg); 1229 oper.shouldEqualDlang(dArg); 1230 } 1231 } 1232 1233 @("No memory allocation bugs in wrapModuleFunctionImpl for Any[][] -> Any[][] -> Any[][] TestAllocator") 1234 @system unittest { 1235 import xlld.memorymanager: allocatorContext; 1236 import xlld.test_d_funcs: FirstOfTwoAnyArrays; 1237 1238 auto testAllocator = TestAllocator(); 1239 1240 with(allocatorContext(theGC)) { 1241 auto dArg = [[any(1.0), any("foo"), any(3.0)], [any(4.0), any(5.0), any(6.0)]]; 1242 auto arg = toXlOper(dArg); 1243 auto oper = wrapModuleFunctionImpl!FirstOfTwoAnyArrays(testAllocator, &arg, &arg); 1244 oper.shouldEqualDlang(dArg); 1245 } 1246 } 1247 1248 @("Correct number of coercions and frees in wrapModuleFunctionImpl") 1249 @system unittest { 1250 import xlld.test_d_funcs: FuncAddEverything; 1251 import xlld.test_util: gNumXlCoerce, gNumXlFree; 1252 1253 const oldNumCoerce = gNumXlCoerce; 1254 const oldNumFree = gNumXlFree; 1255 1256 auto arg = toSRef([1.0, 2.0], theGC); 1257 auto oper = wrapModuleFunctionImpl!FuncAddEverything(theGC, &arg); 1258 1259 (gNumXlCoerce - oldNumCoerce).shouldEqual(1); 1260 (gNumXlFree - oldNumFree).shouldEqual(1); 1261 } 1262 1263 1264 @("Can't return empty 1D array to Excel") 1265 @system unittest { 1266 import xlld.memorymanager: allocatorContext; 1267 import xlld.test_d_funcs: EmptyStrings1D; 1268 1269 with(allocatorContext(theGC)) { 1270 auto dArg = any(1.0); 1271 auto arg = toXlOper(dArg); 1272 auto oper = wrapModuleFunctionImpl!EmptyStrings1D(theGC, &arg); 1273 oper.shouldEqualDlang("#ERROR: empty result"); 1274 } 1275 } 1276 1277 1278 @("Can't return empty 2D array to Excel") 1279 @system unittest { 1280 import xlld.memorymanager: allocatorContext; 1281 import xlld.test_d_funcs: EmptyStrings2D; 1282 1283 with(allocatorContext(theGC)) { 1284 auto dArg = any(1.0); 1285 auto arg = toXlOper(dArg); 1286 auto oper = wrapModuleFunctionImpl!EmptyStrings2D(theGC, &arg); 1287 oper.shouldEqualDlang("#ERROR: empty result"); 1288 } 1289 } 1290 1291 @("Can't return half empty 2D array to Excel") 1292 @system unittest { 1293 import xlld.memorymanager: allocatorContext; 1294 import xlld.test_d_funcs: EmptyStringsHalfEmpty2D; 1295 1296 with(allocatorContext(theGC)) { 1297 auto dArg = any(1.0); 1298 auto arg = toXlOper(dArg); 1299 auto oper = wrapModuleFunctionImpl!EmptyStringsHalfEmpty2D(theGC, &arg); 1300 oper.shouldEqualDlang("#ERROR: empty result"); 1301 } 1302 } 1303 1304 @("issue 25 - make sure to reserve memory for all dArgs") 1305 @system unittest { 1306 import std.typecons: Ternary; 1307 import xlld.memorymanager: allocatorContext, MemoryPool; 1308 import xlld.test_d_funcs: FirstOfTwoAnyArrays; 1309 1310 auto pool = MemoryPool(); 1311 1312 with(allocatorContext(theGC)) { 1313 auto dArg = [[any(1.0), any("foo"), any(3.0)], [any(4.0), any(5.0), any(6.0)]]; 1314 auto arg = toSRef(dArg); 1315 auto oper = wrapModuleFunctionImpl!FirstOfTwoAnyArrays(pool, &arg, &arg); 1316 } 1317 1318 pool.empty.shouldEqual(Ternary.yes); // deallocateAll in wrapImpl 1319 } 1320 1321 string wrapWorksheetFunctionsString(Modules...)(string callingModule = __MODULE__) { 1322 1323 if(!__ctfe) { 1324 return ""; 1325 } 1326 1327 string ret; 1328 foreach(module_; Modules) { 1329 ret ~= wrapModuleWorksheetFunctionsString!module_(callingModule); 1330 } 1331 1332 return ret; 1333 } 1334 1335 1336 string wrapAll(Modules...)(in string mainModule = __MODULE__) { 1337 1338 if(!__ctfe) { 1339 return ""; 1340 } 1341 1342 import xlld.traits: implGetWorksheetFunctionsString; 1343 return 1344 wrapWorksheetFunctionsString!Modules(mainModule) ~ 1345 "\n" ~ 1346 implGetWorksheetFunctionsString!(mainModule) ~ 1347 "\n" ~ 1348 `mixin GenerateDllDef!"` ~ mainModule ~ `";` ~ 1349 "\n"; 1350 } 1351 1352 @("wrapAll") 1353 unittest { 1354 import xlld.memorymanager: allocator; 1355 import xlld.traits: getAllWorksheetFunctions, GenerateDllDef; // for wrapAll 1356 1357 mixin(wrapAll!("xlld.test_d_funcs")); 1358 auto arg = toSRef(cast(double[][])[[1, 2, 3, 4], [11, 12, 13, 14]], allocator); 1359 FuncAddEverything(&arg).shouldEqualDlang(60.0); 1360 } 1361 1362 1363 /** 1364 creates an XLOPER12 that can be returned to Excel which 1365 will be freed by Excel itself 1366 */ 1367 XLOPER12 toAutoFreeOper(T)(T value) { 1368 import xlld.memorymanager: autoFreeAllocator; 1369 import xlld.xlcall: XlType; 1370 1371 auto result = value.toXlOper(autoFreeAllocator); 1372 result.xltype |= XlType.xlbitDLLFree; 1373 return result; 1374 } 1375 1376 ushort operStringLength(T)(in T value) { 1377 import nogc.exception: enforce; 1378 1379 enforce(value.xltype == XlType.xltypeStr, 1380 "Cannot calculate string length for oper of type ", value.xltype); 1381 1382 return cast(ushort)value.val.str[0]; 1383 } 1384 1385 @("operStringLength") 1386 unittest { 1387 import std.experimental.allocator.mallocator: Mallocator; 1388 auto oper = "foobar".toXlOper(Mallocator.instance); 1389 const length = () @nogc { return operStringLength(oper); }(); 1390 length.shouldEqual(6); 1391 } 1392 1393 auto fromXlOperCoerce(T)(LPXLOPER12 val) { 1394 return fromXlOperCoerce(*val); 1395 } 1396 1397 auto fromXlOperCoerce(T, A)(LPXLOPER12 val, auto ref A allocator) { 1398 return fromXlOperCoerce!T(*val, allocator); 1399 } 1400 1401 1402 auto fromXlOperCoerce(T)(ref XLOPER12 val) { 1403 import xlld.memorymanager: allocator; 1404 return fromXlOperCoerce!T(val, allocator); 1405 } 1406 1407 1408 auto fromXlOperCoerce(T, A)(ref XLOPER12 val, auto ref A allocator) { 1409 import xlld.xl: coerce, free; 1410 1411 auto coerced = coerce(&val); 1412 scope(exit) free(&coerced); 1413 1414 return coerced.fromXlOper!T(allocator); 1415 } 1416 1417 1418 @("fromXlOperCoerce") 1419 unittest { 1420 double[][] doubles = [[1, 2, 3, 4], [11, 12, 13, 14]]; 1421 auto doublesOper = toSRef(doubles, theGC); 1422 doublesOper.fromXlOper!(double[][])(theGC).shouldThrowWithMessage( 1423 "oper not of multi type"); 1424 doublesOper.fromXlOperCoerce!(double[][]).shouldEqual(doubles); 1425 } 1426 1427 @("wrap function with @Dispose") 1428 @safe unittest { 1429 import xlld.test_util: gTestAllocator; 1430 import xlld.memorymanager: gTempAllocator; 1431 import xlld.traits: getAllWorksheetFunctions, GenerateDllDef; // for wrapAll 1432 1433 // this is needed since gTestAllocator is global, so we can't rely 1434 // on its destructor 1435 scope(exit) gTestAllocator.verify; 1436 1437 mixin(wrapAll!("xlld.test_d_funcs")); 1438 double[4] args = [1.0, 2.0, 3.0, 4.0]; 1439 auto oper = args[].toSRef(gTempAllocator); // don't use TestAllocator 1440 auto arg = () @trusted { return &oper; }(); 1441 auto ret = () @safe @nogc { return FuncReturnArrayNoGc(arg); }(); 1442 ret.shouldEqualDlang([2.0, 4.0, 6.0, 8.0]); 1443 } 1444 1445 @("wrapModuleFunctionStr function that returns Any[][]") 1446 @safe unittest { 1447 mixin(wrapModuleFunctionStr!("xlld.test_d_funcs", "DoubleArrayToAnyArray")); 1448 1449 auto oper = [[1.0, 2.0], [3.0, 4.0]].toSRef(theMallocator); 1450 auto arg = () @trusted { return &oper; }(); 1451 auto ret = DoubleArrayToAnyArray(arg); 1452 1453 auto opers = () @trusted { return ret.val.array.lparray[0 .. 4]; }(); 1454 opers[0].shouldEqualDlang(2.0); 1455 opers[1].shouldEqualDlang(6.0); 1456 opers[2].shouldEqualDlang("3quux"); 1457 opers[3].shouldEqualDlang("4toto"); 1458 } 1459 1460 @("wrapModuleFunctionStr int -> int") 1461 @safe unittest { 1462 mixin(wrapModuleFunctionStr!("xlld.test_d_funcs", "Twice")); 1463 1464 auto oper = 3.toSRef(theGC); 1465 auto arg = () @trusted { return &oper; }(); 1466 Twice(arg).shouldEqualDlang(6); 1467 } 1468 1469 1470 @("wrapAll function that returns Any[][]") 1471 @safe unittest { 1472 import xlld.traits: getAllWorksheetFunctions, GenerateDllDef; // for wrapAll 1473 1474 mixin(wrapAll!("xlld.test_d_funcs")); 1475 1476 auto oper = [[1.0, 2.0], [3.0, 4.0]].toSRef(theMallocator); 1477 auto arg = () @trusted { return &oper; }(); 1478 auto ret = DoubleArrayToAnyArray(arg); 1479 scope(exit) () @trusted { autoFree(ret); }(); // usually done by Excel 1480 1481 auto opers = () @trusted { return ret.val.array.lparray[0 .. 4]; }(); 1482 opers[0].shouldEqualDlang(2.0); 1483 opers[1].shouldEqualDlang(6.0); 1484 opers[2].shouldEqualDlang("3quux"); 1485 opers[3].shouldEqualDlang("4toto"); 1486 } 1487 1488 @("wrapAll function that takes Any[][]") 1489 unittest { 1490 import xlld.traits: getAllWorksheetFunctions, GenerateDllDef; // for wrapAll 1491 import xlld.memorymanager: allocatorContext; 1492 1493 mixin(wrapAll!("xlld.test_d_funcs")); 1494 1495 LPXLOPER12 ret; 1496 with(allocatorContext(theMallocator)) { 1497 auto oper = [[any(1.0), any(2.0)], [any(3.0), any(4.0)], [any("foo"), any("bar")]].toXlOper(theMallocator); 1498 auto arg = () @trusted { return &oper; }(); 1499 ret = AnyArrayToDoubleArray(arg); 1500 } 1501 1502 auto opers = () @trusted { return ret.val.array.lparray[0 .. 2]; }(); 1503 opers[0].shouldEqualDlang(3.0); // number of rows 1504 opers[1].shouldEqualDlang(2.0); // number of columns 1505 } 1506 1507 1508 @("wrapAll Any[][] -> Any[][]") 1509 unittest { 1510 import xlld.traits: getAllWorksheetFunctions, GenerateDllDef; // for wrapAll 1511 import xlld.memorymanager: allocatorContext; 1512 import xlld.any: Any; 1513 1514 mixin(wrapAll!("xlld.test_d_funcs")); 1515 1516 LPXLOPER12 ret; 1517 with(allocatorContext(theMallocator)) { 1518 auto oper = [[any(1.0), any(2.0)], [any(3.0), any(4.0)], [any("foo"), any("bar")]].toXlOper(theMallocator); 1519 auto arg = () @trusted { return &oper; }(); 1520 ret = AnyArrayToAnyArray(arg); 1521 } 1522 1523 auto opers = () @trusted { return ret.val.array.lparray[0 .. 6]; }(); 1524 ret.val.array.rows.shouldEqual(3); 1525 ret.val.array.columns.shouldEqual(2); 1526 opers[0].shouldEqualDlang(1.0); 1527 opers[1].shouldEqualDlang(2.0); 1528 opers[2].shouldEqualDlang(3.0); 1529 opers[3].shouldEqualDlang(4.0); 1530 opers[4].shouldEqualDlang("foo"); 1531 opers[5].shouldEqualDlang("bar"); 1532 } 1533 1534 @("wrapAll Any[][] -> Any[][] -> Any[][]") 1535 unittest { 1536 import xlld.traits: getAllWorksheetFunctions, GenerateDllDef; // for wrapAll 1537 import xlld.memorymanager: allocatorContext; 1538 import xlld.any: Any; 1539 1540 mixin(wrapAll!("xlld.test_d_funcs")); 1541 1542 LPXLOPER12 ret; 1543 with(allocatorContext(theMallocator)) { 1544 auto oper = [[any(1.0), any("foo"), any(3.0)], [any(4.0), any(5.0), any(6.0)]].toXlOper(theMallocator); 1545 auto arg = () @trusted { return &oper; }(); 1546 ret = FirstOfTwoAnyArrays(arg, arg); 1547 } 1548 1549 auto opers = () @trusted { return ret.val.array.lparray[0 .. 6]; }(); 1550 ret.val.array.rows.shouldEqual(2); 1551 ret.val.array.columns.shouldEqual(3); 1552 opers[0].shouldEqualDlang(1.0); 1553 opers[1].shouldEqualDlang("foo"); 1554 opers[2].shouldEqualDlang(3.0); 1555 opers[3].shouldEqualDlang(4.0); 1556 opers[4].shouldEqualDlang(5.0); 1557 opers[5].shouldEqualDlang(6.0); 1558 }