Show More
@@ -166,78 +166,6 b' def get_input_encoding():' | |||||
166 | # Classes and functions for normal Python syntax handling |
|
166 | # Classes and functions for normal Python syntax handling | |
167 | #----------------------------------------------------------------------------- |
|
167 | #----------------------------------------------------------------------------- | |
168 |
|
168 | |||
169 | # HACK! This implementation, written by Robert K a while ago using the |
|
|||
170 | # compiler module, is more robust than the other one below, but it expects its |
|
|||
171 | # input to be pure python (no ipython syntax). For now we're using it as a |
|
|||
172 | # second-pass splitter after the first pass transforms the input to pure |
|
|||
173 | # python. |
|
|||
174 |
|
||||
175 | def split_blocks(python): |
|
|||
176 | """ Split multiple lines of code into discrete commands that can be |
|
|||
177 | executed singly. |
|
|||
178 |
|
||||
179 | Parameters |
|
|||
180 | ---------- |
|
|||
181 | python : str |
|
|||
182 | Pure, exec'able Python code. |
|
|||
183 |
|
||||
184 | Returns |
|
|||
185 | ------- |
|
|||
186 | commands : list of str |
|
|||
187 | Separate commands that can be exec'ed independently. |
|
|||
188 | """ |
|
|||
189 | # compiler.parse treats trailing spaces after a newline as a |
|
|||
190 | # SyntaxError. This is different than codeop.CommandCompiler, which |
|
|||
191 | # will compile the trailng spaces just fine. We simply strip any |
|
|||
192 | # trailing whitespace off. Passing a string with trailing whitespace |
|
|||
193 | # to exec will fail however. There seems to be some inconsistency in |
|
|||
194 | # how trailing whitespace is handled, but this seems to work. |
|
|||
195 | python_ori = python # save original in case we bail on error |
|
|||
196 | python = python.strip() |
|
|||
197 |
|
||||
198 | # The compiler module will parse the code into an abstract syntax tree. |
|
|||
199 | # This has a bug with str("a\nb"), but not str("""a\nb""")!!! |
|
|||
200 | try: |
|
|||
201 | code_ast = ast.parse(python) |
|
|||
202 | except: |
|
|||
203 | return [python_ori] |
|
|||
204 |
|
||||
205 | # Uncomment to help debug the ast tree |
|
|||
206 | # for n in code_ast.body: |
|
|||
207 | # print n.lineno,'->',n |
|
|||
208 |
|
||||
209 | # Each separate command is available by iterating over ast.node. The |
|
|||
210 | # lineno attribute is the line number (1-indexed) beginning the commands |
|
|||
211 | # suite. |
|
|||
212 | # lines ending with ";" yield a Discard Node that doesn't have a lineno |
|
|||
213 | # attribute. These nodes can and should be discarded. But there are |
|
|||
214 | # other situations that cause Discard nodes that shouldn't be discarded. |
|
|||
215 | # We might eventually discover other cases where lineno is None and have |
|
|||
216 | # to put in a more sophisticated test. |
|
|||
217 | linenos = [x.lineno-1 for x in code_ast.body if x.lineno is not None] |
|
|||
218 |
|
||||
219 | # When we finally get the slices, we will need to slice all the way to |
|
|||
220 | # the end even though we don't have a line number for it. Fortunately, |
|
|||
221 | # None does the job nicely. |
|
|||
222 | linenos.append(None) |
|
|||
223 |
|
||||
224 | # Same problem at the other end: sometimes the ast tree has its |
|
|||
225 | # first complete statement not starting on line 0. In this case |
|
|||
226 | # we might miss part of it. This fixes ticket 266993. Thanks Gael! |
|
|||
227 | linenos[0] = 0 |
|
|||
228 |
|
||||
229 | lines = python.splitlines() |
|
|||
230 |
|
||||
231 | # Create a list of atomic commands. |
|
|||
232 | cmds = [] |
|
|||
233 | for i, j in zip(linenos[:-1], linenos[1:]): |
|
|||
234 | cmd = lines[i:j] |
|
|||
235 | if cmd: |
|
|||
236 | cmds.append('\n'.join(cmd)+'\n') |
|
|||
237 |
|
||||
238 | return cmds |
|
|||
239 |
|
||||
240 |
|
||||
241 | class InputSplitter(object): |
|
169 | class InputSplitter(object): | |
242 | """An object that can split Python source input in executable blocks. |
|
170 | """An object that can split Python source input in executable blocks. | |
243 |
|
171 | |||
@@ -457,88 +385,6 b' class InputSplitter(object):' | |||||
457 | # line at the end. |
|
385 | # line at the end. | |
458 | last_line = self.source.splitlines()[-1] |
|
386 | last_line = self.source.splitlines()[-1] | |
459 | return bool(last_line and not last_line.isspace()) |
|
387 | return bool(last_line and not last_line.isspace()) | |
460 |
|
||||
461 | def split_blocks(self, lines): |
|
|||
462 | """Split a multiline string into multiple input blocks. |
|
|||
463 |
|
||||
464 | Note: this method starts by performing a full reset(). |
|
|||
465 |
|
||||
466 | Parameters |
|
|||
467 | ---------- |
|
|||
468 | lines : str |
|
|||
469 | A possibly multiline string. |
|
|||
470 |
|
||||
471 | Returns |
|
|||
472 | ------- |
|
|||
473 | blocks : list |
|
|||
474 | A list of strings, each possibly multiline. Each string corresponds |
|
|||
475 | to a single block that can be compiled in 'single' mode (unless it |
|
|||
476 | has a syntax error).""" |
|
|||
477 |
|
||||
478 | # This code is fairly delicate. If you make any changes here, make |
|
|||
479 | # absolutely sure that you do run the full test suite and ALL tests |
|
|||
480 | # pass. |
|
|||
481 |
|
||||
482 | self.reset() |
|
|||
483 | blocks = [] |
|
|||
484 |
|
||||
485 | # Reversed copy so we can use pop() efficiently and consume the input |
|
|||
486 | # as a stack |
|
|||
487 | lines = lines.splitlines()[::-1] |
|
|||
488 | # Outer loop over all input |
|
|||
489 | while lines: |
|
|||
490 | #print 'Current lines:', lines # dbg |
|
|||
491 | # Inner loop to build each block |
|
|||
492 | while True: |
|
|||
493 | # Safety exit from inner loop |
|
|||
494 | if not lines: |
|
|||
495 | break |
|
|||
496 | # Grab next line but don't push it yet |
|
|||
497 | next_line = lines.pop() |
|
|||
498 | # Blank/empty lines are pushed as-is |
|
|||
499 | if not next_line or next_line.isspace(): |
|
|||
500 | self.push(next_line) |
|
|||
501 | continue |
|
|||
502 |
|
||||
503 | # Check indentation changes caused by the *next* line |
|
|||
504 | indent_spaces, _full_dedent = self._find_indent(next_line) |
|
|||
505 |
|
||||
506 | # If the next line causes a dedent, it can be for two differnt |
|
|||
507 | # reasons: either an explicit de-dent by the user or a |
|
|||
508 | # return/raise/pass statement. These MUST be handled |
|
|||
509 | # separately: |
|
|||
510 | # |
|
|||
511 | # 1. the first case is only detected when the actual explicit |
|
|||
512 | # dedent happens, and that would be the *first* line of a *new* |
|
|||
513 | # block. Thus, we must put the line back into the input buffer |
|
|||
514 | # so that it starts a new block on the next pass. |
|
|||
515 | # |
|
|||
516 | # 2. the second case is detected in the line before the actual |
|
|||
517 | # dedent happens, so , we consume the line and we can break out |
|
|||
518 | # to start a new block. |
|
|||
519 |
|
||||
520 | # Case 1, explicit dedent causes a break. |
|
|||
521 | # Note: check that we weren't on the very last line, else we'll |
|
|||
522 | # enter an infinite loop adding/removing the last line. |
|
|||
523 | if _full_dedent and lines and not next_line.startswith(' '): |
|
|||
524 | lines.append(next_line) |
|
|||
525 | break |
|
|||
526 |
|
||||
527 | # Otherwise any line is pushed |
|
|||
528 | self.push(next_line) |
|
|||
529 |
|
||||
530 | # Case 2, full dedent with full block ready: |
|
|||
531 | if _full_dedent or \ |
|
|||
532 | self.indent_spaces==0 and not self.push_accepts_more(): |
|
|||
533 | break |
|
|||
534 | # Form the new block with the current source input |
|
|||
535 | blocks.append(self.source_reset()) |
|
|||
536 |
|
||||
537 | #return blocks |
|
|||
538 | # HACK!!! Now that our input is in blocks but guaranteed to be pure |
|
|||
539 | # python syntax, feed it back a second time through the AST-based |
|
|||
540 | # splitter, which is more accurate than ours. |
|
|||
541 | return split_blocks(''.join(blocks)) |
|
|||
542 |
|
388 | |||
543 | #------------------------------------------------------------------------ |
|
389 | #------------------------------------------------------------------------ | |
544 | # Private interface |
|
390 | # Private interface |
@@ -286,97 +286,6 b' class InputSplitterTestCase(unittest.TestCase):' | |||||
286 | isp.push('run foo') |
|
286 | isp.push('run foo') | |
287 | self.assertFalse(isp.push_accepts_more()) |
|
287 | self.assertFalse(isp.push_accepts_more()) | |
288 |
|
288 | |||
289 | def check_split(self, block_lines, compile=True): |
|
|||
290 | blocks = assemble(block_lines) |
|
|||
291 | lines = ''.join(blocks) |
|
|||
292 | oblock = self.isp.split_blocks(lines) |
|
|||
293 | self.assertEqual(oblock, blocks) |
|
|||
294 | if compile: |
|
|||
295 | for block in blocks: |
|
|||
296 | self.isp._compile(block) |
|
|||
297 |
|
||||
298 | def test_split(self): |
|
|||
299 | # All blocks of input we want to test in a list. The format for each |
|
|||
300 | # block is a list of lists, with each inner lists consisting of all the |
|
|||
301 | # lines (as single-lines) that should make up a sub-block. |
|
|||
302 |
|
||||
303 | # Note: do NOT put here sub-blocks that don't compile, as the |
|
|||
304 | # check_split() routine makes a final verification pass to check that |
|
|||
305 | # each sub_block, as returned by split_blocks(), does compile |
|
|||
306 | # correctly. |
|
|||
307 | all_blocks = [ [['x=1']], |
|
|||
308 |
|
||||
309 | [['x=1'], |
|
|||
310 | ['y=2']], |
|
|||
311 |
|
||||
312 | [['x=1', |
|
|||
313 | '# a comment'], |
|
|||
314 | ['y=11']], |
|
|||
315 |
|
||||
316 | [['if 1:', |
|
|||
317 | ' x=1'], |
|
|||
318 | ['y=3']], |
|
|||
319 |
|
||||
320 | [['def f(x):', |
|
|||
321 | ' return x'], |
|
|||
322 | ['x=1']], |
|
|||
323 |
|
||||
324 | [['def f(x):', |
|
|||
325 | ' x+=1', |
|
|||
326 | ' ', |
|
|||
327 | ' return x'], |
|
|||
328 | ['x=1']], |
|
|||
329 |
|
||||
330 | [['def f(x):', |
|
|||
331 | ' if x>0:', |
|
|||
332 | ' y=1', |
|
|||
333 | ' # a comment', |
|
|||
334 | ' else:', |
|
|||
335 | ' y=4', |
|
|||
336 | ' ', |
|
|||
337 | ' return y'], |
|
|||
338 | ['x=1'], |
|
|||
339 | ['if 1:', |
|
|||
340 | ' y=11'] ], |
|
|||
341 |
|
||||
342 | [['for i in range(10):' |
|
|||
343 | ' x=i**2']], |
|
|||
344 |
|
||||
345 | [['for i in range(10):', |
|
|||
346 | ' x=i**2'], |
|
|||
347 | ['z = 1']], |
|
|||
348 |
|
||||
349 | [['for i in range(10):', |
|
|||
350 | ' x=i**2'], |
|
|||
351 | ['z = 1'], |
|
|||
352 | ['a = None']], |
|
|||
353 |
|
||||
354 | [['"asdf"']], |
|
|||
355 |
|
||||
356 | [['"asdf"'], |
|
|||
357 | ['10'], |
|
|||
358 | ], |
|
|||
359 |
|
||||
360 | [['"""foo', |
|
|||
361 | 'bar"""']], |
|
|||
362 | ] |
|
|||
363 | for block_lines in all_blocks: |
|
|||
364 | self.check_split(block_lines) |
|
|||
365 |
|
||||
366 | def test_split_syntax_errors(self): |
|
|||
367 | # Block splitting with invalid syntax |
|
|||
368 | all_blocks = [ [['a syntax error']], |
|
|||
369 |
|
||||
370 | [['x=1', |
|
|||
371 | 'another syntax error']], |
|
|||
372 |
|
||||
373 | [['for i in range(10):' |
|
|||
374 | ' yet another error']], |
|
|||
375 |
|
||||
376 | ] |
|
|||
377 | for block_lines in all_blocks: |
|
|||
378 | self.check_split(block_lines, compile=False) |
|
|||
379 |
|
||||
380 | def test_unicode(self): |
|
289 | def test_unicode(self): | |
381 | self.isp.push(u"Pérez") |
|
290 | self.isp.push(u"Pérez") | |
382 | self.isp.push(u'\xc3\xa9') |
|
291 | self.isp.push(u'\xc3\xa9') |
General Comments 0
You need to be logged in to leave comments.
Login now