├── .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 |
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 | [](https://github.com/symmetryinvestments/excel-d/actions)
4 | [](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 |
--------------------------------------------------------------------------------