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