compilerop.py
161 lines
| 6.4 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 | ||
#----------------------------------------------------------------------------- | ||||
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 | |||
def _fix_module_ds(self, module): | ||||
""" | ||||
Starting in python 3.7 the AST for mule have changed, and if | ||||
luz.paz
|
r24494 | the first expressions encountered is a string it is attached to the | ||
Matthias Bussonnier
|
r24342 | `docstring` attribute of the `Module` ast node. | ||
This breaks IPython, as if this string is the only expression, IPython | ||||
will not return it as the result of the current cell. | ||||
""" | ||||
from ast import Str, Expr, Module, fix_missing_locations | ||||
docstring = getattr(module, 'docstring', None) | ||||
if not docstring: | ||||
return module | ||||
new_body=[Expr(Str(docstring, lineno=1, col_offset=0), lineno=1, col_offset=0)] | ||||
new_body.extend(module.body) | ||||
return fix_missing_locations(Module(new_body)) | ||||
luz.paz
|
r24494 | |||
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
|
r24342 | return self._fix_module_ds(compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)) | ||
luz.paz
|
r24494 | |||
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 | |||
Thomas Kluyver
|
r3529 | def cache(self, code, number=0): | ||
"""Make a name for a block of code, and cache the code. | ||||
luz.paz
|
r24494 | |||
Fernando Perez
|
r3175 | Parameters | ||
---------- | ||||
code : str | ||||
Thomas Kluyver
|
r3529 | The Python source code to cache. | ||
number : int | ||||
A number which forms part of the code's name. Used for the execution | ||||
counter. | ||||
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 | """ | ||
name = code_name(code, number) | ||||
entry = (len(code), time.time(), | ||||
[line+'\n' for line in code.splitlines()], name) | ||||
linecache.cache[name] = entry | ||||
linecache._ipython_cache[name] = entry | ||||
Thomas Kluyver
|
r3529 | return name | ||
Fernando Perez
|
r3175 | |||
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) | ||||