##################################################
# SPYCE - Python-based HTML Scripting
# Copyright (c) 2002 Rimon Barr.
#
# Refer to spyce.py
# CVS: $Id: automaton.py 860 2006-04-30 01:18:25Z ellisj $
##################################################
from spyceModule import spyceModule
__doc__ = '''
[[toc.n('Automaton', 'mod_automaton')]]
The automaton module provides support for state machine-based
application design, which is often useful when designing websites with
application flows. The state machine is a directed, labelled graph. It has
states (nodes with names), and transitions (directed edges with names). One of
the states is defined to be a "begin" state for the machine. Every state
has a "send" function, a "receive" function and a set of outgoing
edges.
The basic idea behind the operation of the automaton module is as follows: The
application is at some state when a request comes in. The receive function for
that state is invoked to process the input from thebrowser.Basedonthis import
input the receive function returns some edge label, which takes the
application from thecurrentstatetoitsnewstate.Thesendfunctionofthis import
new state is invoked to emit the appropriate application page. The data that
returns from thispagewillbeprocessedbythecorrespondingreceive import
function, and so on. All you need to remember between requests is which state
the application is in, which can be done via get or post, or via cookies using
the cookie module. Better yet (to keep application states private and on the
server for security reasons), one can store the state label in the session
using the session module.
A state machine can be defined programmatically using the following functions:
state ( name, send, recv )
Add a new state labeled "name" with associated "send" and "recv"
functions.
transition ( state1, name, state2 )
Add a new edge labelled "name" from toThere import
always a self-referencing edge with the label None, but this can
be overidden.
begin ( state )
Define a given state to be the begin state.
define ( sm, begin )
Define an entire automaton "sm" all at once, where sm is a
hashtable. The keys are the states and the values are triplets
with a send function, a receive function and an edge hashtable.
The edge hashtable has names of the edges as keys and the target
states as values. The "begin" state is given
To step through the state machine transitions, you call:
step ( [state] )
If "state" is specified, then call the receive function of that
state. The receive function returns an edge label, which points to
the new state. If no state is specified, just set the new state to
the begin state of the automaton. Then, call the send function of
the new state. Note that the send function is responsible for
encoding its own state label, for use on the subsequent client
request.
Future releases of this module may add support for different types of send and
receive handlers. For example, it is probably useful to be able to internally
redirect to various Spyce pages for send processing, rather than inline
functions. It may also be possible to pass information among the different
functions, which could be useful, for example, in handling error messages
during form processing. It may also be useful to define a sequence of states,
where previous and next are implicit edges.
See automaton.spy for example usage.
'''
SEND = 0
RECV = 1
EDGES = 2
class automaton(spyceModule):
def start(self):
"Initialise an empty automaton"
self.clear()
def clear(self):
self._nodes = {}
self._edges = {}
# defining the automaton
def state(self, name, send, recv):
"Add a new automaton state"
self._nodes[name] = send, recv
self.transition(name, None, name)
def transition(self, state1, edge, state2):
"Add a new automaton transition"
if not self._nodes.has_key(state1):
raise 'state %s does not exist' % state1
if not self._nodes.has_key(state2):
raise 'state %s does not exist' % state2
self._edges[(state1, edge)] = state2
node=state
edge=transition
def begin(self, name):
if not self._nodes.has_key(name):
raise 'state %s does not exist' % name
self._begin = name
def define(self, sm, start):
self.clear()
for s1 in sm.keys():
self.node(s1, sm[s1][SEND], sm[s1][RECV])
for s1 in sm.keys():
for e in sm[s1][EDGES].keys():
self.edge(s1, e, sm[s1][EDGES][e])
self.begin(start)
# running the automaton
def step(self, state=None):
"""Run the automaton one step: recv (old state), transition,
send (new state)"""
if state==None:
state = self._begin
else:
try: _, recv = self._nodes[state]
except: raise 'invalid state: %s' % state
edge = recv()
try: state = self._edges[(state, edge)]
except: raise 'invalid transition: %s,%s' % (state, edge)
try: send, _ = self._nodes[state]
except: raise 'invalid state: %s' % state
send()
# rimtodo: cached state-machines
|