1 /**
2  * The module contains the object UniNode
3  *
4  * Copyright: (c) 2015-2020, Milofon Project.
5  * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file.
6  * Author: <m.galanin@milofon.pro> Maksim Galanin
7  * Date: 2020-01-12
8  */
9 
10 module uninode.node;
11 
12 private
13 {
14     import std.algorithm.searching: canFind;
15     import std.meta : AliasSeq, allSatisfy, staticMap;
16     import std.format : fmt = format;
17     import std.typecons : Tuple;
18     import std.exception : enforce;
19     import std..string : capitalize;
20     import std.conv : to, ConvOverflowException;
21     import std.array : appender, join;
22     import std.traits;
23 
24     import optional : Optional;
25 }
26 
27 
28 alias Bytes = immutable(ubyte)[];
29 
30 
31 /**
32  * A [UniNode] implementation
33  */
34 struct UniNodeImpl(Node)
35 {
36     private pure nothrow @safe @nogc
37     {
38         union Storage
39         {
40             bool boolean;
41             ulong uinteger;
42             long integer;
43             double floating;
44             string text;
45             Bytes raw;
46             Node[] sequence;
47             Node[string] mapping;
48         }
49 
50         alias GetFieldPair(string F) = AliasSeq!(typeof(__traits(getMember, Storage, F)), F);
51         alias Types = Tuple!(staticMap!(GetFieldPair, FieldNameTuple!Storage));
52         alias AllTypes = Tuple!(staticMap!(GetFieldPair, FieldNameTuple!Storage),
53                 typeof(null), "nil");
54 
55         ref inout(T) _val(T)() inout pure nothrow @trusted @nogc
56             if (isUniNodeType!(T, Node))
57         {
58             return __traits(getMember, _storage, Types.fieldNames[NodeTag!(Node, T)]);
59         }
60 
61         Storage _storage;
62         Tag _tag = Tag.nil;
63     }
64 
65     /**
66      * UniNodeImpl Tag
67      */
68     mixin("enum Tag : ubyte {" ~ [AllTypes.fieldNames].join(", ") ~ "}");
69 
70     /**
71      * Check type node
72      */
73     bool can(Tag tag) inout pure nothrow @safe @nogc
74     {
75         return _tag == tag;
76     }
77 
78     /**
79      * Auto generage can functions
80      */
81     static foreach (string name; AllTypes.fieldNames)
82     {
83         /**
84          * Check node is null
85          */
86         mixin("bool can", name.capitalize, `() inout pure nothrow @safe @nogc
87         {
88             return _tag == Tag.`, name, `;
89         }`);
90     }
91 
92     /**
93      * Return tag Node
94      */
95     const(Tag) tag() inout pure nothrow @safe @nogc
96     {
97         return _tag;
98     }
99 
100     /**
101      * Constructs a UniNode
102      */
103     this(typeof(null)) inout pure nothrow @safe @nogc
104     {
105         _tag = Tag.nil;
106     }
107 
108     /**
109      * Constructs a UniNode
110      *
111      * Params:
112      * value = ctor value
113      */
114     this(T)(auto ref T value) inout pure nothrow @trusted @nogc
115         if (isUniNodeInnerType!(T) && !isRawData!T)
116     {
117         static if (isBoolean!T)
118         {
119             _storage.boolean = value;
120             _tag = Tag.boolean;
121         }
122         else static if(isSignedNumeric!T)
123         {
124             _storage.integer = value;
125             _tag = Tag.integer;
126         }
127         else static if(isUnsignedNumeric!T)
128         {
129             _storage.uinteger = value;
130             _tag = Tag.uinteger;
131         }
132         else static if(isFloatingPoint!T)
133         {
134             _storage.floating = value;
135             _tag = Tag.floating;
136         }
137         else static if(is(Unqual!T == string))
138         {
139             _storage.text = value;
140             _tag = Tag.text;
141         }
142         else
143             _tag = Tag.nil;
144     }
145 
146     /**
147      * Constructs a UniNode
148      * with gc
149      *
150      * Params:
151      * value = ctor value
152      */
153     this(T)(auto ref T value) inout pure nothrow @trusted
154         if (isRawData!T || isUniNodeArray!(T, Node))
155     {
156         static if(isRawData!T)
157         {
158             static if (isStaticArray!T || isMutable!T)
159                 _storage.raw = value.idup;
160             else
161             {
162                 alias ST = typeof(_storage.raw);
163                 _storage.raw = cast(ST)value;
164             }
165             _tag = Tag.raw;
166         }
167         else static if (isUniNodeArray!(T, Node))
168         {
169             alias ST = typeof(_storage.sequence);
170             _storage.sequence = cast(ST)value.dup;
171             _tag = Tag.sequence;
172         }
173         else
174             _tag = Tag.nil;
175     }
176 
177     /**
178      * Constructs a UniNode
179      * with gc and throw
180      *
181      * Params:
182      * value = ctor value
183      */
184     this(T)(auto ref T value) inout pure @trusted
185         if (isUniNodeMapping!(T, Node))
186     {
187         alias ST = typeof(_storage.mapping);
188         _storage.mapping = cast(ST)value.dup;
189         _tag = Tag.mapping;
190     }
191 
192     /**
193      * Constructs a UniNode sequence from arguments
194      *
195      * Params:
196      * value = ctor value
197      */
198     this(T...)(auto ref T value) inout pure nothrow @trusted
199         if (T.length > 1 && allSatisfy!(isUniNodeInnerType, T))
200     {
201         alias ST = typeof(_storage.sequence);
202         Node[] seq = new Node[value.length];
203         static foreach (idx, val; value)
204             seq[idx] = Node(val);
205         _storage.sequence = cast(ST)seq;
206         _tag = Tag.sequence;
207     }
208 
209     /**
210      * Construct empty Node sequence
211      */
212     static Node emptySequence() nothrow @safe
213     {
214         return Node(cast(Node[])null);
215     }
216 
217     /**
218      * Construct empty Node mapping
219      */
220     static Node emptyMapping() @safe
221     {
222         return Node(cast(Node[string])null);
223     }
224 
225     /**
226      * Convert UniNode to sequence
227      */
228     inout(Node[]) getSequence() inout pure @safe
229     {
230         enforceUniNode(can(Tag.sequence),
231             fmt!("Trying to convert sequence but have %s.")(_tag), __FILE__, __LINE__);
232         return _val!(inout(Node[]));
233     }
234 
235     /**
236      * Convert UniNode to mapping
237      */
238     inout(Node[string]) getMapping() inout pure @safe
239     {
240         enforceUniNode(can(Tag.mapping),
241             fmt!("Trying to convert mapping but have %s.")(_tag), __FILE__, __LINE__);
242         return _val!(inout(Node[string]));
243     }
244 
245     /**
246      * Convert UniNode to primitive type
247      */
248     inout(T) get(T)() inout pure @safe
249     {
250         T wrapTo(A)(auto ref A val) inout pure @safe
251         {
252             try
253                 return val.to!T;
254             catch (ConvOverflowException e)
255                 throw new UniNodeException(e.msg);
256         }
257 
258         void checkTag(T)(Tag target, string file = __FILE__, size_t line = __LINE__)
259             inout pure @safe
260         {
261             enforceUniNode(_tag == target,
262                 fmt!("Trying to convert %s but have %s.")(T.stringof, _tag), file, line);
263         }
264 
265         static if (isSignedNumeric!T)
266         {
267             if (canUinteger)
268             {
269                 immutable val = _val!ulong;
270                 enforceUniNode(val < T.max, "Unsigned value great max");
271                 return wrapTo(val);
272             }
273             checkTag!T(Tag.integer);
274             immutable val = _val!long;
275             return wrapTo(val);
276         }
277         else static if (isUnsignedNumeric!T)
278         {
279             if (canInteger)
280             {
281                 immutable val = _val!long;
282                 enforceUniNode(val >= 0, "Signed value less zero");
283                 return wrapTo(val);
284             }
285             checkTag!T(Tag.uinteger);
286             immutable val = _val!ulong;
287             return wrapTo(val);
288         }
289         else static if (isBoolean!T)
290         {
291             if (canUinteger)
292                 return _val!ulong != 0;
293             if (canInteger)
294                 return _val!long != 0;
295             else
296             {
297                 checkTag!T(Tag.boolean);
298                 return _val!bool;
299             }
300         }
301         else static if (isFloatingPoint!T)
302         {
303             if (canUinteger)
304             {
305                 immutable val = _val!ulong;
306                 return wrapTo(val);
307             }
308             if (canInteger)
309             {
310                 immutable val = _val!long;
311                 return wrapTo(val);
312             }
313             else
314             {
315                 checkTag!T(Tag.floating);
316                 return _val!double;
317             }
318         }
319         else static if (is(T == string))
320         {
321             if (canRaw)
322                 return cast(string)_val!Bytes;
323             checkTag!T(Tag.text);
324             return _val!string.to!T;
325         }
326         else static if (isRawData!T)
327         {
328             checkTag!T(Tag.raw);
329             immutable val = _val!Bytes;
330             static if (isStaticArray!T)
331                 return cast(inout(T))val[0..T.length];
332             else
333                 return val.to!T;
334         }
335         else static if (isUniNodeArray!(T, Node))
336             return getSequence();
337         else static if (isUniNodeMapping!(T, Node))
338             return getMapping();
339         else
340             throw new UniNodeException(fmt!"Not support type '%s'"(T.stringof));
341     }
342 
343     /**
344      * Convert UniNode to optional primitive type
345      */
346     Optional!(const(T)) opt(T)() const pure nothrow @safe
347     {
348         alias RT = Optional!(const(T));
349         try
350             return RT(get!T);
351         catch (Exception e)
352             return RT.init;
353     }
354 
355     /**
356      * Convert UniNode to optional primitive type
357      */
358     Optional!(T) opt(T)() pure nothrow @safe
359     {
360         alias RT = Optional!(T);
361         try
362             return RT(get!T);
363         catch (Exception e)
364             return RT.init;
365     }
366 
367     /**
368      * Convert UniNode to optional sequence
369      */
370     Optional!(const(Node[])) optSequence() const pure nothrow @safe
371     {
372         alias RT = Optional!(const(Node[]));
373         try
374             return RT(getSequence());
375         catch (Exception e)
376             return RT.init;
377     }
378 
379     /**
380      * Convert UniNode to optional sequence
381      */
382     Optional!(Node[]) optSequence() pure nothrow @safe
383     {
384         alias RT = Optional!(Node[]);
385         try
386             return RT(getSequence());
387         catch (Exception e)
388             return RT.init;
389     }
390 
391     /**
392      * Convert UniNode to optional mapping
393      */
394     Optional!(const(Node[string])) optMapping() const pure nothrow @safe
395     {
396         alias RT = Optional!(const(Node[string]));
397         try
398             return RT(getMapping());
399         catch (Exception e)
400             return RT.init;
401     }
402 
403     /**
404      * Convert UniNode to optional mapping
405      */
406     Optional!(Node[string]) optMapping() pure nothrow @safe
407     {
408         alias RT = Optional!(Node[string]);
409         try
410             return RT(getMapping());
411         catch (Exception e)
412             return RT.init;
413     }
414 
415     /**
416      * Implement index operator by Node array
417      */
418     inout(Node) opIndex(size_t idx) inout @safe
419     {
420         enforceUniNode(can(Tag.sequence),
421             fmt!("Trying to convert sequence but have %s.")(_tag), __FILE__, __LINE__);
422         return _val!(inout(Node[]))[idx];
423     }
424 
425     /**
426      * Implement index operator by Node object
427      */
428     inout(Node) opIndex(string key) inout @safe
429     {
430         enforceUniNode(can(Tag.mapping),
431             fmt!("Trying to convert mapping but have %s.")(_tag), __FILE__, __LINE__);
432         return _val!(inout(Node[string]))[key];
433     }
434 
435     /**
436      * Implement index assign operator by Node sequence
437      */
438     void opIndexAssign(T)(auto ref T val, size_t idx) @safe
439         if (isUniNodeInnerType!T || isUniNode!T)
440     {
441         enforceUniNode(can(Tag.sequence),
442             fmt!("Trying to convert sequence but have %s.")(_tag), __FILE__, __LINE__);
443         static if (isUniNode!T)
444             _val!(inout(Node[]))[idx] = val;
445         else
446             _val!(inout(Node[]))[idx] = Node(val);
447     }
448 
449     /**
450      * Implement index assign operator by Node mapping
451      */
452     void opIndexAssign(T)(auto ref T val, string key) @safe
453         if (isUniNodeInnerType!T || isUniNode!T)
454     {
455         enforceUniNode(can(Tag.mapping),
456             fmt!("Trying to convert mapping but have %s.")(_tag), __FILE__, __LINE__);
457         static if (isUniNode!T)
458             _val!(inout(Node[string]))[key] = val;
459         else
460             _val!(inout(Node[string]))[key] = Node(val);
461     }
462 
463     /**
464      * Implement operator ~= by UniNode array
465      */
466     void opOpAssign(string op)(auto ref Node elem) @safe
467         if (op == "~")
468     {
469         enforceUniNode(can(Tag.sequence),
470             fmt!("Trying to convert sequence but have %s.")(_tag), __FILE__, __LINE__);
471         _val!(Node[]) ~= elem;
472     }
473 
474     /**
475      * Implement operator ~= by UniNode array
476      */
477     void opOpAssign(string op)(auto ref Node[] elem) @safe
478         if (op == "~")
479     {
480         opOpAssign!op(Node(elem));
481     }
482 
483     /**
484      * Particular keys in an Node can be removed with the remove
485      */
486     void remove(string key) @safe
487     {
488         enforceUniNode(can(Tag.mapping),
489             fmt!("Trying to convert mapping but have %s.")(_tag), __FILE__, __LINE__);
490         _val!(Node[string]).remove(key);
491     }
492 
493     /**
494      * Inserting if not present
495      */
496     Node require(T)(string key, auto ref T val) @safe
497         if (isUniNodeInnerType!T || isUniNode!T)
498     {
499         enforceUniNode(can(Tag.mapping),
500             fmt!("Trying to convert mapping but have %s.")(_tag), __FILE__, __LINE__);
501         if (auto ret = key in _val!(Node[string]))
502             return *ret;
503         else
504         {
505             static if (isUniNode!T)
506                 return _val!(Node[string])[key] = val;
507             else
508                 return _val!(Node[string])[key] = Node(val);
509         }
510     }
511 
512     /**
513      * Implement operator in for mapping
514      */
515     inout(Node)* opBinaryRight(string op)(string key) inout @safe
516         if (op == "in")
517     {
518         enforceUniNode(_tag == Tag.mapping,
519             fmt!("Trying to convert mapping but have %s.")(_tag), __FILE__, __LINE__);
520         return key in _val!(inout(Node[string]));
521     }
522 
523     /**
524      * Iteration by Node mapping
525      */
526     int opApply(D)(D dg) inout
527         if (isCallable!D && (Parameters!D.length == 1 && isUniNode!(Parameters!D[0]))
528                 || (Parameters!D.length == 2 && isUniNode!(Parameters!D[1])))
529     {
530         alias Params = Parameters!D;
531 
532         ref P toDelegateParam(P)(ref inout(Node) node) @trusted
533         {
534             return *(cast(P*)&node);
535         }
536 
537         static if (Params.length == 1 && isUniNode!(Params[0]))
538         {
539             foreach (ref inout(Node) node; _val!(inout(Node[]))())
540             {
541                 if (auto ret = dg(toDelegateParam!(Params[0])(node)))
542                     return ret;
543             }
544         }
545         else static if (Params.length == 2 && isUniNode!(Params[1]))
546         {
547             static if (isSomeString!(Params[0]))
548             {
549                 foreach (Params[0] key, ref inout(Node) node; _val!(inout(Node[string]))())
550                     if (auto ret = dg(key, toDelegateParam!(Params[1])(node)))
551                         return ret;
552             }
553             else
554             {
555                 foreach (Params[0] key, ref inout(Node) node; _val!(inout(Node[])))
556                     if (auto ret = dg(key, toDelegateParam!(Params[1])(node)))
557                         return ret;
558             }
559         }
560         return 0;
561     }
562 
563     /**
564      * Returns the hash of the `Node`'s current value.
565      */
566     size_t toHash() const nothrow @trusted
567     {
568         final switch (_tag)
569         {
570             static foreach (tid, T; AllTypes.Types)
571             {
572                 case tid:
573                     static if (is(T == typeof(null)))
574                         return typeid(T).getHash(null);
575                     else
576                     {
577                         auto val = _val!T;
578                         return typeid(T).getHash(&val);
579                     }
580             }
581         }
582     }
583 
584     /**
585      * Returns the length sequence types
586      */
587     size_t length() const pure @property
588     {
589         return this.match!(
590                 (string val) => val.length,
591                 (Bytes val) => val.length,
592                 (const(Node)[] val) => val.length,
593                 (const(Node[string]) val) => val.length,
594                 () { throw new UniNodeException("Expected " ~ Node.stringof ~ " not length"); }
595             );
596     }
597 
598     /**
599      * Compares two `UniNode`s for equality.
600      */
601     bool opEquals()(auto ref const(Node) rhs) const @safe
602     {
603         return this.match!((value) {
604                 return rhs.match!((rhsValue) {
605                     static if (is(typeof(value) == typeof(rhsValue)))
606                         return value == rhsValue;
607                     else static if (isNumeric!(typeof(value))
608                             && isNumeric!(typeof(rhsValue)))
609                         return value == rhsValue;
610                     else
611                         return false;
612                 });
613             });
614     }
615 
616     /**
617      * Returns string representaion
618      */
619     string toString() const @safe
620     {
621         auto buff = appender!string;
622 
623         void toStringNode(UniNodeImpl!Node node) @safe const
624         {
625             node.match!(
626                     (bool v) => buff.put(fmt!"bool(%s)"(v)),
627                     (long v) => buff.put(fmt!"int(%s)"(v)),
628                     (ulong v) => buff.put(fmt!"uint(%s)"(v)),
629                     (double v) => buff.put(fmt!"float(%s)"(v)),
630                     (string v) => buff.put(fmt!"text(%s)"(v)),
631                     (Bytes v) => buff.put(fmt!"raw(%s)"(v)),
632                     (const(Node)[] v) {
633                         buff.put("[");
634                         const len = v.length;
635                         size_t count;
636                         foreach (ref const(Node) nodeV; v)
637                         {
638                             count++;
639                             toStringNode(nodeV);
640                             if (count < len)
641                                 buff.put(", ");
642                         }
643                         buff.put("]");
644                     },
645                     (const(Node[string]) v) {
646                         buff.put("{");
647                         const len = v.length;
648                         size_t count;
649                         foreach (string key, ref const(Node) nodeV; v)
650                         {
651                             count++;
652                             buff.put(key ~ ":");
653                             toStringNode(nodeV);
654                             if (count < len)
655                                 buff.put(", ");
656                         }
657                         buff.put("}");
658                     },
659                     () => buff.put("nil")
660                 );
661         }
662 
663         toStringNode(this);
664         return buff.data;
665     }
666 }
667 
668 
669 /**
670  * A [UniNode] struct
671  */
672 struct UniNode
673 {
674     private
675     {
676         alias Node = UniNodeImpl!UniNode;
677         alias node this;
678     }
679 
680     /**
681      * Node implementation
682      */
683     Node node;
684 
685     /**
686      * Common constructor
687      */
688     this(T)(auto ref T val) inout pure nothrow @safe @nogc
689         if ((isUniNodeInnerType!T && !isRawData!T) || is (T == typeof(null)))
690     {
691         node = Node(val);
692     }
693 
694     /**
695      * Common constructor
696      */
697     this(T)(auto ref T val) inout pure nothrow @safe
698         if (isRawData!T || isUniNodeArray!(T, Node))
699     {
700         node = Node(val);
701     }
702 
703     /**
704      * Common constructor
705      */
706     this(T)(auto ref T val) inout pure @safe
707         if (isUniNodeMapping!(T, Node))
708     {
709         node = Node(val);
710     }
711 
712     /**
713      * Sequence constructor
714      */
715     this(T...)(auto ref T val) inout pure nothrow @safe
716         if (T.length > 0 && allSatisfy!(isUniNodeInnerType, T))
717     {
718         node = Node(val);
719     }
720 
721     /**
722      * Compares two `UniNode`s for equality.
723      */
724     bool opEquals(const(UniNode) rhs) const pure @safe
725     {
726         return node.opEquals(rhs);
727     }
728 
729     /**
730      * Returns the hash of the `UniNode`'s current value.
731      */
732     size_t toHash() const nothrow @safe
733     {
734         return node.toHash();
735     }
736 }
737 
738 
739 /**
740  * Thrown when an unhandled type is encountered.
741  */
742 class UniNodeException : Exception
743 {
744     /**
745      * common constructor
746      */
747     pure nothrow @safe @nogc
748     this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
749     {
750         super(msg, file, line, next);
751     }
752 }
753 
754 
755 /**
756  * Calls a type-appropriate function with the value held in a [UniNode].
757  */
758 template match(handlers...)
759     if (handlers.length)
760 {
761     /**
762      * The actual `match` function.
763      *
764      * Params:
765      *   self = A [UniNode] object
766      */
767     auto match(Node)(auto ref Node node)
768         if (is(Node : UniNodeImpl!Th, Th))
769     {
770         return matchImpl!(handlers)(node);
771     }
772 }
773 
774 
775 /**
776  * Checking is UniNode
777  */
778 template isUniNode(T)
779 {
780     alias UT = Unqual!T;
781     static if (__traits(compiles, is(UT : UniNodeImpl!UT)))
782         enum isUniNode = is(UT : UniNodeImpl!UT);
783     else
784         enum isUniNode = false;
785 }
786 
787 @("Checking is UniNode")
788 @safe unittest
789 {
790     alias UniUni = UniNode;
791     assert (isUniNode!UniUni);
792     assert (isUniNode!UniNode);
793     assert (!isUniNode!int);
794     assert (isUniNode!(const(UniNode)));
795 }
796 
797 
798 package:
799 
800 
801 alias enforceUniNode = enforce!UniNodeException;
802 
803 
804 /**
805  * Checking for uninode
806  */
807 template isUniNodeType(T, N)
808 {
809     enum isUniNodeType = isUniNodeInnerType!T
810         || isUniNodeArray!(T, N) || isUniNodeMapping!(T, N);
811 }
812 
813 @("Checking for uninode type")
814 @safe unittest
815 {
816     static foreach(T; Fields!(UniNode.Storage))
817         assert(isUniNodeType!(T, UniNode), "Type " ~ T.stringof ~ " not UniNode");
818 }
819 
820 
821 /**
822  * Checking for inner types
823  */
824 template isUniNodeInnerType(T)
825 {
826     alias TU = Unqual!T;
827     enum isUniNodeInnerType = isNumeric!TU || isBoolean!TU ||
828             is(TU == string) || isRawData!TU;
829 }
830 
831 @("Checking for inner types")
832 @safe unittest
833 {
834     static foreach(T; AliasSeq!(int, long, uint, ulong, bool, string))
835         assert (isUniNodeInnerType!(T));
836     assert (isUniNodeInnerType!string);
837     assert (!isUniNodeInnerType!(typeof(null)));
838 }
839 
840 
841 /**
842  * Checking for binary data
843  */
844 template isRawData(T)
845 {
846     enum isRawData = isArray!T && is(Unqual!(ForeachType!T) == ubyte);
847 }
848 
849 @("Checking for binary data")
850 @safe unittest
851 {
852     assert (isRawData!(Bytes));
853 }
854 
855 
856 /**
857  * Checking for array
858  */
859 template isUniNodeArray(T, N)
860 {
861     enum isUniNodeArray = isArray!T && is(Unqual!(ForeachType!T) : Unqual!N);
862 }
863 
864 @("Checking for array")
865 @safe unittest
866 {
867     assert (isUniNodeArray!(UniNode[], UniNode));
868 }
869 
870 
871 /**
872  * Checking for object
873  */
874 template isUniNodeMapping(T, N)
875 {
876     enum isUniNodeMapping = isAssociativeArray!T
877         && is(Unqual!(ForeachType!T) : Unqual!N) && is(KeyType!T == string);
878 }
879 
880 @("Checking for object")
881 @safe unittest
882 {
883     assert (isUniNodeMapping!(UniNode[string], UniNode));
884 }
885 
886 
887 private:
888 
889 
890 /**
891  * True if `handler` is a potential match for `T`, otherwise false.
892  */
893 enum bool canMatch(alias handler, T) = is(typeof((T arg) => handler(arg)));
894 
895 @("Should work canMatch")
896 @safe unittest
897 {
898     static struct OverloadSet
899     {
900         static void fun(int n) {}
901         static void fun(double d) {}
902     }
903 
904     assert(canMatch!(OverloadSet.fun, int));
905     assert(canMatch!(OverloadSet.fun, double));
906 }
907 
908 
909 @("Checking all types")
910 @safe unittest
911 {
912     assert(allSatisfy!(isCopyable, UniNode.Storage));
913     assert(!allSatisfy!(hasElaborateCopyConstructor, UniNode.Storage));
914     assert(!allSatisfy!(hasElaborateDestructor, UniNode.Storage));
915 }
916 
917 
918 /**
919  * Checking for an integer signed number
920  */
921 template isSignedNumeric(T)
922 {
923     enum isSignedNumeric = isNumeric!T && isSigned!T && !isFloatingPoint!T;
924 }
925 
926 @("Checking for an integer signed number")
927 @safe unittest
928 {
929     static foreach(T; AliasSeq!(byte, int, short, long))
930         assert(isSignedNumeric!T);
931 }
932 
933 
934 /**
935  * Checking for an integer unsigned number
936  */
937 template isUnsignedNumeric(T)
938 {
939     enum isUnsignedNumeric = isUnsigned!T && !isFloatingPoint!T;
940 }
941 
942 @("Checking for an integer unsigned number")
943 @safe unittest
944 {
945     static foreach(T; AliasSeq!(ubyte, uint, ushort, ulong))
946         assert(isUnsignedNumeric!T);
947 }
948 
949 
950 /**
951  * Language type to uninode inner tag
952  */
953 template NodeTag(Node, T)
954     if (isUniNodeType!(T, Node))
955 {
956     static if (isBoolean!T)
957         enum NodeTag = Node.Tag.boolean;
958     else static if (isSignedNumeric!T)
959         enum NodeTag = Node.Tag.integer;
960     else static if (isUnsignedNumeric!T)
961         enum NodeTag = Node.Tag.uinteger;
962     else static if (isFloatingPoint!T)
963         enum NodeTag = Node.Tag.floating;
964     else static if (isSomeString!T)
965         enum NodeTag = Node.Tag.text;
966     else static if (isRawData!T)
967         enum NodeTag = Node.Tag.raw;
968     else static if (isUniNodeArray!(T, Node))
969         enum NodeTag = Node.Tag.sequence;
970     else static if (isUniNodeMapping!(T, Node))
971         enum NodeTag = Node.Tag.mapping;
972     else
973         enum NodeTag = Node.Tag.nil;
974 }
975 
976 @("NodeTag test")
977 @safe unittest
978 {
979     static assert (NodeTag!(UniNode, bool) == UniNode.Tag.boolean);
980     static assert (NodeTag!(UniNode, int) == UniNode.Tag.integer);
981     static assert (NodeTag!(UniNode, uint) == UniNode.Tag.uinteger);
982     static assert (NodeTag!(UniNode, float) == UniNode.Tag.floating);
983     static assert (NodeTag!(UniNode, string) == UniNode.Tag.text);
984     static assert (NodeTag!(UniNode, ubyte[]) == UniNode.Tag.raw);
985     static assert (NodeTag!(UniNode, UniNode[]) == UniNode.Tag.sequence);
986     static assert (NodeTag!(UniNode, UniNode[string]) == UniNode.Tag.mapping);
987 }
988 
989 
990 /**
991  * Match implementation
992  */
993 template matchImpl(handlers...)
994     if (handlers.length)
995 {
996     // Converts an unsigned integer to a compile-time string constant.
997     enum toCtString(ulong n) = n.stringof[0 .. $ - 2];
998 
999     auto matchImpl(Node)(auto ref Node node)
1000         if (is(Node : UniNodeImpl!T, T))
1001     {
1002         alias AllTypes = Node.AllTypes.Types;
1003         enum MatchType : ubyte { NO, TPL, FUN, EMP }
1004         struct Match
1005         {
1006             MatchType type;
1007             size_t hid;
1008             ubyte dist;
1009         }
1010 
1011         enum defaultMatch = Match(MatchType.NO, 0, ubyte.max);
1012 
1013         template HandlerMatch(alias handler, size_t hid, T)
1014         {
1015             static if (isCallable!handler)
1016             {
1017                 alias params = Parameters!handler;
1018                 static if (params.length == 1)
1019                 {
1020                     enum dist = GetDistance!(Node, T, params[0]);
1021                     enum type = dist < ubyte.max ? MatchType.FUN : MatchType.NO;
1022                     enum HandlerMatch = Match(type, hid, dist);
1023                 }
1024                 else static if (params.length == 0)
1025                     enum HandlerMatch = Match(MatchType.EMP, hid, ubyte.max);
1026                 else
1027                     enum HandlerMatch = defaultMatch;
1028             }
1029             else static if (canMatch!(handler, T))
1030                 enum HandlerMatch = Match(MatchType.TPL, hid, ubyte.max-1);
1031             else
1032                 enum HandlerMatch = defaultMatch;
1033         }
1034 
1035         enum matches = () {
1036             Match[AllTypes.length] matches;
1037 
1038             foreach (tid, T; AllTypes)
1039             {
1040                 foreach (hid, handler; handlers)
1041                 {
1042                     enum m = HandlerMatch!(handler, hid, T);
1043                     if (matches[tid].type != MatchType.NO)
1044                     {
1045                         if (matches[tid].dist > m.dist)
1046                             matches[tid] = m;
1047                     }
1048                     else
1049                         matches[tid] = m;
1050                 }
1051             }
1052             return matches;
1053         } ();
1054 
1055         // Check for unreachable handlers
1056         static foreach(hid, handler; handlers)
1057         {
1058             static assert(matches[].canFind!(m => m.type != MatchType.NO && m.hid == hid),
1059                 "handler #" ~ toCtString!hid ~ " " ~
1060                 "of type `" ~ ( __traits(isTemplate, handler)
1061                     ? "template"
1062                     : typeof(handler).stringof
1063                 ) ~ "` " ~
1064                 "never matches"
1065             );
1066         }
1067 
1068         // Workaround for dlang issue 19993
1069         static foreach (size_t hid, handler; handlers) {
1070             mixin("alias handler", toCtString!hid, " = handler;");
1071         }
1072 
1073         final switch (node._tag)
1074         {
1075             static foreach (tid, T; AllTypes)
1076             {
1077                 case tid:
1078                     static if (matches[tid].type == MatchType.TPL)
1079                         static if (is(T == typeof(null)))
1080                             return mixin("handler",
1081                                 toCtString!(matches[tid].hid))(null);
1082                         else
1083                             return mixin("handler",
1084                                 toCtString!(matches[tid].hid))(node.get!T);
1085                     else static if (matches[tid].type == MatchType.EMP)
1086                     {
1087                         alias h = handlers[matches[tid].hid];
1088                         static if (is(ReturnType!h == void))
1089                         {
1090                             mixin("handler", toCtString!(matches[tid].hid))();
1091                             return 0;
1092                         }
1093                         else
1094                             return mixin("handler", toCtString!(matches[tid].hid))();
1095                     }
1096                     else static if (matches[tid].type == MatchType.FUN)
1097                     {
1098                         alias h = handlers[matches[tid].hid];
1099                         alias PT = Unqual!(Parameters!h[0]);
1100                         static if (is(ReturnType!h == void))
1101                         {
1102                             static if (isUniNodeArray!(PT, Node) || isUniNodeMapping!(PT, Node))
1103                                 mixin("handler",
1104                                     toCtString!(matches[tid].hid))(node._val!T);
1105                             else
1106                                 mixin("handler",
1107                                     toCtString!(matches[tid].hid))(node.get!(PT));
1108                             return 0;
1109                         }
1110                         else
1111                         {
1112                             static if (isUniNodeArray!(PT, Node) && isUniNodeMapping!(PT, Node))
1113                                 return mixin("handler",
1114                                     toCtString!(matches[tid].hid))(node._val!(T));
1115                             else
1116                                 return mixin("handler",
1117                                     toCtString!(matches[tid].hid))(node.get!(PT));
1118                         }
1119                     }
1120                     else
1121                     {
1122                         static if(exhaustive)
1123                             static assert(false,
1124                                 "No matching handler for type `" ~ T.stringof ~ "`");
1125                         else
1126                             throw new MatchException(
1127                                 "No matching handler for type `" ~ T.stringof ~ "`");
1128                     }
1129             }
1130         }
1131 
1132         assert (false);
1133     }
1134 }
1135 
1136 
1137 /**
1138  * Get distande by types
1139  */
1140 template GetDistance(Node, Org, Trg)
1141     if (isUniNodeType!(Org, Node) || is (Org == typeof(null)))
1142 {
1143     static if (isBoolean!Org && isBoolean!Trg)
1144         enum GetDistance = 0;
1145     else static if (isSignedNumeric!Org && isSignedNumeric!Trg)
1146         enum GetDistance = Org.sizeof - Trg.sizeof;
1147     else static if (isUnsignedNumeric!Org && isUnsignedNumeric!Trg)
1148         enum GetDistance = Org.sizeof - Trg.sizeof;
1149     else static if (isFloatingPoint!Org && isFloatingPoint!Trg)
1150         enum GetDistance = Org.sizeof - Trg.sizeof;
1151     else static if (is(Org == string) && is (Trg == string))
1152         enum GetDistance = 0;
1153     else static if (isRawData!Org && isRawData!Trg)
1154         enum GetDistance = 0;
1155     else static if (isUniNodeArray!(Org, Node) && isUniNodeArray!(Trg, Node))
1156         enum GetDistance = 0;
1157     else static if (isUniNodeMapping!(Org, Node) && isUniNodeMapping!(Trg, Node))
1158         enum GetDistance = 0;
1159     else static if (is(Org == typeof(null)) && is(Trg == typeof(null)))
1160         enum GetDistance = 0;
1161     else
1162         enum GetDistance = ubyte.max;
1163 }
1164 
1165 
1166 auto assumeSafe(F)(F fun)
1167     if (isFunctionPointer!F || isDelegate!F)
1168 {
1169     static if (functionAttributes!F & FunctionAttribute.safe)
1170         return fun;
1171     else
1172         return (ParameterTypeTuple!F args) @trusted {
1173             return fun(args);
1174         };
1175 }
1176