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 }