blob: 93607dc5bcd0099073fc673484f1ab48f51850d3 [file] [log] [blame]
Miss Islington (bot)c59bc982018-02-21 20:09:39 -08001"""Define partial Python code Parser used by editor and hyperparser.
2
3Instances of StringTranslatePseudoMapping are used with str.translate.
4
5The following bound search and match functions are defined:
6_synchre - start of popular statement;
7_junkre - whitespace or comment line;
8_match_stringre: string, possibly without closer;
9_itemre - line that may have bracket structure start;
10_closere - line that must be followed by dedent.
11_chew_ordinaryre - non-special characters.
12"""
Serhiy Storchaka2e576f52017-04-24 09:05:00 +030013from collections.abc import Mapping
David Scherer7aced172000-08-15 01:13:23 +000014import re
15import sys
16
Miss Islington (bot)c59bc982018-02-21 20:09:39 -080017# Reason last statement is continued (or C_NONE if it's not).
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +000018(C_NONE, C_BACKSLASH, C_STRING_FIRST_LINE,
19 C_STRING_NEXT_LINES, C_BRACKET) = range(5)
David Scherer7aced172000-08-15 01:13:23 +000020
Miss Islington (bot)c59bc982018-02-21 20:09:39 -080021# Find what looks like the start of a popular statement.
David Scherer7aced172000-08-15 01:13:23 +000022
23_synchre = re.compile(r"""
24 ^
25 [ \t]*
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000026 (?: while
David Scherer7aced172000-08-15 01:13:23 +000027 | else
28 | def
29 | return
30 | assert
31 | break
32 | class
33 | continue
34 | elif
35 | try
36 | except
37 | raise
38 | import
Kurt B. Kaiser752e4d52001-07-14 04:59:24 +000039 | yield
David Scherer7aced172000-08-15 01:13:23 +000040 )
41 \b
42""", re.VERBOSE | re.MULTILINE).search
43
44# Match blank line or non-indenting comment line.
45
46_junkre = re.compile(r"""
47 [ \t]*
48 (?: \# \S .* )?
49 \n
50""", re.VERBOSE).match
51
52# Match any flavor of string; the terminating quote is optional
53# so that we're robust in the face of incomplete program text.
54
55_match_stringre = re.compile(r"""
56 \""" [^"\\]* (?:
57 (?: \\. | "(?!"") )
58 [^"\\]*
59 )*
60 (?: \""" )?
61
62| " [^"\\\n]* (?: \\. [^"\\\n]* )* "?
63
64| ''' [^'\\]* (?:
65 (?: \\. | '(?!'') )
66 [^'\\]*
67 )*
68 (?: ''' )?
69
70| ' [^'\\\n]* (?: \\. [^'\\\n]* )* '?
71""", re.VERBOSE | re.DOTALL).match
72
73# Match a line that starts with something interesting;
74# used to find the first item of a bracket structure.
75
76_itemre = re.compile(r"""
77 [ \t]*
78 [^\s#\\] # if we match, m.end()-1 is the interesting char
79""", re.VERBOSE).match
80
Miss Islington (bot)c59bc982018-02-21 20:09:39 -080081# Match start of statements that should be followed by a dedent.
David Scherer7aced172000-08-15 01:13:23 +000082
83_closere = re.compile(r"""
84 \s*
85 (?: return
86 | break
87 | continue
88 | raise
89 | pass
90 )
91 \b
92""", re.VERBOSE).match
93
94# Chew up non-special chars as quickly as possible. If match is
95# successful, m.end() less 1 is the index of the last boring char
96# matched. If match is unsuccessful, the string starts with an
97# interesting char.
98
99_chew_ordinaryre = re.compile(r"""
100 [^[\](){}#'"\\]+
101""", re.VERBOSE).match
102
David Scherer7aced172000-08-15 01:13:23 +0000103
Tal Einat9b7f9e62014-07-16 16:33:36 +0300104class StringTranslatePseudoMapping(Mapping):
105 r"""Utility class to be used with str.translate()
106
107 This Mapping class wraps a given dict. When a value for a key is
108 requested via __getitem__() or get(), the key is looked up in the
109 given dict. If found there, the value from the dict is returned.
110 Otherwise, the default value given upon initialization is returned.
111
112 This allows using str.translate() to make some replacements, and to
113 replace all characters for which no replacement was specified with
114 a given character instead of leaving them as-is.
115
116 For example, to replace everything except whitespace with 'x':
117
118 >>> whitespace_chars = ' \t\n\r'
119 >>> preserve_dict = {ord(c): ord(c) for c in whitespace_chars}
120 >>> mapping = StringTranslatePseudoMapping(preserve_dict, ord('x'))
121 >>> text = "a + b\tc\nd"
122 >>> text.translate(mapping)
123 'x x x\tx\nx'
124 """
125 def __init__(self, non_defaults, default_value):
126 self._non_defaults = non_defaults
127 self._default_value = default_value
128
129 def _get(key, _get=non_defaults.get, _default=default_value):
130 return _get(key, _default)
131 self._get = _get
132
133 def __getitem__(self, item):
134 return self._get(item)
135
136 def __len__(self):
137 return len(self._non_defaults)
138
139 def __iter__(self):
140 return iter(self._non_defaults)
141
142 def get(self, key, default=None):
143 return self._get(key)
144
David Scherer7aced172000-08-15 01:13:23 +0000145
146class Parser:
147
148 def __init__(self, indentwidth, tabwidth):
149 self.indentwidth = indentwidth
150 self.tabwidth = tabwidth
151
Walter Dörwald5de48bd2007-06-11 21:38:39 +0000152 def set_str(self, s):
153 assert len(s) == 0 or s[-1] == '\n'
Walter Dörwald5de48bd2007-06-11 21:38:39 +0000154 self.str = s
David Scherer7aced172000-08-15 01:13:23 +0000155 self.study_level = 0
156
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000157 def find_good_parse_start(self, is_char_in_string=None,
David Scherer7aced172000-08-15 01:13:23 +0000158 _synchre=_synchre):
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800159 """
160 Return index of a good place to begin parsing, as close to the
161 end of the string as possible. This will be the start of some
162 popular stmt like "if" or "def". Return None if none found:
163 the caller should pass more prior context then, if possible, or
164 if not (the entire program text up until the point of interest
165 has already been tried) pass 0 to set_lo().
166
167 This will be reliable iff given a reliable is_char_in_string()
168 function, meaning that when it says "no", it's absolutely
169 guaranteed that the char is not in a string.
170 """
David Scherer7aced172000-08-15 01:13:23 +0000171 str, pos = self.str, None
David Scherer7aced172000-08-15 01:13:23 +0000172
David Scherer7aced172000-08-15 01:13:23 +0000173 if not is_char_in_string:
174 # no clue -- make the caller pass everything
175 return None
176
177 # Peek back from the end for a good place to start,
178 # but don't try too often; pos will be left None, or
179 # bumped to a legitimate synch point.
180 limit = len(str)
181 for tries in range(5):
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000182 i = str.rfind(":\n", 0, limit)
David Scherer7aced172000-08-15 01:13:23 +0000183 if i < 0:
184 break
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800185 i = str.rfind('\n', 0, i) + 1 # start of colon line (-1+1=0)
David Scherer7aced172000-08-15 01:13:23 +0000186 m = _synchre(str, i, limit)
187 if m and not is_char_in_string(m.start()):
188 pos = m.start()
189 break
190 limit = i
191 if pos is None:
192 # Nothing looks like a block-opener, or stuff does
193 # but is_char_in_string keeps returning true; most likely
194 # we're in or near a giant string, the colorizer hasn't
195 # caught up enough to be helpful, or there simply *aren't*
196 # any interesting stmts. In any of these cases we're
197 # going to have to parse the whole thing to be sure, so
198 # give it one last try from the start, but stop wasting
199 # time here regardless of the outcome.
200 m = _synchre(str)
201 if m and not is_char_in_string(m.start()):
202 pos = m.start()
203 return pos
204
205 # Peeking back worked; look forward until _synchre no longer
206 # matches.
207 i = pos + 1
208 while 1:
209 m = _synchre(str, i)
210 if m:
211 s, i = m.span()
212 if not is_char_in_string(s):
213 pos = s
214 else:
215 break
216 return pos
217
David Scherer7aced172000-08-15 01:13:23 +0000218 def set_lo(self, lo):
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800219 """ Throw away the start of the string.
220
221 Intended to be called with the result of find_good_parse_start().
222 """
David Scherer7aced172000-08-15 01:13:23 +0000223 assert lo == 0 or self.str[lo-1] == '\n'
224 if lo > 0:
225 self.str = self.str[lo:]
226
Tal Einat9b7f9e62014-07-16 16:33:36 +0300227 # Build a translation table to map uninteresting chars to 'x', open
228 # brackets to '(', close brackets to ')' while preserving quotes,
229 # backslashes, newlines and hashes. This is to be passed to
230 # str.translate() in _study1().
231 _tran = {}
232 _tran.update((ord(c), ord('(')) for c in "({[")
233 _tran.update((ord(c), ord(')')) for c in ")}]")
234 _tran.update((ord(c), ord(c)) for c in "\"'\\\n#")
235 _tran = StringTranslatePseudoMapping(_tran, default_value=ord('x'))
236
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000237 def _study1(self):
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800238 """Find the line numbers of non-continuation lines.
239
240 As quickly as humanly possible <wink>, find the line numbers (0-
241 based) of the non-continuation lines.
242 Creates self.{goodlines, continuation}.
243 """
David Scherer7aced172000-08-15 01:13:23 +0000244 if self.study_level >= 1:
245 return
246 self.study_level = 1
247
248 # Map all uninteresting characters to "x", all open brackets
249 # to "(", all close brackets to ")", then collapse runs of
250 # uninteresting characters. This can cut the number of chars
251 # by a factor of 10-40, and so greatly speed the following loop.
252 str = self.str
Tal Einat9b7f9e62014-07-16 16:33:36 +0300253 str = str.translate(self._tran)
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000254 str = str.replace('xxxxxxxx', 'x')
255 str = str.replace('xxxx', 'x')
256 str = str.replace('xx', 'x')
257 str = str.replace('xx', 'x')
258 str = str.replace('\nx', '\n')
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800259 # Replacing x\n with \n would be incorrect because
260 # x may be preceded by a backslash.
David Scherer7aced172000-08-15 01:13:23 +0000261
262 # March over the squashed version of the program, accumulating
263 # the line numbers of non-continued stmts, and determining
264 # whether & why the last stmt is a continuation.
265 continuation = C_NONE
266 level = lno = 0 # level is nesting level; lno is line number
267 self.goodlines = goodlines = [0]
268 push_good = goodlines.append
269 i, n = 0, len(str)
270 while i < n:
271 ch = str[i]
272 i = i+1
273
274 # cases are checked in decreasing order of frequency
275 if ch == 'x':
276 continue
277
278 if ch == '\n':
279 lno = lno + 1
280 if level == 0:
281 push_good(lno)
282 # else we're in an unclosed bracket structure
283 continue
284
285 if ch == '(':
286 level = level + 1
287 continue
288
289 if ch == ')':
290 if level:
291 level = level - 1
292 # else the program is invalid, but we can't complain
293 continue
294
295 if ch == '"' or ch == "'":
296 # consume the string
297 quote = ch
298 if str[i-1:i+2] == quote * 3:
299 quote = quote * 3
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +0000300 firstlno = lno
David Scherer7aced172000-08-15 01:13:23 +0000301 w = len(quote) - 1
302 i = i+w
303 while i < n:
304 ch = str[i]
305 i = i+1
306
307 if ch == 'x':
308 continue
309
310 if str[i-1:i+w] == quote:
311 i = i+w
312 break
313
314 if ch == '\n':
315 lno = lno + 1
316 if w == 0:
317 # unterminated single-quoted string
318 if level == 0:
319 push_good(lno)
320 break
321 continue
322
323 if ch == '\\':
324 assert i < n
325 if str[i] == '\n':
326 lno = lno + 1
327 i = i+1
328 continue
329
330 # else comment char or paren inside string
331
332 else:
333 # didn't break out of the loop, so we're still
334 # inside a string
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +0000335 if (lno - 1) == firstlno:
336 # before the previous \n in str, we were in the first
337 # line of the string
338 continuation = C_STRING_FIRST_LINE
339 else:
340 continuation = C_STRING_NEXT_LINES
David Scherer7aced172000-08-15 01:13:23 +0000341 continue # with outer loop
342
343 if ch == '#':
344 # consume the comment
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000345 i = str.find('\n', i)
David Scherer7aced172000-08-15 01:13:23 +0000346 assert i >= 0
347 continue
348
349 assert ch == '\\'
350 assert i < n
351 if str[i] == '\n':
352 lno = lno + 1
353 if i+1 == n:
354 continuation = C_BACKSLASH
355 i = i+1
356
357 # The last stmt may be continued for all 3 reasons.
358 # String continuation takes precedence over bracket
359 # continuation, which beats backslash continuation.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +0000360 if (continuation != C_STRING_FIRST_LINE
361 and continuation != C_STRING_NEXT_LINES and level > 0):
David Scherer7aced172000-08-15 01:13:23 +0000362 continuation = C_BRACKET
363 self.continuation = continuation
364
365 # Push the final line number as a sentinel value, regardless of
366 # whether it's continued.
367 assert (continuation == C_NONE) == (goodlines[-1] == lno)
368 if goodlines[-1] != lno:
369 push_good(lno)
370
371 def get_continuation_type(self):
372 self._study1()
373 return self.continuation
374
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000375 def _study2(self):
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800376 """
377 study1 was sufficient to determine the continuation status,
378 but doing more requires looking at every character. study2
379 does this for the last interesting statement in the block.
380 Creates:
381 self.stmt_start, stmt_end
382 slice indices of last interesting stmt
383 self.stmt_bracketing
384 the bracketing structure of the last interesting stmt; for
385 example, for the statement "say(boo) or die",
386 stmt_bracketing will be ((0, 0), (0, 1), (2, 0), (2, 1),
387 (4, 0)). Strings and comments are treated as brackets, for
388 the matter.
389 self.lastch
390 last interesting character before optional trailing comment
391 self.lastopenbracketpos
392 if continuation is C_BRACKET, index of last open bracket
393 """
David Scherer7aced172000-08-15 01:13:23 +0000394 if self.study_level >= 2:
395 return
396 self._study1()
397 self.study_level = 2
398
399 # Set p and q to slice indices of last interesting stmt.
400 str, goodlines = self.str, self.goodlines
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800401 i = len(goodlines) - 1 # Index of newest line.
402 p = len(str) # End of goodlines[i]
David Scherer7aced172000-08-15 01:13:23 +0000403 while i:
404 assert p
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800405 # Make p be the index of the stmt at line number goodlines[i].
David Scherer7aced172000-08-15 01:13:23 +0000406 # Move p back to the stmt at line number goodlines[i-1].
407 q = p
408 for nothing in range(goodlines[i-1], goodlines[i]):
409 # tricky: sets p to 0 if no preceding newline
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000410 p = str.rfind('\n', 0, p-1) + 1
David Scherer7aced172000-08-15 01:13:23 +0000411 # The stmt str[p:q] isn't a continuation, but may be blank
412 # or a non-indenting comment line.
413 if _junkre(str, p):
414 i = i-1
415 else:
416 break
417 if i == 0:
418 # nothing but junk!
419 assert p == 0
420 q = p
421 self.stmt_start, self.stmt_end = p, q
422
423 # Analyze this stmt, to find the last open bracket (if any)
424 # and last interesting character (if any).
425 lastch = ""
426 stack = [] # stack of open bracket indices
427 push_stack = stack.append
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000428 bracketing = [(p, 0)]
David Scherer7aced172000-08-15 01:13:23 +0000429 while p < q:
430 # suck up all except ()[]{}'"#\\
431 m = _chew_ordinaryre(str, p, q)
432 if m:
433 # we skipped at least one boring char
Kurt B. Kaiser3269cc82001-07-13 20:33:46 +0000434 newp = m.end()
David Scherer7aced172000-08-15 01:13:23 +0000435 # back up over totally boring whitespace
Kurt B. Kaiser3269cc82001-07-13 20:33:46 +0000436 i = newp - 1 # index of last boring char
437 while i >= p and str[i] in " \t\n":
David Scherer7aced172000-08-15 01:13:23 +0000438 i = i-1
Kurt B. Kaiser3269cc82001-07-13 20:33:46 +0000439 if i >= p:
David Scherer7aced172000-08-15 01:13:23 +0000440 lastch = str[i]
Kurt B. Kaiser3269cc82001-07-13 20:33:46 +0000441 p = newp
David Scherer7aced172000-08-15 01:13:23 +0000442 if p >= q:
443 break
444
445 ch = str[p]
446
447 if ch in "([{":
448 push_stack(p)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000449 bracketing.append((p, len(stack)))
David Scherer7aced172000-08-15 01:13:23 +0000450 lastch = ch
451 p = p+1
452 continue
453
454 if ch in ")]}":
455 if stack:
456 del stack[-1]
457 lastch = ch
458 p = p+1
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000459 bracketing.append((p, len(stack)))
David Scherer7aced172000-08-15 01:13:23 +0000460 continue
461
462 if ch == '"' or ch == "'":
463 # consume string
464 # Note that study1 did this with a Python loop, but
465 # we use a regexp here; the reason is speed in both
466 # cases; the string may be huge, but study1 pre-squashed
467 # strings to a couple of characters per line. study1
468 # also needed to keep track of newlines, and we don't
469 # have to.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000470 bracketing.append((p, len(stack)+1))
David Scherer7aced172000-08-15 01:13:23 +0000471 lastch = ch
472 p = _match_stringre(str, p, q).end()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000473 bracketing.append((p, len(stack)))
David Scherer7aced172000-08-15 01:13:23 +0000474 continue
475
476 if ch == '#':
477 # consume comment and trailing newline
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000478 bracketing.append((p, len(stack)+1))
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000479 p = str.find('\n', p, q) + 1
David Scherer7aced172000-08-15 01:13:23 +0000480 assert p > 0
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000481 bracketing.append((p, len(stack)))
David Scherer7aced172000-08-15 01:13:23 +0000482 continue
483
484 assert ch == '\\'
485 p = p+1 # beyond backslash
486 assert p < q
487 if str[p] != '\n':
488 # the program is invalid, but can't complain
489 lastch = ch + str[p]
490 p = p+1 # beyond escaped char
491
492 # end while p < q:
493
494 self.lastch = lastch
Miss Islington (bot)dfa11442018-02-21 22:41:41 -0800495 self.lastopenbracketpos = stack[-1] if stack else None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000496 self.stmt_bracketing = tuple(bracketing)
David Scherer7aced172000-08-15 01:13:23 +0000497
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000498 def compute_bracket_indent(self):
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800499 """Return number of spaces the next line should be indented.
500
501 Line continuation must be C_BRACKET.
502 """
David Scherer7aced172000-08-15 01:13:23 +0000503 self._study2()
504 assert self.continuation == C_BRACKET
505 j = self.lastopenbracketpos
506 str = self.str
507 n = len(str)
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000508 origi = i = str.rfind('\n', 0, j) + 1
David Scherer7aced172000-08-15 01:13:23 +0000509 j = j+1 # one beyond open bracket
510 # find first list item; set i to start of its line
511 while j < n:
512 m = _itemre(str, j)
513 if m:
514 j = m.end() - 1 # index of first interesting char
515 extra = 0
516 break
517 else:
518 # this line is junk; advance to next line
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000519 i = j = str.find('\n', j) + 1
David Scherer7aced172000-08-15 01:13:23 +0000520 else:
521 # nothing interesting follows the bracket;
522 # reproduce the bracket line's indentation + a level
523 j = i = origi
524 while str[j] in " \t":
525 j = j+1
526 extra = self.indentwidth
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000527 return len(str[i:j].expandtabs(self.tabwidth)) + extra
David Scherer7aced172000-08-15 01:13:23 +0000528
David Scherer7aced172000-08-15 01:13:23 +0000529 def get_num_lines_in_stmt(self):
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800530 """Return number of physical lines in last stmt.
531
532 The statement doesn't have to be an interesting statement. This is
533 intended to be called when continuation is C_BACKSLASH.
534 """
David Scherer7aced172000-08-15 01:13:23 +0000535 self._study1()
536 goodlines = self.goodlines
537 return goodlines[-1] - goodlines[-2]
538
David Scherer7aced172000-08-15 01:13:23 +0000539 def compute_backslash_indent(self):
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800540 """Return number of spaces the next line should be indented.
541
542 Line continuation must be C_BACKSLASH. Also assume that the new
543 line is the first one following the initial line of the stmt.
544 """
David Scherer7aced172000-08-15 01:13:23 +0000545 self._study2()
546 assert self.continuation == C_BACKSLASH
547 str = self.str
548 i = self.stmt_start
549 while str[i] in " \t":
550 i = i+1
551 startpos = i
552
553 # See whether the initial line starts an assignment stmt; i.e.,
554 # look for an = operator
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000555 endpos = str.find('\n', startpos) + 1
David Scherer7aced172000-08-15 01:13:23 +0000556 found = level = 0
557 while i < endpos:
558 ch = str[i]
559 if ch in "([{":
560 level = level + 1
561 i = i+1
562 elif ch in ")]}":
563 if level:
564 level = level - 1
565 i = i+1
566 elif ch == '"' or ch == "'":
567 i = _match_stringre(str, i, endpos).end()
568 elif ch == '#':
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800569 # This line is unreachable because the # makes a comment of
570 # everything after it.
David Scherer7aced172000-08-15 01:13:23 +0000571 break
572 elif level == 0 and ch == '=' and \
573 (i == 0 or str[i-1] not in "=<>!") and \
574 str[i+1] != '=':
575 found = 1
576 break
577 else:
578 i = i+1
579
580 if found:
581 # found a legit =, but it may be the last interesting
582 # thing on the line
583 i = i+1 # move beyond the =
584 found = re.match(r"\s*\\", str[i:endpos]) is None
585
586 if not found:
587 # oh well ... settle for moving beyond the first chunk
588 # of non-whitespace chars
589 i = startpos
590 while str[i] not in " \t\n":
591 i = i+1
592
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000593 return len(str[self.stmt_start:i].expandtabs(\
David Scherer7aced172000-08-15 01:13:23 +0000594 self.tabwidth)) + 1
595
David Scherer7aced172000-08-15 01:13:23 +0000596 def get_base_indent_string(self):
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800597 """Return the leading whitespace on the initial line of the last
598 interesting stmt.
599 """
David Scherer7aced172000-08-15 01:13:23 +0000600 self._study2()
601 i, n = self.stmt_start, self.stmt_end
602 j = i
603 str = self.str
604 while j < n and str[j] in " \t":
605 j = j + 1
606 return str[i:j]
607
David Scherer7aced172000-08-15 01:13:23 +0000608 def is_block_opener(self):
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800609 "Return True if the last interesting statemtent opens a block."
David Scherer7aced172000-08-15 01:13:23 +0000610 self._study2()
611 return self.lastch == ':'
612
David Scherer7aced172000-08-15 01:13:23 +0000613 def is_block_closer(self):
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800614 "Return True if the last interesting statement closes a block."
David Scherer7aced172000-08-15 01:13:23 +0000615 self._study2()
616 return _closere(self.str, self.stmt_start) is not None
617
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000618 def get_last_stmt_bracketing(self):
Miss Islington (bot)dfa11442018-02-21 22:41:41 -0800619 """Return bracketing structure of the last interesting statement.
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800620
Miss Islington (bot)dfa11442018-02-21 22:41:41 -0800621 The returned tuple is in the format defined in _study2().
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800622 """
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000623 self._study2()
624 return self.stmt_bracketing
Miss Islington (bot)c59bc982018-02-21 20:09:39 -0800625
626
627if __name__ == '__main__': #pragma: nocover
628 import unittest
629 unittest.main('idlelib.idle_test.test_pyparse', verbosity=2)