| #!/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 |