blob: 72bd9e00c1e97a62e18dc9aa2057c114a78672be [file] [log] [blame]
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -05001"""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
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -050017# 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
21if 0: # for throwaway debugging output
22 def dump(*stuff):
Kurt B. Kaiser254eb532002-09-17 03:55:13 +000023 sys.__stdout__.write(" ".join(map(str, stuff)) + "\n")
David Scherer7aced172000-08-15 01:13:23 +000024
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -050025# Find what looks like the start of a popular statement.
David Scherer7aced172000-08-15 01:13:23 +000026
27_synchre = re.compile(r"""
28 ^
29 [ \t]*
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000030 (?: while
David Scherer7aced172000-08-15 01:13:23 +000031 | else
32 | def
33 | return
34 | assert
35 | break
36 | class
37 | continue
38 | elif
39 | try
40 | except
41 | raise
42 | import
Kurt B. Kaiser752e4d52001-07-14 04:59:24 +000043 | yield
David Scherer7aced172000-08-15 01:13:23 +000044 )
45 \b
46""", re.VERBOSE | re.MULTILINE).search
47
48# Match blank line or non-indenting comment line.
49
50_junkre = re.compile(r"""
51 [ \t]*
52 (?: \# \S .* )?
53 \n
54""", re.VERBOSE).match
55
56# Match any flavor of string; the terminating quote is optional
57# so that we're robust in the face of incomplete program text.
58
59_match_stringre = re.compile(r"""
60 \""" [^"\\]* (?:
61 (?: \\. | "(?!"") )
62 [^"\\]*
63 )*
64 (?: \""" )?
65
66| " [^"\\\n]* (?: \\. [^"\\\n]* )* "?
67
68| ''' [^'\\]* (?:
69 (?: \\. | '(?!'') )
70 [^'\\]*
71 )*
72 (?: ''' )?
73
74| ' [^'\\\n]* (?: \\. [^'\\\n]* )* '?
75""", re.VERBOSE | re.DOTALL).match
76
77# Match a line that starts with something interesting;
78# used to find the first item of a bracket structure.
79
80_itemre = re.compile(r"""
81 [ \t]*
82 [^\s#\\] # if we match, m.end()-1 is the interesting char
83""", re.VERBOSE).match
84
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -050085# Match start of statements that should be followed by a dedent.
David Scherer7aced172000-08-15 01:13:23 +000086
87_closere = re.compile(r"""
88 \s*
89 (?: return
90 | break
91 | continue
92 | raise
93 | pass
94 )
95 \b
96""", re.VERBOSE).match
97
98# Chew up non-special chars as quickly as possible. If match is
99# successful, m.end() less 1 is the index of the last boring char
100# matched. If match is unsuccessful, the string starts with an
101# interesting char.
102
103_chew_ordinaryre = re.compile(r"""
104 [^[\](){}#'"\\]+
105""", re.VERBOSE).match
106
David Scherer7aced172000-08-15 01:13:23 +0000107
Tal Einat9b7f9e62014-07-16 16:33:36 +0300108class StringTranslatePseudoMapping(Mapping):
109 r"""Utility class to be used with str.translate()
110
111 This Mapping class wraps a given dict. When a value for a key is
112 requested via __getitem__() or get(), the key is looked up in the
113 given dict. If found there, the value from the dict is returned.
114 Otherwise, the default value given upon initialization is returned.
115
116 This allows using str.translate() to make some replacements, and to
117 replace all characters for which no replacement was specified with
118 a given character instead of leaving them as-is.
119
120 For example, to replace everything except whitespace with 'x':
121
122 >>> whitespace_chars = ' \t\n\r'
123 >>> preserve_dict = {ord(c): ord(c) for c in whitespace_chars}
124 >>> mapping = StringTranslatePseudoMapping(preserve_dict, ord('x'))
125 >>> text = "a + b\tc\nd"
126 >>> text.translate(mapping)
127 'x x x\tx\nx'
128 """
129 def __init__(self, non_defaults, default_value):
130 self._non_defaults = non_defaults
131 self._default_value = default_value
132
133 def _get(key, _get=non_defaults.get, _default=default_value):
134 return _get(key, _default)
135 self._get = _get
136
137 def __getitem__(self, item):
138 return self._get(item)
139
140 def __len__(self):
141 return len(self._non_defaults)
142
143 def __iter__(self):
144 return iter(self._non_defaults)
145
146 def get(self, key, default=None):
147 return self._get(key)
148
David Scherer7aced172000-08-15 01:13:23 +0000149
150class Parser:
151
152 def __init__(self, indentwidth, tabwidth):
153 self.indentwidth = indentwidth
154 self.tabwidth = tabwidth
155
Walter Dörwald5de48bd2007-06-11 21:38:39 +0000156 def set_str(self, s):
157 assert len(s) == 0 or s[-1] == '\n'
Walter Dörwald5de48bd2007-06-11 21:38:39 +0000158 self.str = s
David Scherer7aced172000-08-15 01:13:23 +0000159 self.study_level = 0
160
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000161 def find_good_parse_start(self, is_char_in_string=None,
David Scherer7aced172000-08-15 01:13:23 +0000162 _synchre=_synchre):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500163 """
164 Return index of a good place to begin parsing, as close to the
165 end of the string as possible. This will be the start of some
166 popular stmt like "if" or "def". Return None if none found:
167 the caller should pass more prior context then, if possible, or
168 if not (the entire program text up until the point of interest
169 has already been tried) pass 0 to set_lo().
170
171 This will be reliable iff given a reliable is_char_in_string()
172 function, meaning that when it says "no", it's absolutely
173 guaranteed that the char is not in a string.
174 """
David Scherer7aced172000-08-15 01:13:23 +0000175 str, pos = self.str, None
David Scherer7aced172000-08-15 01:13:23 +0000176
David Scherer7aced172000-08-15 01:13:23 +0000177 if not is_char_in_string:
178 # no clue -- make the caller pass everything
179 return None
180
181 # Peek back from the end for a good place to start,
182 # but don't try too often; pos will be left None, or
183 # bumped to a legitimate synch point.
184 limit = len(str)
185 for tries in range(5):
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000186 i = str.rfind(":\n", 0, limit)
David Scherer7aced172000-08-15 01:13:23 +0000187 if i < 0:
188 break
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500189 i = str.rfind('\n', 0, i) + 1 # start of colon line (-1+1=0)
David Scherer7aced172000-08-15 01:13:23 +0000190 m = _synchre(str, i, limit)
191 if m and not is_char_in_string(m.start()):
192 pos = m.start()
193 break
194 limit = i
195 if pos is None:
196 # Nothing looks like a block-opener, or stuff does
197 # but is_char_in_string keeps returning true; most likely
198 # we're in or near a giant string, the colorizer hasn't
199 # caught up enough to be helpful, or there simply *aren't*
200 # any interesting stmts. In any of these cases we're
201 # going to have to parse the whole thing to be sure, so
202 # give it one last try from the start, but stop wasting
203 # time here regardless of the outcome.
204 m = _synchre(str)
205 if m and not is_char_in_string(m.start()):
206 pos = m.start()
207 return pos
208
209 # Peeking back worked; look forward until _synchre no longer
210 # matches.
211 i = pos + 1
212 while 1:
213 m = _synchre(str, i)
214 if m:
215 s, i = m.span()
216 if not is_char_in_string(s):
217 pos = s
218 else:
219 break
220 return pos
221
David Scherer7aced172000-08-15 01:13:23 +0000222 def set_lo(self, lo):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500223 """ Throw away the start of the string.
224
225 Intended to be called with the result of find_good_parse_start().
226 """
David Scherer7aced172000-08-15 01:13:23 +0000227 assert lo == 0 or self.str[lo-1] == '\n'
228 if lo > 0:
229 self.str = self.str[lo:]
230
Tal Einat9b7f9e62014-07-16 16:33:36 +0300231 # Build a translation table to map uninteresting chars to 'x', open
232 # brackets to '(', close brackets to ')' while preserving quotes,
233 # backslashes, newlines and hashes. This is to be passed to
234 # str.translate() in _study1().
235 _tran = {}
236 _tran.update((ord(c), ord('(')) for c in "({[")
237 _tran.update((ord(c), ord(')')) for c in ")}]")
238 _tran.update((ord(c), ord(c)) for c in "\"'\\\n#")
239 _tran = StringTranslatePseudoMapping(_tran, default_value=ord('x'))
240
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000241 def _study1(self):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500242 """Find the line numbers of non-continuation lines.
243
244 As quickly as humanly possible <wink>, find the line numbers (0-
245 based) of the non-continuation lines.
246 Creates self.{goodlines, continuation}.
247 """
David Scherer7aced172000-08-15 01:13:23 +0000248 if self.study_level >= 1:
249 return
250 self.study_level = 1
251
252 # Map all uninteresting characters to "x", all open brackets
253 # to "(", all close brackets to ")", then collapse runs of
254 # uninteresting characters. This can cut the number of chars
255 # by a factor of 10-40, and so greatly speed the following loop.
256 str = self.str
Tal Einat9b7f9e62014-07-16 16:33:36 +0300257 str = str.translate(self._tran)
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000258 str = str.replace('xxxxxxxx', 'x')
259 str = str.replace('xxxx', 'x')
260 str = str.replace('xx', 'x')
261 str = str.replace('xx', 'x')
262 str = str.replace('\nx', '\n')
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500263 # Replacing x\n with \n would be incorrect because
264 # x may be preceded by a backslash.
David Scherer7aced172000-08-15 01:13:23 +0000265
266 # March over the squashed version of the program, accumulating
267 # the line numbers of non-continued stmts, and determining
268 # whether & why the last stmt is a continuation.
269 continuation = C_NONE
270 level = lno = 0 # level is nesting level; lno is line number
271 self.goodlines = goodlines = [0]
272 push_good = goodlines.append
273 i, n = 0, len(str)
274 while i < n:
275 ch = str[i]
276 i = i+1
277
278 # cases are checked in decreasing order of frequency
279 if ch == 'x':
280 continue
281
282 if ch == '\n':
283 lno = lno + 1
284 if level == 0:
285 push_good(lno)
286 # else we're in an unclosed bracket structure
287 continue
288
289 if ch == '(':
290 level = level + 1
291 continue
292
293 if ch == ')':
294 if level:
295 level = level - 1
296 # else the program is invalid, but we can't complain
297 continue
298
299 if ch == '"' or ch == "'":
300 # consume the string
301 quote = ch
302 if str[i-1:i+2] == quote * 3:
303 quote = quote * 3
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +0000304 firstlno = lno
David Scherer7aced172000-08-15 01:13:23 +0000305 w = len(quote) - 1
306 i = i+w
307 while i < n:
308 ch = str[i]
309 i = i+1
310
311 if ch == 'x':
312 continue
313
314 if str[i-1:i+w] == quote:
315 i = i+w
316 break
317
318 if ch == '\n':
319 lno = lno + 1
320 if w == 0:
321 # unterminated single-quoted string
322 if level == 0:
323 push_good(lno)
324 break
325 continue
326
327 if ch == '\\':
328 assert i < n
329 if str[i] == '\n':
330 lno = lno + 1
331 i = i+1
332 continue
333
334 # else comment char or paren inside string
335
336 else:
337 # didn't break out of the loop, so we're still
338 # inside a string
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +0000339 if (lno - 1) == firstlno:
340 # before the previous \n in str, we were in the first
341 # line of the string
342 continuation = C_STRING_FIRST_LINE
343 else:
344 continuation = C_STRING_NEXT_LINES
David Scherer7aced172000-08-15 01:13:23 +0000345 continue # with outer loop
346
347 if ch == '#':
348 # consume the comment
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000349 i = str.find('\n', i)
David Scherer7aced172000-08-15 01:13:23 +0000350 assert i >= 0
351 continue
352
353 assert ch == '\\'
354 assert i < n
355 if str[i] == '\n':
356 lno = lno + 1
357 if i+1 == n:
358 continuation = C_BACKSLASH
359 i = i+1
360
361 # The last stmt may be continued for all 3 reasons.
362 # String continuation takes precedence over bracket
363 # continuation, which beats backslash continuation.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +0000364 if (continuation != C_STRING_FIRST_LINE
365 and continuation != C_STRING_NEXT_LINES and level > 0):
David Scherer7aced172000-08-15 01:13:23 +0000366 continuation = C_BRACKET
367 self.continuation = continuation
368
369 # Push the final line number as a sentinel value, regardless of
370 # whether it's continued.
371 assert (continuation == C_NONE) == (goodlines[-1] == lno)
372 if goodlines[-1] != lno:
373 push_good(lno)
374
375 def get_continuation_type(self):
376 self._study1()
377 return self.continuation
378
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000379 def _study2(self):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500380 """
381 study1 was sufficient to determine the continuation status,
382 but doing more requires looking at every character. study2
383 does this for the last interesting statement in the block.
384 Creates:
385 self.stmt_start, stmt_end
386 slice indices of last interesting stmt
387 self.stmt_bracketing
388 the bracketing structure of the last interesting stmt; for
389 example, for the statement "say(boo) or die",
390 stmt_bracketing will be ((0, 0), (0, 1), (2, 0), (2, 1),
391 (4, 0)). Strings and comments are treated as brackets, for
392 the matter.
393 self.lastch
394 last interesting character before optional trailing comment
395 self.lastopenbracketpos
396 if continuation is C_BRACKET, index of last open bracket
397 """
David Scherer7aced172000-08-15 01:13:23 +0000398 if self.study_level >= 2:
399 return
400 self._study1()
401 self.study_level = 2
402
403 # Set p and q to slice indices of last interesting stmt.
404 str, goodlines = self.str, self.goodlines
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500405 i = len(goodlines) - 1 # Index of newest line.
406 p = len(str) # End of goodlines[i]
David Scherer7aced172000-08-15 01:13:23 +0000407 while i:
408 assert p
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500409 # Make p be the index of the stmt at line number goodlines[i].
David Scherer7aced172000-08-15 01:13:23 +0000410 # Move p back to the stmt at line number goodlines[i-1].
411 q = p
412 for nothing in range(goodlines[i-1], goodlines[i]):
413 # tricky: sets p to 0 if no preceding newline
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000414 p = str.rfind('\n', 0, p-1) + 1
David Scherer7aced172000-08-15 01:13:23 +0000415 # The stmt str[p:q] isn't a continuation, but may be blank
416 # or a non-indenting comment line.
417 if _junkre(str, p):
418 i = i-1
419 else:
420 break
421 if i == 0:
422 # nothing but junk!
423 assert p == 0
424 q = p
425 self.stmt_start, self.stmt_end = p, q
426
427 # Analyze this stmt, to find the last open bracket (if any)
428 # and last interesting character (if any).
429 lastch = ""
430 stack = [] # stack of open bracket indices
431 push_stack = stack.append
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000432 bracketing = [(p, 0)]
David Scherer7aced172000-08-15 01:13:23 +0000433 while p < q:
434 # suck up all except ()[]{}'"#\\
435 m = _chew_ordinaryre(str, p, q)
436 if m:
437 # we skipped at least one boring char
Kurt B. Kaiser3269cc82001-07-13 20:33:46 +0000438 newp = m.end()
David Scherer7aced172000-08-15 01:13:23 +0000439 # back up over totally boring whitespace
Kurt B. Kaiser3269cc82001-07-13 20:33:46 +0000440 i = newp - 1 # index of last boring char
441 while i >= p and str[i] in " \t\n":
David Scherer7aced172000-08-15 01:13:23 +0000442 i = i-1
Kurt B. Kaiser3269cc82001-07-13 20:33:46 +0000443 if i >= p:
David Scherer7aced172000-08-15 01:13:23 +0000444 lastch = str[i]
Kurt B. Kaiser3269cc82001-07-13 20:33:46 +0000445 p = newp
David Scherer7aced172000-08-15 01:13:23 +0000446 if p >= q:
447 break
448
449 ch = str[p]
450
451 if ch in "([{":
452 push_stack(p)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000453 bracketing.append((p, len(stack)))
David Scherer7aced172000-08-15 01:13:23 +0000454 lastch = ch
455 p = p+1
456 continue
457
458 if ch in ")]}":
459 if stack:
460 del stack[-1]
461 lastch = ch
462 p = p+1
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000463 bracketing.append((p, len(stack)))
David Scherer7aced172000-08-15 01:13:23 +0000464 continue
465
466 if ch == '"' or ch == "'":
467 # consume string
468 # Note that study1 did this with a Python loop, but
469 # we use a regexp here; the reason is speed in both
470 # cases; the string may be huge, but study1 pre-squashed
471 # strings to a couple of characters per line. study1
472 # also needed to keep track of newlines, and we don't
473 # have to.
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000474 bracketing.append((p, len(stack)+1))
David Scherer7aced172000-08-15 01:13:23 +0000475 lastch = ch
476 p = _match_stringre(str, p, q).end()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000477 bracketing.append((p, len(stack)))
David Scherer7aced172000-08-15 01:13:23 +0000478 continue
479
480 if ch == '#':
481 # consume comment and trailing newline
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000482 bracketing.append((p, len(stack)+1))
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000483 p = str.find('\n', p, q) + 1
David Scherer7aced172000-08-15 01:13:23 +0000484 assert p > 0
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000485 bracketing.append((p, len(stack)))
David Scherer7aced172000-08-15 01:13:23 +0000486 continue
487
488 assert ch == '\\'
489 p = p+1 # beyond backslash
490 assert p < q
491 if str[p] != '\n':
492 # the program is invalid, but can't complain
493 lastch = ch + str[p]
494 p = p+1 # beyond escaped char
495
496 # end while p < q:
497
498 self.lastch = lastch
499 if stack:
500 self.lastopenbracketpos = stack[-1]
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000501 self.stmt_bracketing = tuple(bracketing)
David Scherer7aced172000-08-15 01:13:23 +0000502
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000503 def compute_bracket_indent(self):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500504 """Return number of spaces the next line should be indented.
505
506 Line continuation must be C_BRACKET.
507 """
David Scherer7aced172000-08-15 01:13:23 +0000508 self._study2()
509 assert self.continuation == C_BRACKET
510 j = self.lastopenbracketpos
511 str = self.str
512 n = len(str)
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000513 origi = i = str.rfind('\n', 0, j) + 1
David Scherer7aced172000-08-15 01:13:23 +0000514 j = j+1 # one beyond open bracket
515 # find first list item; set i to start of its line
516 while j < n:
517 m = _itemre(str, j)
518 if m:
519 j = m.end() - 1 # index of first interesting char
520 extra = 0
521 break
522 else:
523 # this line is junk; advance to next line
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000524 i = j = str.find('\n', j) + 1
David Scherer7aced172000-08-15 01:13:23 +0000525 else:
526 # nothing interesting follows the bracket;
527 # reproduce the bracket line's indentation + a level
528 j = i = origi
529 while str[j] in " \t":
530 j = j+1
531 extra = self.indentwidth
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000532 return len(str[i:j].expandtabs(self.tabwidth)) + extra
David Scherer7aced172000-08-15 01:13:23 +0000533
David Scherer7aced172000-08-15 01:13:23 +0000534 def get_num_lines_in_stmt(self):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500535 """Return number of physical lines in last stmt.
536
537 The statement doesn't have to be an interesting statement. This is
538 intended to be called when continuation is C_BACKSLASH.
539 """
David Scherer7aced172000-08-15 01:13:23 +0000540 self._study1()
541 goodlines = self.goodlines
542 return goodlines[-1] - goodlines[-2]
543
David Scherer7aced172000-08-15 01:13:23 +0000544 def compute_backslash_indent(self):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500545 """Return number of spaces the next line should be indented.
546
547 Line continuation must be C_BACKSLASH. Also assume that the new
548 line is the first one following the initial line of the stmt.
549 """
David Scherer7aced172000-08-15 01:13:23 +0000550 self._study2()
551 assert self.continuation == C_BACKSLASH
552 str = self.str
553 i = self.stmt_start
554 while str[i] in " \t":
555 i = i+1
556 startpos = i
557
558 # See whether the initial line starts an assignment stmt; i.e.,
559 # look for an = operator
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000560 endpos = str.find('\n', startpos) + 1
David Scherer7aced172000-08-15 01:13:23 +0000561 found = level = 0
562 while i < endpos:
563 ch = str[i]
564 if ch in "([{":
565 level = level + 1
566 i = i+1
567 elif ch in ")]}":
568 if level:
569 level = level - 1
570 i = i+1
571 elif ch == '"' or ch == "'":
572 i = _match_stringre(str, i, endpos).end()
573 elif ch == '#':
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500574 # This line is unreachable because the # makes a comment of
575 # everything after it.
David Scherer7aced172000-08-15 01:13:23 +0000576 break
577 elif level == 0 and ch == '=' and \
578 (i == 0 or str[i-1] not in "=<>!") and \
579 str[i+1] != '=':
580 found = 1
581 break
582 else:
583 i = i+1
584
585 if found:
586 # found a legit =, but it may be the last interesting
587 # thing on the line
588 i = i+1 # move beyond the =
589 found = re.match(r"\s*\\", str[i:endpos]) is None
590
591 if not found:
592 # oh well ... settle for moving beyond the first chunk
593 # of non-whitespace chars
594 i = startpos
595 while str[i] not in " \t\n":
596 i = i+1
597
Kurt B. Kaiser254eb532002-09-17 03:55:13 +0000598 return len(str[self.stmt_start:i].expandtabs(\
David Scherer7aced172000-08-15 01:13:23 +0000599 self.tabwidth)) + 1
600
David Scherer7aced172000-08-15 01:13:23 +0000601 def get_base_indent_string(self):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500602 """Return the leading whitespace on the initial line of the last
603 interesting stmt.
604 """
David Scherer7aced172000-08-15 01:13:23 +0000605 self._study2()
606 i, n = self.stmt_start, self.stmt_end
607 j = i
608 str = self.str
609 while j < n and str[j] in " \t":
610 j = j + 1
611 return str[i:j]
612
David Scherer7aced172000-08-15 01:13:23 +0000613 def is_block_opener(self):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500614 "Return True if the last interesting statemtent opens a block."
David Scherer7aced172000-08-15 01:13:23 +0000615 self._study2()
616 return self.lastch == ':'
617
David Scherer7aced172000-08-15 01:13:23 +0000618 def is_block_closer(self):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500619 "Return True if the last interesting statement closes a block."
David Scherer7aced172000-08-15 01:13:23 +0000620 self._study2()
621 return _closere(self.str, self.stmt_start) is not None
622
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500623 # XXX - is this used?
David Scherer7aced172000-08-15 01:13:23 +0000624 lastopenbracketpos = None
625
626 def get_last_open_bracket_pos(self):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500627 "Return index of last open bracket or None."
David Scherer7aced172000-08-15 01:13:23 +0000628 self._study2()
629 return self.lastopenbracketpos
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000630
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500631 # XXX - is this used?
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000632 stmt_bracketing = None
633
634 def get_last_stmt_bracketing(self):
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500635 """Return a tuple of the structure of the bracketing of the last
636 interesting statement.
637
638 Tuple is in the format defined in _study2().
639 """
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000640 self._study2()
641 return self.stmt_bracketing
Cheryl Sabellac84cf6c2018-02-21 22:48:36 -0500642
643
644if __name__ == '__main__': #pragma: nocover
645 import unittest
646 unittest.main('idlelib.idle_test.test_pyparse', verbosity=2)