Index: third_party/pexpect/pexpect/FSM.py |
diff --git a/third_party/pexpect/pexpect/FSM.py b/third_party/pexpect/pexpect/FSM.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..46b392ea08aaf53577b27c0552776ecc86d72510 |
--- /dev/null |
+++ b/third_party/pexpect/pexpect/FSM.py |
@@ -0,0 +1,334 @@ |
+#!/usr/bin/env python |
+ |
+'''This module implements a Finite State Machine (FSM). In addition to state |
+this FSM also maintains a user defined "memory". So this FSM can be used as a |
+Push-down Automata (PDA) since a PDA is a FSM + memory. |
+ |
+The following describes how the FSM works, but you will probably also need to |
+see the example function to understand how the FSM is used in practice. |
+ |
+You define an FSM by building tables of transitions. For a given input symbol |
+the process() method uses these tables to decide what action to call and what |
+the next state will be. The FSM has a table of transitions that associate: |
+ |
+ (input_symbol, current_state) --> (action, next_state) |
+ |
+Where "action" is a function you define. The symbols and states can be any |
+objects. You use the add_transition() and add_transition_list() methods to add |
+to the transition table. The FSM also has a table of transitions that |
+associate: |
+ |
+ (current_state) --> (action, next_state) |
+ |
+You use the add_transition_any() method to add to this transition table. The |
+FSM also has one default transition that is not associated with any specific |
+input_symbol or state. You use the set_default_transition() method to set the |
+default transition. |
+ |
+When an action function is called it is passed a reference to the FSM. The |
+action function may then access attributes of the FSM such as input_symbol, |
+current_state, or "memory". The "memory" attribute can be any object that you |
+want to pass along to the action functions. It is not used by the FSM itself. |
+For parsing you would typically pass a list to be used as a stack. |
+ |
+The processing sequence is as follows. The process() method is given an |
+input_symbol to process. The FSM will search the table of transitions that |
+associate: |
+ |
+ (input_symbol, current_state) --> (action, next_state) |
+ |
+If the pair (input_symbol, current_state) is found then process() will call the |
+associated action function and then set the current state to the next_state. |
+ |
+If the FSM cannot find a match for (input_symbol, current_state) it will then |
+search the table of transitions that associate: |
+ |
+ (current_state) --> (action, next_state) |
+ |
+If the current_state is found then the process() method will call the |
+associated action function and then set the current state to the next_state. |
+Notice that this table lacks an input_symbol. It lets you define transitions |
+for a current_state and ANY input_symbol. Hence, it is called the "any" table. |
+Remember, it is always checked after first searching the table for a specific |
+(input_symbol, current_state). |
+ |
+For the case where the FSM did not match either of the previous two cases the |
+FSM will try to use the default transition. If the default transition is |
+defined then the process() method will call the associated action function and |
+then set the current state to the next_state. This lets you define a default |
+transition as a catch-all case. You can think of it as an exception handler. |
+There can be only one default transition. |
+ |
+Finally, if none of the previous cases are defined for an input_symbol and |
+current_state then the FSM will raise an exception. This may be desirable, but |
+you can always prevent this just by defining a default transition. |
+ |
+Noah Spurrier 20020822 |
+ |
+PEXPECT LICENSE |
+ |
+ This license is approved by the OSI and FSF as GPL-compatible. |
+ http://opensource.org/licenses/isc-license.txt |
+ |
+ Copyright (c) 2012, Noah Spurrier <noah@noah.org> |
+ PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY |
+ PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE |
+ COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. |
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
+ |
+''' |
+ |
+class ExceptionFSM(Exception): |
+ |
+ '''This is the FSM Exception class.''' |
+ |
+ def __init__(self, value): |
+ self.value = value |
+ |
+ def __str__(self): |
+ return 'ExceptionFSM: ' + str(self.value) |
+ |
+class FSM: |
+ |
+ '''This is a Finite State Machine (FSM). |
+ ''' |
+ |
+ def __init__(self, initial_state, memory=None): |
+ |
+ '''This creates the FSM. You set the initial state here. The "memory" |
+ attribute is any object that you want to pass along to the action |
+ functions. It is not used by the FSM. For parsing you would typically |
+ pass a list to be used as a stack. ''' |
+ |
+ # Map (input_symbol, current_state) --> (action, next_state). |
+ self.state_transitions = {} |
+ # Map (current_state) --> (action, next_state). |
+ self.state_transitions_any = {} |
+ self.default_transition = None |
+ |
+ self.input_symbol = None |
+ self.initial_state = initial_state |
+ self.current_state = self.initial_state |
+ self.next_state = None |
+ self.action = None |
+ self.memory = memory |
+ |
+ def reset (self): |
+ |
+ '''This sets the current_state to the initial_state and sets |
+ input_symbol to None. The initial state was set by the constructor |
+ __init__(). ''' |
+ |
+ self.current_state = self.initial_state |
+ self.input_symbol = None |
+ |
+ def add_transition (self, input_symbol, state, action=None, next_state=None): |
+ |
+ '''This adds a transition that associates: |
+ |
+ (input_symbol, current_state) --> (action, next_state) |
+ |
+ The action may be set to None in which case the process() method will |
+ ignore the action and only set the next_state. The next_state may be |
+ set to None in which case the current state will be unchanged. |
+ |
+ You can also set transitions for a list of symbols by using |
+ add_transition_list(). ''' |
+ |
+ if next_state is None: |
+ next_state = state |
+ self.state_transitions[(input_symbol, state)] = (action, next_state) |
+ |
+ def add_transition_list (self, list_input_symbols, state, action=None, next_state=None): |
+ |
+ '''This adds the same transition for a list of input symbols. |
+ You can pass a list or a string. Note that it is handy to use |
+ string.digits, string.whitespace, string.letters, etc. to add |
+ transitions that match character classes. |
+ |
+ The action may be set to None in which case the process() method will |
+ ignore the action and only set the next_state. The next_state may be |
+ set to None in which case the current state will be unchanged. ''' |
+ |
+ if next_state is None: |
+ next_state = state |
+ for input_symbol in list_input_symbols: |
+ self.add_transition (input_symbol, state, action, next_state) |
+ |
+ def add_transition_any (self, state, action=None, next_state=None): |
+ |
+ '''This adds a transition that associates: |
+ |
+ (current_state) --> (action, next_state) |
+ |
+ That is, any input symbol will match the current state. |
+ The process() method checks the "any" state associations after it first |
+ checks for an exact match of (input_symbol, current_state). |
+ |
+ The action may be set to None in which case the process() method will |
+ ignore the action and only set the next_state. The next_state may be |
+ set to None in which case the current state will be unchanged. ''' |
+ |
+ if next_state is None: |
+ next_state = state |
+ self.state_transitions_any [state] = (action, next_state) |
+ |
+ def set_default_transition (self, action, next_state): |
+ |
+ '''This sets the default transition. This defines an action and |
+ next_state if the FSM cannot find the input symbol and the current |
+ state in the transition list and if the FSM cannot find the |
+ current_state in the transition_any list. This is useful as a final |
+ fall-through state for catching errors and undefined states. |
+ |
+ The default transition can be removed by setting the attribute |
+ default_transition to None. ''' |
+ |
+ self.default_transition = (action, next_state) |
+ |
+ def get_transition (self, input_symbol, state): |
+ |
+ '''This returns (action, next state) given an input_symbol and state. |
+ This does not modify the FSM state, so calling this method has no side |
+ effects. Normally you do not call this method directly. It is called by |
+ process(). |
+ |
+ The sequence of steps to check for a defined transition goes from the |
+ most specific to the least specific. |
+ |
+ 1. Check state_transitions[] that match exactly the tuple, |
+ (input_symbol, state) |
+ |
+ 2. Check state_transitions_any[] that match (state) |
+ In other words, match a specific state and ANY input_symbol. |
+ |
+ 3. Check if the default_transition is defined. |
+ This catches any input_symbol and any state. |
+ This is a handler for errors, undefined states, or defaults. |
+ |
+ 4. No transition was defined. If we get here then raise an exception. |
+ ''' |
+ |
+ if (input_symbol, state) in self.state_transitions: |
+ return self.state_transitions[(input_symbol, state)] |
+ elif state in self.state_transitions_any: |
+ return self.state_transitions_any[state] |
+ elif self.default_transition is not None: |
+ return self.default_transition |
+ else: |
+ raise ExceptionFSM ('Transition is undefined: (%s, %s).' % |
+ (str(input_symbol), str(state)) ) |
+ |
+ def process (self, input_symbol): |
+ |
+ '''This is the main method that you call to process input. This may |
+ cause the FSM to change state and call an action. This method calls |
+ get_transition() to find the action and next_state associated with the |
+ input_symbol and current_state. If the action is None then the action |
+ is not called and only the current state is changed. This method |
+ processes one complete input symbol. You can process a list of symbols |
+ (or a string) by calling process_list(). ''' |
+ |
+ self.input_symbol = input_symbol |
+ (self.action, self.next_state) = self.get_transition (self.input_symbol, self.current_state) |
+ if self.action is not None: |
+ self.action (self) |
+ self.current_state = self.next_state |
+ self.next_state = None |
+ |
+ def process_list (self, input_symbols): |
+ |
+ '''This takes a list and sends each element to process(). The list may |
+ be a string or any iterable object. ''' |
+ |
+ for s in input_symbols: |
+ self.process (s) |
+ |
+############################################################################## |
+# The following is an example that demonstrates the use of the FSM class to |
+# process an RPN expression. Run this module from the command line. You will |
+# get a prompt > for input. Enter an RPN Expression. Numbers may be integers. |
+# Operators are * / + - Use the = sign to evaluate and print the expression. |
+# For example: |
+# |
+# 167 3 2 2 * * * 1 - = |
+# |
+# will print: |
+# |
+# 2003 |
+############################################################################## |
+ |
+import sys |
+import string |
+ |
+PY3 = (sys.version_info[0] >= 3) |
+ |
+# |
+# These define the actions. |
+# Note that "memory" is a list being used as a stack. |
+# |
+ |
+def BeginBuildNumber (fsm): |
+ fsm.memory.append (fsm.input_symbol) |
+ |
+def BuildNumber (fsm): |
+ s = fsm.memory.pop () |
+ s = s + fsm.input_symbol |
+ fsm.memory.append (s) |
+ |
+def EndBuildNumber (fsm): |
+ s = fsm.memory.pop () |
+ fsm.memory.append (int(s)) |
+ |
+def DoOperator (fsm): |
+ ar = fsm.memory.pop() |
+ al = fsm.memory.pop() |
+ if fsm.input_symbol == '+': |
+ fsm.memory.append (al + ar) |
+ elif fsm.input_symbol == '-': |
+ fsm.memory.append (al - ar) |
+ elif fsm.input_symbol == '*': |
+ fsm.memory.append (al * ar) |
+ elif fsm.input_symbol == '/': |
+ fsm.memory.append (al / ar) |
+ |
+def DoEqual (fsm): |
+ print(str(fsm.memory.pop())) |
+ |
+def Error (fsm): |
+ print('That does not compute.') |
+ print(str(fsm.input_symbol)) |
+ |
+def main(): |
+ |
+ '''This is where the example starts and the FSM state transitions are |
+ defined. Note that states are strings (such as 'INIT'). This is not |
+ necessary, but it makes the example easier to read. ''' |
+ |
+ f = FSM ('INIT', []) |
+ f.set_default_transition (Error, 'INIT') |
+ f.add_transition_any ('INIT', None, 'INIT') |
+ f.add_transition ('=', 'INIT', DoEqual, 'INIT') |
+ f.add_transition_list (string.digits, 'INIT', BeginBuildNumber, 'BUILDING_NUMBER') |
+ f.add_transition_list (string.digits, 'BUILDING_NUMBER', BuildNumber, 'BUILDING_NUMBER') |
+ f.add_transition_list (string.whitespace, 'BUILDING_NUMBER', EndBuildNumber, 'INIT') |
+ f.add_transition_list ('+-*/', 'INIT', DoOperator, 'INIT') |
+ |
+ print() |
+ print('Enter an RPN Expression.') |
+ print('Numbers may be integers. Operators are * / + -') |
+ print('Use the = sign to evaluate and print the expression.') |
+ print('For example: ') |
+ print(' 167 3 2 2 * * * 1 - =') |
+ inputstr = (input if PY3 else raw_input)('> ') # analysis:ignore |
+ f.process_list(inputstr) |
+ |
+ |
+if __name__ == '__main__': |
+ main() |