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