Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Go generator #7

Merged
merged 5 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
##The Care and Feeding of
#SMC
##The State Machine Compiler
## 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.
SMC is a Java application that translates a state transition table into a program that implements the described state machine. Output languages include Java, Go, C, and C++. Adding other languages is trivial.

### Command Line
`ant compile && ant jar`

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

* `<language>` is either `C`, `Cpp`, or `Java`.
* `<language>` is one of: `C`, `Cpp`, `Go`, 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
### 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
Expand All @@ -30,7 +32,7 @@ When this is run through SMC it produces the source code for a state machine nam
* 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
### 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.
Expand Down Expand Up @@ -196,7 +198,7 @@ We use the _dash_ (`-`) character for two purposes. When used as an action it m

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

###Super States
### 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
Expand Down
15 changes: 15 additions & 0 deletions src/smc/Utilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ public static String commaList(List<String> names) {
return commaList;
}

public static String iotaList(String typeName, List<String> names) {
String iotaList = "";
boolean first = true;
for (String name : names) {
iotaList += "\t" + name + (first ? " " + typeName + " = iota" : "") + "\n";
first = false;
}
return iotaList;
}

public static String capitalize(String s) {
if (s.length() < 2) return s.toUpperCase();
return s.substring(0, 1).toUpperCase() + s.substring(1);
}

public static List<String> addPrefix(String prefix, List<String> list) {
List<String> result = new ArrayList<>();
for (String element : list)
Expand Down
33 changes: 33 additions & 0 deletions src/smc/generators/GoCodeGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package smc.generators;

import smc.OptimizedStateMachine;
import smc.generators.nestedSwitchCaseGenerator.NSCNodeVisitor;
import smc.implementers.GoNestedSwitchCaseImplementer;

import java.io.IOException;
import java.nio.file.Files;
import java.util.Map;

public class GoCodeGenerator extends CodeGenerator {
private GoNestedSwitchCaseImplementer implementer;

public GoCodeGenerator(OptimizedStateMachine optimizedStateMachine,
String outputDirectory,
Map<String, String> flags) {
super(optimizedStateMachine, outputDirectory, flags);
implementer = new GoNestedSwitchCaseImplementer(flags);
}

protected NSCNodeVisitor getImplementer() {
return implementer;
}

public void writeFiles() throws IOException {
String outputFileName = camelToSnake(optimizedStateMachine.header.fsm + ".go");
Files.write(getOutputPath(outputFileName), implementer.getOutput().getBytes());
}

private static String camelToSnake(String s) {
return s.replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ private NSCNode.FSMClassNode makeFsmNode(OptimizedStateMachine sm) {
fsm.stateProperty = statePropertyNode;
fsm.handleEvent = handleEventNode;
fsm.actions = sm.actions;
fsm.states = sm.states;
return fsm;
}

Expand Down
1 change: 1 addition & 0 deletions src/smc/generators/nestedSwitchCaseGenerator/NSCNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public class FSMClassNode implements NSCNode {
public String className;
public String actionsName;
public List<String> actions;
public List<String> states;

public void accept(NSCNodeVisitor visitor) {
visitor.visit(this);
Expand Down
146 changes: 146 additions & 0 deletions src/smc/implementers/GoNestedSwitchCaseImplementer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package smc.implementers;

import smc.Utilities;
import smc.generators.nestedSwitchCaseGenerator.NSCNodeVisitor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static smc.generators.nestedSwitchCaseGenerator.NSCNode.*;

public class GoNestedSwitchCaseImplementer implements NSCNodeVisitor {
private String fsmName;
private String actionsName;
private String output = "";
private List<String> actions = new ArrayList<>();
private List<Error> errors = new ArrayList<>();
private List<String> states = new ArrayList<>();
private Map<String, String> flags;

public GoNestedSwitchCaseImplementer(Map<String, String> flags) {
this.flags = flags;
}

public void visit(SwitchCaseNode switchCaseNode) {
output += String.format("\tswitch %s {\n", switchCaseNode.variableName);
switchCaseNode.generateCases(this);
output += "}\n";
}

public void visit(CaseNode caseNode) {
output += String.format("\tcase %s%s:\n", caseNode.switchName.toLowerCase(), caseNode.caseName);
caseNode.caseActionNode.accept(this);
output += "\n\n";
}

public void visit(FunctionCallNode functionCallNode) {
output += String.format("%s(", functionCallNode.functionName);
if (functionCallNode.argument != null) {
functionCallNode.argument.accept(this);
}
output += ")\n";
}

public void visit(EnumNode enumNode) {
output += String.format(
"const (\n%s)\n\n",
Utilities.iotaList(
enumNode.name.toLowerCase() + "T",
Utilities.addPrefix(enumNode.name.toLowerCase(), enumNode.enumerators)));
}

public void visit(StatePropertyNode statePropertyNode) {
output += "state"+statePropertyNode.initialState;
}

public void visit(EventDelegatorsNode eventDelegatorsNode) {
for (String event : eventDelegatorsNode.events) {
output += String.format("func (f *%s) %s() { f.processEvent(event%s, \"%s\") }\n", fsmName, event, event, event);
}
}

public void visit(FSMClassNode fsmClassNode) {
if (fsmClassNode.actionsName == null) {
errors.add(Error.NO_ACTIONS);
return;
}

fsmName = fsmClassNode.className;
actionsName = fsmClassNode.actionsName;
actions = fsmClassNode.actions;
states = fsmClassNode.states;

output += String.format(
"// Package %s is an auto-generated Finite State Machine.\n" +
"// DO NOT EDIT.\n" +
"package %s\n\n" +
"// %s is the Finite State Machine.\n" +
"type %s struct {\n" +
"\tactions %s\n" +
"\tstate stateT\n" +
"}\n\n" +
"// New returns a new %s.\n" +
"func New(actions %s) *%s {\n" +
"\t return &%s{actions: actions, state: ",
fsmName.toLowerCase(), fsmName.toLowerCase(), fsmName, fsmName,
actionsName, fsmName, actionsName, fsmName, fsmName);
fsmClassNode.stateProperty.accept(this);
output += "}\n}\n\n";

fsmClassNode.delegators.accept(this);

output += "type stateT int\n";
fsmClassNode.stateEnum.accept(this);
output += "type eventT int\n";
fsmClassNode.eventEnum.accept(this);
fsmClassNode.handleEvent.accept(this);
}

public void visit(HandleEventNode handleEventNode) {
output += String.format(
"func (f *%s) processEvent(event eventT, eventName string) {\n" +
"\tstate := f.state\n" +
"\tsetState := func(s stateT) { f.state = s; state = s }\n",
fsmName);

for (String action : actions) {
output += String.format(
"\t%s := func() { f.actions.%s() }\n",
action, Utilities.capitalize(action));
}
output += "\n";

for (String state : states) {
output += String.format(
"\tconst State%s = state%s\n",
state, Utilities.capitalize(state));
}
output += "\n";

handleEventNode.switchCase.accept(this);
output += "}\n\n";
}

public void visit(EnumeratorNode enumeratorNode) {
output += enumeratorNode.enumeration + enumeratorNode.enumerator;
}

public void visit(DefaultCaseNode defaultCaseNode) {
output += String.format(
"" +
"\tdefault:\n" +
"\t\tf.actions.UnexpectedTransition(\"%s\", eventName);\n\n",
defaultCaseNode.state);
}

public String getOutput() {
return output;
}

public List<Error> getErrors() {
return errors;
}

public enum Error {NO_ACTIONS}
}
12 changes: 12 additions & 0 deletions test_cases/go_turnstile/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
all : clean two_coin_turnstile.go turnstile_test.go
go vet .
go test .

clean :
rm -f two_coin_turnstile.go

two_coin_turnstile.go : two_coin_turnstile.sm
java -jar ../../build/jar/smc.jar -l Go $?
go fmt $@


11 changes: 11 additions & 0 deletions test_cases/go_turnstile/turnstile_actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package twocointurnstile

// TurnstileActions represents the possible actions that can be performed.
type TurnstileActions interface {
Lock()
Unlock()
AlarmOn()
AlarmOff()
Thankyou()
UnexpectedTransition(state, event string)
}
102 changes: 102 additions & 0 deletions test_cases/go_turnstile/turnstile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package twocointurnstile

import (
"fmt"
"testing"
)

var _ TurnstileActions = &MyTwoCoinTurnstile{}

// MyTwoCoinTurnstile implements the TurnstileActions interface.
type MyTwoCoinTurnstile struct {
output string
t *testing.T
}

func (m *MyTwoCoinTurnstile) Lock() {
m.output += "L"
}

func (m *MyTwoCoinTurnstile) Unlock() {
m.output += "U"
}

func (m *MyTwoCoinTurnstile) Thankyou() {
m.output += "T"
}

func (m *MyTwoCoinTurnstile) AlarmOn() {
m.output += "A"
}

func (m *MyTwoCoinTurnstile) AlarmOff() {
m.output += "O"
}

func (m *MyTwoCoinTurnstile) UnexpectedTransition(state, event string) {
e := fmt.Sprintf("X(%v,%v)", state, event)
m.output += e
}

func (m *MyTwoCoinTurnstile) check(function, want string) {
m.t.Helper()

if m.output != want {
m.t.Errorf("%s failed: got %v, want %v", function, m.output, want)
}
}

func setup(t *testing.T) (*TwoCoinTurnstile, *MyTwoCoinTurnstile) {
m := &MyTwoCoinTurnstile{t: t}
fsm := New(m)
return fsm, m
}

func TestNormalBehavior(t *testing.T) {
fsm, m := setup(t)
fsm.Coin()
fsm.Coin()
fsm.Pass()
m.check("testNormalBehavior", "UL")
}

func TestAlarm(t *testing.T) {
fsm, m := setup(t)
fsm.Pass()
m.check("testAlarm", "A")
}

func TestThankyou(t *testing.T) {
fsm, m := setup(t)
fsm.Coin()
fsm.Coin()
fsm.Coin()
m.check("testThankyou", "UT")
}

func TestNormalManyThanksAndAlarm(t *testing.T) {
fsm, m := setup(t)
fsm.Coin()
fsm.Coin()
fsm.Pass()
fsm.Coin()
fsm.Coin()
fsm.Coin()
fsm.Pass()
fsm.Pass()
m.check("testNormalManyThanksAndAlarm", "ULUTLA")
}

func TestUndefined(t *testing.T) {
fsm, m := setup(t)
fsm.Pass()
fsm.Pass()
m.check("testUndefined", "AX(Alarming,Pass)")
}

func TestReset(t *testing.T) {
fsm, m := setup(t)
fsm.Pass()
fsm.Reset()
m.check("testReset", "AOL")
}