1 module gfx.core.typecons;
2 
3 import std.range.primitives : isInputRange;
4 
5 /// template that resolves to true if an object of type T can be assigned to null
6 template isNullAssignable(T) {
7     enum isNullAssignable =
8         is(typeof((inout int = 0) {
9             T t = T.init;
10             t = null;
11         }));
12 }
13 
14 version(unittest) {
15     private interface   ITest {}
16     private class       CTest {}
17     private struct      STest {}
18     static assert( isNullAssignable!ITest);
19     static assert( isNullAssignable!CTest);
20     static assert(!isNullAssignable!STest);
21     static assert( isNullAssignable!(STest*));
22 }
23 
24 /// constructs an option from a value
25 auto some(T)(T val) {
26     static if (isNullAssignable!T) {
27         return Option!T(val);
28     }
29     else {
30         import std.traits : Unqual;
31         return Option!(Unqual!T)(val);
32     }
33 }
34 
35 /// symbolic value that constructs an Option in none state
36 enum none(T) = Option!(T).init;
37 
38 /// Check that init value yields a none
39 unittest {
40 
41     auto vopt = none!int;
42     assert(vopt.isNone);
43     vopt = 12;
44     assert(vopt.isSome);
45     assert(vopt.front == 12);
46 
47     // auto ropt = none!CTest;
48     // assert(ropt.isNone);
49     // assert(ropt._val is null);
50     // ropt = new CTest;
51     // assert(vopt.isSome);
52 }
53 
54 auto option(R)(R input) if (isInputRange!R)
55 {
56     import std.range.primitives : ElementType;
57     alias T = ElementType!R;
58     Option!T res;
59 
60     if (!input.empty) {
61         res = input.front;
62         input.popFront();
63         assert(input.empty, "attempt to build Option with more than one element)");
64     }
65 
66     return res;
67 }
68 
69 
70 struct Option(T)
71 {
72     import std.traits : Unqual;
73     alias ValT = Unqual!T;
74     private ValT _val = ValT.init;
75 
76     static if (isNullAssignable!ValT) {
77         this(inout ValT val) inout {
78             _val = val;
79         }
80 
81         @property bool isSome() const {
82             return _val !is null;
83         }
84 
85         @property bool isNone() const {
86             return _val is null;
87         }
88 
89         void setNone() {
90             _val = null;
91         }
92 
93         void opAssign()(T val) {
94             _val = val;
95         }
96     }
97     else {
98         private bool _isSome    = false;
99 
100         this(inout ValT val) inout {
101             _val = val;
102             _isSome = true;
103         }
104 
105         @property bool isSome() const {
106             return _isSome;
107         }
108 
109         @property bool isNone() const {
110             return !_isSome;
111         }
112 
113         void setNone() {
114             .destroy(_val);
115             _isSome = false;
116         }
117 
118         void opAssign()(ValT val) {
119             _val = val;
120             _isSome = true;
121         }
122     }
123 
124     // casting to type that have implicit cast available (e.g Option!int to Option!uint)
125     auto opCast(V : Option!U, U)() if (is(T : U)) {
126         return Option!U(val_);
127     }
128 
129     @property ref inout(ValT) get() inout @safe pure nothrow
130     {
131         enum message = "Called `get' on none Option!" ~ T.stringof ~ ".";
132         assert(isSome, message);
133         return _val;
134     }
135 
136     template toString()
137     {
138         import std.format : FormatSpec, formatValue;
139         // Needs to be a template because of DMD @@BUG@@ 13737.
140         void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
141         {
142             if (isNull)
143             {
144                 sink.formatValue("Option.none", fmt);
145             }
146             else
147             {
148                 sink.formatValue(_value, fmt);
149             }
150         }
151 
152         // Issue 14940
153         void toString()(scope void delegate(const(char)[]) @safe sink, FormatSpec!char fmt)
154         {
155             if (isNull)
156             {
157                 sink.formatValue("Option.none", fmt);
158             }
159             else
160             {
161                 sink.formatValue(_value, fmt);
162             }
163         }
164     }
165 
166     // range interface
167 
168     @property bool empty() const {
169         return isNone;
170     }
171 
172     @property size_t length() const {
173         return isSome ? 1 : 0;
174     }
175 
176     @property void popFront() {
177         setNone();
178     }
179 
180     static if (isNullAssignable!ValT) {
181         @property inout(ValT) front() inout {
182             return get;
183         }
184         @property Option!(inout(ValT)) save() inout {
185             return Option!(inout(ValT))(_val);
186         }
187     }
188     else {
189         @property ValT front() const {
190             return get;
191         }
192 
193         @property Option!ValT save() const {
194             return isSome ? Option!ValT(_val) : none!T;
195         }
196     }
197 
198 }
199 
200 
201 template isOption(T)
202 {
203     import std.traits : TemplateOf;
204 
205     static if (__traits(compiles, TemplateOf!T)) {
206         enum isOption = __traits(isSame, TemplateOf!T, Option);
207     }
208     else {
209         enum isOption = false;
210     }
211 }
212 
213 static assert (isOption!(Option!int));
214 // static assert (isOption!(Option!Object));
215 static assert (!isOption!(Object));
216 
217 
218 /// execute fun with option value as parameter if option isSome
219 auto ifSome(alias fun, OptT)(OptT opt) {
220     static assert(isOption!OptT, "ifSome must be called with Option");
221     if (opt.isSome) {
222         fun(opt.get);
223     }
224     return opt;
225 }
226 /// execute fun without parameter if option isNone
227 auto ifNone(alias fun, OptT)(OptT opt) {
228     static assert(isOption!OptT, "ifNone must be called with Option");
229     if (opt.isNone) {
230         fun();
231     }
232     return opt;
233 }