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