compilerop.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. """Compiler tools with improved interactive support.
  2. Provides compilation machinery similar to codeop, but with caching support so
  3. we can provide interactive tracebacks.
  4. Authors
  5. -------
  6. * Robert Kern
  7. * Fernando Perez
  8. * Thomas Kluyver
  9. """
  10. # Note: though it might be more natural to name this module 'compiler', that
  11. # name is in the stdlib and name collisions with the stdlib tend to produce
  12. # weird problems (often with third-party tools).
  13. #-----------------------------------------------------------------------------
  14. # Copyright (C) 2010-2011 The IPython Development Team.
  15. #
  16. # Distributed under the terms of the BSD License.
  17. #
  18. # The full license is in the file COPYING.txt, distributed with this software.
  19. #-----------------------------------------------------------------------------
  20. #-----------------------------------------------------------------------------
  21. # Imports
  22. #-----------------------------------------------------------------------------
  23. # Stdlib imports
  24. import __future__
  25. from ast import PyCF_ONLY_AST
  26. import codeop
  27. import functools
  28. import hashlib
  29. import linecache
  30. import operator
  31. import time
  32. from contextlib import contextmanager
  33. #-----------------------------------------------------------------------------
  34. # Constants
  35. #-----------------------------------------------------------------------------
  36. # Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h,
  37. # this is used as a bitmask to extract future-related code flags.
  38. PyCF_MASK = functools.reduce(operator.or_,
  39. (getattr(__future__, fname).compiler_flag
  40. for fname in __future__.all_feature_names))
  41. #-----------------------------------------------------------------------------
  42. # Local utilities
  43. #-----------------------------------------------------------------------------
  44. def code_name(code, number=0) -> str:
  45. """ Compute a (probably) unique name for code for caching.
  46. This now expects code to be unicode.
  47. """
  48. hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest()
  49. # Include the number and 12 characters of the hash in the name. It's
  50. # pretty much impossible that in a single session we'll have collisions
  51. # even with truncated hashes, and the full one makes tracebacks too long
  52. return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12])
  53. #-----------------------------------------------------------------------------
  54. # Classes and functions
  55. #-----------------------------------------------------------------------------
  56. class CachingCompiler(codeop.Compile):
  57. """A compiler that caches code compiled from interactive statements.
  58. """
  59. def __init__(self):
  60. codeop.Compile.__init__(self)
  61. # Caching a dictionary { filename: execution_count } for nicely
  62. # rendered tracebacks. The filename corresponds to the filename
  63. # argument used for the builtins.compile function.
  64. self._filename_map = {}
  65. def ast_parse(self, source, filename='<unknown>', symbol='exec'):
  66. """Parse code to an AST with the current compiler flags active.
  67. Arguments are exactly the same as ast.parse (in the standard library),
  68. and are passed to the built-in compile function."""
  69. return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)
  70. def reset_compiler_flags(self):
  71. """Reset compiler flags to default state."""
  72. # This value is copied from codeop.Compile.__init__, so if that ever
  73. # changes, it will need to be updated.
  74. self.flags = codeop.PyCF_DONT_IMPLY_DEDENT
  75. @property
  76. def compiler_flags(self):
  77. """Flags currently active in the compilation process.
  78. """
  79. return self.flags
  80. def get_code_name(self, raw_code, transformed_code, number):
  81. """Compute filename given the code, and the cell number.
  82. Parameters
  83. ----------
  84. raw_code : str
  85. The raw cell code.
  86. transformed_code : str
  87. The executable Python source code to cache and compile.
  88. number : int
  89. A number which forms part of the code's name. Used for the execution
  90. counter.
  91. Returns
  92. -------
  93. The computed filename.
  94. """
  95. return code_name(transformed_code, number)
  96. def format_code_name(self, name) -> str:
  97. """Return a user-friendly label and name for a code block.
  98. Parameters
  99. ----------
  100. name : str
  101. The name for the code block returned from get_code_name
  102. Returns
  103. -------
  104. A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used.
  105. """
  106. if name in self._filename_map:
  107. return "Cell", "In[%s]" % self._filename_map[name]
  108. def cache(self, transformed_code, number=0, raw_code=None):
  109. """Make a name for a block of code, and cache the code.
  110. Parameters
  111. ----------
  112. transformed_code : str
  113. The executable Python source code to cache and compile.
  114. number : int
  115. A number which forms part of the code's name. Used for the execution
  116. counter.
  117. raw_code : str
  118. The raw code before transformation, if None, set to `transformed_code`.
  119. Returns
  120. -------
  121. The name of the cached code (as a string). Pass this as the filename
  122. argument to compilation, so that tracebacks are correctly hooked up.
  123. """
  124. if raw_code is None:
  125. raw_code = transformed_code
  126. name = self.get_code_name(raw_code, transformed_code, number)
  127. # Save the execution count
  128. self._filename_map[name] = number
  129. # Since Python 2.5, setting mtime to `None` means the lines will
  130. # never be removed by `linecache.checkcache`. This means all the
  131. # monkeypatching has *never* been necessary, since this code was
  132. # only added in 2010, at which point IPython had already stopped
  133. # supporting Python 2.4.
  134. #
  135. # Note that `linecache.clearcache` and `linecache.updatecache` may
  136. # still remove our code from the cache, but those show explicit
  137. # intent, and we should not try to interfere. Normally the former
  138. # is never called except when out of memory, and the latter is only
  139. # called for lines *not* in the cache.
  140. entry = (
  141. len(transformed_code),
  142. None,
  143. [line + "\n" for line in transformed_code.splitlines()],
  144. name,
  145. )
  146. linecache.cache[name] = entry
  147. return name
  148. @contextmanager
  149. def extra_flags(self, flags):
  150. ## bits that we'll set to 1
  151. turn_on_bits = ~self.flags & flags
  152. self.flags = self.flags | flags
  153. try:
  154. yield
  155. finally:
  156. # turn off only the bits we turned on so that something like
  157. # __future__ that set flags stays.
  158. self.flags &= ~turn_on_bits