1 
2 module state_machine.base;
3 
4 /// State machine using an integer or string state variable.
5 mixin template StateMachine(alias variable, states...)
6     if(((is(typeof(variable) : int) || is(typeof(variable) : string)) && states.length > 0) ||
7       (is(typeof(variable) == enum) && states.length == 0))
8 {
9     import state_machine.util;
10 
11     import std.algorithm;
12     import std.meta;
13     import std.traits;
14 
15     private
16     {
17         static if(is(typeof(variable())))
18         {
19             // For properties, .stringof ends with parentheses.
20             enum __name__ = variable.stringof[0 .. $ - 2];
21         }
22         else
23         {
24             enum __name__ = variable.stringof;
25         }
26 
27         static if(is(typeof(variable) == enum))
28         {
29             // States on enum types are derived from their members.
30             enum __states__ = __traits(allMembers, typeof(variable));
31         }
32         else
33         {
34             enum __states__ = states;
35         }
36 
37         struct BeforeTransition
38         {
39             string state;
40         }
41 
42         struct AfterTransition
43         {
44             string state;
45         }
46 
47         typeof(variable) __prevState__;
48     }
49 
50     @property
51     static string[] opDispatch(string op : __name__ ~ "Names")()
52     {
53         return [ __states__ ];
54     }
55 
56     @property
57     static typeof(variable)[string] opDispatch(string op : __name__ ~ "Values")()
58     {
59         typeof(variable)[string] values;
60 
61         static if(is(typeof(variable) == enum))
62         {
63             foreach(string state; __states__)
64             {
65                 values[state] = __traits(getMember, typeof(variable), state);
66             }
67         }
68         else static if(is(typeof(variable) : int))
69         {
70             foreach(typeof(variable) index, string state; __states__)
71             {
72                 values[state] = index;
73             }
74         }
75         else
76         {
77             foreach(string state; __states__)
78             {
79                 // The most useful.
80                 values[state] = state;
81             }
82         }
83 
84         return values;
85     }
86 
87     @property
88     bool opDispatch(string state)()
89         if([ __states__ ].countUntil(state) != -1)
90     {
91         // Compare state variable.
92         static if(is(typeof(variable) == enum))
93         {
94             return variable == __traits(getMember, typeof(variable), state);
95         }
96         else static if(is(typeof(variable) : int))
97         {
98             // Ensure countUntil happens at compile-time.
99             enum index = [ __states__ ].countUntil(state);
100             return variable == index;
101         }
102         else
103         {
104             return variable == state;
105         }
106     }
107 
108     typeof(variable) opDispatch(string op : "prev" ~ __name__)()
109     {
110         return __prevState__;
111     }
112 
113     void opDispatch(string op : "revert" ~ __name__)()
114     {
115         variable = __prevState__;
116     }
117 
118     bool opDispatch(string op)()
119         if(op.length > 2 && op[0 .. 2] == "to" &&
120           [ __states__ ].map!toTitle.countUntil(op[2 .. $]) != -1)
121     {
122         enum index = [ __states__ ].map!toTitle.countUntil(op[2 .. $]);
123 
124         // Fire and check any BeforeTransition callbacks.
125         foreach(name; __traits(allMembers, typeof(this)))
126         {
127             alias member = Alias!(__traits(getMember, typeof(this), name));
128 
129             static if(is(typeof(member) == function))
130             {
131                 static if(arity!member <= 1)
132                 {
133                     foreach(attribute; __traits(getAttributes, member))
134                     {
135                         static if(is(attribute == BeforeTransition) ||
136                                  (is(typeof(attribute) == BeforeTransition) &&
137                                   attribute.state == __states__[index]))
138                         {
139                             static if(is(typeof(member()) : bool))
140                             {
141                                 static if(arity!member == 1)
142                                 {
143                                     // Callback can accept destination state.
144                                     bool result = member(__states__[index]);
145                                 }
146                                 else
147                                 {
148                                     bool result = member();
149                                 }
150 
151                                 if(!result)
152                                 {
153                                     return false;
154                                 }
155                             }
156                             else
157                             {
158                                 static if(arity!member == 1)
159                                 {
160                                     member(__states__[index]);
161                                 }
162                                 else
163                                 {
164                                     member();
165                                 }
166                             }
167                         }
168                     }
169                 }
170             }
171         }
172 
173         // Save previous state.
174         __prevState__ = variable;
175 
176         // Update state variable.
177         static if(is(typeof(variable) == enum))
178         {
179             enum string constant = __states__[index];
180             variable = __traits(getMember, typeof(variable), constant);
181         }
182         else static if(is(typeof(variable) : int))
183         {
184             variable = index;
185         }
186         else
187         {
188             variable = __states__[index];
189         }
190 
191         // Fire any AfterTransition callbacks.
192         foreach(name; __traits(allMembers, typeof(this)))
193         {
194             alias member = Alias!(__traits(getMember, typeof(this), name));
195 
196             static if(is(typeof(member) == function))
197             {
198                 static if(arity!member <= 1)
199                 {
200                     foreach(attribute; __traits(getAttributes, member))
201                     {
202                         static if(is(attribute == AfterTransition) ||
203                                  (is(typeof(attribute) == AfterTransition) &&
204                                   attribute.state == __states__[index]))
205                         {
206                             static if(arity!member == 1)
207                             {
208                                 // Callback can accept destination state.
209                                 member(__states__[index]);
210                             }
211                             else
212                             {
213                                 member();
214                             }
215                         }
216                     }
217                 }
218             }
219         }
220 
221         return true;
222     }
223 }