1 /**
2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 */
6 module my.fsm;
7 
8 import std.format : format;
9 
10 /** A state machine derived from the types it is based on.
11  *
12  * Each state have its unique data that it works on.
13  *
14  * The state transitions are calculated by `next` and the actions are performed
15  * by `act`.
16  */
17 struct Fsm(StateTT...) {
18     static import sumtype;
19 
20     alias StateT = sumtype.SumType!StateTT;
21 
22     /// The states and state specific data.
23     StateT state;
24 
25     /// Log messages of the last state transition (next).
26     /// Only updated in debug build.
27     string logNext;
28 
29     /// Helper function to convert the return type to `StateT`.
30     static StateT opCall(T)(auto ref T a) {
31         return StateT(a);
32     }
33 }
34 
35 /// Transition to the next state.
36 template next(handlers...) {
37     void next(Self)(auto ref Self self) if (is(Self : Fsm!StateT, StateT...)) {
38         static import sumtype;
39 
40         auto nextSt = sumtype.match!handlers(self.state);
41         debug self.logNext = format!"%s -> %s"(self.state, nextSt);
42 
43         self.state = nextSt;
44     }
45 }
46 
47 /// Act on the current state. Use `(ref S)` to modify the states data.
48 template act(handlers...) {
49     void act(Self)(auto ref Self self) if (is(Self : Fsm!StateT, StateT...)) {
50         static import sumtype;
51 
52         sumtype.match!handlers(self.state);
53     }
54 }
55 
56 @("shall transition the fsm from A to B|C")
57 unittest {
58     struct Global {
59         int x;
60     }
61 
62     struct A {
63     }
64 
65     struct B {
66         int x;
67     }
68 
69     struct C {
70         bool x;
71     }
72 
73     Global global;
74     Fsm!(A, B, C) fsm;
75     bool running = true;
76 
77     while (running) {
78         fsm.next!((A a) { global.x++; return fsm(B(0)); }, (B a) {
79             running = false;
80             if (a.x > 3)
81                 return fsm(C(true));
82             return fsm(a);
83         }, (C a) { running = false; return fsm(a); });
84 
85         fsm.act!((A a) {}, (ref B a) { a.x++; }, (C a) {});
86     }
87 
88     assert(global.x == 1);
89 }
90 
91 /** Hold a mapping between a Type and data.
92  *
93  * The get function is used to get the corresponding data.
94  *
95  * This is useful when e.g. combined with a state machine to retrieve the state
96  * local data if a state is represented as a type.
97  *
98  * Params:
99  *  RawDataT = type holding the data, retrieved via opIndex
100  *  Ts = the types mapping to RawDataT by their position
101  */
102 struct TypeDataMap(RawDataT, Ts...)
103         if (is(RawDataT : DataT!Args, alias DataT, Args...)) {
104     alias SrcT = Ts;
105     RawDataT data;
106 
107     this(RawDataT a) {
108         this.data = a;
109     }
110 
111     void opAssign(RawDataT a) {
112         this.data = a;
113     }
114 
115     static if (is(RawDataT : DataT!Args, alias DataT, Args...))
116         static assert(Ts.length == Args.length,
117                 "Mismatch between Tuple and TypeMap template arguments");
118 }
119 
120 auto ref get(T, TMap)(auto ref TMap tmap)
121         if (is(TMap : TypeDataMap!(W, SrcT), W, SrcT...)) {
122     template Index(size_t Idx, T, Ts...) {
123         static if (Ts.length == 0) {
124             static assert(0, "Type " ~ T.stringof ~ " not found in the TypeMap");
125         } else static if (is(T == Ts[0])) {
126             enum Index = Idx;
127         } else {
128             enum Index = Index!(Idx + 1, T, Ts[1 .. $]);
129         }
130     }
131 
132     return tmap.data[Index!(0, T, TMap.SrcT)];
133 }
134 
135 @("shall retrieve the data for the type")
136 unittest {
137     import std.typecons : Tuple;
138 
139     TypeDataMap!(Tuple!(int, bool), bool, int) a;
140     static assert(is(typeof(a.get!bool) == int), "wrong type");
141     a.data[1] = true;
142     assert(a.get!int == true);
143 }