initial commit
This commit is contained in:
commit
44a768a6c0
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# python
|
||||
/venv
|
9
Makefile
Normal file
9
Makefile
Normal file
|
@ -0,0 +1,9 @@
|
|||
SOURCE=state-machine.gv
|
||||
|
||||
png:
|
||||
dot -Tpng ${SOURCE} -ooutput.png
|
||||
|
||||
.PHONY: cpp
|
||||
cpp: venv
|
||||
./generator.py -i ${SOURCE}
|
||||
clang++ --std=c++11 *.cpp -o state_machine
|
18
README.md
Normal file
18
README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# State machine generator
|
||||
|
||||
This tool is used to describe a state machine in dot format (used by Graphviz).
|
||||
It then generates the corresponding C++ code with identified callbacks.
|
||||
|
||||
## Usage
|
||||
- Create a Python3 virtualenv with pydot installed
|
||||
- Create a dot file with the state machine named "state_machine.gv"
|
||||
- Run 'make png' to see the PNG representation
|
||||
- Run 'make cpp' to generate the C++ code
|
||||
|
||||
## C++ code
|
||||
The tool will generate 3 files:
|
||||
- 'state_machine.h' and 'state_machine.cpp' that contain the skeleton of the
|
||||
state machine
|
||||
- 'state_impl.cpp' that contains the empty callbacks
|
||||
|
||||
You can compile these sources with a simple main like the one provided.
|
209
generator.py
Executable file
209
generator.py
Executable file
|
@ -0,0 +1,209 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
from string import Template
|
||||
import pydot
|
||||
|
||||
def get_states(edges):
|
||||
states = dict()
|
||||
for edge in graph.get_edges():
|
||||
source = edge.get_source()
|
||||
dest = edge.get_destination()
|
||||
if source not in states.keys():
|
||||
states[source] = []
|
||||
if dest not in states.keys():
|
||||
states[dest] = []
|
||||
|
||||
states[source].append(dest)
|
||||
|
||||
return states
|
||||
|
||||
|
||||
def generate_states_stub(state_names):
|
||||
# Specialization
|
||||
with open('state_impl.cpp', 'w') as fd:
|
||||
fd.write('''#include "state_machine.h"
|
||||
''')
|
||||
state_spe_impl = Template('''
|
||||
//
|
||||
// State $name
|
||||
//
|
||||
void State$name::enter()
|
||||
{
|
||||
}
|
||||
|
||||
void State$name::leave()
|
||||
{
|
||||
}
|
||||
|
||||
''')
|
||||
for state_name in state_names:
|
||||
d = dict(name=state_name)
|
||||
fd.write(state_spe_impl.substitute(d))
|
||||
|
||||
|
||||
def generate_header(states):
|
||||
state_names = states.keys()
|
||||
nb_states = len(state_names)
|
||||
with open("state_machine.h", "w") as fd:
|
||||
fd.write('''#include <array>
|
||||
#include <string>
|
||||
|
||||
''')
|
||||
|
||||
# Enum
|
||||
fd.write('enum StateEnum {\n ')
|
||||
fd.write(',\n '.join(state_names))
|
||||
fd.write('\n};\n')
|
||||
|
||||
# State
|
||||
state_template = Template('''
|
||||
class State {
|
||||
public:
|
||||
State() {};
|
||||
~State() {};
|
||||
bool canTransitionTo(const State& state) const;
|
||||
std::string get_name() const;
|
||||
void print() const;
|
||||
|
||||
StateEnum value;
|
||||
|
||||
virtual void enter() {};
|
||||
virtual void leave() {};
|
||||
|
||||
protected:
|
||||
std::array<bool, $nb_states> transitions;
|
||||
|
||||
private:
|
||||
};
|
||||
''')
|
||||
|
||||
d = dict(nb_states=nb_states)
|
||||
fd.write(state_template.substitute(d))
|
||||
|
||||
# Specialization
|
||||
state_spe_template = Template('''
|
||||
class State$name: public State {
|
||||
public:
|
||||
State$name() {
|
||||
value = $name;
|
||||
transitions = $transitions;
|
||||
};
|
||||
|
||||
void enter();
|
||||
void leave();
|
||||
};
|
||||
''')
|
||||
for state_name in state_names:
|
||||
transitions = ['true' if x in states[state_name] else 'false' for x in state_names]
|
||||
str_transitions = '{ ' + ', '.join(transitions) + ' }'
|
||||
|
||||
d = dict(name=state_name, transitions=str_transitions)
|
||||
fd.write(state_spe_template.substitute(d))
|
||||
|
||||
# State machine
|
||||
state_machine_template = Template('''
|
||||
class StateMachine {
|
||||
public:
|
||||
StateMachine(StateEnum initial_state):currentState(initial_state) {};
|
||||
~StateMachine() {};
|
||||
|
||||
bool transitionTo(StateEnum s);
|
||||
void print() const;
|
||||
|
||||
private:
|
||||
StateEnum currentState;
|
||||
std::array<State, $nb_states> states = $states_array;
|
||||
};
|
||||
''')
|
||||
states_class = ['State' + x + '()' for x in state_names]
|
||||
states_array = '{ ' + ', '.join(states_class) + ' }'
|
||||
d = dict(nb_states=nb_states, states_array=states_array)
|
||||
fd.write(state_machine_template.substitute(d))
|
||||
|
||||
|
||||
def generate_impl(states):
|
||||
state_names = states.keys()
|
||||
nb_states = len(state_names)
|
||||
with open('state_machine.cpp', 'w') as fd:
|
||||
state_machine_impl = Template('''#include "state_machine.h"
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool State::canTransitionTo(const State& state) const
|
||||
{
|
||||
return this->transitions[state.value];
|
||||
}
|
||||
|
||||
std::string State::get_name() const
|
||||
{
|
||||
const array<string, $nb_states> names = $states_list;
|
||||
return names[this->value];
|
||||
}
|
||||
|
||||
void State::print() const
|
||||
{
|
||||
const array<string, $nb_states> names = $states_list;
|
||||
const string& name = names[this->value];
|
||||
cout << "State " << this->get_name() << " (" << this->value << ")\\n";
|
||||
}
|
||||
|
||||
bool StateMachine::transitionTo(StateEnum s)
|
||||
{
|
||||
State& currentState = this->states[this->currentState];
|
||||
State& nextState = this->states[s];
|
||||
const bool allowed = currentState.canTransitionTo(nextState);
|
||||
|
||||
cout << "Transition from " << currentState.get_name()
|
||||
<< " to " << nextState.get_name() << ": ";
|
||||
if (allowed) {
|
||||
cout << "allowed." << "\\n";
|
||||
} else {
|
||||
cout << "denied." << "\\n";
|
||||
}
|
||||
|
||||
if (allowed) {
|
||||
currentState.leave();
|
||||
nextState.enter();
|
||||
this->currentState = s;
|
||||
}
|
||||
|
||||
return allowed;
|
||||
}
|
||||
|
||||
void StateMachine::print() const
|
||||
{
|
||||
cout << "States:\\n";
|
||||
for (const auto& s : this->states) {
|
||||
s.print();
|
||||
}
|
||||
}
|
||||
''')
|
||||
states_list = '{ "' + '", "'.join(state_names) + '" }'
|
||||
d = dict(nb_states=nb_states, states_list=states_list)
|
||||
fd.write(state_machine_impl.substitute(d))
|
||||
|
||||
|
||||
def generate_cpp(states):
|
||||
generate_header(states)
|
||||
generate_impl(states)
|
||||
generate_states_stub(states)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Generate code for state machine.')
|
||||
parser.add_argument('--input', '-i', type=open, help='Graphviz file representing the state machine.')
|
||||
|
||||
args = parser.parse_args()
|
||||
text = args.input.read()
|
||||
graphs = pydot.graph_from_dot_data(text)
|
||||
|
||||
for graph in graphs:
|
||||
#print(graph)
|
||||
|
||||
states = get_states(graph.get_edges)
|
||||
print(states)
|
||||
generate_cpp(states)
|
19
main.cpp
Normal file
19
main.cpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include "state_machine.h"
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
StateMachine mach(Boot);
|
||||
mach.print();
|
||||
|
||||
mach.transitionTo(Gallery);
|
||||
mach.transitionTo(GalleryTransition);
|
||||
mach.transitionTo(CaptureStill);
|
||||
mach.transitionTo(Settings);
|
||||
mach.transitionTo(SettingsTransition);
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
21
state-machine.gv
Normal file
21
state-machine.gv
Normal file
|
@ -0,0 +1,21 @@
|
|||
digraph One {
|
||||
Boot -> GalleryTransition;
|
||||
|
||||
GalleryTransition -> Gallery;
|
||||
Gallery -> CaptureStillTransition;
|
||||
Gallery -> CaptureVideoTransition;
|
||||
|
||||
CaptureStillTransition -> CaptureStill;
|
||||
CaptureStillTransition -> Gallery;
|
||||
CaptureStill -> GalleryTransition;
|
||||
CaptureStill -> SettingsTransition;
|
||||
|
||||
CaptureVideoTransition -> CaptureVideo;
|
||||
CaptureVideoTransition -> Gallery;
|
||||
CaptureVideo -> GalleryTransition;
|
||||
CaptureVideo -> SettingsTransition;
|
||||
|
||||
SettingsTransition ->Settings;
|
||||
Settings -> CaptureStillTransition;
|
||||
Settings -> CaptureVideoTransition;
|
||||
}
|
Loading…
Reference in a new issue