mirror of
				https://github.com/python/cpython.git
				synced 2025-10-29 20:51:26 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			618 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			618 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import re
 | |
| import sys
 | |
| from collections import Mapping
 | |
| from functools import partial
 | |
| 
 | |
| # Reason last stmt is continued (or C_NONE if it's not).
 | |
| (C_NONE, C_BACKSLASH, C_STRING_FIRST_LINE,
 | |
|  C_STRING_NEXT_LINES, C_BRACKET) = range(5)
 | |
| 
 | |
| if 0:   # for throwaway debugging output
 | |
|     def dump(*stuff):
 | |
|         sys.__stdout__.write(" ".join(map(str, stuff)) + "\n")
 | |
| 
 | |
| # Find what looks like the start of a popular stmt.
 | |
| 
 | |
| _synchre = re.compile(r"""
 | |
|     ^
 | |
|     [ \t]*
 | |
|     (?: while
 | |
|     |   else
 | |
|     |   def
 | |
|     |   return
 | |
|     |   assert
 | |
|     |   break
 | |
|     |   class
 | |
|     |   continue
 | |
|     |   elif
 | |
|     |   try
 | |
|     |   except
 | |
|     |   raise
 | |
|     |   import
 | |
|     |   yield
 | |
|     )
 | |
|     \b
 | |
| """, re.VERBOSE | re.MULTILINE).search
 | |
| 
 | |
| # Match blank line or non-indenting comment line.
 | |
| 
 | |
| _junkre = re.compile(r"""
 | |
|     [ \t]*
 | |
|     (?: \# \S .* )?
 | |
|     \n
 | |
| """, re.VERBOSE).match
 | |
| 
 | |
| # Match any flavor of string; the terminating quote is optional
 | |
| # so that we're robust in the face of incomplete program text.
 | |
| 
 | |
| _match_stringre = re.compile(r"""
 | |
|     \""" [^"\\]* (?:
 | |
|                      (?: \\. | "(?!"") )
 | |
|                      [^"\\]*
 | |
|                  )*
 | |
|     (?: \""" )?
 | |
| 
 | |
| |   " [^"\\\n]* (?: \\. [^"\\\n]* )* "?
 | |
| 
 | |
| |   ''' [^'\\]* (?:
 | |
|                    (?: \\. | '(?!'') )
 | |
|                    [^'\\]*
 | |
|                 )*
 | |
|     (?: ''' )?
 | |
| 
 | |
| |   ' [^'\\\n]* (?: \\. [^'\\\n]* )* '?
 | |
| """, re.VERBOSE | re.DOTALL).match
 | |
| 
 | |
| # Match a line that starts with something interesting;
 | |
| # used to find the first item of a bracket structure.
 | |
| 
 | |
| _itemre = re.compile(r"""
 | |
|     [ \t]*
 | |
|     [^\s#\\]    # if we match, m.end()-1 is the interesting char
 | |
| """, re.VERBOSE).match
 | |
| 
 | |
| # Match start of stmts that should be followed by a dedent.
 | |
| 
 | |
| _closere = re.compile(r"""
 | |
|     \s*
 | |
|     (?: return
 | |
|     |   break
 | |
|     |   continue
 | |
|     |   raise
 | |
|     |   pass
 | |
|     )
 | |
|     \b
 | |
| """, re.VERBOSE).match
 | |
| 
 | |
| # Chew up non-special chars as quickly as possible.  If match is
 | |
| # successful, m.end() less 1 is the index of the last boring char
 | |
| # matched.  If match is unsuccessful, the string starts with an
 | |
| # interesting char.
 | |
| 
 | |
| _chew_ordinaryre = re.compile(r"""
 | |
|     [^[\](){}#'"\\]+
 | |
| """, re.VERBOSE).match
 | |
| 
 | |
| 
 | |
| class StringTranslatePseudoMapping(Mapping):
 | |
|     r"""Utility class to be used with str.translate()
 | |
| 
 | |
|     This Mapping class wraps a given dict. When a value for a key is
 | |
|     requested via __getitem__() or get(), the key is looked up in the
 | |
|     given dict. If found there, the value from the dict is returned.
 | |
|     Otherwise, the default value given upon initialization is returned.
 | |
| 
 | |
|     This allows using str.translate() to make some replacements, and to
 | |
|     replace all characters for which no replacement was specified with
 | |
|     a given character instead of leaving them as-is.
 | |
| 
 | |
|     For example, to replace everything except whitespace with 'x':
 | |
| 
 | |
|     >>> whitespace_chars = ' \t\n\r'
 | |
|     >>> preserve_dict = {ord(c): ord(c) for c in whitespace_chars}
 | |
|     >>> mapping = StringTranslatePseudoMapping(preserve_dict, ord('x'))
 | |
|     >>> text = "a + b\tc\nd"
 | |
|     >>> text.translate(mapping)
 | |
|     'x x x\tx\nx'
 | |
|     """
 | |
|     def __init__(self, non_defaults, default_value):
 | |
|         self._non_defaults = non_defaults
 | |
|         self._default_value = default_value
 | |
| 
 | |
|         def _get(key, _get=non_defaults.get, _default=default_value):
 | |
|             return _get(key, _default)
 | |
|         self._get = _get
 | |
| 
 | |
|     def __getitem__(self, item):
 | |
|         return self._get(item)
 | |
| 
 | |
|     def __len__(self):
 | |
|         return len(self._non_defaults)
 | |
| 
 | |
|     def __iter__(self):
 | |
|         return iter(self._non_defaults)
 | |
| 
 | |
|     def get(self, key, default=None):
 | |
|         return self._get(key)
 | |
| 
 | |
| 
 | |
| class Parser:
 | |
| 
 | |
|     def __init__(self, indentwidth, tabwidth):
 | |
|         self.indentwidth = indentwidth
 | |
|         self.tabwidth = tabwidth
 | |
| 
 | |
|     def set_str(self, s):
 | |
|         assert len(s) == 0 or s[-1] == '\n'
 | |
|         self.str = s
 | |
|         self.study_level = 0
 | |
| 
 | |
|     # Return index of a good place to begin parsing, as close to the
 | |
|     # end of the string as possible.  This will be the start of some
 | |
|     # popular stmt like "if" or "def".  Return None if none found:
 | |
|     # the caller should pass more prior context then, if possible, or
 | |
|     # if not (the entire program text up until the point of interest
 | |
|     # has already been tried) pass 0 to set_lo.
 | |
|     #
 | |
|     # This will be reliable iff given a reliable is_char_in_string
 | |
|     # function, meaning that when it says "no", it's absolutely
 | |
|     # guaranteed that the char is not in a string.
 | |
| 
 | |
|     def find_good_parse_start(self, is_char_in_string=None,
 | |
|                               _synchre=_synchre):
 | |
|         str, pos = self.str, None
 | |
| 
 | |
|         if not is_char_in_string:
 | |
|             # no clue -- make the caller pass everything
 | |
|             return None
 | |
| 
 | |
|         # Peek back from the end for a good place to start,
 | |
|         # but don't try too often; pos will be left None, or
 | |
|         # bumped to a legitimate synch point.
 | |
|         limit = len(str)
 | |
|         for tries in range(5):
 | |
|             i = str.rfind(":\n", 0, limit)
 | |
|             if i < 0:
 | |
|                 break
 | |
|             i = str.rfind('\n', 0, i) + 1  # start of colon line
 | |
|             m = _synchre(str, i, limit)
 | |
|             if m and not is_char_in_string(m.start()):
 | |
|                 pos = m.start()
 | |
|                 break
 | |
|             limit = i
 | |
|         if pos is None:
 | |
|             # Nothing looks like a block-opener, or stuff does
 | |
|             # but is_char_in_string keeps returning true; most likely
 | |
|             # we're in or near a giant string, the colorizer hasn't
 | |
|             # caught up enough to be helpful, or there simply *aren't*
 | |
|             # any interesting stmts.  In any of these cases we're
 | |
|             # going to have to parse the whole thing to be sure, so
 | |
|             # give it one last try from the start, but stop wasting
 | |
|             # time here regardless of the outcome.
 | |
|             m = _synchre(str)
 | |
|             if m and not is_char_in_string(m.start()):
 | |
|                 pos = m.start()
 | |
|             return pos
 | |
| 
 | |
|         # Peeking back worked; look forward until _synchre no longer
 | |
|         # matches.
 | |
|         i = pos + 1
 | |
|         while 1:
 | |
|             m = _synchre(str, i)
 | |
|             if m:
 | |
|                 s, i = m.span()
 | |
|                 if not is_char_in_string(s):
 | |
|                     pos = s
 | |
|             else:
 | |
|                 break
 | |
|         return pos
 | |
| 
 | |
|     # Throw away the start of the string.  Intended to be called with
 | |
|     # find_good_parse_start's result.
 | |
| 
 | |
|     def set_lo(self, lo):
 | |
|         assert lo == 0 or self.str[lo-1] == '\n'
 | |
|         if lo > 0:
 | |
|             self.str = self.str[lo:]
 | |
| 
 | |
|     # Build a translation table to map uninteresting chars to 'x', open
 | |
|     # brackets to '(', close brackets to ')' while preserving quotes,
 | |
|     # backslashes, newlines and hashes. This is to be passed to
 | |
|     # str.translate() in _study1().
 | |
|     _tran = {}
 | |
|     _tran.update((ord(c), ord('(')) for c in "({[")
 | |
|     _tran.update((ord(c), ord(')')) for c in ")}]")
 | |
|     _tran.update((ord(c), ord(c)) for c in "\"'\\\n#")
 | |
|     _tran = StringTranslatePseudoMapping(_tran, default_value=ord('x'))
 | |
| 
 | |
|     # As quickly as humanly possible <wink>, find the line numbers (0-
 | |
|     # based) of the non-continuation lines.
 | |
|     # Creates self.{goodlines, continuation}.
 | |
| 
 | |
|     def _study1(self):
 | |
|         if self.study_level >= 1:
 | |
|             return
 | |
|         self.study_level = 1
 | |
| 
 | |
|         # Map all uninteresting characters to "x", all open brackets
 | |
|         # to "(", all close brackets to ")", then collapse runs of
 | |
|         # uninteresting characters.  This can cut the number of chars
 | |
|         # by a factor of 10-40, and so greatly speed the following loop.
 | |
|         str = self.str
 | |
|         str = str.translate(self._tran)
 | |
|         str = str.replace('xxxxxxxx', 'x')
 | |
|         str = str.replace('xxxx', 'x')
 | |
|         str = str.replace('xx', 'x')
 | |
|         str = str.replace('xx', 'x')
 | |
|         str = str.replace('\nx', '\n')
 | |
|         # note that replacing x\n with \n would be incorrect, because
 | |
|         # x may be preceded by a backslash
 | |
| 
 | |
|         # March over the squashed version of the program, accumulating
 | |
|         # the line numbers of non-continued stmts, and determining
 | |
|         # whether & why the last stmt is a continuation.
 | |
|         continuation = C_NONE
 | |
|         level = lno = 0     # level is nesting level; lno is line number
 | |
|         self.goodlines = goodlines = [0]
 | |
|         push_good = goodlines.append
 | |
|         i, n = 0, len(str)
 | |
|         while i < n:
 | |
|             ch = str[i]
 | |
|             i = i+1
 | |
| 
 | |
|             # cases are checked in decreasing order of frequency
 | |
|             if ch == 'x':
 | |
|                 continue
 | |
| 
 | |
|             if ch == '\n':
 | |
|                 lno = lno + 1
 | |
|                 if level == 0:
 | |
|                     push_good(lno)
 | |
|                     # else we're in an unclosed bracket structure
 | |
|                 continue
 | |
| 
 | |
|             if ch == '(':
 | |
|                 level = level + 1
 | |
|                 continue
 | |
| 
 | |
|             if ch == ')':
 | |
|                 if level:
 | |
|                     level = level - 1
 | |
|                     # else the program is invalid, but we can't complain
 | |
|                 continue
 | |
| 
 | |
|             if ch == '"' or ch == "'":
 | |
|                 # consume the string
 | |
|                 quote = ch
 | |
|                 if str[i-1:i+2] == quote * 3:
 | |
|                     quote = quote * 3
 | |
|                 firstlno = lno
 | |
|                 w = len(quote) - 1
 | |
|                 i = i+w
 | |
|                 while i < n:
 | |
|                     ch = str[i]
 | |
|                     i = i+1
 | |
| 
 | |
|                     if ch == 'x':
 | |
|                         continue
 | |
| 
 | |
|                     if str[i-1:i+w] == quote:
 | |
|                         i = i+w
 | |
|                         break
 | |
| 
 | |
|                     if ch == '\n':
 | |
|                         lno = lno + 1
 | |
|                         if w == 0:
 | |
|                             # unterminated single-quoted string
 | |
|                             if level == 0:
 | |
|                                 push_good(lno)
 | |
|                             break
 | |
|                         continue
 | |
| 
 | |
|                     if ch == '\\':
 | |
|                         assert i < n
 | |
|                         if str[i] == '\n':
 | |
|                             lno = lno + 1
 | |
|                         i = i+1
 | |
|                         continue
 | |
| 
 | |
|                     # else comment char or paren inside string
 | |
| 
 | |
|                 else:
 | |
|                     # didn't break out of the loop, so we're still
 | |
|                     # inside a string
 | |
|                     if (lno - 1) == firstlno:
 | |
|                         # before the previous \n in str, we were in the first
 | |
|                         # line of the string
 | |
|                         continuation = C_STRING_FIRST_LINE
 | |
|                     else:
 | |
|                         continuation = C_STRING_NEXT_LINES
 | |
|                 continue    # with outer loop
 | |
| 
 | |
|             if ch == '#':
 | |
|                 # consume the comment
 | |
|                 i = str.find('\n', i)
 | |
|                 assert i >= 0
 | |
|                 continue
 | |
| 
 | |
|             assert ch == '\\'
 | |
|             assert i < n
 | |
|             if str[i] == '\n':
 | |
|                 lno = lno + 1
 | |
|                 if i+1 == n:
 | |
|                     continuation = C_BACKSLASH
 | |
|             i = i+1
 | |
| 
 | |
|         # The last stmt may be continued for all 3 reasons.
 | |
|         # String continuation takes precedence over bracket
 | |
|         # continuation, which beats backslash continuation.
 | |
|         if (continuation != C_STRING_FIRST_LINE
 | |
|             and continuation != C_STRING_NEXT_LINES and level > 0):
 | |
|             continuation = C_BRACKET
 | |
|         self.continuation = continuation
 | |
| 
 | |
|         # Push the final line number as a sentinel value, regardless of
 | |
|         # whether it's continued.
 | |
|         assert (continuation == C_NONE) == (goodlines[-1] == lno)
 | |
|         if goodlines[-1] != lno:
 | |
|             push_good(lno)
 | |
| 
 | |
|     def get_continuation_type(self):
 | |
|         self._study1()
 | |
|         return self.continuation
 | |
| 
 | |
|     # study1 was sufficient to determine the continuation status,
 | |
|     # but doing more requires looking at every character.  study2
 | |
|     # does this for the last interesting statement in the block.
 | |
|     # Creates:
 | |
|     #     self.stmt_start, stmt_end
 | |
|     #         slice indices of last interesting stmt
 | |
|     #     self.stmt_bracketing
 | |
|     #         the bracketing structure of the last interesting stmt;
 | |
|     #         for example, for the statement "say(boo) or die", stmt_bracketing
 | |
|     #         will be [(0, 0), (3, 1), (8, 0)]. Strings and comments are
 | |
|     #         treated as brackets, for the matter.
 | |
|     #     self.lastch
 | |
|     #         last non-whitespace character before optional trailing
 | |
|     #         comment
 | |
|     #     self.lastopenbracketpos
 | |
|     #         if continuation is C_BRACKET, index of last open bracket
 | |
| 
 | |
|     def _study2(self):
 | |
|         if self.study_level >= 2:
 | |
|             return
 | |
|         self._study1()
 | |
|         self.study_level = 2
 | |
| 
 | |
|         # Set p and q to slice indices of last interesting stmt.
 | |
|         str, goodlines = self.str, self.goodlines
 | |
|         i = len(goodlines) - 1
 | |
|         p = len(str)    # index of newest line
 | |
|         while i:
 | |
|             assert p
 | |
|             # p is the index of the stmt at line number goodlines[i].
 | |
|             # Move p back to the stmt at line number goodlines[i-1].
 | |
|             q = p
 | |
|             for nothing in range(goodlines[i-1], goodlines[i]):
 | |
|                 # tricky: sets p to 0 if no preceding newline
 | |
|                 p = str.rfind('\n', 0, p-1) + 1
 | |
|             # The stmt str[p:q] isn't a continuation, but may be blank
 | |
|             # or a non-indenting comment line.
 | |
|             if  _junkre(str, p):
 | |
|                 i = i-1
 | |
|             else:
 | |
|                 break
 | |
|         if i == 0:
 | |
|             # nothing but junk!
 | |
|             assert p == 0
 | |
|             q = p
 | |
|         self.stmt_start, self.stmt_end = p, q
 | |
| 
 | |
|         # Analyze this stmt, to find the last open bracket (if any)
 | |
|         # and last interesting character (if any).
 | |
|         lastch = ""
 | |
|         stack = []  # stack of open bracket indices
 | |
|         push_stack = stack.append
 | |
|         bracketing = [(p, 0)]
 | |
|         while p < q:
 | |
|             # suck up all except ()[]{}'"#\\
 | |
|             m = _chew_ordinaryre(str, p, q)
 | |
|             if m:
 | |
|                 # we skipped at least one boring char
 | |
|                 newp = m.end()
 | |
|                 # back up over totally boring whitespace
 | |
|                 i = newp - 1    # index of last boring char
 | |
|                 while i >= p and str[i] in " \t\n":
 | |
|                     i = i-1
 | |
|                 if i >= p:
 | |
|                     lastch = str[i]
 | |
|                 p = newp
 | |
|                 if p >= q:
 | |
|                     break
 | |
| 
 | |
|             ch = str[p]
 | |
| 
 | |
|             if ch in "([{":
 | |
|                 push_stack(p)
 | |
|                 bracketing.append((p, len(stack)))
 | |
|                 lastch = ch
 | |
|                 p = p+1
 | |
|                 continue
 | |
| 
 | |
|             if ch in ")]}":
 | |
|                 if stack:
 | |
|                     del stack[-1]
 | |
|                 lastch = ch
 | |
|                 p = p+1
 | |
|                 bracketing.append((p, len(stack)))
 | |
|                 continue
 | |
| 
 | |
|             if ch == '"' or ch == "'":
 | |
|                 # consume string
 | |
|                 # Note that study1 did this with a Python loop, but
 | |
|                 # we use a regexp here; the reason is speed in both
 | |
|                 # cases; the string may be huge, but study1 pre-squashed
 | |
|                 # strings to a couple of characters per line.  study1
 | |
|                 # also needed to keep track of newlines, and we don't
 | |
|                 # have to.
 | |
|                 bracketing.append((p, len(stack)+1))
 | |
|                 lastch = ch
 | |
|                 p = _match_stringre(str, p, q).end()
 | |
|                 bracketing.append((p, len(stack)))
 | |
|                 continue
 | |
| 
 | |
|             if ch == '#':
 | |
|                 # consume comment and trailing newline
 | |
|                 bracketing.append((p, len(stack)+1))
 | |
|                 p = str.find('\n', p, q) + 1
 | |
|                 assert p > 0
 | |
|                 bracketing.append((p, len(stack)))
 | |
|                 continue
 | |
| 
 | |
|             assert ch == '\\'
 | |
|             p = p+1     # beyond backslash
 | |
|             assert p < q
 | |
|             if str[p] != '\n':
 | |
|                 # the program is invalid, but can't complain
 | |
|                 lastch = ch + str[p]
 | |
|             p = p+1     # beyond escaped char
 | |
| 
 | |
|         # end while p < q:
 | |
| 
 | |
|         self.lastch = lastch
 | |
|         if stack:
 | |
|             self.lastopenbracketpos = stack[-1]
 | |
|         self.stmt_bracketing = tuple(bracketing)
 | |
| 
 | |
|     # Assuming continuation is C_BRACKET, return the number
 | |
|     # of spaces the next line should be indented.
 | |
| 
 | |
|     def compute_bracket_indent(self):
 | |
|         self._study2()
 | |
|         assert self.continuation == C_BRACKET
 | |
|         j = self.lastopenbracketpos
 | |
|         str = self.str
 | |
|         n = len(str)
 | |
|         origi = i = str.rfind('\n', 0, j) + 1
 | |
|         j = j+1     # one beyond open bracket
 | |
|         # find first list item; set i to start of its line
 | |
|         while j < n:
 | |
|             m = _itemre(str, j)
 | |
|             if m:
 | |
|                 j = m.end() - 1     # index of first interesting char
 | |
|                 extra = 0
 | |
|                 break
 | |
|             else:
 | |
|                 # this line is junk; advance to next line
 | |
|                 i = j = str.find('\n', j) + 1
 | |
|         else:
 | |
|             # nothing interesting follows the bracket;
 | |
|             # reproduce the bracket line's indentation + a level
 | |
|             j = i = origi
 | |
|             while str[j] in " \t":
 | |
|                 j = j+1
 | |
|             extra = self.indentwidth
 | |
|         return len(str[i:j].expandtabs(self.tabwidth)) + extra
 | |
| 
 | |
|     # Return number of physical lines in last stmt (whether or not
 | |
|     # it's an interesting stmt!  this is intended to be called when
 | |
|     # continuation is C_BACKSLASH).
 | |
| 
 | |
|     def get_num_lines_in_stmt(self):
 | |
|         self._study1()
 | |
|         goodlines = self.goodlines
 | |
|         return goodlines[-1] - goodlines[-2]
 | |
| 
 | |
|     # Assuming continuation is C_BACKSLASH, return the number of spaces
 | |
|     # the next line should be indented.  Also assuming the new line is
 | |
|     # the first one following the initial line of the stmt.
 | |
| 
 | |
|     def compute_backslash_indent(self):
 | |
|         self._study2()
 | |
|         assert self.continuation == C_BACKSLASH
 | |
|         str = self.str
 | |
|         i = self.stmt_start
 | |
|         while str[i] in " \t":
 | |
|             i = i+1
 | |
|         startpos = i
 | |
| 
 | |
|         # See whether the initial line starts an assignment stmt; i.e.,
 | |
|         # look for an = operator
 | |
|         endpos = str.find('\n', startpos) + 1
 | |
|         found = level = 0
 | |
|         while i < endpos:
 | |
|             ch = str[i]
 | |
|             if ch in "([{":
 | |
|                 level = level + 1
 | |
|                 i = i+1
 | |
|             elif ch in ")]}":
 | |
|                 if level:
 | |
|                     level = level - 1
 | |
|                 i = i+1
 | |
|             elif ch == '"' or ch == "'":
 | |
|                 i = _match_stringre(str, i, endpos).end()
 | |
|             elif ch == '#':
 | |
|                 break
 | |
|             elif level == 0 and ch == '=' and \
 | |
|                    (i == 0 or str[i-1] not in "=<>!") and \
 | |
|                    str[i+1] != '=':
 | |
|                 found = 1
 | |
|                 break
 | |
|             else:
 | |
|                 i = i+1
 | |
| 
 | |
|         if found:
 | |
|             # found a legit =, but it may be the last interesting
 | |
|             # thing on the line
 | |
|             i = i+1     # move beyond the =
 | |
|             found = re.match(r"\s*\\", str[i:endpos]) is None
 | |
| 
 | |
|         if not found:
 | |
|             # oh well ... settle for moving beyond the first chunk
 | |
|             # of non-whitespace chars
 | |
|             i = startpos
 | |
|             while str[i] not in " \t\n":
 | |
|                 i = i+1
 | |
| 
 | |
|         return len(str[self.stmt_start:i].expandtabs(\
 | |
|                                      self.tabwidth)) + 1
 | |
| 
 | |
|     # Return the leading whitespace on the initial line of the last
 | |
|     # interesting stmt.
 | |
| 
 | |
|     def get_base_indent_string(self):
 | |
|         self._study2()
 | |
|         i, n = self.stmt_start, self.stmt_end
 | |
|         j = i
 | |
|         str = self.str
 | |
|         while j < n and str[j] in " \t":
 | |
|             j = j + 1
 | |
|         return str[i:j]
 | |
| 
 | |
|     # Did the last interesting stmt open a block?
 | |
| 
 | |
|     def is_block_opener(self):
 | |
|         self._study2()
 | |
|         return self.lastch == ':'
 | |
| 
 | |
|     # Did the last interesting stmt close a block?
 | |
| 
 | |
|     def is_block_closer(self):
 | |
|         self._study2()
 | |
|         return _closere(self.str, self.stmt_start) is not None
 | |
| 
 | |
|     # index of last open bracket ({[, or None if none
 | |
|     lastopenbracketpos = None
 | |
| 
 | |
|     def get_last_open_bracket_pos(self):
 | |
|         self._study2()
 | |
|         return self.lastopenbracketpos
 | |
| 
 | |
|     # the structure of the bracketing of the last interesting statement,
 | |
|     # in the format defined in _study2, or None if the text didn't contain
 | |
|     # anything
 | |
|     stmt_bracketing = None
 | |
| 
 | |
|     def get_last_stmt_bracketing(self):
 | |
|         self._study2()
 | |
|         return self.stmt_bracketing
 | 
