compilerop.py
196 lines
| 7.3 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 | |||
Fernando Perez
|
r3175 | # This is ugly, but it must be done this way to allow multiple | ||
# simultaneous ipython instances to coexist. Since Python itself | ||||
# directly accesses the data structures in the linecache module, and | ||||
# the cache therein is global, we must work with that data structure. | ||||
# We must hold a reference to the original checkcache routine and call | ||||
# that in our own check_cache() below, but the special IPython cache | ||||
# must also be shared by all IPython instances. If we were to hold | ||||
# separate caches (one in each CachingCompiler instance), any call made | ||||
# by Python itself to linecache.checkcache() would obliterate the | ||||
# cached data from the other IPython instances. | ||||
if not hasattr(linecache, '_ipython_cache'): | ||||
linecache._ipython_cache = {} | ||||
if not hasattr(linecache, '_checkcache_ori'): | ||||
linecache._checkcache_ori = linecache.checkcache | ||||
# Now, we must monkeypatch the linecache directly so that parts of the | ||||
# stdlib that call it outside our control go through our codepath | ||||
# (otherwise we'd lose our tracebacks). | ||||
Thomas Kluyver
|
r9140 | linecache.checkcache = check_linecache_ipython | ||
Matthias Bussonnier
|
r24342 | |||
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) | ||||
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 | ||||
martinRenou
|
r26329 | entry = ( | ||
len(transformed_code), | ||||
time.time(), | ||||
[line + "\n" for line in transformed_code.splitlines()], | ||||
name, | ||||
) | ||||
Fernando Perez
|
r3175 | linecache.cache[name] = entry | ||
linecache._ipython_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): | ||
"""Call linecache.checkcache() safely protecting our cached values. | ||||
""" | ||||
luz.paz
|
r24132 | # First call the original checkcache as intended | ||
Thomas Kluyver
|
r9140 | linecache._checkcache_ori(*args) | ||
# Then, update back the cache with our data, so that tracebacks related | ||||
# to our compiled codes can be produced. | ||||
linecache.cache.update(linecache._ipython_cache) | ||||