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