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