1 /**
2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 This module contains utilities to reduce the boilerplate when implementing a
11 FSM.
12 
13 # Callback Builder
14 Useful to generate the callbacks that control the actions to perform in an FSM
15 or the state transitions.
16 */
17 module dextool.fsm;
18 
19 /** Generate a mixin of callbacks for each literal in `EnumT`.
20  *
21  * Example:
22  * ---
23  * enum MyAct { foo }
24  * mixin(makeCallback!MyAct.switchOn("act").callbackOn("obj").finalize);
25  * // generates a callback such as this
26  * obj.actFoo();
27  * ---
28  *
29  * Params:
30  *  EnumT = enum to switch on. All literals must be implemented as callbacks.
31  */
32 BuildCallbacks!EnumT makeCallbacks(EnumT)() {
33     return BuildCallbacks!EnumT();
34 }
35 
36 struct BuildCallbacks(EnumT) {
37     private {
38         /// variable determining the callback.
39         string callbackSwitch_;
40         /// if specified it will be the object to do the callbacks on.
41         string objVar_;
42         /// prefi all callbacks with this string.
43         string prefix_;
44         /// default parameters used in the callback
45         string[] defaultParams_;
46         /// if accessing EnumT requires lookup specification
47         string lookup_;
48     }
49 
50     /// specific parameters for an enum literal.
51     string[][EnumT] specificParams;
52 
53     /// How to lookup `EnumT`, if needed.
54     auto lookup(string v) {
55         this.lookup_ = v;
56         return this;
57     }
58 
59     /// Determine which callback to use.
60     auto switchOn(string v) {
61         this.callbackSwitch_ = v;
62         return this;
63     }
64 
65     /// Object to do the callbacks on, if specified.
66     auto callbackOn(string v) {
67         this.objVar_ = v;
68         return this;
69     }
70 
71     /// Prefix all callbacks with `v`.
72     auto prefix(string v) {
73         this.prefix_ = v;
74         return this;
75     }
76 
77     /// Default parameters to pass on to all callbacks.
78     auto defaultParams(string[] v) {
79         this.defaultParams_ = v;
80         return this;
81     }
82 
83     /// If a specific callback requires unique parameters.
84     auto specificParam(EnumT e, string[] v) {
85         this.specificParams[e] = v;
86         return this;
87     }
88 
89     /// Returns: the mixin code doing the callbacks as specified.
90     string finalize() {
91         import std.conv : to;
92         import std.format : format;
93         import std.traits : EnumMembers;
94         import std.uni : toUpper;
95 
96         const enumFqn = () {
97             if (lookup_.length == 0)
98                 return EnumT.stringof;
99             return lookup_ ~ "." ~ EnumT.stringof;
100         }();
101         string s = format("final switch(%s) {\n", callbackSwitch_);
102         static foreach (a; EnumMembers!EnumT) {
103             {
104                 const literal = a.to!string;
105                 const callback = () {
106                     if (prefix_.length == 0)
107                         return literal;
108                     else if (literal.length == 1)
109                         return prefix_ ~ literal.toUpper;
110                     return format("%s%s%s", prefix_, literal[0].toUpper, literal[1 .. $]);
111                 }();
112                 const obj = objVar_ is null ? null : objVar_ ~ ".";
113                 const paramsRaw = () {
114                     if (auto p = a in specificParams)
115                         return *p;
116                     return defaultParams_;
117                 }();
118                 const params = format("%-(%s, %)", paramsRaw);
119                 s ~= format("case %s.%s: %s%s(%s);break;\n", enumFqn, literal,
120                         obj, callback, params);
121             }
122         }
123         s ~= "}";
124 
125         return s;
126     }
127 }
128 
129 @("shall construct a string with callbacks for each enum literal")
130 unittest {
131     static struct Struct {
132         enum Dummy {
133             a,
134             fortyTwo
135         }
136     }
137 
138     void preA(string x) {
139     }
140 
141     void preFortyTwo(string a, string b) {
142     }
143 
144     Struct.Dummy sw;
145     enum r = makeCallbacks!(Struct.Dummy).lookup("Struct").switchOn("sw")
146             .prefix("pre").specificParam(Struct.Dummy.fortyTwo, ["foo", "bar"]).finalize;
147     assert(r == "final switch(sw) {
148 case Struct.Dummy.a: preA();break;
149 case Struct.Dummy.fortyTwo: preFortyTwo(foo, bar);break;
150 }", r);
151 }