blob: ff27c896686a2b0c4bf352e79868d6180f3f7ff2 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (c) 2008, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# ---
#
# Create a state machine object based on a definition file.
#
__author__ = 'falmeida@google.com (Filipe Almeida)'
class OrderedDict:
"""Ordered dictionary implementation."""
# Define the minimum functionality we need for our application.
# Easiser would be to subclass from UserDict.DictMixin, and only
# define __getitem__, __setitem__, __delitem__, and keys, but that's
# not as portable. We don't need to define much more, so we just do.
def __init__(self):
self._dict = {}
self._keys = []
def __getitem__(self, key):
return self._dict[key]
def __setitem__(self, key, value):
if key not in self._keys:
self._keys.append(key)
self._dict[key] = value
def __delitem__(self, key):
self._keys.remove(key)
del self._dict[key]
def keys(self):
return self._keys
# Below are all we have to define in addition to what DictMixin would need
def __len__(self):
return len(self.keys())
def __contains__(self, key):
return self.has_key(key)
def __iter__(self):
# It's not as portable -- though it would be more space-efficient -- to do
# for k in self.keys(): yield k
return iter(self.keys())
class State(object):
"""Contains information about a specific state."""
def __init__(self):
pass
name = None
external_name = None
transitions = []
class Transition(object):
"""Contains information about a specific transition."""
def __init__(self, condition, source, destination):
self.condition = condition
self.source = source
self.destination = destination
class FSMConfig(object):
"""Container for the statemachine definition."""
sm = {} # dictionary that contains the finite state machine definition
# loaded from a config file.
transitions = [] # List of transitions.
conditions = {} # Mapping between the condition name and the bracket
# expression.
states = OrderedDict() # Ordered dictionary of states.
name = None
comment = None
def AddState(self, **dic):
"""Called from the definition file with the description of the state.
Receives a dictionary and populates internal structures based on it. The
dictionary is in the following format:
{'name': state_name,
'external': exposed state name,
'transitions': [
[condition, destination_state ],
[condition, destination_state ]
]
}
"""
state = State()
state.name = dic['name']
state.external_name = dic['external']
state_transitions = []
for (condition, destination) in dic['transitions']:
transition = Transition(condition, state.name, destination)
state_transitions.append(transition)
self.transitions.extend(state_transitions)
state.transitions = state_transitions
self.states[state.name] = state
def AddCondition(self, name, expression):
"""Called from the definition file with the definition of a condition.
Receives the name of the condition and it's expression.
"""
self.conditions[name] = expression
def Load(self, filename):
"""Load the state machine definition file.
In the definition file, which is based on the python syntax, the following
variables and functions are defined.
name: Name of the state machine
comment: Comment line on the generated file.
condition(): A mapping between condition names and bracket expressions.
state(): Defines a state and it's transitions. It accepts the following
attributes:
name: name of the state
external: exported name of the state. The exported name can be used
multiple times in order to create a super state.
transitions: List of pairs containing the condition for the transition
and the destination state. Transitions are ordered so if
a default rule is used, it must be the last one in the list.
Example:
name = 'c comment parser'
condition('/', '/')
condition('*', '*')
condition('linefeed', '\\n')
condition('default', '[:default:]')
state(name = 'text',
external = 'comment',
transitions = [
[ '/', 'comment_start' ],
[ 'default', 'text' ]
])
state(name = 'comment_start',
external = 'comment',
transitions = [
[ '/', 'comment_line' ],
[ '*', 'comment_multiline' ],
[ 'default', 'text' ]
])
state(name = 'comment_line',
external = 'comment',
transitions = [
[ 'linefeed', 'text' ],
[ 'default', 'comment_line' ]
])
state(name = 'comment_multiline',
external = 'comment',
transitions = [
[ '*', 'comment_multiline_close' ],
[ 'default', 'comment_multiline' ]
])
state(name = 'comment_multiline_close',
external = 'comment',
transitions = [
[ '/', 'text' ],
[ 'default', 'comment_multiline' ]
])
"""
self.sm['state'] = self.AddState
self.sm['condition'] = self.AddCondition
execfile(filename, self.sm)
self.name = self.sm['name']
if not self.name.isalnum():
raise Exception("State machine name must consist of only alphanumeric"
"characters.")
self.comment = self.sm['comment']
def __init__(self):
pass