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     scope 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     scope 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 }