1 ///
2 module sbin.type;
3 
4 package import std.traits;
5 package import std.range;
6 
7 import std.bitmanip : nativeToLittleEndian, littleEndianToNative;
8 
9 import sbin.repr;
10 
11 /// UDA for marking struct fields to be skipped.
12 enum sbinSkip;
13 
14 /// variable length uint
15 struct vluint
16 {
17     ulong value; ///
18     ///
19     this()(ulong v) { value = v; }
20     alias value this; ///
21 }
22 
23 /// variable length int
24 struct vlint
25 {
26     long value; ///
27     ///
28     this()(long v) { value = v; }
29     alias value this; ///
30 }
31 
32 package alias pack = nativeToLittleEndian;
33 
34 package auto unpack(T, size_t N)(ubyte[N] arr)
35     if (N == T.sizeof)
36 {
37     static if (T.sizeof == 1) return cast(T)arr[0];
38     else return littleEndianToNative!T(arr);
39 }
40 
41 version (Have_taggedalgebraic) package import taggedalgebraic;
42 version (Have_mir_core) package import mir.algebraic;
43 
44 version (Have_sumtype)
45 {
46     version = Have_any_sumtype;
47     package import sumtype;
48 }
49 else static if (__VERSION__ >= 2097)
50 {
51     version = Have_any_sumtype;
52     version = Have_std_sumtype;
53     package import std.sumtype;
54     alias sumtype = std.sumtype;
55 }
56 
57 template isTagged(T)
58 {
59     version (Have_taggedalgebraic)
60         enum isTaggedAlgebraic = is(T == TaggedUnion!X, X) || is(T == TaggedAlgebraic!Y, Y);
61     else
62         enum isTaggedAlgebraic = false;
63 
64     version (Have_mir_core)
65         enum isMirAlgebraic = isVariant!T;
66     else
67         enum isMirAlgebraic = false;
68 
69     version (Have_any_sumtype)
70         enum isSumType = is(T == SumType!Args, Args...);
71     else
72         enum isSumType = false;
73 
74     enum any = isTaggedAlgebraic || isMirAlgebraic || isSumType;
75 }
76 
77 package(sbin)
78 template taggedMatch(handlers...)
79 {
80     auto taggedMatch(T)(auto ref T val) if (isTagged!(T).any)
81     {
82         static if (isTagged!(T).isSumType)
83             return sumtype.match!(handlers)(val);
84         else
85         static if (isTagged!(T).isTaggedAlgebraic)
86         {
87             static if (is(T == TaggedUnion!X, X))
88                 return taggedalgebraic.visit.visit!(handlers)(val);
89             else static if (is(T == TaggedAlgebraic!Y, Y))
90                 return taggedalgebraic.visit.visit!(handlers)(val.get!(TaggedUnion!Y));
91         }
92         else
93         static if (isTagged!(T).isMirAlgebraic)
94             return mir.algebraic.match!(handlers)(val);
95         else
96         static assert(0, "??");
97     }
98 }
99 
100 package(sbin)
101 template TaggedTagType(T) if (isTagged!(T).any)
102 {
103     static if (isTagged!(T).isTaggedAlgebraic || isTagged!(T).isMirAlgebraic)
104         alias TaggedTagType = T.Kind;
105     else static if (isTagged!(T).isSumType)
106     {
107         import std : AliasSeq, Filter;
108         enum bool canHoldTag(X) = T.Types.length <= X.max;
109         alias unsignedInts = AliasSeq!(ubyte, ushort, uint, ulong);
110         alias TaggedTagType = Filter!(canHoldTag, unsignedInts)[0];
111     }
112 }
113 
114 package(sbin)
115 auto getTaggedAllTags(T)() if (isTagged!(T).any)
116 {
117     import std : EnumMembers;
118 
119     static if (isTagged!(T).isSumType)
120     {
121         alias TTT = TaggedTagType!T;
122         TTT[T.Types.length] ret;
123         foreach (i, ref v; ret) v = cast(TTT)i;
124         return ret;
125     }
126     else
127     static if (isTagged!(T).isTaggedAlgebraic || isTagged!(T).isMirAlgebraic)
128         return [EnumMembers!(T.Kind)];
129     else
130     static assert(0, "??");
131 }
132 
133 package(sbin)
134 template TaggedTypeByTag(T, alias tag) if (isTagged!(T).any)
135 {
136     static if (isTagged!(T).isTaggedAlgebraic)
137         alias r = TypeOf!tag;
138     else
139     static if (isTagged!(T).isMirAlgebraic)
140     {
141         ptrdiff_t indexOf(T.Kind[] lst, T.Kind kind)
142         {
143             foreach (i, v; lst) if (v == kind) return i;
144             assert(0);
145         }
146         alias r = T.AllowedTypes[indexOf(getTaggedAllTags!T, tag)];
147     }
148     else
149     static if (isTagged!(T).isSumType)
150         alias r = T.Types[tag];
151 
152     alias TaggedTypeByTag = r;
153 }
154 
155 package(sbin)
156 auto getTaggedTag(T)(in T val) if (isTagged!(T).any)
157 {
158     static if (isTagged!(T).isSumType)
159     {
160         alias TTT = TaggedTagType!T;
161         version (Have_std_sumtype)
162         {
163             import std : staticIndexOf;
164             // version from std doesn't have typeIndex
165             // https://github.com/dlang/phobos/pull/7922
166             return cast(TTT)(sumtype.match!(v => staticIndexOf!(typeof(v), T.Types))(cast()val));
167         }
168         else
169             return cast(TTT)val.typeIndex;
170     }
171     else
172     static if (isTagged!(T).isMirAlgebraic || isTagged!(T).isTaggedAlgebraic)
173         return val.kind;
174     else static assert(0, "unsupported " ~ T.stringof);
175 }
176 
177 template isVoidArray(T)
178 {
179     static if( (is(T U == U[]) || is(T U == U[N], size_t N)) &&
180                 is(Unqual!U == void)) enum isVoidArray = true;
181     else enum isVoidArray = false;
182 }
183 
184 unittest
185 {
186     assert (isVoidArray!(void[]));
187     assert (isVoidArray!(void[2]));
188     assert (isVoidArray!(const(void)[]));
189     assert (isVoidArray!(const(void[])));
190     assert (isVoidArray!(const(void)[2]));
191     assert (isVoidArray!(const(void[2])));
192     assert (isVoidArray!(immutable(void)[]));
193     assert (isVoidArray!(immutable(void[])));
194     assert (isVoidArray!(immutable(void)[2]));
195     assert (isVoidArray!(immutable(void[2])));
196 
197     assert (!isVoidArray!(long[]));
198     assert (!isVoidArray!(long[2]));
199     assert (!isVoidArray!(byte[]));
200     assert (!isVoidArray!(byte[2]));
201 }
202 
203 template EnumNumType(T) if (is(T == enum))
204 {
205     enum C = [EnumMembers!T].length;
206          static if (C <= ubyte.max)  alias EnumNumType = ubyte;
207     else static if (C <= ushort.max) alias EnumNumType = ushort;
208     else static if (C <= uint.max)   alias EnumNumType = uint;
209     else                             alias EnumNumType = ulong;
210 }
211 
212 unittest
213 {
214     enum E { e }
215     static assert(is(E == enum));
216     static assert(is(EnumNumType!E == ubyte));
217 }
218 
219 unittest
220 {
221     enum Color
222     {
223         black = "#000000",
224         red = "#ff0000",
225         green = "#00ff00",
226         blue = "#0000ff",
227         white = "#ffffff"
228     }
229 
230     static assert(is(EnumNumType!Color == ubyte));
231 
232     enum Level { low, medium, high }
233 
234     static assert(is(EnumNumType!Level == ubyte));
235 
236     static string bigElems() pure
237     {
238         import std.format : formattedWrite;
239         import std.array : appender;
240         import std.range : put;
241 
242         auto buf = appender!(char[]);
243         formattedWrite(buf, "enum Big { ");
244         foreach (i; 0 .. 300)
245             formattedWrite(buf, "e%d,", i);
246         buf.put(" }");
247         return buf.data.idup;
248     }
249 
250     mixin(bigElems());
251 
252     static assert(is(EnumNumType!Big == ushort));
253     assert(is(EnumNumType!Big == ushort));
254 }
255 
256 ///
257 auto getEnumNum(T)(T val) @safe @nogc pure nothrow
258     if (is(T == enum))
259 {
260     alias Ret = EnumNumType!T;
261     static foreach (Ret i; 0 .. [EnumMembers!T].length)
262         if ((EnumMembers!T)[i] == val) return i;
263     return Ret.max;
264 }
265 
266 unittest
267 {
268     enum Foo { one, two }
269     assert (getEnumNum(Foo.two) == 1);
270     static assert(is(typeof(getEnumNum(Foo.two)) == ubyte));
271     assert(is(typeof(getEnumNum(Foo.two)) == ubyte));
272 }
273 
274 ///
275 template hasCustomRepr(T, RH=EmptyReprHandler)
276 {
277     static if (hasMember!(T, "sbinCustomRepr") && hasMember!(T, "sbinFromCustomRepr"))
278     {
279         import sbin.serialize : sbinSerialize;
280 
281         alias Repr = ReturnType!(() => T.init.sbinCustomRepr);
282 
283         enum hasCustomRepr = is(typeof(sbinSerialize!RH(Repr.init))) && 
284                 is(typeof(((){ return T.sbinFromCustomRepr(Repr.init); })()) == Unqual!T);
285     }
286     else enum hasCustomRepr = false;
287 }
288 
289 unittest
290 {
291     static class Foo { ulong id; }
292     static assert (hasCustomRepr!Foo == false);
293 }
294 
295 unittest
296 {
297     static class Foo
298     {
299         ulong id;
300         ulong sbinCustomRepr() const @property { return id; }
301     }
302     static assert (hasCustomRepr!Foo == false);
303     assert (hasCustomRepr!Foo == false);
304 }
305 
306 unittest
307 {
308     static class Foo
309     {
310         ulong id;
311         this(ulong v) { id = v; }
312         ulong sbinCustomRepr() const @property { return id; }
313         static Foo sbinFromCustomRepr(ulong v) { return new Foo(v); }
314     }
315     static assert (hasCustomRepr!Foo == true);
316     assert (hasCustomRepr!Foo == true);
317     auto foo = new Foo(12);
318     assert (foo.sbinCustomRepr == 12);
319     auto foo2 = Foo.sbinFromCustomRepr(foo.sbinCustomRepr);
320     assert (foo.id == foo2.id);
321 }
322 
323 unittest
324 {
325     static class Bar { ulong id; this(ulong v) { id = v; } }
326     static class Foo
327     {
328         ulong id;
329         this(ulong v) { id = v; }
330         Bar sbinCustomRepr() const @property { return new Bar(id); }
331         static Foo sbinFromCustomRepr(Bar v) { return new Foo(v.id); }
332     }
333     static assert (hasCustomRepr!Foo == false);
334 }
335 
336 unittest
337 {
338     static struct Bar { ulong id; }
339     static class Foo
340     {
341         ulong id;
342         this(ulong v) { id = v; }
343         Bar sbinCustomRepr() const @property { return Bar(id); }
344         static Foo sbinFromCustomRepr()(auto ref const Bar v) { return new Foo(v.id); }
345     }
346     import sbin.serialize : sbinSerialize;
347     static assert (is(typeof(sbinSerialize(Bar.init))));
348     static assert (hasCustomRepr!Foo == true);
349 }
350 
351 unittest
352 {
353     static class Foo
354     {
355         ulong id;
356         this(ulong v) { id = v; }
357         ulong sbinCustomRepr() const @property { return id; }
358         static Foo sbinFromCustomRepr(ulong v, int add) { return new Foo(v+add); }
359     }
360     static assert (hasCustomRepr!Foo == false);
361 }
362 
363 unittest
364 {
365     static class Foo
366     {
367         ulong id;
368         this(ulong v) { id = v; }
369         ulong sbinCustomRepr() const @property { return id; }
370         static Foo sbinFromCustomRepr(ulong v, int add=1) { return new Foo(v+add); }
371     }
372     static assert (hasCustomRepr!Foo == true);
373 }