blob: aa9d2c00b38e7f7a7e590634c222794ecb99adb3 [file] [log] [blame]
Tor Norbye3a2425a2013-11-04 10:16:08 -08001 # $Id: statemachine.py 6388 2010-08-13 12:24:34Z milde $
2# Author: David Goodger <goodger@python.org>
3# Copyright: This module has been placed in the public domain.
4
5"""
6A finite state machine specialized for regular-expression-based text filters,
7this module defines the following classes:
8
9- `StateMachine`, a state machine
10- `State`, a state superclass
11- `StateMachineWS`, a whitespace-sensitive version of `StateMachine`
12- `StateWS`, a state superclass for use with `StateMachineWS`
13- `SearchStateMachine`, uses `re.search()` instead of `re.match()`
14- `SearchStateMachineWS`, uses `re.search()` instead of `re.match()`
15- `ViewList`, extends standard Python lists.
16- `StringList`, string-specific ViewList.
17
18Exception classes:
19
20- `StateMachineError`
21- `UnknownStateError`
22- `DuplicateStateError`
23- `UnknownTransitionError`
24- `DuplicateTransitionError`
25- `TransitionPatternNotFound`
26- `TransitionMethodNotFound`
27- `UnexpectedIndentationError`
28- `TransitionCorrection`: Raised to switch to another transition.
29- `StateCorrection`: Raised to switch to another state & transition.
30
31Functions:
32
33- `string2lines()`: split a multi-line string into a list of one-line strings
34
35
36How To Use This Module
37======================
38(See the individual classes, methods, and attributes for details.)
39
401. Import it: ``import statemachine`` or ``from statemachine import ...``.
41 You will also need to ``import re``.
42
432. Derive a subclass of `State` (or `StateWS`) for each state in your state
44 machine::
45
46 class MyState(statemachine.State):
47
48 Within the state's class definition:
49
50 a) Include a pattern for each transition, in `State.patterns`::
51
52 patterns = {'atransition': r'pattern', ...}
53
54 b) Include a list of initial transitions to be set up automatically, in
55 `State.initial_transitions`::
56
57 initial_transitions = ['atransition', ...]
58
59 c) Define a method for each transition, with the same name as the
60 transition pattern::
61
62 def atransition(self, match, context, next_state):
63 # do something
64 result = [...] # a list
65 return context, next_state, result
66 # context, next_state may be altered
67
68 Transition methods may raise an `EOFError` to cut processing short.
69
70 d) You may wish to override the `State.bof()` and/or `State.eof()` implicit
71 transition methods, which handle the beginning- and end-of-file.
72
73 e) In order to handle nested processing, you may wish to override the
74 attributes `State.nested_sm` and/or `State.nested_sm_kwargs`.
75
76 If you are using `StateWS` as a base class, in order to handle nested
77 indented blocks, you may wish to:
78
79 - override the attributes `StateWS.indent_sm`,
80 `StateWS.indent_sm_kwargs`, `StateWS.known_indent_sm`, and/or
81 `StateWS.known_indent_sm_kwargs`;
82 - override the `StateWS.blank()` method; and/or
83 - override or extend the `StateWS.indent()`, `StateWS.known_indent()`,
84 and/or `StateWS.firstknown_indent()` methods.
85
863. Create a state machine object::
87
88 sm = StateMachine(state_classes=[MyState, ...],
89 initial_state='MyState')
90
914. Obtain the input text, which needs to be converted into a tab-free list of
92 one-line strings. For example, to read text from a file called
93 'inputfile'::
94
95 input_string = open('inputfile').read()
96 input_lines = statemachine.string2lines(input_string)
97
985. Run the state machine on the input text and collect the results, a list::
99
100 results = sm.run(input_lines)
101
1026. Remove any lingering circular references::
103
104 sm.unlink()
105"""
106
107__docformat__ = 'restructuredtext'
108
109import sys
110import re
111import types
112import unicodedata
113
114
115class StateMachine:
116
117 """
118 A finite state machine for text filters using regular expressions.
119
120 The input is provided in the form of a list of one-line strings (no
121 newlines). States are subclasses of the `State` class. Transitions consist
122 of regular expression patterns and transition methods, and are defined in
123 each state.
124
125 The state machine is started with the `run()` method, which returns the
126 results of processing in a list.
127 """
128
129 def __init__(self, state_classes, initial_state, debug=0):
130 """
131 Initialize a `StateMachine` object; add state objects.
132
133 Parameters:
134
135 - `state_classes`: a list of `State` (sub)classes.
136 - `initial_state`: a string, the class name of the initial state.
137 - `debug`: a boolean; produce verbose output if true (nonzero).
138 """
139
140 self.input_lines = None
141 """`StringList` of input lines (without newlines).
142 Filled by `self.run()`."""
143
144 self.input_offset = 0
145 """Offset of `self.input_lines` from the beginning of the file."""
146
147 self.line = None
148 """Current input line."""
149
150 self.line_offset = -1
151 """Current input line offset from beginning of `self.input_lines`."""
152
153 self.debug = debug
154 """Debugging mode on/off."""
155
156 self.initial_state = initial_state
157 """The name of the initial state (key to `self.states`)."""
158
159 self.current_state = initial_state
160 """The name of the current state (key to `self.states`)."""
161
162 self.states = {}
163 """Mapping of {state_name: State_object}."""
164
165 self.add_states(state_classes)
166
167 self.observers = []
168 """List of bound methods or functions to call whenever the current
169 line changes. Observers are called with one argument, ``self``.
170 Cleared at the end of `run()`."""
171
172 def unlink(self):
173 """Remove circular references to objects no longer required."""
174 for state in self.states.values():
175 state.unlink()
176 self.states = None
177
178 def run(self, input_lines, input_offset=0, context=None,
179 input_source=None, initial_state=None):
180 """
181 Run the state machine on `input_lines`. Return results (a list).
182
183 Reset `self.line_offset` and `self.current_state`. Run the
184 beginning-of-file transition. Input one line at a time and check for a
185 matching transition. If a match is found, call the transition method
186 and possibly change the state. Store the context returned by the
187 transition method to be passed on to the next transition matched.
188 Accumulate the results returned by the transition methods in a list.
189 Run the end-of-file transition. Finally, return the accumulated
190 results.
191
192 Parameters:
193
194 - `input_lines`: a list of strings without newlines, or `StringList`.
195 - `input_offset`: the line offset of `input_lines` from the beginning
196 of the file.
197 - `context`: application-specific storage.
198 - `input_source`: name or path of source of `input_lines`.
199 - `initial_state`: name of initial state.
200 """
201 self.runtime_init()
202 if isinstance(input_lines, StringList):
203 self.input_lines = input_lines
204 else:
205 self.input_lines = StringList(input_lines, source=input_source)
206 self.input_offset = input_offset
207 self.line_offset = -1
208 self.current_state = initial_state or self.initial_state
209 if self.debug:
210 print >>sys.stderr, (
211 '\nStateMachine.run: input_lines (line_offset=%s):\n| %s'
212 % (self.line_offset, '\n| '.join(self.input_lines)))
213 transitions = None
214 results = []
215 state = self.get_state()
216 try:
217 if self.debug:
218 print >>sys.stderr, ('\nStateMachine.run: bof transition')
219 context, result = state.bof(context)
220 results.extend(result)
221 while 1:
222 try:
223 try:
224 self.next_line()
225 if self.debug:
226 source, offset = self.input_lines.info(
227 self.line_offset)
228 print >>sys.stderr, (
229 '\nStateMachine.run: line (source=%r, '
230 'offset=%r):\n| %s'
231 % (source, offset, self.line))
232 context, next_state, result = self.check_line(
233 context, state, transitions)
234 except EOFError:
235 if self.debug:
236 print >>sys.stderr, (
237 '\nStateMachine.run: %s.eof transition'
238 % state.__class__.__name__)
239 result = state.eof(context)
240 results.extend(result)
241 break
242 else:
243 results.extend(result)
244 except TransitionCorrection, exception:
245 self.previous_line() # back up for another try
246 transitions = (exception.args[0],)
247 if self.debug:
248 print >>sys.stderr, (
249 '\nStateMachine.run: TransitionCorrection to '
250 'state "%s", transition %s.'
251 % (state.__class__.__name__, transitions[0]))
252 continue
253 except StateCorrection, exception:
254 self.previous_line() # back up for another try
255 next_state = exception.args[0]
256 if len(exception.args) == 1:
257 transitions = None
258 else:
259 transitions = (exception.args[1],)
260 if self.debug:
261 print >>sys.stderr, (
262 '\nStateMachine.run: StateCorrection to state '
263 '"%s", transition %s.'
264 % (next_state, transitions[0]))
265 else:
266 transitions = None
267 state = self.get_state(next_state)
268 except:
269 if self.debug:
270 self.error()
271 raise
272 self.observers = []
273 return results
274
275 def get_state(self, next_state=None):
276 """
277 Return current state object; set it first if `next_state` given.
278
279 Parameter `next_state`: a string, the name of the next state.
280
281 Exception: `UnknownStateError` raised if `next_state` unknown.
282 """
283 if next_state:
284 if self.debug and next_state != self.current_state:
285 print >>sys.stderr, \
286 ('\nStateMachine.get_state: Changing state from '
287 '"%s" to "%s" (input line %s).'
288 % (self.current_state, next_state,
289 self.abs_line_number()))
290 self.current_state = next_state
291 try:
292 return self.states[self.current_state]
293 except KeyError:
294 raise UnknownStateError(self.current_state)
295
296 def next_line(self, n=1):
297 """Load `self.line` with the `n`'th next line and return it."""
298 try:
299 try:
300 self.line_offset += n
301 self.line = self.input_lines[self.line_offset]
302 except IndexError:
303 self.line = None
304 raise EOFError
305 return self.line
306 finally:
307 self.notify_observers()
308
309 def is_next_line_blank(self):
310 """Return 1 if the next line is blank or non-existant."""
311 try:
312 return not self.input_lines[self.line_offset + 1].strip()
313 except IndexError:
314 return 1
315
316 def at_eof(self):
317 """Return 1 if the input is at or past end-of-file."""
318 return self.line_offset >= len(self.input_lines) - 1
319
320 def at_bof(self):
321 """Return 1 if the input is at or before beginning-of-file."""
322 return self.line_offset <= 0
323
324 def previous_line(self, n=1):
325 """Load `self.line` with the `n`'th previous line and return it."""
326 self.line_offset -= n
327 if self.line_offset < 0:
328 self.line = None
329 else:
330 self.line = self.input_lines[self.line_offset]
331 self.notify_observers()
332 return self.line
333
334 def goto_line(self, line_offset):
335 """Jump to absolute line offset `line_offset`, load and return it."""
336 try:
337 try:
338 self.line_offset = line_offset - self.input_offset
339 self.line = self.input_lines[self.line_offset]
340 except IndexError:
341 self.line = None
342 raise EOFError
343 return self.line
344 finally:
345 self.notify_observers()
346
347 def get_source(self, line_offset):
348 """Return source of line at absolute line offset `line_offset`."""
349 return self.input_lines.source(line_offset - self.input_offset)
350
351 def abs_line_offset(self):
352 """Return line offset of current line, from beginning of file."""
353 return self.line_offset + self.input_offset
354
355 def abs_line_number(self):
356 """Return line number of current line (counting from 1)."""
357 return self.line_offset + self.input_offset + 1
358
359 def get_source_and_line(self, lineno=None):
360 """Return (source, line) tuple for current or given line number.
361
362 Looks up the source and line number in the `self.input_lines`
363 StringList instance to count for included source files.
364
365 If the optional argument `lineno` is given, convert it from an
366 absolute line number to the corresponding (source, line) pair.
367 """
368 if lineno is None:
369 offset = self.line_offset
370 else:
371 offset = lineno - self.input_offset - 1
372 try:
373 src, srcoffset = self.input_lines.info(offset)
374 srcline = srcoffset + 1
375 except (TypeError):
376 # line is None if index is "Just past the end"
377 src, srcline = self.get_source_and_line(offset + self.input_offset)
378 return src, srcline + 1
379 except (IndexError): # `offset` is off the list
380 src, srcline = None, None
381 # raise AssertionError('cannot find line %d in %s lines' %
382 # (offset, len(self.input_lines)))
383 # # list(self.input_lines.lines())))
384 # assert offset == srcoffset, str(self.input_lines)
385 # print "get_source_and_line(%s):" % lineno,
386 # print offset + 1, '->', src, srcline
387 # print self.input_lines
388 return (src, srcline)
389
390 def insert_input(self, input_lines, source):
391 self.input_lines.insert(self.line_offset + 1, '',
392 source='internal padding after '+source,
393 offset=len(input_lines))
394 self.input_lines.insert(self.line_offset + 1, '',
395 source='internal padding before '+source,
396 offset=-1)
397 self.input_lines.insert(self.line_offset + 2,
398 StringList(input_lines, source))
399
400 def get_text_block(self, flush_left=0):
401 """
402 Return a contiguous block of text.
403
404 If `flush_left` is true, raise `UnexpectedIndentationError` if an
405 indented line is encountered before the text block ends (with a blank
406 line).
407 """
408 try:
409 block = self.input_lines.get_text_block(self.line_offset,
410 flush_left)
411 self.next_line(len(block) - 1)
412 return block
413 except UnexpectedIndentationError, error:
414 block, source, lineno = error.args
415 self.next_line(len(block) - 1) # advance to last line of block
416 raise
417
418 def check_line(self, context, state, transitions=None):
419 """
420 Examine one line of input for a transition match & execute its method.
421
422 Parameters:
423
424 - `context`: application-dependent storage.
425 - `state`: a `State` object, the current state.
426 - `transitions`: an optional ordered list of transition names to try,
427 instead of ``state.transition_order``.
428
429 Return the values returned by the transition method:
430
431 - context: possibly modified from the parameter `context`;
432 - next state name (`State` subclass name);
433 - the result output of the transition, a list.
434
435 When there is no match, ``state.no_match()`` is called and its return
436 value is returned.
437 """
438 if transitions is None:
439 transitions = state.transition_order
440 state_correction = None
441 if self.debug:
442 print >>sys.stderr, (
443 '\nStateMachine.check_line: state="%s", transitions=%r.'
444 % (state.__class__.__name__, transitions))
445 for name in transitions:
446 pattern, method, next_state = state.transitions[name]
447 match = pattern.match(self.line)
448 if match:
449 if self.debug:
450 print >>sys.stderr, (
451 '\nStateMachine.check_line: Matched transition '
452 '"%s" in state "%s".'
453 % (name, state.__class__.__name__))
454 return method(match, context, next_state)
455 else:
456 if self.debug:
457 print >>sys.stderr, (
458 '\nStateMachine.check_line: No match in state "%s".'
459 % state.__class__.__name__)
460 return state.no_match(context, transitions)
461
462 def add_state(self, state_class):
463 """
464 Initialize & add a `state_class` (`State` subclass) object.
465
466 Exception: `DuplicateStateError` raised if `state_class` was already
467 added.
468 """
469 statename = state_class.__name__
470 if statename in self.states:
471 raise DuplicateStateError(statename)
472 self.states[statename] = state_class(self, self.debug)
473
474 def add_states(self, state_classes):
475 """
476 Add `state_classes` (a list of `State` subclasses).
477 """
478 for state_class in state_classes:
479 self.add_state(state_class)
480
481 def runtime_init(self):
482 """
483 Initialize `self.states`.
484 """
485 for state in self.states.values():
486 state.runtime_init()
487
488 def error(self):
489 """Report error details."""
490 type, value, module, line, function = _exception_data()
491 print >>sys.stderr, '%s: %s' % (type, value)
492 print >>sys.stderr, 'input line %s' % (self.abs_line_number())
493 print >>sys.stderr, ('module %s, line %s, function %s'
494 % (module, line, function))
495
496 def attach_observer(self, observer):
497 """
498 The `observer` parameter is a function or bound method which takes two
499 arguments, the source and offset of the current line.
500 """
501 self.observers.append(observer)
502
503 def detach_observer(self, observer):
504 self.observers.remove(observer)
505
506 def notify_observers(self):
507 for observer in self.observers:
508 try:
509 info = self.input_lines.info(self.line_offset)
510 except IndexError:
511 info = (None, None)
512 observer(*info)
513
514
515class State:
516
517 """
518 State superclass. Contains a list of transitions, and transition methods.
519
520 Transition methods all have the same signature. They take 3 parameters:
521
522 - An `re` match object. ``match.string`` contains the matched input line,
523 ``match.start()`` gives the start index of the match, and
524 ``match.end()`` gives the end index.
525 - A context object, whose meaning is application-defined (initial value
526 ``None``). It can be used to store any information required by the state
527 machine, and the retured context is passed on to the next transition
528 method unchanged.
529 - The name of the next state, a string, taken from the transitions list;
530 normally it is returned unchanged, but it may be altered by the
531 transition method if necessary.
532
533 Transition methods all return a 3-tuple:
534
535 - A context object, as (potentially) modified by the transition method.
536 - The next state name (a return value of ``None`` means no state change).
537 - The processing result, a list, which is accumulated by the state
538 machine.
539
540 Transition methods may raise an `EOFError` to cut processing short.
541
542 There are two implicit transitions, and corresponding transition methods
543 are defined: `bof()` handles the beginning-of-file, and `eof()` handles
544 the end-of-file. These methods have non-standard signatures and return
545 values. `bof()` returns the initial context and results, and may be used
546 to return a header string, or do any other processing needed. `eof()`
547 should handle any remaining context and wrap things up; it returns the
548 final processing result.
549
550 Typical applications need only subclass `State` (or a subclass), set the
551 `patterns` and `initial_transitions` class attributes, and provide
552 corresponding transition methods. The default object initialization will
553 take care of constructing the list of transitions.
554 """
555
556 patterns = None
557 """
558 {Name: pattern} mapping, used by `make_transition()`. Each pattern may
559 be a string or a compiled `re` pattern. Override in subclasses.
560 """
561
562 initial_transitions = None
563 """
564 A list of transitions to initialize when a `State` is instantiated.
565 Each entry is either a transition name string, or a (transition name, next
566 state name) pair. See `make_transitions()`. Override in subclasses.
567 """
568
569 nested_sm = None
570 """
571 The `StateMachine` class for handling nested processing.
572
573 If left as ``None``, `nested_sm` defaults to the class of the state's
574 controlling state machine. Override it in subclasses to avoid the default.
575 """
576
577 nested_sm_kwargs = None
578 """
579 Keyword arguments dictionary, passed to the `nested_sm` constructor.
580
581 Two keys must have entries in the dictionary:
582
583 - Key 'state_classes' must be set to a list of `State` classes.
584 - Key 'initial_state' must be set to the name of the initial state class.
585
586 If `nested_sm_kwargs` is left as ``None``, 'state_classes' defaults to the
587 class of the current state, and 'initial_state' defaults to the name of
588 the class of the current state. Override in subclasses to avoid the
589 defaults.
590 """
591
592 def __init__(self, state_machine, debug=0):
593 """
594 Initialize a `State` object; make & add initial transitions.
595
596 Parameters:
597
598 - `statemachine`: the controlling `StateMachine` object.
599 - `debug`: a boolean; produce verbose output if true (nonzero).
600 """
601
602 self.transition_order = []
603 """A list of transition names in search order."""
604
605 self.transitions = {}
606 """
607 A mapping of transition names to 3-tuples containing
608 (compiled_pattern, transition_method, next_state_name). Initialized as
609 an instance attribute dynamically (instead of as a class attribute)
610 because it may make forward references to patterns and methods in this
611 or other classes.
612 """
613
614 self.add_initial_transitions()
615
616 self.state_machine = state_machine
617 """A reference to the controlling `StateMachine` object."""
618
619 self.debug = debug
620 """Debugging mode on/off."""
621
622 if self.nested_sm is None:
623 self.nested_sm = self.state_machine.__class__
624 if self.nested_sm_kwargs is None:
625 self.nested_sm_kwargs = {'state_classes': [self.__class__],
626 'initial_state': self.__class__.__name__}
627
628 def runtime_init(self):
629 """
630 Initialize this `State` before running the state machine; called from
631 `self.state_machine.run()`.
632 """
633 pass
634
635 def unlink(self):
636 """Remove circular references to objects no longer required."""
637 self.state_machine = None
638
639 def add_initial_transitions(self):
640 """Make and add transitions listed in `self.initial_transitions`."""
641 if self.initial_transitions:
642 names, transitions = self.make_transitions(
643 self.initial_transitions)
644 self.add_transitions(names, transitions)
645
646 def add_transitions(self, names, transitions):
647 """
648 Add a list of transitions to the start of the transition list.
649
650 Parameters:
651
652 - `names`: a list of transition names.
653 - `transitions`: a mapping of names to transition tuples.
654
655 Exceptions: `DuplicateTransitionError`, `UnknownTransitionError`.
656 """
657 for name in names:
658 if name in self.transitions:
659 raise DuplicateTransitionError(name)
660 if name not in transitions:
661 raise UnknownTransitionError(name)
662 self.transition_order[:0] = names
663 self.transitions.update(transitions)
664
665 def add_transition(self, name, transition):
666 """
667 Add a transition to the start of the transition list.
668
669 Parameter `transition`: a ready-made transition 3-tuple.
670
671 Exception: `DuplicateTransitionError`.
672 """
673 if name in self.transitions:
674 raise DuplicateTransitionError(name)
675 self.transition_order[:0] = [name]
676 self.transitions[name] = transition
677
678 def remove_transition(self, name):
679 """
680 Remove a transition by `name`.
681
682 Exception: `UnknownTransitionError`.
683 """
684 try:
685 del self.transitions[name]
686 self.transition_order.remove(name)
687 except:
688 raise UnknownTransitionError(name)
689
690 def make_transition(self, name, next_state=None):
691 """
692 Make & return a transition tuple based on `name`.
693
694 This is a convenience function to simplify transition creation.
695
696 Parameters:
697
698 - `name`: a string, the name of the transition pattern & method. This
699 `State` object must have a method called '`name`', and a dictionary
700 `self.patterns` containing a key '`name`'.
701 - `next_state`: a string, the name of the next `State` object for this
702 transition. A value of ``None`` (or absent) implies no state change
703 (i.e., continue with the same state).
704
705 Exceptions: `TransitionPatternNotFound`, `TransitionMethodNotFound`.
706 """
707 if next_state is None:
708 next_state = self.__class__.__name__
709 try:
710 pattern = self.patterns[name]
711 if not hasattr(pattern, 'match'):
712 pattern = re.compile(pattern)
713 except KeyError:
714 raise TransitionPatternNotFound(
715 '%s.patterns[%r]' % (self.__class__.__name__, name))
716 try:
717 method = getattr(self, name)
718 except AttributeError:
719 raise TransitionMethodNotFound(
720 '%s.%s' % (self.__class__.__name__, name))
721 return (pattern, method, next_state)
722
723 def make_transitions(self, name_list):
724 """
725 Return a list of transition names and a transition mapping.
726
727 Parameter `name_list`: a list, where each entry is either a transition
728 name string, or a 1- or 2-tuple (transition name, optional next state
729 name).
730 """
731 stringtype = type('')
732 names = []
733 transitions = {}
734 for namestate in name_list:
735 if type(namestate) is stringtype:
736 transitions[namestate] = self.make_transition(namestate)
737 names.append(namestate)
738 else:
739 transitions[namestate[0]] = self.make_transition(*namestate)
740 names.append(namestate[0])
741 return names, transitions
742
743 def no_match(self, context, transitions):
744 """
745 Called when there is no match from `StateMachine.check_line()`.
746
747 Return the same values returned by transition methods:
748
749 - context: unchanged;
750 - next state name: ``None``;
751 - empty result list.
752
753 Override in subclasses to catch this event.
754 """
755 return context, None, []
756
757 def bof(self, context):
758 """
759 Handle beginning-of-file. Return unchanged `context`, empty result.
760
761 Override in subclasses.
762
763 Parameter `context`: application-defined storage.
764 """
765 return context, []
766
767 def eof(self, context):
768 """
769 Handle end-of-file. Return empty result.
770
771 Override in subclasses.
772
773 Parameter `context`: application-defined storage.
774 """
775 return []
776
777 def nop(self, match, context, next_state):
778 """
779 A "do nothing" transition method.
780
781 Return unchanged `context` & `next_state`, empty result. Useful for
782 simple state changes (actionless transitions).
783 """
784 return context, next_state, []
785
786
787class StateMachineWS(StateMachine):
788
789 """
790 `StateMachine` subclass specialized for whitespace recognition.
791
792 There are three methods provided for extracting indented text blocks:
793
794 - `get_indented()`: use when the indent is unknown.
795 - `get_known_indented()`: use when the indent is known for all lines.
796 - `get_first_known_indented()`: use when only the first line's indent is
797 known.
798 """
799
800 def get_indented(self, until_blank=0, strip_indent=1):
801 """
802 Return a block of indented lines of text, and info.
803
804 Extract an indented block where the indent is unknown for all lines.
805
806 :Parameters:
807 - `until_blank`: Stop collecting at the first blank line if true
808 (1).
809 - `strip_indent`: Strip common leading indent if true (1,
810 default).
811
812 :Return:
813 - the indented block (a list of lines of text),
814 - its indent,
815 - its first line offset from BOF, and
816 - whether or not it finished with a blank line.
817 """
818 offset = self.abs_line_offset()
819 indented, indent, blank_finish = self.input_lines.get_indented(
820 self.line_offset, until_blank, strip_indent)
821 if indented:
822 self.next_line(len(indented) - 1) # advance to last indented line
823 while indented and not indented[0].strip():
824 indented.trim_start()
825 offset += 1
826 return indented, indent, offset, blank_finish
827
828 def get_known_indented(self, indent, until_blank=0, strip_indent=1):
829 """
830 Return an indented block and info.
831
832 Extract an indented block where the indent is known for all lines.
833 Starting with the current line, extract the entire text block with at
834 least `indent` indentation (which must be whitespace, except for the
835 first line).
836
837 :Parameters:
838 - `indent`: The number of indent columns/characters.
839 - `until_blank`: Stop collecting at the first blank line if true
840 (1).
841 - `strip_indent`: Strip `indent` characters of indentation if true
842 (1, default).
843
844 :Return:
845 - the indented block,
846 - its first line offset from BOF, and
847 - whether or not it finished with a blank line.
848 """
849 offset = self.abs_line_offset()
850 indented, indent, blank_finish = self.input_lines.get_indented(
851 self.line_offset, until_blank, strip_indent,
852 block_indent=indent)
853 self.next_line(len(indented) - 1) # advance to last indented line
854 while indented and not indented[0].strip():
855 indented.trim_start()
856 offset += 1
857 return indented, offset, blank_finish
858
859 def get_first_known_indented(self, indent, until_blank=0, strip_indent=1,
860 strip_top=1):
861 """
862 Return an indented block and info.
863
864 Extract an indented block where the indent is known for the first line
865 and unknown for all other lines.
866
867 :Parameters:
868 - `indent`: The first line's indent (# of columns/characters).
869 - `until_blank`: Stop collecting at the first blank line if true
870 (1).
871 - `strip_indent`: Strip `indent` characters of indentation if true
872 (1, default).
873 - `strip_top`: Strip blank lines from the beginning of the block.
874
875 :Return:
876 - the indented block,
877 - its indent,
878 - its first line offset from BOF, and
879 - whether or not it finished with a blank line.
880 """
881 offset = self.abs_line_offset()
882 indented, indent, blank_finish = self.input_lines.get_indented(
883 self.line_offset, until_blank, strip_indent,
884 first_indent=indent)
885 self.next_line(len(indented) - 1) # advance to last indented line
886 if strip_top:
887 while indented and not indented[0].strip():
888 indented.trim_start()
889 offset += 1
890 return indented, indent, offset, blank_finish
891
892
893class StateWS(State):
894
895 """
896 State superclass specialized for whitespace (blank lines & indents).
897
898 Use this class with `StateMachineWS`. The transitions 'blank' (for blank
899 lines) and 'indent' (for indented text blocks) are added automatically,
900 before any other transitions. The transition method `blank()` handles
901 blank lines and `indent()` handles nested indented blocks. Indented
902 blocks trigger a new state machine to be created by `indent()` and run.
903 The class of the state machine to be created is in `indent_sm`, and the
904 constructor keyword arguments are in the dictionary `indent_sm_kwargs`.
905
906 The methods `known_indent()` and `firstknown_indent()` are provided for
907 indented blocks where the indent (all lines' and first line's only,
908 respectively) is known to the transition method, along with the attributes
909 `known_indent_sm` and `known_indent_sm_kwargs`. Neither transition method
910 is triggered automatically.
911 """
912
913 indent_sm = None
914 """
915 The `StateMachine` class handling indented text blocks.
916
917 If left as ``None``, `indent_sm` defaults to the value of
918 `State.nested_sm`. Override it in subclasses to avoid the default.
919 """
920
921 indent_sm_kwargs = None
922 """
923 Keyword arguments dictionary, passed to the `indent_sm` constructor.
924
925 If left as ``None``, `indent_sm_kwargs` defaults to the value of
926 `State.nested_sm_kwargs`. Override it in subclasses to avoid the default.
927 """
928
929 known_indent_sm = None
930 """
931 The `StateMachine` class handling known-indented text blocks.
932
933 If left as ``None``, `known_indent_sm` defaults to the value of
934 `indent_sm`. Override it in subclasses to avoid the default.
935 """
936
937 known_indent_sm_kwargs = None
938 """
939 Keyword arguments dictionary, passed to the `known_indent_sm` constructor.
940
941 If left as ``None``, `known_indent_sm_kwargs` defaults to the value of
942 `indent_sm_kwargs`. Override it in subclasses to avoid the default.
943 """
944
945 ws_patterns = {'blank': ' *$',
946 'indent': ' +'}
947 """Patterns for default whitespace transitions. May be overridden in
948 subclasses."""
949
950 ws_initial_transitions = ('blank', 'indent')
951 """Default initial whitespace transitions, added before those listed in
952 `State.initial_transitions`. May be overridden in subclasses."""
953
954 def __init__(self, state_machine, debug=0):
955 """
956 Initialize a `StateSM` object; extends `State.__init__()`.
957
958 Check for indent state machine attributes, set defaults if not set.
959 """
960 State.__init__(self, state_machine, debug)
961 if self.indent_sm is None:
962 self.indent_sm = self.nested_sm
963 if self.indent_sm_kwargs is None:
964 self.indent_sm_kwargs = self.nested_sm_kwargs
965 if self.known_indent_sm is None:
966 self.known_indent_sm = self.indent_sm
967 if self.known_indent_sm_kwargs is None:
968 self.known_indent_sm_kwargs = self.indent_sm_kwargs
969
970 def add_initial_transitions(self):
971 """
972 Add whitespace-specific transitions before those defined in subclass.
973
974 Extends `State.add_initial_transitions()`.
975 """
976 State.add_initial_transitions(self)
977 if self.patterns is None:
978 self.patterns = {}
979 self.patterns.update(self.ws_patterns)
980 names, transitions = self.make_transitions(
981 self.ws_initial_transitions)
982 self.add_transitions(names, transitions)
983
984 def blank(self, match, context, next_state):
985 """Handle blank lines. Does nothing. Override in subclasses."""
986 return self.nop(match, context, next_state)
987
988 def indent(self, match, context, next_state):
989 """
990 Handle an indented text block. Extend or override in subclasses.
991
992 Recursively run the registered state machine for indented blocks
993 (`self.indent_sm`).
994 """
995 indented, indent, line_offset, blank_finish = \
996 self.state_machine.get_indented()
997 sm = self.indent_sm(debug=self.debug, **self.indent_sm_kwargs)
998 results = sm.run(indented, input_offset=line_offset)
999 return context, next_state, results
1000
1001 def known_indent(self, match, context, next_state):
1002 """
1003 Handle a known-indent text block. Extend or override in subclasses.
1004
1005 Recursively run the registered state machine for known-indent indented
1006 blocks (`self.known_indent_sm`). The indent is the length of the
1007 match, ``match.end()``.
1008 """
1009 indented, line_offset, blank_finish = \
1010 self.state_machine.get_known_indented(match.end())
1011 sm = self.known_indent_sm(debug=self.debug,
1012 **self.known_indent_sm_kwargs)
1013 results = sm.run(indented, input_offset=line_offset)
1014 return context, next_state, results
1015
1016 def first_known_indent(self, match, context, next_state):
1017 """
1018 Handle an indented text block (first line's indent known).
1019
1020 Extend or override in subclasses.
1021
1022 Recursively run the registered state machine for known-indent indented
1023 blocks (`self.known_indent_sm`). The indent is the length of the
1024 match, ``match.end()``.
1025 """
1026 indented, line_offset, blank_finish = \
1027 self.state_machine.get_first_known_indented(match.end())
1028 sm = self.known_indent_sm(debug=self.debug,
1029 **self.known_indent_sm_kwargs)
1030 results = sm.run(indented, input_offset=line_offset)
1031 return context, next_state, results
1032
1033
1034class _SearchOverride:
1035
1036 """
1037 Mix-in class to override `StateMachine` regular expression behavior.
1038
1039 Changes regular expression matching, from the default `re.match()`
1040 (succeeds only if the pattern matches at the start of `self.line`) to
1041 `re.search()` (succeeds if the pattern matches anywhere in `self.line`).
1042 When subclassing a `StateMachine`, list this class **first** in the
1043 inheritance list of the class definition.
1044 """
1045
1046 def match(self, pattern):
1047 """
1048 Return the result of a regular expression search.
1049
1050 Overrides `StateMachine.match()`.
1051
1052 Parameter `pattern`: `re` compiled regular expression.
1053 """
1054 return pattern.search(self.line)
1055
1056
1057class SearchStateMachine(_SearchOverride, StateMachine):
1058 """`StateMachine` which uses `re.search()` instead of `re.match()`."""
1059 pass
1060
1061
1062class SearchStateMachineWS(_SearchOverride, StateMachineWS):
1063 """`StateMachineWS` which uses `re.search()` instead of `re.match()`."""
1064 pass
1065
1066
1067class ViewList:
1068
1069 """
1070 List with extended functionality: slices of ViewList objects are child
1071 lists, linked to their parents. Changes made to a child list also affect
1072 the parent list. A child list is effectively a "view" (in the SQL sense)
1073 of the parent list. Changes to parent lists, however, do *not* affect
1074 active child lists. If a parent list is changed, any active child lists
1075 should be recreated.
1076
1077 The start and end of the slice can be trimmed using the `trim_start()` and
1078 `trim_end()` methods, without affecting the parent list. The link between
1079 child and parent lists can be broken by calling `disconnect()` on the
1080 child list.
1081
1082 Also, ViewList objects keep track of the source & offset of each item.
1083 This information is accessible via the `source()`, `offset()`, and
1084 `info()` methods.
1085 """
1086
1087 def __init__(self, initlist=None, source=None, items=None,
1088 parent=None, parent_offset=None):
1089 self.data = []
1090 """The actual list of data, flattened from various sources."""
1091
1092 self.items = []
1093 """A list of (source, offset) pairs, same length as `self.data`: the
1094 source of each line and the offset of each line from the beginning of
1095 its source."""
1096
1097 self.parent = parent
1098 """The parent list."""
1099
1100 self.parent_offset = parent_offset
1101 """Offset of this list from the beginning of the parent list."""
1102
1103 if isinstance(initlist, ViewList):
1104 self.data = initlist.data[:]
1105 self.items = initlist.items[:]
1106 elif initlist is not None:
1107 self.data = list(initlist)
1108 if items:
1109 self.items = items
1110 else:
1111 self.items = [(source, i) for i in range(len(initlist))]
1112 assert len(self.data) == len(self.items), 'data mismatch'
1113
1114 def __str__(self):
1115 return str(self.data)
1116
1117 def __repr__(self):
1118 return '%s(%s, items=%s)' % (self.__class__.__name__,
1119 self.data, self.items)
1120
1121 def __lt__(self, other): return self.data < self.__cast(other)
1122 def __le__(self, other): return self.data <= self.__cast(other)
1123 def __eq__(self, other): return self.data == self.__cast(other)
1124 def __ne__(self, other): return self.data != self.__cast(other)
1125 def __gt__(self, other): return self.data > self.__cast(other)
1126 def __ge__(self, other): return self.data >= self.__cast(other)
1127 def __cmp__(self, other): return cmp(self.data, self.__cast(other))
1128
1129 def __cast(self, other):
1130 if isinstance(other, ViewList):
1131 return other.data
1132 else:
1133 return other
1134
1135 def __contains__(self, item): return item in self.data
1136 def __len__(self): return len(self.data)
1137
1138 # The __getitem__()/__setitem__() methods check whether the index
1139 # is a slice first, since indexing a native list with a slice object
1140 # just works.
1141
1142 def __getitem__(self, i):
1143 if isinstance(i, types.SliceType):
1144 assert i.step in (None, 1), 'cannot handle slice with stride'
1145 return self.__class__(self.data[i.start:i.stop],
1146 items=self.items[i.start:i.stop],
1147 parent=self, parent_offset=i.start or 0)
1148 else:
1149 return self.data[i]
1150
1151 def __setitem__(self, i, item):
1152 if isinstance(i, types.SliceType):
1153 assert i.step in (None, 1), 'cannot handle slice with stride'
1154 if not isinstance(item, ViewList):
1155 raise TypeError('assigning non-ViewList to ViewList slice')
1156 self.data[i.start:i.stop] = item.data
1157 self.items[i.start:i.stop] = item.items
1158 assert len(self.data) == len(self.items), 'data mismatch'
1159 if self.parent:
1160 self.parent[(i.start or 0) + self.parent_offset
1161 : (i.stop or len(self)) + self.parent_offset] = item
1162 else:
1163 self.data[i] = item
1164 if self.parent:
1165 self.parent[i + self.parent_offset] = item
1166
1167 def __delitem__(self, i):
1168 try:
1169 del self.data[i]
1170 del self.items[i]
1171 if self.parent:
1172 del self.parent[i + self.parent_offset]
1173 except TypeError:
1174 assert i.step is None, 'cannot handle slice with stride'
1175 del self.data[i.start:i.stop]
1176 del self.items[i.start:i.stop]
1177 if self.parent:
1178 del self.parent[(i.start or 0) + self.parent_offset
1179 : (i.stop or len(self)) + self.parent_offset]
1180
1181 def __add__(self, other):
1182 if isinstance(other, ViewList):
1183 return self.__class__(self.data + other.data,
1184 items=(self.items + other.items))
1185 else:
1186 raise TypeError('adding non-ViewList to a ViewList')
1187
1188 def __radd__(self, other):
1189 if isinstance(other, ViewList):
1190 return self.__class__(other.data + self.data,
1191 items=(other.items + self.items))
1192 else:
1193 raise TypeError('adding ViewList to a non-ViewList')
1194
1195 def __iadd__(self, other):
1196 if isinstance(other, ViewList):
1197 self.data += other.data
1198 else:
1199 raise TypeError('argument to += must be a ViewList')
1200 return self
1201
1202 def __mul__(self, n):
1203 return self.__class__(self.data * n, items=(self.items * n))
1204
1205 __rmul__ = __mul__
1206
1207 def __imul__(self, n):
1208 self.data *= n
1209 self.items *= n
1210 return self
1211
1212 def extend(self, other):
1213 if not isinstance(other, ViewList):
1214 raise TypeError('extending a ViewList with a non-ViewList')
1215 if self.parent:
1216 self.parent.insert(len(self.data) + self.parent_offset, other)
1217 self.data.extend(other.data)
1218 self.items.extend(other.items)
1219
1220 def append(self, item, source=None, offset=0):
1221 if source is None:
1222 self.extend(item)
1223 else:
1224 if self.parent:
1225 self.parent.insert(len(self.data) + self.parent_offset, item,
1226 source, offset)
1227 self.data.append(item)
1228 self.items.append((source, offset))
1229
1230 def insert(self, i, item, source=None, offset=0):
1231 if source is None:
1232 if not isinstance(item, ViewList):
1233 raise TypeError('inserting non-ViewList with no source given')
1234 self.data[i:i] = item.data
1235 self.items[i:i] = item.items
1236 if self.parent:
1237 index = (len(self.data) + i) % len(self.data)
1238 self.parent.insert(index + self.parent_offset, item)
1239 else:
1240 self.data.insert(i, item)
1241 self.items.insert(i, (source, offset))
1242 if self.parent:
1243 index = (len(self.data) + i) % len(self.data)
1244 self.parent.insert(index + self.parent_offset, item,
1245 source, offset)
1246
1247 def pop(self, i=-1):
1248 if self.parent:
1249 index = (len(self.data) + i) % len(self.data)
1250 self.parent.pop(index + self.parent_offset)
1251 self.items.pop(i)
1252 return self.data.pop(i)
1253
1254 def trim_start(self, n=1):
1255 """
1256 Remove items from the start of the list, without touching the parent.
1257 """
1258 if n > len(self.data):
1259 raise IndexError("Size of trim too large; can't trim %s items "
1260 "from a list of size %s." % (n, len(self.data)))
1261 elif n < 0:
1262 raise IndexError('Trim size must be >= 0.')
1263 del self.data[:n]
1264 del self.items[:n]
1265 if self.parent:
1266 self.parent_offset += n
1267
1268 def trim_end(self, n=1):
1269 """
1270 Remove items from the end of the list, without touching the parent.
1271 """
1272 if n > len(self.data):
1273 raise IndexError("Size of trim too large; can't trim %s items "
1274 "from a list of size %s." % (n, len(self.data)))
1275 elif n < 0:
1276 raise IndexError('Trim size must be >= 0.')
1277 del self.data[-n:]
1278 del self.items[-n:]
1279
1280 def remove(self, item):
1281 index = self.index(item)
1282 del self[index]
1283
1284 def count(self, item): return self.data.count(item)
1285 def index(self, item): return self.data.index(item)
1286
1287 def reverse(self):
1288 self.data.reverse()
1289 self.items.reverse()
1290 self.parent = None
1291
1292 def sort(self, *args):
1293 tmp = zip(self.data, self.items)
1294 tmp.sort(*args)
1295 self.data = [entry[0] for entry in tmp]
1296 self.items = [entry[1] for entry in tmp]
1297 self.parent = None
1298
1299 def info(self, i):
1300 """Return source & offset for index `i`."""
1301 try:
1302 return self.items[i]
1303 except IndexError:
1304 if i == len(self.data): # Just past the end
1305 return self.items[i - 1][0], None
1306 else:
1307 raise
1308
1309 def source(self, i):
1310 """Return source for index `i`."""
1311 return self.info(i)[0]
1312
1313 def offset(self, i):
1314 """Return offset for index `i`."""
1315 return self.info(i)[1]
1316
1317 def disconnect(self):
1318 """Break link between this list and parent list."""
1319 self.parent = None
1320
1321 def xitems(self):
1322 """Return iterator yielding (source, offset, value) tuples."""
1323 for (value, (source, offset)) in zip(self.data, self.items):
1324 yield (source, offset, value)
1325
1326 def pprint(self):
1327 """Print the list in `grep` format (`source:offset:value` lines)"""
1328 for line in self.xitems():
1329 print "%s:%d:%s" % line
1330
1331
1332class StringList(ViewList):
1333
1334 """A `ViewList` with string-specific methods."""
1335
1336 def trim_left(self, length, start=0, end=sys.maxint):
1337 """
1338 Trim `length` characters off the beginning of each item, in-place,
1339 from index `start` to `end`. No whitespace-checking is done on the
1340 trimmed text. Does not affect slice parent.
1341 """
1342 self.data[start:end] = [line[length:]
1343 for line in self.data[start:end]]
1344
1345 def get_text_block(self, start, flush_left=0):
1346 """
1347 Return a contiguous block of text.
1348
1349 If `flush_left` is true, raise `UnexpectedIndentationError` if an
1350 indented line is encountered before the text block ends (with a blank
1351 line).
1352 """
1353 end = start
1354 last = len(self.data)
1355 while end < last:
1356 line = self.data[end]
1357 if not line.strip():
1358 break
1359 if flush_left and (line[0] == ' '):
1360 source, offset = self.info(end)
1361 raise UnexpectedIndentationError(self[start:end], source,
1362 offset + 1)
1363 end += 1
1364 return self[start:end]
1365
1366 def get_indented(self, start=0, until_blank=0, strip_indent=1,
1367 block_indent=None, first_indent=None):
1368 """
1369 Extract and return a StringList of indented lines of text.
1370
1371 Collect all lines with indentation, determine the minimum indentation,
1372 remove the minimum indentation from all indented lines (unless
1373 `strip_indent` is false), and return them. All lines up to but not
1374 including the first unindented line will be returned.
1375
1376 :Parameters:
1377 - `start`: The index of the first line to examine.
1378 - `until_blank`: Stop collecting at the first blank line if true.
1379 - `strip_indent`: Strip common leading indent if true (default).
1380 - `block_indent`: The indent of the entire block, if known.
1381 - `first_indent`: The indent of the first line, if known.
1382
1383 :Return:
1384 - a StringList of indented lines with mininum indent removed;
1385 - the amount of the indent;
1386 - a boolean: did the indented block finish with a blank line or EOF?
1387 """
1388 indent = block_indent # start with None if unknown
1389 end = start
1390 if block_indent is not None and first_indent is None:
1391 first_indent = block_indent
1392 if first_indent is not None:
1393 end += 1
1394 last = len(self.data)
1395 while end < last:
1396 line = self.data[end]
1397 if line and (line[0] != ' '
1398 or (block_indent is not None
1399 and line[:block_indent].strip())):
1400 # Line not indented or insufficiently indented.
1401 # Block finished properly iff the last indented line blank:
1402 blank_finish = ((end > start)
1403 and not self.data[end - 1].strip())
1404 break
1405 stripped = line.lstrip()
1406 if not stripped: # blank line
1407 if until_blank:
1408 blank_finish = 1
1409 break
1410 elif block_indent is None:
1411 line_indent = len(line) - len(stripped)
1412 if indent is None:
1413 indent = line_indent
1414 else:
1415 indent = min(indent, line_indent)
1416 end += 1
1417 else:
1418 blank_finish = 1 # block ends at end of lines
1419 block = self[start:end]
1420 if first_indent is not None and block:
1421 block.data[0] = block.data[0][first_indent:]
1422 if indent and strip_indent:
1423 block.trim_left(indent, start=(first_indent is not None))
1424 return block, indent or 0, blank_finish
1425
1426 def get_2D_block(self, top, left, bottom, right, strip_indent=1):
1427 block = self[top:bottom]
1428 indent = right
1429 for i in range(len(block.data)):
1430 block.data[i] = line = block.data[i][left:right].rstrip()
1431 if line:
1432 indent = min(indent, len(line) - len(line.lstrip()))
1433 if strip_indent and 0 < indent < right:
1434 block.data = [line[indent:] for line in block.data]
1435 return block
1436
1437 def pad_double_width(self, pad_char):
1438 """
1439 Pad all double-width characters in self by appending `pad_char` to each.
1440 For East Asian language support.
1441 """
1442 if hasattr(unicodedata, 'east_asian_width'):
1443 east_asian_width = unicodedata.east_asian_width
1444 else:
1445 return # new in Python 2.4
1446 for i in range(len(self.data)):
1447 line = self.data[i]
1448 if isinstance(line, unicode):
1449 new = []
1450 for char in line:
1451 new.append(char)
1452 if east_asian_width(char) in 'WF': # 'W'ide & 'F'ull-width
1453 new.append(pad_char)
1454 self.data[i] = ''.join(new)
1455
1456 def replace(self, old, new):
1457 """Replace all occurrences of substring `old` with `new`."""
1458 for i in range(len(self.data)):
1459 self.data[i] = self.data[i].replace(old, new)
1460
1461
1462class StateMachineError(Exception): pass
1463class UnknownStateError(StateMachineError): pass
1464class DuplicateStateError(StateMachineError): pass
1465class UnknownTransitionError(StateMachineError): pass
1466class DuplicateTransitionError(StateMachineError): pass
1467class TransitionPatternNotFound(StateMachineError): pass
1468class TransitionMethodNotFound(StateMachineError): pass
1469class UnexpectedIndentationError(StateMachineError): pass
1470
1471
1472class TransitionCorrection(Exception):
1473
1474 """
1475 Raise from within a transition method to switch to another transition.
1476
1477 Raise with one argument, the new transition name.
1478 """
1479
1480
1481class StateCorrection(Exception):
1482
1483 """
1484 Raise from within a transition method to switch to another state.
1485
1486 Raise with one or two arguments: new state name, and an optional new
1487 transition name.
1488 """
1489
1490
1491def string2lines(astring, tab_width=8, convert_whitespace=0,
1492 whitespace=re.compile('[\v\f]')):
1493 """
1494 Return a list of one-line strings with tabs expanded, no newlines, and
1495 trailing whitespace stripped.
1496
1497 Each tab is expanded with between 1 and `tab_width` spaces, so that the
1498 next character's index becomes a multiple of `tab_width` (8 by default).
1499
1500 Parameters:
1501
1502 - `astring`: a multi-line string.
1503 - `tab_width`: the number of columns between tab stops.
1504 - `convert_whitespace`: convert form feeds and vertical tabs to spaces?
1505 """
1506 if convert_whitespace:
1507 astring = whitespace.sub(' ', astring)
1508 return [s.expandtabs(tab_width).rstrip() for s in astring.splitlines()]
1509
1510def _exception_data():
1511 """
1512 Return exception information:
1513
1514 - the exception's class name;
1515 - the exception object;
1516 - the name of the file containing the offending code;
1517 - the line number of the offending code;
1518 - the function name of the offending code.
1519 """
1520 type, value, traceback = sys.exc_info()
1521 while traceback.tb_next:
1522 traceback = traceback.tb_next
1523 code = traceback.tb_frame.f_code
1524 return (type.__name__, value, code.co_filename, traceback.tb_lineno,
1525 code.co_name)