11.3. Framework Interface Basics
We can make some easy choices right away. Because of goal 1 aboveand, of course, because of the name of this chapterwe're going to design an embedded DSL. That means taking the library approach rather than building a translator (like YACC) or an interpreter (like Make).
Though it may not be obvious from our CD player example, we might in general want to create multiple instances of any finite state machine, each with a separate state. Therefore, it makes sense to encapsulate FSM logic in a reusable package. Since C++'s unit of data encapsulation is a class, our framework is going to help us build FSM classes. Because the whole FSM will be represented as a class, it seems reasonable to represent transition actions like start playback as member functions of that class.
We'd like to be able to use readable names like Playing and open_close to indicate states and events. At this point we can't say much about what kind of C++ entity (type, integer value, function object, and so on) to use for state names like Playing. Events are a different story, though: In the CD player, only the cd-detected event contains data, but in general every distinct kind of event might need to transmit a different data type. Therefore, event names should denote types. To embed arbitrary data in an event, the FSM author can just declare an event class with corresponding data members.
Given that finite state machines will be classes, and that events will be implemented as types, we can imagine that a state machine built with our framework might be used as follows:
int main()
{
player p; // an instance of the FSM
p.process_event(open_close()); // user opens CD player
p.process_event(open_close()); // inserts CD and closes
p.process_event( // CD is detected
cd_detected(
"louie, louie"
, std::vector<std::clock_t>( /* track lengths */ )
)
);
p.process_event(play()); // etc.
p.process_event(pause());
p.process_event(play());
p.process_event(stop());
return 0;
}
 |