xref: /openbsd-src/gnu/llvm/lldb/third_party/Python/module/pexpect-4.6/pexpect/FSM.py (revision 061da546b983eb767bad15e67af1174fb0bcf31c)
1*061da546Spatrick#!/usr/bin/env python
2*061da546Spatrick
3*061da546Spatrick'''This module implements a Finite State Machine (FSM). In addition to state
4*061da546Spatrickthis FSM also maintains a user defined "memory". So this FSM can be used as a
5*061da546SpatrickPush-down Automata (PDA) since a PDA is a FSM + memory.
6*061da546Spatrick
7*061da546SpatrickThe following describes how the FSM works, but you will probably also need to
8*061da546Spatricksee the example function to understand how the FSM is used in practice.
9*061da546Spatrick
10*061da546SpatrickYou define an FSM by building tables of transitions. For a given input symbol
11*061da546Spatrickthe process() method uses these tables to decide what action to call and what
12*061da546Spatrickthe next state will be. The FSM has a table of transitions that associate:
13*061da546Spatrick
14*061da546Spatrick        (input_symbol, current_state) --> (action, next_state)
15*061da546Spatrick
16*061da546SpatrickWhere "action" is a function you define. The symbols and states can be any
17*061da546Spatrickobjects. You use the add_transition() and add_transition_list() methods to add
18*061da546Spatrickto the transition table. The FSM also has a table of transitions that
19*061da546Spatrickassociate:
20*061da546Spatrick
21*061da546Spatrick        (current_state) --> (action, next_state)
22*061da546Spatrick
23*061da546SpatrickYou use the add_transition_any() method to add to this transition table. The
24*061da546SpatrickFSM also has one default transition that is not associated with any specific
25*061da546Spatrickinput_symbol or state. You use the set_default_transition() method to set the
26*061da546Spatrickdefault transition.
27*061da546Spatrick
28*061da546SpatrickWhen an action function is called it is passed a reference to the FSM. The
29*061da546Spatrickaction function may then access attributes of the FSM such as input_symbol,
30*061da546Spatrickcurrent_state, or "memory". The "memory" attribute can be any object that you
31*061da546Spatrickwant to pass along to the action functions. It is not used by the FSM itself.
32*061da546SpatrickFor parsing you would typically pass a list to be used as a stack.
33*061da546Spatrick
34*061da546SpatrickThe processing sequence is as follows. The process() method is given an
35*061da546Spatrickinput_symbol to process. The FSM will search the table of transitions that
36*061da546Spatrickassociate:
37*061da546Spatrick
38*061da546Spatrick        (input_symbol, current_state) --> (action, next_state)
39*061da546Spatrick
40*061da546SpatrickIf the pair (input_symbol, current_state) is found then process() will call the
41*061da546Spatrickassociated action function and then set the current state to the next_state.
42*061da546Spatrick
43*061da546SpatrickIf the FSM cannot find a match for (input_symbol, current_state) it will then
44*061da546Spatricksearch the table of transitions that associate:
45*061da546Spatrick
46*061da546Spatrick        (current_state) --> (action, next_state)
47*061da546Spatrick
48*061da546SpatrickIf the current_state is found then the process() method will call the
49*061da546Spatrickassociated action function and then set the current state to the next_state.
50*061da546SpatrickNotice that this table lacks an input_symbol. It lets you define transitions
51*061da546Spatrickfor a current_state and ANY input_symbol. Hence, it is called the "any" table.
52*061da546SpatrickRemember, it is always checked after first searching the table for a specific
53*061da546Spatrick(input_symbol, current_state).
54*061da546Spatrick
55*061da546SpatrickFor the case where the FSM did not match either of the previous two cases the
56*061da546SpatrickFSM will try to use the default transition. If the default transition is
57*061da546Spatrickdefined then the process() method will call the associated action function and
58*061da546Spatrickthen set the current state to the next_state. This lets you define a default
59*061da546Spatricktransition as a catch-all case. You can think of it as an exception handler.
60*061da546SpatrickThere can be only one default transition.
61*061da546Spatrick
62*061da546SpatrickFinally, if none of the previous cases are defined for an input_symbol and
63*061da546Spatrickcurrent_state then the FSM will raise an exception. This may be desirable, but
64*061da546Spatrickyou can always prevent this just by defining a default transition.
65*061da546Spatrick
66*061da546SpatrickNoah Spurrier 20020822
67*061da546Spatrick
68*061da546SpatrickPEXPECT LICENSE
69*061da546Spatrick
70*061da546Spatrick    This license is approved by the OSI and FSF as GPL-compatible.
71*061da546Spatrick        http://opensource.org/licenses/isc-license.txt
72*061da546Spatrick
73*061da546Spatrick    Copyright (c) 2012, Noah Spurrier <noah@noah.org>
74*061da546Spatrick    PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
75*061da546Spatrick    PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
76*061da546Spatrick    COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
77*061da546Spatrick    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
78*061da546Spatrick    WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
79*061da546Spatrick    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
80*061da546Spatrick    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
81*061da546Spatrick    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
82*061da546Spatrick    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
83*061da546Spatrick    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
84*061da546Spatrick
85*061da546Spatrick'''
86*061da546Spatrick
87*061da546Spatrickclass ExceptionFSM(Exception):
88*061da546Spatrick
89*061da546Spatrick    '''This is the FSM Exception class.'''
90*061da546Spatrick
91*061da546Spatrick    def __init__(self, value):
92*061da546Spatrick        self.value = value
93*061da546Spatrick
94*061da546Spatrick    def __str__(self):
95*061da546Spatrick        return 'ExceptionFSM: ' + str(self.value)
96*061da546Spatrick
97*061da546Spatrickclass FSM:
98*061da546Spatrick
99*061da546Spatrick    '''This is a Finite State Machine (FSM).
100*061da546Spatrick    '''
101*061da546Spatrick
102*061da546Spatrick    def __init__(self, initial_state, memory=None):
103*061da546Spatrick
104*061da546Spatrick        '''This creates the FSM. You set the initial state here. The "memory"
105*061da546Spatrick        attribute is any object that you want to pass along to the action
106*061da546Spatrick        functions. It is not used by the FSM. For parsing you would typically
107*061da546Spatrick        pass a list to be used as a stack. '''
108*061da546Spatrick
109*061da546Spatrick        # Map (input_symbol, current_state) --> (action, next_state).
110*061da546Spatrick        self.state_transitions = {}
111*061da546Spatrick        # Map (current_state) --> (action, next_state).
112*061da546Spatrick        self.state_transitions_any = {}
113*061da546Spatrick        self.default_transition = None
114*061da546Spatrick
115*061da546Spatrick        self.input_symbol = None
116*061da546Spatrick        self.initial_state = initial_state
117*061da546Spatrick        self.current_state = self.initial_state
118*061da546Spatrick        self.next_state = None
119*061da546Spatrick        self.action = None
120*061da546Spatrick        self.memory = memory
121*061da546Spatrick
122*061da546Spatrick    def reset (self):
123*061da546Spatrick
124*061da546Spatrick        '''This sets the current_state to the initial_state and sets
125*061da546Spatrick        input_symbol to None. The initial state was set by the constructor
126*061da546Spatrick        __init__(). '''
127*061da546Spatrick
128*061da546Spatrick        self.current_state = self.initial_state
129*061da546Spatrick        self.input_symbol = None
130*061da546Spatrick
131*061da546Spatrick    def add_transition (self, input_symbol, state, action=None, next_state=None):
132*061da546Spatrick
133*061da546Spatrick        '''This adds a transition that associates:
134*061da546Spatrick
135*061da546Spatrick                (input_symbol, current_state) --> (action, next_state)
136*061da546Spatrick
137*061da546Spatrick        The action may be set to None in which case the process() method will
138*061da546Spatrick        ignore the action and only set the next_state. The next_state may be
139*061da546Spatrick        set to None in which case the current state will be unchanged.
140*061da546Spatrick
141*061da546Spatrick        You can also set transitions for a list of symbols by using
142*061da546Spatrick        add_transition_list(). '''
143*061da546Spatrick
144*061da546Spatrick        if next_state is None:
145*061da546Spatrick            next_state = state
146*061da546Spatrick        self.state_transitions[(input_symbol, state)] = (action, next_state)
147*061da546Spatrick
148*061da546Spatrick    def add_transition_list (self, list_input_symbols, state, action=None, next_state=None):
149*061da546Spatrick
150*061da546Spatrick        '''This adds the same transition for a list of input symbols.
151*061da546Spatrick        You can pass a list or a string. Note that it is handy to use
152*061da546Spatrick        string.digits, string.whitespace, string.letters, etc. to add
153*061da546Spatrick        transitions that match character classes.
154*061da546Spatrick
155*061da546Spatrick        The action may be set to None in which case the process() method will
156*061da546Spatrick        ignore the action and only set the next_state. The next_state may be
157*061da546Spatrick        set to None in which case the current state will be unchanged. '''
158*061da546Spatrick
159*061da546Spatrick        if next_state is None:
160*061da546Spatrick            next_state = state
161*061da546Spatrick        for input_symbol in list_input_symbols:
162*061da546Spatrick            self.add_transition (input_symbol, state, action, next_state)
163*061da546Spatrick
164*061da546Spatrick    def add_transition_any (self, state, action=None, next_state=None):
165*061da546Spatrick
166*061da546Spatrick        '''This adds a transition that associates:
167*061da546Spatrick
168*061da546Spatrick                (current_state) --> (action, next_state)
169*061da546Spatrick
170*061da546Spatrick        That is, any input symbol will match the current state.
171*061da546Spatrick        The process() method checks the "any" state associations after it first
172*061da546Spatrick        checks for an exact match of (input_symbol, current_state).
173*061da546Spatrick
174*061da546Spatrick        The action may be set to None in which case the process() method will
175*061da546Spatrick        ignore the action and only set the next_state. The next_state may be
176*061da546Spatrick        set to None in which case the current state will be unchanged. '''
177*061da546Spatrick
178*061da546Spatrick        if next_state is None:
179*061da546Spatrick            next_state = state
180*061da546Spatrick        self.state_transitions_any [state] = (action, next_state)
181*061da546Spatrick
182*061da546Spatrick    def set_default_transition (self, action, next_state):
183*061da546Spatrick
184*061da546Spatrick        '''This sets the default transition. This defines an action and
185*061da546Spatrick        next_state if the FSM cannot find the input symbol and the current
186*061da546Spatrick        state in the transition list and if the FSM cannot find the
187*061da546Spatrick        current_state in the transition_any list. This is useful as a final
188*061da546Spatrick        fall-through state for catching errors and undefined states.
189*061da546Spatrick
190*061da546Spatrick        The default transition can be removed by setting the attribute
191*061da546Spatrick        default_transition to None. '''
192*061da546Spatrick
193*061da546Spatrick        self.default_transition = (action, next_state)
194*061da546Spatrick
195*061da546Spatrick    def get_transition (self, input_symbol, state):
196*061da546Spatrick
197*061da546Spatrick        '''This returns (action, next state) given an input_symbol and state.
198*061da546Spatrick        This does not modify the FSM state, so calling this method has no side
199*061da546Spatrick        effects. Normally you do not call this method directly. It is called by
200*061da546Spatrick        process().
201*061da546Spatrick
202*061da546Spatrick        The sequence of steps to check for a defined transition goes from the
203*061da546Spatrick        most specific to the least specific.
204*061da546Spatrick
205*061da546Spatrick        1. Check state_transitions[] that match exactly the tuple,
206*061da546Spatrick            (input_symbol, state)
207*061da546Spatrick
208*061da546Spatrick        2. Check state_transitions_any[] that match (state)
209*061da546Spatrick            In other words, match a specific state and ANY input_symbol.
210*061da546Spatrick
211*061da546Spatrick        3. Check if the default_transition is defined.
212*061da546Spatrick            This catches any input_symbol and any state.
213*061da546Spatrick            This is a handler for errors, undefined states, or defaults.
214*061da546Spatrick
215*061da546Spatrick        4. No transition was defined. If we get here then raise an exception.
216*061da546Spatrick        '''
217*061da546Spatrick
218*061da546Spatrick        if (input_symbol, state) in self.state_transitions:
219*061da546Spatrick            return self.state_transitions[(input_symbol, state)]
220*061da546Spatrick        elif state in self.state_transitions_any:
221*061da546Spatrick            return self.state_transitions_any[state]
222*061da546Spatrick        elif self.default_transition is not None:
223*061da546Spatrick            return self.default_transition
224*061da546Spatrick        else:
225*061da546Spatrick            raise ExceptionFSM ('Transition is undefined: (%s, %s).' %
226*061da546Spatrick                (str(input_symbol), str(state)) )
227*061da546Spatrick
228*061da546Spatrick    def process (self, input_symbol):
229*061da546Spatrick
230*061da546Spatrick        '''This is the main method that you call to process input. This may
231*061da546Spatrick        cause the FSM to change state and call an action. This method calls
232*061da546Spatrick        get_transition() to find the action and next_state associated with the
233*061da546Spatrick        input_symbol and current_state. If the action is None then the action
234*061da546Spatrick        is not called and only the current state is changed. This method
235*061da546Spatrick        processes one complete input symbol. You can process a list of symbols
236*061da546Spatrick        (or a string) by calling process_list(). '''
237*061da546Spatrick
238*061da546Spatrick        self.input_symbol = input_symbol
239*061da546Spatrick        (self.action, self.next_state) = self.get_transition (self.input_symbol, self.current_state)
240*061da546Spatrick        if self.action is not None:
241*061da546Spatrick            self.action (self)
242*061da546Spatrick        self.current_state = self.next_state
243*061da546Spatrick        self.next_state = None
244*061da546Spatrick
245*061da546Spatrick    def process_list (self, input_symbols):
246*061da546Spatrick
247*061da546Spatrick        '''This takes a list and sends each element to process(). The list may
248*061da546Spatrick        be a string or any iterable object. '''
249*061da546Spatrick
250*061da546Spatrick        for s in input_symbols:
251*061da546Spatrick            self.process (s)
252*061da546Spatrick
253*061da546Spatrick##############################################################################
254*061da546Spatrick# The following is an example that demonstrates the use of the FSM class to
255*061da546Spatrick# process an RPN expression. Run this module from the command line. You will
256*061da546Spatrick# get a prompt > for input. Enter an RPN Expression. Numbers may be integers.
257*061da546Spatrick# Operators are * / + - Use the = sign to evaluate and print the expression.
258*061da546Spatrick# For example:
259*061da546Spatrick#
260*061da546Spatrick#    167 3 2 2 * * * 1 - =
261*061da546Spatrick#
262*061da546Spatrick# will print:
263*061da546Spatrick#
264*061da546Spatrick#    2003
265*061da546Spatrick##############################################################################
266*061da546Spatrick
267*061da546Spatrickimport sys
268*061da546Spatrickimport string
269*061da546Spatrick
270*061da546SpatrickPY3 = (sys.version_info[0] >= 3)
271*061da546Spatrick
272*061da546Spatrick#
273*061da546Spatrick# These define the actions.
274*061da546Spatrick# Note that "memory" is a list being used as a stack.
275*061da546Spatrick#
276*061da546Spatrick
277*061da546Spatrickdef BeginBuildNumber (fsm):
278*061da546Spatrick    fsm.memory.append (fsm.input_symbol)
279*061da546Spatrick
280*061da546Spatrickdef BuildNumber (fsm):
281*061da546Spatrick    s = fsm.memory.pop ()
282*061da546Spatrick    s = s + fsm.input_symbol
283*061da546Spatrick    fsm.memory.append (s)
284*061da546Spatrick
285*061da546Spatrickdef EndBuildNumber (fsm):
286*061da546Spatrick    s = fsm.memory.pop ()
287*061da546Spatrick    fsm.memory.append (int(s))
288*061da546Spatrick
289*061da546Spatrickdef DoOperator (fsm):
290*061da546Spatrick    ar = fsm.memory.pop()
291*061da546Spatrick    al = fsm.memory.pop()
292*061da546Spatrick    if fsm.input_symbol == '+':
293*061da546Spatrick        fsm.memory.append (al + ar)
294*061da546Spatrick    elif fsm.input_symbol == '-':
295*061da546Spatrick        fsm.memory.append (al - ar)
296*061da546Spatrick    elif fsm.input_symbol == '*':
297*061da546Spatrick        fsm.memory.append (al * ar)
298*061da546Spatrick    elif fsm.input_symbol == '/':
299*061da546Spatrick        fsm.memory.append (al / ar)
300*061da546Spatrick
301*061da546Spatrickdef DoEqual (fsm):
302*061da546Spatrick    print(str(fsm.memory.pop()))
303*061da546Spatrick
304*061da546Spatrickdef Error (fsm):
305*061da546Spatrick    print('That does not compute.')
306*061da546Spatrick    print(str(fsm.input_symbol))
307*061da546Spatrick
308*061da546Spatrickdef main():
309*061da546Spatrick
310*061da546Spatrick    '''This is where the example starts and the FSM state transitions are
311*061da546Spatrick    defined. Note that states are strings (such as 'INIT'). This is not
312*061da546Spatrick    necessary, but it makes the example easier to read. '''
313*061da546Spatrick
314*061da546Spatrick    f = FSM ('INIT', [])
315*061da546Spatrick    f.set_default_transition (Error, 'INIT')
316*061da546Spatrick    f.add_transition_any  ('INIT', None, 'INIT')
317*061da546Spatrick    f.add_transition      ('=',               'INIT',            DoEqual,          'INIT')
318*061da546Spatrick    f.add_transition_list (string.digits,     'INIT',            BeginBuildNumber, 'BUILDING_NUMBER')
319*061da546Spatrick    f.add_transition_list (string.digits,     'BUILDING_NUMBER', BuildNumber,      'BUILDING_NUMBER')
320*061da546Spatrick    f.add_transition_list (string.whitespace, 'BUILDING_NUMBER', EndBuildNumber,   'INIT')
321*061da546Spatrick    f.add_transition_list ('+-*/',            'INIT',            DoOperator,       'INIT')
322*061da546Spatrick
323*061da546Spatrick    print()
324*061da546Spatrick    print('Enter an RPN Expression.')
325*061da546Spatrick    print('Numbers may be integers. Operators are * / + -')
326*061da546Spatrick    print('Use the = sign to evaluate and print the expression.')
327*061da546Spatrick    print('For example: ')
328*061da546Spatrick    print('    167 3 2 2 * * * 1 - =')
329*061da546Spatrick    inputstr = (input if PY3 else raw_input)('> ')  # analysis:ignore
330*061da546Spatrick    f.process_list(inputstr)
331*061da546Spatrick
332*061da546Spatrick
333*061da546Spatrickif __name__ == '__main__':
334*061da546Spatrick    main()
335