• Stars
    star
    119
  • Rank 297,930 (Top 6 %)
  • Language
    Java
  • Created about 10 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

State Machine Compiler for Clean Code video series.

##The Care and Feeding of #SMC ##The State Machine Compiler

SMC is a Java application that translates a state transition table into a program that implements the described state machine. Output languages include Java, C, and C++. Adding other languages is trivial.

###Command Line java -jar smc.jar -l <language> -o <directory> -f <flags>

  • <language> is either C, Cpp, or Java.
  • <directory> is the output directory. Your new state machine will be written there.
  • <flags> currently for Java only. package:package_name will put the appropriate package statement in the generated code.

###Syntax The syntax for the state transition table is based on a simple state transition table. Here is a straightforward example that describes the logic of a subway turnstile. turnstile.sm:

Initial: Locked
FSM: Turnstile
{
  Locked    Coin    Unlocked    unlock
  Locked    Pass    Locked      alarm
  Unlocked  Coin    Unlocked    thankyou
  Unlocked  Pass    Locked      lock
}

When this is run through SMC it produces the source code for a state machine named Turnstile. That machine starts in the Locked state, and follows the following logic:

  • Given we are in the Locked state, when we get a Coin event, then we transition to the Unlocked state and invoke the unlock action.
  • Given we are in the Locked state, when we get a Pass event, then we stay in the Locked state and invoke the alarm action.
  • Given we are in the Unlocked state, when we get a Coin event, then we stay in the Unlocked state and invoke the thankyou action.
  • GIven we are in the Unlocked state, when we get a Pass event, then we transition to the Locked state and invoke the lock action.

###Opacity One of the goals of SMC is to produce code that the programmer never needs to look at, and does not check in to source code control. It is intended that SMC will generate the appropriate code during the pre-compile phase of your build.

The output of SMC is two sets of functions: The Event functions and the Actions functions. For most languages these functions will be arranged into an abstract class in which the Event functions are public, and the Action functions are protected and abstract.

The programmer derives an implementation class from the generated class, and implements all the action functions. The programmer then creates an instance of the implementation class and invokes the appropriate event functions as those events occur. The generated code will make sure that the appropriate action functions are called in response to those events.

The state of the generated state machine is opaque to the programmer and kept private to the generated code. From the programmer's point of view the generated code is a black box that translates events into actions.

Here is a UML diagram that depicts the situation. The programmer writes the user and the Implementation. The generated Turnstile class is opaque.

               +---------------+
+------+       | <<generated>> |
| user |------>|   Turnstile   |
+------+       +---------------+
               | + Coin()      |
               | + Pass()      |
               +---------------+
                       A
                       |
                       |
          +-------------------------+
          |     Implementation      |
          +-------------------------+
          | # lock()                |
          | # unlock()              |
          | # thankyou()            |
          | # alarm()               |
          | # unhandledTransition() |
          +-------------------------+

Generated Java Code

Here is the Java code that SMC will generate for this state machine. It is an abstract class that provides a simple nested switch/case implementation, a set of public event functions, and a set of protected abstract methods for the action functions.

public abstract class Turnstile {
	public abstract void unhandledTransition(String state, String event);
    private enum State {Locked,Unlocked}
    private enum Event {Coin,Pass}
	private State state = State.Locked;
	private void setState(State s) {state = s;}
	public void Coin() {handleEvent(Event.Coin);}
	public void Pass() {handleEvent(Event.Pass);}
	private void handleEvent(Event event) {
		switch(state) {
			case Locked:
			switch(event) {
				case Coin:
				setState(State.Unlocked);
				unlock();
				break;
				case Pass:
				setState(State.Locked);
				alarm();
				break;
				default: unhandledTransition(state.name(), event.name()); break;
			}
			break;
			case Unlocked:
			switch(event) {
				case Coin:
				setState(State.Unlocked);
				thankyou();
				break;
				case Pass:
				setState(State.Locked);
				lock();
				break;
				default: unhandledTransition(state.name(), event.name()); break;
			}
			break;
		}
	}
	protected abstract void thankyou();
	protected abstract void unlock();
	protected abstract void alarm();
	protected abstract void lock();
}

Actions Interface

It is often more convenient to express the abstract Action functions as an interface, or an abstract class. We can accomplish this by adding the Actions: header to the state machine description.

Initial: Locked
FSM: Turnstile
Actions: TurnstileActions
{
  ...
}

The programer will write the TurnstileActions interface to declare all the Action functions. SMC will generate code that implements that interface. Here how this looks in UML:

          +-------------------------+
          |      <<interface>>      |
          |     Implementation      |
          +-------------------------+
          | + lock()                |
          | + unlock()              |
          | + thankyou()            |
          | + alarm()               |
          +-------------------------+
                       A
                       |
               +---------------+
+------+       | <<generated>> |
| user |------>|   Turnstile   |
+------+       +---------------+
               | + Coin()      |
               | + Pass()      |
               +---------------+
                       A
                       |
                       |
          +-------------------------+
          |     Implementation      |
          +-------------------------+
          | # lock()                |
          | # unlock()              |
          | # thankyou()            |
          | # alarm()               |
          | # unhandledTransition() |
          +-------------------------+

NOTE: This is optional for Java, but necessary in C and C++. In C, the TurnstileActions interface is implemented as a struct holding pointers to functions to the Action functions. (See the test_cases directory).

Syntactic Sugar.

While the syntax described so far is sufficient for any state machine, there are things we can do to make it more convenient. For example, the Turnstile can be expressed more simply by combining the transitions that share the same starting state:

Initial: Locked
FSM: Turnstile
{
  Locked    {
    Coin    Unlocked    unlock
    Pass    Locked      alarm
  }
  Unlocked  {
    Coin    Unlocked    thankyou
    Pass    Locked      lock
  }
}

Now let's add an Alarming state that must be reset by a repairman:

Initial: Locked
FSM: Turnstile
{
  Locked    {
    Coin    Unlocked    unlock
    Pass    Alarming    alarmOn
    Reset   -           {alarmOff lock}
  }
  Unlocked  {
    Reset   Locked      {alarmOff lock}
    Coin    Unlocked    thankyou
    Pass    Locked      lock
  }
  Alarming {
    Coin    -          -
    Pass    -          -  
    Reset   Locked     {alarmOff lock}
  }
}

We use the dash (-) character for two purposes. When used as an action it means that there are no actions to perform. When used as the next-state it means that the state does not change. Note: the star (*) character can be used as a synonym for dash.

When more than one action should be performed, they can be grouped together in braces ({}).

###Super States Notice the duplication of the Reset transition. In all three states the Reset event does the same thing. It transitions to the Locked state and it invokes the lock and alarmOff actions. This duplication can be eliminated by using a Super State as follows:

Initial: Locked
FSM: Turnstile
{
  // This is an abstract super state.
  (Resetable)  {
    Reset       Locked       {alarmOff lock}
  }
  Locked : Resetable    { 
    Coin    Unlocked    unlock
    Pass    Alarming    alarmOn
  }
  Unlocked : Resetable {
    Coin    Unlocked    thankyou
    Pass    Locked      lock
  }
  Alarming : Resetable { // inherits all it's transitions from Resetable.
  }
}

The parenthesis around Resetable indicate that it is an abstract state. The State machine will never actually be in this state. Rather, it exists to be used as a super-state by Locked, Unlocked, and Alarming. Note the use of the colon (:) character to denote that those three states derive from Resetable.

A state with a super-state inherits all the transitions of that super-state. Super-state transitions can be overridden if necessary. What's more, a state can derive from more than one super-state by using additional colon operators as follows:

state : superstate1 : superstate2 {...

Super-states do not have to be abstract. A state can derive from any other state, whether abstract or not. However, if we mark a state as abstract, then SMC will ensure that it is never used as the target of a transition. The state machine will never be in that state.

Comments

A comment is any string beginning with two slashes, and ending with a line-end. They can be placed at the start of a line as in the example above; or they can be placed at the end of a line.

Entry and Exit actions

In the previous example, the fact that the alarm is turned on every time the Alarming state is entered and is turned off every time the Alarming state is exited, is hidden within the logic of several different transitions. We can make it explicit by using entry actions and exit actions.

Initial: Locked
FSM: Turnstile
{
  (Resetable) {
    Reset       Locked       -
  }
  Locked : Resetable <lock     {
    Coin    Unlocked    -
    Pass    Alarming    -
  }
  Unlocked : Resetable <unlock  {
    Coin    Unlocked    thankyou
    Pass    Locked      -
  }
  Alarming : Resetable <alarmOn >alarmOff   -    -    -
}

The less-than (<) character denote an entry-action. It is invoked whenever the state is entered. Likewise the greater-than (>) character denotes an exit-action which is invoked whenever the state is exited.

In the above example, notice that nearly all the actions have been restated as entry- and exit-actions. You may find that this makes the state machine more readable.

The Entry- and Exit-actions of superstates are inherited by their derivative states.

Semantic Differences with Entry- and Exit-actions.

Note also that there is a slight semantic difference between the last two examples. If we are in the Locked state, and we get a Reset event, then the lock action will be invoked even though we are already in the locked state. This is because every transition invokes all the exit- and entry-actions, regardless of whether the state is actually changing. Thus, when we are in the Unlocked state, and we get a Coin event, even though we stay in the Unlocked state, the unlock action will be invoked.

Internal Structure.

The internal structure of SMC is a simple traditional compiler. Here is a picture:

+-------+    +--------+    +----------+    +-----------+    +-----------+    +--------------+
| Lexer |--->| Parser |--->| Semantic |--->| Optimizer |--->| Generator |--->| Implementing |
+-------+    +--------+    | Analyzer |    +-----------+    +-----------+    |   Visitor    |
                           +----------+                                      +--------------+

This closely reflects the package structure of the java code.

  • The Lexer translates the source code into a stream of lexical tokens which act as events going into the Parser.
  • The Parser is a simple finite state machine that implements the Backus-Naur description of the source code (See below). That state machine is implemented as a simple state transition table held within a Java array of Transition objects. The actions of that parser state machine use the Builder pattern to create a Syntax Data Structure.
  • The Semantic Analyzer ensures that the Syntax Data Structure describes a true finite state machine, and if so, translates it into a Semantic Data Structure that can only hold true finite state machines.
  • The Optimizer then translates the Semantic Data Structure into a simple state transition table. It reduces all the super state inheritance, and the entry- and exit-actions back into vanilla states, events, and actions.
  • The Generator converts the optimized state transition table into a set of code-generation-nodes that represent a Nested Switch Case statement in a language agnostic way.
  • Finally, the Implementing Visitors translate the code-generation-nodes into a true programming language, like Java.

The upshot of all this is that you can generate a new language, like C#, by simply writing a new Implementing Visitor, which is a relatively trivial task. (See Below)

Writing a Code Generator

The value of the -l command line argument is used to find a class whose name is:

smc.generators.<LANGUAGE>CodeGenerator.

This class decides which Implementing Visitor to create, and how the output files should be written.

You can create a generator for a new language by deriving that class from smc.generators.CodeGenerator and putting it in the classpath. Check out the source code for the Java code generator. It's pretty straightforward.

BNF

The Backus-Naur form (BNF) of the SMC source code is:

<FSM> ::= <header>* <logic>
<header> ::= <name> ":" <name>
    
<logic> ::= "{" <transition>* "}"
<transition> ::= <state-spec> <subtransition>
             |   <state-spec> "{" <subtransition>* "}"
<state-spec> :== <state> <state-modifier>*	
<state> ::= <name> | "(" <name> ")"	
<state-modifier> :== ":" <name>
                 |   "<" <name>
                 |   ">" <name>
<subtransition> :: <event> <next-state> <action>
<action> ::= <name> | "{" <name>* "}" | "-"
<next-state> ::= <state> | "-"
<event> :: <name> | "-"

License

You may use this program and source code for any purpose at all at your own risk. It is not copyrighted. Have fun!

More Repositories

1

fitnesse

FitNesse -- The Acceptance Test Wiki
Java
2,031
star
2

spacewar

Space War starting in Episode 55 of cleancoders.com
Clojure
605
star
3

more-speech

A Nostr browser in Clojure.
Clojure
283
star
4

PPP

Excercises for Principles, Patterns, and Practices, iHop, Pood.
Java
158
star
5

PDP8EmulatorIpad

PDP8 Emulator for the iPad
Lua
112
star
6

videostore

The videostore example from Martin Fowler's Refactoring, and from Episode 3 of cleancoders.com
Java
84
star
7

MACS_GOMOKU

Mobile Application Case Study -- GOMOKU project
Swift
82
star
8

WTFisaMonad

Talk: WTF is a Monad
Clojure
79
star
9

clojureOrbit

Orbital simulator in Clojure
Clojure
77
star
10

AdventOfCode2022

Advent of Code 2022
Clojure
73
star
11

ubc-website

Website for Uncle Bob Consulting. Example for cleancoders.com functional programming series.
Clojure
70
star
12

rubyslim

Slim port for Ruby
Ruby
61
star
13

Episode-10-ExpenseReport

The Expense Report example from cleancoders.com episode 10
Java
58
star
14

javaargs

The Java version of the Args Program.
Java
53
star
15

FunctionalDesign

The source code for the examples in Functional Design
Clojure
53
star
16

unclebob.github.io

Uncle Bob's blog
HTML
47
star
17

AdventOfCode2021

Clojure
47
star
18

Clean-Code-Performance

C++
44
star
19

HTWCleanCoders

Hunt the Wumpus for Clean Coders Acceptance Testing Episodes.
Java
33
star
20

Euler

Clojure
33
star
21

sudoku

Sudoku solver for E61 of Clean Code (cleancoders.com)
Clojure
27
star
22

BinarySearch

Binary Search using TDD
Java
24
star
23

AdventOfCode2020

Clojure
20
star
24

mastermindclj

Master Mind in Clojure for Clean Code Functional series.
Clojure
20
star
25

functor

experimental macro for allowing Algol-like block structure scoping in clojure
Clojure
19
star
26

HTW

Hunt The Wumpus
JavaScript
19
star
27

springslim

A Java Slim Service that manages Spring Transactions.
Java
18
star
28

rubyargs

The ruby version of the Args program that I initially wrote in Java.
Ruby
17
star
29

smcjava

State Machine Compiler
16
star
30

wator

Clojure
16
star
31

nslim

.NET port of SLIM.
C#
14
star
32

environmentcontroller

TDD Exercise
Java
14
star
33

AOC23

Clojure
13
star
34

Empire

Based on old VMS empire game
Java
13
star
35

BIP340-elliptic-curve-signatures

Explore signing documents using BIP340 Schnorr Signatures. A la https://bips.xyz/340
Clojure
13
star
36

MazeSolver

Solve a maze from an image file.
Clojure
11
star
37

RndSelect

The project in response to @ploeh's blog about types and tests.
11
star
38

tddrefcpp

Exercises and Examples for TDD/REF in C++
C++
10
star
39

fSharpCaseStudy

F#
10
star
40

bugs

A simple game of chasing bugs
Clojure
9
star
41

gravity

A simple gravity simulator
Clojure
9
star
42

covid_analysis

Clojure
9
star
43

empty

OM Empty Repository
9
star
44

fitnesseextras

Some utilities for FitNesse.
Java
9
star
45

javasquint

A simple iterator based lazy evaluation program.
Java
8
star
46

fitnessedotorg

FitNesse website fitnesse.org
JavaScript
8
star
47

perforcecmsystem

Plugin for marrying FitNesse and PerForce by Markus Gartner
Java
8
star
48

CleanIOT

Python
7
star
49

turtle-graphics

A simple graphics tool based loosely on Logo turtle graphics.
Clojure
7
star
50

Welc

The code for the WELC course
Java
7
star
51

ScriptSchedule

Converts FinalDraft script to a shooting schedule suitable for Numbers or Excel.
Clojure
6
star
52

Mage-of-Lief

Partially completed scupture.
Ruby
6
star
53

GoVideoStore

Martin Fowler's lovely Video Store problem, in GoLang. For Clean Code 2d. ed.
Go
5
star
54

RubyLibraryIHop

project for IHOP course in Ruby
Ruby
4
star
55

BoboliaTaxes

A simple tax calculator for the mythical land of Bobolia. Example from Clean Code 2d. ed.
Python
3
star
56

clojars-unclebobmartin

clojars group verification
2
star