Skip to content

Commit

Permalink
Use a fixed compiler in HyREPL
Browse files Browse the repository at this point in the history
These changes make `HyREPL` use a single `HyASTCompiler` instance, instead of
creating one every time a valid source string is processed.

This change avoids the unnecessary re-initiation of the standard library
`import` and `require` steps that currently occur within the module tracked by a
`HyREPL` instance.

Also, one can now pass an existing compiler instance to `hy_repl` and
`hy_compiler`.

Closes hylang#1698.
  • Loading branch information
brandonwillard committed Nov 11, 2018
1 parent c5abc85 commit 39be6cd
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 29 deletions.
9 changes: 7 additions & 2 deletions hy/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import hy
from hy.lex import LexException, PrematureEndOfInput, mangle
from hy.compiler import HyTypeError, hy_compile
from hy.compiler import HyTypeError, HyASTCompiler, hy_compile
from hy.importer import hy_eval, hy_parse, runhy
from hy.completer import completion, Completer
from hy.macros import macro, require
Expand Down Expand Up @@ -68,6 +68,8 @@ def __init__(self, spy=False, output_fn=None, locals=None,
# Load cmdline-specific macros.
require('hy.cmdline', module_name, assignments='ALL')

self.hy_compiler = HyASTCompiler(self.module)

self.spy = spy

if output_fn is None:
Expand Down Expand Up @@ -116,7 +118,10 @@ def ast_callback(main_ast, expr_ast):
new_ast = ast.Module(main_ast.body +
[ast.Expr(expr_ast.body)])
print(astor.to_source(new_ast))
value = hy_eval(do, self.locals, self.module, ast_callback)

value = hy_eval(do, self.locals,
ast_callback=ast_callback,
compiler=self.hy_compiler)
except HyTypeError as e:
if e.source is None:
e.source = source
Expand Down
47 changes: 32 additions & 15 deletions hy/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1554,9 +1554,7 @@ def compile_dispatch_tag_macro(self, expr, root, tag, arg):
def compile_eval_and_compile(self, expr, root, body):
new_expr = HyExpression([HySymbol("do").replace(expr[0])]).replace(expr)

hy.importer.hy_eval(new_expr + body,
self.module.__dict__,
self.module)
hy.importer.hy_eval(new_expr + body, self.module.__dict__, self.module)

return (self._compile_branch(body)
if ast_str(root) == "eval_and_compile"
Expand Down Expand Up @@ -1723,41 +1721,60 @@ def compile_dict(self, m):
return ret + asty.Dict(m, keys=keyvalues[::2], values=keyvalues[1::2])


def hy_compile(tree, module, root=ast.Module, get_expr=False):
def get_compiler_module(module=None, compiler=None, calling_frame=False):
"""Get a module object from a compiler, given module object,
string name of a module, and (optionally) the calling frame; otherwise,
raise an error."""

module = getattr(compiler, 'module', None) or module

if isinstance(module, string_types):
if module.startswith('<') and module.endswith('>'):
module = types.ModuleType(module)
else:
module = importlib.import_module(ast_str(module, piecewise=True))

if calling_frame and not module:
module = hy.importer.calling_module(n=2)

if not inspect.ismodule(module):
raise TypeError('Invalid module type: {}'.format(type(module)))

return module


def hy_compile(tree, module=None, root=ast.Module, get_expr=False, compiler=None):
"""
Compile a Hy tree into a Python AST tree.
Parameters
----------
module: str or types.ModuleType
module: str or types.ModuleType, optional
Module, or name of the module, in which the Hy tree is evaluated.
The module associated with `compiler` takes priority over this value.
root: ast object, optional (ast.Module)
Root object for the Python AST tree.
get_expr: bool, optional (False)
If true, return a tuple with `(root_obj, last_expression)`.
compiler: HyASTCompiler, optional
An existing Hy compiler to use for compilation. Also serves as
the `module` value when given.
Returns
-------
out : A Python AST tree
"""

if isinstance(module, string_types):
if module.startswith('<') and module.endswith('>'):
module = types.ModuleType(module)
else:
module = importlib.import_module(ast_str(module, piecewise=True))
if not inspect.ismodule(module):
raise TypeError('Invalid module type: {}'.format(type(module)))

module = get_compiler_module(module, compiler, False)

tree = wrap_value(tree)
if not isinstance(tree, HyObject):
raise HyCompileError("`tree` must be a HyObject or capable of "
"being promoted to one")

compiler = HyASTCompiler(module)
compiler = compiler or HyASTCompiler(module)
result = compiler.compile(tree)
expr = result.force_expr

Expand Down
26 changes: 14 additions & 12 deletions hy/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
from contextlib import contextmanager

from hy.errors import HyTypeError
from hy.compiler import hy_compile, ast_str
from hy.compiler import hy_compile, get_compiler_module
from hy.lex import tokenize, LexException
from hy.models import HyExpression, HySymbol
from hy._compat import string_types, PY3
from hy._compat import PY3


hy_ast_compile_flags = (__future__.CO_FUTURE_DIVISION |
Expand Down Expand Up @@ -96,7 +96,8 @@ def hy_parse(source):
return HyExpression([HySymbol("do")] + tokenize(source + "\n"))


def hy_eval(hytree, locals=None, module=None, ast_callback=None):
def hy_eval(hytree, locals=None, module=None, ast_callback=None,
compiler=None):
"""Evaluates a quoted expression and returns the value.
Examples
Expand All @@ -123,25 +124,25 @@ def hy_eval(hytree, locals=None, module=None, ast_callback=None):
module: str or types.ModuleType, optional
Module, or name of the module, to which the Hy tree is assigned and
the global values are taken.
Defaults to the calling frame's module, if any, and '__eval__'
otherwise.
The module associated with `compiler` takes priority over this value.
When neither `module` nor `compiler` is specified, the calling frame's
module is used.
ast_callback: callable, optional
A callback that is passed the Hy compiled tree and resulting
expression object, in that order, after compilation but before
evaluation.
compiler: HyASTCompiler, optional
An existing Hy compiler to use for compilation. Also serves as
the `module` value when given.
Returns
-------
out : Result of evaluating the Hy compiled tree.
"""
if module is None:
module = calling_module()

if isinstance(module, string_types):
module = importlib.import_module(ast_str(module, piecewise=True))
elif not inspect.ismodule(module):
raise TypeError('Invalid module type: {}'.format(type(module)))
module = get_compiler_module(module, compiler, True)

if locals is None:
frame = inspect.stack()[1][0]
Expand All @@ -150,7 +151,8 @@ def hy_eval(hytree, locals=None, module=None, ast_callback=None):
if not isinstance(locals, dict):
raise TypeError("Locals must be a dictionary")

_ast, expr = hy_compile(hytree, module, get_expr=True)
_ast, expr = hy_compile(hytree, module=module, get_expr=True,
compiler=compiler)

# Spoof the positions in the generated ast...
for node in ast.walk(_ast):
Expand Down

0 comments on commit 39be6cd

Please sign in to comment.