├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .skeleton.html ├── LICENSE ├── README.md ├── dub.sdl ├── dub.selections.json ├── example ├── .gitignore ├── README.md ├── d_funcs.d ├── dlookup.xlsx ├── dub.sdl ├── dub.selections.json ├── example.xlsx └── myxll.d ├── makedocs.sh ├── reggaefile.d ├── source └── xlld │ ├── any.d │ ├── conv │ ├── from.d │ ├── misc.d │ ├── package.d │ └── to.d │ ├── dummy.d │ ├── from.d │ ├── func │ ├── framework.d │ ├── xl.d │ └── xlf.d │ ├── memorymanager.d │ ├── package.d │ ├── sdk │ ├── framework.d │ ├── xlcall.d │ ├── xlcallcpp.d │ └── xll.d │ ├── static_ │ └── package.d │ ├── test │ └── util.d │ └── wrap │ ├── package.d │ ├── traits.d │ ├── worksheet.d │ └── wrap.d └── tests ├── test ├── d_funcs.d ├── package.d └── xl_funcs.d ├── ut ├── conv │ ├── from.d │ ├── misc.d │ └── to.d ├── func │ └── xlf.d ├── issues.d ├── misc.d └── wrap │ ├── all.d │ ├── module_.d │ ├── traits.d │ ├── wrap.d │ └── wrapped.d └── ut_main.d /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | name: Dub Test 7 | strategy: 8 | matrix: 9 | os: [ubuntu-20.04, windows-2019] 10 | dc: [dmd-2.091.0, ldc-1.20.1] 11 | 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Install D compiler 17 | uses: dlang-community/setup-dlang@v1.1.1 18 | with: 19 | compiler: ${{ matrix.dc }} 20 | 21 | - name: Run tests 22 | run: dub test -q --build=unittest-cov 23 | 24 | - uses: codecov/codecov-action@v2 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | bin 3 | *.def 4 | *.exe 5 | *.dll 6 | *.xll 7 | *.lib 8 | *.a 9 | *.lst 10 | docs -------------------------------------------------------------------------------- /.skeleton.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Documentation 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 29 |
30 |
31 |
32 | 34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2016-2019, Kaleidic Associates Advisory Limited 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # excel-d 2 | 3 | [![Actions Status](https://github.com/symmetryinvestments/excel-d/workflows/CI/badge.svg)](https://github.com/symmetryinvestments/excel-d/actions) 4 | [![Coverage](https://codecov.io/gh/symmetryinvestments/excel-d/branch/master/graph/badge.svg)](https://codecov.io/gh/symmetryinvestments/excel-d) 5 | 6 | Excel API bindings and wrapper API for D 7 | 8 | This dub package contains D declarations for the [Excel SDK](https://msdn.microsoft.com/en-us/library/office/bb687883.aspx) 9 | as well as a D wrapper API. This allows programmers to write Excel worksheet functions in D. 10 | 11 | Motivation and background for the project can be found [here](https://dlang.org/blog/2017/05/31/project-highlight-excel-d/). 12 | See also the [DConf 2017 lightning talk](https://youtu.be/Q70yUGR7d8k?si=KzxHf0fSjUDrQFI_) about excel-d. 13 | 14 | Run makedocs.sh to generate the documentation 15 | Generated documentation - a work in progress - is available at 16 | [dpldocs](http://excel-d.dpldocs.info/index.html). 17 | 18 | A working XLL example can be found in the [`example`](example) 19 | directory. Running `dub build` there will create an XLL 20 | (`myxll32.xll`) that can be loaded in Excel making all of the 21 | functions in `test/xlld/test_d_funcs.d` available to be used in Excel 22 | cells. The types are automatically converted between D native types 23 | and Excel ones. To build the example: `dub build -c example [--arch=x86_mscoff|--arch=x86_64]`. 24 | 25 | For this package to build you will need the Excel SDK `xlcall32.lib` 26 | that can be downloaded [from Microsoft](http://go.microsoft.com/fwlink/?LinkID=251082&clcid=0x409). 27 | Copying it to the build directory should be sufficient 28 | (i.e. when building the example, to the `example` directory). 29 | The library file should be useable as-is, as long as on 32-bit Excel `dub build` is run with 30 | `--arch=x86_mscoff` to use Microsoft's binary format. If linking with optlink, the file must 31 | be converted first. We recommend using `link.exe` to not need the conversion. On 64 bit Excel 32 | just use `--arch=x86_64` - no questions of different library formats. 33 | 34 | As part of the build a `.def` file is generated with all functions to be exported by the XLL. 35 | 36 | Excel won't load the XLL automatically: this must be done manually in File->Tools->Add-Ins. 37 | Click on "Go" for "Excel Add-Ins" (the default) and select your XLL there after clicking on 38 | "Browse". 39 | 40 | The only difference between building for 32-bit or 64-bit Excel is the `arch=` option passed 41 | to dub. A 32-bit XLL will only work on 32-bit Excel and similarly for 64-bit. You will also 42 | need the appropriate 32/64 xlcall32.lib from the Excel SDK to link. 43 | 44 | Sample code (see the [example](example) directory for more): 45 | 46 | ```d 47 | import xlld; 48 | 49 | @Excel(ArgumentText("Array to add"), 50 | HelpTopic("Adds all cells in an array"), 51 | FunctionHelp("Adds all cells in an array"), 52 | ArgumentHelp(["The array to add"])) 53 | double FuncAddEverything(double[][] args) nothrow @nogc { // nothrow and @nogc are optional 54 | import std.algorithm: fold; 55 | import std.math: isNaN; 56 | 57 | double ret = 0; 58 | foreach(row; args) 59 | ret += row.fold!((a, b) => b.isNaN ? a : a + b)(0.0); 60 | return ret; 61 | } 62 | ``` 63 | 64 | and then in Excel: 65 | 66 | `=FuncAddEverything(A1:D20)` 67 | 68 | Future functionality will include creating menu items and dialogue boxes. Pull requests welcomed. 69 | 70 | 71 | WARNING: Memory for parameters passed to D functions 72 | --------------------------------------------------- 73 | 74 | Any parameters with indirections (pointers, slices) should NOT be escaped. The memory for those 75 | parameters WILL be reused and might cause crashes. 76 | 77 | There is support to fail at compile-time if user-written D functions attempt to escape their 78 | arguments but unfortunately given the current D defaults requires user intervention. Annotate 79 | all D code to be called by Excel with `@safe` and compile with `-dip1000` - all parameters will 80 | then need to be `scope` or the code will not compile. 81 | 82 | It is *strongly* advised to compile with `-dip1000` and to make all your functions `@safe`, 83 | or your add-ins could cause Excel to crash. 84 | 85 | 86 | Function spelling 87 | ------------------ 88 | 89 | excel-d will always convert the first character in the D function being wrapped to uppercase 90 | since that is the Excel convention. 91 | 92 | 93 | Variant type `Any` 94 | --------------------- 95 | 96 | Sometimes it is useful for a D function to take in any type that Excel supports. Typically 97 | this will happen when receiving a matrix of values where the types might differ 98 | (e.g. the columns are date, string, double). To get the expected D type from an `Any` value, 99 | use `xlld.wrap.fromXlOper`. An example: 100 | 101 | ```d 102 | double Func(Any[][] values) { 103 | import xlld.wrap: fromXlOper; 104 | import std.experimental.allocator: theAllocator; 105 | foreach(row; values) { 106 | auto date = row[0].fromXlOper!DateTime(theAllocator); 107 | auto string_ = row[1].fromXlOper!DateTime(theAllocator); 108 | auto double_ = row[2].fromXlOper!double(theAllocator); 109 | // ... 110 | } 111 | return ret; 112 | } 113 | ``` 114 | 115 | 116 | Asynchronous functions 117 | ---------------------- 118 | 119 | A D function can be decorated with the `@Async` UDA and will be executed asynchronously: 120 | 121 | ```d 122 | @Async 123 | double AsyncFunc(double d) { 124 | // long-running task 125 | } 126 | ``` 127 | 128 | Please see [the Microsoft documentation](https://msdn.microsoft.com/en-us/library/office/ff796219(v=office.14).aspx). 129 | 130 | Custom enum coversions 131 | ---------------------- 132 | 133 | If the usual conversions between strings and enums don't work for the user, it is possible to register 134 | custom coversions by calling the functions `registerConversionTo` and `registerConversionFrom`. 135 | 136 | Structs 137 | -------- 138 | 139 | D structs can be returned by functions. They are transformed into a string representation. 140 | 141 | D structs can also be passed to functions. To do so, pass in a 1D array with the same number 142 | of elements as the struct in question. 143 | 144 | 145 | Optional custom memory allocation and `@nogc` 146 | --------------------------------------------- 147 | 148 | If you are not familiar with questions of memory allocation, the below may seem intimidating. 149 | However it's entirely optional and unless performance and latency are critical to you (or 150 | possibly if you are interfacing with C or C++ code) then you do not need to worry about the 151 | extra complexity introduced by using allocators. The code in the previous section will simply 152 | work. 153 | 154 | excel-d uses a custom allocator for all allocations that are needed when doing the conversions 155 | between D and Excel types. It uses a different one for allocations of XLOPER12s that are 156 | returned to Excel, which are then freed in xlAutoFree12 with the same allocator. D functions 157 | that are `@nogc` are wrapped by `@nogc` Excel functions and similarly for `@safe`. However, 158 | if returning a value that is dynamically allocated from a D function and not using the GC 159 | (such as an array of doubles), it is necessary to specify how that memory is to be freed. 160 | An example: 161 | 162 | ```d 163 | // @Dispose is used to tell the framework how to free memory that is dynamically 164 | // allocated by the D function. After returning, the value is converted to an 165 | // Excel type and the D value is freed using the lambda defined here. 166 | @Dispose!((ret) { 167 | import std.experimental.allocator.mallocator: Mallocator; 168 | import std.experimental.allocator: dispose; 169 | Mallocator.instance.dispose(ret); 170 | }) 171 | double[] FuncReturnArrayNoGc(double[] numbers) @nogc @safe nothrow { 172 | import std.experimental.allocator.mallocator: Mallocator; 173 | import std.experimental.allocator: makeArray; 174 | import std.algorithm: map; 175 | 176 | try { 177 | // Allocate memory here in order to return an array of doubles. 178 | // The memory will be freed after the call by calling the 179 | // function in `@Dispose` above 180 | return Mallocator.instance.makeArray(numbers.map!(a => a * 2)); 181 | } catch(Exception _) { 182 | return []; 183 | } 184 | } 185 | ``` 186 | 187 | This allows for `@nogc` functions to be called from Excel without memory leaks. 188 | 189 | 190 | Registering code to run when the XLL is unloaded 191 | ------------------------------------------------ 192 | 193 | Since this library automatically writes `xlAutoClose` it is not possible to use it to 194 | run custom code at XLL unloading. As an alternative XLL writers can use 195 | `xlld.xll.registerAutoCloseFunc` passing it a function or a delegate to be executed 196 | when `xlAutoClose` is called. 197 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "excel-d" 2 | description "Excel bindings for D" 3 | authors "Laeeth Isharc" "Stefan Koch" "Atila Neves" 4 | copyright "Copyright © 2016-2019 Kaleidic Associates Advisory Limited" 5 | license "BSD" 6 | dependency "nogc" version="~>0.5.0" 7 | 8 | 9 | configuration "default" { 10 | targetType "sourceLibrary" 11 | } 12 | 13 | 14 | configuration "testLibrary" { 15 | // FIXME: static constructors don't get run if the targetType is "library" 16 | targetType "sourceLibrary" 17 | versions "testLibraryExcelD" 18 | dependency "unit-threaded" version="*" 19 | } 20 | 21 | 22 | configuration "unittest" { 23 | targetType "executable" 24 | targetName "ut" 25 | targetPath "bin" 26 | importPaths "source" "tests" 27 | sourcePaths "source" "tests" 28 | mainSourceFile "tests/ut_main.d" 29 | 30 | versions "testingExcelD" "testLibraryExcelD" 31 | dflags "-preview=dip1000" 32 | 33 | dependency "unit-threaded" version="*" 34 | dependency "automem" version="*" 35 | } 36 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "automem": "0.6.7", 5 | "localimport": "1.3.0", 6 | "nogc": "0.5.1", 7 | "stdx-allocator": "2.77.5", 8 | "test_allocator": "0.3.3", 9 | "unit-threaded": "2.0.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | dub.selections.json -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example XLL 2 | 3 | The dub package in this directory will build an XLL that Excel can load. 4 | The functions exported aren't very useful can be found in 5 | [excel-d's source tree](../source/xlld/test_d_funcs). 6 | 7 | Either copy the appropriate (32/64 bit) `xlcall32.lib` from the Excel SDK 8 | in this directory to build (then either `dub build --arch=x86_mscoff` or `dub build --arch=x86_64`) 9 | or make sure it's somewhere that `link.exe` can find. 10 | -------------------------------------------------------------------------------- /example/d_funcs.d: -------------------------------------------------------------------------------- 1 | /** 2 | Only exists to test the wrapping functionality Contains functions 3 | with regular D types that will get wrapped so they can be called by 4 | the spreadsheet. 5 | */ 6 | 7 | import xlld; 8 | import std.datetime: DateTime; 9 | import std.typecons: Tuple; 10 | 11 | double[][] dlookup(string[][] haystack, string[] needles, double columnNumberD) nothrow 12 | { 13 | import std.exception; 14 | import std.algorithm:map,countUntil; 15 | import std.range:repeat,transposed; 16 | import std.array:array; 17 | import std.conv:to; 18 | 19 | double toDouble(long pos) 20 | { 21 | return (pos==-1) ? double.nan : pos.to!double + 1.0; 22 | } 23 | try 24 | { 25 | auto columnNumber = columnNumberD.to!int; 26 | auto haystackColumn = haystack.map!(row => row[columnNumber-1].to!string).array; 27 | return needles.map!( needle => [toDouble(haystackColumn.countUntil(needle).to!long)]).array; 28 | } 29 | catch(Exception e) 30 | { 31 | return ([double.nan].repeat(needles.length)).array; 32 | } 33 | } 34 | @Register(ArgumentText("Array to add"), 35 | HelpTopic("https://github.com/symmetryinvestments/excel-d!0"), 36 | FunctionHelp("Adds all cells in an array"), 37 | ArgumentHelp(["The array to add"])) 38 | double FuncAddEverything(double[][] args) nothrow @nogc { 39 | import std.algorithm: fold; 40 | import std.math: isNaN; 41 | 42 | double ret = 0; 43 | foreach(row; args) 44 | ret += row.fold!((a, b) => b.isNaN ? 0.0 : a + b)(0.0); 45 | return ret; 46 | } 47 | 48 | // @Dispose is used to tell the framework how to free memory that is dynamically 49 | // allocated by the D function. After returning, the value is converted to an 50 | // Excel type sand the D value is freed using the lambda defined here. 51 | @Dispose!((ret) { 52 | import std.experimental.allocator.mallocator: Mallocator; 53 | import std.experimental.allocator: dispose; 54 | Mallocator.instance.dispose(ret); 55 | }) 56 | double[] FuncReturnArrayNoGc(scope double[] numbers) @nogc @safe nothrow { 57 | import std.experimental.allocator.mallocator: Mallocator; 58 | import std.experimental.allocator: makeArray; 59 | import std.algorithm: map; 60 | 61 | try { 62 | // Allocate memory here in order to return an array of doubles. 63 | // The memory will be freed after the call by calling the 64 | // function in `@Dispose` above 65 | return Mallocator.instance.makeArray(numbers.map!(a => a * 2)); 66 | } catch(Exception _) { 67 | return []; 68 | } 69 | } 70 | 71 | double[][] FuncTripleEverything(double[][] args) nothrow { 72 | double[][] ret; 73 | ret.length = args.length; 74 | foreach(i; 0 .. args.length) { 75 | ret[i].length = args[i].length; 76 | foreach(j; 0 .. args[i].length) 77 | ret[i][j] = args[i][j] * 3; 78 | } 79 | 80 | return ret; 81 | } 82 | 83 | double FuncAllLengths(string[][] args) nothrow @nogc { 84 | import std.algorithm: fold; 85 | 86 | double ret = 0; 87 | foreach(row; args) 88 | ret += row.fold!((a, b) => a + b.length)(0.0); 89 | return ret; 90 | } 91 | 92 | double[][] FuncLengths(string[][] args) nothrow { 93 | double[][] ret; 94 | 95 | ret.length = args.length; 96 | foreach(i; 0 .. args.length) { 97 | ret[i].length = args[i].length; 98 | foreach(j; 0 .. args[i].length) 99 | ret[i][j] = args[i][j].length; 100 | } 101 | 102 | return ret; 103 | } 104 | 105 | 106 | string[][] FuncBob(string[][] args) nothrow { 107 | string[][] ret; 108 | 109 | ret.length = args.length; 110 | foreach(i; 0 .. args.length) { 111 | ret[i].length = args[i].length; 112 | foreach(j; 0 .. args[i].length) 113 | ret[i][j] = args[i][j] ~ "bob"; 114 | } 115 | 116 | return ret; 117 | } 118 | 119 | 120 | double FuncDoubleSlice(double[] arg) nothrow @nogc { 121 | return arg.length; 122 | } 123 | 124 | double FuncStringSlice(string[] arg) nothrow @nogc { 125 | return arg.length; 126 | } 127 | 128 | double[] FuncSliceTimes3(double[] arg) nothrow { 129 | import std.algorithm; 130 | import std.array; 131 | return arg.map!(a => a * 3).array; 132 | } 133 | 134 | string[] StringsToStrings(string[] args) nothrow { 135 | import std.algorithm; 136 | import std.array; 137 | return args.map!(a => a ~ "foo").array; 138 | } 139 | 140 | string StringsToString(string[] args) nothrow { 141 | import std.string; 142 | return args.join(", "); 143 | } 144 | 145 | string StringToString(string arg) nothrow { 146 | return arg ~ "bar"; 147 | } 148 | 149 | private string shouldNotBeAProblem(string, string[]) nothrow { 150 | return ""; 151 | } 152 | 153 | string ManyToString(string arg0, string arg1, string arg2) nothrow { 154 | return arg0 ~ arg1 ~ arg2; 155 | } 156 | 157 | double FuncThrows(double) { 158 | throw new Exception("oops"); 159 | } 160 | 161 | Any[][] FirstOfTwoAnyArrays(Any[][] lhs, Any[][] rhs) nothrow { 162 | return lhs; 163 | } 164 | 165 | string[][] FirstOfTwoAnyArraysToString(Any[][] testarg, Any[][] rhs) nothrow { 166 | import std.array, std.algorithm, std.conv; 167 | try { 168 | return testarg.map!(map!(to!string)).map!array.array; 169 | } catch(Exception e) { 170 | return [[e.msg]]; 171 | } 172 | } 173 | 174 | string DateTimeToString(DateTime dt) @safe { 175 | import std.conv: text; 176 | return text("year: ", dt.year, ", month: ", dt.month, ", day: ", dt.day, 177 | ", hour: ", dt.hour, ", minute: ", dt.minute, ", second: ", dt.second); 178 | } 179 | 180 | string DateTimesToString(scope DateTime[] dts) @safe { 181 | import std.conv: text; 182 | import std.algorithm: map; 183 | import std.string: join; 184 | return dts.map!(dt => text("year: ", dt.year, ", month: ", dt.month, ", day: ", dt.day, 185 | ", hour: ", dt.hour, ", minute: ", dt.minute, ", second: ", dt.second)).join("\n"); 186 | } 187 | 188 | double FuncTwice(double d) @safe nothrow @nogc { 189 | return d * 2; 190 | } 191 | 192 | @Async 193 | double FuncTwiceAsync(double d) { 194 | import core.thread; 195 | Thread.sleep(5.seconds); 196 | return d * 2; 197 | } 198 | 199 | double IntToDouble(int i) { 200 | return i * 2; 201 | } 202 | 203 | int DoubleToInt(double d) { 204 | return cast(int)(d * 2); 205 | } 206 | 207 | int IntToInt(int i) @safe nothrow @nogc { 208 | return i * 2; 209 | } 210 | 211 | DateTime[] DateTimes(int year, int month, int day) { 212 | return [ 213 | DateTime(year, month, day), 214 | DateTime(year + 1, month + 1, day + 1), 215 | DateTime(year + 2, month + 2, day + 2), 216 | ]; 217 | } 218 | 219 | string FuncCaller() @safe { 220 | import xlld.func.xlf: xlfCaller = caller; 221 | import xlld.sdk.xlcall: XlType; 222 | import std.conv: text; 223 | 224 | auto caller = xlfCaller; 225 | 226 | switch(caller.xltype) with(XlType) { 227 | 228 | default: 229 | return "Unknown caller type"; 230 | 231 | case xltypeSRef: 232 | return text("Called from cell. Rows: ", 233 | caller.val.sref.ref_.rwFirst, " .. ", caller.val.sref.ref_.rwLast, 234 | " Cols: ", caller.val.sref.ref_.colFirst, " .. ", caller.val.sref.ref_.colLast); 235 | 236 | case xltypeRef: 237 | return "Called from a multi-cell array formula"; 238 | } 239 | } 240 | 241 | string FuncCallerAdjacent() @safe { 242 | import xlld.func.xl: Coerced; 243 | import xlld.func.xlf: caller; 244 | import xlld.sdk.xlcall: XlType; 245 | import std.exception: enforce; 246 | 247 | auto res = caller; 248 | 249 | enforce(res.xltype == XlType.xltypeSRef); 250 | 251 | ++res.val.sref.ref_.colFirst; 252 | ++res.val.sref.ref_.colLast; 253 | 254 | auto coerced = Coerced(res); 255 | return "Guy next to me: " ~ coerced.toString; 256 | } 257 | 258 | double[] Doubles() @safe { 259 | return [33.3, 123.0]; 260 | } 261 | 262 | enum MyEnum { foo, bar, baz } 263 | 264 | 265 | string FuncEnumArg(MyEnum val) @safe { 266 | import std.conv: text; 267 | return "prefix_" ~ val.text; 268 | } 269 | 270 | MyEnum FuncEnumRet(int i) @safe { 271 | return cast(MyEnum)i; 272 | } 273 | 274 | struct Point { int x, y; } 275 | 276 | int FuncPointArg(Point point) @safe { 277 | return point.x + point.y; 278 | } 279 | 280 | Point FuncPointRet(int x, int y) @safe { 281 | return Point(x + 1, y + 2); 282 | } 283 | 284 | auto FuncSimpleTupleRet(double d, string s) @safe { 285 | import std.typecons: tuple; 286 | return tuple(d, s); 287 | } 288 | 289 | auto FuncComplexTupleRet(int d1, int d2) @safe { 290 | import std.typecons: tuple; 291 | return tuple([DateTime(2017, 1, d1), DateTime(2017, 2, d1)], 292 | [DateTime(2018, 1, d1), DateTime(2018, 2, d2)]); 293 | } 294 | 295 | int FuncAddOptional(int i, int j = 42) @safe { 296 | return i + j; 297 | } 298 | 299 | string FuncAppendOptional(scope string a, scope string b = "quux") @safe { 300 | return a ~ b; 301 | } 302 | 303 | auto FuncVector(int i) @safe @nogc { 304 | import automem.vector: vector; 305 | import std.range: iota; 306 | import std.experimental.allocator.mallocator: Mallocator; 307 | 308 | return vector!Mallocator(i.iota); 309 | } 310 | 311 | 312 | auto FuncVector2D(int i) @safe @nogc { 313 | import automem.vector: vector; 314 | import std.range: iota; 315 | import std.experimental.allocator.mallocator: Mallocator; 316 | 317 | return vector!Mallocator(vector!Mallocator(i, i, i), 318 | vector!Mallocator(i + 1, i + 1, i + 1)); 319 | } 320 | 321 | auto FuncStringVector(int i) @safe @nogc { 322 | import automem.vector: vector; 323 | import std.experimental.allocator.mallocator: Mallocator; 324 | 325 | return vector!Mallocator("hi"); 326 | } 327 | -------------------------------------------------------------------------------- /example/dlookup.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symmetryinvestments/excel-d/1e63c864b9d268ba1d106b2f54172a5302ba1a59/example/dlookup.xlsx -------------------------------------------------------------------------------- /example/dub.sdl: -------------------------------------------------------------------------------- 1 | name "myxll" 2 | description "Example of how to use excel-d" 3 | license "BSD 3-clause" 4 | dependency "excel-d" path=".." # normally this would be version="~>0.0.1" or similar 5 | sourceFiles "myxll.d" "d_funcs.d" 6 | targetType "dynamicLibrary" 7 | dflags "-dip25" "-dip1000" 8 | 9 | configuration "xll" { 10 | preBuildCommands "dub run -c def --nodeps -q -- myxll.def" 11 | sourceFiles "myxll.def" 12 | # must have the appropriate 32/64 bit Excel SDK xlcall32.lib in the path of the app 13 | # unfortunately they're both called xlcall32.lib 14 | 15 | libs "xlcall32" 16 | postBuildCommands "copy /y myxll.dll myxll.xll" 17 | } 18 | 19 | configuration "justxll" { 20 | sourceFiles "myxll.def" 21 | # must have the appropriate 32/64 bit Excel SDK xlcall32.lib in the path of the app 22 | # unfortunately they're both called xlcall32.lib 23 | 24 | libs "xlcall32" 25 | postBuildCommands "copy /y myxll.dll myxll.xll" 26 | } 27 | 28 | 29 | // This builds a binary that writes out the necessary .def file 30 | // to export the functions 31 | configuration "def" { 32 | targetType "executable" 33 | targetName "write_def" 34 | versions "exceldDef" 35 | } 36 | -------------------------------------------------------------------------------- /example/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "automem": "0.6.1", 5 | "excel-d": {"path":".."}, 6 | "localimport": "1.3.0", 7 | "nogc": "0.5.0", 8 | "stdx-allocator": "2.77.5", 9 | "test_allocator": "0.3.2", 10 | "unit-threaded": "0.10.5" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/example.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symmetryinvestments/excel-d/1e63c864b9d268ba1d106b2f54172a5302ba1a59/example/example.xlsx -------------------------------------------------------------------------------- /example/myxll.d: -------------------------------------------------------------------------------- 1 | module example.myxll; 2 | 3 | import xlld: wrapAll; 4 | 5 | // wrapAll takes a list of modules, in this case we're only wrapping 6 | // one, but there's no limit 7 | // The current file will then contain all the code to correctly 8 | // start an XLL with wrapped Excel functions of all D functions 9 | // in the listed modules 10 | mixin(wrapAll!("d_funcs")); 11 | -------------------------------------------------------------------------------- /makedocs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | project_dir="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 6 | echo "generating documents for $project_dir" 7 | cd /tmp 8 | [[ -e adrdox ]] && rm -rf adrdox 9 | git clone --depth=1 https://github.com/adamdruppe/adrdox 10 | cp "$project_dir"/.skeleton.html adrdox/skeleton.html 11 | cd adrdox 12 | make 13 | ./doc2 -i "$project_dir"/source 14 | mv generated-docs/* "$project_dir"/docs 15 | cp "$project_dir"/docs/xlld.html "$project_dir"/docs/index.html 16 | -------------------------------------------------------------------------------- /reggaefile.d: -------------------------------------------------------------------------------- 1 | import reggae; 2 | import std.typecons; 3 | 4 | alias ut = dubTestTarget!(CompilerFlags("-g -debug"), 5 | LinkerFlags(), 6 | CompilationMode.package_); 7 | mixin build!(ut); 8 | -------------------------------------------------------------------------------- /source/xlld/any.d: -------------------------------------------------------------------------------- 1 | /** 2 | Any type 3 | */ 4 | module xlld.any; 5 | 6 | /// 7 | struct Any { 8 | import xlld.sdk.xlcall: XLOPER12; 9 | 10 | /// 11 | XLOPER12 _impl; 12 | alias _impl this; 13 | 14 | /// 15 | bool opEquals(Any other) @trusted const { 16 | import xlld.sdk.xlcall: XlType; 17 | import xlld.conv.from: fromXlOper; 18 | 19 | switch(_impl.xltype) { 20 | 21 | default: 22 | import xlld.conv.misc: stripMemoryBitmask; 23 | 24 | if(_impl.xltype.stripMemoryBitmask != other._impl.xltype.stripMemoryBitmask) 25 | return false; 26 | 27 | XLOPER12 comp = _impl; 28 | comp.xltype = other._impl.xltype; 29 | 30 | return comp == other._impl; 31 | 32 | case XlType.xltypeStr: 33 | 34 | import std.experimental.allocator.gc_allocator: GCAllocator; 35 | return _impl.fromXlOper!string(GCAllocator.instance) == 36 | other._impl.fromXlOper!string(GCAllocator.instance); 37 | 38 | case XlType.xltypeMulti: 39 | 40 | if(_impl.val.array.rows != other._impl.val.array.rows) return false; 41 | if(_impl.val.array.columns != other._impl.val.array.columns) return false; 42 | 43 | int i; 44 | foreach(r; 0 .. _impl.val.array.rows) { 45 | foreach(c; 0 .. _impl.val.array.columns) { 46 | if(Any(cast(XLOPER12)_impl.val.array.lparray[i]) != 47 | Any(cast(XLOPER12)other._impl.val.array.lparray[i])) 48 | return false; 49 | ++i; 50 | } 51 | } 52 | 53 | return true; 54 | } 55 | } 56 | 57 | /// 58 | string toString() @safe const { 59 | import std.conv: text; 60 | import xlld.sdk.xlcall: XlType; 61 | import xlld.conv.from: fromXlOper; 62 | import xlld.sdk.xlcall: xlbitXLFree, xlbitDLLFree; 63 | import std.experimental.allocator.gc_allocator: GCAllocator; 64 | 65 | alias allocator = GCAllocator.instance; 66 | 67 | string ret = text("Any(", ); 68 | const type = _impl.xltype & ~(xlbitXLFree | xlbitDLLFree); 69 | switch(type) { 70 | default: 71 | ret ~= type.text; 72 | break; 73 | case XlType.xltypeStr: 74 | ret ~= () @trusted { return text(`"`, _impl.fromXlOper!string(allocator), `"`); }(); 75 | break; 76 | case XlType.xltypeNum: 77 | ret ~= () @trusted { return _impl.fromXlOper!double(allocator).text; }(); 78 | break; 79 | case XlType.xltypeInt: 80 | ret ~= () @trusted { return _impl.fromXlOper!int(allocator).text; }(); 81 | break; 82 | case XlType.xltypeMulti: 83 | int i; 84 | ret ~= `[`; 85 | const rows = () @trusted { return _impl.val.array.rows; }(); 86 | const cols = () @trusted { return _impl.val.array.columns; }(); 87 | foreach(r; 0 .. rows) { 88 | ret ~= `[`; 89 | foreach(c; 0 .. cols) { 90 | auto oper = () @trusted { return _impl.val.array.lparray[i++]; }(); 91 | ret ~= text(Any(cast(XLOPER12)oper), `, `); 92 | } 93 | ret ~= `], `; 94 | } 95 | ret ~= `]`; 96 | break; 97 | } 98 | return ret ~ ")"; 99 | } 100 | 101 | } 102 | 103 | 104 | /// 105 | auto any(T, A)(auto ref T value, auto ref A allocator, in string file = __FILE__, in size_t line = __LINE__) @trusted { 106 | import xlld.conv: toXlOper; 107 | static if(__traits(compiles, Any(value.toXlOper(allocator, file, line)))) 108 | return Any(value.toXlOper(allocator, file, line)); 109 | else 110 | return Any(value.toXlOper(allocator)); 111 | } 112 | -------------------------------------------------------------------------------- /source/xlld/conv/from.d: -------------------------------------------------------------------------------- 1 | /** 2 | Conversions from XLOPER12 to D types 3 | */ 4 | module xlld.conv.from; 5 | 6 | import xlld.from; 7 | import xlld.sdk.xlcall: XLOPER12; 8 | import xlld.any: Any; 9 | import xlld.wrap.wrap: isWantedType; 10 | import std.traits: Unqual; 11 | import std.datetime: DateTime; 12 | 13 | 14 | alias ToEnumConversionFunction = int delegate(string); 15 | package __gshared ToEnumConversionFunction[string] gToEnumConversions; 16 | shared from!"core.sync.mutex".Mutex gToEnumMutex; 17 | 18 | 19 | // FIXME - why is this not the same as isUserStruct? 20 | template isRegularStruct(T) { 21 | import xlld.any: Any; 22 | import std.traits: Unqual; 23 | import std.datetime: DateTime; 24 | enum isRegularStruct = is(T == struct) && !is(Unqual!T == Any) && !is(Unqual!T == DateTime); 25 | } 26 | 27 | /// 28 | auto fromXlOper(T, A)(ref XLOPER12 val, ref A allocator) { 29 | return (&val).fromXlOper!T(allocator); 30 | } 31 | 32 | /// RValue overload 33 | auto fromXlOper(T, A)(XLOPER12 val, ref A allocator) { 34 | return fromXlOper!T(val, allocator); 35 | } 36 | 37 | __gshared immutable fromXlOperDoubleWrongTypeException = new Exception("Wrong type for fromXlOper!double"); 38 | /// 39 | auto fromXlOper(T, A)(XLOPER12* val, ref A allocator) if(is(Unqual!T == double)) { 40 | import xlld.sdk.xlcall: XlType; 41 | import xlld.conv.misc: stripMemoryBitmask; 42 | 43 | if(val.xltype.stripMemoryBitmask == XlType.xltypeMissing) 44 | return double.init; 45 | 46 | if(val.xltype.stripMemoryBitmask == XlType.xltypeInt) 47 | return cast(T)val.val.w; 48 | 49 | if(val.xltype.stripMemoryBitmask != XlType.xltypeNum) 50 | throw fromXlOperDoubleWrongTypeException; 51 | 52 | return val.val.num; 53 | } 54 | 55 | __gshared immutable fromXlOperIntWrongTypeException = new Exception("Wrong type for fromXlOper!int"); 56 | 57 | /// 58 | auto fromXlOper(T, A)(XLOPER12* val, ref A allocator) if(is(Unqual!T == int)) { 59 | import xlld.conv.misc: stripMemoryBitmask; 60 | import xlld.sdk.xlcall: XlType; 61 | 62 | if(val.xltype.stripMemoryBitmask == XlType.xltypeMissing) 63 | return int.init; 64 | 65 | if(val.xltype.stripMemoryBitmask == XlType.xltypeNum) 66 | return cast(typeof(return))val.val.num; 67 | 68 | if(val.xltype.stripMemoryBitmask != XlType.xltypeInt) 69 | throw fromXlOperIntWrongTypeException; 70 | 71 | return val.val.w; 72 | } 73 | 74 | 75 | /// 76 | __gshared immutable fromXlOperMemoryException = new Exception("Could not allocate memory for array of char"); 77 | /// 78 | __gshared immutable fromXlOperConvException = new Exception("Could not convert double to string"); 79 | 80 | __gshared immutable fromXlOperStringTypeException = new Exception("Wrong type for fromXlOper!string"); 81 | 82 | /// 83 | auto fromXlOper(T, A)(XLOPER12* val, ref A allocator) if(is(Unqual!T == string)) { 84 | 85 | import xlld.conv.misc: stripMemoryBitmask; 86 | import xlld.sdk.xlcall: XlType; 87 | import std.experimental.allocator: makeArray; 88 | import std.utf: byChar; 89 | import std.range: walkLength; 90 | 91 | const stripType = stripMemoryBitmask(val.xltype); 92 | 93 | if(stripType == XlType.xltypeMissing) 94 | return null; 95 | 96 | if(stripType != XlType.xltypeStr && stripType != XlType.xltypeNum) 97 | throw fromXlOperStringTypeException; 98 | 99 | 100 | if(stripType == XlType.xltypeStr) { 101 | 102 | auto chars = () @trusted { return val.val.str[1 .. val.val.str[0] + 1].byChar; }(); 103 | const length = chars.save.walkLength; 104 | auto ret = () @trusted { return allocator.makeArray!char(length); }(); 105 | 106 | if(ret is null && length > 0) 107 | throw fromXlOperMemoryException; 108 | 109 | int i; 110 | foreach(ch; () @trusted { return val.val.str[1 .. val.val.str[0] + 1].byChar; }()) 111 | ret[i++] = ch; 112 | 113 | return () @trusted { return cast(string)ret; }(); 114 | } else { 115 | 116 | // if a double, try to convert it to a string 117 | import std.math: isNaN; 118 | import core.stdc.stdio: snprintf; 119 | 120 | char[1024] buffer; 121 | const numChars = () @trusted { 122 | if(val.val.num.isNaN) 123 | return snprintf(&buffer[0], buffer.length, "#NaN"); 124 | else 125 | return snprintf(&buffer[0], buffer.length, "%lf", val.val.num); 126 | }(); 127 | if(numChars > buffer.length - 1) 128 | throw fromXlOperConvException; 129 | auto ret = () @trusted { return allocator.makeArray!char(numChars); }(); 130 | 131 | if(ret is null && numChars > 0) 132 | throw fromXlOperMemoryException; 133 | 134 | ret[] = buffer[0 .. numChars]; 135 | return () @trusted { return cast(string)ret; }(); 136 | } 137 | } 138 | 139 | 140 | /// 141 | T fromXlOper(T, A)(XLOPER12* oper, ref A allocator) if(is(Unqual!T == Any)) { 142 | import xlld.conv.misc: dup; 143 | return Any((*oper).dup(allocator)); 144 | } 145 | 146 | private enum Dimensions { 147 | One, 148 | Two, 149 | } 150 | 151 | 152 | /// 2D slices 153 | auto fromXlOper(T, A)(XLOPER12* val, ref A allocator) 154 | if(is(T: E[][], E) && 155 | !(is(Unqual!T == string[])) && 156 | (!is(T: EE[][][], EE) || is(Unqual!(typeof(T.init[0][0])) == string))) 157 | { 158 | return val.fromXlOperMulti!(Dimensions.Two, typeof(T.init[0][0]))(allocator); 159 | } 160 | 161 | 162 | /// 1D slices 163 | auto fromXlOper(T, A)(XLOPER12* val, ref A allocator) 164 | if(is(T: E[], E) && 165 | !(is(Unqual!T == string)) && 166 | (!is(T: EE[][], EE) || is(Unqual!(typeof(T.init[0])) == string))) 167 | { 168 | return val.fromXlOperMulti!(Dimensions.One, typeof(T.init[0]))(allocator); 169 | } 170 | 171 | 172 | /// 173 | __gshared immutable fromXlOperMultiOperException = new Exception("fromXlOper: oper not of multi type"); 174 | /// 175 | __gshared immutable fromXlOperMultiMemoryException = new Exception("fromXlOper: Could not allocate memory in fromXlOperMulti"); 176 | 177 | private auto fromXlOperMulti(Dimensions dim, T, A)(XLOPER12* val, ref A allocator) { 178 | import xlld.conv.misc: stripMemoryBitmask; 179 | import xlld.sdk.xlcall: XlType; 180 | 181 | if(stripMemoryBitmask(val.xltype) == XlType.xltypeNil) { 182 | static if(dim == Dimensions.Two) 183 | return T[][].init; 184 | else static if(dim == Dimensions.One) 185 | return T[].init; 186 | else 187 | static assert(0, "Unknown number of dimensions in fromXlOperMulti"); 188 | } 189 | 190 | // See ut.wrap.wrap.xltypeNum can convert to array 191 | if(stripMemoryBitmask(val.xltype) == XlType.xltypeNum) { 192 | return fromXlOperMultiNumber!(dim, T)(val, allocator); 193 | } 194 | 195 | if(!isMulti(*val)) throw fromXlOperMultiOperException; 196 | 197 | assert(() @trusted { return val.val.array.rows; }() > 0 && 198 | () @trusted { return val.val.array.columns; }() > 0, 199 | "Multi opers may not have 0 rows or columns"); 200 | 201 | // This case has to be handled differently since we're converting to a 1D array 202 | // of structs from a 2D array in Excel. 203 | static if(isRegularStruct!T) 204 | return fromXlOperMultiStruct!(dim, T)(val, allocator); 205 | else 206 | return fromXlOperMultiStandard!(dim, T)(val, allocator); 207 | } 208 | 209 | 210 | // convert a number to an array 211 | private auto fromXlOperMultiNumber(Dimensions dim, T, A)(XLOPER12* val, ref A allocator) 212 | { 213 | 214 | static if(dim == Dimensions.Two) { 215 | import std.experimental.allocator: makeMultidimensionalArray; 216 | auto ret = allocator.makeMultidimensionalArray!T(1, 1); 217 | ret[0][0] = val.fromXlOper!T(allocator); 218 | return ret; 219 | } else static if(dim == Dimensions.One) { 220 | import std.experimental.allocator: makeArray; 221 | auto ret = allocator.makeArray!T(1); 222 | ret[0] = val.fromXlOper!T(allocator); 223 | return ret; 224 | } else 225 | static assert(0, "Unknown number of dimensions in fromXlOperMulti"); 226 | } 227 | 228 | // no-frills fromXlOperMulti 229 | private auto fromXlOperMultiStandard(Dimensions dim, T, A) 230 | (XLOPER12* val, ref A allocator) 231 | in(from!"xlld.conv.misc".stripMemoryBitmask(val.xltype) == 232 | from!"xlld.sdk.xlcall".XlType.xltypeMulti) 233 | { 234 | import xlld.memorymanager: makeArray2D; 235 | import std.experimental.allocator: makeArray; 236 | 237 | // @trusted because of the `in` contract 238 | const rows = () @trusted { return val.val.array.rows; }(); 239 | const cols = () @trusted { return val.val.array.columns; }(); 240 | 241 | static if(dim == Dimensions.Two) 242 | auto ret = allocator.makeArray2D!T(*val); 243 | else static if(dim == Dimensions.One) 244 | auto ret = allocator.makeArray!T(rows * cols); 245 | else 246 | static assert(0, "Unknown number of dimensions in fromXlOperMulti"); 247 | 248 | if(() @trusted { return ret.ptr; }() is null) 249 | throw fromXlOperMultiMemoryException; 250 | 251 | (*val).apply!(T, (shouldConvert, row, col, cellVal) { 252 | 253 | auto value = shouldConvert ? cellVal.fromXlOper!T(allocator) : T.init; 254 | 255 | static if(dim == Dimensions.Two) 256 | ret[row][col] = value; 257 | else 258 | ret[row * cols + col] = value; 259 | }); 260 | 261 | return ret; 262 | } 263 | 264 | // return an array of structs 265 | private auto fromXlOperMultiStruct(Dimensions dim, T, A)(XLOPER12* val, ref A allocator) 266 | in(from!"xlld.conv.misc".stripMemoryBitmask(val.xltype) == 267 | from!"xlld.sdk.xlcall".XlType.xltypeMulti) 268 | { 269 | import xlld.sdk.xlcall: XlType; 270 | import std.experimental.allocator: makeArray; 271 | 272 | static assert(dim == Dimensions.One, "Only 1D struct arrays are supported"); 273 | 274 | const rows = () @trusted { return val.val.array.rows; }(); 275 | // The length of the struct array has -1 because the first row are names 276 | auto ret = allocator.makeArray!T(rows - 1); 277 | if(() @trusted { return ret.ptr; }() is null) 278 | throw fromXlOperMultiMemoryException; 279 | 280 | foreach(r, ref elt; ret) { 281 | XLOPER12 array1d; 282 | array1d.xltype = XlType.xltypeMulti; 283 | () @trusted { 284 | array1d.val.array.rows = 1; 285 | array1d.val.array.columns = T.tupleof.length; 286 | array1d.val.array.lparray = val.val.array.lparray + (r + 1) * T.tupleof.length; 287 | }(); 288 | elt = array1d.fromXlOper!T(allocator); 289 | } 290 | 291 | return ret; 292 | } 293 | 294 | // apply a function to an oper of type xltypeMulti 295 | // the function must take a boolean value indicating if the cell value 296 | // is to be converted or not, the row index, the column index, 297 | // and a reference to the cell value itself 298 | private void apply(T, alias F)(ref XLOPER12 oper) 299 | in(from!"xlld.conv.misc".stripMemoryBitmask(oper.xltype) == 300 | from!"xlld.sdk.xlcall".XlType.xltypeMulti) 301 | { 302 | import xlld.sdk.xlcall: XlType; 303 | import xlld.func.xl: coerce, free; 304 | import xlld.any: Any; 305 | import xlld.conv.misc: stripMemoryBitmask; 306 | version(unittest) import xlld.test.util: gNumXlAllocated, gNumXlFree; 307 | 308 | // @trusted due to the contract 309 | const rows = () @trusted { return oper.val.array.rows; }(); 310 | const cols = () @trusted { return oper.val.array.columns; }(); 311 | auto values = () @trusted { return oper.val.array.lparray[0 .. (rows * cols)]; }(); 312 | 313 | foreach(const row; 0 .. rows) { 314 | foreach(const col; 0 .. cols) { 315 | 316 | auto cellVal = coerce(&values[row * cols + col]); 317 | 318 | // Issue 22's unittest ends up coercing more than xlld.test.util can handle 319 | // so we undo the side-effect here 320 | version(unittest) --gNumXlAllocated; // ignore this for testing 321 | 322 | scope(exit) { 323 | free(&cellVal); 324 | // see comment above about gNumXlCoerce 325 | version(unittest) --gNumXlFree; 326 | } 327 | 328 | const isExpectedType = cellVal.xltype.stripMemoryBitmask == dlangToXlOperType!T.Type; 329 | const isConvertibleToDouble = 330 | cellVal.xltype == XlType.xltypeNum && 331 | (dlangToXlOperType!T.Type == XlType.xltypeStr || dlangToXlOperType!T.Type == XlType.xltypeInt); 332 | const isAny = is(Unqual!T == Any); 333 | 334 | const shouldConvert = isExpectedType || isConvertibleToDouble || isAny; 335 | 336 | F(shouldConvert, row, col, cellVal); 337 | } 338 | } 339 | } 340 | 341 | 342 | __gshared immutable fromXlOperDateTimeTypeException = new Exception("Wrong type for fromXlOper!DateTime"); 343 | 344 | /// 345 | T fromXlOper(T, A)(XLOPER12* oper, ref A allocator) 346 | if(is(Unqual!T == DateTime)) 347 | { 348 | import xlld.conv.misc: stripMemoryBitmask; 349 | import xlld.sdk.framework: Excel12f; 350 | import xlld.sdk.xlcall: XlType, xlretSuccess, xlfYear, xlfMonth, xlfDay, xlfHour, xlfMinute, xlfSecond; 351 | 352 | if(oper.xltype.stripMemoryBitmask != XlType.xltypeNum) 353 | throw fromXlOperDateTimeTypeException; 354 | 355 | XLOPER12 ret; 356 | 357 | auto get(int fn) @trusted { 358 | const code = Excel12f(fn, &ret, oper); 359 | if(code != xlretSuccess) 360 | throw new Exception("Error calling xlf datetime part function"); 361 | 362 | // for some reason the Excel API returns doubles 363 | assert(ret.xltype == XlType.xltypeNum, "xlf datetime part return not xltypeNum"); 364 | return cast(int)ret.val.num; 365 | } 366 | 367 | return T(get(xlfYear), get(xlfMonth), get(xlfDay), 368 | get(xlfHour), get(xlfMinute), get(xlfSecond)); 369 | } 370 | 371 | T fromXlOper(T, A)(XLOPER12* oper, ref A allocator) if(is(Unqual!T == bool)) { 372 | 373 | import xlld.sdk.xlcall: XlType; 374 | import std.uni: toLower; 375 | 376 | if(oper.xltype == XlType.xltypeStr) { 377 | return oper.fromXlOper!string(allocator).toLower == "true"; 378 | } 379 | 380 | return cast(T)oper.val.bool_; 381 | } 382 | 383 | T fromXlOper(T, A)(XLOPER12* oper, ref A allocator) if(is(T == enum)) { 384 | import xlld.conv.misc: stripMemoryBitmask; 385 | import xlld.sdk.xlcall: XlType; 386 | import std.conv: to; 387 | import std.traits: fullyQualifiedName; 388 | 389 | static immutable typeException = new Exception("Wrong type for fromXlOper!" ~ T.stringof); 390 | if(oper.xltype.stripMemoryBitmask != XlType.xltypeStr) 391 | throw typeException; 392 | 393 | enum name = fullyQualifiedName!T; 394 | auto str = oper.fromXlOper!string(allocator); 395 | 396 | return () @trusted { 397 | assert(gToEnumMutex !is null, "gToEnumMutex is null"); 398 | 399 | gToEnumMutex.lock_nothrow; 400 | scope(exit) gToEnumMutex.unlock_nothrow; 401 | 402 | return name in gToEnumConversions 403 | ? cast(T) gToEnumConversions[name](str) 404 | : str.to!T; 405 | }(); 406 | } 407 | 408 | 409 | // convert a user-defined struct 410 | T fromXlOper(T, A)(XLOPER12* oper, ref A allocator) 411 | if(isRegularStruct!T) 412 | { 413 | import xlld.conv.misc: stripMemoryBitmask; 414 | import xlld.sdk.xlcall: XlType; 415 | import nogc: enforce; 416 | 417 | static immutable multiException = new Exception("Can only convert arrays to structs. Must be either 1xN, Nx1, 2xN or Nx2"); 418 | if(oper.xltype.stripMemoryBitmask != XlType.xltypeMulti) 419 | throw multiException; 420 | 421 | // @trusted due to check above 422 | const rows = () @trusted { return oper.val.array.rows; }(); 423 | const cols = () @trusted { return oper.val.array.columns; }(); 424 | const length = rows * cols; 425 | 426 | if(rows == 1 || cols == 1) 427 | enforce(length == T.tupleof.length, 428 | "1D array length must match number of members in ", T.stringof, 429 | ". Expected ", T.tupleof.length, ", got ", length); 430 | else 431 | enforce((rows == 2 && cols == T.tupleof.length) || 432 | (rows == T.tupleof.length && cols == 2), 433 | "2D array must be 2x", T.tupleof.length, " or ", T.tupleof.length, "x2 for ", T.stringof); 434 | T ret; 435 | 436 | size_t ptrIndex(size_t i) { 437 | 438 | if(rows == 1 || cols == 1) 439 | return i; 440 | 441 | // ignore column headers 442 | if(rows == 2) 443 | return i + cols; 444 | 445 | // ignore row headers (+ 1) 446 | if(cols == 2) 447 | return i * 2 + 1; 448 | 449 | assert(0); 450 | } 451 | 452 | static immutable wrongTypeException = new Exception("Wrong type converting oper to " ~ T.stringof); 453 | 454 | foreach(i, ref member; ret.tupleof) { 455 | try { 456 | auto elt = () @trusted { return oper.val.array.lparray[ptrIndex(i)]; }(); 457 | member = elt.fromXlOper!(typeof(member))(allocator); 458 | } catch(Exception _) 459 | throw wrongTypeException; 460 | } 461 | 462 | return ret; 463 | } 464 | 465 | 466 | /// 467 | auto fromXlOperCoerce(T)(XLOPER12* val) { 468 | return fromXlOperCoerce(*val); 469 | } 470 | 471 | /// 472 | auto fromXlOperCoerce(T, A)(XLOPER12* val, auto ref A allocator) { 473 | return fromXlOperCoerce!T(*val, allocator); 474 | } 475 | 476 | 477 | /// 478 | auto fromXlOperCoerce(T)(ref XLOPER12 val) { 479 | import xlld.memorymanager: allocator; 480 | return fromXlOperCoerce!T(val, allocator); 481 | } 482 | 483 | 484 | /// 485 | auto fromXlOperCoerce(T, A)(ref XLOPER12 val, auto ref A allocator) { 486 | import xlld.func.xl: coerce, free; 487 | 488 | auto coerced = coerce(&val); 489 | scope(exit) free(&coerced); 490 | 491 | return coerced.fromXlOper!T(allocator); 492 | } 493 | 494 | 495 | private enum invalidXlOperType = 0xdeadbeef; 496 | 497 | /** 498 | Maps a D type to two integer xltypes from XLOPER12. 499 | InputType is the type actually passed in by the spreadsheet, 500 | whilst Type is the Type that it gets coerced to. 501 | */ 502 | template dlangToXlOperType(T) { 503 | import xlld.sdk.xlcall: XlType; 504 | static if(is(Unqual!T == string[]) || is(Unqual!T == string[][]) || 505 | is(Unqual!T == double[]) || is(Unqual!T == double[][]) || 506 | is(Unqual!T == int[]) || is(Unqual!T == int[][]) || 507 | is(Unqual!T == DateTime[]) || is(Unqual!T == DateTime[][])) 508 | { 509 | enum InputType = XlType.xltypeSRef; 510 | enum Type = XlType.xltypeMulti; 511 | } else static if(is(Unqual!T == double)) { 512 | enum InputType = XlType.xltypeNum; 513 | enum Type = XlType.xltypeNum; 514 | } else static if(is(Unqual!T == int)) { 515 | enum InputType = XlType.xltypeInt; 516 | enum Type = XlType.xltypeInt; 517 | } else static if(is(Unqual!T == string)) { 518 | enum InputType = XlType.xltypeStr; 519 | enum Type = XlType.xltypeStr; 520 | } else static if(is(Unqual!T == DateTime)) { 521 | enum InputType = XlType.xltypeNum; 522 | enum Type = XlType.xltypeNum; 523 | } else { 524 | enum InputType = invalidXlOperType; 525 | enum Type = invalidXlOperType; 526 | } 527 | } 528 | 529 | /** 530 | If an oper is of multi type 531 | */ 532 | bool isMulti(ref const(XLOPER12) oper) @safe @nogc pure nothrow { 533 | import xlld.conv.misc: stripMemoryBitmask; 534 | import xlld.sdk.xlcall: XlType; 535 | 536 | return stripMemoryBitmask(oper.xltype) == XlType.xltypeMulti; 537 | } 538 | 539 | /** 540 | Register a custom conversion from string to an enum type. This function will 541 | be called before converting any enum arguments to be passed to a wrapped 542 | D function. 543 | */ 544 | void registerConversionTo(T)(ToEnumConversionFunction func) @trusted { 545 | import std.traits: fullyQualifiedName; 546 | 547 | assert(gToEnumMutex !is null, "gToEnumMutex is null"); 548 | 549 | gToEnumMutex.lock_nothrow; 550 | scope(exit)gToEnumMutex.unlock_nothrow; 551 | 552 | gToEnumConversions[fullyQualifiedName!T] = func; 553 | } 554 | 555 | void unregisterConversionTo(T)() @trusted { 556 | import std.traits: fullyQualifiedName; 557 | 558 | assert(gToEnumMutex !is null, "gToEnumMutex is null"); 559 | 560 | gToEnumMutex.lock_nothrow; 561 | scope(exit)gToEnumMutex.unlock_nothrow; 562 | 563 | gToEnumConversions.remove(fullyQualifiedName!T); 564 | } 565 | -------------------------------------------------------------------------------- /source/xlld/conv/misc.d: -------------------------------------------------------------------------------- 1 | /** 2 | Miscelleanous functions that assist in type conversions. 3 | */ 4 | module xlld.conv.misc; 5 | 6 | import xlld.from; 7 | 8 | /// 9 | template isUserStruct(T) { 10 | import xlld.any: Any; 11 | import xlld.sdk.xlcall: XLOPER12; 12 | import std.datetime: DateTime; 13 | import std.typecons: Tuple; 14 | import std.traits: Unqual; 15 | import std.range.primitives: isInputRange; 16 | 17 | enum isUserStruct = 18 | is(T == struct) 19 | && !is(Unqual!T == Any) 20 | && !is(Unqual!T == DateTime) 21 | && !is(Unqual!T: Tuple!A, A...) 22 | && !is(Unqual!T == XLOPER12) 23 | && !isVector!T 24 | && !isInputRange!T 25 | ; 26 | } 27 | 28 | template isVector(T) { 29 | version(Have_automem) { 30 | import automem.vector: Vector; 31 | import std.traits: Unqual; 32 | enum isVector = is(Unqual!T == Vector!(E, A), E, A); 33 | } else 34 | enum isVector = false; 35 | } 36 | 37 | 38 | template isSomeString(T) { 39 | import std.traits: isSomeString_ = isSomeString, Unqual; 40 | enum isSomeString = 41 | isSomeString_!T 42 | || (isVector!T && is(Unqual!(typeof(T.init[0])) == char)) 43 | ; 44 | } 45 | 46 | 47 | template isTuple(T) { 48 | import std.typecons: Tuple; 49 | import std.traits: Unqual; 50 | enum isTuple = is(Unqual!T: Tuple!A, A...); 51 | } 52 | 53 | 54 | template isSequence(T) { 55 | import std.traits: isArray; 56 | enum isSequence = isArray!T || isTuple!T || isVector!T; 57 | } 58 | 59 | /// 60 | __gshared immutable gDupMemoryException = new Exception("Failed to allocate memory in dup"); 61 | 62 | 63 | /** 64 | Deep copy of an oper 65 | */ 66 | from!"xlld.sdk.xlcall".XLOPER12 dup(A)(from!"xlld.sdk.xlcall".XLOPER12 oper, ref A allocator) @safe { 67 | 68 | import xlld.sdk.xlcall: XLOPER12, XlType; 69 | import std.experimental.allocator: makeArray; 70 | 71 | XLOPER12 ret; 72 | 73 | ret.xltype = oper.xltype; 74 | 75 | switch(stripMemoryBitmask(oper.xltype)) with(XlType) { 76 | 77 | default: 78 | ret = oper; 79 | return ret; 80 | 81 | case xltypeStr: 82 | const length = operStringLength(oper) + 1; 83 | 84 | () @trusted { 85 | ret.val.str = allocator.makeArray!wchar(length).ptr; 86 | if(ret.val.str is null) 87 | throw gDupMemoryException; 88 | }(); 89 | 90 | () @trusted { ret.val.str[0 .. length] = oper.val.str[0 .. length]; }(); 91 | return ret; 92 | 93 | case xltypeMulti: 94 | () @trusted { 95 | ret.val.array.rows = oper.val.array.rows; 96 | ret.val.array.columns = oper.val.array.columns; 97 | const length = oper.val.array.rows * oper.val.array.columns; 98 | ret.val.array.lparray = allocator.makeArray!XLOPER12(length).ptr; 99 | 100 | if(ret.val.array.lparray is null) 101 | throw gDupMemoryException; 102 | 103 | foreach(i; 0 .. length) { 104 | ret.val.array.lparray[i] = oper.val.array.lparray[i].dup(allocator); 105 | } 106 | }(); 107 | 108 | return ret; 109 | } 110 | 111 | assert(0); 112 | } 113 | 114 | 115 | 116 | from!"xlld.sdk.xlcall".XlType stripMemoryBitmask(in from!"xlld.sdk.xlcall".XlType type) @safe @nogc pure nothrow { 117 | import xlld.sdk.xlcall: XlType, xlbitXLFree, xlbitDLLFree; 118 | return cast(XlType)(type & ~(xlbitXLFree | xlbitDLLFree)); 119 | } 120 | 121 | /// 122 | ushort operStringLength(T)(in T value) { 123 | import xlld.sdk.xlcall: XlType; 124 | import nogc.exception: enforce; 125 | 126 | // nogc.enforce is @system 127 | () @trusted { 128 | enforce(value.xltype.stripMemoryBitmask == XlType.xltypeStr, 129 | "Cannot calculate string length for oper of type ", cast(int) value.xltype); 130 | }(); 131 | 132 | // @trusted because of the `enforce` above 133 | return () @trusted { return cast(ushort) value.val.str[0]; }(); 134 | } 135 | 136 | 137 | // can't be pure because to!double isn't pure 138 | string toString(in from!"xlld.sdk.xlcall".XLOPER12 oper) @safe { 139 | import xlld.sdk.xlcall: XlType; 140 | import xlld.conv.misc: stripMemoryBitmask; 141 | import std.conv: text; 142 | import std.format: format; 143 | 144 | string ret; 145 | 146 | ret ~= "XLOPER12("; 147 | switch(stripMemoryBitmask(oper.xltype)) { 148 | default: 149 | ret ~= oper.xltype.stripMemoryBitmask.text; 150 | break; 151 | 152 | case XlType.xltypeSRef: 153 | import xlld.func.xl: Coerced; 154 | auto coerced = () @trusted { return Coerced(&oper); }(); 155 | return "SRef[ " ~ coerced.toString ~ " ]"; 156 | 157 | case XlType.xltypeNum: 158 | ret ~= format!"%.6f"(oper.val.num); 159 | break; 160 | 161 | case XlType.xltypeStr: 162 | ret ~= `"`; 163 | () @trusted { 164 | const ulong length = oper.val.str[0]; 165 | ret ~= text(oper.val.str[1 .. 1 + length]); 166 | }(); 167 | ret ~= `"`; 168 | break; 169 | 170 | case XlType.xltypeInt: 171 | ret ~= text(oper.val.w); 172 | break; 173 | 174 | case XlType.xltypeBool: 175 | ret ~= text(oper.val.bool_); 176 | break; 177 | 178 | case XlType.xltypeErr: 179 | ret ~= "ERROR"; 180 | break; 181 | 182 | case XlType.xltypeBigData: 183 | () @trusted { 184 | ret ~= "BIG("; 185 | ret ~= text(oper.val.bigdata.h.hdata); 186 | ret ~= ", "; 187 | ret ~= text(oper.val.bigdata.cbData); 188 | ret ~= ")"; 189 | }(); 190 | } 191 | ret ~= ")"; 192 | return ret; 193 | } 194 | 195 | /// 196 | __gshared immutable multiMemoryException = new Exception("Failed to allocate memory for multi oper"); 197 | 198 | /// Returns an array XLOPER12 199 | from!"xlld.sdk.xlcall".XLOPER12 multi(A)(int rows, int cols, ref A allocator) @trusted { 200 | import xlld.sdk.xlcall: XLOPER12, XlType; 201 | 202 | auto ret = XLOPER12(); 203 | 204 | ret.xltype = XlType.xltypeMulti; 205 | () @trusted { 206 | ret.val.array.rows = rows; 207 | ret.val.array.columns = cols; 208 | }(); 209 | 210 | auto slice = allocator.allocate(rows * cols * ret.sizeof); 211 | ret.val.array.lparray = () @trusted { return cast(XLOPER12*) slice.ptr; }(); 212 | 213 | if(ret.val.array.lparray is null) 214 | throw multiMemoryException; 215 | 216 | return ret; 217 | } 218 | -------------------------------------------------------------------------------- /source/xlld/conv/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Conversions from/to D types and XLOPER12 3 | */ 4 | module xlld.conv; 5 | 6 | public import xlld.conv.from; 7 | public import xlld.conv.to; 8 | public import xlld.conv.misc; 9 | -------------------------------------------------------------------------------- /source/xlld/conv/to.d: -------------------------------------------------------------------------------- 1 | /** 2 | Conversions from D types to XLOPER12 3 | */ 4 | module xlld.conv.to; 5 | 6 | 7 | import xlld.from; 8 | import xlld.conv.misc: isUserStruct, isVector, isSomeString, isTuple; 9 | import xlld.sdk.xlcall: XLOPER12, XlType; 10 | import xlld.any: Any; 11 | import xlld.wrap.wrap: isWantedType; 12 | import std.traits: isIntegral, Unqual; 13 | import std.datetime: DateTime; 14 | import std.range.primitives: isInputRange; 15 | 16 | 17 | alias FromEnumConversionFunction = string delegate(int) @safe; 18 | package __gshared FromEnumConversionFunction[string] gFromEnumConversions; 19 | shared from!"core.sync.mutex".Mutex gFromEnumMutex; 20 | 21 | 22 | /// 23 | XLOPER12 toXlOper(T)(T val) { 24 | import std.experimental.allocator: theAllocator; 25 | return toXlOper(val, theAllocator); 26 | } 27 | 28 | 29 | /// 30 | XLOPER12 toXlOper(T, A)(in T val, ref A allocator) if(isIntegral!T) { 31 | import xlld.sdk.xlcall: XlType; 32 | 33 | auto ret = XLOPER12(); 34 | ret.xltype = XlType.xltypeInt; 35 | ret.val.w = val; 36 | 37 | return ret; 38 | } 39 | 40 | 41 | /// 42 | XLOPER12 toXlOper(T, A)(in T val, ref A allocator) if(is(Unqual!T == double)) { 43 | import xlld.sdk.xlcall: XlType; 44 | 45 | auto ret = XLOPER12(); 46 | ret.xltype = XlType.xltypeNum; 47 | ret.val.num = val; 48 | 49 | return ret; 50 | } 51 | 52 | 53 | /// 54 | __gshared immutable toXlOperMemoryException = new Exception("Failed to allocate memory for string oper"); 55 | 56 | 57 | /// 58 | XLOPER12 toXlOper(T, A)(in T val, ref A allocator) 59 | if(isSomeString!T) 60 | { 61 | import xlld.sdk.xlcall: XCHAR; 62 | import std.utf: byWchar; 63 | 64 | const numBytes = numOperStringBytes(val); 65 | auto wval = () @trusted { return cast(wchar[]) allocator.allocate(numBytes); }(); 66 | if(&wval[0] is null) 67 | throw toXlOperMemoryException; 68 | 69 | int i = 1; 70 | 71 | static if(__traits(compiles, val.range)) 72 | auto range = val.range; 73 | else 74 | alias range = val; 75 | 76 | foreach(ch; range.byWchar) { 77 | wval[i++] = ch; 78 | } 79 | 80 | wval[0] = cast(ushort)(i - 1); 81 | 82 | auto ret = XLOPER12(); 83 | ret.xltype = XlType.xltypeStr; 84 | () @trusted { ret.val.str = cast(XCHAR*)&wval[0]; }(); 85 | 86 | return ret; 87 | } 88 | 89 | 90 | 91 | /// the number of bytes required to store `str` as an XLOPER12 string 92 | package size_t numOperStringBytes(T)(in T str) 93 | if(isSomeString!T) 94 | { 95 | // XLOPER12 strings are wide strings where index 0 is the length 96 | // and [1 .. $] is the actual string 97 | return cast(typeof(return)) ((str.length + 1) * wchar.sizeof); 98 | } 99 | 100 | 101 | private template hasLength(R) { 102 | enum hasLength = is(typeof( 103 | { 104 | import std.traits: isIntegral; 105 | auto l = R.init.length; 106 | static assert(isIntegral!(typeof(l))); 107 | })); 108 | } 109 | 110 | /// If we can get the length of R and R is an input range 111 | private template isLengthRange(R) { 112 | import xlld.conv.misc: isVector; 113 | import std.range.primitives: isInputRange, isForwardRange; 114 | 115 | enum isLengthRange = 116 | isForwardRange!R 117 | || (isInputRange!R && hasLength!R) 118 | // Vector is no longer a range itself, but we pretend it is 119 | || (isVector!R) 120 | ; 121 | } 122 | 123 | /// If it's a 1D range 124 | private template isRange1D(T) { 125 | import std.range.primitives: ElementType; 126 | 127 | enum isRange1D = 128 | isLengthRange!T 129 | // the element type has to itself not be a range, which in our case 130 | // means a "proper" scalar or a string of some sort 131 | && (!isLengthRange!(ElementType!T) || isSomeString!(ElementType!T)) 132 | && !isSomeString!T 133 | ; 134 | } 135 | 136 | ///If it's a 2D range 137 | private template isRange2D(T) { 138 | import std.range.primitives: ElementType; 139 | enum isRange2D = isLengthRange!T && isRange1D!(ElementType!T); 140 | } 141 | 142 | 143 | XLOPER12 toXlOper(T, A)(auto ref T vector, ref A allocator) 144 | if(isVector!T && !(is(Unqual!(typeof(vector[0])) == char))) 145 | { 146 | return vector.range.toXlOper(allocator); 147 | } 148 | 149 | 150 | XLOPER12 toXlOper(T, A)(T range, ref A allocator) 151 | if(isRange1D!T) 152 | { 153 | import std.range: only; 154 | return only(range).toXlOper(allocator); 155 | } 156 | 157 | XLOPER12 toXlOper(T, A)(T range, ref A allocator) 158 | if(isRange2D!T) 159 | { 160 | import xlld.conv.misc: multi; 161 | import std.range: walkLength; 162 | import std.array: save, front; 163 | import std.algorithm: any; 164 | import std.range.primitives: isForwardRange; 165 | 166 | static __gshared immutable shapeException = new Exception("# of columns must all be the same and aren't"); 167 | 168 | const rows = cast(int) range.rangeLength; 169 | const frontLength = range.front.rangeLength; 170 | 171 | static if(isForwardRange!T) 172 | auto checkRange = range.save; 173 | else 174 | alias checkRange = range; 175 | 176 | if(checkRange.any!(r => r.rangeLength != frontLength)) 177 | throw shapeException; 178 | 179 | const cols = cast(int) range.front.rangeLength; 180 | auto ret = multi(rows, cols, allocator); 181 | auto opers = () @trusted { return ret.val.array.lparray[0 .. rows*cols]; }(); 182 | 183 | int i = 0; 184 | foreach(ref subRange; range) { 185 | foreach(ref elt; subRange) { 186 | opers[i++] = elt.toXlOper(allocator); 187 | } 188 | } 189 | 190 | return ret; 191 | } 192 | 193 | private auto rangeLength(R)(auto ref R range) 194 | if(isInputRange!R || isVector!R) 195 | { 196 | import std.range.primitives: isForwardRange; 197 | 198 | static if(hasLength!R) 199 | return range.length; 200 | else static if(isForwardRange!R) { 201 | import std.range.primitives: walkLength; 202 | import std.array: save; 203 | return range.save.walkLength; 204 | } else 205 | static assert(false, "Can't get length for " ~ R.stringof); 206 | } 207 | 208 | 209 | /// 210 | XLOPER12 toXlOper(T, A)(T value, ref A allocator) if(is(Unqual!T == Any)) { 211 | import xlld.conv.misc: dup; 212 | return value.dup(allocator); 213 | } 214 | 215 | 216 | XLOPER12 toXlOper(T, A)(T value, ref A allocator, in string file = __FILE__, in size_t line = __LINE__) 217 | @safe if(is(Unqual!T == DateTime)) 218 | { 219 | import xlld.sdk.framework: Excel12f; 220 | import xlld.sdk.xlcall: xlfDate, xlfTime, xlretSuccess; 221 | 222 | XLOPER12 ret, date, time; 223 | 224 | auto year = value.year.toXlOper(allocator); 225 | auto month = () @trusted { return toXlOper(cast(int)value.month, allocator); }(); 226 | auto day = value.day.toXlOper(allocator); 227 | 228 | () @trusted { 229 | assert(year.xltype == XlType.xltypeInt); 230 | assert(month.xltype == XlType.xltypeInt); 231 | assert(day.xltype == XlType.xltypeInt); 232 | }(); 233 | 234 | const dateCode = () @trusted { return Excel12f(xlfDate, &date, &year, &month, &day); }(); 235 | if(dateCode != xlretSuccess) 236 | throw new Exception("Error calling xlfDate", file, line); 237 | () @trusted { assert(date.xltype == XlType.xltypeNum); }(); 238 | 239 | auto hour = value.hour.toXlOper(allocator); 240 | auto minute = value.minute.toXlOper(allocator); 241 | auto second = value.second.toXlOper(allocator); 242 | 243 | const timeCode = () @trusted { return Excel12f(xlfTime, &time, &hour, &minute, &second); }(); 244 | if(timeCode != xlretSuccess) 245 | throw new Exception("Error calling xlfTime", file, line); 246 | 247 | () @trusted { assert(time.xltype == XlType.xltypeNum); }(); 248 | 249 | ret.xltype = XlType.xltypeNum; 250 | ret.val.num = date.val.num + time.val.num; 251 | return ret; 252 | } 253 | 254 | 255 | XLOPER12 toXlOper(T, A)(T value, ref A allocator) if(is(Unqual!T == bool)) { 256 | import xlld.sdk.xlcall: XlType; 257 | XLOPER12 ret; 258 | ret.xltype = XlType.xltypeBool; 259 | ret.val.bool_ = cast(typeof(ret.val.bool_)) value; 260 | return ret; 261 | } 262 | 263 | /** 264 | Register a custom conversion from enum (going through integer) to a string. 265 | This function will be called to convert enum return values from wrapped 266 | D functions into strings in Excel. 267 | */ 268 | void registerConversionFrom(T)(FromEnumConversionFunction func) @trusted { 269 | import std.traits: fullyQualifiedName; 270 | 271 | assert(gFromEnumMutex !is null, "gFromEnumMutex is null"); 272 | 273 | gFromEnumMutex.lock_nothrow; 274 | scope(exit)gFromEnumMutex.unlock_nothrow; 275 | 276 | gFromEnumConversions[fullyQualifiedName!T] = func; 277 | } 278 | 279 | 280 | void unregisterConversionFrom(T)() @trusted { 281 | import std.traits: fullyQualifiedName; 282 | 283 | assert(gFromEnumMutex !is null, "gFromEnumMutex is null"); 284 | 285 | gFromEnumMutex.lock_nothrow; 286 | scope(exit)gFromEnumMutex.unlock_nothrow; 287 | 288 | gFromEnumConversions.remove(fullyQualifiedName!T); 289 | } 290 | 291 | 292 | 293 | 294 | XLOPER12 toXlOper(T, A)(T value, ref A allocator) @trusted if(is(T == enum)) { 295 | 296 | import std.conv: text; 297 | import std.traits: fullyQualifiedName; 298 | import core.memory: GC; 299 | 300 | enum name = fullyQualifiedName!T; 301 | 302 | { 303 | assert(gFromEnumMutex !is null, "gFromEnumMutex is null"); 304 | 305 | gFromEnumMutex.lock_nothrow; 306 | scope(exit) gFromEnumMutex.unlock_nothrow; 307 | 308 | if(name in gFromEnumConversions) 309 | return gFromEnumConversions[name](value).toXlOper(allocator); 310 | } 311 | 312 | auto str = text(value); 313 | auto ret = str.toXlOper(allocator); 314 | () @trusted { GC.free(cast(void*)str.ptr); }(); 315 | 316 | return ret; 317 | } 318 | 319 | XLOPER12 toXlOper(T, A)(T value, ref A allocator) 320 | if(isUserStruct!T) 321 | { 322 | import std.conv: text; 323 | import core.memory: GC; 324 | 325 | auto str = text(value); 326 | 327 | auto ret = str.toXlOper(allocator); 328 | () @trusted { GC.free(cast(void*)str.ptr); }(); 329 | 330 | return ret; 331 | } 332 | 333 | XLOPER12 toXlOper(T, A)(T value, ref A allocator) 334 | if(isTuple!T) 335 | { 336 | import xlld.conv.misc: multi; 337 | import std.experimental.allocator: makeArray; 338 | 339 | const rows = 1; 340 | const cols = value.length; 341 | 342 | auto ret = multi(rows, cols, allocator); 343 | auto opers = () @trusted { return ret.val.array.lparray[0 .. rows*cols]; }(); 344 | 345 | static foreach(i; 0 .. value.length) { 346 | opers[i] = value[i].toXlOper(allocator); 347 | } 348 | 349 | return ret; 350 | } 351 | 352 | 353 | XLOPER12 toXlOper(T, A)(T value, ref A allocator) if(is(Unqual!T == XLOPER12)) 354 | { 355 | return value; 356 | } 357 | 358 | 359 | /** 360 | creates an XLOPER12 that can be returned to Excel which 361 | will be freed by Excel itself 362 | */ 363 | XLOPER12 toAutoFreeOper(T)(T value) { 364 | import xlld.memorymanager: autoFreeAllocator; 365 | import xlld.sdk.xlcall: XlType; 366 | 367 | auto result = value.toXlOper(autoFreeAllocator); 368 | result.xltype |= XlType.xlbitDLLFree; 369 | return result; 370 | } 371 | -------------------------------------------------------------------------------- /source/xlld/dummy.d: -------------------------------------------------------------------------------- 1 | /** 2 | Deals with linking issues such as on Windows without having to link to the 3 | real implementations or dependent packages' unittest builds. 4 | Only for testing. 5 | */ 6 | module xlld.dummy; 7 | 8 | 9 | 10 | version(XllDummyGetter) { 11 | // to be able to link 12 | extern(C) auto getWorksheetFunctions() @safe pure nothrow { 13 | import xlld: WorksheetFunction; 14 | WorksheetFunction[] ret; 15 | return ret; 16 | } 17 | } 18 | 19 | 20 | version(testingExcelD) 21 | enum useDummy = true; 22 | else version(exceldDef) 23 | enum useDummy = true; 24 | else 25 | enum useDummy = false; 26 | 27 | version(Windows): 28 | static if(useDummy) { 29 | 30 | import xlld.sdk.xlcall; 31 | 32 | extern(System) int Excel4v(int xlfn, LPXLOPER operRes, int count, LPXLOPER* opers) { 33 | return 0; 34 | } 35 | 36 | extern(System) int Excel4(int xlfn, LPXLOPER operRes, int count,... ) { 37 | return 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/xlld/from.d: -------------------------------------------------------------------------------- 1 | /** 2 | Utility to avoid top-level imports 3 | */ 4 | module xlld.from; 5 | 6 | /** 7 | Local imports everywhere. 8 | */ 9 | template from(string moduleName) { 10 | mixin("import from = " ~ moduleName ~ ";"); 11 | } 12 | -------------------------------------------------------------------------------- /source/xlld/func/framework.d: -------------------------------------------------------------------------------- 1 | /** 2 | Easier calling of Excel12f 3 | */ 4 | module xlld.func.framework; 5 | 6 | /// 7 | __gshared immutable excel12Exception = new Exception("Error calling Excel12f"); 8 | 9 | /** 10 | D version of Excel12f. "D version" in the sense that the types 11 | are all D types. Avoids having to manually convert to XLOPER12. 12 | e.g. excel12(xlfFoo, 1.0, 2.0); 13 | 14 | Returns a value of type T to specified at compile time which 15 | must be freed with xlld.memorymanager: autoFreeAllocator 16 | */ 17 | T excel12(T, A...)(int xlfn, auto ref A args) @trusted { 18 | import xlld.memorymanager: gTempAllocator, autoFreeAllocator; 19 | import xlld.conv.from: fromXlOper; 20 | import xlld.conv: toXlOper; 21 | import xlld.sdk.xlcall: XLOPER12, LPXLOPER12, xlretSuccess; 22 | import xlld.sdk.framework: Excel12f; 23 | import std.meta: allSatisfy; 24 | import std.traits: Unqual; 25 | import std.experimental.allocator.gc_allocator: GCAllocator; 26 | import std.experimental.allocator.mallocator: Mallocator; 27 | 28 | // doubles never need the allocator anyway, so we use Mallocator 29 | // to guarantee @nogc when needed 30 | enum nogc(T) = is(Unqual!T == double); 31 | static if(allSatisfy!(nogc, A)) 32 | alias allocator = Mallocator.instance; 33 | else 34 | alias allocator = GCAllocator.instance; 35 | 36 | XLOPER12[A.length] operArgs; 37 | LPXLOPER12[A.length] operArgPtrs; 38 | 39 | foreach(i, _; A) { 40 | operArgs[i] = args[i].toXlOper(allocator); 41 | operArgPtrs[i] = &operArgs[i]; 42 | } 43 | 44 | XLOPER12 result; 45 | if(Excel12f(xlfn, &result, operArgPtrs) != xlretSuccess) 46 | throw excel12Exception; 47 | 48 | return result.fromXlOper!T(autoFreeAllocator); 49 | } 50 | 51 | /** 52 | Version of excel12 that avoids automatic memory management 53 | by asking the caller to supply a compile-time function 54 | to call on the result. 55 | */ 56 | auto excel12Then(alias F, A...)(int xlfn, auto ref A args) { 57 | import std.traits: Parameters, Unqual, isArray, ReturnType; 58 | import std.experimental.allocator: dispose; 59 | import xlld.memorymanager: autoFreeAllocator; 60 | 61 | static assert(Parameters!F.length == 1, "Must pass function of one argument"); 62 | alias T = Parameters!F[0]; 63 | auto excelRet = excel12!T(xlfn, args); 64 | static if(is(ReturnType!F == void)) 65 | F(excelRet); 66 | else 67 | auto ret = F(excelRet); 68 | 69 | static if(isArray!T) { 70 | import std.range: ElementType; 71 | alias RealType = Unqual!(ElementType!T)[]; 72 | } else 73 | alias RealType = Unqual!T; 74 | 75 | void freeRet(U)() @trusted { 76 | autoFreeAllocator.dispose(cast(U)excelRet); 77 | } 78 | 79 | static if(__traits(compiles, freeRet!RealType())) 80 | freeRet!RealType(); 81 | 82 | static if(!is(ReturnType!F == void)) 83 | return ret; 84 | } 85 | -------------------------------------------------------------------------------- /source/xlld/func/xl.d: -------------------------------------------------------------------------------- 1 | /** 2 | Wraps calls to xlXXX "functions" via the Excel4/Excel12 functions 3 | */ 4 | module xlld.func.xl; 5 | 6 | import xlld.sdk.xlcall: XLOPER12, LPXLOPER12; 7 | 8 | 9 | /// 10 | XLOPER12 coerce(in LPXLOPER12 oper) nothrow @nogc @trusted { 11 | import xlld.sdk.framework: Excel12f; 12 | import xlld.sdk.xlcall: xlCoerce; 13 | 14 | XLOPER12 coerced; 15 | Excel12f(xlCoerce, &coerced, oper); 16 | return coerced; 17 | } 18 | 19 | /// 20 | void free(scope ref const(XLOPER12) oper) nothrow @nogc @trusted { 21 | free(&oper); 22 | } 23 | 24 | /// 25 | void free(scope const(XLOPER12)* oper) nothrow @nogc @trusted { 26 | import xlld.sdk.framework: Excel12f; 27 | import xlld.sdk.xlcall: xlFree; 28 | 29 | const(XLOPER12)*[1] arg = [oper]; 30 | Excel12f(xlFree, null, arg); 31 | } 32 | 33 | 34 | /// 35 | struct Coerced { 36 | XLOPER12 oper; 37 | private bool coerced; 38 | 39 | alias oper this; 40 | 41 | this(inout(XLOPER12)* oper) @safe @nogc nothrow inout { 42 | this.oper = coerce(oper); 43 | this.coerced = true; 44 | } 45 | 46 | this(inout(XLOPER12) oper) @safe @nogc nothrow inout { 47 | this.oper = () @trusted { return coerce(&oper); }(); 48 | this.coerced = true; 49 | } 50 | 51 | ~this() @safe @nogc nothrow { 52 | if(coerced) free(oper); 53 | } 54 | } 55 | 56 | /** 57 | Automatically calls xlfFree when destroyed 58 | */ 59 | struct ScopedOper { 60 | XLOPER12 oper; 61 | private bool _free; 62 | 63 | alias oper this; 64 | 65 | this(inout(XLOPER12) oper) @safe @nogc nothrow inout { 66 | this.oper = oper; 67 | _free = true; 68 | } 69 | 70 | this(inout(XLOPER12*) oper) @safe @nogc nothrow inout { 71 | this.oper = *oper; 72 | _free = true; 73 | } 74 | 75 | ~this() @safe @nogc nothrow { 76 | if(_free) free(oper); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /source/xlld/func/xlf.d: -------------------------------------------------------------------------------- 1 | /** 2 | This module provides D versions of xlf Excel "functions" 3 | */ 4 | module xlld.func.xlf; 5 | 6 | import xlld.func.framework: excel12; 7 | import xlld.sdk.xlcall: XLOPER12; 8 | 9 | 10 | // should be pure but can't due to calling Excel12 11 | int year(double date) @safe @nogc nothrow { 12 | import xlld.sdk.xlcall: xlfYear; 13 | return datePart(date, xlfYear); 14 | } 15 | 16 | // should be pure but can't due to calling Excel12 17 | int month(double date) @safe @nogc nothrow { 18 | import xlld.sdk.xlcall: xlfMonth; 19 | return datePart(date, xlfMonth); 20 | } 21 | 22 | // should be pure but can't due to calling Excel12 23 | int day(double date) @safe @nogc nothrow { 24 | import xlld.sdk.xlcall: xlfDay; 25 | return datePart(date, xlfDay); 26 | } 27 | 28 | // should be pure but can't due to calling Excel12 29 | int hour(double date) @safe @nogc nothrow { 30 | import xlld.sdk.xlcall: xlfHour; 31 | return datePart(date, xlfHour); 32 | } 33 | 34 | // should be pure but can't due to calling Excel12 35 | int minute(double date) @safe @nogc nothrow { 36 | import xlld.sdk.xlcall: xlfMinute; 37 | return datePart(date, xlfMinute); 38 | } 39 | 40 | // should be pure but can't due to calling Excel12 41 | int second(double date) @safe @nogc nothrow { 42 | import xlld.sdk.xlcall: xlfSecond; 43 | return datePart(date, xlfSecond); 44 | } 45 | 46 | private int datePart(double date, int xlfn) @safe @nogc nothrow { 47 | // the Excel APIs for some reason return a double 48 | try 49 | return cast(int)excel12!double(xlfn, date); 50 | catch(Exception ex) 51 | return 0; 52 | } 53 | 54 | 55 | double date(int year, int month, int day) @safe @nogc nothrow { 56 | import xlld.sdk.xlcall: xlfDate; 57 | try 58 | return excel12!double(xlfDate, year, month, day); 59 | catch(Exception ex) 60 | return 0; 61 | } 62 | 63 | double time(int year, int month, int day) @safe @nogc nothrow { 64 | import xlld.sdk.xlcall: xlfTime; 65 | try 66 | return excel12!double(xlfTime, year, month, day); 67 | catch(Exception ex) 68 | return 0; 69 | } 70 | 71 | int rtd(XLOPER12 comId, 72 | XLOPER12 server, 73 | XLOPER12 topic0 = XLOPER12(), 74 | XLOPER12 topic1 = XLOPER12(), 75 | XLOPER12 topic2 = XLOPER12(), 76 | XLOPER12 topic3 = XLOPER12(), 77 | XLOPER12 topic4 = XLOPER12(), 78 | XLOPER12 topic5 = XLOPER12(), 79 | XLOPER12 topic6 = XLOPER12(), 80 | XLOPER12 topic7 = XLOPER12(), 81 | XLOPER12 topic8 = XLOPER12(), 82 | XLOPER12 topic9 = XLOPER12()) 83 | { 84 | import xlld.sdk.xlcall: xlfRtd; 85 | import xlld.sdk.framework: Excel12f; 86 | 87 | XLOPER12 result; 88 | return Excel12f(xlfRtd, &result, comId, server, 89 | topic0, topic1, topic2, topic3, topic4, topic5, topic6, topic7, topic8, topic9); 90 | } 91 | 92 | __gshared immutable callerException = new Exception("Error calling xlfCaller"); 93 | 94 | /** 95 | Returns the caller. The returned XLOPER12 is documented here: 96 | https://docs.microsoft.com/en-us/office/client-developer/excel/xlfcaller 97 | */ 98 | auto caller() @safe { 99 | import xlld.sdk.xlcall: xlfCaller, xlretSuccess; 100 | import xlld.sdk.framework: Excel12f; 101 | import xlld.func.xl: ScopedOper; 102 | 103 | XLOPER12 result; 104 | () @trusted { 105 | if(Excel12f(xlfCaller, &result) != xlretSuccess) { 106 | throw callerException; 107 | } 108 | }(); 109 | 110 | return ScopedOper(result); 111 | } 112 | 113 | auto callerCell() @safe { 114 | import xlld.sdk.xlcall: XlType; 115 | import xlld.func.xl: coerce, free, Coerced; 116 | 117 | auto oper = caller(); 118 | 119 | if(oper.xltype != XlType.xltypeSRef) 120 | throw new Exception("Caller not a cell"); 121 | 122 | return Coerced(oper); 123 | } 124 | -------------------------------------------------------------------------------- /source/xlld/memorymanager.d: -------------------------------------------------------------------------------- 1 | /** 2 | MemoryManager.D 3 | 4 | Ported from MemoryManager.cpp by Laeeth Isharc 5 | // 6 | // Platform: Microsoft Windows 7 | // 8 | ///*************************************************************************** 9 | */ 10 | module xlld.memorymanager; 11 | 12 | import xlld.sdk.xlcall: XLOPER12, LPXLOPER12; 13 | import xlld.any: Any; 14 | import std.experimental.allocator.building_blocks.allocator_list: AllocatorList; 15 | import std.experimental.allocator.mallocator: Mallocator; 16 | import std.experimental.allocator.building_blocks.region: Region; 17 | import std.algorithm.comparison: max; 18 | import std.traits: isArray; 19 | import std.meta: allSatisfy; 20 | 21 | /// 22 | alias allocator = Mallocator.instance; 23 | /// 24 | alias autoFreeAllocator = Mallocator.instance; 25 | 26 | /// 27 | alias MemoryPool = AllocatorList!((size_t n) => Region!Mallocator(max(n, size_t(1024 * 1024))), Mallocator); 28 | /// 29 | MemoryPool gTempAllocator; 30 | 31 | /// 32 | T[][] makeArray2D(T, A)(ref A allocator, ref XLOPER12 oper) { 33 | import xlld.conv.from: isMulti; 34 | import std.experimental.allocator: makeMultidimensionalArray; 35 | with(oper.val.array) return 36 | isMulti(oper) ? 37 | allocator.makeMultidimensionalArray!T(rows, columns) : 38 | typeof(return).init; 39 | } 40 | 41 | /// the function called by the Excel callback 42 | void autoFree(LPXLOPER12 arg) nothrow { 43 | import xlld.sdk.framework: freeXLOper; 44 | freeXLOper(arg, autoFreeAllocator); 45 | } 46 | 47 | /// 48 | struct AllocatorContext(A) { 49 | /// 50 | A* _allocator_; 51 | 52 | /// 53 | this(ref A allocator) { 54 | _allocator_ = &allocator; 55 | } 56 | 57 | /// 58 | auto any(T)(auto ref T value, in string file = __FILE__, in size_t line = __LINE__) { 59 | import xlld.any: _any = any; 60 | return _any(value, *_allocator_, file, line); 61 | } 62 | 63 | /// 64 | auto fromXlOper(T, U)(U oper) { 65 | import xlld.conv.from: convFromXlOper = fromXlOper; 66 | return convFromXlOper!T(oper, _allocator_); 67 | } 68 | 69 | /// 70 | auto toXlOper(T)(T val) { 71 | import xlld.conv: convToXlOper = toXlOper; 72 | return convToXlOper(val, _allocator_); 73 | } 74 | 75 | version(unittest) { 76 | /// 77 | auto toSRef(T)(T val) { 78 | import xlld.test.util: toSRef_ = toSRef; 79 | return toSRef_(val, _allocator_); 80 | } 81 | } 82 | } 83 | 84 | /// 85 | auto allocatorContext(A)(ref A allocator) { 86 | return AllocatorContext!A(allocator); 87 | } 88 | -------------------------------------------------------------------------------- /source/xlld/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Autowrapping of D functions to be made available to call from Excel, plus 3 | helper functions for accessing Excel functionality. 4 | 5 | For most users, xlld.wrap will be enough. 6 | */ 7 | module xlld; 8 | 9 | public import xlld.sdk.xlcall; 10 | public import xlld.sdk.framework; 11 | public import xlld.wrap; 12 | public import xlld.conv; 13 | public import xlld.func.xlf; 14 | public import xlld.func.xl; 15 | public import xlld.func.framework; 16 | public import xlld.any; 17 | public import xlld.memorymanager: allocatorContext; 18 | version(unittest) public import xlld.test.util; 19 | -------------------------------------------------------------------------------- /source/xlld/sdk/framework.d: -------------------------------------------------------------------------------- 1 | /** 2 | Utility functions for writing XLLs 3 | */ 4 | module xlld.sdk.framework; 5 | 6 | 7 | import xlld.sdk.xlcall; 8 | 9 | 10 | /** 11 | Will free any malloc'd memory associated with the given 12 | LPXLOPER, assuming it has any memory associated with it 13 | 14 | Parameters: 15 | 16 | LPXLOPER pxloper Pointer to the XLOPER whose associated 17 | */ 18 | void freeXLOper(T, A)(T pxloper, ref A allocator) 19 | if(is(T == LPXLOPER) || is(T == LPXLOPER12)) 20 | { 21 | import std.experimental.allocator: dispose; 22 | 23 | switch (pxloper.xltype & ~XlType.xlbitDLLFree) with(XlType) { 24 | case xltypeStr: 25 | if (pxloper.val.str !is null) { 26 | void* bytesPtr = pxloper.val.str; 27 | const numBytes = (pxloper.val.str[0] + 1) * wchar.sizeof; 28 | allocator.dispose(bytesPtr[0 .. numBytes]); 29 | } 30 | break; 31 | case xltypeRef: 32 | if (pxloper.val.mref.lpmref !is null) 33 | allocator.dispose(pxloper.val.mref.lpmref); 34 | break; 35 | case xltypeMulti: 36 | auto cxloper = pxloper.val.array.rows * pxloper.val.array.columns; 37 | const numOpers = cxloper; 38 | if (pxloper.val.array.lparray !is null) 39 | { 40 | auto pxloperFree = pxloper.val.array.lparray; 41 | while (cxloper > 0) 42 | { 43 | freeXLOper(pxloperFree, allocator); 44 | pxloperFree++; 45 | cxloper--; 46 | } 47 | allocator.dispose(pxloper.val.array.lparray[0 .. numOpers]); 48 | } 49 | break; 50 | case xltypeBigData: 51 | if (pxloper.val.bigdata.h.lpbData !is null) 52 | allocator.dispose(pxloper.val.bigdata.h.lpbData); 53 | break; 54 | default: // todo: add error handling 55 | break; 56 | } 57 | } 58 | 59 | @("Free regular XLOPER") 60 | unittest { 61 | import xlld.memorymanager: allocator; 62 | XLOPER oper; 63 | freeXLOper(&oper, allocator); 64 | } 65 | 66 | @("Free XLOPER12") 67 | unittest { 68 | import xlld.memorymanager: allocator; 69 | XLOPER12 oper; 70 | freeXLOper(&oper, allocator); 71 | } 72 | 73 | /** 74 | Wrapper for the Excel12 function that allows passing D arrays 75 | 76 | Purpose: 77 | A fancy wrapper for the Excel12() function. It also 78 | does the following: 79 | 80 | (1) Checks that none of the LPXLOPER12 arguments are 0, 81 | which would indicate that creating a temporary XLOPER12 82 | has failed. In this case, it doesn't call Excel12 83 | but it does print a debug message. 84 | (2) If an error occurs while calling Excel12, 85 | print a useful debug message. 86 | (3) When done, free all temporary memory. 87 | 88 | #1 and #2 require _DEBUG to be defined. 89 | 90 | Parameters: 91 | 92 | int xlfn Function number (xl...) to call 93 | LPXLOPER12 pxResult Pointer to a place to stuff the result, 94 | or 0 if you don't care about the result. 95 | int count Number of arguments 96 | ... (all LPXLOPER12s) - the arguments. 97 | 98 | Returns: 99 | 100 | A return code (Some of the xlret... values, as defined 101 | in XLCALL.H, OR'ed together). 102 | 103 | Comments: 104 | */ 105 | 106 | int Excel12f(int xlfn, LPXLOPER12 pxResult, in LPXLOPER12[] args...) nothrow @nogc 107 | { 108 | import xlld.sdk.xlcallcpp: Excel12v; 109 | import std.algorithm: all; 110 | 111 | assert(args.all!(a => a !is null)); 112 | return Excel12v(xlfn, pxResult, cast(int)args.length, cast(LPXLOPER12*)args.ptr); 113 | } 114 | 115 | /// 116 | int Excel12f(int xlfn, LPXLOPER12 result, scope const(XLOPER12)[] args...) nothrow { 117 | 118 | import std.experimental.allocator: makeArray, dispose; 119 | import std.experimental.allocator.mallocator: Mallocator; 120 | 121 | scope ptrArgs = Mallocator.instance.makeArray!(const(XLOPER12)*)(args.length); 122 | scope(exit) Mallocator.instance.dispose(ptrArgs); 123 | 124 | foreach(i, ref arg; args) 125 | ptrArgs[i] = &arg; 126 | 127 | return Excel12f(xlfn, result, ptrArgs); 128 | } 129 | 130 | /// 131 | int Excel12f(int xlfn, LPXLOPER12 pxResult) nothrow @nogc 132 | { 133 | return Excel12f(xlfn, pxResult, LPXLOPER12[].init); 134 | } 135 | -------------------------------------------------------------------------------- /source/xlld/sdk/xlcallcpp.d: -------------------------------------------------------------------------------- 1 | /** 2 | Microsoft Excel Developer's Toolkit 3 | Version 14.0 4 | 5 | File: SRC\XLCALL.CPP 6 | Description: Code file for Excel callbacks 7 | Platform: Microsoft Windows 8 | 9 | This file defines the entry points 10 | which are used in the Microsoft Excel C API. 11 | 12 | */ 13 | module xlld.sdk.xlcallcpp; 14 | 15 | import xlld.sdk.xlcall: LPXLOPER12; 16 | 17 | /** 18 | Excel 12 entry points backwards compatible with Excel 11 19 | 20 | Excel12 and Excel12v ensure backwards compatibility with Excel 11 21 | and earlier versions. These functions will return xlretFailed when 22 | used to callback into Excel 11 and earlier versions 23 | */ 24 | 25 | 26 | // PASCAL 27 | alias EXCEL12PROC = extern(Windows) int function (int xlfn, int coper, LPXLOPER12 *rgpxloper12, LPXLOPER12 xloper12Res) nothrow @nogc; 28 | 29 | EXCEL12PROC gExcel12; 30 | 31 | void FetchExcel12EntryPt() nothrow @nogc 32 | { 33 | version(Windows) { 34 | import core.sys.windows.windows: GetModuleHandleW, GetProcAddress; 35 | if (gExcel12 is null) 36 | { 37 | auto hmodule = GetModuleHandleW(null); 38 | if (hmodule !is null) 39 | { 40 | enum EXCEL12ENTRYPT="MdCallBack12"; 41 | gExcel12 = cast(EXCEL12PROC) GetProcAddress(hmodule, EXCEL12ENTRYPT); 42 | } 43 | assert(gExcel12 !is null, "No entry point fetched"); 44 | } 45 | } 46 | } 47 | 48 | /** 49 | This function explicitly sets EXCEL12ENTRYPT. 50 | 51 | If the XLL is loaded not by Excel.exe, but by a HPC cluster container DLL, 52 | then GetModuleHandle(null) would return the process EXE module handle. 53 | In that case GetProcAddress would fail, since the process EXE doesn't 54 | export EXCEL12ENTRYPT ( since it's not Excel.exe). 55 | 56 | First try to fetch the known good entry point, 57 | then set the passed in address. 58 | */ 59 | extern(Windows) void SetExcel12EntryPt(EXCEL12PROC gExcel12New) @nogc nothrow 60 | { 61 | if (gExcel12 is null) 62 | { 63 | gExcel12 = gExcel12New; 64 | } 65 | } 66 | 67 | //_cdecl 68 | int Excel12(int xlfn, LPXLOPER12 operRes, LPXLOPER12[] args ...) 69 | { 70 | import core.vararg: va_list; 71 | import xlld.sdk.xlcall: xlretFailed, xlretInvCount; 72 | 73 | enum cxloper12Max=255; 74 | LPXLOPER12[cxloper12Max] rgxloper12; 75 | va_list ap; 76 | int ioper; 77 | int mdRet; 78 | 79 | FetchExcel12EntryPt(); 80 | if (gExcel12 is null) 81 | { 82 | mdRet = xlretFailed; 83 | } 84 | else 85 | { 86 | mdRet = xlretInvCount; 87 | if ((args.length >= 0) && (args.length<= cxloper12Max)) 88 | { 89 | foreach(i,arg;args) 90 | rgxloper12[ioper] = arg; 91 | // original line was mdRet = (gExcel12)(xlfn, count, &rgxloper12[0], operRes); 92 | mdRet = (*gExcel12)(xlfn, cast(int)args.length, rgxloper12.ptr, operRes); 93 | } 94 | } 95 | return(mdRet); 96 | 97 | } 98 | 99 | extern(Windows) int Excel12v(int xlfn, LPXLOPER12 operRes, int count, LPXLOPER12* opers) 100 | nothrow @nogc 101 | { 102 | import xlld.sdk.xlcall: xlretFailed; 103 | 104 | int mdRet; 105 | FetchExcel12EntryPt(); 106 | if (gExcel12 is null) 107 | { 108 | mdRet = xlretFailed; 109 | } 110 | else 111 | { 112 | // original line was mdRet = (gExcel12)(xlfn, count, &rgxloper12[0], operRes); 113 | mdRet = (*gExcel12)(xlfn, count, opers, operRes); 114 | } 115 | return(mdRet); 116 | 117 | } 118 | -------------------------------------------------------------------------------- /source/xlld/sdk/xll.d: -------------------------------------------------------------------------------- 1 | /** 2 | Code from generic.h and generic.d 3 | Ported to the D Programming Language by Laeeth Isharc (2015) 4 | This module provides the ceremony that must be done for every XLL. 5 | At least one module must be linked to this one implementing the 6 | getWorkSheetFunctions function so that they can be registered 7 | with Excel. 8 | */ 9 | 10 | module xlld.sdk.xll; 11 | 12 | import xlld: WorksheetFunction, LPXLOPER12; 13 | version(testingExcelD) import unit_threaded; 14 | 15 | 16 | alias AutoCloseFunc = void delegate() nothrow; 17 | 18 | private AutoCloseFunc[] gAutoCloseFuncs; 19 | 20 | /** 21 | Registers a delegate to be called when the XLL is unloaded 22 | */ 23 | void registerAutoCloseFunc(AutoCloseFunc func) nothrow { 24 | gAutoCloseFuncs ~= func; 25 | } 26 | 27 | /** 28 | Registers a function to be called when the XLL is unloaded 29 | */ 30 | void registerAutoCloseFunc(void function() nothrow func) nothrow { 31 | gAutoCloseFuncs ~= { func(); }; 32 | } 33 | 34 | void callRegisteredAutoCloseFuncs() nothrow { 35 | foreach(func; gAutoCloseFuncs) func(); 36 | } 37 | 38 | 39 | version(Windows) { 40 | version(exceldDef) 41 | enum dllMain = false; 42 | else version(unittest) 43 | enum dllMain = false; 44 | else 45 | enum dllMain = true; 46 | } else 47 | enum dllMain = false; 48 | 49 | 50 | static if(dllMain) { 51 | 52 | import core.sys.windows.windows; 53 | 54 | extern(Windows) BOOL DllMain( HANDLE hDLL, DWORD dwReason, LPVOID lpReserved ) 55 | { 56 | import core.runtime; 57 | import core.sys.windows.dll; 58 | switch (dwReason) 59 | { 60 | case DLL_PROCESS_ATTACH: 61 | Runtime.initialize(); 62 | dll_process_attach( hDLL, true ); 63 | break; 64 | case DLL_PROCESS_DETACH: 65 | Runtime.terminate(); 66 | dll_process_detach( hDLL, true ); 67 | break; 68 | case DLL_THREAD_ATTACH: 69 | dll_thread_attach( true, true ); 70 | break; 71 | case DLL_THREAD_DETACH: 72 | dll_thread_detach( true, true ); 73 | break; 74 | default: 75 | break; 76 | } 77 | return true; 78 | } 79 | } 80 | 81 | // this function must be defined in a module compiled with 82 | // the current module 83 | // It's extern(C) so that it can be defined in any module 84 | extern(C) WorksheetFunction[] getWorksheetFunctions() @safe pure nothrow; 85 | 86 | extern(Windows) int xlAutoOpen() { 87 | import core.runtime: rt_init; 88 | 89 | rt_init(); // move to DllOpen? 90 | registerAllWorkSheetFunctions; 91 | 92 | return 1; 93 | } 94 | 95 | private void registerAllWorkSheetFunctions() { 96 | import xlld.memorymanager: allocator; 97 | import xlld.sdk.framework: Excel12f, freeXLOper; 98 | import xlld.sdk.xlcall: xlGetName, xlfRegister, XLOPER12; 99 | import xlld.conv: toXlOper; 100 | import std.algorithm: map; 101 | import std.array: array; 102 | 103 | // get name of this XLL, needed to pass to xlfRegister 104 | static XLOPER12 dllName; 105 | Excel12f(xlGetName, &dllName); 106 | 107 | foreach(strings; getWorksheetFunctions.map!(a => a.toStringArray)) { 108 | auto opers = strings.map!(a => a.toXlOper(allocator)).array; 109 | scope(exit) foreach(ref oper; opers) freeXLOper(&oper, allocator); 110 | 111 | auto args = new LPXLOPER12[opers.length + 1]; 112 | args[0] = &dllName; 113 | foreach(i; 0 .. opers.length) 114 | args[i + 1] = &opers[i]; 115 | 116 | Excel12f(xlfRegister, cast(LPXLOPER12)null, args); 117 | } 118 | } 119 | 120 | extern(Windows) int xlAutoClose() { 121 | import core.runtime: rt_term; 122 | 123 | callRegisteredAutoCloseFuncs; 124 | 125 | rt_term; 126 | return 1; 127 | } 128 | 129 | extern(Windows) int xlAutoFree12(LPXLOPER12 arg) nothrow { 130 | import xlld.memorymanager: autoFree; 131 | import xlld.sdk.xlcall: xlbitDLLFree; 132 | 133 | if(!(arg.xltype & xlbitDLLFree)) { 134 | log("[ERROR]: Trying to free XLOPER12 without xlbitDLLFree, ignoring"); 135 | return 0; 136 | } 137 | 138 | autoFree(arg); 139 | return 1; 140 | } 141 | 142 | extern(Windows) LPXLOPER12 xlAddInManagerInfo12(LPXLOPER12 xAction) { 143 | import xlld.sdk.xlcall: XLOPER12, XlType, xltypeInt, xlCoerce, xlerrValue; 144 | import xlld.sdk.framework: Excel12f; 145 | import xlld.conv: toAutoFreeOper; 146 | 147 | static XLOPER12 xInfo, xIntAction; 148 | 149 | // 150 | // This code coerces the passed-in value to an integer. This is how the 151 | // code determines what is being requested. If it receives a 1, 152 | // it returns a string representing the long name. If it receives 153 | // anything else, it returns a #VALUE! error. 154 | // 155 | 156 | //we need an XLOPER12 with a _value_ of xltypeInt 157 | XLOPER12 arg; 158 | arg.xltype = XlType.xltypeInt; 159 | arg.val.w = xltypeInt; 160 | 161 | Excel12f(xlCoerce, &xIntAction, [xAction, &arg]); 162 | 163 | if (xIntAction.val.w == 1) { 164 | xInfo = "My XLL".toAutoFreeOper; 165 | } else { 166 | xInfo.xltype = XlType.xltypeErr; 167 | xInfo.val.err = xlerrValue; 168 | } 169 | 170 | return &xInfo; 171 | } 172 | 173 | 174 | version(Windows) { 175 | extern(Windows) void OutputDebugStringW(const wchar* fmt) @nogc nothrow; 176 | } 177 | 178 | 179 | /** 180 | Polymorphic logging function. 181 | Prints to the console when unit testing and on Linux, 182 | otherwise uses the system logger on Windows. 183 | */ 184 | void log(A...)(auto ref A args) @trusted { 185 | try { 186 | version(unittest) { 187 | version(Have_unit_threaded) { 188 | import unit_threaded: writelnUt; 189 | writelnUt(args); 190 | } else { 191 | import std.stdio: writeln; 192 | writeln(args); 193 | } 194 | } else version(Windows) { 195 | import nogc.conv: text, toWStringz; 196 | scope txt = text(args); 197 | scope wtxt = txt[].toWStringz; 198 | OutputDebugStringW(wtxt.ptr); 199 | } else { 200 | import std.experimental.logger: trace; 201 | trace(args); 202 | } 203 | } catch(Exception e) { 204 | import core.stdc.stdio: printf; 205 | printf("Error - could not log\n"); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /source/xlld/static_/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | To avoid cyclic dependencies with module constructors 3 | */ 4 | module xlld.static_; 5 | 6 | /// 7 | shared static this() nothrow { 8 | import xlld.conv.from: gToEnumMutex; 9 | import xlld.conv.to: gFromEnumMutex; 10 | import core.sync.mutex: Mutex; 11 | gToEnumMutex = new shared Mutex; 12 | gFromEnumMutex = new shared Mutex; 13 | } 14 | 15 | 16 | version(testLibraryExcelD): 17 | 18 | shared static this() nothrow { 19 | import xlld.sdk.xlcall: XLOPER12; 20 | import xlld.test.util: gAsyncReturns, AA; 21 | gAsyncReturns = AA!(XLOPER12, XLOPER12).create; 22 | } 23 | 24 | static this() nothrow { 25 | import xlld.test.util: excel12UnitTest; 26 | import xlld.sdk.xlcallcpp: SetExcel12EntryPt; 27 | // this effectively "implements" the Excel12v function 28 | // so that the code can be unit tested without needing to link 29 | // with the Excel SDK 30 | SetExcel12EntryPt(&excel12UnitTest); 31 | } 32 | 33 | /// 34 | static ~this() { 35 | import xlld.test.util: gAllocated, gFreed, gNumXlAllocated, gNumXlFree; 36 | import unit_threaded.should: shouldBeSameSetAs; 37 | gFreed[0 .. gNumXlFree].shouldBeSameSetAs(gAllocated[0 .. gNumXlAllocated]); 38 | } 39 | -------------------------------------------------------------------------------- /source/xlld/test/util.d: -------------------------------------------------------------------------------- 1 | /** 2 | Utility test functions 3 | */ 4 | module xlld.test.util; 5 | 6 | version(testLibraryExcelD): 7 | 8 | import xlld.sdk.xlcall: XLOPER12, XlType; 9 | 10 | /// 11 | TestAllocator gTestAllocator; 12 | /// emulates SRef types by storing what the referenced type actually is 13 | XlType gReferencedType; 14 | 15 | // tracks calls to `coerce` and `free` to make sure memory allocations/deallocations match 16 | int gNumXlAllocated; 17 | /// 18 | int gNumXlFree; 19 | /// 20 | enum maxAllocTrack = 1000; 21 | /// 22 | const(void)*[maxAllocTrack] gAllocated; 23 | /// 24 | const(void)*[maxAllocTrack] gFreed; 25 | 26 | alias XlFunction = int; 27 | 28 | /** 29 | This stores what Excel12f should "return" for each integer XL "function" 30 | */ 31 | XLOPER12[][XlFunction] gXlFuncResults; 32 | 33 | shared AA!(XLOPER12, XLOPER12) gAsyncReturns = void; 34 | 35 | 36 | XLOPER12 asyncReturn(XLOPER12 asyncHandle) @safe { 37 | return gAsyncReturns[asyncHandle]; 38 | } 39 | 40 | private void fakeAllocate(XLOPER12 oper) @nogc nothrow { 41 | fakeAllocate(&oper); 42 | } 43 | 44 | private void fakeAllocate(XLOPER12* oper) @nogc nothrow { 45 | gAllocated[gNumXlAllocated++] = oper.val.str; 46 | } 47 | 48 | 49 | private void fakeFree(XLOPER12* oper) @nogc nothrow { 50 | gFreed[gNumXlFree++] = oper.val.str; 51 | } 52 | 53 | /// 54 | extern(Windows) int excel12UnitTest(int xlfn, int numOpers, XLOPER12** opers, XLOPER12* result) 55 | nothrow @nogc 56 | { 57 | 58 | import xlld.sdk.xlcall; 59 | import xlld.conv: toXlOper; 60 | import xlld.conv.misc: stripMemoryBitmask; 61 | import std.experimental.allocator.gc_allocator: GCAllocator; 62 | import std.experimental.allocator.mallocator: Mallocator; 63 | import std.array: front, popFront, empty; 64 | 65 | if(auto xlfnResults = xlfn in gXlFuncResults) { 66 | 67 | assert(!(*xlfnResults).empty, "No results to return for xlfn"); 68 | 69 | auto mockResult = (*xlfnResults).front; 70 | (*xlfnResults).popFront; 71 | 72 | if(xlfn == xlfCaller) { 73 | fakeAllocate(mockResult); 74 | } 75 | *result = mockResult; 76 | return xlretSuccess; 77 | } 78 | 79 | switch(xlfn) { 80 | 81 | default: 82 | return xlretFailed; 83 | 84 | case xlFree: 85 | assert(numOpers == 1); 86 | auto oper = opers[0]; 87 | 88 | fakeFree(oper); 89 | 90 | if(oper.xltype == XlType.xltypeStr) { 91 | try 92 | *oper = "".toXlOper(Mallocator.instance); 93 | catch(Exception _) { 94 | assert(false, "Error converting in excel12UnitTest"); 95 | } 96 | } 97 | 98 | return xlretSuccess; 99 | 100 | case xlCoerce: 101 | assert(numOpers == 1); 102 | 103 | auto oper = opers[0]; 104 | fakeAllocate(oper); 105 | *result = *oper; 106 | 107 | switch(oper.xltype) with(XlType) { 108 | 109 | case xltypeSRef: 110 | result.xltype = gReferencedType; 111 | break; 112 | 113 | case xltypeNum: 114 | case xltypeStr: 115 | result.xltype = oper.xltype; 116 | break; 117 | 118 | case xltypeMissing: 119 | result.xltype = xltypeNil; 120 | break; 121 | 122 | default: 123 | } 124 | 125 | return xlretSuccess; 126 | 127 | case xlAsyncReturn: 128 | assert(numOpers == 2); 129 | gAsyncReturns[*opers[0]] = *opers[1]; 130 | return xlretSuccess; 131 | } 132 | } 133 | 134 | 135 | /// automatically converts from oper to compare with a D type 136 | void shouldEqualDlang(U) 137 | (XLOPER12* actual, 138 | U expected, 139 | string file = __FILE__, 140 | size_t line = __LINE__) 141 | @trusted 142 | { 143 | import xlld.memorymanager: allocator; 144 | import xlld.conv.from: fromXlOper; 145 | import xlld.conv.misc: stripMemoryBitmask; 146 | import xlld.sdk.xlcall: XlType; 147 | import unit_threaded; 148 | import std.traits: Unqual; 149 | import std.conv: text; 150 | import std.experimental.allocator.gc_allocator: GCAllocator; 151 | 152 | actual.shouldNotBeNull; 153 | 154 | const type = actual.xltype.stripMemoryBitmask; 155 | 156 | if(type == XlType.xltypeErr) 157 | fail("XLOPER is of error type", file, line); 158 | 159 | static if(!is(Unqual!U == string)) 160 | if(type == XlType.xltypeStr) 161 | fail(text(`XLOPER is of string type. Value: "`, 162 | actual.fromXlOper!string(GCAllocator.instance), `"`), 163 | file, line); 164 | 165 | actual.fromXlOper!U(allocator).shouldEqual(expected, file, line); 166 | } 167 | 168 | /// automatically converts from oper to compare with a D type 169 | void shouldEqualDlang(U)(ref XLOPER12 actual, U expected, string file = __FILE__, size_t line = __LINE__) @trusted { 170 | shouldEqualDlang(&actual, expected, file, line); 171 | } 172 | 173 | /// automatically converts from oper to compare with a D type 174 | void shouldEqualDlang(U)(XLOPER12 actual, U expected, string file = __FILE__, size_t line = __LINE__) @trusted { 175 | shouldEqualDlang(actual, expected, file, line); 176 | } 177 | 178 | 179 | /// 180 | XLOPER12 toSRef(T, A)(scope T val, ref A allocator) @safe { 181 | import xlld.conv: toXlOper; 182 | 183 | auto ret = () @trusted { return toXlOper(val, allocator); }(); 184 | //hide real type somewhere to retrieve it 185 | gReferencedType = ret.xltype; 186 | ret.xltype = XlType.xltypeSRef; 187 | return ret; 188 | } 189 | 190 | /// Mimics Excel calling a particular D function, including freeing memory 191 | void fromExcel(alias F, A...)(auto ref A args) { 192 | import xlld.wrap: wrapModuleFunctionImpl; 193 | import xlld.memorymanager: gTempAllocator, autoFree; 194 | 195 | auto oper = wrapModuleFunctionImpl!F(gTempAllocator, args); 196 | autoFree(oper); 197 | } 198 | 199 | 200 | /// tracks allocations and throws in the destructor if there is a memory leak 201 | /// it also throws when there is an attempt to deallocate memory that wasn't 202 | /// allocated 203 | struct TestAllocator { 204 | 205 | import std.experimental.allocator.common: platformAlignment; 206 | import std.experimental.allocator.mallocator: Mallocator; 207 | 208 | /// 209 | alias allocator = Mallocator.instance; 210 | 211 | private static struct ByteRange { 212 | void* ptr; 213 | size_t length; 214 | inout(void)[] opSlice() @trusted @nogc inout nothrow { 215 | return ptr[0 .. length]; 216 | } 217 | } 218 | 219 | /// 220 | bool debug_; 221 | private ByteRange[] _allocations; 222 | private ByteRange[] _deallocations; 223 | private int _numAllocations; 224 | 225 | /// 226 | enum uint alignment = platformAlignment; 227 | 228 | /// 229 | void[] allocate(size_t numBytes) @trusted @nogc { 230 | import std.experimental.allocator: makeArray, expandArray; 231 | import core.stdc.stdio: printf; 232 | 233 | static __gshared immutable exception = new Exception("Allocation failed"); 234 | 235 | ++_numAllocations; 236 | 237 | auto ret = allocator.allocate(numBytes); 238 | if(numBytes > 0 && ret.length == 0) 239 | throw exception; 240 | 241 | if(debug_) () @trusted { printf("+ allocate: %p for %d bytes\n", &ret[0], cast(int)ret.length); }(); 242 | auto newEntry = ByteRange(&ret[0], ret.length); 243 | 244 | if(_allocations is null) 245 | _allocations = allocator.makeArray(1, newEntry); 246 | else 247 | () @trusted { allocator.expandArray(_allocations, 1, newEntry); }(); 248 | 249 | return ret; 250 | } 251 | 252 | /// 253 | bool deallocate(void[] bytes) @trusted @nogc nothrow { 254 | import std.experimental.allocator: makeArray, expandArray; 255 | import std.algorithm: remove, find, canFind; 256 | import std.array: front, empty; 257 | import core.stdc.stdio: printf, sprintf; 258 | 259 | bool pred(ByteRange other) { return other.ptr == bytes.ptr && other.length == bytes.length; } 260 | 261 | static char[1024] buffer; 262 | 263 | if(debug_) printf("- deallocate: %p for %d bytes\n", &bytes[0], cast(int)bytes.length); 264 | 265 | auto findAllocations = _allocations.find!pred; 266 | 267 | if(findAllocations.empty) { 268 | if(_deallocations.canFind!pred) { 269 | auto index = sprintf(&buffer[0], 270 | "Double free on byte range Ptr: %p, length: %ld, allocations:\n", 271 | &bytes[0], bytes.length); 272 | index = printAllocations(buffer, index); 273 | assert(false, buffer[0 .. index]); 274 | 275 | } else { 276 | auto index = sprintf(&buffer[0], 277 | "Unknown deallocate byte range. Ptr: %p, length: %ld, allocations:\n", 278 | &bytes[0], bytes.length); 279 | index = printAllocations(buffer, index); 280 | assert(false, buffer[0 .. index]); 281 | } 282 | } 283 | 284 | if(_deallocations is null) 285 | _deallocations = allocator.makeArray(1, findAllocations.front); 286 | else 287 | () @trusted { allocator.expandArray(_allocations, 1, findAllocations.front); }(); 288 | 289 | 290 | _allocations = _allocations.remove!pred; 291 | 292 | return () @trusted { return allocator.deallocate(bytes); }(); 293 | } 294 | 295 | /// 296 | bool deallocateAll() @safe @nogc nothrow { 297 | import std.array: empty, front; 298 | 299 | while(!_allocations.empty) { 300 | auto allocation = _allocations.front; 301 | deallocate(allocation[]); 302 | } 303 | return true; 304 | } 305 | 306 | /// 307 | auto numAllocations() @safe @nogc pure nothrow const { 308 | return _numAllocations; 309 | } 310 | 311 | /// 312 | ~this() @safe @nogc nothrow { 313 | verify; 314 | } 315 | 316 | /// 317 | void verify() @trusted @nogc nothrow { 318 | 319 | static char[1024] buffer; 320 | 321 | if(_allocations.length) { 322 | import core.stdc.stdio: sprintf; 323 | auto index = sprintf(&buffer[0], "Memory leak in TestAllocator. Allocations:\n"); 324 | index = printAllocations(buffer, index); 325 | assert(false, buffer[0 .. index]); 326 | } 327 | } 328 | 329 | /// 330 | int printAllocations(int N)(ref char[N] buffer, int index = 0) @trusted @nogc const nothrow { 331 | import core.stdc.stdio: sprintf; 332 | index += sprintf(&buffer[index], "[\n"); 333 | foreach(ref allocation; _allocations) { 334 | index += sprintf(&buffer[index], " ByteRange(%p, %ld),\n", 335 | allocation.ptr, allocation.length); 336 | } 337 | 338 | index += sprintf(&buffer[index], "]"); 339 | buffer[index++] = 0; // null terminate 340 | return index; 341 | } 342 | } 343 | 344 | 345 | /** 346 | @nogc associative array 347 | */ 348 | struct AA(K, V, int N = 100) { 349 | import core.sync.mutex: Mutex; 350 | 351 | Entry[N] entries; 352 | size_t index; 353 | Mutex mutex; 354 | 355 | static struct Entry { 356 | K key; 357 | V value; 358 | } 359 | 360 | @disable this(); 361 | 362 | static shared(AA) create() @trusted { 363 | shared AA aa = void; 364 | aa.index = 0; 365 | aa.mutex = new shared Mutex; 366 | foreach(ref entry; aa.entries[]) { 367 | entry = typeof(entry).init; 368 | } 369 | return aa; 370 | } 371 | 372 | V opIndex(in K key) shared { 373 | import std.algorithm: find; 374 | import std.array: front, empty; 375 | 376 | mutex.lock_nothrow; 377 | scope(exit) mutex.unlock_nothrow; 378 | 379 | auto fromKey = () @trusted { return entries[].find!(e => cast(K)e.key == key); }(); 380 | return fromKey.empty 381 | ? V.init 382 | : cast(V)fromKey.front.value; 383 | } 384 | 385 | void opIndexAssign(V value, K key) shared { 386 | import core.atomic: atomicOp; 387 | 388 | mutex.lock_nothrow; 389 | scope(exit) mutex.unlock_nothrow; 390 | 391 | assert(index < N - 1, "No more space"); 392 | entries[index] = Entry(key, value); 393 | index.atomicOp!"+="(1); 394 | } 395 | } 396 | 397 | @("AA") 398 | @safe unittest { 399 | import unit_threaded; 400 | import core.exception: AssertError; 401 | auto aa = AA!(string, int).create; 402 | aa["foo"] = 5; 403 | aa["foo"].shouldEqual(5); 404 | aa["bar"].shouldEqual(0); 405 | } 406 | 407 | 408 | XLOPER12 newAsyncHandle() @safe nothrow { 409 | import xlld.sdk.xlcall: XlType; 410 | import core.atomic: atomicOp; 411 | 412 | static shared typeof(XLOPER12.val.w) index = 1; 413 | XLOPER12 asyncHandle; 414 | 415 | asyncHandle.xltype = XlType.xltypeBigData; 416 | () @trusted { asyncHandle.val.bigdata.h.hdata = cast(void*)index; }(); 417 | index.atomicOp!"+="(1); 418 | 419 | return asyncHandle; 420 | } 421 | 422 | struct MockXlFunction { 423 | 424 | XlFunction xlFunction; 425 | typeof(gXlFuncResults) oldResults; 426 | 427 | this(int xlFunction, XLOPER12 result) @safe { 428 | this(xlFunction, [result]); 429 | } 430 | 431 | this(int xlFunction, XLOPER12[] results) @safe { 432 | this.xlFunction = xlFunction; 433 | this.oldResults = gXlFuncResults.dup; 434 | gXlFuncResults[xlFunction] ~= results; 435 | } 436 | 437 | this(int xlFunction, double value) @safe { 438 | import xlld.conv.to: toXlOper; 439 | import std.experimental.allocator.gc_allocator: GCAllocator; 440 | this(xlFunction, value.toXlOper(GCAllocator.instance)); 441 | } 442 | 443 | ~this() @safe { 444 | gXlFuncResults = oldResults; 445 | } 446 | } 447 | 448 | struct MockDateTime { 449 | 450 | import xlld.sdk.xlcall: xlfYear, xlfMonth, xlfDay, xlfHour, xlfMinute, xlfSecond; 451 | 452 | MockXlFunction year, month, day, hour, minute, second; 453 | 454 | this(int year, int month, int day, int hour, int minute, int second) @safe { 455 | import xlld.conv: toXlOper; 456 | import std.experimental.allocator.gc_allocator: GCAllocator; 457 | alias theGC = GCAllocator.instance; 458 | 459 | this.year = MockXlFunction(xlfYear, double(year).toXlOper(theGC)); 460 | this.month = MockXlFunction(xlfMonth, double(month).toXlOper(theGC)); 461 | this.day = MockXlFunction(xlfDay, double(day).toXlOper(theGC)); 462 | this.hour = MockXlFunction(xlfHour, double(hour).toXlOper(theGC)); 463 | this.minute = MockXlFunction(xlfMinute, double(minute).toXlOper(theGC)); 464 | this.second = MockXlFunction(xlfSecond, double(second).toXlOper(theGC)); 465 | } 466 | } 467 | 468 | struct MockDateTimes { 469 | 470 | import std.datetime: DateTime; 471 | import std.range: isInputRange, ElementType; 472 | import std.traits: Unqual; 473 | 474 | MockDateTime[] mocks; 475 | 476 | this(DateTime[] dateTimes...) @safe { 477 | fromRange(dateTimes); 478 | } 479 | 480 | this(R)(R dateTimes) @safe if(isInputRange!R && is(Unqual!(ElementType!R) == DateTime)) { 481 | fromRange(dateTimes); 482 | } 483 | 484 | private void fromRange(R)(scope R dateTimes) @trusted if(isInputRange!R && is(Unqual!(ElementType!R) == DateTime)) { 485 | foreach(dateTime; dateTimes) 486 | mocks ~= MockDateTime(dateTime.year, dateTime.month, dateTime.day, 487 | dateTime.hour, dateTime.minute, dateTime.second); 488 | } 489 | } 490 | 491 | struct MockDates { 492 | import std.range: isInputRange, ElementType; 493 | 494 | MockXlFunction[] mocks; 495 | 496 | this(R)(R dates) if(isInputRange!R) { 497 | import xlld.sdk.xlcall: xlfDate; 498 | foreach(date; dates) 499 | mocks ~= MockXlFunction(xlfDate, double(date)); 500 | } 501 | } 502 | 503 | struct MockTimes { 504 | import std.range: isInputRange, ElementType; 505 | 506 | MockXlFunction[] mocks; 507 | 508 | this(R)(R dates) if(isInputRange!R) { 509 | import xlld.sdk.xlcall: xlfTime; 510 | foreach(date; dates) 511 | mocks ~= MockXlFunction(xlfTime, double(date)); 512 | } 513 | } 514 | 515 | 516 | struct FailingAllocator { 517 | void[] allocate(size_t numBytes) @safe @nogc pure nothrow { 518 | return null; 519 | } 520 | 521 | bool deallocate(void[] bytes) @safe @nogc pure nothrow { 522 | assert(false); 523 | } 524 | } 525 | -------------------------------------------------------------------------------- /source/xlld/wrap/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Autowrapping of D functions to be called from Excel. Generally this will be enough: 3 | 4 | ------ 5 | import xlld; 6 | mixin wrapAll!("mymodule1", "mymodule2", ...); 7 | 8 | All public functions in the modules in the call to `wrapAll` will then be available 9 | to be called from Excel. 10 | ------ 11 | */ 12 | module xlld.wrap; 13 | 14 | public import xlld.wrap.wrap; 15 | public import xlld.wrap.traits; 16 | public import xlld.wrap.worksheet; 17 | -------------------------------------------------------------------------------- /source/xlld/wrap/traits.d: -------------------------------------------------------------------------------- 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] "); 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; 431 | -------------------------------------------------------------------------------- /source/xlld/wrap/worksheet.d: -------------------------------------------------------------------------------- 1 | /** 2 | Interface for registering worksheet functions with Excel 3 | */ 4 | module xlld.wrap.worksheet; 5 | 6 | /** 7 | Simple wrapper struct for a value. Provides a type-safe way 8 | of making sure positional arguments match the intended semantics, 9 | which is important given that nearly all of the arguments for 10 | worksheet function registration are of the same type: wstring 11 | ST is short for "SmallType". 12 | */ 13 | private mixin template ST(string name, T = wstring) { 14 | mixin(`struct ` ~ name ~ `{ T value; }`); 15 | } 16 | 17 | // see: https://docs.microsoft.com/en-us/office/client-developer/excel/xlfregister-form-1 18 | 19 | /// 20 | struct Procedure { 21 | wstring value; 22 | string toString() @safe pure const { 23 | import std.conv: to; 24 | return value.to!string; 25 | } 26 | } 27 | 28 | mixin ST!"TypeText"; 29 | /++ 30 | Function name as it will appear in the Function Wizard. 31 | +/ 32 | mixin ST!"FunctionText"; 33 | /++ 34 | Names of the arguments, semicolon separated. For example: 35 | 36 | foo;bar 37 | +/ 38 | mixin ST!"ArgumentText"; 39 | mixin ST!"MacroType"; 40 | /++ 41 | Category name it appears in in the wizard. Must be picked 42 | from the list of existing categories in Excel. 43 | +/ 44 | mixin ST!"Category"; 45 | /++ 46 | One-character, case-sensitive key. "A" assigns this command 47 | to ctrl+shift+A. Used only for commands. 48 | +/ 49 | mixin ST!"ShortcutText"; 50 | /++ 51 | Reference to the help file to display when the user clicks help. 52 | Form of `filepath!HelpContextID` or `URL!0`. 53 | 54 | The !0 is required if you provide a web link. 55 | +/ 56 | mixin ST!"HelpTopic"; 57 | /++ 58 | Describes your function in the Function Wizard. 59 | ++/ 60 | mixin ST!"FunctionHelp"; 61 | /++ 62 | Array of text strings displayed in the function dialog 63 | in Excel to describe each arg. 64 | +/ 65 | struct ArgumentHelp { 66 | wstring[] value; 67 | 68 | // allow @ArgumentHelp(x, y, x) too 69 | this(wstring[] txt...) pure nothrow @safe { 70 | // this is fine because below it is all copied 71 | // into GC memory anyway. 72 | this(txt[]); 73 | } 74 | 75 | this(scope wstring[] txt) pure nothrow @safe { 76 | foreach(t; txt) add(t); 77 | } 78 | 79 | void add(wstring txt) @safe pure nothrow { 80 | // Excel has a bug that chops off the last 81 | // character of this, so adding a space here 82 | // works around that. 83 | value ~= txt ~ " "; 84 | } 85 | } 86 | 87 | 88 | /** 89 | The arguments used to register a worksheet function with the spreadsheet. 90 | */ 91 | struct WorksheetFunction { 92 | // the first few parameters have to be set, the others are optional 93 | Procedure procedure; 94 | TypeText typeText; 95 | FunctionText functionText; 96 | Optional optional; 97 | alias optional this; //for ease of use 98 | 99 | /** 100 | Returns an array suitable for use with spreadsheet registration 101 | */ 102 | const(wstring)[] toStringArray() @safe pure const nothrow { 103 | return 104 | [ 105 | procedure.value, typeText.value, 106 | functionText.value, argumentText.value, 107 | macroType.value, category.value, 108 | shortcutText.value, helpTopic.value, functionHelp.value 109 | ] ~ argumentHelp.value; 110 | } 111 | 112 | bool opEquals(const WorksheetFunction rhs) const @safe pure { return optional == rhs.optional; } 113 | } 114 | 115 | // helper template to type-check variadic template constructor below 116 | private alias toType(alias U) = typeof(U); 117 | 118 | /** 119 | Optional arguments that can be set by a function author, but don't necessarily 120 | have to be. 121 | */ 122 | struct Optional { 123 | ArgumentText argumentText; 124 | MacroType macroType = MacroType("1"w); 125 | Category category; 126 | ShortcutText shortcutText; 127 | HelpTopic helpTopic; 128 | FunctionHelp functionHelp; 129 | ArgumentHelp argumentHelp; 130 | 131 | this(T...)(T args) { 132 | import std.meta: staticIndexOf, staticMap, allSatisfy, AliasSeq; 133 | import std.conv: text; 134 | 135 | static assert(T.length <= this.tupleof.length, "Too many arguments for Optional/Register"); 136 | 137 | // myTypes: ArgumentText, MacroType, ... 138 | alias myTypes = staticMap!(toType, AliasSeq!(this.tupleof)); 139 | enum isOneOfMyTypes(U) = staticIndexOf!(U, myTypes) != -1; 140 | static assert(allSatisfy!(isOneOfMyTypes, T), 141 | text("Unknown types passed to Optional/Register constructor. ", 142 | "Has to be one of:\n", myTypes.stringof)); 143 | 144 | // loop over whatever was given and set each of our members based on the 145 | // type of the parameter instead of by position 146 | foreach(ref member; this.tupleof) { 147 | enum index = staticIndexOf!(typeof(member), T); 148 | static if(index != -1) 149 | member = args[index]; 150 | } 151 | } 152 | } 153 | 154 | /** 155 | A user-facing name to use as an UDA to decorate D functions. 156 | Any arguments passed to its constructor will be used to register 157 | the function with the spreadsheet. 158 | */ 159 | alias Register = Optional; 160 | alias Excel = Optional; 161 | 162 | 163 | /// 164 | struct Dispose(alias function_) { 165 | alias dispose = function_; 166 | } 167 | 168 | /// Information about a function parameter. Meant to be used as a UDA. 169 | mixin ST!"ExcelParameter"; 170 | -------------------------------------------------------------------------------- /tests/test/d_funcs.d: -------------------------------------------------------------------------------- 1 | /** 2 | Only exists to test the wrapping functionality Contains functions 3 | with regular D types that will get wrapped so they can be called by 4 | the spreadsheet. 5 | */ 6 | 7 | module test.d_funcs; 8 | 9 | version(unittest): 10 | 11 | import xlld; 12 | import std.datetime: DateTime; 13 | 14 | 15 | /// 16 | @Register(ArgumentText("Array to add"), 17 | HelpTopic("Adds all cells in an array"), 18 | FunctionHelp("Adds all cells in an array"), 19 | ArgumentHelp(["The array to add"])) 20 | double FuncAddEverything(double[][] args) nothrow @nogc { 21 | import std.algorithm: fold; 22 | import std.math: isNaN; 23 | 24 | double ret = 0; 25 | foreach(row; args) 26 | ret += row.fold!((a, b) => b.isNaN ? 0.0 : a + b)(0.0); 27 | return ret; 28 | } 29 | 30 | /// 31 | double[][] FuncTripleEverything(double[][] args) nothrow { 32 | double[][] ret; 33 | ret.length = args.length; 34 | foreach(i; 0 .. args.length) { 35 | ret[i].length = args[i].length; 36 | foreach(j; 0 .. args[i].length) 37 | ret[i][j] = args[i][j] * 3; 38 | } 39 | 40 | return ret; 41 | } 42 | 43 | /// 44 | double FuncAllLengths(string[][] args) nothrow @nogc { 45 | import std.algorithm: fold; 46 | 47 | double ret = 0; 48 | foreach(row; args) 49 | ret += row.fold!((a, b) => a + b.length)(0.0); 50 | return ret; 51 | } 52 | 53 | /// 54 | double[][] FuncLengths(string[][] args) nothrow { 55 | double[][] ret; 56 | 57 | ret.length = args.length; 58 | foreach(i; 0 .. args.length) { 59 | ret[i].length = args[i].length; 60 | foreach(j; 0 .. args[i].length) 61 | ret[i][j] = args[i][j].length; 62 | } 63 | 64 | return ret; 65 | } 66 | 67 | 68 | /// 69 | string[][] FuncBob(string[][] args) nothrow { 70 | string[][] ret; 71 | 72 | ret.length = args.length; 73 | foreach(i; 0 .. args.length) { 74 | ret[i].length = args[i].length; 75 | foreach(j; 0 .. args[i].length) 76 | ret[i][j] = args[i][j] ~ "bob"; 77 | } 78 | 79 | return ret; 80 | } 81 | 82 | 83 | /// 84 | double FuncDoubleSlice(double[] arg) nothrow @nogc { 85 | return arg.length; 86 | } 87 | 88 | /// 89 | double FuncStringSlice(string[] arg) nothrow @nogc { 90 | return arg.length; 91 | } 92 | 93 | /// 94 | double[] FuncSliceTimes3(double[] arg) nothrow { 95 | import std.algorithm; 96 | import std.array; 97 | return arg.map!(a => a * 3).array; 98 | } 99 | 100 | /// 101 | string[] StringsToStrings(string[] args) nothrow { 102 | import std.algorithm; 103 | import std.array; 104 | return args.map!(a => a ~ "foo").array; 105 | } 106 | 107 | /// 108 | string StringsToString(string[] args) nothrow { 109 | import std.string; 110 | return args.join(", "); 111 | } 112 | 113 | /// 114 | string StringToString(string arg) nothrow { 115 | return arg ~ "bar"; 116 | } 117 | 118 | private string shouldNotBeAProblem(string, string[]) nothrow { 119 | return ""; 120 | } 121 | 122 | /// 123 | string ManyToString(string arg0, string arg1, string arg2) nothrow { 124 | return arg0 ~ arg1 ~ arg2; 125 | } 126 | 127 | // shouldn't get wrapped 128 | double FuncThrows(double) { 129 | throw new Exception("oops"); 130 | } 131 | 132 | /// 133 | double FuncAsserts(double) { 134 | assert(false); 135 | } 136 | 137 | 138 | /** 139 | @Dispose is used to tell the framework how to free memory that is dynamically 140 | allocated by the D function. After returning, the value is converted to an 141 | Excel type sand the D value is freed using the lambda defined here. 142 | In this example we're using TestAllocator to make sure that there are no 143 | memory leaks. 144 | */ 145 | @Dispose!((ret) { 146 | import xlld.test.util: gTestAllocator; 147 | import std.experimental.allocator: dispose; 148 | gTestAllocator.dispose(ret); 149 | }) 150 | 151 | /// 152 | double[] FuncReturnArrayNoGc(scope double[] numbers) @nogc @safe nothrow { 153 | import xlld.test.util: gTestAllocator; 154 | import std.experimental.allocator: makeArray; 155 | import std.algorithm: map; 156 | 157 | try { 158 | return () @trusted { return gTestAllocator.makeArray(numbers.map!(a => a * 2)); }(); 159 | } catch(Exception _) { 160 | return []; 161 | } 162 | } 163 | 164 | 165 | /// 166 | Any[][] DoubleArrayToAnyArray(scope double[][] values) @safe nothrow { 167 | import std.experimental.allocator.mallocator: Mallocator; 168 | import std.conv: to; 169 | 170 | alias allocator = Mallocator.instance; 171 | 172 | string third, fourth; 173 | try { 174 | third = values[1][0].to!string; 175 | fourth = values[1][1].to!string; 176 | } catch(Exception ex) { 177 | third = "oops"; 178 | fourth = "oops"; 179 | } 180 | 181 | return () @trusted { 182 | with(allocatorContext(allocator)) { 183 | try 184 | return [ 185 | [any(values[0][0] * 2), any(values[0][1] * 3)], 186 | [any(third ~ "quux"), any(fourth ~ "toto")], 187 | ]; 188 | catch(Exception _) { 189 | Any[][] empty; 190 | return empty; 191 | } 192 | } 193 | }(); 194 | } 195 | 196 | 197 | /// 198 | double[] AnyArrayToDoubleArray(Any[][] values) nothrow { 199 | return [values.length, values.length ? values[0].length : 0]; 200 | } 201 | 202 | 203 | /// 204 | Any[][] AnyArrayToAnyArray(Any[][] values) nothrow { 205 | return values; 206 | } 207 | 208 | /// 209 | Any[][] FirstOfTwoAnyArrays(Any[][] a, Any[][]) nothrow { 210 | return a; 211 | } 212 | 213 | /// 214 | string[] EmptyStrings1D(Any) nothrow { 215 | string[] empty; 216 | return empty; 217 | } 218 | 219 | 220 | /// 221 | string[][] EmptyStrings2D(Any) nothrow { 222 | string[][] empty; 223 | return empty; 224 | } 225 | 226 | /// 227 | string[][] EmptyStringsHalfEmpty2D(Any) nothrow { 228 | string[][] empty; 229 | empty.length = 1; 230 | assert(empty[0].length == 0); 231 | return empty; 232 | } 233 | 234 | /// 235 | int Twice(int i) @safe nothrow { 236 | return i * 2; 237 | } 238 | 239 | /// 240 | double FuncConstDouble(const double a) @safe nothrow { 241 | return a; 242 | } 243 | 244 | double DateTimeToDouble(DateTime d) @safe nothrow { 245 | return d.year * 2; 246 | } 247 | 248 | string DateTimesToString(scope DateTime[] ds) @safe nothrow { 249 | import std.algorithm: map; 250 | import std.string: join; 251 | import std.conv: text; 252 | return ds.map!(d => d.day.text).join(", "); 253 | } 254 | 255 | 256 | @Async 257 | double AsyncDoubleToDouble(double d) @safe nothrow { 258 | return d * 2; 259 | } 260 | 261 | import core.stdc.stdio; 262 | double Overloaded(double d) @safe @nogc nothrow { 263 | () @trusted { printf("double\n"); }(); 264 | return d * 2; 265 | } 266 | 267 | double Overloaded(string s) @safe @nogc nothrow { 268 | () @trusted { printf("string\n"); }(); 269 | return s.length; 270 | } 271 | 272 | double NaN() { 273 | return double.init; 274 | } 275 | 276 | int BoolToInt(bool b) { 277 | return cast(int)b; 278 | } 279 | 280 | enum MyEnum { 281 | foo, bar, baz, 282 | } 283 | 284 | string FuncMyEnumArg(MyEnum val) @safe { 285 | import std.conv: text; 286 | return "prefix_" ~ val.text; 287 | } 288 | 289 | MyEnum FuncMyEnumRet(int i) @safe { 290 | return cast(MyEnum)i; 291 | } 292 | 293 | struct Point { 294 | int x, y; 295 | } 296 | 297 | int FuncPointArg(Point p) @safe { 298 | return p.x + p.y; 299 | } 300 | 301 | Point FuncPointRet(int x, int y) @safe { 302 | return Point(x, y); 303 | } 304 | 305 | auto FuncSimpleTupleRet(int i, scope string s) @safe { 306 | import std.typecons: tuple; 307 | return tuple(i, s); 308 | } 309 | 310 | auto FuncComplexTupleRet(int d1, int d2) @safe { 311 | import std.typecons: tuple; 312 | return tuple([DateTime(2017, 1, d1), DateTime(2017, 2, d1)], 313 | [DateTime(2018, 1, d1), DateTime(2018, 2, d2)]); 314 | } 315 | 316 | auto FuncTupleArrayRet() @safe { 317 | import std.typecons: tuple; 318 | return [ 319 | tuple(DateTime(2017, 1, 1), 11.1), 320 | tuple(DateTime(2018, 1, 1), 22.2), 321 | tuple(DateTime(2019, 1, 1), 33.3), 322 | ]; 323 | } 324 | 325 | struct DateAndString { 326 | DateTime dateTime; 327 | string string_; 328 | } 329 | 330 | DateAndString[] FuncDateAndStringRet() { 331 | return [DateAndString(DateTime(2017, 1, 2), "foobar")]; 332 | } 333 | 334 | 335 | void FuncEnumArray(MyEnum[]) { 336 | 337 | } 338 | 339 | 340 | XLOPER12 FuncOper(double d) { 341 | import std.experimental.allocator.gc_allocator: GCAllocator; 342 | return (d * 2).toXlOper(GCAllocator.instance); 343 | } 344 | 345 | 346 | auto FuncVector(int i) @safe @nogc { 347 | import automem.vector: vector; 348 | import std.range: iota; 349 | import std.experimental.allocator.mallocator: Mallocator; 350 | 351 | return vector!Mallocator(i.iota); 352 | } 353 | 354 | 355 | auto FuncVector2D(int i) @safe @nogc { 356 | import automem.vector: vector; 357 | import std.range: iota; 358 | import std.experimental.allocator.mallocator: Mallocator; 359 | 360 | return vector!Mallocator(vector!Mallocator(i, i, i), 361 | vector!Mallocator(i + 1, i + 1, i + 1)); 362 | } 363 | 364 | 365 | auto FuncStringVector(int i) @safe @nogc { 366 | import automem.vector: vector; 367 | import std.experimental.allocator.mallocator: Mallocator; 368 | 369 | return vector!Mallocator("hi"); 370 | } 371 | 372 | string appendFoo(string s) { 373 | return s ~ "_foo"; 374 | } 375 | -------------------------------------------------------------------------------- /tests/test/package.d: -------------------------------------------------------------------------------- 1 | module test; 2 | 3 | public import xlld.test.util; 4 | public import xlld.sdk.xlcall; 5 | public import xlld.any: any, Any; 6 | public import unit_threaded; 7 | public import std.datetime: DateTime; 8 | 9 | import std.experimental.allocator.gc_allocator: GCAllocator; 10 | alias theGC = GCAllocator.instance; 11 | -------------------------------------------------------------------------------- /tests/test/xl_funcs.d: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | Only exists to test the module reflection functionality 4 | Contains functions with types that the spreadsheet knows about 5 | */ 6 | 7 | module test.xl_funcs; 8 | 9 | version(unittest): 10 | 11 | import xlld.from; 12 | 13 | import xlld.sdk.xlcall; 14 | import xlld.wrap.worksheet; 15 | import xlld.wrap.traits: Async; 16 | 17 | extern(Windows) double FuncMulByTwo(double n) nothrow { 18 | return n * 2; 19 | } 20 | 21 | extern(Windows) double FuncFP12(FP12* cells) nothrow { 22 | return 0; 23 | } 24 | 25 | 26 | extern(Windows) from!"xlld.sdk.xlcall".LPXLOPER12 FuncFib (from!"xlld.sdk.xlcall".LPXLOPER12 n) nothrow { 27 | return null; 28 | } 29 | 30 | @Async 31 | extern(Windows) void FuncAsync(from!"xlld.sdk.xlcall".LPXLOPER12 n, 32 | from!"xlld.sdk.xlcall".LPXLOPER12 asyncHandle) 33 | nothrow 34 | { 35 | 36 | } 37 | -------------------------------------------------------------------------------- /tests/ut/conv/from.d: -------------------------------------------------------------------------------- 1 | /** 2 | Tests for converting from XLOPER12 to D types. 3 | */ 4 | module ut.conv.from; 5 | 6 | import test; 7 | import xlld.conv.to: toXlOper; 8 | import xlld.conv.from; 9 | 10 | 11 | @("fromXlOper!double") 12 | @system unittest { 13 | import xlld.sdk.framework: freeXLOper; 14 | 15 | TestAllocator allocator; 16 | auto num = 4.0; 17 | auto oper = num.toXlOper(allocator); 18 | auto back = oper.fromXlOper!double(allocator); 19 | back.shouldEqual(num); 20 | 21 | freeXLOper(&oper, allocator); 22 | } 23 | 24 | 25 | /// 26 | @("isNan for fromXlOper!double") 27 | @system unittest { 28 | import std.math: isNaN; 29 | import xlld.memorymanager: allocator; 30 | XLOPER12 oper; 31 | oper.xltype = XlType.xltypeMissing; 32 | fromXlOper!double(&oper, allocator).isNaN.shouldBeTrue; 33 | } 34 | 35 | 36 | @("fromXlOper!double wrong oper type") 37 | @system unittest { 38 | "foo".toXlOper(theGC).fromXlOper!double(theGC).shouldThrowWithMessage("Wrong type for fromXlOper!double"); 39 | } 40 | 41 | 42 | /// 43 | @("fromXlOper!int") 44 | @system unittest { 45 | 42.toXlOper(theGC).fromXlOper!int(theGC).shouldEqual(42); 46 | } 47 | 48 | /// 49 | @("fromXlOper!int when given xltypeNum") 50 | @system unittest { 51 | 42.0.toXlOper(theGC).fromXlOper!int(theGC).shouldEqual(42); 52 | } 53 | 54 | /// 55 | @("0 for fromXlOper!int missing oper") 56 | @system unittest { 57 | XLOPER12 oper; 58 | oper.xltype = XlType.xltypeMissing; 59 | oper.fromXlOper!int(theGC).shouldEqual(0); 60 | } 61 | 62 | @("fromXlOper!int wrong oper type") 63 | @system unittest { 64 | "foo".toXlOper(theGC).fromXlOper!int(theGC).shouldThrowWithMessage("Wrong type for fromXlOper!int"); 65 | } 66 | 67 | /// 68 | @("fromXlOper!string missing") 69 | @system unittest { 70 | import xlld.memorymanager: allocator; 71 | XLOPER12 oper; 72 | oper.xltype = XlType.xltypeMissing; 73 | fromXlOper!string(&oper, allocator).shouldBeNull; 74 | } 75 | 76 | /// 77 | @("fromXlOper!string") 78 | @system unittest { 79 | import std.experimental.allocator: dispose; 80 | import xlld.sdk.framework: freeXLOper; 81 | 82 | TestAllocator allocator; 83 | auto oper = "foo".toXlOper(allocator); 84 | auto str = fromXlOper!string(&oper, allocator); 85 | allocator.numAllocations.shouldEqual(2); 86 | 87 | freeXLOper(&oper, allocator); 88 | str.shouldEqual("foo"); 89 | allocator.dispose(cast(void[])str); 90 | } 91 | 92 | /// 93 | @("fromXlOper!string unicode") 94 | @system unittest { 95 | auto oper = "é".toXlOper(theGC); 96 | auto str = fromXlOper!string(&oper, theGC); 97 | str.shouldEqual("é"); 98 | } 99 | 100 | @("fromXlOper!string allocation failure") 101 | @system unittest { 102 | auto allocator = FailingAllocator(); 103 | "foo".toXlOper(theGC).fromXlOper!string(allocator).shouldThrowWithMessage("Could not allocate memory for array of char"); 104 | } 105 | 106 | 107 | @("fromXlOper!string wrong oper type") 108 | @system unittest { 109 | 42.toXlOper(theGC).fromXlOper!string(theGC).shouldThrowWithMessage("Wrong type for fromXlOper!string"); 110 | } 111 | 112 | /// 113 | @("fromXlOper any double") 114 | @system unittest { 115 | any(5.0, theGC).fromXlOper!Any(theGC).shouldEqual(any(5.0, theGC)); 116 | } 117 | 118 | /// 119 | @("fromXlOper any string") 120 | @system unittest { 121 | any("foo", theGC).fromXlOper!Any(theGC)._impl 122 | .fromXlOper!string(theGC).shouldEqual("foo"); 123 | } 124 | 125 | /// 126 | @("fromXlOper!string[][]") 127 | unittest { 128 | import xlld.memorymanager: allocator; 129 | import xlld.sdk.framework: freeXLOper; 130 | 131 | auto strings = [["foo", "bar", "baz"], ["toto", "titi", "quux"]]; 132 | auto oper = strings.toXlOper(allocator); 133 | scope(exit) freeXLOper(&oper, allocator); 134 | oper.fromXlOper!(string[][])(allocator).shouldEqual(strings); 135 | } 136 | 137 | /// 138 | @("fromXlOper!double[][]") 139 | unittest { 140 | import xlld.memorymanager: allocator; 141 | import xlld.sdk.framework: freeXLOper; 142 | 143 | auto doubles = [[1.0, 2.0], [3.0, 4.0]]; 144 | auto oper = doubles.toXlOper(allocator); 145 | scope(exit) freeXLOper(&oper, allocator); 146 | oper.fromXlOper!(double[][])(allocator).shouldEqual(doubles); 147 | } 148 | 149 | /// 150 | @("fromXlOper!string[][] TestAllocator") 151 | unittest { 152 | import std.experimental.allocator: disposeMultidimensionalArray; 153 | import xlld.sdk.framework: freeXLOper; 154 | 155 | TestAllocator allocator; 156 | auto strings = [["foo", "bar", "baz"], ["toto", "titi", "quux"]]; 157 | auto oper = strings.toXlOper(allocator); 158 | auto backAgain = oper.fromXlOper!(string[][])(allocator); 159 | 160 | allocator.numAllocations.shouldEqual(16); 161 | 162 | freeXLOper(&oper, allocator); 163 | backAgain.shouldEqual(strings); 164 | allocator.disposeMultidimensionalArray(cast(void[][][])backAgain); 165 | } 166 | 167 | /// 168 | @("fromXlOper!string[][] when not all opers are strings") 169 | unittest { 170 | import xlld.conv.misc: multi; 171 | import std.experimental.allocator.mallocator: Mallocator; 172 | alias allocator = theGC; 173 | 174 | const rows = 2; 175 | const cols = 3; 176 | auto array = multi(rows, cols, allocator); 177 | auto opers = array.val.array.lparray[0 .. rows*cols]; 178 | const strings = ["foo", "bar", "baz"]; 179 | const numbers = [1.0, 2.0, 3.0]; 180 | 181 | int i; 182 | foreach(r; 0 .. rows) { 183 | foreach(c; 0 .. cols) { 184 | if(r == 0) 185 | opers[i++] = strings[c].toXlOper(allocator); 186 | else 187 | opers[i++] = numbers[c].toXlOper(allocator); 188 | } 189 | } 190 | 191 | opers[3].fromXlOper!string(allocator).shouldEqual("1.000000"); 192 | // sanity checks 193 | opers[0].fromXlOper!string(allocator).shouldEqual("foo"); 194 | opers[3].fromXlOper!double(allocator).shouldEqual(1.0); 195 | // the actual assertion 196 | array.fromXlOper!(string[][])(allocator).shouldEqual([["foo", "bar", "baz"], 197 | ["1.000000", "2.000000", "3.000000"]]); 198 | } 199 | 200 | 201 | /// 202 | @("fromXlOper!double[][] TestAllocator") 203 | unittest { 204 | import xlld.sdk.framework: freeXLOper; 205 | import std.experimental.allocator: disposeMultidimensionalArray; 206 | 207 | TestAllocator allocator; 208 | auto doubles = [[1.0, 2.0], [3.0, 4.0]]; 209 | auto oper = doubles.toXlOper(allocator); 210 | auto backAgain = oper.fromXlOper!(double[][])(allocator); 211 | 212 | allocator.numAllocations.shouldEqual(4); 213 | 214 | freeXLOper(&oper, allocator); 215 | backAgain.shouldEqual(doubles); 216 | allocator.disposeMultidimensionalArray(backAgain); 217 | } 218 | 219 | /// 220 | @("fromXlOper!string[]") 221 | unittest { 222 | import xlld.memorymanager: allocator; 223 | import xlld.sdk.framework: freeXLOper; 224 | 225 | auto strings = ["foo", "bar", "baz", "toto", "titi", "quux"]; 226 | auto oper = strings.toXlOper(allocator); 227 | scope(exit) freeXLOper(&oper, allocator); 228 | oper.fromXlOper!(string[])(allocator).shouldEqual(strings); 229 | } 230 | 231 | /// 232 | @("fromXlOper!double[] from row") 233 | unittest { 234 | import xlld.sdk.xlcall: xlfCaller; 235 | 236 | XLOPER12 caller; 237 | caller.xltype = XlType.xltypeSRef; 238 | caller.val.sref.ref_.rwFirst = 1; 239 | caller.val.sref.ref_.rwLast = 1; 240 | caller.val.sref.ref_.colFirst = 2; 241 | caller.val.sref.ref_.colLast = 4; 242 | 243 | with(MockXlFunction(xlfCaller, caller)) { 244 | auto doubles = [1.0, 2.0, 3.0, 4.0]; 245 | auto oper = doubles.toXlOper(theGC); 246 | oper.shouldEqualDlang(doubles); 247 | } 248 | } 249 | 250 | /// 251 | @("fromXlOper!double[]") 252 | unittest { 253 | auto doubles = [1.0, 2.0, 3.0, 4.0]; 254 | doubles.toXlOper(theGC).fromXlOper!(double[])(theGC).shouldEqual(doubles); 255 | } 256 | 257 | @("fromXlOper!int[]") 258 | unittest { 259 | auto ints = [1, 2, 3, 4]; 260 | ints.toXlOper(theGC).fromXlOper!(int[])(theGC).shouldEqual(ints); 261 | } 262 | 263 | 264 | /// 265 | @("fromXlOper!string[] TestAllocator") 266 | unittest { 267 | import std.experimental.allocator: disposeMultidimensionalArray; 268 | import xlld.sdk.framework: freeXLOper; 269 | 270 | TestAllocator allocator; 271 | auto strings = ["foo", "bar", "baz", "toto", "titi", "quux"]; 272 | auto oper = strings.toXlOper(allocator); 273 | auto backAgain = oper.fromXlOper!(string[])(allocator); 274 | 275 | allocator.numAllocations.shouldEqual(14); 276 | 277 | backAgain.shouldEqual(strings); 278 | freeXLOper(&oper, allocator); 279 | allocator.disposeMultidimensionalArray(cast(void[][])backAgain); 280 | } 281 | 282 | /// 283 | @("fromXlOper!double[] TestAllocator") 284 | unittest { 285 | import std.experimental.allocator: dispose; 286 | import xlld.sdk.framework: freeXLOper; 287 | 288 | TestAllocator allocator; 289 | auto doubles = [1.0, 2.0, 3.0, 4.0]; 290 | auto oper = doubles.toXlOper(allocator); 291 | auto backAgain = oper.fromXlOper!(double[])(allocator); 292 | 293 | allocator.numAllocations.shouldEqual(2); 294 | 295 | backAgain.shouldEqual(doubles); 296 | freeXLOper(&oper, allocator); 297 | allocator.dispose(backAgain); 298 | } 299 | 300 | @("fromXlOper!double[][] nil") 301 | @system unittest { 302 | XLOPER12 oper; 303 | oper.xltype = XlType.xltypeNil; 304 | double[][] empty; 305 | oper.fromXlOper!(double[][])(theGC).shouldEqual(empty); 306 | } 307 | 308 | 309 | @("fromXlOper any 1D array") 310 | @system unittest { 311 | import xlld.memorymanager: allocatorContext; 312 | with(allocatorContext(theGC)) { 313 | auto array = [any(1.0), any("foo")]; 314 | auto oper = toXlOper(array); 315 | auto back = fromXlOper!(Any[])(oper); 316 | back.shouldEqual(array); 317 | } 318 | } 319 | 320 | 321 | /// 322 | @("fromXlOper Any 2D array") 323 | @system unittest { 324 | import xlld.memorymanager: allocatorContext; 325 | with(allocatorContext(theGC)) { 326 | auto array = [[any(1.0), any(2.0)], [any("foo"), any("bar")]]; 327 | auto oper = toXlOper(array); 328 | auto back = fromXlOper!(Any[][])(oper); 329 | back.shouldEqual(array); 330 | } 331 | } 332 | 333 | /// 334 | @("fromXlOper!DateTime") 335 | @system unittest { 336 | XLOPER12 oper; 337 | auto mockDateTime = MockDateTime(2017, 12, 31, 1, 2, 3); 338 | 339 | const dateTime = oper.fromXlOper!DateTime(theGC); 340 | 341 | dateTime.year.shouldEqual(2017); 342 | dateTime.month.shouldEqual(12); 343 | dateTime.day.shouldEqual(31); 344 | dateTime.hour.shouldEqual(1); 345 | dateTime.minute.shouldEqual(2); 346 | dateTime.second.shouldEqual(3); 347 | } 348 | 349 | @("fromXlOper!DateTime wrong oper type") 350 | @system unittest { 351 | 42.toXlOper(theGC).fromXlOper!DateTime(theGC).shouldThrowWithMessage( 352 | "Wrong type for fromXlOper!DateTime"); 353 | } 354 | 355 | 356 | @("fromXlOper!bool when bool") 357 | @system unittest { 358 | import xlld.sdk.xlcall: XLOPER12, XlType; 359 | XLOPER12 oper; 360 | oper.xltype = XlType.xltypeBool; 361 | oper.val.bool_ = 1; 362 | oper.fromXlOper!bool(theGC).shouldEqual(true); 363 | 364 | oper.val.bool_ = 0; 365 | oper.fromXlOper!bool(theGC).shouldEqual(false); 366 | 367 | oper.val.bool_ = 2; 368 | oper.fromXlOper!bool(theGC).shouldEqual(true); 369 | } 370 | 371 | @("fromXlOper!bool when int") 372 | @system unittest { 373 | 42.toXlOper(theGC).fromXlOper!bool(theGC).shouldEqual(true); 374 | 0.toXlOper(theGC).fromXlOper!bool(theGC).shouldEqual(false); 375 | } 376 | 377 | @("fromXlOper!bool when double") 378 | @system unittest { 379 | 33.3.toXlOper(theGC).fromXlOper!bool(theGC).shouldEqual(true); 380 | 0.0.toXlOper(theGC).fromXlOper!bool(theGC).shouldEqual(false); 381 | } 382 | 383 | 384 | @("fromXlOper!bool when string") 385 | @system unittest { 386 | "true".toXlOper(theGC).fromXlOper!bool(theGC).shouldEqual(true); 387 | "True".toXlOper(theGC).fromXlOper!bool(theGC).shouldEqual(true); 388 | "TRUE".toXlOper(theGC).fromXlOper!bool(theGC).shouldEqual(true); 389 | "false".toXlOper(theGC).fromXlOper!bool(theGC).shouldEqual(false); 390 | } 391 | 392 | @("fromXlOper!enum") 393 | @system unittest { 394 | enum Enum { 395 | foo, bar, baz, 396 | } 397 | 398 | "bar".toXlOper(theGC).fromXlOper!Enum(theGC).shouldEqual(Enum.bar); 399 | "quux".toXlOper(theGC).fromXlOper!Enum(theGC).shouldThrowWithMessage("Enum does not have a member named 'quux'"); 400 | } 401 | 402 | @("fromXlOper!enum wrong type") 403 | @system unittest { 404 | enum Enum { foo, bar, baz, } 405 | 406 | 42.toXlOper(theGC).fromXlOper!Enum(theGC).shouldThrowWithMessage( 407 | "Wrong type for fromXlOper!Enum"); 408 | } 409 | 410 | 411 | @("1D array to struct") 412 | @system unittest { 413 | static struct Foo { int x, y; } 414 | [2, 3].toXlOper(theGC).fromXlOper!Foo(theGC).shouldEqual(Foo(2, 3)); 415 | } 416 | 417 | @("wrong oper type to struct") 418 | @system unittest { 419 | static struct Foo { int x, y; } 420 | 421 | 2.toXlOper(theGC).fromXlOper!Foo(theGC).shouldThrowWithMessage( 422 | "Can only convert arrays to structs. Must be either 1xN, Nx1, 2xN or Nx2"); 423 | } 424 | 425 | @("1D array to struct with wrong length") 426 | @system unittest { 427 | 428 | import nogc: NoGcException; 429 | 430 | static struct Foo { int x, y; } 431 | 432 | [2, 3, 4].toXlOper(theGC).fromXlOper!Foo(theGC).shouldThrowWithMessage!NoGcException( 433 | "1D array length must match number of members in Foo. Expected 2, got 3"); 434 | 435 | [2].toXlOper(theGC).fromXlOper!Foo(theGC).shouldThrowWithMessage!NoGcException( 436 | "1D array length must match number of members in Foo. Expected 2, got 1"); 437 | } 438 | 439 | @("1D array to struct with wrong type") 440 | @system unittest { 441 | static struct Foo { int x, y; } 442 | 443 | ["foo", "bar"].toXlOper(theGC).fromXlOper!Foo(theGC).shouldThrowWithMessage( 444 | "Wrong type converting oper to Foo"); 445 | } 446 | 447 | @("2D horizontal array to struct") 448 | unittest { 449 | import xlld.memorymanager: allocatorContext; 450 | 451 | static struct Foo { int x, y, z; } 452 | 453 | with(allocatorContext(theGC)) { 454 | [[any("x"), any("y"), any("z")], [any(2), any(3), any(4)]].toFrom!Foo.shouldEqual(Foo(2, 3, 4)); 455 | } 456 | } 457 | 458 | @("2D vertical array to struct") 459 | unittest { 460 | import xlld.memorymanager: allocatorContext; 461 | 462 | static struct Foo { int x, y, z; } 463 | 464 | with(allocatorContext(theGC)) { 465 | [[any("x"), any(2)], [any("y"), any(3)], [any("z"), any(4)]].toFrom!Foo.shouldEqual(Foo(2, 3, 4)); 466 | } 467 | } 468 | 469 | 470 | @("2D array wrong size") 471 | unittest { 472 | import xlld.memorymanager: allocatorContext; 473 | import nogc: NoGcException; 474 | 475 | static struct Foo { int x, y, z; } 476 | 477 | with(allocatorContext(theGC)) { 478 | [[any("x"), any(2)], [any("y"), any(3)], [any("z"), any(4)], [any("w"), any(5)]].toFrom!Foo. 479 | shouldThrowWithMessage!NoGcException("2D array must be 2x3 or 3x2 for Foo"); 480 | } 481 | } 482 | 483 | 484 | @("PriceBar[]") 485 | @system /*allocatorContext*/ unittest { 486 | 487 | import xlld.memorymanager: allocatorContext; 488 | 489 | static struct PriceBar { 490 | double open, high, low, close; 491 | } 492 | 493 | with(allocatorContext(theGC)) { 494 | auto array = 495 | [ 496 | [any("open"), any("high"), any("low"), any("close")], 497 | [any(1.1), any(2.2), any(3.3), any(4.4)], 498 | [any(2.1), any(3.2), any(4.3), any(5.4)], 499 | ]; 500 | 501 | array.toFrom!(PriceBar[]).should == [ 502 | PriceBar(1.1, 2.2, 3.3, 4.4), 503 | PriceBar(2.1, 3.2, 4.3, 5.4), 504 | ]; 505 | } 506 | } 507 | 508 | 509 | @("fromXlOperCoerce") 510 | unittest { 511 | double[][] doubles = [[1, 2, 3, 4], [11, 12, 13, 14]]; 512 | auto doublesOper = toSRef(doubles, theGC); 513 | doublesOper.fromXlOper!(double[][])(theGC).shouldThrowWithMessage( 514 | "fromXlOper: oper not of multi type"); 515 | doublesOper.fromXlOperCoerce!(double[][]).shouldEqual(doubles); 516 | } 517 | 518 | 519 | private auto toFrom(R, T)(T val) { 520 | import std.experimental.allocator.gc_allocator: GCAllocator; 521 | return val.toXlOper(GCAllocator.instance).fromXlOper!R(GCAllocator.instance); 522 | } 523 | 524 | 525 | @("fromXlOper!(Tuple!(double, double))") 526 | @system unittest { 527 | import std.typecons: tuple, Tuple; 528 | import xlld.conv.from: fromXlOper; 529 | tuple(22.2, 33.3) 530 | .toXlOper(theGC) 531 | .fromXlOper!(Tuple!(double, double))(theGC) 532 | .shouldEqual(tuple(22.2, 33.3)); 533 | } 534 | 535 | @("fromXlOper!(Tuple!(int, int, int))") 536 | @system unittest { 537 | import std.typecons: tuple, Tuple; 538 | import xlld.conv.from: fromXlOper; 539 | tuple(1, 2, 3) 540 | .toXlOper(theGC) 541 | .fromXlOper!(Tuple!(int, int, int))(theGC) 542 | .shouldEqual(tuple(1, 2, 3)); 543 | } 544 | 545 | 546 | @("double[].fromXlOper!int[]") 547 | @system unittest { 548 | [1.0, 2.0] 549 | .toXlOper(theGC) 550 | .fromXlOper!(int[])(theGC) 551 | .should == [1, 2]; 552 | } 553 | 554 | 555 | @("autoFreeOper") 556 | @system unittest { 557 | import xlld.conv.to: toAutoFreeOper; 558 | [42.0].toAutoFreeOper.fromXlOper!(double[])(theGC).should == [42.0]; 559 | [42].toAutoFreeOper.fromXlOper!(int[])(theGC).should == [42]; 560 | } 561 | -------------------------------------------------------------------------------- /tests/ut/conv/misc.d: -------------------------------------------------------------------------------- 1 | module ut.conv.misc; 2 | 3 | 4 | import test; 5 | import xlld.conv.misc; 6 | import xlld.conv.to: toXlOper; 7 | 8 | 9 | @("isUserStruct") 10 | @safe pure unittest { 11 | import std.datetime: DateTime; 12 | import std.typecons: Tuple; 13 | import xlld.any: Any; 14 | 15 | static struct Foo {} 16 | 17 | static assert( isUserStruct!Foo); 18 | static assert(!isUserStruct!Any); 19 | static assert(!isUserStruct!DateTime); 20 | static assert(!isUserStruct!(Tuple!(int, int))); 21 | static assert(!isUserStruct!(Tuple!(int, string))); 22 | static assert(!isUserStruct!(Tuple!(int, Foo, double))); 23 | } 24 | 25 | /// 26 | @("dup") 27 | @safe unittest { 28 | auto int_ = 42.toXlOper(theGC); 29 | int_.dup(theGC).shouldEqualDlang(42); 30 | 31 | auto double_ = (33.3).toXlOper(theGC); 32 | double_.dup(theGC).shouldEqualDlang(33.3); 33 | 34 | auto string_ = "foobar".toXlOper(theGC); 35 | string_.dup(theGC).shouldEqualDlang("foobar"); 36 | 37 | auto array = () @trusted { 38 | return [ 39 | ["foo", "bar", "baz"], 40 | ["quux", "toto", "brzz"] 41 | ] 42 | .toXlOper(theGC); 43 | }(); 44 | 45 | array.dup(theGC).shouldEqualDlang( 46 | [ 47 | ["foo", "bar", "baz"], 48 | ["quux", "toto", "brzz"], 49 | ] 50 | ); 51 | } 52 | 53 | 54 | @("dup string allocator fails") 55 | @safe unittest { 56 | auto allocator = FailingAllocator(); 57 | "foo".toXlOper(theGC).dup(allocator).shouldThrowWithMessage("Failed to allocate memory in dup"); 58 | } 59 | 60 | 61 | @("dup multi allocator fails") 62 | @safe unittest { 63 | auto allocator = FailingAllocator(); 64 | auto oper = () @trusted { return [33.3].toXlOper(theGC); }(); 65 | oper.dup(allocator).shouldThrowWithMessage("Failed to allocate memory in dup"); 66 | } 67 | 68 | 69 | /// 70 | @("operStringLength") 71 | @safe unittest { 72 | auto oper = "foobar".toXlOper(theGC); 73 | const length = () @nogc { return operStringLength(oper); }(); 74 | length.shouldEqual(6); 75 | } 76 | 77 | 78 | /// 79 | @("multi") 80 | @safe unittest { 81 | auto allocator = FailingAllocator(); 82 | multi(2, 3, allocator).shouldThrowWithMessage("Failed to allocate memory for multi oper"); 83 | } 84 | 85 | 86 | @("string.autofree.dup") 87 | @system unittest { 88 | import xlld.conv.to: toAutoFreeOper; 89 | "foo".toAutoFreeOper.dup(theGC).shouldEqualDlang("foo"); 90 | } 91 | -------------------------------------------------------------------------------- /tests/ut/conv/to.d: -------------------------------------------------------------------------------- 1 | /** 2 | Tests for converting from D types to XLOPER12 3 | */ 4 | module ut.conv.to; 5 | 6 | import test; 7 | import xlld.conv.to; 8 | 9 | 10 | /// 11 | @("int") 12 | unittest { 13 | auto oper = 42.toXlOper(theGC); 14 | oper.xltype.shouldEqual(XlType.xltypeInt); 15 | oper.val.w.shouldEqual(42); 16 | } 17 | 18 | 19 | /// 20 | @("double") 21 | unittest { 22 | auto oper = (42.0).toXlOper(theGC); 23 | oper.xltype.shouldEqual(XlType.xltypeNum); 24 | oper.val.num.shouldEqual(42.0); 25 | } 26 | 27 | /// 28 | @("string.utf8") 29 | @system unittest { 30 | import xlld.memorymanager: allocator; 31 | import xlld.sdk.framework: freeXLOper; 32 | import std.conv: to; 33 | 34 | const str = "foo"; 35 | auto oper = str.toXlOper(allocator); 36 | scope(exit) freeXLOper(&oper, allocator); 37 | 38 | oper.xltype.shouldEqual(XlType.xltypeStr); 39 | (cast(int)oper.val.str[0]).shouldEqual(str.length); 40 | (cast(wchar*)oper.val.str)[1 .. str.length + 1].to!string.shouldEqual(str); 41 | } 42 | 43 | 44 | /// 45 | @("string.utf16") 46 | @system unittest { 47 | import xlld.memorymanager: allocator; 48 | import xlld.sdk.framework: freeXLOper; 49 | 50 | const str = "foo"w; 51 | auto oper = str.toXlOper(allocator); 52 | scope(exit) freeXLOper(&oper, allocator); 53 | 54 | oper.xltype.shouldEqual(XlType.xltypeStr); 55 | (cast(int)oper.val.str[0]).shouldEqual(str.length); 56 | (cast(wchar*)oper.val.str)[1 .. str.length + 1].shouldEqual(str); 57 | } 58 | 59 | /// 60 | @("string.TestAllocator") 61 | @system unittest { 62 | import xlld.sdk.framework: freeXLOper; 63 | 64 | auto allocator = TestAllocator(); 65 | auto oper = "foo".toXlOper(allocator); 66 | allocator.numAllocations.shouldEqual(1); 67 | freeXLOper(&oper, allocator); 68 | } 69 | 70 | /// 71 | @("string.unicode") 72 | @system unittest { 73 | import std.utf: byWchar; 74 | import std.array: array; 75 | 76 | "é".byWchar.array.length.shouldEqual(1); 77 | "é"w.byWchar.array.length.shouldEqual(1); 78 | 79 | auto oper = "é".toXlOper(theGC); 80 | const ushort length = oper.val.str[0]; 81 | length.shouldEqual("é"w.length); 82 | } 83 | 84 | @("string.allocator.fail") 85 | @safe unittest { 86 | import xlld.conv.misc: dup; 87 | auto allocator = FailingAllocator(); 88 | "foo".toXlOper(theGC).dup(allocator).shouldThrowWithMessage("Failed to allocate memory in dup"); 89 | } 90 | 91 | 92 | /// 93 | @("array.string.2d") 94 | @system unittest { 95 | import xlld.memorymanager: allocator; 96 | import xlld.sdk.framework: freeXLOper; 97 | 98 | auto oper = [["foo", "bar", "baz"], ["toto", "titi", "quux"]].toXlOper(allocator); 99 | scope(exit) freeXLOper(&oper, allocator); 100 | 101 | oper.xltype.shouldEqual(XlType.xltypeMulti); 102 | oper.val.array.rows.shouldEqual(2); 103 | oper.val.array.columns.shouldEqual(3); 104 | auto opers = oper.val.array.lparray[0 .. oper.val.array.rows * oper.val.array.columns]; 105 | 106 | opers[0].shouldEqualDlang("foo"); 107 | opers[3].shouldEqualDlang("toto"); 108 | opers[5].shouldEqualDlang("quux"); 109 | } 110 | 111 | /// 112 | @("array.string.2d.TestAllocator") 113 | @system unittest { 114 | import xlld.sdk.framework: freeXLOper; 115 | 116 | TestAllocator allocator; 117 | auto oper = [["foo", "bar", "baz"], ["toto", "titi", "quux"]].toXlOper(allocator); 118 | allocator.numAllocations.shouldEqual(7); 119 | freeXLOper(&oper, allocator); 120 | } 121 | 122 | /// 123 | @("array.double.2d") 124 | @system unittest { 125 | import xlld.sdk.framework: freeXLOper; 126 | 127 | TestAllocator allocator; 128 | auto oper = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]].toXlOper(allocator); 129 | allocator.numAllocations.shouldEqual(1); 130 | freeXLOper(&oper, allocator); 131 | } 132 | 133 | @("array.double.2d.allocator.fail") 134 | @system unittest { 135 | auto allocator = FailingAllocator(); 136 | [33.3].toXlOper(allocator).shouldThrowWithMessage("Failed to allocate memory for multi oper"); 137 | } 138 | 139 | @("array.double.2d.wrong shape") 140 | @system unittest { 141 | [[33.3], [1.0, 2.0]].toXlOper(theGC).shouldThrowWithMessage("# of columns must all be the same and aren't"); 142 | } 143 | 144 | /// 145 | @("array.string.1d") 146 | @system unittest { 147 | import xlld.sdk.framework: freeXLOper; 148 | 149 | TestAllocator allocator; 150 | auto oper = ["foo", "bar", "baz", "toto", "titi", "quux"].toXlOper(allocator); 151 | allocator.numAllocations.shouldEqual(7); 152 | freeXLOper(&oper, allocator); 153 | } 154 | 155 | /// 156 | @("any.double") 157 | unittest { 158 | any(5.0, theGC).toXlOper(theGC).shouldEqualDlang(5.0); 159 | } 160 | 161 | /// 162 | @("any.string") 163 | unittest { 164 | any("foo", theGC).toXlOper(theGC).shouldEqualDlang("foo"); 165 | } 166 | 167 | /// 168 | @("array.array.double.2d") 169 | unittest { 170 | any([[1.0, 2.0], [3.0, 4.0]], theGC) 171 | .toXlOper(theGC).shouldEqualDlang([[1.0, 2.0], [3.0, 4.0]]); 172 | } 173 | 174 | /// 175 | @("any.array.string.2d") 176 | unittest { 177 | any([["foo", "bar"], ["quux", "toto"]], theGC) 178 | .toXlOper(theGC).shouldEqualDlang([["foo", "bar"], ["quux", "toto"]]); 179 | } 180 | 181 | 182 | /// 183 | @("any.array.1d.0") 184 | unittest { 185 | import xlld.memorymanager: allocatorContext; 186 | 187 | with(allocatorContext(theGC)) { 188 | auto oper = toXlOper([any(42.0), any("foo")]); 189 | oper.xltype.shouldEqual(XlType.xltypeMulti); 190 | oper.val.array.lparray[0].shouldEqualDlang(42.0); 191 | oper.val.array.lparray[1].shouldEqualDlang("foo"); 192 | } 193 | } 194 | 195 | 196 | /// 197 | @("any.array.1d.1") 198 | unittest { 199 | const a = any([any(1.0, theGC), any("foo", theGC)], 200 | theGC); 201 | auto oper = a.toXlOper(theGC); 202 | oper.xltype.shouldEqual(XlType.xltypeMulti); 203 | 204 | const rows = oper.val.array.rows; 205 | const cols = oper.val.array.columns; 206 | auto opers = oper.val.array.lparray[0 .. rows * cols]; 207 | opers[0].shouldEqualDlang(1.0); 208 | opers[1].shouldEqualDlang("foo"); 209 | } 210 | 211 | /// 212 | @("any.array.2d.0") 213 | unittest { 214 | import xlld.memorymanager: allocatorContext; 215 | 216 | with(allocatorContext(theGC)) { 217 | auto oper = toXlOper([[any(42.0), any("foo"), any("quux")], [any("bar"), any(7.0), any("toto")]]); 218 | oper.xltype.shouldEqual(XlType.xltypeMulti); 219 | oper.val.array.rows.shouldEqual(2); 220 | oper.val.array.columns.shouldEqual(3); 221 | oper.val.array.lparray[0].shouldEqualDlang(42.0); 222 | oper.val.array.lparray[1].shouldEqualDlang("foo"); 223 | oper.val.array.lparray[2].shouldEqualDlang("quux"); 224 | oper.val.array.lparray[3].shouldEqualDlang("bar"); 225 | oper.val.array.lparray[4].shouldEqualDlang(7.0); 226 | oper.val.array.lparray[5].shouldEqualDlang("toto"); 227 | } 228 | } 229 | 230 | 231 | /// 232 | @("any.array.2d.1") 233 | unittest { 234 | const a = any([ 235 | [any(1.0, theGC), any(2.0, theGC)], 236 | [any("foo", theGC), any("bar", theGC)] 237 | ], 238 | theGC); 239 | auto oper = a.toXlOper(theGC); 240 | oper.xltype.shouldEqual(XlType.xltypeMulti); 241 | 242 | const rows = oper.val.array.rows; 243 | const cols = oper.val.array.columns; 244 | auto opers = oper.val.array.lparray[0 .. rows * cols]; 245 | opers[0].shouldEqualDlang(1.0); 246 | opers[1].shouldEqualDlang(2.0); 247 | opers[2].shouldEqualDlang("foo"); 248 | opers[3].shouldEqualDlang("bar"); 249 | } 250 | 251 | @("DateTime") 252 | @safe unittest { 253 | 254 | import xlld.sdk.xlcall: xlfDate, xlfTime; 255 | 256 | const dateTime = DateTime(2017, 12, 31, 1, 2, 3); 257 | { 258 | auto mockDate = MockXlFunction(xlfDate, 0.1.toXlOper(theGC)); 259 | auto mockTime = MockXlFunction(xlfTime, 0.2.toXlOper(theGC)); 260 | 261 | auto oper = dateTime.toXlOper(theGC); 262 | 263 | oper.xltype.shouldEqual(XlType.xltypeNum); 264 | oper.val.num.shouldApproxEqual(0.3); 265 | } 266 | 267 | { 268 | auto mockDate = MockXlFunction(xlfDate, 1.1.toXlOper(theGC)); 269 | auto mockTime = MockXlFunction(xlfTime, 1.2.toXlOper(theGC)); 270 | 271 | auto oper = dateTime.toXlOper(theGC); 272 | 273 | oper.xltype.shouldEqual(XlType.xltypeNum); 274 | oper.val.num.shouldApproxEqual(2.3); 275 | } 276 | } 277 | 278 | /// 279 | @("bool") 280 | @system unittest { 281 | import xlld.sdk.xlcall: XlType; 282 | { 283 | const oper = true.toXlOper(theGC); 284 | oper.xltype.shouldEqual(XlType.xltypeBool); 285 | oper.val.bool_.shouldEqual(1); 286 | } 287 | 288 | { 289 | const oper = false.toXlOper(theGC); 290 | oper.xltype.shouldEqual(XlType.xltypeBool); 291 | oper.val.bool_.shouldEqual(0); 292 | } 293 | } 294 | 295 | @("enum") 296 | @safe unittest { 297 | 298 | enum Enum { 299 | foo, 300 | bar, 301 | baz, 302 | } 303 | 304 | Enum.bar.toXlOper(theGC).shouldEqualDlang("bar"); 305 | } 306 | 307 | 308 | @("struct") 309 | @safe unittest { 310 | static struct Foo { int x, y; } 311 | Foo(2, 3).toXlOper(theGC).shouldEqualDlang("Foo(2, 3)"); 312 | } 313 | 314 | @("Tuple!(int, int)") 315 | @safe unittest { 316 | import std.typecons: tuple; 317 | auto oper = tuple(42, 33).toXlOper(theGC); 318 | oper.shouldEqualDlang([42, 33]); 319 | } 320 | 321 | @("Tuple!(DateTime, double)[]") 322 | @system unittest { 323 | import xlld.conv.misc: stripMemoryBitmask; 324 | import xlld.test.util: MockDates; 325 | import xlld.conv.from: fromXlOper; 326 | import std.typecons: tuple; 327 | import std.conv: to; 328 | import std.algorithm: map; 329 | 330 | auto dates = MockDates([0.1, 0.2, 0.3]); 331 | auto times = MockTimes([0.1, 0.2, 0.3]); 332 | 333 | auto oper = [ 334 | tuple(DateTime(2017, 1, 1), 11.1), 335 | tuple(DateTime(2018, 1, 1), 22.2), 336 | tuple(DateTime(2019, 1, 1), 33.3), 337 | ].toXlOper(theGC); 338 | 339 | void assertMulti(in XLOPER12 oper) { 340 | if(oper.xltype.stripMemoryBitmask != XlType.xltypeMulti) { 341 | if(oper.xltype.stripMemoryBitmask == XlType.xltypeStr) 342 | throw new Exception(oper.fromXlOper!string(theGC)); 343 | else 344 | throw new Exception("Oper not of multi type: " ~ to!string(oper)); 345 | } 346 | } 347 | 348 | 349 | assertMulti(oper); 350 | oper.val.array.rows.shouldEqual(1); 351 | oper.val.array.columns.shouldEqual(3); 352 | 353 | auto dateTimes = MockDateTimes(DateTime(2017, 1, 1), DateTime(2018, 1, 1), DateTime(2019, 1, 1)); 354 | 355 | auto elts = oper.val.array.lparray[0 .. 3]; 356 | foreach(elt; elts) assertMulti(elt); 357 | 358 | elts[0].val.array.lparray[0].fromXlOper!DateTime(theGC).shouldEqual(DateTime(2017, 1, 1)); 359 | elts[0].val.array.lparray[1].fromXlOper!double(theGC).shouldEqual(11.1); 360 | 361 | elts[1].val.array.lparray[0].fromXlOper!DateTime(theGC).shouldEqual(DateTime(2018, 1, 1)); 362 | elts[1].val.array.lparray[1].fromXlOper!double(theGC).shouldEqual(22.2); 363 | 364 | elts[2].val.array.lparray[0].fromXlOper!DateTime(theGC).shouldEqual(DateTime(2019, 1, 1)); 365 | elts[2].val.array.lparray[1].fromXlOper!double(theGC).shouldEqual(33.3); 366 | } 367 | 368 | @("struct.array") 369 | @system unittest { 370 | import test.d_funcs: DateAndString; 371 | import std.datetime: DateTime; 372 | auto oper = [DateAndString(DateTime(2017, 1, 2), "foobar")].toXlOper(theGC); 373 | } 374 | 375 | 376 | @("range.1d.int") 377 | @system unittest { 378 | import std.range: iota; 379 | import std.experimental.allocator.mallocator: Mallocator; 380 | 381 | auto range = 5.iota; 382 | auto oper = () @nogc { return range.toXlOper(Mallocator.instance); }(); 383 | oper.shouldEqualDlang([0, 1, 2, 3, 4]); 384 | } 385 | 386 | 387 | @("range.2d.int") 388 | @system unittest { 389 | import std.range: iota, enumerate, repeat, take; 390 | import std.algorithm: map; 391 | import std.experimental.allocator.mallocator: Mallocator; 392 | 393 | auto range = 3 394 | .iota 395 | .map!(i => i.repeat.take(2)) 396 | ; 397 | auto oper = () @nogc { return range.toXlOper(Mallocator.instance); }(); 398 | oper.shouldEqualDlang([[0, 0], [1, 1], [2, 2]]); 399 | } 400 | 401 | 402 | @("const(char)[]") 403 | @system unittest { 404 | const(char)[] str = "foo"; 405 | str.toXlOper(theGC).shouldEqualDlang("foo"); 406 | } 407 | -------------------------------------------------------------------------------- /tests/ut/func/xlf.d: -------------------------------------------------------------------------------- 1 | module ut.func.xlf; 2 | 3 | import test; 4 | import xlld.func.xlf; 5 | 6 | @("callerCell throws if caller is string") 7 | unittest { 8 | import xlld.sdk.xlcall: xlfCaller; 9 | import xlld.conv.to: toXlOper; 10 | 11 | with(MockXlFunction(xlfCaller, "foobar".toXlOper(theGC))) { 12 | try 13 | callerCell; 14 | catch (Exception e) 15 | e.msg.should == "Caller not a cell"; 16 | } 17 | } 18 | 19 | 20 | @("callerCell with SRef") 21 | unittest { 22 | import xlld.sdk.xlcall: xlfCaller; 23 | 24 | with(MockXlFunction(xlfCaller, "foobar".toSRef(theGC))) { 25 | auto oper = callerCell; 26 | oper.shouldEqualDlang("foobar"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/ut/issues.d: -------------------------------------------------------------------------------- 1 | module ut.issues; 2 | 3 | 4 | import test; 5 | import xlld; 6 | 7 | 8 | @("74") 9 | @safe unittest { 10 | auto oper = () @trusted { return [[1, 2, 3], [4, 5, 6]].toXlOper(theGC); }(); 11 | auto back = oper.fromXlOper!(Any[][])(theGC); 12 | } 13 | 14 | 15 | @("89") 16 | @safe unittest { 17 | import ut.wrap.wrapped: appendFoo; 18 | import xlld.memorymanager: allocator; 19 | auto arg = toXlOper("quux", allocator); 20 | appendFoo(&arg).shouldEqualDlang("quux_foo"); 21 | } 22 | 23 | 24 | @("92") 25 | @safe pure unittest { 26 | wrapModuleMember!("test.d_funcs", "core.stdc.stdio").should == ""; 27 | } 28 | -------------------------------------------------------------------------------- /tests/ut/misc.d: -------------------------------------------------------------------------------- 1 | module ut.misc; 2 | 3 | import test; 4 | import xlld.any; 5 | import xlld.sdk.xll; 6 | 7 | @("opEquals str") 8 | unittest { 9 | any("foo", theGC).shouldEqual(any("foo", theGC)); 10 | any("foo", theGC).shouldNotEqual(any("bar", theGC)); 11 | any("foo", theGC).shouldNotEqual(any(33.3, theGC)); 12 | } 13 | 14 | @("opEquals multi") 15 | unittest { 16 | any([1.0, 2.0], theGC).shouldEqual(any([1.0, 2.0], theGC)); 17 | any([1.0, 2.0], theGC).shouldNotEqual(any("foos", theGC)); 18 | any([1.0, 2.0], theGC).shouldNotEqual(any([2.0, 2.0], theGC)); 19 | } 20 | 21 | 22 | @("registerAutoClose delegate") 23 | unittest { 24 | int i; 25 | registerAutoCloseFunc({ ++i; }); 26 | callRegisteredAutoCloseFuncs(); 27 | i.shouldEqual(1); 28 | } 29 | 30 | @("registerAutoClose function") 31 | unittest { 32 | const old = gAutoCloseCounter; 33 | registerAutoCloseFunc(&testAutoCloseFunc); 34 | callRegisteredAutoCloseFuncs(); 35 | (gAutoCloseCounter - old).shouldEqual(1); 36 | } 37 | 38 | 39 | int gAutoCloseCounter; 40 | 41 | 42 | void testAutoCloseFunc() nothrow { 43 | ++gAutoCloseCounter; 44 | } 45 | -------------------------------------------------------------------------------- /tests/ut/wrap/all.d: -------------------------------------------------------------------------------- 1 | module ut.wrap.all; 2 | 3 | import test; 4 | import ut.wrap.wrapped; 5 | import xlld.wrap; 6 | import xlld.conv.to: toXlOper; 7 | 8 | /// 9 | @("wrapAll function that returns Any[][]") 10 | @safe unittest { 11 | import xlld.memorymanager: autoFree; 12 | 13 | auto oper = [[1.0, 2.0], [3.0, 4.0]].toSRef(theGC); 14 | auto arg = () @trusted { return &oper; }(); 15 | auto ret = DoubleArrayToAnyArray(arg); 16 | scope(exit) () @trusted { autoFree(ret); }(); // usually done by Excel 17 | 18 | auto opers = () @trusted { return ret.val.array.lparray[0 .. 4]; }(); 19 | opers[0].shouldEqualDlang(2.0); 20 | opers[1].shouldEqualDlang(6.0); 21 | opers[2].shouldEqualDlang("3quux"); 22 | opers[3].shouldEqualDlang("4toto"); 23 | } 24 | 25 | /// 26 | @("wrapAll function that takes Any[][]") 27 | unittest { 28 | import xlld.memorymanager: allocatorContext; 29 | 30 | XLOPER12* ret; 31 | with(allocatorContext(theGC)) { 32 | auto oper = [[any(1.0), any(2.0)], [any(3.0), any(4.0)], [any("foo"), any("bar")]].toXlOper(theGC); 33 | auto arg = () @trusted { return &oper; }(); 34 | ret = AnyArrayToDoubleArray(arg); 35 | } 36 | 37 | auto opers = () @trusted { return ret.val.array.lparray[0 .. 2]; }(); 38 | opers[0].shouldEqualDlang(3.0); // number of rows 39 | opers[1].shouldEqualDlang(2.0); // number of columns 40 | } 41 | 42 | 43 | /// 44 | @("wrapAll Any[][] -> Any[][]") 45 | unittest { 46 | import xlld.memorymanager: allocatorContext; 47 | import xlld.any: Any; 48 | 49 | XLOPER12* ret; 50 | with(allocatorContext(theGC)) { 51 | auto oper = [[any(1.0), any(2.0)], [any(3.0), any(4.0)], [any("foo"), any("bar")]].toXlOper(theGC); 52 | auto arg = () @trusted { return &oper; }(); 53 | ret = AnyArrayToAnyArray(arg); 54 | } 55 | 56 | auto opers = () @trusted { return ret.val.array.lparray[0 .. 6]; }(); 57 | ret.val.array.rows.shouldEqual(3); 58 | ret.val.array.columns.shouldEqual(2); 59 | opers[0].shouldEqualDlang(1.0); 60 | opers[1].shouldEqualDlang(2.0); 61 | opers[2].shouldEqualDlang(3.0); 62 | opers[3].shouldEqualDlang(4.0); 63 | opers[4].shouldEqualDlang("foo"); 64 | opers[5].shouldEqualDlang("bar"); 65 | } 66 | 67 | /// 68 | @("wrapAll Any[][] -> Any[][] -> Any[][]") 69 | unittest { 70 | import xlld.memorymanager: allocatorContext; 71 | import xlld.any: Any; 72 | 73 | XLOPER12* ret; 74 | with(allocatorContext(theGC)) { 75 | auto oper = [[any(1.0), any("foo"), any(3.0)], [any(4.0), any(5.0), any(6.0)]].toXlOper(theGC); 76 | auto arg = () @trusted { return &oper; }(); 77 | ret = FirstOfTwoAnyArrays(arg, arg); 78 | } 79 | 80 | auto opers = () @trusted { return ret.val.array.lparray[0 .. 6]; }(); 81 | ret.val.array.rows.shouldEqual(2); 82 | ret.val.array.columns.shouldEqual(3); 83 | opers[0].shouldEqualDlang(1.0); 84 | opers[1].shouldEqualDlang("foo"); 85 | opers[2].shouldEqualDlang(3.0); 86 | opers[3].shouldEqualDlang(4.0); 87 | opers[4].shouldEqualDlang(5.0); 88 | opers[5].shouldEqualDlang(6.0); 89 | } 90 | 91 | /// 92 | @("wrapAll overloaded functions are not wrapped") 93 | unittest { 94 | auto double_ = (42.0).toXlOper(theGC); 95 | auto string_ = "foobar".toXlOper(theGC); 96 | static assert(!__traits(compiles, Overloaded(&double_).shouldEqualDlang(84.0))); 97 | static assert(!__traits(compiles, Overloaded(&string_).shouldEqualDlang(84.0))); 98 | } 99 | 100 | /// 101 | @("wrapAll bool -> int") 102 | @safe unittest { 103 | auto string_ = "true".toXlOper(theGC); 104 | () @trusted { BoolToInt(&string_).shouldEqualDlang(1); }(); 105 | } 106 | 107 | /// 108 | @("wrapAll FuncAddEverything") 109 | unittest { 110 | import xlld.memorymanager: allocator; 111 | 112 | auto arg = toSRef(cast(double[][])[[1, 2, 3, 4], [11, 12, 13, 14]], allocator); 113 | FuncAddEverything(&arg).shouldEqualDlang(60.0); 114 | } 115 | 116 | 117 | 118 | /// 119 | @("wrapAll FuncReturnArrayNoGC") 120 | @safe unittest { 121 | import xlld.test.util: gTestAllocator; 122 | import xlld.memorymanager: gTempAllocator; 123 | 124 | // this is needed since gTestAllocator is global, so we can't rely 125 | // on its destructor 126 | scope(exit) gTestAllocator.verify; 127 | 128 | double[4] args = [1.0, 2.0, 3.0, 4.0]; 129 | auto oper = args[].toSRef(gTempAllocator); // don't use gTestAllocator 130 | auto arg = () @trusted { return &oper; }(); 131 | auto ret = () @safe @nogc { return FuncReturnArrayNoGc(arg); }(); 132 | ret.shouldEqualDlang([2.0, 4.0, 6.0, 8.0]); 133 | } 134 | -------------------------------------------------------------------------------- /tests/ut/wrap/module_.d: -------------------------------------------------------------------------------- 1 | module ut.wrap.module_; 2 | 3 | import test; 4 | import ut.wrap.wrapped; 5 | import xlld.wrap; 6 | import xlld.conv.to: toXlOper; 7 | import std.datetime; 8 | import std.experimental.allocator.mallocator: Mallocator; 9 | alias theMallocator = Mallocator.instance; 10 | 11 | 12 | /// 13 | @("Wrap double[][] -> double") 14 | @system unittest { 15 | import xlld.memorymanager: allocator; 16 | 17 | auto arg = toSRef(cast(double[][])[[1, 2, 3, 4], [11, 12, 13, 14]], allocator); 18 | FuncAddEverything(&arg).shouldEqualDlang(60.0); 19 | 20 | arg = toSRef(cast(double[][])[[0, 1, 2, 3], [4, 5, 6, 7]], allocator); 21 | FuncAddEverything(&arg).shouldEqualDlang(28.0); 22 | } 23 | 24 | /// 25 | @("Wrap double[][] -> double[][]") 26 | @system unittest { 27 | import xlld.memorymanager: allocator; 28 | 29 | auto arg = toSRef(cast(double[][])[[1, 2, 3, 4], [11, 12, 13, 14]], allocator); 30 | FuncTripleEverything(&arg).shouldEqualDlang(cast(double[][])[[3, 6, 9, 12], [33, 36, 39, 42]]); 31 | 32 | arg = toSRef(cast(double[][])[[0, 1, 2, 3], [4, 5, 6, 7]], allocator); 33 | FuncTripleEverything(&arg).shouldEqualDlang(cast(double[][])[[0, 3, 6, 9], [12, 15, 18, 21]]); 34 | } 35 | 36 | 37 | /// 38 | @("Wrap string[][] -> double") 39 | @system unittest { 40 | 41 | import xlld.memorymanager: allocator; 42 | 43 | auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]], allocator); 44 | FuncAllLengths(&arg).shouldEqualDlang(29.0); 45 | 46 | arg = toSRef([["", "", "", ""], ["", "", "", ""]], allocator); 47 | FuncAllLengths(&arg).shouldEqualDlang(0.0); 48 | } 49 | 50 | /// 51 | @("Wrap string[][] -> double[][]") 52 | @system unittest { 53 | 54 | import xlld.memorymanager: allocator; 55 | 56 | auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]], allocator); 57 | FuncLengths(&arg).shouldEqualDlang(cast(double[][])[[3, 3, 3, 4], [4, 4, 4, 4]]); 58 | 59 | arg = toSRef([["", "", ""], ["", "", "huh"]], allocator); 60 | FuncLengths(&arg).shouldEqualDlang(cast(double[][])[[0, 0, 0], [0, 0, 3]]); 61 | } 62 | 63 | /// 64 | @("Wrap string[][] -> string[][]") 65 | @system unittest { 66 | 67 | import xlld.memorymanager: allocator; 68 | 69 | auto arg = toSRef([["foo", "bar", "baz", "quux"], ["toto", "titi", "tutu", "tete"]], allocator); 70 | FuncBob(&arg).shouldEqualDlang([["foobob", "barbob", "bazbob", "quuxbob"], 71 | ["totobob", "titibob", "tutubob", "tetebob"]]); 72 | } 73 | 74 | /// 75 | @("Wrap string[] -> double") 76 | @system unittest { 77 | import xlld.memorymanager: allocator; 78 | 79 | auto arg = toSRef([["foo", "bar"], ["baz", "quux"]], allocator); 80 | FuncStringSlice(&arg).shouldEqualDlang(4.0); 81 | } 82 | 83 | /// 84 | @("Wrap double[] -> double") 85 | @system unittest { 86 | import xlld.memorymanager: allocator; 87 | auto arg = toSRef([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], allocator); 88 | FuncDoubleSlice(&arg).shouldEqualDlang(6.0); 89 | } 90 | 91 | /// 92 | @("Wrap double[] -> double[]") 93 | @system unittest { 94 | import xlld.memorymanager: allocator; 95 | auto arg = toSRef([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], allocator); 96 | FuncSliceTimes3(&arg).shouldEqualDlang([3.0, 6.0, 9.0, 12.0, 15.0, 18.0]); 97 | } 98 | 99 | /// 100 | @("Wrap string[] -> string[]") 101 | @system unittest { 102 | import xlld.memorymanager: allocator; 103 | auto arg = toSRef(["quux", "toto"], allocator); 104 | StringsToStrings(&arg).shouldEqualDlang(["quuxfoo", "totofoo"]); 105 | } 106 | 107 | /// 108 | @("Wrap string[] -> string") 109 | @system unittest { 110 | import xlld.memorymanager: allocator; 111 | auto arg = toSRef(["quux", "toto"], allocator); 112 | StringsToString(&arg).shouldEqualDlang("quux, toto"); 113 | } 114 | 115 | /// 116 | @("Wrap string -> string") 117 | @system unittest { 118 | import xlld.memorymanager: allocator; 119 | auto arg = toXlOper("foo", allocator); 120 | StringToString(&arg).shouldEqualDlang("foobar"); 121 | } 122 | 123 | /// 124 | @("Wrap string, string, string -> string") 125 | @system unittest { 126 | import xlld.memorymanager: allocator; 127 | auto arg0 = toXlOper("foo", allocator); 128 | auto arg1 = toXlOper("bar", allocator); 129 | auto arg2 = toXlOper("baz", allocator); 130 | ManyToString(&arg0, &arg1, &arg2).shouldEqualDlang("foobarbaz"); 131 | } 132 | 133 | /// 134 | @("nothrow functions") 135 | @system unittest { 136 | import xlld.memorymanager: allocator; 137 | auto arg = toXlOper(2.0, allocator); 138 | static assert(__traits(compiles, FuncThrows(&arg))); 139 | } 140 | 141 | /// 142 | @("FuncAddEverything wrapper is @nogc") 143 | @system @nogc unittest { 144 | import std.experimental.allocator.mallocator: Mallocator; 145 | import xlld.sdk.framework: freeXLOper; 146 | 147 | auto arg = toXlOper(2.0, Mallocator.instance); 148 | scope(exit) freeXLOper(&arg, Mallocator.instance); 149 | FuncAddEverything(&arg); 150 | } 151 | 152 | /// 153 | @("Wrap a function that throws") 154 | @system unittest { 155 | auto arg = toSRef(33.3, theGC); 156 | FuncThrows(&arg); // should not actually throw 157 | } 158 | 159 | /// 160 | @("Wrap a function that asserts") 161 | @system unittest { 162 | auto arg = toSRef(33.3, theGC); 163 | FuncAsserts(&arg); // should not actually throw 164 | } 165 | 166 | /// 167 | @("Wrap a function that accepts DateTime") 168 | @system unittest { 169 | import xlld.sdk.xlcall: XlType; 170 | import xlld.conv.misc: stripMemoryBitmask; 171 | import std.conv: text; 172 | 173 | // the argument doesn't matter since we mock extracting the year from it 174 | // but it does have to be of double type (DateTime for Excel) 175 | auto arg = 33.3.toXlOper(theGC); 176 | 177 | const year = 2017; 178 | const mock = MockDateTime(year, 1, 2, 3, 4, 5); 179 | const ret = DateTimeToDouble(&arg); 180 | 181 | try 182 | ret.xltype.stripMemoryBitmask.shouldEqual(XlType.xltypeNum); 183 | catch(Exception _) 184 | assert(false, text("Expected xltypeNum, got ", *ret)); 185 | 186 | ret.val.num.shouldEqual(year * 2); 187 | } 188 | 189 | /// 190 | @("Wrap a function that accepts DateTime[]") 191 | @system unittest { 192 | //the arguments don't matter since we mock extracting year, etc. from them 193 | //they just need to be double (DateTime to Excel) 194 | auto arg = [0.1, 0.2].toXlOper(theGC); 195 | 196 | auto mockDateTimes = MockDateTimes(DateTime(1, 1, 31), 197 | DateTime(1, 1, 30)); 198 | 199 | auto ret = DateTimesToString(&arg); 200 | 201 | ret.shouldEqualDlang("31, 30"); 202 | } 203 | 204 | 205 | @Serial 206 | @("Wrap a function that takes an enum") 207 | @safe unittest { 208 | import test.d_funcs: MyEnum; 209 | 210 | auto arg = MyEnum.baz.toXlOper(theGC); 211 | auto ret = () @trusted { return FuncMyEnumArg(&arg); }(); 212 | ret.shouldEqualDlang("prefix_baz"); 213 | } 214 | 215 | @Serial 216 | @("Wrap a function that returns an enum") 217 | @safe unittest { 218 | import test.d_funcs: MyEnum; 219 | 220 | auto arg = 1.toXlOper(theGC); 221 | auto ret = () @trusted { return FuncMyEnumRet(&arg); }(); 222 | ret.shouldEqualDlang("bar"); 223 | } 224 | 225 | 226 | @Serial 227 | @("Register a custom to enum conversion") 228 | @safe unittest { 229 | import std.conv: to; 230 | import test.d_funcs: MyEnum; 231 | import xlld.conv.from: registerConversionTo, unregisterConversionTo; 232 | 233 | registerConversionTo!MyEnum((str) => str[3 .. $].to!MyEnum); 234 | scope(exit) unregisterConversionTo!MyEnum; 235 | 236 | auto arg = "___baz".toXlOper(theGC); 237 | auto ret = () @trusted { return FuncMyEnumArg(&arg); }(); 238 | 239 | ret.shouldEqualDlang("prefix_baz"); 240 | } 241 | 242 | @Serial 243 | @("Register a custom from enum conversion") 244 | @safe unittest { 245 | 246 | import std.conv: text; 247 | import test.d_funcs: MyEnum; 248 | import xlld.conv: registerConversionFrom, unregisterConversionFrom; 249 | 250 | registerConversionFrom!MyEnum((val) => "___" ~ text(cast(MyEnum)val)); 251 | scope(exit)unregisterConversionFrom!MyEnum; 252 | 253 | auto arg = 1.toXlOper(theGC); 254 | auto ret = () @trusted { return FuncMyEnumRet(&arg); }(); 255 | 256 | ret.shouldEqualDlang("___bar"); 257 | } 258 | 259 | @("Wrap a function that takes a struct using 1D array") 260 | unittest { 261 | auto arg = [2, 3].toXlOper(theGC); 262 | auto ret = () @trusted { return FuncPointArg(&arg); }(); 263 | 264 | ret.shouldEqualDlang(5); 265 | } 266 | 267 | @("Wrap a function that returns a struct") 268 | unittest { 269 | auto arg1 = 2.toXlOper(theGC); 270 | auto arg2 = 3.toXlOper(theGC); 271 | auto ret = () @trusted { return FuncPointRet(&arg1, &arg2); }(); 272 | 273 | ret.shouldEqualDlang("Point(2, 3)"); 274 | } 275 | 276 | @("Wrap a function that returns a simple tuple") 277 | unittest { 278 | import xlld.conv.from: fromXlOper; 279 | import xlld.conv.misc: stripMemoryBitmask; 280 | import std.conv: to; 281 | 282 | auto arg1 = 42.toXlOper(theGC); 283 | auto arg2 = "foo".toXlOper(theGC); 284 | auto ret = () @trusted { return FuncSimpleTupleRet(&arg1, &arg2); }(); 285 | 286 | if(ret.xltype.stripMemoryBitmask != XlType.xltypeMulti) { 287 | if(ret.xltype.stripMemoryBitmask == XlType.xltypeStr) 288 | throw new Exception(ret.fromXlOper!string(theGC)); 289 | else 290 | throw new Exception("Oper not of multi type: " ~ to!string(*ret)); 291 | } 292 | 293 | ret.val.array.rows.shouldEqual(1); 294 | ret.val.array.columns.shouldEqual(2); 295 | ret.val.array.lparray[0].fromXlOper!int(theGC).shouldEqual(42); 296 | ret.val.array.lparray[1].fromXlOper!string(theGC).shouldEqual("foo"); 297 | } 298 | 299 | @("Wrap a function that returns a complex tuple") 300 | unittest { 301 | import xlld.conv.from: fromXlOper; 302 | import xlld.conv.misc: stripMemoryBitmask; 303 | import std.conv: to; 304 | 305 | auto dates = MockDates([1.0, 2.0, 3.0, 4.0]); 306 | auto times = MockTimes([101.0, 102.0, 103.0, 104.0]); 307 | auto dateTimes = MockDateTimes(DateTime(2017, 1, 2), DateTime(2017, 2, 2), 308 | DateTime(2018, 1, 3), DateTime(2018, 2, 3)); 309 | auto arg1 = 2.toXlOper(theGC); 310 | auto arg2 = 3.toXlOper(theGC); 311 | auto ret = () @trusted { return FuncComplexTupleRet(&arg1, &arg2); }(); 312 | 313 | if(ret.xltype.stripMemoryBitmask != XlType.xltypeMulti) { 314 | if(ret.xltype.stripMemoryBitmask == XlType.xltypeStr) 315 | throw new Exception(ret.fromXlOper!string(theGC)); 316 | else 317 | throw new Exception("Oper not of multi type: " ~ to!string(*ret)); 318 | } 319 | 320 | ret.xltype.stripMemoryBitmask.shouldEqual(XlType.xltypeMulti); 321 | ret.val.array.rows.shouldEqual(1); 322 | ret.val.array.columns.shouldEqual(2); 323 | 324 | ret.val.array.lparray[0].fromXlOper!(DateTime[])(theGC).shouldEqual( 325 | [DateTime(2017, 1, 2), DateTime(2017, 2, 2)]); 326 | 327 | ret.val.array.lparray[1].fromXlOper!(DateTime[])(theGC).shouldEqual( 328 | [DateTime(2018, 1, 3), DateTime(2018, 2, 3)]); 329 | } 330 | 331 | @("Wrap a function that returns an array of tuples") 332 | unittest { 333 | import xlld.conv.from: fromXlOper; 334 | import xlld.conv.misc: stripMemoryBitmask; 335 | import std.conv: to; 336 | 337 | auto dates = MockDates([0.1, 0.2, 0.3]); 338 | auto times = MockTimes([10.1, 11.1, 12.1]); 339 | auto dateTimes = MockDateTimes(DateTime(2017, 1, 1), DateTime(2018, 1, 1), DateTime(2019, 1, 1)); 340 | 341 | auto oper = () @trusted { return FuncTupleArrayRet(); }(); 342 | 343 | void assertMulti(in XLOPER12 oper) { 344 | if(oper.xltype.stripMemoryBitmask != XlType.xltypeMulti) { 345 | if(oper.xltype.stripMemoryBitmask == XlType.xltypeStr) 346 | throw new Exception(oper.fromXlOper!string(theGC)); 347 | else 348 | throw new Exception("Oper not of multi type: " ~ to!string(oper)); 349 | } 350 | } 351 | 352 | assertMulti(*oper); 353 | oper.val.array.rows.shouldEqual(1); 354 | oper.val.array.columns.shouldEqual(3); 355 | 356 | auto elts = oper.val.array.lparray[0 .. 3]; 357 | foreach(elt; elts) assertMulti(elt); 358 | 359 | elts[0].val.array.lparray[0].fromXlOper!DateTime(theGC).shouldEqual(DateTime(2017, 1, 1)); 360 | elts[0].val.array.lparray[1].fromXlOper!double(theGC).shouldEqual(11.1); 361 | 362 | elts[1].val.array.lparray[0].fromXlOper!DateTime(theGC).shouldEqual(DateTime(2018, 1, 1)); 363 | elts[1].val.array.lparray[1].fromXlOper!double(theGC).shouldEqual(22.2); 364 | 365 | elts[2].val.array.lparray[0].fromXlOper!DateTime(theGC).shouldEqual(DateTime(2019, 1, 1)); 366 | elts[2].val.array.lparray[1].fromXlOper!double(theGC).shouldEqual(33.3); 367 | } 368 | 369 | @("Wrap a function that takes an enum array") 370 | unittest { 371 | import test.d_funcs: MyEnum; 372 | auto arg = [MyEnum.foo].toXlOper(theGC); 373 | FuncEnumArray(&arg); 374 | } 375 | 376 | /// 377 | @("wrapModuleFunctionStr") 378 | @system unittest { 379 | import xlld.wrap.worksheet; 380 | import std.traits: getUDAs; 381 | 382 | mixin(wrapModuleFunctionStr!("test.d_funcs", "FuncAddEverything")); 383 | alias registerAttrs = getUDAs!(FuncAddEverything, Register); 384 | static assert(registerAttrs[0].argumentText.value == "Array to add"); 385 | } 386 | 387 | 388 | /// 389 | @("No memory allocation bugs in wrapModuleFunctionImpl for double return Mallocator") 390 | @system unittest { 391 | import test.d_funcs: FuncAddEverything; 392 | import xlld.sdk.xlcall: xlbitDLLFree; 393 | import xlld.memorymanager: autoFree; 394 | 395 | TestAllocator allocator; 396 | auto arg = toSRef([1.0, 2.0], theMallocator); 397 | auto oper = wrapModuleFunctionImpl!FuncAddEverything(allocator, &arg); 398 | (oper.xltype & xlbitDLLFree).shouldBeTrue; 399 | allocator.numAllocations.shouldEqual(2); 400 | oper.shouldEqualDlang(3.0); 401 | autoFree(oper); // normally this is done by Excel 402 | } 403 | 404 | /// 405 | @("No memory allocation bugs in wrapModuleFunctionImpl for double[][] return Mallocator") 406 | @system unittest { 407 | import test.d_funcs: FuncTripleEverything; 408 | import xlld.sdk.xlcall: xlbitDLLFree, XlType; 409 | import xlld.memorymanager: autoFree; 410 | 411 | TestAllocator allocator; 412 | auto arg = toSRef([1.0, 2.0, 3.0], theMallocator); 413 | auto oper = wrapModuleFunctionImpl!FuncTripleEverything(allocator, &arg); 414 | (oper.xltype & xlbitDLLFree).shouldBeTrue; 415 | (oper.xltype & ~xlbitDLLFree).shouldEqual(XlType.xltypeMulti); 416 | allocator.numAllocations.shouldEqual(2); 417 | oper.shouldEqualDlang([[3.0, 6.0, 9.0]]); 418 | autoFree(oper); // normally this is done by Excel 419 | } 420 | 421 | /// 422 | @("No memory allocation bugs in wrapModuleFunctionImpl for double[][] return pool") 423 | @system unittest { 424 | import std.typecons: Ternary; 425 | import xlld.memorymanager: gTempAllocator, autoFree; 426 | import test.d_funcs: FuncTripleEverything; 427 | 428 | auto arg = toSRef([1.0, 2.0, 3.0], gTempAllocator); 429 | auto oper = wrapModuleFunctionImpl!FuncTripleEverything(gTempAllocator, &arg); 430 | gTempAllocator.empty.shouldEqual(Ternary.yes); 431 | oper.shouldEqualDlang([[3.0, 6.0, 9.0]]); 432 | autoFree(oper); // normally this is done by Excel 433 | } 434 | 435 | /// 436 | @("No memory allocation bugs in wrapModuleFunctionImpl for string") 437 | @system unittest { 438 | import std.typecons: Ternary; 439 | import xlld.memorymanager: gTempAllocator; 440 | import test.d_funcs: StringToString; 441 | 442 | auto arg = "foo".toSRef(gTempAllocator); 443 | auto oper = wrapModuleFunctionImpl!StringToString(gTempAllocator, &arg); 444 | gTempAllocator.empty.shouldEqual(Ternary.yes); 445 | oper.shouldEqualDlang("foobar"); 446 | } 447 | 448 | /// 449 | @("No memory allocation bugs in wrapModuleFunctionImpl for Any[][] -> Any[][] -> Any[][] mallocator") 450 | @system unittest { 451 | import xlld.memorymanager: allocatorContext; 452 | import test.d_funcs: FirstOfTwoAnyArrays; 453 | 454 | with(allocatorContext(theGC)) { 455 | auto dArg = [[any(1.0), any("foo"), any(3.0)], [any(4.0), any(5.0), any(6.0)]]; 456 | auto arg = toXlOper(dArg); 457 | auto oper = wrapModuleFunctionImpl!FirstOfTwoAnyArrays(theMallocator, &arg, &arg); 458 | oper.shouldEqualDlang(dArg); 459 | } 460 | } 461 | 462 | /// 463 | @("No memory allocation bugs in wrapModuleFunctionImpl for Any[][] -> Any[][] -> Any[][] TestAllocator") 464 | @system unittest { 465 | import xlld.memorymanager: allocatorContext; 466 | import test.d_funcs: FirstOfTwoAnyArrays; 467 | 468 | auto testAllocator = TestAllocator(); 469 | 470 | with(allocatorContext(theGC)) { 471 | auto dArg = [ 472 | [ any(1.0), any("foo"), any(3.0) ], 473 | [ any(4.0), any(5.0), any(6.0) ], 474 | ]; 475 | auto arg = toXlOper(dArg); 476 | auto oper = wrapModuleFunctionImpl!FirstOfTwoAnyArrays(testAllocator, &arg, &arg); 477 | oper.shouldEqualDlang(dArg); 478 | } 479 | } 480 | 481 | /// 482 | @("Correct number of coercions and frees in wrapModuleFunctionImpl") 483 | @system unittest { 484 | import test.d_funcs: FuncAddEverything; 485 | import xlld.test.util: gNumXlAllocated, gNumXlFree; 486 | 487 | const oldNumAllocated = gNumXlAllocated; 488 | const oldNumFree = gNumXlFree; 489 | 490 | auto arg = toSRef([1.0, 2.0], theGC); 491 | auto oper = wrapModuleFunctionImpl!FuncAddEverything(theGC, &arg); 492 | 493 | (gNumXlAllocated - oldNumAllocated).shouldEqual(1); 494 | (gNumXlFree - oldNumFree).shouldEqual(1); 495 | } 496 | 497 | 498 | /// 499 | @("Can't return empty 1D array to Excel") 500 | @system unittest { 501 | import xlld.memorymanager: allocatorContext; 502 | import test.d_funcs: EmptyStrings1D; 503 | 504 | with(allocatorContext(theGC)) { 505 | auto dArg = any(1.0); 506 | auto arg = toXlOper(dArg); 507 | auto oper = wrapModuleFunctionImpl!EmptyStrings1D(theGC, &arg); 508 | oper.shouldEqualDlang("#ERROR: empty result"); 509 | } 510 | } 511 | 512 | 513 | /// 514 | @("Can't return empty 2D array to Excel") 515 | @system unittest { 516 | import xlld.memorymanager: allocatorContext; 517 | import test.d_funcs: EmptyStrings2D; 518 | 519 | with(allocatorContext(theGC)) { 520 | auto dArg = any(1.0); 521 | auto arg = toXlOper(dArg); 522 | auto oper = wrapModuleFunctionImpl!EmptyStrings2D(theGC, &arg); 523 | oper.shouldEqualDlang("#ERROR: empty result"); 524 | } 525 | } 526 | 527 | /// 528 | @("Can't return half empty 2D array to Excel") 529 | @system unittest { 530 | import xlld.memorymanager: allocatorContext; 531 | import test.d_funcs: EmptyStringsHalfEmpty2D; 532 | 533 | with(allocatorContext(theGC)) { 534 | auto dArg = any(1.0); 535 | auto arg = toXlOper(dArg); 536 | auto oper = wrapModuleFunctionImpl!EmptyStringsHalfEmpty2D(theGC, &arg); 537 | oper.shouldEqualDlang("#ERROR: empty result"); 538 | } 539 | } 540 | 541 | /// 542 | @("issue 25 - make sure to reserve memory for all dArgs") 543 | @system unittest { 544 | import std.typecons: Ternary; 545 | import xlld.memorymanager: allocatorContext, MemoryPool; 546 | import test.d_funcs: FirstOfTwoAnyArrays; 547 | 548 | auto pool = MemoryPool(); 549 | 550 | with(allocatorContext(theGC)) { 551 | auto dArg = [[any(1.0), any("foo"), any(3.0)], [any(4.0), any(5.0), any(6.0)]]; 552 | auto arg = toSRef(dArg); 553 | auto oper = wrapModuleFunctionImpl!FirstOfTwoAnyArrays(pool, &arg, &arg); 554 | } 555 | 556 | pool.empty.shouldEqual(Ternary.yes); // deallocateAll in wrapImpl 557 | } 558 | 559 | 560 | /// 561 | @("wrapModuleFunctionStr function that returns Any[][]") 562 | @safe unittest { 563 | mixin(wrapModuleFunctionStr!("test.d_funcs", "DoubleArrayToAnyArray")); 564 | 565 | auto oper = [[1.0, 2.0], [3.0, 4.0]].toSRef(theGC); 566 | auto arg = () @trusted { return &oper; }(); 567 | auto ret = DoubleArrayToAnyArray(arg); 568 | 569 | auto opers = () @trusted { return ret.val.array.lparray[0 .. 4]; }(); 570 | opers[0].shouldEqualDlang(2.0); 571 | opers[1].shouldEqualDlang(6.0); 572 | opers[2].shouldEqualDlang("3quux"); 573 | opers[3].shouldEqualDlang("4toto"); 574 | } 575 | 576 | /// 577 | @("wrapModuleFunctionStr int -> int") 578 | @safe unittest { 579 | mixin(wrapModuleFunctionStr!("test.d_funcs", "Twice")); 580 | 581 | auto oper = 3.toSRef(theGC); 582 | auto arg = () @trusted { return &oper; }(); 583 | Twice(arg).shouldEqualDlang(6); 584 | } 585 | 586 | /// 587 | @("issue 31 - D functions can have const arguments") 588 | @safe unittest { 589 | mixin(wrapModuleFunctionStr!("test.d_funcs", "FuncConstDouble")); 590 | 591 | auto oper = (3.0).toSRef(theGC); 592 | auto arg = () @trusted { return &oper; }(); 593 | FuncConstDouble(arg).shouldEqualDlang(3.0); 594 | } 595 | 596 | 597 | @Flaky 598 | @("wrapModuleFunctionStr async double -> double") 599 | unittest { 600 | import xlld.conv.from: fromXlOper; 601 | import xlld.wrap.traits: Async; 602 | import xlld.test.util: asyncReturn, newAsyncHandle; 603 | import core.time: MonoTime; 604 | import core.thread; 605 | 606 | mixin(wrapModuleFunctionStr!("test.d_funcs", "AsyncDoubleToDouble")); 607 | 608 | auto oper = (3.0).toXlOper(theGC); 609 | auto arg = () @trusted { return &oper; }(); 610 | auto asyncHandle = newAsyncHandle; 611 | () @trusted { AsyncDoubleToDouble(arg, &asyncHandle); }(); 612 | 613 | const start = MonoTime.currTime; 614 | const expected = 6.0; 615 | while(asyncReturn(asyncHandle).fromXlOper!double(theGC) != expected && 616 | MonoTime.currTime - start < 1.seconds) 617 | { 618 | Thread.sleep(10.msecs); 619 | } 620 | asyncReturn(asyncHandle).shouldEqualDlang(expected); 621 | } 622 | 623 | @("wrapModuleFunctionStr () -> NaN") 624 | unittest { 625 | mixin(wrapModuleFunctionStr!("test.d_funcs", "NaN")); 626 | NaN().shouldEqualDlang("#NaN"); 627 | } 628 | 629 | 630 | 631 | @("wrapModuleFunctionImpl gTempAllocator @safe") 632 | @safe unittest { 633 | import xlld.memorymanager: gTempAllocator; 634 | import test.d_funcs: Twice; 635 | 636 | auto arg = 3.toXlOper(theGC); 637 | auto argPtr = () @trusted { return &arg; }(); 638 | auto oper = wrapModuleFunctionImpl!Twice(gTempAllocator, argPtr); 639 | oper.shouldEqualDlang(6); 640 | } 641 | 642 | 643 | @("oper") 644 | @safe unittest { 645 | auto arg = (3.3).toXlOper(theGC); 646 | scope argPtr = &arg; 647 | FuncOper(argPtr).shouldEqualDlang(6.6); 648 | } 649 | 650 | 651 | @("vector.1d") 652 | @safe unittest { 653 | auto arg = 7.toXlOper(theGC); 654 | scope argPtr = &arg; 655 | FuncVector(argPtr).shouldEqualDlang([0, 1, 2, 3, 4, 5, 6]); 656 | } 657 | 658 | 659 | @("vector.2d") 660 | @safe unittest { 661 | auto arg = 7.toXlOper(theGC); 662 | scope argPtr = &arg; 663 | FuncVector2D(argPtr).shouldEqualDlang([[7, 7, 7], [8, 8, 8]]); 664 | } 665 | -------------------------------------------------------------------------------- /tests/ut/wrap/traits.d: -------------------------------------------------------------------------------- 1 | module ut.wrap.traits; 2 | 3 | import test; 4 | import xlld.wrap.traits; 5 | import xlld.wrap.worksheet; 6 | 7 | 8 | /// return a WorksheetFunction for a double function(double) with no 9 | /// optional arguments 10 | WorksheetFunction makeWorksheetFunction(wstring name, wstring typeText) @safe pure nothrow { 11 | return 12 | WorksheetFunction( 13 | Procedure(name), 14 | TypeText(typeText), 15 | FunctionText(name), 16 | Optional( 17 | ArgumentText(""w), 18 | MacroType("1"w), 19 | Category(""w), 20 | ShortcutText(""w), 21 | HelpTopic(""w), 22 | FunctionHelp(""w), 23 | ArgumentHelp([""w]), 24 | ) 25 | ); 26 | } 27 | 28 | /// 29 | WorksheetFunction doubleToDoubleFunction(wstring name) @safe pure nothrow { 30 | auto ret = makeWorksheetFunction(name, "BB"w); 31 | ret.optional.argumentText = ArgumentText("n"); // See test.xl_funcs.FuncMulByTwo 32 | return ret; 33 | } 34 | 35 | /// 36 | WorksheetFunction FP12ToDoubleFunction(wstring name) @safe pure nothrow { 37 | auto ret = makeWorksheetFunction(name, "BK%"w); 38 | ret.optional.argumentText = ArgumentText("cells"); // See test.xl_funcs.FuncFP12 39 | return ret; 40 | } 41 | 42 | /// 43 | WorksheetFunction operToOperFunction(wstring name) @safe pure nothrow { 44 | auto ret = makeWorksheetFunction(name, "UU"w); 45 | ret.optional.argumentText = ArgumentText("n"); // See test.xl_funcs.FuncFib 46 | return ret; 47 | } 48 | 49 | WorksheetFunction asyncFunction(wstring name) @safe pure nothrow { 50 | auto ret = makeWorksheetFunction(name, ">UX"w); 51 | ret.optional.argumentHelp.add(""); // FuncAsync has two parameters 52 | ret.optional.argumentText = ArgumentText("n;asyncHandle"); 53 | return ret; 54 | } 55 | 56 | /// 57 | @("getWorksheetFunction for double -> double functions with no extra attributes") 58 | @system pure unittest { 59 | extern(Windows) double foo(double n) nothrow @nogc { return 0; } 60 | getWorksheetFunction!foo.shouldEqual(doubleToDoubleFunction("foo")); 61 | 62 | extern(Windows) double bar(double n) nothrow @nogc { return 0; } 63 | getWorksheetFunction!bar.shouldEqual(doubleToDoubleFunction("bar")); 64 | } 65 | 66 | /// 67 | @("getWorksheetFunction for double -> int functions should fail") 68 | @safe pure unittest { 69 | extern(Windows) double foo(int) { return 0; } 70 | getWorksheetFunction!foo.shouldThrowWithMessage("Unsupported function type double(int) for foo"); 71 | } 72 | 73 | /// 74 | @("getworksheetFunction with @Register in order") 75 | @system pure unittest { 76 | 77 | @Register(ArgumentText("my arg txt"), MacroType("macro")) 78 | extern(Windows) double foo(double) nothrow; 79 | 80 | auto expected = doubleToDoubleFunction("foo"); 81 | expected.argumentText = ArgumentText("my arg txt"); 82 | expected.macroType = MacroType("macro"); 83 | 84 | getWorksheetFunction!foo.shouldEqual(expected); 85 | } 86 | 87 | /// 88 | @("getworksheetFunction with @Register out of order") 89 | @system pure unittest { 90 | 91 | @Register(HelpTopic("I need somebody"), ArgumentText("my arg txt")) 92 | extern(Windows) double foo(double) nothrow; 93 | 94 | auto expected = doubleToDoubleFunction("foo"); 95 | expected.argumentText = ArgumentText("my arg txt"); 96 | expected.helpTopic = HelpTopic("I need somebody"); 97 | 98 | getWorksheetFunction!foo.shouldEqual(expected); 99 | } 100 | 101 | 102 | @("getWorksheetFunction with @ExcelParameter") 103 | @system pure unittest { 104 | extern(Windows) double withParamUDA(@ExcelParameter("the double") double d) nothrow; 105 | 106 | auto expected = doubleToDoubleFunction("withParamUDA"); 107 | expected.optional.argumentHelp = ArgumentHelp("the double"); 108 | expected.optional.argumentText = ArgumentText("d"); 109 | 110 | getWorksheetFunction!withParamUDA.should == expected; 111 | } 112 | 113 | 114 | @safe pure unittest { 115 | extern(Windows) double doubleToDouble(double) nothrow; 116 | static assert(isWorksheetFunction!doubleToDouble); 117 | 118 | extern(Windows) LPXLOPER12 operToOper(LPXLOPER12) nothrow; 119 | static assert(isWorksheetFunction!operToOper); 120 | 121 | extern(Windows) void funcAsync(LPXLOPER12 n, LPXLOPER12 asyncHandle) nothrow; 122 | static assert(isWorksheetFunction!funcAsync); 123 | 124 | LPXLOPER12 operToOperWrongLinkage(LPXLOPER12) nothrow; 125 | static assert(isWorksheetFunctionModuloLinkage!operToOperWrongLinkage); 126 | static assert(!isWorksheetFunction!operToOperWrongLinkage); 127 | 128 | enum MyEnum { foo, bar, baz, } 129 | 130 | extern(Windows) MyEnum FuncEnumRet(LPXLOPER12 n) nothrow; 131 | static assert(!isWorksheetFunction!FuncEnumRet); 132 | 133 | extern(Windows) LPXLOPER12 FuncEnumArg(MyEnum _) nothrow; 134 | static assert(!isWorksheetFunction!FuncEnumArg); 135 | } 136 | 137 | 138 | @("getWorksheetFunctions on test.xl_funcs") 139 | @system pure unittest { 140 | getModuleWorksheetFunctions!"test.xl_funcs".shouldEqual( 141 | [ 142 | doubleToDoubleFunction("FuncMulByTwo"), 143 | FP12ToDoubleFunction("FuncFP12"), 144 | operToOperFunction("FuncFib"), 145 | asyncFunction("FuncAsync"), 146 | ] 147 | ); 148 | } 149 | 150 | @("template mixin for getWorkSheetFunctions for test.xl_funcs") 151 | unittest { 152 | import xlld.wrap.worksheet; 153 | 154 | // mixin the function here then call it to see if it does what it's supposed to 155 | mixin(implGetWorksheetFunctionsString!"test.xl_funcs"); 156 | getWorksheetFunctions.shouldEqual( 157 | [ 158 | doubleToDoubleFunction("FuncMulByTwo"), 159 | FP12ToDoubleFunction("FuncFP12"), 160 | operToOperFunction("FuncFib"), 161 | asyncFunction("FuncAsync"), 162 | ] 163 | ); 164 | } 165 | 166 | @("implGetWorksheetFunctionsString runtime") 167 | unittest { 168 | import xlld.wrap.worksheet; 169 | 170 | // mixin the function here then call it to see if it does what it's supposed to 171 | mixin(implGetWorksheetFunctionsString("test.xl_funcs")); 172 | getWorksheetFunctions.shouldEqual( 173 | [ 174 | doubleToDoubleFunction("FuncMulByTwo"), 175 | FP12ToDoubleFunction("FuncFP12"), 176 | operToOperFunction("FuncFib"), 177 | asyncFunction("FuncAsync"), 178 | ] 179 | ); 180 | } 181 | 182 | 183 | @("worksheet functions to .def file") 184 | unittest { 185 | dllDefFile!"test.xl_funcs"("myxll32.dll", "Simple D add-in").shouldEqual( 186 | DllDefFile( 187 | [ 188 | Statement("LIBRARY", "myxll32.dll"), 189 | Statement("EXPORTS", 190 | [ 191 | "xlAutoOpen", 192 | "xlAutoClose", 193 | "xlAutoFree12", 194 | "FuncMulByTwo", 195 | "FuncFP12", 196 | "FuncFib", 197 | "FuncAsync", 198 | ]), 199 | ] 200 | ) 201 | ); 202 | } 203 | 204 | 205 | @("getTypeText") 206 | @safe pure unittest { 207 | import std.conv: to; // working around unit-threaded bug 208 | 209 | double foo(double); 210 | getTypeText!foo.to!string.shouldEqual("BB"); 211 | 212 | double bar(FP12*); 213 | getTypeText!bar.to!string.shouldEqual("BK%"); 214 | 215 | FP12* baz(FP12*); 216 | getTypeText!baz.to!string.shouldEqual("K%K%"); 217 | 218 | FP12* qux(double); 219 | getTypeText!qux.to!string.shouldEqual("K%B"); 220 | 221 | LPXLOPER12 fun(LPXLOPER12); 222 | getTypeText!fun.to!string.shouldEqual("UU"); 223 | 224 | void void_(LPXLOPER12, LPXLOPER12); 225 | getTypeText!void_.to!string.shouldEqual(">UU"); 226 | 227 | @Async 228 | void async(LPXLOPER12, LPXLOPER12); 229 | getTypeText!async.to!string.shouldEqual(">UX"); 230 | } 231 | -------------------------------------------------------------------------------- /tests/ut/wrap/wrap.d: -------------------------------------------------------------------------------- 1 | module ut.wrap.wrap; 2 | 3 | import test; 4 | import xlld.wrap.wrap; 5 | import xlld.conv.to: toXlOper; 6 | 7 | 8 | // this has to be a top-level function and can't be declared in the unittest 9 | double twice(double d) { return d * 2; } 10 | 11 | /// 12 | @Flaky 13 | @("wrapAsync") 14 | @system unittest { 15 | import xlld.test.util: asyncReturn, newAsyncHandle; 16 | import xlld.conv.from: fromXlOper; 17 | import core.time: MonoTime; 18 | import core.thread; 19 | 20 | const start = MonoTime.currTime; 21 | auto asyncHandle = newAsyncHandle; 22 | auto oper = (3.2).toXlOper(theGC); 23 | wrapAsync!twice(theGC, cast(immutable)asyncHandle, oper); 24 | const expected = 6.4; 25 | while(asyncReturn(asyncHandle).fromXlOper!double(theGC) != expected && 26 | MonoTime.currTime - start < 1.seconds) 27 | { 28 | Thread.sleep(10.msecs); 29 | } 30 | asyncReturn(asyncHandle).shouldEqualDlang(expected); 31 | } 32 | 33 | @("xltypeNum can convert to array") 34 | @safe unittest { 35 | import std.typecons: tuple; 36 | 37 | void fun(double[] arg) {} 38 | auto arg = 33.3.toSRef(theGC); 39 | auto argPtr = () @trusted { return &arg; }(); 40 | toDArgs!fun(theGC, argPtr).shouldEqual(tuple([33.3])); 41 | } 42 | 43 | @("xltypeNil can convert to array") 44 | @safe unittest { 45 | import xlld.sdk.xlcall: XlType; 46 | import std.typecons: tuple; 47 | 48 | void fun(double[] arg) {} 49 | XLOPER12 arg; 50 | arg.xltype = XlType.xltypeNil; 51 | double[] empty; 52 | auto argPtr = () @trusted { return &arg; }(); 53 | toDArgs!fun(theGC, argPtr).shouldEqual(tuple(empty)); 54 | } 55 | 56 | 57 | @("excelRet!double[] from row caller") 58 | unittest { 59 | import xlld.sdk.xlcall: XlType, xlfCaller; 60 | import xlld.conv.misc: stripMemoryBitmask; 61 | import xlld.memorymanager: autoFree; 62 | 63 | XLOPER12 caller; 64 | caller.xltype = XlType.xltypeSRef; 65 | caller.val.sref.ref_.rwFirst = 1; 66 | caller.val.sref.ref_.rwLast = 1; 67 | caller.val.sref.ref_.colFirst = 2; 68 | caller.val.sref.ref_.colLast = 4; 69 | 70 | with(MockXlFunction(xlfCaller, caller)) { 71 | auto doubles = [1.0, 2.0, 3.0, 4.0]; 72 | auto oper = excelRet(doubles); 73 | scope(exit) autoFree(&oper); 74 | 75 | oper.shouldEqualDlang(doubles); 76 | oper.xltype.stripMemoryBitmask.shouldEqual(XlType.xltypeMulti); 77 | oper.val.array.rows.shouldEqual(1); 78 | oper.val.array.columns.shouldEqual(4); 79 | } 80 | } 81 | 82 | @("excelRet!double[] from column caller") 83 | unittest { 84 | import xlld.sdk.xlcall: XlType, xlfCaller; 85 | import xlld.conv.misc: stripMemoryBitmask; 86 | import xlld.memorymanager: autoFree; 87 | 88 | XLOPER12 caller; 89 | caller.xltype = XlType.xltypeSRef; 90 | caller.val.sref.ref_.rwFirst = 1; 91 | caller.val.sref.ref_.rwLast = 4; 92 | caller.val.sref.ref_.colFirst = 5; 93 | caller.val.sref.ref_.colLast = 5; 94 | 95 | with(MockXlFunction(xlfCaller, caller)) { 96 | auto doubles = [1.0, 2.0, 3.0, 4.0]; 97 | auto oper = excelRet(doubles); 98 | scope(exit) autoFree(&oper); 99 | 100 | oper.shouldEqualDlang(doubles); 101 | oper.xltype.stripMemoryBitmask.shouldEqual(XlType.xltypeMulti); 102 | oper.val.array.rows.shouldEqual(4); 103 | oper.val.array.columns.shouldEqual(1); 104 | } 105 | } 106 | 107 | 108 | @("excelRet!tuple from column caller") 109 | unittest { 110 | import xlld.sdk.xlcall: XlType, xlfCaller; 111 | import xlld.conv.misc: stripMemoryBitmask; 112 | import xlld.memorymanager: autoFree; 113 | import std.typecons: tuple; 114 | 115 | XLOPER12 caller; 116 | caller.xltype = XlType.xltypeSRef; 117 | caller.val.sref.ref_.rwFirst = 1; 118 | caller.val.sref.ref_.rwLast = 4; 119 | caller.val.sref.ref_.colFirst = 5; 120 | caller.val.sref.ref_.colLast = 5; 121 | 122 | with(MockXlFunction(xlfCaller, caller)) { 123 | auto doubles = tuple(1.0, 2.0, 3.0, 4.0); 124 | auto oper = excelRet(doubles); 125 | scope(exit) autoFree(&oper); 126 | 127 | oper.shouldEqualDlang(doubles); 128 | oper.xltype.stripMemoryBitmask.shouldEqual(XlType.xltypeMulti); 129 | oper.val.array.rows.shouldEqual(4); 130 | oper.val.array.columns.shouldEqual(1); 131 | } 132 | } 133 | 134 | @("excelRet!vector from column caller") 135 | unittest { 136 | import xlld.sdk.xlcall: XlType, xlfCaller; 137 | import xlld.conv.misc: stripMemoryBitmask; 138 | import xlld.memorymanager: autoFree; 139 | import automem.vector: vector; 140 | 141 | XLOPER12 caller; 142 | caller.xltype = XlType.xltypeSRef; 143 | caller.val.sref.ref_.rwFirst = 1; 144 | caller.val.sref.ref_.rwLast = 4; 145 | caller.val.sref.ref_.colFirst = 5; 146 | caller.val.sref.ref_.colLast = 5; 147 | 148 | with(MockXlFunction(xlfCaller, caller)) { 149 | auto doubles = vector(1.0, 2.0, 3.0, 4.0); 150 | auto oper = excelRet(doubles); 151 | scope(exit) autoFree(&oper); 152 | 153 | oper.shouldEqualDlang(doubles[]); 154 | oper.xltype.stripMemoryBitmask.shouldEqual(XlType.xltypeMulti); 155 | oper.val.array.rows.shouldEqual(4); 156 | oper.val.array.columns.shouldEqual(1); 157 | } 158 | } 159 | 160 | 161 | @("excelRet!double[] from other caller") 162 | unittest { 163 | import xlld.sdk.xlcall: XlType, xlfCaller; 164 | import xlld.conv.misc: stripMemoryBitmask; 165 | import xlld.memorymanager: autoFree; 166 | 167 | XLOPER12 caller; 168 | caller.xltype = XlType.xltypeErr; 169 | 170 | with(MockXlFunction(xlfCaller, caller)) { 171 | auto doubles = [1.0, 2.0, 3.0, 4.0]; 172 | auto oper = excelRet(doubles); 173 | scope(exit) autoFree(&oper); 174 | 175 | oper.shouldEqualDlang(doubles); 176 | oper.xltype.stripMemoryBitmask.shouldEqual(XlType.xltypeMulti); 177 | oper.val.array.rows.shouldEqual(1); 178 | oper.val.array.columns.shouldEqual(4); 179 | } 180 | } 181 | 182 | @("toDArgs optional arguments") 183 | @safe unittest { 184 | import std.typecons: tuple; 185 | 186 | static int add(int i, int j = 42); 187 | 188 | XLOPER12 missing; 189 | missing.xltype = XlType.xltypeMissing; 190 | 191 | auto i = 2.toXlOper(theGC); 192 | auto j = 3.toXlOper(theGC); 193 | 194 | auto iPtr = () @trusted { return &i; }(); 195 | auto jPtr = () @trusted { return &j; }(); 196 | auto missingPtr = () @trusted { return &missing; }(); 197 | 198 | toDArgs!add(theGC, iPtr, jPtr).shouldEqual(tuple(2, 3)); 199 | toDArgs!add(theGC, iPtr, missingPtr).shouldEqual(tuple(2, 42)); 200 | } 201 | -------------------------------------------------------------------------------- /tests/ut/wrap/wrapped.d: -------------------------------------------------------------------------------- 1 | /** 2 | This module exists so that the instantiation of the wrapper functions 3 | for Excel only happens once. From LDC 1.9.0, two `extern(Windows)` functions 4 | with the same name can't exist since they mangle the same. 5 | 6 | Instead we create them here. 7 | */ 8 | module ut.wrap.wrapped; 9 | 10 | import xlld.wrap; 11 | import xlld.wrap.traits: Async; 12 | import std.typecons: No; 13 | 14 | mixin(wrapAll!"test.d_funcs"(No.onlyExports, No.pascalCase)); 15 | -------------------------------------------------------------------------------- /tests/ut_main.d: -------------------------------------------------------------------------------- 1 | import unit_threaded; 2 | 3 | int main(string[] args) 4 | { 5 | return args.runTests!( 6 | "xlld.any", 7 | "xlld.wrap.wrap", 8 | "xlld.wrap.traits", 9 | "xlld.sdk.xll", 10 | "xlld.test.util", 11 | "ut.wrap.module_", 12 | "ut.wrap.all", 13 | "ut.conv.from", 14 | "ut.conv.to", 15 | "ut.conv.misc", 16 | "ut.func.xlf", 17 | "ut.wrap.wrap", 18 | "ut.wrap.traits", 19 | "ut.misc", 20 | "ut.issues", 21 | ); 22 | } 23 | --------------------------------------------------------------------------------