A program used to compile dot graph snippets into state machine directly used within python code.
Originally a tool for the ENACRobotique robotics club.
Until OCaml and Opam support Windows completely, this program is Linux only.
Install ocaml and opam from your package manager. Then using opam, configure an additionnal repo:
opam repo add KirrimK https://github.com/KirrimK/opam-repo.gitThen install the compiler:
opam update
opam install graph2stratA .deb package built from the statically-built executable is made available for the sake of convenience.
Download the latest .deb package from the Releases page.
Install it using dpkg:
sudo dpkg -i graph2strat-static-x.x.x.deb
Clone this repo, then using opam, install the required dependencies: dune, menhir, dune-build-info (and odoc for the documentation).
Then run dune build to build the compiler.
Run the build using _build/default/bin/main.exe <input_file> [-o <output_file>].
OCaml code documentation can be generated using dune build @doc.
Automated tests can be run using dune runtest.
Run the program using g2s <dot_input_file> [-o <python_output_file>] (or whatever the name of your executable is, if downloaded as binary from the github releases page).
This will generate a file named <dot_input_file>_g2s.py containing the generated python file in the current folder.
You can also specify the output file using the '-o' option.
It will take files using this format:
// comments are allowed like this
#init NameOfInitState //the initial state is declared with the #init keyword
digraph STM_NAME { // the graph name is used as the name of the state machine (must start by a letter)
NameOfState [comment="enter:on_enter;leave:on_leave;loop:on_loop"] // This is a node, the on_enter and on_leave methods are declared in the comment
NameOfInitState -> NameOfState [label="guard"] // This is a transition between states, the guard is declared in the label
//comment
}Import the corresponding generated file in your python code, and use it like this:
from <name_of_generated_file> import G2S
class Parent:
# Write here the code for your parent object, it will be passed to the state machine
def on_enter(self, local, name_previous_state: str) -> None:
print("on_enter")
def on_leave(self, local, name_next_state: str) -> None:
print("on_leave")
def on_loop(self, local) -> None:
print("on_loop")
def guard(self, local) -> bool:
print("guard")
return True
my_parent_object = Parent()
my_stm = G2S(my_parent_object, debug=True)
my_stm.start() # start the state machine, on_enter for the initial state will be called
my_stm.step() # calls on_loop of the current state, and checks for transitions
...Here is the structure your dot file should have:
//you can have comments like this
#init NameOfInitState
digraph STM_NAME {
<states and transition declarations> //you can add a comment at the end of each line
//on empty lines too
}States are declared using the following syntax:
NameOfState [comment="type:function_name;..."] // explicitely declares a stateHere are examples of valid callbacks to specify in the comment field:
enter:function_name: the functionfunction_namewill be called when entering the stateleave:function_name: the functionfunction_namewill be called when leaving the stateloop:function_name: the functionfunction_namewill be called repeatedly while in the state and at least once
Combine all those by separating them with a semicolon, in any order:
enter:function_name;leave:function_name;loop:function_name
The on_enter function should have the following signature:
# in the parent object
def on_enter(self, local, name_previous_state: str) -> None:
# do stuff
passThe on_leave function should have the following signature:
# in the parent object
def on_leave(self, local, name_next_state: str) -> None:
# do stuff
passThe on_loop function should have the following signature:
# in the parent object
def on_loop(self, local) -> None:
# do stuff
passThe local variable is an object that you can use to stored temporary variables while being in a state: it is created when entering a state, destroyed when leaving it and passed to the loop and guard functions.
Transitions are declared using the following syntax:
NameOfInitState -> NameOfState [label="function_name"] // declares a transition from NameOfInitState to NameOfState that checks the guard against function_name
{StateA StateB StateC ...} -> NameOfState [label="function_name"] // declares a transition from multiple states to NameOfState that checks the guard against function_name
You can also check the test folder for examples.
The guard is expected to have the following signature:
# in the parent object
def guard(self, local) -> bool:
if <should_activate_transition>:
return True
else:
return FalseDon't declare nodes with the same name but different caracteristics, it's a bad practice anyway. State names cannot be 1 character long.
Partial auto testing is done, but please check that the output seems to correspond to the input. If a bug is found, please file an issue on the github page.