cl-simple-fsm
2020-02-18
No Description
Upstream URL
Author
License
An intuitive implementation of a finite state machine.
1Operation
1.1Creating a state machine
First, create your state machine, and store it somewhere. (defvar *state*
(make-instance 'info.isoraqathedh.finite-state-machine:finite-state-machine
:states (list :start
:sign
:pre-decimal-point-digit
:decimal-point
:post-decimal-point-digit
:reject)
:accepting-states (list :pre-decimal-point-digit
:post-decimal-point-digit)))
(All unqualified symbols are in common-lisp or the package that this system uses,
info.isoraqathedh.finite-state-machine.)
Alternatively, you can subclass finite-state-machine
if you wish to make many of the same machine and give them an identity:
(defclass decimal-recogniser (finite-state-machine)
()
(:default-initargs
:states (list :start
:sign
:pre-decimal-point-digit
:decimal-point
:post-decimal-point-digit
:reject)
:accepting-states (list :pre-decimal-point-digit
:post-decimal-point-digit)))
(defvar *state* (make-instance 'decimal-recogniser))
You must provide make-instance a list of states, or it will complain.
If you don't provide a starting state via :start-state,
then the first one is automatically selected as the start state.
If you don't provide any :accepting-states,
this is acceptable but a little bit silly.
The states can be anything you feel is appropriate,
but if the default comparison function #'eql is inadequate,
you may want to set :test to compare them with each other.
For simplicity, choose keywords.
1.2Transitions
The next step is to define some transitions.This is done by adding methods tonext-state,which takes in the state machine (with its current state) and an event.What that event is can again be anything you desire,
as long as you can specify it as a specialiser on the method.
If you do use a one-off state machine as above,
then you should use an eql specialiser for your methods.
(defmethod next-state ((machine decimal-recogniser) (event character))
(ecase (state machine)
(:start
(case event
((#\+ #\-) :sign)
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) :pre-decimal-point-digit)
(#\. :decimal-point)
(t :reject)))
(:sign
(case event
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) :pre-decimal-point-digit)
(#\. :decimal-point)
(t :reject)))
(:pre-decimal-point-digit
(case event
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) :pre-decimal-point-digit)
(#\. :decimal-point)
(t :reject)))
((:decimal-point :post-decimal-point-digit)
(case event
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) :post-decimal-point-digit)
(t :reject)))
(:reject :reject)))
After that, you can run the machine on a particular sequence (here, a string).
To check if the machine is in an accepting state, use acceptingp:
(defun decimal-number-p (to-check)
(loop with recogniser = (make-instance 'decimal-recogniser)
for c across to-check
do (next-state! recogniser c)
finally (return (acceptingp recogniser))))
(decimal-number-p "123.45")
(decimal-number-p "-123")
(decimal-number-p "bogus")
Note we have used next-state! here,
which automatically sets the next state on the original object.
For best results, consider a token class that lists out all objects
that can change the state of the machine as the event.
2Things to do [0/4]
2.1TODOTransition table
There will be a way to more succinctly represent the transition tableso that code like state-transition-method don't have to be written.2.2TODOEncompassing macros
All of these should have some way to wrap them all around as one coherent whole.Candidates are:define-state-machine, which defines a state machineand its transitions at once; andwith-state-machine, which creates a state machinelasting for the body of the macro.
2.3TODOReconsider history
Finite state machines don't have history. It may be better to remove them.2.4TODOBuilt-in tokens
Consider creatingtokenised-finite-state-machine,which contains within it the list of tokens that it recognises.3License
MIT