annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/pyparse.py @ 69:33d812a61356

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 17:55:14 -0400
parents
children
rev   line source
jpayne@69 1 """Define partial Python code Parser used by editor and hyperparser.
jpayne@69 2
jpayne@69 3 Instances of ParseMap are used with str.translate.
jpayne@69 4
jpayne@69 5 The following bound search and match functions are defined:
jpayne@69 6 _synchre - start of popular statement;
jpayne@69 7 _junkre - whitespace or comment line;
jpayne@69 8 _match_stringre: string, possibly without closer;
jpayne@69 9 _itemre - line that may have bracket structure start;
jpayne@69 10 _closere - line that must be followed by dedent.
jpayne@69 11 _chew_ordinaryre - non-special characters.
jpayne@69 12 """
jpayne@69 13 import re
jpayne@69 14
jpayne@69 15 # Reason last statement is continued (or C_NONE if it's not).
jpayne@69 16 (C_NONE, C_BACKSLASH, C_STRING_FIRST_LINE,
jpayne@69 17 C_STRING_NEXT_LINES, C_BRACKET) = range(5)
jpayne@69 18
jpayne@69 19 # Find what looks like the start of a popular statement.
jpayne@69 20
jpayne@69 21 _synchre = re.compile(r"""
jpayne@69 22 ^
jpayne@69 23 [ \t]*
jpayne@69 24 (?: while
jpayne@69 25 | else
jpayne@69 26 | def
jpayne@69 27 | return
jpayne@69 28 | assert
jpayne@69 29 | break
jpayne@69 30 | class
jpayne@69 31 | continue
jpayne@69 32 | elif
jpayne@69 33 | try
jpayne@69 34 | except
jpayne@69 35 | raise
jpayne@69 36 | import
jpayne@69 37 | yield
jpayne@69 38 )
jpayne@69 39 \b
jpayne@69 40 """, re.VERBOSE | re.MULTILINE).search
jpayne@69 41
jpayne@69 42 # Match blank line or non-indenting comment line.
jpayne@69 43
jpayne@69 44 _junkre = re.compile(r"""
jpayne@69 45 [ \t]*
jpayne@69 46 (?: \# \S .* )?
jpayne@69 47 \n
jpayne@69 48 """, re.VERBOSE).match
jpayne@69 49
jpayne@69 50 # Match any flavor of string; the terminating quote is optional
jpayne@69 51 # so that we're robust in the face of incomplete program text.
jpayne@69 52
jpayne@69 53 _match_stringre = re.compile(r"""
jpayne@69 54 \""" [^"\\]* (?:
jpayne@69 55 (?: \\. | "(?!"") )
jpayne@69 56 [^"\\]*
jpayne@69 57 )*
jpayne@69 58 (?: \""" )?
jpayne@69 59
jpayne@69 60 | " [^"\\\n]* (?: \\. [^"\\\n]* )* "?
jpayne@69 61
jpayne@69 62 | ''' [^'\\]* (?:
jpayne@69 63 (?: \\. | '(?!'') )
jpayne@69 64 [^'\\]*
jpayne@69 65 )*
jpayne@69 66 (?: ''' )?
jpayne@69 67
jpayne@69 68 | ' [^'\\\n]* (?: \\. [^'\\\n]* )* '?
jpayne@69 69 """, re.VERBOSE | re.DOTALL).match
jpayne@69 70
jpayne@69 71 # Match a line that starts with something interesting;
jpayne@69 72 # used to find the first item of a bracket structure.
jpayne@69 73
jpayne@69 74 _itemre = re.compile(r"""
jpayne@69 75 [ \t]*
jpayne@69 76 [^\s#\\] # if we match, m.end()-1 is the interesting char
jpayne@69 77 """, re.VERBOSE).match
jpayne@69 78
jpayne@69 79 # Match start of statements that should be followed by a dedent.
jpayne@69 80
jpayne@69 81 _closere = re.compile(r"""
jpayne@69 82 \s*
jpayne@69 83 (?: return
jpayne@69 84 | break
jpayne@69 85 | continue
jpayne@69 86 | raise
jpayne@69 87 | pass
jpayne@69 88 )
jpayne@69 89 \b
jpayne@69 90 """, re.VERBOSE).match
jpayne@69 91
jpayne@69 92 # Chew up non-special chars as quickly as possible. If match is
jpayne@69 93 # successful, m.end() less 1 is the index of the last boring char
jpayne@69 94 # matched. If match is unsuccessful, the string starts with an
jpayne@69 95 # interesting char.
jpayne@69 96
jpayne@69 97 _chew_ordinaryre = re.compile(r"""
jpayne@69 98 [^[\](){}#'"\\]+
jpayne@69 99 """, re.VERBOSE).match
jpayne@69 100
jpayne@69 101
jpayne@69 102 class ParseMap(dict):
jpayne@69 103 r"""Dict subclass that maps anything not in dict to 'x'.
jpayne@69 104
jpayne@69 105 This is designed to be used with str.translate in study1.
jpayne@69 106 Anything not specifically mapped otherwise becomes 'x'.
jpayne@69 107 Example: replace everything except whitespace with 'x'.
jpayne@69 108
jpayne@69 109 >>> keepwhite = ParseMap((ord(c), ord(c)) for c in ' \t\n\r')
jpayne@69 110 >>> "a + b\tc\nd".translate(keepwhite)
jpayne@69 111 'x x x\tx\nx'
jpayne@69 112 """
jpayne@69 113 # Calling this triples access time; see bpo-32940
jpayne@69 114 def __missing__(self, key):
jpayne@69 115 return 120 # ord('x')
jpayne@69 116
jpayne@69 117
jpayne@69 118 # Map all ascii to 120 to avoid __missing__ call, then replace some.
jpayne@69 119 trans = ParseMap.fromkeys(range(128), 120)
jpayne@69 120 trans.update((ord(c), ord('(')) for c in "({[") # open brackets => '(';
jpayne@69 121 trans.update((ord(c), ord(')')) for c in ")}]") # close brackets => ')'.
jpayne@69 122 trans.update((ord(c), ord(c)) for c in "\"'\\\n#") # Keep these.
jpayne@69 123
jpayne@69 124
jpayne@69 125 class Parser:
jpayne@69 126
jpayne@69 127 def __init__(self, indentwidth, tabwidth):
jpayne@69 128 self.indentwidth = indentwidth
jpayne@69 129 self.tabwidth = tabwidth
jpayne@69 130
jpayne@69 131 def set_code(self, s):
jpayne@69 132 assert len(s) == 0 or s[-1] == '\n'
jpayne@69 133 self.code = s
jpayne@69 134 self.study_level = 0
jpayne@69 135
jpayne@69 136 def find_good_parse_start(self, is_char_in_string=None,
jpayne@69 137 _synchre=_synchre):
jpayne@69 138 """
jpayne@69 139 Return index of a good place to begin parsing, as close to the
jpayne@69 140 end of the string as possible. This will be the start of some
jpayne@69 141 popular stmt like "if" or "def". Return None if none found:
jpayne@69 142 the caller should pass more prior context then, if possible, or
jpayne@69 143 if not (the entire program text up until the point of interest
jpayne@69 144 has already been tried) pass 0 to set_lo().
jpayne@69 145
jpayne@69 146 This will be reliable iff given a reliable is_char_in_string()
jpayne@69 147 function, meaning that when it says "no", it's absolutely
jpayne@69 148 guaranteed that the char is not in a string.
jpayne@69 149 """
jpayne@69 150 code, pos = self.code, None
jpayne@69 151
jpayne@69 152 if not is_char_in_string:
jpayne@69 153 # no clue -- make the caller pass everything
jpayne@69 154 return None
jpayne@69 155
jpayne@69 156 # Peek back from the end for a good place to start,
jpayne@69 157 # but don't try too often; pos will be left None, or
jpayne@69 158 # bumped to a legitimate synch point.
jpayne@69 159 limit = len(code)
jpayne@69 160 for tries in range(5):
jpayne@69 161 i = code.rfind(":\n", 0, limit)
jpayne@69 162 if i < 0:
jpayne@69 163 break
jpayne@69 164 i = code.rfind('\n', 0, i) + 1 # start of colon line (-1+1=0)
jpayne@69 165 m = _synchre(code, i, limit)
jpayne@69 166 if m and not is_char_in_string(m.start()):
jpayne@69 167 pos = m.start()
jpayne@69 168 break
jpayne@69 169 limit = i
jpayne@69 170 if pos is None:
jpayne@69 171 # Nothing looks like a block-opener, or stuff does
jpayne@69 172 # but is_char_in_string keeps returning true; most likely
jpayne@69 173 # we're in or near a giant string, the colorizer hasn't
jpayne@69 174 # caught up enough to be helpful, or there simply *aren't*
jpayne@69 175 # any interesting stmts. In any of these cases we're
jpayne@69 176 # going to have to parse the whole thing to be sure, so
jpayne@69 177 # give it one last try from the start, but stop wasting
jpayne@69 178 # time here regardless of the outcome.
jpayne@69 179 m = _synchre(code)
jpayne@69 180 if m and not is_char_in_string(m.start()):
jpayne@69 181 pos = m.start()
jpayne@69 182 return pos
jpayne@69 183
jpayne@69 184 # Peeking back worked; look forward until _synchre no longer
jpayne@69 185 # matches.
jpayne@69 186 i = pos + 1
jpayne@69 187 while 1:
jpayne@69 188 m = _synchre(code, i)
jpayne@69 189 if m:
jpayne@69 190 s, i = m.span()
jpayne@69 191 if not is_char_in_string(s):
jpayne@69 192 pos = s
jpayne@69 193 else:
jpayne@69 194 break
jpayne@69 195 return pos
jpayne@69 196
jpayne@69 197 def set_lo(self, lo):
jpayne@69 198 """ Throw away the start of the string.
jpayne@69 199
jpayne@69 200 Intended to be called with the result of find_good_parse_start().
jpayne@69 201 """
jpayne@69 202 assert lo == 0 or self.code[lo-1] == '\n'
jpayne@69 203 if lo > 0:
jpayne@69 204 self.code = self.code[lo:]
jpayne@69 205
jpayne@69 206 def _study1(self):
jpayne@69 207 """Find the line numbers of non-continuation lines.
jpayne@69 208
jpayne@69 209 As quickly as humanly possible <wink>, find the line numbers (0-
jpayne@69 210 based) of the non-continuation lines.
jpayne@69 211 Creates self.{goodlines, continuation}.
jpayne@69 212 """
jpayne@69 213 if self.study_level >= 1:
jpayne@69 214 return
jpayne@69 215 self.study_level = 1
jpayne@69 216
jpayne@69 217 # Map all uninteresting characters to "x", all open brackets
jpayne@69 218 # to "(", all close brackets to ")", then collapse runs of
jpayne@69 219 # uninteresting characters. This can cut the number of chars
jpayne@69 220 # by a factor of 10-40, and so greatly speed the following loop.
jpayne@69 221 code = self.code
jpayne@69 222 code = code.translate(trans)
jpayne@69 223 code = code.replace('xxxxxxxx', 'x')
jpayne@69 224 code = code.replace('xxxx', 'x')
jpayne@69 225 code = code.replace('xx', 'x')
jpayne@69 226 code = code.replace('xx', 'x')
jpayne@69 227 code = code.replace('\nx', '\n')
jpayne@69 228 # Replacing x\n with \n would be incorrect because
jpayne@69 229 # x may be preceded by a backslash.
jpayne@69 230
jpayne@69 231 # March over the squashed version of the program, accumulating
jpayne@69 232 # the line numbers of non-continued stmts, and determining
jpayne@69 233 # whether & why the last stmt is a continuation.
jpayne@69 234 continuation = C_NONE
jpayne@69 235 level = lno = 0 # level is nesting level; lno is line number
jpayne@69 236 self.goodlines = goodlines = [0]
jpayne@69 237 push_good = goodlines.append
jpayne@69 238 i, n = 0, len(code)
jpayne@69 239 while i < n:
jpayne@69 240 ch = code[i]
jpayne@69 241 i = i+1
jpayne@69 242
jpayne@69 243 # cases are checked in decreasing order of frequency
jpayne@69 244 if ch == 'x':
jpayne@69 245 continue
jpayne@69 246
jpayne@69 247 if ch == '\n':
jpayne@69 248 lno = lno + 1
jpayne@69 249 if level == 0:
jpayne@69 250 push_good(lno)
jpayne@69 251 # else we're in an unclosed bracket structure
jpayne@69 252 continue
jpayne@69 253
jpayne@69 254 if ch == '(':
jpayne@69 255 level = level + 1
jpayne@69 256 continue
jpayne@69 257
jpayne@69 258 if ch == ')':
jpayne@69 259 if level:
jpayne@69 260 level = level - 1
jpayne@69 261 # else the program is invalid, but we can't complain
jpayne@69 262 continue
jpayne@69 263
jpayne@69 264 if ch == '"' or ch == "'":
jpayne@69 265 # consume the string
jpayne@69 266 quote = ch
jpayne@69 267 if code[i-1:i+2] == quote * 3:
jpayne@69 268 quote = quote * 3
jpayne@69 269 firstlno = lno
jpayne@69 270 w = len(quote) - 1
jpayne@69 271 i = i+w
jpayne@69 272 while i < n:
jpayne@69 273 ch = code[i]
jpayne@69 274 i = i+1
jpayne@69 275
jpayne@69 276 if ch == 'x':
jpayne@69 277 continue
jpayne@69 278
jpayne@69 279 if code[i-1:i+w] == quote:
jpayne@69 280 i = i+w
jpayne@69 281 break
jpayne@69 282
jpayne@69 283 if ch == '\n':
jpayne@69 284 lno = lno + 1
jpayne@69 285 if w == 0:
jpayne@69 286 # unterminated single-quoted string
jpayne@69 287 if level == 0:
jpayne@69 288 push_good(lno)
jpayne@69 289 break
jpayne@69 290 continue
jpayne@69 291
jpayne@69 292 if ch == '\\':
jpayne@69 293 assert i < n
jpayne@69 294 if code[i] == '\n':
jpayne@69 295 lno = lno + 1
jpayne@69 296 i = i+1
jpayne@69 297 continue
jpayne@69 298
jpayne@69 299 # else comment char or paren inside string
jpayne@69 300
jpayne@69 301 else:
jpayne@69 302 # didn't break out of the loop, so we're still
jpayne@69 303 # inside a string
jpayne@69 304 if (lno - 1) == firstlno:
jpayne@69 305 # before the previous \n in code, we were in the first
jpayne@69 306 # line of the string
jpayne@69 307 continuation = C_STRING_FIRST_LINE
jpayne@69 308 else:
jpayne@69 309 continuation = C_STRING_NEXT_LINES
jpayne@69 310 continue # with outer loop
jpayne@69 311
jpayne@69 312 if ch == '#':
jpayne@69 313 # consume the comment
jpayne@69 314 i = code.find('\n', i)
jpayne@69 315 assert i >= 0
jpayne@69 316 continue
jpayne@69 317
jpayne@69 318 assert ch == '\\'
jpayne@69 319 assert i < n
jpayne@69 320 if code[i] == '\n':
jpayne@69 321 lno = lno + 1
jpayne@69 322 if i+1 == n:
jpayne@69 323 continuation = C_BACKSLASH
jpayne@69 324 i = i+1
jpayne@69 325
jpayne@69 326 # The last stmt may be continued for all 3 reasons.
jpayne@69 327 # String continuation takes precedence over bracket
jpayne@69 328 # continuation, which beats backslash continuation.
jpayne@69 329 if (continuation != C_STRING_FIRST_LINE
jpayne@69 330 and continuation != C_STRING_NEXT_LINES and level > 0):
jpayne@69 331 continuation = C_BRACKET
jpayne@69 332 self.continuation = continuation
jpayne@69 333
jpayne@69 334 # Push the final line number as a sentinel value, regardless of
jpayne@69 335 # whether it's continued.
jpayne@69 336 assert (continuation == C_NONE) == (goodlines[-1] == lno)
jpayne@69 337 if goodlines[-1] != lno:
jpayne@69 338 push_good(lno)
jpayne@69 339
jpayne@69 340 def get_continuation_type(self):
jpayne@69 341 self._study1()
jpayne@69 342 return self.continuation
jpayne@69 343
jpayne@69 344 def _study2(self):
jpayne@69 345 """
jpayne@69 346 study1 was sufficient to determine the continuation status,
jpayne@69 347 but doing more requires looking at every character. study2
jpayne@69 348 does this for the last interesting statement in the block.
jpayne@69 349 Creates:
jpayne@69 350 self.stmt_start, stmt_end
jpayne@69 351 slice indices of last interesting stmt
jpayne@69 352 self.stmt_bracketing
jpayne@69 353 the bracketing structure of the last interesting stmt; for
jpayne@69 354 example, for the statement "say(boo) or die",
jpayne@69 355 stmt_bracketing will be ((0, 0), (0, 1), (2, 0), (2, 1),
jpayne@69 356 (4, 0)). Strings and comments are treated as brackets, for
jpayne@69 357 the matter.
jpayne@69 358 self.lastch
jpayne@69 359 last interesting character before optional trailing comment
jpayne@69 360 self.lastopenbracketpos
jpayne@69 361 if continuation is C_BRACKET, index of last open bracket
jpayne@69 362 """
jpayne@69 363 if self.study_level >= 2:
jpayne@69 364 return
jpayne@69 365 self._study1()
jpayne@69 366 self.study_level = 2
jpayne@69 367
jpayne@69 368 # Set p and q to slice indices of last interesting stmt.
jpayne@69 369 code, goodlines = self.code, self.goodlines
jpayne@69 370 i = len(goodlines) - 1 # Index of newest line.
jpayne@69 371 p = len(code) # End of goodlines[i]
jpayne@69 372 while i:
jpayne@69 373 assert p
jpayne@69 374 # Make p be the index of the stmt at line number goodlines[i].
jpayne@69 375 # Move p back to the stmt at line number goodlines[i-1].
jpayne@69 376 q = p
jpayne@69 377 for nothing in range(goodlines[i-1], goodlines[i]):
jpayne@69 378 # tricky: sets p to 0 if no preceding newline
jpayne@69 379 p = code.rfind('\n', 0, p-1) + 1
jpayne@69 380 # The stmt code[p:q] isn't a continuation, but may be blank
jpayne@69 381 # or a non-indenting comment line.
jpayne@69 382 if _junkre(code, p):
jpayne@69 383 i = i-1
jpayne@69 384 else:
jpayne@69 385 break
jpayne@69 386 if i == 0:
jpayne@69 387 # nothing but junk!
jpayne@69 388 assert p == 0
jpayne@69 389 q = p
jpayne@69 390 self.stmt_start, self.stmt_end = p, q
jpayne@69 391
jpayne@69 392 # Analyze this stmt, to find the last open bracket (if any)
jpayne@69 393 # and last interesting character (if any).
jpayne@69 394 lastch = ""
jpayne@69 395 stack = [] # stack of open bracket indices
jpayne@69 396 push_stack = stack.append
jpayne@69 397 bracketing = [(p, 0)]
jpayne@69 398 while p < q:
jpayne@69 399 # suck up all except ()[]{}'"#\\
jpayne@69 400 m = _chew_ordinaryre(code, p, q)
jpayne@69 401 if m:
jpayne@69 402 # we skipped at least one boring char
jpayne@69 403 newp = m.end()
jpayne@69 404 # back up over totally boring whitespace
jpayne@69 405 i = newp - 1 # index of last boring char
jpayne@69 406 while i >= p and code[i] in " \t\n":
jpayne@69 407 i = i-1
jpayne@69 408 if i >= p:
jpayne@69 409 lastch = code[i]
jpayne@69 410 p = newp
jpayne@69 411 if p >= q:
jpayne@69 412 break
jpayne@69 413
jpayne@69 414 ch = code[p]
jpayne@69 415
jpayne@69 416 if ch in "([{":
jpayne@69 417 push_stack(p)
jpayne@69 418 bracketing.append((p, len(stack)))
jpayne@69 419 lastch = ch
jpayne@69 420 p = p+1
jpayne@69 421 continue
jpayne@69 422
jpayne@69 423 if ch in ")]}":
jpayne@69 424 if stack:
jpayne@69 425 del stack[-1]
jpayne@69 426 lastch = ch
jpayne@69 427 p = p+1
jpayne@69 428 bracketing.append((p, len(stack)))
jpayne@69 429 continue
jpayne@69 430
jpayne@69 431 if ch == '"' or ch == "'":
jpayne@69 432 # consume string
jpayne@69 433 # Note that study1 did this with a Python loop, but
jpayne@69 434 # we use a regexp here; the reason is speed in both
jpayne@69 435 # cases; the string may be huge, but study1 pre-squashed
jpayne@69 436 # strings to a couple of characters per line. study1
jpayne@69 437 # also needed to keep track of newlines, and we don't
jpayne@69 438 # have to.
jpayne@69 439 bracketing.append((p, len(stack)+1))
jpayne@69 440 lastch = ch
jpayne@69 441 p = _match_stringre(code, p, q).end()
jpayne@69 442 bracketing.append((p, len(stack)))
jpayne@69 443 continue
jpayne@69 444
jpayne@69 445 if ch == '#':
jpayne@69 446 # consume comment and trailing newline
jpayne@69 447 bracketing.append((p, len(stack)+1))
jpayne@69 448 p = code.find('\n', p, q) + 1
jpayne@69 449 assert p > 0
jpayne@69 450 bracketing.append((p, len(stack)))
jpayne@69 451 continue
jpayne@69 452
jpayne@69 453 assert ch == '\\'
jpayne@69 454 p = p+1 # beyond backslash
jpayne@69 455 assert p < q
jpayne@69 456 if code[p] != '\n':
jpayne@69 457 # the program is invalid, but can't complain
jpayne@69 458 lastch = ch + code[p]
jpayne@69 459 p = p+1 # beyond escaped char
jpayne@69 460
jpayne@69 461 # end while p < q:
jpayne@69 462
jpayne@69 463 self.lastch = lastch
jpayne@69 464 self.lastopenbracketpos = stack[-1] if stack else None
jpayne@69 465 self.stmt_bracketing = tuple(bracketing)
jpayne@69 466
jpayne@69 467 def compute_bracket_indent(self):
jpayne@69 468 """Return number of spaces the next line should be indented.
jpayne@69 469
jpayne@69 470 Line continuation must be C_BRACKET.
jpayne@69 471 """
jpayne@69 472 self._study2()
jpayne@69 473 assert self.continuation == C_BRACKET
jpayne@69 474 j = self.lastopenbracketpos
jpayne@69 475 code = self.code
jpayne@69 476 n = len(code)
jpayne@69 477 origi = i = code.rfind('\n', 0, j) + 1
jpayne@69 478 j = j+1 # one beyond open bracket
jpayne@69 479 # find first list item; set i to start of its line
jpayne@69 480 while j < n:
jpayne@69 481 m = _itemre(code, j)
jpayne@69 482 if m:
jpayne@69 483 j = m.end() - 1 # index of first interesting char
jpayne@69 484 extra = 0
jpayne@69 485 break
jpayne@69 486 else:
jpayne@69 487 # this line is junk; advance to next line
jpayne@69 488 i = j = code.find('\n', j) + 1
jpayne@69 489 else:
jpayne@69 490 # nothing interesting follows the bracket;
jpayne@69 491 # reproduce the bracket line's indentation + a level
jpayne@69 492 j = i = origi
jpayne@69 493 while code[j] in " \t":
jpayne@69 494 j = j+1
jpayne@69 495 extra = self.indentwidth
jpayne@69 496 return len(code[i:j].expandtabs(self.tabwidth)) + extra
jpayne@69 497
jpayne@69 498 def get_num_lines_in_stmt(self):
jpayne@69 499 """Return number of physical lines in last stmt.
jpayne@69 500
jpayne@69 501 The statement doesn't have to be an interesting statement. This is
jpayne@69 502 intended to be called when continuation is C_BACKSLASH.
jpayne@69 503 """
jpayne@69 504 self._study1()
jpayne@69 505 goodlines = self.goodlines
jpayne@69 506 return goodlines[-1] - goodlines[-2]
jpayne@69 507
jpayne@69 508 def compute_backslash_indent(self):
jpayne@69 509 """Return number of spaces the next line should be indented.
jpayne@69 510
jpayne@69 511 Line continuation must be C_BACKSLASH. Also assume that the new
jpayne@69 512 line is the first one following the initial line of the stmt.
jpayne@69 513 """
jpayne@69 514 self._study2()
jpayne@69 515 assert self.continuation == C_BACKSLASH
jpayne@69 516 code = self.code
jpayne@69 517 i = self.stmt_start
jpayne@69 518 while code[i] in " \t":
jpayne@69 519 i = i+1
jpayne@69 520 startpos = i
jpayne@69 521
jpayne@69 522 # See whether the initial line starts an assignment stmt; i.e.,
jpayne@69 523 # look for an = operator
jpayne@69 524 endpos = code.find('\n', startpos) + 1
jpayne@69 525 found = level = 0
jpayne@69 526 while i < endpos:
jpayne@69 527 ch = code[i]
jpayne@69 528 if ch in "([{":
jpayne@69 529 level = level + 1
jpayne@69 530 i = i+1
jpayne@69 531 elif ch in ")]}":
jpayne@69 532 if level:
jpayne@69 533 level = level - 1
jpayne@69 534 i = i+1
jpayne@69 535 elif ch == '"' or ch == "'":
jpayne@69 536 i = _match_stringre(code, i, endpos).end()
jpayne@69 537 elif ch == '#':
jpayne@69 538 # This line is unreachable because the # makes a comment of
jpayne@69 539 # everything after it.
jpayne@69 540 break
jpayne@69 541 elif level == 0 and ch == '=' and \
jpayne@69 542 (i == 0 or code[i-1] not in "=<>!") and \
jpayne@69 543 code[i+1] != '=':
jpayne@69 544 found = 1
jpayne@69 545 break
jpayne@69 546 else:
jpayne@69 547 i = i+1
jpayne@69 548
jpayne@69 549 if found:
jpayne@69 550 # found a legit =, but it may be the last interesting
jpayne@69 551 # thing on the line
jpayne@69 552 i = i+1 # move beyond the =
jpayne@69 553 found = re.match(r"\s*\\", code[i:endpos]) is None
jpayne@69 554
jpayne@69 555 if not found:
jpayne@69 556 # oh well ... settle for moving beyond the first chunk
jpayne@69 557 # of non-whitespace chars
jpayne@69 558 i = startpos
jpayne@69 559 while code[i] not in " \t\n":
jpayne@69 560 i = i+1
jpayne@69 561
jpayne@69 562 return len(code[self.stmt_start:i].expandtabs(\
jpayne@69 563 self.tabwidth)) + 1
jpayne@69 564
jpayne@69 565 def get_base_indent_string(self):
jpayne@69 566 """Return the leading whitespace on the initial line of the last
jpayne@69 567 interesting stmt.
jpayne@69 568 """
jpayne@69 569 self._study2()
jpayne@69 570 i, n = self.stmt_start, self.stmt_end
jpayne@69 571 j = i
jpayne@69 572 code = self.code
jpayne@69 573 while j < n and code[j] in " \t":
jpayne@69 574 j = j + 1
jpayne@69 575 return code[i:j]
jpayne@69 576
jpayne@69 577 def is_block_opener(self):
jpayne@69 578 "Return True if the last interesting statement opens a block."
jpayne@69 579 self._study2()
jpayne@69 580 return self.lastch == ':'
jpayne@69 581
jpayne@69 582 def is_block_closer(self):
jpayne@69 583 "Return True if the last interesting statement closes a block."
jpayne@69 584 self._study2()
jpayne@69 585 return _closere(self.code, self.stmt_start) is not None
jpayne@69 586
jpayne@69 587 def get_last_stmt_bracketing(self):
jpayne@69 588 """Return bracketing structure of the last interesting statement.
jpayne@69 589
jpayne@69 590 The returned tuple is in the format defined in _study2().
jpayne@69 591 """
jpayne@69 592 self._study2()
jpayne@69 593 return self.stmt_bracketing
jpayne@69 594
jpayne@69 595
jpayne@69 596 if __name__ == '__main__':
jpayne@69 597 from unittest import main
jpayne@69 598 main('idlelib.idle_test.test_pyparse', verbosity=2)