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 }