get
	type folk
	city New York
	date 1 April 1995 - 30 June 1995
	end
or
	add
	performer Indigo Girls
	type folk,rock
	venue Bottom Line
	city New York
	state NY
	date 12 April 1992
	end
and it returns a list of entries from the database satisfying the
constraints or adds a new entry to the database.  After a few commands and
subcommands were added to the inline fsm it got quite unwieldy.  After
writing the fsm class I now have:
    from fsm import FSM, FSMError
    fsm = FSM()
    #       state    input  next      action
    #                       state 
    fsm.add('start', 'add', 'adding', start_add)
    fsm.add('start', 'get', 'getting', start_get)
    fsm.add('start', 'help', 'done', do_help)
    fsm.add('start', 'clear', 'start', do_clear)
    fsm.add('start', 'default', 'defaulting', start_default)
    fsm.add('start', None, 'start', noop)
    fsm.add('start', EOF, 'done', noop)
    ...
    fsm.start('start')
    ...
    try:
	for rawline in lines:
	    ...
	    word = ...
	    ...
	    fsm.execute(word)
	fsm.execute(EOF)
	...
    except FSMError:
	send_help(user, email, reply)
The code is no shorter since all the little bits of inline code turned into
a bunch of little functions, but it's a heck of a lot easier to understand
the structure looking at a set of fsm.add() calls.
(Side note:  I remember writing a finite state machine class in C for LYMB.
It wasn't this easy... :-)
Here's the FSM class:
----------cut----------
# finite state machine class
# class stores dictionary of state/input keys, values are
# next state and action
# when searching for matching state/input key, exact match
# is checked first, then state/None as default for that state.
# action is a function to execute which takes the current state
# and input as arguments
FSMError = 'Invalid input to finite state machine'
class FSM:
    def __init__(self):
	self.states = {}
	self.state = None
	self.dbg = None
    # add another state to the fsm
    def add(self, state, input, newstate, action):
	self.states[(state, input)] = (newstate, action)
    # perform a state transition and action execution
    def execute(self, input):
	si = (self.state, input)
	sn = (self.state, None)
	# exact state match?
	if self.states.has_key(si):
	    newstate, action = self.states[si]
	# no, how about a None (catch-all) match?
	elif self.states.has_key(sn):
	    newstate, action = self.states[sn]
	if self.dbg != None:
	    self.dbg.write('State: %s / Input: %s / Next State: %s / Action: %s\n' % \
			   (self.state, input, newstate, action))
	apply(action, (self.state, input))
	self.state = newstate
    # define the start state
    def start(self, state):
	self.state = state
    # assign a writable file to catch debugging transitions
    def debug(self, out):
	self.dbg = out
----------cut----------
-- Skip Montanaro skip@automatrix.com (518)372-5583 Musi-Cal: http://www.calendar.com/concerts/ -or- concerts@calendar.com Internet Conference Calendar: http://www.calendar.com/conferences/