Skip to content

Commit 94d2aaa

Browse files
committed
First commit
Presenting the automatabpp module
1 parent 362b7e7 commit 94d2aaa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+7931
-1
lines changed

README.md

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,110 @@
1-
# Python Automata Based Programming Paradigm
1+
# Python Automata-Based Programming Paradigm
2+
3+
### Short introduction to the project
4+
5+
Presenting my Python programming paradigm with which we can separate the application into several parts:
6+
7+
- Behaviour
8+
- Execution
9+
- MainLoop
10+
11+
Many of the people reading this might think: _"Hey, but MainLoop **is** the behaviour and execution as well"_ and you would be right.
12+
However, do allow me to elaborate on this. My reasoning is that everything should be simplified if it can be.
13+
14+
The goal here is actually to define some behaviour parts of your program in easy to read graphs and only write the execution parts in the code.
15+
This way you can concentrate on writing code for your main loop only.
16+
17+
Naturally, everything is better when there is an example with it to explain it even more so I recommend you check [the examples](#additional-links) in the repository first.
18+
19+
### A short explanation of the project
20+
21+
To really simplify what this is all about in one sentence: ___"Instead of programming the code yourself you draw graphs to define the behaviour."___
22+
23+
These graphs are loaded when first executing your code and are _automatically_ connected to their execution part.
24+
25+
Each graph is called a machine, and each execution is called a state. (Yes, there can be several machines working consecutively at the same time)
26+
27+
To get a better idea of what's going on here's a quick example. Using a [__yEd Graph Editor__][4] Create a .graphml graph like this one and store it in the _graphs/_ folder of your project as _first.graphml_ .
28+
29+
![](./README/images/first.png)
30+
31+
Run the next code: (don't forget to clone the repository into your project folder)
32+
33+
```python
34+
import automatabpp as FSM
35+
36+
FSM.BEHAVIOUR.load_behaviour_from_graph("first.graphml", "My First Finite State Machine")
37+
38+
@FSM.EXECUTION.state
39+
def ON_START(*args, **kwargs):
40+
print("FSM start")
41+
42+
@FSM.EXECUTION.state
43+
def HELLO(*args, **kwargs):
44+
print("Hello!")
45+
46+
FSM.OPERATION.start_fsm() # prints "FSM start"
47+
FSM.OPERATION.run_fsm("is_anyone_there") # prints "Hello!"
48+
```
49+
50+
To learn more please check [the examples at the bottom of the page](#additional-links).
51+
52+
***
53+
54+
### Why a paradigm and not a pattern?
55+
56+
While developing this project I ran into this quote on [wikipedia][1]. To quote:
57+
58+
> "Automata-based programming is a programming paradigm in which the program or part of it is thought of as a model of a finite state machine (automatabpp) or any other (often more complicated) formal automaton."
59+
60+
There is also a [definition on programming paradigm][2] that I found fitting this project perfectly.
61+
62+
> "A programming paradigm is a style, or “way,” of programming."
63+
64+
Indeed, there is more than enough style in developing this way once you learn how to do it.
65+
66+
### How does this differ from ... ?
67+
68+
There are countless ways to develop your program and this one is just one take on it.
69+
However I do feel there are some advanteges to using the Automata-Based programming over some other approach.
70+
* Explanation of the code can be easily visualized since most of the code is done by drawing graphs
71+
* If the machines are well defined the execution and the main loop can be really simple
72+
* __It's fun__ _(if you consider drawing FSMs to be fun)_
73+
74+
I'll also leave these [two quotes here][3]:
75+
> "Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman
76+
77+
> "Typing is no substitute for thinking." - Richard W. Hamming
78+
79+
### What other software do I need ?
80+
I would recommend you download the [yEd Graph Editor from their official homepage][4]. This project can only import ___.graphml___ graphs at the moment.
81+
However I do plan on adding code to import more formats in the future.
82+
83+
__yEd Graph Editor__ is used for viewing and drawing our state machine graphs.
84+
We consider nodes of the graph to be the states and the edges to be the transitions.
85+
86+
I also recommend using **Python 3.4+** although I haven't even tried it yet on the older versions. This project uses `logging` and `xml` Python modules so install them if needed.
87+
88+
### Directories
89+
90+
* **./automatabpp** - our module source code. This directory contains the python code for this project.
91+
* **./comparisons** - a small module which offers some lambda comparison functions you can use with this project.
92+
* **./graphs** - yEd graphs are stored in this directory by default
93+
* **./README** - I've really took my time to write all of this so I would appreciate you checking it out
94+
95+
### Additional Links
96+
97+
Here are some other .md links for You to read about this project.
98+
* [A list of available functions](README/FSM.md)
99+
* [Tutorial](README/tutorial.md) - make a _"Hello, World!"_ type of application (sort of). Also some more insight on what happens in the code itself.
100+
* [Example 1](README/examples/example1.md) - a simple e-mail validation machine
101+
* [Example 2](README/examples/example2.md) - an example for developing with multiple machines
102+
* [Example 3](README/examples/example3.md) - an example showcasing Base64 encode with this paradigm
103+
* [Example 4](README/examples/example4.md) - developing your embedded application with _complex_ behaviour
104+
* [Other](README/other.md)
105+
106+
107+
[1]: https://en.wikipedia.org/wiki/Automata-based_programming "Automata-based programming"
108+
[2]: https://cs.lmu.edu/~ray/notes/paradigms/ "Programming Paradigms"
109+
[3]: https://en.wikiquote.org/wiki/Programming_languages "Wikiquote - Programming languages"
110+
[4]: https://www.yworks.com/products/yed "yWorks Homepage"

README/FSM.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# FSM
2+
3+
I won't cover too much how the code is written but rather how to use it.
4+
5+
Once we `import automatabpp` into our python script we can use the following 4 classes.
6+
7+
| [OPERATION](#operation) | [BEHAVIOUR](#behaviour) | [EXECUTION](#execution) | [INTERFACE](#interface) |
8+
| --- | --- | --- | --- |
9+
10+
11+
### OPERATION
12+
13+
Operation is a class that only executes the global operations on the FSMs.
14+
```python
15+
OPERATION.start_fsm() # Starts the FSMs by sending the '_start_' command to all the machines
16+
17+
OPERATION.stop_fsm() # Stops all the FSMs by sending the '_stop_' command to all the machines,
18+
# reseting them to the default '_START_' state and emptying the machine commands stack.
19+
20+
OPERATION.reset_fsm() # Stops and starts the FSMs
21+
22+
OPERATION.run_fsm() # Runs all the commands in the CommandQueue until the queue is empty.
23+
24+
OPERATION.run_fsm(cmd) # Runs only the cmd on all machines now without running the rest of the queue.
25+
```
26+
27+
### BEHAVIOUR
28+
Behaviour class is used to load the machine behaviour from the graph.
29+
```python
30+
BEHAVIOUR.set_default_graph_directory(path) # sets the default graph directory path
31+
32+
BEHAVIOUR.load_behaviour_from_graph(path, machine_name) # loads the machine from path and stores it as machine_name
33+
```
34+
35+
### EXECUTION
36+
Execution class consists only of an operator on our function that defines what function should be called once the state has been reached.
37+
```python
38+
EXECUTION.state(func) # usually used as a decorator over the function we wish to be called on state execution
39+
```
40+
41+
### INTERFACE
42+
Interface is an interface to the automatabpp we can use. Consists of only a decorator we can use on a function.
43+
```python
44+
INTERFACE.run_command_if_lambda_on_result_true(lambda_function, command)
45+
# When the function decorated with this function is called, the command will be run if the lambda on result is True
46+
```
47+
48+
49+
| [Back to Main][prev] | ----- | [Tutorial][next] |
50+
| --- | --- | --- |
51+
52+
[prev]: ../README.md "Main"
53+
[next]: tutorial.md "Tutorial"

README/examples/example1.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Example 1
2+
3+
Here is an explanation of [example1.py][pycode] script you can run.
4+
5+
| [BACKGROUND](#background) | [GRAPH](#graph) | [CODE OVERVIEW](#code-overview)| [OTHER](#other) |
6+
| --- | --- | --- | --- |
7+
8+
### Background
9+
10+
One of the basic uses of FSMs is regex validation, and one of the most used regex validations is e-mail address validation.
11+
12+
[This stack overflow answer][1] sheds some light on what regex we should use to check if an e-mail is valid or not.
13+
14+
What I will do here is walk you through implementing a simple automatabpp overcoming this problem.
15+
16+
### Graph
17+
18+
Here is the graph from the above mentioned stack overflow answer:
19+
20+
<img src="https://i.stack.imgur.com/YI6KR.png" width="640" height="400" />
21+
22+
I guess this graph could have been made somewhat simpler but this is not of interest at the moment.
23+
What we will do is implement just the upper half of this graph as we really don't want to deal with some weird ASCII characters currently.
24+
25+
The image of our state machine that implements this behaviour can be seen on the next picture. It is located in the _graphs_ folder under the name _email/email.graphml_
26+
27+
<img src="../images/examples/example1/email.png" width="640" height="420" />
28+
29+
Be sure to check how the transitions are defined. If we want to use multiple commands between the same state we only need to separate it by spaces or newlines.
30+
31+
If the validation fails at any step the state of the machine will be `GARBAGE` and if this has been our last character to check we only need to send the `_stop_` command to see if the result is an e-mail or not.
32+
33+
34+
### Code Overview
35+
36+
Let's see how this is implemented in the [example1.py][pycode] script.
37+
38+
39+
- We import all the automatabpp module classes:
40+
```python
41+
from automatabpp import *
42+
```
43+
- This is a function we will use to shorten the code:
44+
```python
45+
def test_email():
46+
for char in email_to_test:
47+
OPERATION.run_fsm(char)
48+
OPERATION.stop_fsm()
49+
```
50+
What this means is we will take one by one character from the e-mail and run it through our machine. At the very end we will send the `_stop_` command to let the automatabpp know we're done.
51+
52+
- Here comes our graph loading and execution definitions. The `EXECUTION.state` decorator connects the function to the state with the same name in the graph:
53+
```python
54+
BEHAVIOUR.load_behaviour_from_graph("email/email.graphml", "E-Mail validation machine")
55+
56+
@EXECUTION.state
57+
def GARBAGE(**_):
58+
print("{} IS NOT A VALID EMAIL".format(email_to_test))
59+
60+
@EXECUTION.state
61+
def EMAIL(**_):
62+
print("{} IS A VALID EMAIL".format(email_to_test))
63+
```
64+
We are only interested in two states here: "Is it, or is it not, a valid e-mail?"
65+
66+
- Let's run the machine and check our first e-mail address:
67+
```python
68+
OPERATION.start_fsm()
69+
70+
email_to_test = "[email protected]"
71+
test_email()
72+
```
73+
- Once we're done we can restart the machine since it is currently in __EMAIL__ state and check another e-mail address:
74+
```python
75+
OPERATION.reset_fsm()
76+
77+
email_to_test = "an._invalid@email@example-"
78+
test_email()
79+
```
80+
81+
If we run our code we should see the following in the console:
82+
```console
83+
user@computer:~$ python example1.py
84+
a_valid@email.example IS A VALID EMAIL
85+
an._invalid@email@example- IS NOT A VALID EMAIL
86+
user@computer:~$
87+
```
88+
89+
### Other
90+
91+
Let's check a couple of things we've learned here:
92+
* We don't need to define every state execution in the code
93+
* The FSMs can be reset at any time
94+
* We can define multiple transitions using the same edge in a graph
95+
96+
97+
| [Back to Tutorial][prev] | ----- | [Example 2][next] |
98+
| --- | --- | --- |
99+
100+
[1]: https://stackoverflow.com/questions/201323/how-to-validate-an-email-address-using-a-regular-expression "E-Mail regex"
101+
[pycode]: ../../example1.py "pycode"
102+
[prev]: ../tutorial.md "Tutorial"
103+
[next]: example2.md "Example 2"

README/examples/example2.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Example 2
2+
3+
I needed a simple example of how the machines can work and communicate together so I came up with this one. Check the code at [example2.py][pycode] .
4+
5+
| [BACKGROUND](#background) | [GRAPH](#graph) | [CODE OVERVIEW](#code-overview)| [OTHER](#other) |
6+
| --- | --- | --- | --- |
7+
8+
### Background
9+
10+
It might not be obvious so far but there can be multiple machines parsing commands at the same time. They can even
11+
command each other what to do next, and all that without having to write a single line of py code (other than importing and running the automatabpp).
12+
13+
### Graph
14+
15+
Let's see the two graphs we will be using this time. The first one is called _hbfs/harderfaster.graphml_
16+
17+
<img src="../images/examples/example2/harderfaster.png" width="640" height="420" />
18+
19+
I've already put a note in the image about what you should note this time. The node `HARDER` has the value `better` in it's description. What this means is that once the state `HARDER` is executed the command `better` will be added to the `CommandQueue` for later execution.
20+
21+
State `FASTER` also has a similar description named `stronger`.
22+
23+
Let's check the graph under _hbfs/betterstronger.graphml_
24+
25+
<img src="../images/examples/example2/betterstronger.png" width="640" height="420" />
26+
27+
The graph is almost the same with the only difference of one additional state.
28+
This state is added so the two graphs are balanced and don't start at the same time.
29+
30+
31+
### Code Overview
32+
33+
Let's see how this is implemented in the [example2.py][pycode] script.
34+
35+
36+
- We import all the automatabpp module classes:
37+
```python
38+
from automatabpp import *
39+
```
40+
- Let's define a function for printing everything in the same line but with a delay:
41+
```python
42+
def print_w_timeout(text: str):
43+
import time
44+
print("\r {}".format(text), end="")
45+
time.sleep(0.6)
46+
```
47+
48+
- Load the first graph:
49+
```python
50+
BEHAVIOUR.load_behaviour_from_graph("hbfs/harderfaster.graphml", "HARDER/FASTER")
51+
52+
@EXECUTION.state
53+
def HARDER(**_):
54+
print_w_timeout("Work it harder")
55+
56+
@EXECUTION.state
57+
def FASTER(**_):
58+
print_w_timeout("Do it faster")
59+
```
60+
The execution is just printing with our custom print function.
61+
62+
- Do the same for the second graph:
63+
```python
64+
BEHAVIOUR.load_behaviour_from_graph("hbfs/betterstronger.graphml", "BETTER/STRONGER")
65+
66+
@EXECUTION.state
67+
def BETTER(**_):
68+
print_w_timeout("make it better")
69+
70+
@EXECUTION.state
71+
def STRONGER(**_):
72+
print_w_timeout("makes us stronger")
73+
```
74+
75+
- All that's left is to run the machine:
76+
```python
77+
OPERATION.start_fsm()
78+
OPERATION.run_fsm()
79+
```
80+
One thing to note here is that the `OPERATION.run_fsm()` is executed here without any argument.
81+
That means it will run all the commands in the `CommandQueue` until the queue is empty.
82+
83+
Run the code and see what you get.
84+
```console
85+
user@computer:~$ python example2.py
86+
```
87+
88+
### Other
89+
90+
Let's check a couple of things we've learned here:
91+
* We can define multiple graphs at the same time
92+
* The after-execution-commands can also be defined in the graph
93+
* Graphs defined in this way can be stuck in an infinite loop
94+
95+
| [Back to Example 1][prev] | ----- | [Example 3][next] |
96+
| --- | --- | --- |
97+
98+
[pycode]: ../../example2.py "pycode"
99+
[prev]: example1.md "Example 1"
100+
[next]: example3.md "Example 3"

0 commit comments

Comments
 (0)