##// END OF EJS Templates
future annotation importing
Matthias Bussonnier -
Show More
@@ -1,797 +1,798 b''
1 """DEPRECATED: Input handling and transformation machinery.
1 """DEPRECATED: Input handling and transformation machinery.
2
2
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
4
4
5 The first class in this module, :class:`InputSplitter`, is designed to tell when
5 The first class in this module, :class:`InputSplitter`, is designed to tell when
6 input from a line-oriented frontend is complete and should be executed, and when
6 input from a line-oriented frontend is complete and should be executed, and when
7 the user should be prompted for another line of code instead. The name 'input
7 the user should be prompted for another line of code instead. The name 'input
8 splitter' is largely for historical reasons.
8 splitter' is largely for historical reasons.
9
9
10 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
10 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
11 with full support for the extended IPython syntax (magics, system calls, etc).
11 with full support for the extended IPython syntax (magics, system calls, etc).
12 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
12 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
13 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
13 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
14 and stores the results.
14 and stores the results.
15
15
16 For more details, see the class docstrings below.
16 For more details, see the class docstrings below.
17 """
17 """
18 from __future__ import annotations
18
19
19 from warnings import warn
20 from warnings import warn
20
21
21 warn('IPython.core.inputsplitter is deprecated since IPython 7 in favor of `IPython.core.inputtransformer2`',
22 warn('IPython.core.inputsplitter is deprecated since IPython 7 in favor of `IPython.core.inputtransformer2`',
22 DeprecationWarning)
23 DeprecationWarning)
23
24
24 # Copyright (c) IPython Development Team.
25 # Copyright (c) IPython Development Team.
25 # Distributed under the terms of the Modified BSD License.
26 # Distributed under the terms of the Modified BSD License.
26 import ast
27 import ast
27 import codeop
28 import codeop
28 import io
29 import io
29 import re
30 import re
30 import sys
31 import sys
31 import tokenize
32 import tokenize
32 import warnings
33 import warnings
33
34
34 from typing import List, Tuple, Union, Optional, TYPE_CHECKING
35 from typing import List, Tuple, Union, Optional, TYPE_CHECKING
35 from types import CodeType
36 from types import CodeType
36
37
37 from IPython.core.inputtransformer import (leading_indent,
38 from IPython.core.inputtransformer import (leading_indent,
38 classic_prompt,
39 classic_prompt,
39 ipy_prompt,
40 ipy_prompt,
40 cellmagic,
41 cellmagic,
41 assemble_logical_lines,
42 assemble_logical_lines,
42 help_end,
43 help_end,
43 escaped_commands,
44 escaped_commands,
44 assign_from_magic,
45 assign_from_magic,
45 assign_from_system,
46 assign_from_system,
46 assemble_python_lines,
47 assemble_python_lines,
47 )
48 )
48 from IPython.utils import tokenutil
49 from IPython.utils import tokenutil
49
50
50 # These are available in this module for backwards compatibility.
51 # These are available in this module for backwards compatibility.
51 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
52 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
52 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
53 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
53 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
54 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
54
55
55 if TYPE_CHECKING:
56 if TYPE_CHECKING:
56 from typing_extensions import Self
57 from typing_extensions import Self
57 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
58 # Utilities
59 # Utilities
59 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
60
61
61 # FIXME: These are general-purpose utilities that later can be moved to the
62 # FIXME: These are general-purpose utilities that later can be moved to the
62 # general ward. Kept here for now because we're being very strict about test
63 # general ward. Kept here for now because we're being very strict about test
63 # coverage with this code, and this lets us ensure that we keep 100% coverage
64 # coverage with this code, and this lets us ensure that we keep 100% coverage
64 # while developing.
65 # while developing.
65
66
66 # compiled regexps for autoindent management
67 # compiled regexps for autoindent management
67 dedent_re = re.compile('|'.join([
68 dedent_re = re.compile('|'.join([
68 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
69 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
69 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
70 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
70 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
71 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
71 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
72 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
72 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
73 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
73 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
74 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
74 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
75 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
75 ]))
76 ]))
76 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
77 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
77
78
78 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
79 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
79 # before pure comments
80 # before pure comments
80 comment_line_re = re.compile(r'^\s*\#')
81 comment_line_re = re.compile(r'^\s*\#')
81
82
82
83
83 def num_ini_spaces(s):
84 def num_ini_spaces(s):
84 """Return the number of initial spaces in a string.
85 """Return the number of initial spaces in a string.
85
86
86 Note that tabs are counted as a single space. For now, we do *not* support
87 Note that tabs are counted as a single space. For now, we do *not* support
87 mixing of tabs and spaces in the user's input.
88 mixing of tabs and spaces in the user's input.
88
89
89 Parameters
90 Parameters
90 ----------
91 ----------
91 s : string
92 s : string
92
93
93 Returns
94 Returns
94 -------
95 -------
95 n : int
96 n : int
96 """
97 """
97 warnings.warn(
98 warnings.warn(
98 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
99 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
99 "It is considered fro removal in in future version. "
100 "It is considered fro removal in in future version. "
100 "Please open an issue if you believe it should be kept.",
101 "Please open an issue if you believe it should be kept.",
101 stacklevel=2,
102 stacklevel=2,
102 category=PendingDeprecationWarning,
103 category=PendingDeprecationWarning,
103 )
104 )
104 ini_spaces = ini_spaces_re.match(s)
105 ini_spaces = ini_spaces_re.match(s)
105 if ini_spaces:
106 if ini_spaces:
106 return ini_spaces.end()
107 return ini_spaces.end()
107 else:
108 else:
108 return 0
109 return 0
109
110
110 # Fake token types for partial_tokenize:
111 # Fake token types for partial_tokenize:
111 INCOMPLETE_STRING = tokenize.N_TOKENS
112 INCOMPLETE_STRING = tokenize.N_TOKENS
112 IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
113 IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
113
114
114 # The 2 classes below have the same API as TokenInfo, but don't try to look up
115 # The 2 classes below have the same API as TokenInfo, but don't try to look up
115 # a token type name that they won't find.
116 # a token type name that they won't find.
116 class IncompleteString:
117 class IncompleteString:
117 type = exact_type = INCOMPLETE_STRING
118 type = exact_type = INCOMPLETE_STRING
118 def __init__(self, s, start, end, line):
119 def __init__(self, s, start, end, line):
119 self.s = s
120 self.s = s
120 self.start = start
121 self.start = start
121 self.end = end
122 self.end = end
122 self.line = line
123 self.line = line
123
124
124 class InMultilineStatement:
125 class InMultilineStatement:
125 type = exact_type = IN_MULTILINE_STATEMENT
126 type = exact_type = IN_MULTILINE_STATEMENT
126 def __init__(self, pos, line):
127 def __init__(self, pos, line):
127 self.s = ''
128 self.s = ''
128 self.start = self.end = pos
129 self.start = self.end = pos
129 self.line = line
130 self.line = line
130
131
131 def partial_tokens(s):
132 def partial_tokens(s):
132 """Iterate over tokens from a possibly-incomplete string of code.
133 """Iterate over tokens from a possibly-incomplete string of code.
133
134
134 This adds two special token types: INCOMPLETE_STRING and
135 This adds two special token types: INCOMPLETE_STRING and
135 IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
136 IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
136 represent the two main ways for code to be incomplete.
137 represent the two main ways for code to be incomplete.
137 """
138 """
138 readline = io.StringIO(s).readline
139 readline = io.StringIO(s).readline
139 token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
140 token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
140 try:
141 try:
141 for token in tokenutil.generate_tokens_catch_errors(readline):
142 for token in tokenutil.generate_tokens_catch_errors(readline):
142 yield token
143 yield token
143 except tokenize.TokenError as e:
144 except tokenize.TokenError as e:
144 # catch EOF error
145 # catch EOF error
145 lines = s.splitlines(keepends=True)
146 lines = s.splitlines(keepends=True)
146 end = len(lines), len(lines[-1])
147 end = len(lines), len(lines[-1])
147 if 'multi-line string' in e.args[0]:
148 if 'multi-line string' in e.args[0]:
148 l, c = start = token.end
149 l, c = start = token.end
149 s = lines[l-1][c:] + ''.join(lines[l:])
150 s = lines[l-1][c:] + ''.join(lines[l:])
150 yield IncompleteString(s, start, end, lines[-1])
151 yield IncompleteString(s, start, end, lines[-1])
151 elif 'multi-line statement' in e.args[0]:
152 elif 'multi-line statement' in e.args[0]:
152 yield InMultilineStatement(end, lines[-1])
153 yield InMultilineStatement(end, lines[-1])
153 else:
154 else:
154 raise
155 raise
155
156
156 def find_next_indent(code) -> int:
157 def find_next_indent(code) -> int:
157 """Find the number of spaces for the next line of indentation"""
158 """Find the number of spaces for the next line of indentation"""
158 tokens = list(partial_tokens(code))
159 tokens = list(partial_tokens(code))
159 if tokens[-1].type == tokenize.ENDMARKER:
160 if tokens[-1].type == tokenize.ENDMARKER:
160 tokens.pop()
161 tokens.pop()
161 if not tokens:
162 if not tokens:
162 return 0
163 return 0
163
164
164 while tokens[-1].type in {
165 while tokens[-1].type in {
165 tokenize.DEDENT,
166 tokenize.DEDENT,
166 tokenize.NEWLINE,
167 tokenize.NEWLINE,
167 tokenize.COMMENT,
168 tokenize.COMMENT,
168 tokenize.ERRORTOKEN,
169 tokenize.ERRORTOKEN,
169 }:
170 }:
170 tokens.pop()
171 tokens.pop()
171
172
172 # Starting in Python 3.12, the tokenize module adds implicit newlines at the end
173 # Starting in Python 3.12, the tokenize module adds implicit newlines at the end
173 # of input. We need to remove those if we're in a multiline statement
174 # of input. We need to remove those if we're in a multiline statement
174 if tokens[-1].type == IN_MULTILINE_STATEMENT:
175 if tokens[-1].type == IN_MULTILINE_STATEMENT:
175 while tokens[-2].type in {tokenize.NL}:
176 while tokens[-2].type in {tokenize.NL}:
176 tokens.pop(-2)
177 tokens.pop(-2)
177
178
178
179
179 if tokens[-1].type == INCOMPLETE_STRING:
180 if tokens[-1].type == INCOMPLETE_STRING:
180 # Inside a multiline string
181 # Inside a multiline string
181 return 0
182 return 0
182
183
183 # Find the indents used before
184 # Find the indents used before
184 prev_indents = [0]
185 prev_indents = [0]
185 def _add_indent(n):
186 def _add_indent(n):
186 if n != prev_indents[-1]:
187 if n != prev_indents[-1]:
187 prev_indents.append(n)
188 prev_indents.append(n)
188
189
189 tokiter = iter(tokens)
190 tokiter = iter(tokens)
190 for tok in tokiter:
191 for tok in tokiter:
191 if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
192 if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
192 _add_indent(tok.end[1])
193 _add_indent(tok.end[1])
193 elif (tok.type == tokenize.NL):
194 elif (tok.type == tokenize.NL):
194 try:
195 try:
195 _add_indent(next(tokiter).start[1])
196 _add_indent(next(tokiter).start[1])
196 except StopIteration:
197 except StopIteration:
197 break
198 break
198
199
199 last_indent = prev_indents.pop()
200 last_indent = prev_indents.pop()
200
201
201 # If we've just opened a multiline statement (e.g. 'a = ['), indent more
202 # If we've just opened a multiline statement (e.g. 'a = ['), indent more
202 if tokens[-1].type == IN_MULTILINE_STATEMENT:
203 if tokens[-1].type == IN_MULTILINE_STATEMENT:
203 if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
204 if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
204 return last_indent + 4
205 return last_indent + 4
205 return last_indent
206 return last_indent
206
207
207 if tokens[-1].exact_type == tokenize.COLON:
208 if tokens[-1].exact_type == tokenize.COLON:
208 # Line ends with colon - indent
209 # Line ends with colon - indent
209 return last_indent + 4
210 return last_indent + 4
210
211
211 if last_indent:
212 if last_indent:
212 # Examine the last line for dedent cues - statements like return or
213 # Examine the last line for dedent cues - statements like return or
213 # raise which normally end a block of code.
214 # raise which normally end a block of code.
214 last_line_starts = 0
215 last_line_starts = 0
215 for i, tok in enumerate(tokens):
216 for i, tok in enumerate(tokens):
216 if tok.type == tokenize.NEWLINE:
217 if tok.type == tokenize.NEWLINE:
217 last_line_starts = i + 1
218 last_line_starts = i + 1
218
219
219 last_line_tokens = tokens[last_line_starts:]
220 last_line_tokens = tokens[last_line_starts:]
220 names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
221 names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
221 if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
222 if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
222 # Find the most recent indentation less than the current level
223 # Find the most recent indentation less than the current level
223 for indent in reversed(prev_indents):
224 for indent in reversed(prev_indents):
224 if indent < last_indent:
225 if indent < last_indent:
225 return indent
226 return indent
226
227
227 return last_indent
228 return last_indent
228
229
229
230
230 def last_blank(src):
231 def last_blank(src):
231 """Determine if the input source ends in a blank.
232 """Determine if the input source ends in a blank.
232
233
233 A blank is either a newline or a line consisting of whitespace.
234 A blank is either a newline or a line consisting of whitespace.
234
235
235 Parameters
236 Parameters
236 ----------
237 ----------
237 src : string
238 src : string
238 A single or multiline string.
239 A single or multiline string.
239 """
240 """
240 if not src: return False
241 if not src: return False
241 ll = src.splitlines()[-1]
242 ll = src.splitlines()[-1]
242 return (ll == '') or ll.isspace()
243 return (ll == '') or ll.isspace()
243
244
244
245
245 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
246 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
246 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
247 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
247
248
248 def last_two_blanks(src):
249 def last_two_blanks(src):
249 """Determine if the input source ends in two blanks.
250 """Determine if the input source ends in two blanks.
250
251
251 A blank is either a newline or a line consisting of whitespace.
252 A blank is either a newline or a line consisting of whitespace.
252
253
253 Parameters
254 Parameters
254 ----------
255 ----------
255 src : string
256 src : string
256 A single or multiline string.
257 A single or multiline string.
257 """
258 """
258 if not src: return False
259 if not src: return False
259 # The logic here is tricky: I couldn't get a regexp to work and pass all
260 # The logic here is tricky: I couldn't get a regexp to work and pass all
260 # the tests, so I took a different approach: split the source by lines,
261 # the tests, so I took a different approach: split the source by lines,
261 # grab the last two and prepend '###\n' as a stand-in for whatever was in
262 # grab the last two and prepend '###\n' as a stand-in for whatever was in
262 # the body before the last two lines. Then, with that structure, it's
263 # the body before the last two lines. Then, with that structure, it's
263 # possible to analyze with two regexps. Not the most elegant solution, but
264 # possible to analyze with two regexps. Not the most elegant solution, but
264 # it works. If anyone tries to change this logic, make sure to validate
265 # it works. If anyone tries to change this logic, make sure to validate
265 # the whole test suite first!
266 # the whole test suite first!
266 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
267 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
267 return (bool(last_two_blanks_re.match(new_src)) or
268 return (bool(last_two_blanks_re.match(new_src)) or
268 bool(last_two_blanks_re2.match(new_src)) )
269 bool(last_two_blanks_re2.match(new_src)) )
269
270
270
271
271 def remove_comments(src):
272 def remove_comments(src):
272 """Remove all comments from input source.
273 """Remove all comments from input source.
273
274
274 Note: comments are NOT recognized inside of strings!
275 Note: comments are NOT recognized inside of strings!
275
276
276 Parameters
277 Parameters
277 ----------
278 ----------
278 src : string
279 src : string
279 A single or multiline input string.
280 A single or multiline input string.
280
281
281 Returns
282 Returns
282 -------
283 -------
283 String with all Python comments removed.
284 String with all Python comments removed.
284 """
285 """
285
286
286 return re.sub('#.*', '', src)
287 return re.sub('#.*', '', src)
287
288
288
289
289 def get_input_encoding():
290 def get_input_encoding():
290 """Return the default standard input encoding.
291 """Return the default standard input encoding.
291
292
292 If sys.stdin has no encoding, 'ascii' is returned."""
293 If sys.stdin has no encoding, 'ascii' is returned."""
293 # There are strange environments for which sys.stdin.encoding is None. We
294 # There are strange environments for which sys.stdin.encoding is None. We
294 # ensure that a valid encoding is returned.
295 # ensure that a valid encoding is returned.
295 encoding = getattr(sys.stdin, 'encoding', None)
296 encoding = getattr(sys.stdin, 'encoding', None)
296 if encoding is None:
297 if encoding is None:
297 encoding = 'ascii'
298 encoding = 'ascii'
298 return encoding
299 return encoding
299
300
300 #-----------------------------------------------------------------------------
301 #-----------------------------------------------------------------------------
301 # Classes and functions for normal Python syntax handling
302 # Classes and functions for normal Python syntax handling
302 #-----------------------------------------------------------------------------
303 #-----------------------------------------------------------------------------
303
304
304 class InputSplitter(object):
305 class InputSplitter(object):
305 r"""An object that can accumulate lines of Python source before execution.
306 r"""An object that can accumulate lines of Python source before execution.
306
307
307 This object is designed to be fed python source line-by-line, using
308 This object is designed to be fed python source line-by-line, using
308 :meth:`push`. It will return on each push whether the currently pushed
309 :meth:`push`. It will return on each push whether the currently pushed
309 code could be executed already. In addition, it provides a method called
310 code could be executed already. In addition, it provides a method called
310 :meth:`push_accepts_more` that can be used to query whether more input
311 :meth:`push_accepts_more` that can be used to query whether more input
311 can be pushed into a single interactive block.
312 can be pushed into a single interactive block.
312
313
313 This is a simple example of how an interactive terminal-based client can use
314 This is a simple example of how an interactive terminal-based client can use
314 this tool::
315 this tool::
315
316
316 isp = InputSplitter()
317 isp = InputSplitter()
317 while isp.push_accepts_more():
318 while isp.push_accepts_more():
318 indent = ' '*isp.indent_spaces
319 indent = ' '*isp.indent_spaces
319 prompt = '>>> ' + indent
320 prompt = '>>> ' + indent
320 line = indent + raw_input(prompt)
321 line = indent + raw_input(prompt)
321 isp.push(line)
322 isp.push(line)
322 print 'Input source was:\n', isp.source_reset(),
323 print 'Input source was:\n', isp.source_reset(),
323 """
324 """
324 # A cache for storing the current indentation
325 # A cache for storing the current indentation
325 # The first value stores the most recently processed source input
326 # The first value stores the most recently processed source input
326 # The second value is the number of spaces for the current indentation
327 # The second value is the number of spaces for the current indentation
327 # If self.source matches the first value, the second value is a valid
328 # If self.source matches the first value, the second value is a valid
328 # current indentation. Otherwise, the cache is invalid and the indentation
329 # current indentation. Otherwise, the cache is invalid and the indentation
329 # must be recalculated.
330 # must be recalculated.
330 _indent_spaces_cache: Union[Tuple[None, None], Tuple[str, int]] = None, None
331 _indent_spaces_cache: Union[Tuple[None, None], Tuple[str, int]] = None, None
331 # String, indicating the default input encoding. It is computed by default
332 # String, indicating the default input encoding. It is computed by default
332 # at initialization time via get_input_encoding(), but it can be reset by a
333 # at initialization time via get_input_encoding(), but it can be reset by a
333 # client with specific knowledge of the encoding.
334 # client with specific knowledge of the encoding.
334 encoding = ''
335 encoding = ''
335 # String where the current full source input is stored, properly encoded.
336 # String where the current full source input is stored, properly encoded.
336 # Reading this attribute is the normal way of querying the currently pushed
337 # Reading this attribute is the normal way of querying the currently pushed
337 # source code, that has been properly encoded.
338 # source code, that has been properly encoded.
338 source: str = ""
339 source: str = ""
339 # Code object corresponding to the current source. It is automatically
340 # Code object corresponding to the current source. It is automatically
340 # synced to the source, so it can be queried at any time to obtain the code
341 # synced to the source, so it can be queried at any time to obtain the code
341 # object; it will be None if the source doesn't compile to valid Python.
342 # object; it will be None if the source doesn't compile to valid Python.
342 code: Optional[CodeType] = None
343 code: Optional[CodeType] = None
343
344
344 # Private attributes
345 # Private attributes
345
346
346 # List with lines of input accumulated so far
347 # List with lines of input accumulated so far
347 _buffer: List[str]
348 _buffer: List[str]
348 # Command compiler
349 # Command compiler
349 _compile: codeop.CommandCompiler
350 _compile: codeop.CommandCompiler
350 # Boolean indicating whether the current block is complete
351 # Boolean indicating whether the current block is complete
351 _is_complete: Optional[bool] = None
352 _is_complete: Optional[bool] = None
352 # Boolean indicating whether the current block has an unrecoverable syntax error
353 # Boolean indicating whether the current block has an unrecoverable syntax error
353 _is_invalid: bool = False
354 _is_invalid: bool = False
354
355
355 def __init__(self) -> None:
356 def __init__(self) -> None:
356 """Create a new InputSplitter instance."""
357 """Create a new InputSplitter instance."""
357 self._buffer = []
358 self._buffer = []
358 self._compile = codeop.CommandCompiler()
359 self._compile = codeop.CommandCompiler()
359 self.encoding = get_input_encoding()
360 self.encoding = get_input_encoding()
360
361
361 def reset(self):
362 def reset(self):
362 """Reset the input buffer and associated state."""
363 """Reset the input buffer and associated state."""
363 self._buffer[:] = []
364 self._buffer[:] = []
364 self.source = ''
365 self.source = ''
365 self.code = None
366 self.code = None
366 self._is_complete = False
367 self._is_complete = False
367 self._is_invalid = False
368 self._is_invalid = False
368
369
369 def source_reset(self):
370 def source_reset(self):
370 """Return the input source and perform a full reset.
371 """Return the input source and perform a full reset.
371 """
372 """
372 out = self.source
373 out = self.source
373 self.reset()
374 self.reset()
374 return out
375 return out
375
376
376 def check_complete(self, source):
377 def check_complete(self, source):
377 """Return whether a block of code is ready to execute, or should be continued
378 """Return whether a block of code is ready to execute, or should be continued
378
379
379 This is a non-stateful API, and will reset the state of this InputSplitter.
380 This is a non-stateful API, and will reset the state of this InputSplitter.
380
381
381 Parameters
382 Parameters
382 ----------
383 ----------
383 source : string
384 source : string
384 Python input code, which can be multiline.
385 Python input code, which can be multiline.
385
386
386 Returns
387 Returns
387 -------
388 -------
388 status : str
389 status : str
389 One of 'complete', 'incomplete', or 'invalid' if source is not a
390 One of 'complete', 'incomplete', or 'invalid' if source is not a
390 prefix of valid code.
391 prefix of valid code.
391 indent_spaces : int or None
392 indent_spaces : int or None
392 The number of spaces by which to indent the next line of code. If
393 The number of spaces by which to indent the next line of code. If
393 status is not 'incomplete', this is None.
394 status is not 'incomplete', this is None.
394 """
395 """
395 self.reset()
396 self.reset()
396 try:
397 try:
397 self.push(source)
398 self.push(source)
398 except SyntaxError:
399 except SyntaxError:
399 # Transformers in IPythonInputSplitter can raise SyntaxError,
400 # Transformers in IPythonInputSplitter can raise SyntaxError,
400 # which push() will not catch.
401 # which push() will not catch.
401 return 'invalid', None
402 return 'invalid', None
402 else:
403 else:
403 if self._is_invalid:
404 if self._is_invalid:
404 return 'invalid', None
405 return 'invalid', None
405 elif self.push_accepts_more():
406 elif self.push_accepts_more():
406 return 'incomplete', self.get_indent_spaces()
407 return 'incomplete', self.get_indent_spaces()
407 else:
408 else:
408 return 'complete', None
409 return 'complete', None
409 finally:
410 finally:
410 self.reset()
411 self.reset()
411
412
412 def push(self, lines:str) -> bool:
413 def push(self, lines:str) -> bool:
413 """Push one or more lines of input.
414 """Push one or more lines of input.
414
415
415 This stores the given lines and returns a status code indicating
416 This stores the given lines and returns a status code indicating
416 whether the code forms a complete Python block or not.
417 whether the code forms a complete Python block or not.
417
418
418 Any exceptions generated in compilation are swallowed, but if an
419 Any exceptions generated in compilation are swallowed, but if an
419 exception was produced, the method returns True.
420 exception was produced, the method returns True.
420
421
421 Parameters
422 Parameters
422 ----------
423 ----------
423 lines : string
424 lines : string
424 One or more lines of Python input.
425 One or more lines of Python input.
425
426
426 Returns
427 Returns
427 -------
428 -------
428 is_complete : boolean
429 is_complete : boolean
429 True if the current input source (the result of the current input
430 True if the current input source (the result of the current input
430 plus prior inputs) forms a complete Python execution block. Note that
431 plus prior inputs) forms a complete Python execution block. Note that
431 this value is also stored as a private attribute (``_is_complete``), so it
432 this value is also stored as a private attribute (``_is_complete``), so it
432 can be queried at any time.
433 can be queried at any time.
433 """
434 """
434 assert isinstance(lines, str)
435 assert isinstance(lines, str)
435 self._store(lines)
436 self._store(lines)
436 source = self.source
437 source = self.source
437
438
438 # Before calling _compile(), reset the code object to None so that if an
439 # Before calling _compile(), reset the code object to None so that if an
439 # exception is raised in compilation, we don't mislead by having
440 # exception is raised in compilation, we don't mislead by having
440 # inconsistent code/source attributes.
441 # inconsistent code/source attributes.
441 self.code, self._is_complete = None, None
442 self.code, self._is_complete = None, None
442 self._is_invalid = False
443 self._is_invalid = False
443
444
444 # Honor termination lines properly
445 # Honor termination lines properly
445 if source.endswith('\\\n'):
446 if source.endswith('\\\n'):
446 return False
447 return False
447
448
448 try:
449 try:
449 with warnings.catch_warnings():
450 with warnings.catch_warnings():
450 warnings.simplefilter('error', SyntaxWarning)
451 warnings.simplefilter('error', SyntaxWarning)
451 self.code = self._compile(source, symbol="exec")
452 self.code = self._compile(source, symbol="exec")
452 # Invalid syntax can produce any of a number of different errors from
453 # Invalid syntax can produce any of a number of different errors from
453 # inside the compiler, so we have to catch them all. Syntax errors
454 # inside the compiler, so we have to catch them all. Syntax errors
454 # immediately produce a 'ready' block, so the invalid Python can be
455 # immediately produce a 'ready' block, so the invalid Python can be
455 # sent to the kernel for evaluation with possible ipython
456 # sent to the kernel for evaluation with possible ipython
456 # special-syntax conversion.
457 # special-syntax conversion.
457 except (SyntaxError, OverflowError, ValueError, TypeError,
458 except (SyntaxError, OverflowError, ValueError, TypeError,
458 MemoryError, SyntaxWarning):
459 MemoryError, SyntaxWarning):
459 self._is_complete = True
460 self._is_complete = True
460 self._is_invalid = True
461 self._is_invalid = True
461 else:
462 else:
462 # Compilation didn't produce any exceptions (though it may not have
463 # Compilation didn't produce any exceptions (though it may not have
463 # given a complete code object)
464 # given a complete code object)
464 self._is_complete = self.code is not None
465 self._is_complete = self.code is not None
465
466
466 return self._is_complete
467 return self._is_complete
467
468
468 def push_accepts_more(self):
469 def push_accepts_more(self):
469 """Return whether a block of interactive input can accept more input.
470 """Return whether a block of interactive input can accept more input.
470
471
471 This method is meant to be used by line-oriented frontends, who need to
472 This method is meant to be used by line-oriented frontends, who need to
472 guess whether a block is complete or not based solely on prior and
473 guess whether a block is complete or not based solely on prior and
473 current input lines. The InputSplitter considers it has a complete
474 current input lines. The InputSplitter considers it has a complete
474 interactive block and will not accept more input when either:
475 interactive block and will not accept more input when either:
475
476
476 * A SyntaxError is raised
477 * A SyntaxError is raised
477
478
478 * The code is complete and consists of a single line or a single
479 * The code is complete and consists of a single line or a single
479 non-compound statement
480 non-compound statement
480
481
481 * The code is complete and has a blank line at the end
482 * The code is complete and has a blank line at the end
482
483
483 If the current input produces a syntax error, this method immediately
484 If the current input produces a syntax error, this method immediately
484 returns False but does *not* raise the syntax error exception, as
485 returns False but does *not* raise the syntax error exception, as
485 typically clients will want to send invalid syntax to an execution
486 typically clients will want to send invalid syntax to an execution
486 backend which might convert the invalid syntax into valid Python via
487 backend which might convert the invalid syntax into valid Python via
487 one of the dynamic IPython mechanisms.
488 one of the dynamic IPython mechanisms.
488 """
489 """
489
490
490 # With incomplete input, unconditionally accept more
491 # With incomplete input, unconditionally accept more
491 # A syntax error also sets _is_complete to True - see push()
492 # A syntax error also sets _is_complete to True - see push()
492 if not self._is_complete:
493 if not self._is_complete:
493 #print("Not complete") # debug
494 #print("Not complete") # debug
494 return True
495 return True
495
496
496 # The user can make any (complete) input execute by leaving a blank line
497 # The user can make any (complete) input execute by leaving a blank line
497 last_line = self.source.splitlines()[-1]
498 last_line = self.source.splitlines()[-1]
498 if (not last_line) or last_line.isspace():
499 if (not last_line) or last_line.isspace():
499 #print("Blank line") # debug
500 #print("Blank line") # debug
500 return False
501 return False
501
502
502 # If there's just a single line or AST node, and we're flush left, as is
503 # If there's just a single line or AST node, and we're flush left, as is
503 # the case after a simple statement such as 'a=1', we want to execute it
504 # the case after a simple statement such as 'a=1', we want to execute it
504 # straight away.
505 # straight away.
505 if self.get_indent_spaces() == 0:
506 if self.get_indent_spaces() == 0:
506 if len(self.source.splitlines()) <= 1:
507 if len(self.source.splitlines()) <= 1:
507 return False
508 return False
508
509
509 try:
510 try:
510 code_ast = ast.parse("".join(self._buffer))
511 code_ast = ast.parse("".join(self._buffer))
511 except Exception:
512 except Exception:
512 #print("Can't parse AST") # debug
513 #print("Can't parse AST") # debug
513 return False
514 return False
514 else:
515 else:
515 if len(code_ast.body) == 1 and \
516 if len(code_ast.body) == 1 and \
516 not hasattr(code_ast.body[0], 'body'):
517 not hasattr(code_ast.body[0], 'body'):
517 #print("Simple statement") # debug
518 #print("Simple statement") # debug
518 return False
519 return False
519
520
520 # General fallback - accept more code
521 # General fallback - accept more code
521 return True
522 return True
522
523
523 def get_indent_spaces(self) -> int:
524 def get_indent_spaces(self) -> int:
524 sourcefor, n = self._indent_spaces_cache
525 sourcefor, n = self._indent_spaces_cache
525 if sourcefor == self.source:
526 if sourcefor == self.source:
526 assert n is not None
527 assert n is not None
527 return n
528 return n
528
529
529 # self.source always has a trailing newline
530 # self.source always has a trailing newline
530 n = find_next_indent(self.source[:-1])
531 n = find_next_indent(self.source[:-1])
531 self._indent_spaces_cache = (self.source, n)
532 self._indent_spaces_cache = (self.source, n)
532 return n
533 return n
533
534
534 # Backwards compatibility. I think all code that used .indent_spaces was
535 # Backwards compatibility. I think all code that used .indent_spaces was
535 # inside IPython, but we can leave this here until IPython 7 in case any
536 # inside IPython, but we can leave this here until IPython 7 in case any
536 # other modules are using it. -TK, November 2017
537 # other modules are using it. -TK, November 2017
537 indent_spaces = property(get_indent_spaces)
538 indent_spaces = property(get_indent_spaces)
538
539
539 def _store(self, lines, buffer=None, store='source'):
540 def _store(self, lines, buffer=None, store='source'):
540 """Store one or more lines of input.
541 """Store one or more lines of input.
541
542
542 If input lines are not newline-terminated, a newline is automatically
543 If input lines are not newline-terminated, a newline is automatically
543 appended."""
544 appended."""
544
545
545 if buffer is None:
546 if buffer is None:
546 buffer = self._buffer
547 buffer = self._buffer
547
548
548 if lines.endswith('\n'):
549 if lines.endswith('\n'):
549 buffer.append(lines)
550 buffer.append(lines)
550 else:
551 else:
551 buffer.append(lines+'\n')
552 buffer.append(lines+'\n')
552 setattr(self, store, self._set_source(buffer))
553 setattr(self, store, self._set_source(buffer))
553
554
554 def _set_source(self, buffer):
555 def _set_source(self, buffer):
555 return u''.join(buffer)
556 return u''.join(buffer)
556
557
557
558
558 class IPythonInputSplitter(InputSplitter):
559 class IPythonInputSplitter(InputSplitter):
559 """An input splitter that recognizes all of IPython's special syntax."""
560 """An input splitter that recognizes all of IPython's special syntax."""
560
561
561 # String with raw, untransformed input.
562 # String with raw, untransformed input.
562 source_raw = ''
563 source_raw = ''
563
564
564 # Flag to track when a transformer has stored input that it hasn't given
565 # Flag to track when a transformer has stored input that it hasn't given
565 # back yet.
566 # back yet.
566 transformer_accumulating = False
567 transformer_accumulating = False
567
568
568 # Flag to track when assemble_python_lines has stored input that it hasn't
569 # Flag to track when assemble_python_lines has stored input that it hasn't
569 # given back yet.
570 # given back yet.
570 within_python_line = False
571 within_python_line = False
571
572
572 # Private attributes
573 # Private attributes
573
574
574 # List with lines of raw input accumulated so far.
575 # List with lines of raw input accumulated so far.
575 _buffer_raw: List[str]
576 _buffer_raw: List[str]
576
577
577 def __init__(self, line_input_checker=True, physical_line_transforms=None,
578 def __init__(self, line_input_checker=True, physical_line_transforms=None,
578 logical_line_transforms=None, python_line_transforms=None):
579 logical_line_transforms=None, python_line_transforms=None):
579 super(IPythonInputSplitter, self).__init__()
580 super(IPythonInputSplitter, self).__init__()
580 self._buffer_raw = []
581 self._buffer_raw = []
581 self._validate = True
582 self._validate = True
582
583
583 if physical_line_transforms is not None:
584 if physical_line_transforms is not None:
584 self.physical_line_transforms = physical_line_transforms
585 self.physical_line_transforms = physical_line_transforms
585 else:
586 else:
586 self.physical_line_transforms = [
587 self.physical_line_transforms = [
587 leading_indent(),
588 leading_indent(),
588 classic_prompt(),
589 classic_prompt(),
589 ipy_prompt(),
590 ipy_prompt(),
590 cellmagic(end_on_blank_line=line_input_checker),
591 cellmagic(end_on_blank_line=line_input_checker),
591 ]
592 ]
592
593
593 self.assemble_logical_lines = assemble_logical_lines()
594 self.assemble_logical_lines = assemble_logical_lines()
594 if logical_line_transforms is not None:
595 if logical_line_transforms is not None:
595 self.logical_line_transforms = logical_line_transforms
596 self.logical_line_transforms = logical_line_transforms
596 else:
597 else:
597 self.logical_line_transforms = [
598 self.logical_line_transforms = [
598 help_end(),
599 help_end(),
599 escaped_commands(),
600 escaped_commands(),
600 assign_from_magic(),
601 assign_from_magic(),
601 assign_from_system(),
602 assign_from_system(),
602 ]
603 ]
603
604
604 self.assemble_python_lines = assemble_python_lines()
605 self.assemble_python_lines = assemble_python_lines()
605 if python_line_transforms is not None:
606 if python_line_transforms is not None:
606 self.python_line_transforms = python_line_transforms
607 self.python_line_transforms = python_line_transforms
607 else:
608 else:
608 # We don't use any of these at present
609 # We don't use any of these at present
609 self.python_line_transforms = []
610 self.python_line_transforms = []
610
611
611 @property
612 @property
612 def transforms(self):
613 def transforms(self):
613 "Quick access to all transformers."
614 "Quick access to all transformers."
614 return self.physical_line_transforms + \
615 return self.physical_line_transforms + \
615 [self.assemble_logical_lines] + self.logical_line_transforms + \
616 [self.assemble_logical_lines] + self.logical_line_transforms + \
616 [self.assemble_python_lines] + self.python_line_transforms
617 [self.assemble_python_lines] + self.python_line_transforms
617
618
618 @property
619 @property
619 def transforms_in_use(self):
620 def transforms_in_use(self):
620 """Transformers, excluding logical line transformers if we're in a
621 """Transformers, excluding logical line transformers if we're in a
621 Python line."""
622 Python line."""
622 t = self.physical_line_transforms[:]
623 t = self.physical_line_transforms[:]
623 if not self.within_python_line:
624 if not self.within_python_line:
624 t += [self.assemble_logical_lines] + self.logical_line_transforms
625 t += [self.assemble_logical_lines] + self.logical_line_transforms
625 return t + [self.assemble_python_lines] + self.python_line_transforms
626 return t + [self.assemble_python_lines] + self.python_line_transforms
626
627
627 def reset(self):
628 def reset(self):
628 """Reset the input buffer and associated state."""
629 """Reset the input buffer and associated state."""
629 super(IPythonInputSplitter, self).reset()
630 super(IPythonInputSplitter, self).reset()
630 self._buffer_raw[:] = []
631 self._buffer_raw[:] = []
631 self.source_raw = ''
632 self.source_raw = ''
632 self.transformer_accumulating = False
633 self.transformer_accumulating = False
633 self.within_python_line = False
634 self.within_python_line = False
634
635
635 for t in self.transforms:
636 for t in self.transforms:
636 try:
637 try:
637 t.reset()
638 t.reset()
638 except SyntaxError:
639 except SyntaxError:
639 # Nothing that calls reset() expects to handle transformer
640 # Nothing that calls reset() expects to handle transformer
640 # errors
641 # errors
641 pass
642 pass
642
643
643 def flush_transformers(self: Self):
644 def flush_transformers(self: Self):
644 def _flush(transform, outs: List[str]):
645 def _flush(transform, outs: List[str]):
645 """yield transformed lines
646 """yield transformed lines
646
647
647 always strings, never None
648 always strings, never None
648
649
649 transform: the current transform
650 transform: the current transform
650 outs: an iterable of previously transformed inputs.
651 outs: an iterable of previously transformed inputs.
651 Each may be multiline, which will be passed
652 Each may be multiline, which will be passed
652 one line at a time to transform.
653 one line at a time to transform.
653 """
654 """
654 for out in outs:
655 for out in outs:
655 for line in out.splitlines():
656 for line in out.splitlines():
656 # push one line at a time
657 # push one line at a time
657 tmp = transform.push(line)
658 tmp = transform.push(line)
658 if tmp is not None:
659 if tmp is not None:
659 yield tmp
660 yield tmp
660
661
661 # reset the transform
662 # reset the transform
662 tmp = transform.reset()
663 tmp = transform.reset()
663 if tmp is not None:
664 if tmp is not None:
664 yield tmp
665 yield tmp
665
666
666 out: List[str] = []
667 out: List[str] = []
667 for t in self.transforms_in_use:
668 for t in self.transforms_in_use:
668 out = _flush(t, out)
669 out = _flush(t, out)
669
670
670 out = list(out)
671 out = list(out)
671 if out:
672 if out:
672 self._store('\n'.join(out))
673 self._store('\n'.join(out))
673
674
674 def raw_reset(self):
675 def raw_reset(self):
675 """Return raw input only and perform a full reset.
676 """Return raw input only and perform a full reset.
676 """
677 """
677 out = self.source_raw
678 out = self.source_raw
678 self.reset()
679 self.reset()
679 return out
680 return out
680
681
681 def source_reset(self):
682 def source_reset(self):
682 try:
683 try:
683 self.flush_transformers()
684 self.flush_transformers()
684 return self.source
685 return self.source
685 finally:
686 finally:
686 self.reset()
687 self.reset()
687
688
688 def push_accepts_more(self):
689 def push_accepts_more(self):
689 if self.transformer_accumulating:
690 if self.transformer_accumulating:
690 return True
691 return True
691 else:
692 else:
692 return super(IPythonInputSplitter, self).push_accepts_more()
693 return super(IPythonInputSplitter, self).push_accepts_more()
693
694
694 def transform_cell(self, cell):
695 def transform_cell(self, cell):
695 """Process and translate a cell of input.
696 """Process and translate a cell of input.
696 """
697 """
697 self.reset()
698 self.reset()
698 try:
699 try:
699 self.push(cell)
700 self.push(cell)
700 self.flush_transformers()
701 self.flush_transformers()
701 return self.source
702 return self.source
702 finally:
703 finally:
703 self.reset()
704 self.reset()
704
705
705 def push(self, lines:str) -> bool:
706 def push(self, lines:str) -> bool:
706 """Push one or more lines of IPython input.
707 """Push one or more lines of IPython input.
707
708
708 This stores the given lines and returns a status code indicating
709 This stores the given lines and returns a status code indicating
709 whether the code forms a complete Python block or not, after processing
710 whether the code forms a complete Python block or not, after processing
710 all input lines for special IPython syntax.
711 all input lines for special IPython syntax.
711
712
712 Any exceptions generated in compilation are swallowed, but if an
713 Any exceptions generated in compilation are swallowed, but if an
713 exception was produced, the method returns True.
714 exception was produced, the method returns True.
714
715
715 Parameters
716 Parameters
716 ----------
717 ----------
717 lines : string
718 lines : string
718 One or more lines of Python input.
719 One or more lines of Python input.
719
720
720 Returns
721 Returns
721 -------
722 -------
722 is_complete : boolean
723 is_complete : boolean
723 True if the current input source (the result of the current input
724 True if the current input source (the result of the current input
724 plus prior inputs) forms a complete Python execution block. Note that
725 plus prior inputs) forms a complete Python execution block. Note that
725 this value is also stored as a private attribute (_is_complete), so it
726 this value is also stored as a private attribute (_is_complete), so it
726 can be queried at any time.
727 can be queried at any time.
727 """
728 """
728 assert isinstance(lines, str)
729 assert isinstance(lines, str)
729 # We must ensure all input is pure unicode
730 # We must ensure all input is pure unicode
730 # ''.splitlines() --> [], but we need to push the empty line to transformers
731 # ''.splitlines() --> [], but we need to push the empty line to transformers
731 lines_list = lines.splitlines()
732 lines_list = lines.splitlines()
732 if not lines_list:
733 if not lines_list:
733 lines_list = ['']
734 lines_list = ['']
734
735
735 # Store raw source before applying any transformations to it. Note
736 # Store raw source before applying any transformations to it. Note
736 # that this must be done *after* the reset() call that would otherwise
737 # that this must be done *after* the reset() call that would otherwise
737 # flush the buffer.
738 # flush the buffer.
738 self._store(lines, self._buffer_raw, 'source_raw')
739 self._store(lines, self._buffer_raw, 'source_raw')
739
740
740 transformed_lines_list = []
741 transformed_lines_list = []
741 for line in lines_list:
742 for line in lines_list:
742 transformed = self._transform_line(line)
743 transformed = self._transform_line(line)
743 if transformed is not None:
744 if transformed is not None:
744 transformed_lines_list.append(transformed)
745 transformed_lines_list.append(transformed)
745
746
746 if transformed_lines_list:
747 if transformed_lines_list:
747 transformed_lines = '\n'.join(transformed_lines_list)
748 transformed_lines = '\n'.join(transformed_lines_list)
748 return super(IPythonInputSplitter, self).push(transformed_lines)
749 return super(IPythonInputSplitter, self).push(transformed_lines)
749 else:
750 else:
750 # Got nothing back from transformers - they must be waiting for
751 # Got nothing back from transformers - they must be waiting for
751 # more input.
752 # more input.
752 return False
753 return False
753
754
754 def _transform_line(self, line):
755 def _transform_line(self, line):
755 """Push a line of input code through the various transformers.
756 """Push a line of input code through the various transformers.
756
757
757 Returns any output from the transformers, or None if a transformer
758 Returns any output from the transformers, or None if a transformer
758 is accumulating lines.
759 is accumulating lines.
759
760
760 Sets self.transformer_accumulating as a side effect.
761 Sets self.transformer_accumulating as a side effect.
761 """
762 """
762 def _accumulating(dbg):
763 def _accumulating(dbg):
763 #print(dbg)
764 #print(dbg)
764 self.transformer_accumulating = True
765 self.transformer_accumulating = True
765 return None
766 return None
766
767
767 for transformer in self.physical_line_transforms:
768 for transformer in self.physical_line_transforms:
768 line = transformer.push(line)
769 line = transformer.push(line)
769 if line is None:
770 if line is None:
770 return _accumulating(transformer)
771 return _accumulating(transformer)
771
772
772 if not self.within_python_line:
773 if not self.within_python_line:
773 line = self.assemble_logical_lines.push(line)
774 line = self.assemble_logical_lines.push(line)
774 if line is None:
775 if line is None:
775 return _accumulating('acc logical line')
776 return _accumulating('acc logical line')
776
777
777 for transformer in self.logical_line_transforms:
778 for transformer in self.logical_line_transforms:
778 line = transformer.push(line)
779 line = transformer.push(line)
779 if line is None:
780 if line is None:
780 return _accumulating(transformer)
781 return _accumulating(transformer)
781
782
782 line = self.assemble_python_lines.push(line)
783 line = self.assemble_python_lines.push(line)
783 if line is None:
784 if line is None:
784 self.within_python_line = True
785 self.within_python_line = True
785 return _accumulating('acc python line')
786 return _accumulating('acc python line')
786 else:
787 else:
787 self.within_python_line = False
788 self.within_python_line = False
788
789
789 for transformer in self.python_line_transforms:
790 for transformer in self.python_line_transforms:
790 line = transformer.push(line)
791 line = transformer.push(line)
791 if line is None:
792 if line is None:
792 return _accumulating(transformer)
793 return _accumulating(transformer)
793
794
794 #print("transformers clear") #debug
795 #print("transformers clear") #debug
795 self.transformer_accumulating = False
796 self.transformer_accumulating = False
796 return line
797 return line
797
798
General Comments 0
You need to be logged in to leave comments. Login now