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 @safe pure unittest { 148 static assert(isOneOf!(int, int, int)); 149 static assert(!isOneOf!(int, double, string)); 150 } 151 152 // whether or not this is a function that can be called from Excel 153 template isWorksheetFunction(alias F) { 154 static if(isWorksheetFunctionModuloLinkage!F) { 155 import std.traits: functionLinkage; 156 enum isWorksheetFunction = functionLinkage!F == "Windows"; 157 } else 158 enum isWorksheetFunction = false; 159 } 160 161 /// if the types match for a worksheet function but without checking the linkage 162 template isWorksheetFunctionModuloLinkage(alias F) { 163 import std.traits: ReturnType, Parameters, isCallable; 164 import std.meta: anySatisfy; 165 166 static if(!isCallable!F) 167 enum isWorksheetFunctionModuloLinkage = false; 168 else { 169 170 enum isEnum(T) = is(T == enum); 171 enum isOneOfSupported(U) = isOneOf!(U, double, FP12*, LPXLOPER12); 172 173 enum isWorksheetFunctionModuloLinkage = 174 isSupportedFunction!(F, isOneOfSupported) && 175 !is(ReturnType!F == enum) && 176 !anySatisfy!(isEnum, Parameters!F); 177 } 178 } 179 180 181 /** 182 Gets all Excel-callable functions in a given module 183 */ 184 WorksheetFunction[] getModuleWorksheetFunctions(string moduleName) 185 (Flag!"onlyExports" onlyExports = No.onlyExports) 186 { 187 mixin(`import ` ~ moduleName ~ `;`); 188 alias module_ = Identity!(mixin(moduleName)); 189 190 WorksheetFunction[] ret; 191 192 foreach(moduleMemberStr; __traits(allMembers, module_)) { 193 194 alias moduleMember = Identity!(__traits(getMember, module_, moduleMemberStr)); 195 196 static if(isWorksheetFunction!moduleMember) { 197 try { 198 const shouldWrap = onlyExports ? __traits(getProtection, moduleMember) == "export" : true; 199 if(shouldWrap) 200 ret ~= getWorksheetFunction!(moduleMember); 201 } catch(Exception ex) 202 assert(0); //can't happen 203 } else static if(isWorksheetFunctionModuloLinkage!moduleMember) { 204 import std.traits: functionLinkage; 205 pragma(msg, "!!!!! excel-d warning: function " ~ __traits(identifier, moduleMember) ~ 206 " has the right types to be callable from Excel but isn't due to having " ~ 207 functionLinkage!moduleMember ~ " linkage instead of the required 'Windows'"); 208 } 209 } 210 211 return ret; 212 } 213 214 /** 215 Gets all Excel-callable functions from the given modules 216 */ 217 WorksheetFunction[] getAllWorksheetFunctions(Modules...) 218 (Flag!"onlyExports" onlyExports = No.onlyExports) 219 pure @safe if(allSatisfy!(isSomeString, typeof(Modules))) 220 { 221 WorksheetFunction[] ret; 222 223 foreach(module_; Modules) { 224 ret ~= getModuleWorksheetFunctions!module_(onlyExports); 225 } 226 227 return ret; 228 } 229 230 /** 231 Implements the getWorksheetFunctions function needed by xlld.sdk.xll in 232 order to register the Excel-callable functions at runtime 233 This used to be a template mixin but even using a string mixin inside 234 fails to actually make it an extern(C) function. 235 */ 236 string implGetWorksheetFunctionsString(Modules...)() if(allSatisfy!(isSomeString, typeof(Modules))) { 237 return implGetWorksheetFunctionsString(Modules); 238 } 239 240 241 string implGetWorksheetFunctionsString(string[] modules...) { 242 import std.array: join; 243 244 if(!__ctfe) { 245 return ""; 246 } 247 248 string modulesString() { 249 250 string[] ret; 251 foreach(module_; modules) { 252 ret ~= `"` ~ module_ ~ `"`; 253 } 254 return ret.join(", "); 255 } 256 257 return 258 [ 259 `extern(C) WorksheetFunction[] getWorksheetFunctions() @safe pure nothrow {`, 260 ` return getAllWorksheetFunctions!(` ~ modulesString ~ `);`, 261 `}`, 262 ].join("\n"); 263 } 264 265 266 /// 267 struct DllDefFile { 268 Statement[] statements; 269 } 270 271 /// 272 struct Statement { 273 string name; 274 string[] args; 275 276 this(string name, string[] args) @safe pure nothrow { 277 this.name = name; 278 this.args = args; 279 } 280 281 this(string name, string arg) @safe pure nothrow { 282 this(name, [arg]); 283 } 284 285 string toString() @safe pure const { 286 import std.array: join; 287 import std.algorithm: map; 288 289 if(name == "EXPORTS") 290 return name ~ "\n" ~ args.map!(a => "\t\t" ~ a).join("\n"); 291 else 292 return name ~ "\t\t" ~ args.map!(a => stringify(name, a)).join(" "); 293 } 294 295 static private string stringify(in string name, in string arg) @safe pure { 296 if(name == "LIBRARY") return `"` ~ arg ~ `"`; 297 if(name == "DESCRIPTION") return `'` ~ arg ~ `'`; 298 return arg; 299 } 300 } 301 302 /** 303 Returns a structure descripting a Windows .def file. 304 This allows the tests to not care about the specific formatting 305 used when writing the information out. 306 This encapsulates all the functions to be exported by the DLL/XLL. 307 */ 308 DllDefFile dllDefFile(Modules...) 309 (string libName, 310 string description, 311 Flag!"onlyExports" onlyExports = No.onlyExports) 312 if(allSatisfy!(isSomeString, typeof(Modules))) 313 { 314 import std.conv: to; 315 316 auto statements = [ 317 Statement("LIBRARY", libName), 318 ]; 319 320 string[] exports = ["xlAutoOpen", "xlAutoClose", "xlAutoFree12"]; 321 foreach(func; getAllWorksheetFunctions!Modules(onlyExports)) { 322 exports ~= func.procedure.to!string; 323 } 324 325 return DllDefFile(statements ~ Statement("EXPORTS", exports)); 326 } 327 328 329 /// 330 mixin template GenerateDllDef(string module_ = __MODULE__) { 331 version(exceldDef) { 332 void main(string[] args) nothrow { 333 import xlld.wrap.traits: generateDllDef; 334 try { 335 generateDllDef!module_(args); 336 } catch(Exception ex) { 337 import std.stdio: stderr; 338 try 339 stderr.writeln("Error: ", ex.msg); 340 catch(Exception ex2) 341 assert(0, "Program could not write exception message"); 342 } 343 } 344 } 345 } 346 347 /// 348 void generateDllDef(string module_ = __MODULE__, 349 Flag!"onlyExports" onlyExports = No.onlyExports) 350 (string[] args) 351 { 352 import std.stdio: File; 353 import std.exception: enforce; 354 import std.path: stripExtension; 355 356 enforce(args.length >= 2 && args.length <= 4, 357 "Usage: " ~ args[0] ~ " [file_name] <lib_name> <description>"); 358 359 immutable fileName = args[1]; 360 immutable libName = args.length > 2 361 ? args[2] 362 : fileName.stripExtension ~ ".xll"; 363 immutable description = args.length > 3 364 ? args[3] 365 : "Simple D add-in to Excel"; 366 367 auto file = File(fileName, "w"); 368 foreach(stmt; dllDefFile!module_(libName, description, onlyExports).statements) 369 file.writeln(stmt.toString); 370 } 371 372 /** 373 UDA for functions to be executed asynchronously 374 */ 375 enum Async; 376 377 version(unittest) { 378 // to link 379 extern(C) auto getWorksheetFunctions() @safe pure nothrow { 380 import xlld: WorksheetFunction; 381 WorksheetFunction[] ret; 382 return ret; 383 } 384 }