summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib/pyparse.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/idlelib/pyparse.py')
-rw-r--r--Lib/idlelib/pyparse.py161
1 files changed, 95 insertions, 66 deletions
diff --git a/Lib/idlelib/pyparse.py b/Lib/idlelib/pyparse.py
index 536b2d7..72bd9e0 100644
--- a/Lib/idlelib/pyparse.py
+++ b/Lib/idlelib/pyparse.py
@@ -1,8 +1,20 @@
+"""Define partial Python code Parser used by editor and hyperparser.
+
+Instances of StringTranslatePseudoMapping are used with str.translate.
+
+The following bound search and match functions are defined:
+_synchre - start of popular statement;
+_junkre - whitespace or comment line;
+_match_stringre: string, possibly without closer;
+_itemre - line that may have bracket structure start;
+_closere - line that must be followed by dedent.
+_chew_ordinaryre - non-special characters.
+"""
from collections.abc import Mapping
import re
import sys
-# Reason last stmt is continued (or C_NONE if it's not).
+# Reason last statement 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)
@@ -10,7 +22,7 @@ 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.
+# Find what looks like the start of a popular statement.
_synchre = re.compile(r"""
^
@@ -70,7 +82,7 @@ _itemre = re.compile(r"""
[^\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.
+# Match start of statements that should be followed by a dedent.
_closere = re.compile(r"""
\s*
@@ -146,19 +158,20 @@ class Parser:
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):
+ """
+ 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.
+ """
str, pos = self.str, None
if not is_char_in_string:
@@ -173,7 +186,7 @@ class Parser:
i = str.rfind(":\n", 0, limit)
if i < 0:
break
- i = str.rfind('\n', 0, i) + 1 # start of colon line
+ i = str.rfind('\n', 0, i) + 1 # start of colon line (-1+1=0)
m = _synchre(str, i, limit)
if m and not is_char_in_string(m.start()):
pos = m.start()
@@ -206,10 +219,11 @@ class Parser:
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):
+ """ Throw away the start of the string.
+
+ Intended to be called with the result of find_good_parse_start().
+ """
assert lo == 0 or self.str[lo-1] == '\n'
if lo > 0:
self.str = self.str[lo:]
@@ -224,11 +238,13 @@ class Parser:
_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):
+ """Find the line numbers of non-continuation lines.
+
+ As quickly as humanly possible <wink>, find the line numbers (0-
+ based) of the non-continuation lines.
+ Creates self.{goodlines, continuation}.
+ """
if self.study_level >= 1:
return
self.study_level = 1
@@ -244,8 +260,8 @@ class Parser:
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
+ # 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
@@ -360,24 +376,25 @@ class Parser:
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):
+ """
+ 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), (0, 1), (2, 0), (2, 1),
+ (4, 0)). Strings and comments are treated as brackets, for
+ the matter.
+ self.lastch
+ last interesting character before optional trailing comment
+ self.lastopenbracketpos
+ if continuation is C_BRACKET, index of last open bracket
+ """
if self.study_level >= 2:
return
self._study1()
@@ -385,11 +402,11 @@ class Parser:
# 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
+ i = len(goodlines) - 1 # Index of newest line.
+ p = len(str) # End of goodlines[i]
while i:
assert p
- # p is the index of the stmt at line number goodlines[i].
+ # Make p be 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]):
@@ -483,10 +500,11 @@ class Parser:
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):
+ """Return number of spaces the next line should be indented.
+
+ Line continuation must be C_BRACKET.
+ """
self._study2()
assert self.continuation == C_BRACKET
j = self.lastopenbracketpos
@@ -513,20 +531,22 @@ class Parser:
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):
+ """Return number of physical lines in last stmt.
+
+ The statement doesn't have to be an interesting statement. This is
+ intended to be called when continuation is C_BACKSLASH.
+ """
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):
+ """Return number of spaces the next line should be indented.
+
+ Line continuation must be C_BACKSLASH. Also assume that the new
+ line is the first one following the initial line of the stmt.
+ """
self._study2()
assert self.continuation == C_BACKSLASH
str = self.str
@@ -551,6 +571,8 @@ class Parser:
elif ch == '"' or ch == "'":
i = _match_stringre(str, i, endpos).end()
elif ch == '#':
+ # This line is unreachable because the # makes a comment of
+ # everything after it.
break
elif level == 0 and ch == '=' and \
(i == 0 or str[i-1] not in "=<>!") and \
@@ -576,10 +598,10 @@ class Parser:
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):
+ """Return the leading whitespace on the initial line of the last
+ interesting stmt.
+ """
self._study2()
i, n = self.stmt_start, self.stmt_end
j = i
@@ -588,30 +610,37 @@ class Parser:
j = j + 1
return str[i:j]
- # Did the last interesting stmt open a block?
-
def is_block_opener(self):
+ "Return True if the last interesting statemtent opens a block."
self._study2()
return self.lastch == ':'
- # Did the last interesting stmt close a block?
-
def is_block_closer(self):
+ "Return True if the last interesting statement closes a block."
self._study2()
return _closere(self.str, self.stmt_start) is not None
- # index of last open bracket ({[, or None if none
+ # XXX - is this used?
lastopenbracketpos = None
def get_last_open_bracket_pos(self):
+ "Return index of last open bracket or None."
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
+ # XXX - is this used?
stmt_bracketing = None
def get_last_stmt_bracketing(self):
+ """Return a tuple of the structure of the bracketing of the last
+ interesting statement.
+
+ Tuple is in the format defined in _study2().
+ """
self._study2()
return self.stmt_bracketing
+
+
+if __name__ == '__main__': #pragma: nocover
+ import unittest
+ unittest.main('idlelib.idle_test.test_pyparse', verbosity=2)