1 module aermicioi.aedi.util.typecons;
2 
3 import std..string : stripRight;
4 
5 @safe:
6 
7 /**
8 Creates a lightweight tuple of values.
9 
10 Params:
11     args = list of arguments to pack in a tuple.
12 Returns:
13     Tuple!Args a tuple of arguments, probably auto expandable thx to alias this.
14 **/
15 auto tuple(Args...)(auto ref Args args) {
16     return Tuple!Args(args);
17 }
18 
19 /**
20 ditto
21 **/
22 struct Tuple(Args...) {
23 
24     /**
25     The packed arguments.
26     **/
27     Args args;
28 
29     /**
30     Alias this to args, for possible auto expansion.
31     **/
32     alias args this;
33 
34     /**
35     Compatibility alias to expand functionality of std.typecons.Tuple
36     **/
37     alias expand = args;
38 }
39 
40 /**
41 Pseudo immutable optional with a value or not.
42 
43 Params:
44     optional = value to contain in the optional itself.
45 
46 Returns:
47     An optional filled or not with value.
48 **/
49 Optional!T optional(T)(return ref T optional) {
50     return Optional!T(optional);
51 }
52 
53 /**
54 ditto
55 **/
56 Optional!T optional(T)(return T optional) {
57     return Optional!T(optional);
58 }
59 
60 /**
61 ditto
62 **/
63 Optional!T optional(T)() {
64     return Optional!T();
65 }
66 
67 /**
68 ditto
69 **/
70 struct Optional(T) {
71     private T payload_;
72     immutable bool isNull = true;
73 
74     /**
75     Constructor for pseudo immutable optional.
76 
77     Params:
78         payload = payload to hold onto.
79     **/
80     this(ref T payload) return {
81         static if (is(T : Z*, Z) || is(T == class) || is(T == interface)) {
82             this.isNull = payload is null;
83         } else {
84             this.isNull = false;
85         }
86 
87         this.payload_ = payload;
88     }
89 
90     /**
91     Return a const version of this optional.
92 
93     Returns:
94         Optional!(const T) a constant version.
95     **/
96     Optional!(const T) opCast(X : const T)() {
97         return Optional!(const T)(payload);
98     }
99 
100     /**
101     Get the payload hosted in this optional.
102 
103     Returns:
104         T payload that is hosted.
105     **/
106     ref inout(T) payload() inout pure nothrow @nogc @safe
107     out (; !isNull, "Cannot provide a value out of optional when it is null.") {
108         return this.payload_;
109     }
110 
111     /**
112     Get the payload if exists or provide an alternative.
113 
114     Params:
115         alternative = alternative to supply
116 
117     Returns:
118         Always a payload to use.
119     **/
120     ref inout(T) orElse(return ref scope inout(T) alternative) inout pure nothrow @nogc @safe {
121 
122         if (this == null) {
123             return alternative;
124         }
125 
126         return this.payload_;
127     }
128 
129     void orElseThrow(T : Exception)(lazy T exception) inout pure @safe {
130         if (this == null) {
131             throw exception;
132         }
133     }
134 
135     /**
136     Check whether optional is null or not.
137 
138     Params:
139         term = null term to accept.
140 
141     Returns:
142         true if null, false otherwise
143     **/
144     bool opEquals(inout typeof(null) term) inout pure nothrow @nogc @safe {
145         return this.isNull;
146     }
147 
148     bool opEquals()(inout lazy T value) inout {
149         if (this == null) {
150             return false;
151         }
152 
153         return this.payload == value;
154     }
155 
156     /**
157     Convenience subtyping.
158     **/
159     alias payload this;
160 }
161 
162 /**
163 Pair a key and a value toghether.
164 
165 Params:
166     value = value to pair with key
167     key = key associated to value
168 
169 Returns:
170     Pair!(ValueType, KeyType) a pair of key => value.
171 **/
172 Pair!(ValueType, KeyType) pair(ValueType, KeyType)(auto ref ValueType value, auto ref KeyType key) {
173     return Pair!(ValueType, KeyType)(value, key);
174 }
175 
176 /**
177 ditto
178 **/
179 alias paired = pair;
180 
181 /**
182 ditto
183 **/
184 struct Pair(ValueType, KeyType) {
185 
186     /**
187     Value in pair
188     **/
189     ValueType value;
190 
191     /**
192     Key in pair
193     **/
194     KeyType key;
195 }
196 
197 /**
198 Interface for objects that can be subscribed to specific events emmited by them.
199 
200 Params:
201     EventType = type of events for which implementor will call subscribers.
202     Args = list of arguments accepted by subscriber
203 **/
204 @safe interface Subscribable(EventType, Callback)
205 {
206 
207     public
208     {
209 
210         /**
211         Subscribe a delegate to a particular event emmited by object
212 
213         Params:
214         	type = type of event emmited by object
215             subscriber = the callback to be called on event emmited
216         Returns:
217         	typeof(this)
218         **/
219         Subscribable subscribe(EventType type, Callback subscriber);
220     }
221 }
222 
223 /**
224 A default mixin implementing storage logic for subscribers.
225 **/
226 mixin template SubscribableMixin(EventType, Callback) {
227     private {
228         Callback[][EventType] subscribers;
229     }
230 
231     @safe public
232     {
233 
234         /**
235         Subscribe a delegate to a particular event emmited by object
236 
237         Params:
238         	type = type of event emmited by object
239             subscriber = the callback to be called on event emmited
240         Returns:
241         	typeof(this)
242         **/
243         typeof(this) subscribe(EventType type, Callback subscriber)
244         in (subscriber !is null, "Cannot subcribe to event, when no subscriber is passed(null).")
245         {
246             subscribers[type] ~= subscriber;
247 
248             return this;
249         }
250     }
251 
252     @safe private {
253         /**
254         Share subscribers with another subscribable.
255 
256         Params:
257             subscribable = subscribable that wil receive subscribers
258 
259         Returns:
260             this
261         **/
262         inout(typeof(this)) transfer(Subscribable!(EventType, Callback) subscribable) inout {
263             foreach (key, subscribers; subscribers) {
264                 foreach (subscriber; subscribers) {
265                     subscribable.subscribe(key, subscriber);
266                 }
267             }
268 
269             return this;
270         }
271 
272         /**
273         Get subscribers for particular event.
274 
275         Params:
276             type = event type
277 
278         Returns:
279             List of subscribers
280         **/
281         inout(Callback)[] subscribersOfEvent(EventType type) inout {
282             if (type in subscribers) {
283                 return subscribers[type];
284             }
285 
286             return [];
287         }
288 
289         private alias Params = Parameters!Callback;
290 
291         /**
292         Invokes all subscribers for an event with arguments.
293 
294         Params:
295             type = event type
296             args = list of arguments to subscriber
297         **/
298         ReturnType!Callback invoke(EventType type, Params params) const {
299             import std.range : empty;
300             static if (!is(ReturnType!Callback == void)) {
301                 ReturnType result;
302             }
303 
304             auto subscribers = subscribersOfEvent(type);
305 
306             if (subscribers.empty) {
307                 static if (!is(ReturnType!Callback == void)) {
308                     return result;
309                 } else {
310                     return;
311                 }
312             }
313 
314             foreach (subscriber; subscribers[0 .. $ - 1]) {
315                 subscriber(params);
316             }
317 
318             static if (!is(ReturnType!Callback == void)) {
319                 return subscribers[$ - 1](params);
320             } else {
321                 subscribers[$ - 1](params);
322             }
323         }
324 
325         static if (!is(ReturnType!Callback == void) && (Params.length > 0)) {
326             /**
327             Invokes all subscribers for an event with arguments.
328 
329             Compared to invoke method, this method will pass return value of
330             subscribers to subsequent invoked ones, if possible.
331 
332             Params:
333                 at = at which argument position to replace argument from args with return value of precedent value;
334                 type = event type
335                 args = list of arguments to subscriber
336             **/
337             ReturnType transform(size_t at)(EventType type, Params args) const
338                 if ((at < args.length) && is(ReturnType : Args[at])) {
339                 ReturnType result = args[at];
340 
341                 foreach (subscriber; subscribersOfEvent(type)) {
342                     result = subscriber(args[0 .. at], result, args[at + 1 .. $]);
343                 }
344 
345                 static if (!is(ReturnType == void)) {
346                     return result;
347                 }
348             }
349         }
350     }
351 }
352 
353 /**
354 Mix in an all args constructor
355 **/
356 mixin template AllArgsConstructor() {
357 
358     /**
359     Generated constructor accepting all defined fields.
360 
361     Params:
362         args = list of fields as arguments to constructor
363 
364     Returns:
365         Fully constructed typeof(this) instance
366     **/
367     this()(auto ref inout typeof(this.tupleof) args) inout {
368 
369         this.tupleof = args;
370     }
371 }
372 
373 mixin template ArgsConstructor(Args...) {
374 
375     debug pragma(msg, typeof(Args));
376 
377     /**
378     Generated constructor accepting a list of defined fields.
379 
380     Params:
381         args = list of fields as arguments to constructor
382 
383     Returns:
384         Fully constructed typeof(this) instance
385     **/
386     this()(auto ref inout scope typeof(Args) args) {
387         static foreach (index, arg; Args) {
388             arg = args[index];
389         }
390     }
391 }
392 
393 /**
394 Mix in a copy constructor
395 **/
396 mixin template CopyConstructor() {
397 
398     /**
399     Generated copy constructor.
400 
401     Params:
402         copyable = copyable instance
403 
404     Returns:
405         Fully constructed typeof(this) instance
406     **/
407     this()(auto ref scope typeof(this) copyable) {
408         this.tupleof = copyable.tupleof;
409     }
410 }