compilerop.py
214 lines
| 7.5 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r3175 | """Compiler tools with improved interactive support. | ||
Provides compilation machinery similar to codeop, but with caching support so | ||||
we can provide interactive tracebacks. | ||||
Authors | ||||
------- | ||||
* Robert Kern | ||||
* Fernando Perez | ||||
Thomas Kluyver
|
r3533 | * Thomas Kluyver | ||
Fernando Perez
|
r3175 | """ | ||
# Note: though it might be more natural to name this module 'compiler', that | ||||
# name is in the stdlib and name collisions with the stdlib tend to produce | ||||
# weird problems (often with third-party tools). | ||||
#----------------------------------------------------------------------------- | ||||
Matthias BUSSONNIER
|
r5390 | # Copyright (C) 2010-2011 The IPython Development Team. | ||
Fernando Perez
|
r3175 | # | ||
# Distributed under the terms of the BSD License. | ||||
# | ||||
# The full license is in the file COPYING.txt, distributed with this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
# Stdlib imports | ||||
Bradley M. Froehle
|
r7894 | import __future__ | ||
Thomas Kluyver
|
r4795 | from ast import PyCF_ONLY_AST | ||
Fernando Perez
|
r3175 | import codeop | ||
Bradley M. Froehle
|
r7894 | import functools | ||
Fernando Perez
|
r3175 | import hashlib | ||
import linecache | ||||
Bradley M. Froehle
|
r7894 | import operator | ||
Fernando Perez
|
r3175 | import time | ||
Matthias Bussonnier
|
r25019 | from contextlib import contextmanager | ||
Fernando Perez
|
r3175 | |||
#----------------------------------------------------------------------------- | ||||
Bradley M. Froehle
|
r7894 | # Constants | ||
#----------------------------------------------------------------------------- | ||||
luz.paz
|
r24493 | # Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h, | ||
Bradley M. Froehle
|
r7894 | # this is used as a bitmask to extract future-related code flags. | ||
PyCF_MASK = functools.reduce(operator.or_, | ||||
(getattr(__future__, fname).compiler_flag | ||||
for fname in __future__.all_feature_names)) | ||||
#----------------------------------------------------------------------------- | ||||
Fernando Perez
|
r3175 | # Local utilities | ||
#----------------------------------------------------------------------------- | ||||
def code_name(code, number=0): | ||||
""" Compute a (probably) unique name for code for caching. | ||||
luz.paz
|
r24494 | |||
Thomas Kluyver
|
r3442 | This now expects code to be unicode. | ||
Fernando Perez
|
r3175 | """ | ||
Srinath
|
r23794 | hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest() | ||
Fernando Perez
|
r3175 | # Include the number and 12 characters of the hash in the name. It's | ||
# pretty much impossible that in a single session we'll have collisions | ||||
# even with truncated hashes, and the full one makes tracebacks too long | ||||
return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12]) | ||||
#----------------------------------------------------------------------------- | ||||
# Classes and functions | ||||
#----------------------------------------------------------------------------- | ||||
Thomas Kluyver
|
r3529 | class CachingCompiler(codeop.Compile): | ||
Fernando Perez
|
r3175 | """A compiler that caches code compiled from interactive statements. | ||
""" | ||||
def __init__(self): | ||||
Thomas Kluyver
|
r3529 | codeop.Compile.__init__(self) | ||
luz.paz
|
r24494 | |||
martinRenou
|
r26685 | # Caching a dictionary { filename: execution_count } for nicely | ||
# rendered tracebacks. The filename corresponds to the filename | ||||
# argument used for the builtins.compile function. | ||||
self._filename_map = {} | ||||
Matthias Bussonnier
|
r24342 | |||
Thomas Kluyver
|
r4795 | def ast_parse(self, source, filename='<unknown>', symbol='exec'): | ||
Thomas Kluyver
|
r4796 | """Parse code to an AST with the current compiler flags active. | ||
luz.paz
|
r24494 | |||
Thomas Kluyver
|
r4796 | Arguments are exactly the same as ast.parse (in the standard library), | ||
and are passed to the built-in compile function.""" | ||||
Matthias Bussonnier
|
r24564 | return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1) | ||
martinRenou
|
r26329 | |||
Thomas Kluyver
|
r4795 | def reset_compiler_flags(self): | ||
"""Reset compiler flags to default state.""" | ||||
# This value is copied from codeop.Compile.__init__, so if that ever | ||||
# changes, it will need to be updated. | ||||
self.flags = codeop.PyCF_DONT_IMPLY_DEDENT | ||||
Fernando Perez
|
r3175 | |||
@property | ||||
def compiler_flags(self): | ||||
"""Flags currently active in the compilation process. | ||||
""" | ||||
Thomas Kluyver
|
r3529 | return self.flags | ||
luz.paz
|
r24494 | |||
martinRenou
|
r26329 | def get_code_name(self, raw_code, transformed_code, number): | ||
"""Compute filename given the code, and the cell number. | ||||
Parameters | ||||
---------- | ||||
raw_code : str | ||||
Matthias Bussonnier
|
r26491 | The raw cell code. | ||
martinRenou
|
r26329 | transformed_code : str | ||
Matthias Bussonnier
|
r26491 | The executable Python source code to cache and compile. | ||
martinRenou
|
r26329 | number : int | ||
Matthias Bussonnier
|
r26491 | A number which forms part of the code's name. Used for the execution | ||
counter. | ||||
martinRenou
|
r26329 | |||
Returns | ||||
------- | ||||
The computed filename. | ||||
""" | ||||
return code_name(transformed_code, number) | ||||
Jason Grout
|
r27875 | def format_code_name(self, name): | ||
"""Return a user-friendly label and name for a code block. | ||||
Parameters | ||||
---------- | ||||
name : str | ||||
The name for the code block returned from get_code_name | ||||
Returns | ||||
------- | ||||
A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used. | ||||
""" | ||||
if name in self._filename_map: | ||||
return "Cell", "In[%s]" % self._filename_map[name] | ||||
martinRenou
|
r26329 | def cache(self, transformed_code, number=0, raw_code=None): | ||
Thomas Kluyver
|
r3529 | """Make a name for a block of code, and cache the code. | ||
luz.paz
|
r24494 | |||
Fernando Perez
|
r3175 | Parameters | ||
---------- | ||||
martinRenou
|
r26329 | transformed_code : str | ||
Matthias Bussonnier
|
r26491 | The executable Python source code to cache and compile. | ||
Thomas Kluyver
|
r3529 | number : int | ||
Matthias Bussonnier
|
r26491 | A number which forms part of the code's name. Used for the execution | ||
counter. | ||||
martinRenou
|
r26329 | raw_code : str | ||
Matthias Bussonnier
|
r26491 | The raw code before transformation, if None, set to `transformed_code`. | ||
luz.paz
|
r24494 | |||
Thomas Kluyver
|
r3529 | Returns | ||
------- | ||||
The name of the cached code (as a string). Pass this as the filename | ||||
argument to compilation, so that tracebacks are correctly hooked up. | ||||
Fernando Perez
|
r3175 | """ | ||
martinRenou
|
r26329 | if raw_code is None: | ||
raw_code = transformed_code | ||||
name = self.get_code_name(raw_code, transformed_code, number) | ||||
martinRenou
|
r26685 | |||
# Save the execution count | ||||
self._filename_map[name] = number | ||||
Ben Longbons
|
r27845 | # Since Python 2.5, setting mtime to `None` means the lines will | ||
# never be removed by `linecache.checkcache`. This means all the | ||||
# monkeypatching has *never* been necessary, since this code was | ||||
# only added in 2010, at which point IPython had already stopped | ||||
# supporting Python 2.4. | ||||
# | ||||
# Note that `linecache.clearcache` and `linecache.updatecache` may | ||||
# still remove our code from the cache, but those show explicit | ||||
# intent, and we should not try to interfere. Normally the former | ||||
# is never called except when out of memory, and the latter is only | ||||
# called for lines *not* in the cache. | ||||
martinRenou
|
r26329 | entry = ( | ||
len(transformed_code), | ||||
Ben Longbons
|
r27845 | None, | ||
martinRenou
|
r26329 | [line + "\n" for line in transformed_code.splitlines()], | ||
name, | ||||
) | ||||
Fernando Perez
|
r3175 | linecache.cache[name] = entry | ||
Thomas Kluyver
|
r3529 | return name | ||
Fernando Perez
|
r3175 | |||
Matthias Bussonnier
|
r25019 | @contextmanager | ||
def extra_flags(self, flags): | ||||
Matthias Bussonnier
|
r25055 | ## bits that we'll set to 1 | ||
turn_on_bits = ~self.flags & flags | ||||
Matthias Bussonnier
|
r25019 | self.flags = self.flags | flags | ||
try: | ||||
yield | ||||
finally: | ||||
Matthias Bussonnier
|
r25055 | # turn off only the bits we turned on so that something like | ||
martinRenou
|
r26329 | # __future__ that set flags stays. | ||
Matthias Bussonnier
|
r25055 | self.flags &= ~turn_on_bits | ||
Matthias Bussonnier
|
r25019 | |||
Thomas Kluyver
|
r9140 | def check_linecache_ipython(*args): | ||
Ben Longbons
|
r27845 | """Deprecated since IPython 8.6. Call linecache.checkcache() directly. | ||
It was already not necessary to call this function directly. If no | ||||
CachingCompiler had been created, this function would fail badly. If | ||||
an instance had been created, this function would've been monkeypatched | ||||
into place. | ||||
As of IPython 8.6, the monkeypatching has gone away entirely. But there | ||||
were still internal callers of this function, so maybe external callers | ||||
also existed? | ||||
Thomas Kluyver
|
r9140 | """ | ||
Ben Longbons
|
r27845 | import warnings | ||
Matthias Bussonnier
|
r27846 | |||
Matthias Bussonnier
|
r27847 | warnings.warn( | ||
"Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.", | ||||
DeprecationWarning, | ||||
stacklevel=2, | ||||
) | ||||
Ben Longbons
|
r27845 | linecache.checkcache() | ||