1 /** 2 This module implements the compile-time reflection machinery to 3 automatically register all D functions that are eligible in a 4 compile-time define list of modules to be called from Excel. 5 6 Import this module from any module from your XLL build and: 7 8 ----------- 9 import xlld; 10 11 mixin(implGetWorksheetFunctionsString!("module1", "module2", "module3")); 12 ----------- 13 14 All eligible functions in the 3 example modules above will automagically 15 be accessible from Excel (assuming the built XLL is loaded as an add-in). 16 */ 17 module xlld.wrap.traits; 18 19 import xlld.wrap.worksheet; 20 import xlld.sdk.xlcall; 21 import std.traits: isSomeFunction, isSomeString; 22 import std.meta: allSatisfy; 23 import std.typecons: Flag, No; 24 25 /// import unit_threaded and introduce helper functions for testing 26 version(testingExcelD) { 27 import unit_threaded; 28 } 29 30 /** 31 Take a D function as a compile-time parameter and returns a 32 WorksheetFunction struct with the fields filled in accordingly. 33 */ 34 WorksheetFunction getWorksheetFunction(alias F)() if(isSomeFunction!F) { 35 import xlld.wrap.wrap: pascalCase; 36 import std.traits: ReturnType, Parameters, getUDAs; 37 import std.conv: text, to; 38 39 alias R = ReturnType!F; 40 alias T = Parameters!F; 41 42 static if(!isWorksheetFunction!F) { 43 throw new Exception("Unsupported function type " ~ R.stringof ~ T.stringof ~ " for " ~ 44 __traits(identifier, F).stringof[1 .. $-1]); 45 } else { 46 47 WorksheetFunction ret; 48 auto name = __traits(identifier, F).pascalCase.to!wstring; 49 ret.procedure = Procedure(name); 50 ret.functionText = FunctionText(name); 51 ret.typeText = TypeText(getTypeText!F); 52 53 // check to see if decorated with @Register 54 alias registerAttrs = getUDAs!(F, Register); 55 static if(registerAttrs.length > 0) { 56 static assert(registerAttrs.length == 1, 57 text("Only 1 @Register allowed, found ", registerAttrs.length, 58 " on function ", __traits(identifier, F))); 59 ret.optional = registerAttrs[0]; 60 } 61 62 return ret; 63 } 64 } 65 66 67 wstring getTypeText(alias F)() if(isSomeFunction!F) { 68 import std.traits: ReturnType, Parameters, Unqual, hasUDA; 69 70 wstring typeToString(T)() { 71 72 alias Type = Unqual!T; 73 74 static if(is(Type == double)) 75 return "B"; 76 else static if(is(Type == FP12*)) 77 return "K%"; 78 else static if(is(Type == LPXLOPER12)) 79 return "U"; 80 else static if(is(Type == void)) 81 return ">"; 82 else 83 static assert(false, "Unsupported type " ~ T.stringof); 84 } 85 86 auto retType = typeToString!(ReturnType!F); 87 foreach(i, argType; Parameters!F) { 88 static if(i == Parameters!F.length - 1 && hasUDA!(F, Async)) 89 retType ~= "X"; 90 else 91 retType ~= typeToString!(argType); 92 93 } 94 95 return retType; 96 } 97 98 99 100 // helper template for aliasing 101 alias Identity(alias T) = T; 102 103 104 /** 105 Is true if F is a callable function and functionTypePredicate is true 106 for the return type and all parameter types of F. 107 */ 108 template isSupportedFunction(alias F, alias functionTypePredicate) { 109 import std.traits: ReturnType, Parameters; 110 import std.meta: allSatisfy; 111 112 static if(isCallableFunction!F) { 113 enum returnTypeOk = functionTypePredicate!(ReturnType!F) || is(ReturnType!F == void); 114 enum paramTypesOk = allSatisfy!(functionTypePredicate, Parameters!F); 115 enum isSupportedFunction = returnTypeOk && paramTypesOk; 116 } else 117 enum isSupportedFunction = false; 118 } 119 120 121 template isCallableFunction(alias F) { 122 import std.traits: isSomeFunction, Parameters; 123 import std.typecons: Tuple; 124 125 /// trying to get a pointer to something is a good way of making sure we can 126 /// attempt to evaluate `isSomeFunction` - it's not always possible 127 enum canGetPointerToIt = __traits(compiles, &F); 128 129 static if(canGetPointerToIt) { 130 static if(isSomeFunction!F) 131 enum isCallableFunction = __traits(compiles, F(Tuple!(Parameters!F)().expand)); 132 else 133 enum isCallableFunction = false; 134 } else 135 enum isCallableFunction = false; 136 } 137 138 139 // if T is one of A 140 template isOneOf(T, A...) { 141 static if(A.length == 0) 142 enum isOneOf = false; 143 else 144 enum isOneOf = is(T == A[0]) || isOneOf!(T, A[1..$]); 145 } 146 147 @("isOneOf") 148 @safe pure unittest { 149 static assert(isOneOf!(int, int, int)); 150 static assert(!isOneOf!(int, double, string)); 151 } 152 153 // whether or not this is a function that can be called from Excel 154 template isWorksheetFunction(alias F) { 155 static if(isWorksheetFunctionModuloLinkage!F) { 156 import std.traits: functionLinkage; 157 enum isWorksheetFunction = functionLinkage!F == "Windows"; 158 } else 159 enum isWorksheetFunction = false; 160 } 161 162 /// if the types match for a worksheet function but without checking the linkage 163 template isWorksheetFunctionModuloLinkage(alias F) { 164 import std.traits: ReturnType, Parameters, isCallable; 165 import std.meta: anySatisfy; 166 167 static if(!isCallable!F) 168 enum isWorksheetFunctionModuloLinkage = false; 169 else { 170 171 enum isEnum(T) = is(T == enum); 172 enum isOneOfSupported(U) = isOneOf!(U, double, FP12*, LPXLOPER12); 173 174 enum isWorksheetFunctionModuloLinkage = 175 isSupportedFunction!(F, isOneOfSupported) && 176 !is(ReturnType!F == enum) && 177 !anySatisfy!(isEnum, Parameters!F); 178 } 179 } 180 181 182 /** 183 Gets all Excel-callable functions in a given module 184 */ 185 WorksheetFunction[] getModuleWorksheetFunctions(string moduleName) 186 (Flag!"onlyExports" onlyExports = No.onlyExports) 187 { 188 mixin(`import ` ~ moduleName ~ `;`); 189 alias module_ = Identity!(mixin(moduleName)); 190 191 WorksheetFunction[] ret; 192 193 foreach(moduleMemberStr; __traits(allMembers, module_)) { 194 195 alias moduleMember = Identity!(__traits(getMember, module_, moduleMemberStr)); 196 197 static if(isWorksheetFunction!moduleMember) { 198 try { 199 const shouldWrap = onlyExports ? __traits(getProtection, moduleMember) == "export" : true; 200 if(shouldWrap) 201 ret ~= getWorksheetFunction!(moduleMember); 202 } catch(Exception ex) 203 assert(0); //can't happen 204 } else static if(isWorksheetFunctionModuloLinkage!moduleMember) { 205 import std.traits: functionLinkage; 206 pragma(msg, "!!!!! excel-d warning: function " ~ __traits(identifier, moduleMember) ~ 207 " has the right types to be callable from Excel but isn't due to having " ~ 208 functionLinkage!moduleMember ~ " linkage instead of the required 'Windows'"); 209 } 210 } 211 212 return ret; 213 } 214 215 /** 216 Gets all Excel-callable functions from the given modules 217 */ 218 WorksheetFunction[] getAllWorksheetFunctions(Modules...) 219 (Flag!"onlyExports" onlyExports = No.onlyExports) 220 pure @safe if(allSatisfy!(isSomeString, typeof(Modules))) 221 { 222 WorksheetFunction[] ret; 223 224 foreach(module_; Modules) { 225 ret ~= getModuleWorksheetFunctions!module_(onlyExports); 226 } 227 228 return ret; 229 } 230 231 /** 232 Implements the getWorksheetFunctions function needed by xlld.sdk.xll in 233 order to register the Excel-callable functions at runtime 234 This used to be a template mixin but even using a string mixin inside 235 fails to actually make it an extern(C) function. 236 */ 237 string implGetWorksheetFunctionsString(Modules...)() if(allSatisfy!(isSomeString, typeof(Modules))) { 238 return implGetWorksheetFunctionsString(Modules); 239 } 240 241 242 string implGetWorksheetFunctionsString(string[] modules...) { 243 import std.array: join; 244 245 if(!__ctfe) { 246 return ""; 247 } 248 249 string modulesString() { 250 251 string[] ret; 252 foreach(module_; modules) { 253 ret ~= `"` ~ module_ ~ `"`; 254 } 255 return ret.join(", "); 256 } 257 258 return 259 [ 260 `extern(C) WorksheetFunction[] getWorksheetFunctions() @safe pure nothrow {`, 261 ` return getAllWorksheetFunctions!(` ~ modulesString ~ `);`, 262 `}`, 263 ].join("\n"); 264 } 265 266 267 /// 268 struct DllDefFile { 269 Statement[] statements; 270 } 271 272 /// 273 struct Statement { 274 string name; 275 string[] args; 276 277 this(string name, string[] args) @safe pure nothrow { 278 this.name = name; 279 this.args = args; 280 } 281 282 this(string name, string arg) @safe pure nothrow { 283 this(name, [arg]); 284 } 285 286 string toString() @safe pure const { 287 import std.array: join; 288 import std.algorithm: map; 289 290 if(name == "EXPORTS") 291 return name ~ "\n" ~ args.map!(a => "\t\t" ~ a).join("\n"); 292 else 293 return name ~ "\t\t" ~ args.map!(a => stringify(name, a)).join(" "); 294 } 295 296 static private string stringify(in string name, in string arg) @safe pure { 297 if(name == "LIBRARY") return `"` ~ arg ~ `"`; 298 if(name == "DESCRIPTION") return `'` ~ arg ~ `'`; 299 return arg; 300 } 301 } 302 303 /** 304 Returns a structure descripting a Windows .def file. 305 This allows the tests to not care about the specific formatting 306 used when writing the information out. 307 This encapsulates all the functions to be exported by the DLL/XLL. 308 */ 309 DllDefFile dllDefFile(Modules...) 310 (string libName, 311 string description, 312 Flag!"onlyExports" onlyExports = No.onlyExports) 313 if(allSatisfy!(isSomeString, typeof(Modules))) 314 { 315 import std.conv: to; 316 317 auto statements = [ 318 Statement("LIBRARY", libName), 319 ]; 320 321 string[] exports = ["xlAutoOpen", "xlAutoClose", "xlAutoFree12"]; 322 foreach(func; getAllWorksheetFunctions!Modules(onlyExports)) { 323 exports ~= func.procedure.to!string; 324 } 325 326 return DllDefFile(statements ~ Statement("EXPORTS", exports)); 327 } 328 329 330 /// 331 mixin template GenerateDllDef(string module_ = __MODULE__) { 332 version(exceldDef) { 333 void main(string[] args) nothrow { 334 import xlld.wrap.traits: generateDllDef; 335 try { 336 generateDllDef!module_(args); 337 } catch(Exception ex) { 338 import std.stdio: stderr; 339 try 340 stderr.writeln("Error: ", ex.msg); 341 catch(Exception ex2) 342 assert(0, "Program could not write exception message"); 343 } 344 } 345 } 346 } 347 348 /// 349 void generateDllDef(string module_ = __MODULE__, 350 Flag!"onlyExports" onlyExports = No.onlyExports) 351 (string[] args) 352 { 353 import std.stdio: File; 354 import std.exception: enforce; 355 import std.path: stripExtension; 356 357 enforce(args.length >= 2 && args.length <= 4, 358 "Usage: " ~ args[0] ~ " [file_name] <lib_name> <description>"); 359 360 immutable fileName = args[1]; 361 immutable libName = args.length > 2 362 ? args[2] 363 : fileName.stripExtension ~ ".xll"; 364 immutable description = args.length > 3 365 ? args[3] 366 : "Simple D add-in to Excel"; 367 368 auto file = File(fileName, "w"); 369 foreach(stmt; dllDefFile!module_(libName, description, onlyExports).statements) 370 file.writeln(stmt.toString); 371 } 372 373 /** 374 UDA for functions to be executed asynchronously 375 */ 376 enum Async;