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