Make macros module-specific.
A macro is available in the module where it was defined and in any module that does a require of the defining module. Only macros defined in hy.core are globally available. Fixes #181
This commit is contained in:
parent
b7c5ff2991
commit
269da19d76
@ -8,12 +8,13 @@ import astor.codegen
|
||||
import sys
|
||||
import ast
|
||||
|
||||
module_name = "<STDIN>"
|
||||
|
||||
hst = import_file_to_hst(sys.argv[1])
|
||||
print(hst)
|
||||
print("")
|
||||
print("")
|
||||
_ast = import_file_to_ast(sys.argv[1])
|
||||
_ast = import_file_to_ast(sys.argv[1], module_name)
|
||||
print("")
|
||||
print("")
|
||||
print(ast.dump(_ast))
|
||||
@ -21,4 +22,4 @@ print("")
|
||||
print("")
|
||||
print(astor.codegen.to_source(_ast))
|
||||
|
||||
import_file_to_module("<STDIN>", sys.argv[1])
|
||||
import_file_to_module(module_name, sys.argv[1])
|
||||
|
@ -41,7 +41,7 @@ from hy.importer import ast_compile, import_buffer_to_module
|
||||
|
||||
import hy.completer
|
||||
|
||||
from hy.macros import macro
|
||||
from hy.macros import macro, require
|
||||
from hy.models.expression import HyExpression
|
||||
from hy.models.string import HyString
|
||||
from hy.models.symbol import HySymbol
|
||||
@ -66,7 +66,7 @@ class HyREPL(code.InteractiveConsole):
|
||||
return True
|
||||
|
||||
try:
|
||||
tokens = process(_machine.nodes)
|
||||
tokens = process(_machine.nodes, "__console__")
|
||||
except Exception:
|
||||
_machine = Machine(Idle, 1, 0)
|
||||
self.showtraceback()
|
||||
@ -74,7 +74,7 @@ class HyREPL(code.InteractiveConsole):
|
||||
|
||||
_machine = Machine(Idle, 1, 0)
|
||||
try:
|
||||
_ast = hy_compile(tokens, root=ast.Interactive)
|
||||
_ast = hy_compile(tokens, "__console__", root=ast.Interactive)
|
||||
code = ast_compile(_ast, filename, symbol)
|
||||
except Exception:
|
||||
self.showtraceback()
|
||||
@ -135,6 +135,9 @@ def ideas_macro():
|
||||
|
||||
""")])
|
||||
|
||||
require("hy.cmdline", "__console__")
|
||||
require("hy.cmdline", "__main__")
|
||||
|
||||
|
||||
def run_command(source):
|
||||
try:
|
||||
|
@ -40,6 +40,8 @@ from hy.core import process
|
||||
|
||||
from hy.util import str_type
|
||||
|
||||
from hy.macros import require
|
||||
|
||||
import codecs
|
||||
import traceback
|
||||
import ast
|
||||
@ -321,10 +323,11 @@ def checkargs(exact=None, min=None, max=None):
|
||||
|
||||
class HyASTCompiler(object):
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, module_name):
|
||||
self.anon_fn_count = 0
|
||||
self.anon_var_count = 0
|
||||
self.imports = defaultdict(set)
|
||||
self.module_name = module_name
|
||||
|
||||
def get_anon_var(self):
|
||||
self.anon_var_count += 1
|
||||
@ -366,7 +369,7 @@ class HyASTCompiler(object):
|
||||
|
||||
def compile_atom(self, atom_type, atom):
|
||||
if atom_type in _compile_table:
|
||||
atom = process(atom)
|
||||
atom = process(atom, self.module_name)
|
||||
ret = _compile_table[atom_type](self, atom)
|
||||
if not isinstance(ret, Result):
|
||||
ret = Result() + ret
|
||||
@ -599,7 +602,8 @@ class HyASTCompiler(object):
|
||||
|
||||
ret = self.compile(HyExpression([
|
||||
HySymbol("hy_eval")] + expr + [
|
||||
HyExpression([HySymbol("locals")])]).replace(expr))
|
||||
HyExpression([HySymbol("locals")])] + [
|
||||
HyString(self.module_name)]).replace(expr))
|
||||
|
||||
ret.add_imports("hy.importer", ["hy_eval"])
|
||||
|
||||
@ -1225,6 +1229,7 @@ class HyASTCompiler(object):
|
||||
expression.pop(0)
|
||||
for entry in expression:
|
||||
__import__(entry) # Import it fo' them macros.
|
||||
require(entry, self.module_name)
|
||||
return Result()
|
||||
|
||||
@builds("and")
|
||||
@ -1623,7 +1628,11 @@ class HyASTCompiler(object):
|
||||
]).replace(expression)
|
||||
|
||||
# Compile-time hack: we want to get our new macro now
|
||||
hy.importer.hy_eval(new_expression, {'hy': hy})
|
||||
# We must provide __name__ in the namespace to make the Python
|
||||
# compiler set the __module__ attribute of the macro function.
|
||||
hy.importer.hy_eval(new_expression,
|
||||
{'hy': hy, '__name__': self.module_name},
|
||||
self.module_name)
|
||||
|
||||
# We really want to have a `hy` import to get hy.macro in
|
||||
ret = self.compile(new_expression)
|
||||
@ -1694,7 +1703,7 @@ class HyASTCompiler(object):
|
||||
return ret
|
||||
|
||||
|
||||
def hy_compile(tree, root=ast.Module, get_expr=False):
|
||||
def hy_compile(tree, module_name, root=ast.Module, get_expr=False):
|
||||
"""
|
||||
Compile a HyObject tree into a Python AST Module.
|
||||
|
||||
@ -1711,7 +1720,7 @@ def hy_compile(tree, root=ast.Module, get_expr=False):
|
||||
expr = None
|
||||
|
||||
if tree:
|
||||
compiler = HyASTCompiler()
|
||||
compiler = HyASTCompiler(module_name)
|
||||
result = compiler.compile(tree)
|
||||
expr = result.force_expr
|
||||
|
||||
|
@ -25,12 +25,12 @@ MACROS = [
|
||||
]
|
||||
|
||||
|
||||
def process(tree):
|
||||
def process(tree, module_name):
|
||||
load_macros()
|
||||
old = None
|
||||
while old != tree:
|
||||
old = tree
|
||||
tree = mprocess(tree)
|
||||
tree = mprocess(tree, module_name)
|
||||
return tree
|
||||
|
||||
|
||||
|
@ -56,21 +56,21 @@ def import_file_to_hst(fpath):
|
||||
return import_buffer_to_hst(f.read())
|
||||
|
||||
|
||||
def import_buffer_to_ast(buf):
|
||||
def import_buffer_to_ast(buf, module_name):
|
||||
""" Import content from buf and return a Python AST."""
|
||||
return hy_compile(import_buffer_to_hst(buf))
|
||||
return hy_compile(import_buffer_to_hst(buf), module_name)
|
||||
|
||||
|
||||
def import_file_to_ast(fpath):
|
||||
def import_file_to_ast(fpath, module_name):
|
||||
"""Import content from fpath and return a Python AST."""
|
||||
return hy_compile(import_file_to_hst(fpath))
|
||||
return hy_compile(import_file_to_hst(fpath), module_name)
|
||||
|
||||
|
||||
def import_file_to_module(module_name, fpath):
|
||||
"""Import content from fpath and puts it into a Python module.
|
||||
|
||||
Returns the module."""
|
||||
_ast = import_file_to_ast(fpath)
|
||||
_ast = import_file_to_ast(fpath, module_name)
|
||||
mod = imp.new_module(module_name)
|
||||
mod.__file__ = fpath
|
||||
eval(ast_compile(_ast, fpath, "exec"), mod.__dict__)
|
||||
@ -78,20 +78,20 @@ def import_file_to_module(module_name, fpath):
|
||||
|
||||
|
||||
def import_buffer_to_module(module_name, buf):
|
||||
_ast = import_buffer_to_ast(buf)
|
||||
_ast = import_buffer_to_ast(buf, module_name)
|
||||
mod = imp.new_module(module_name)
|
||||
eval(ast_compile(_ast, "", "exec"), mod.__dict__)
|
||||
return mod
|
||||
|
||||
|
||||
def hy_eval(hytree, namespace):
|
||||
def hy_eval(hytree, namespace, module_name):
|
||||
foo = HyObject()
|
||||
foo.start_line = 0
|
||||
foo.end_line = 0
|
||||
foo.start_column = 0
|
||||
foo.end_column = 0
|
||||
hytree.replace(foo)
|
||||
_ast, expr = hy_compile(hytree, get_expr=True)
|
||||
_ast, expr = hy_compile(hytree, module_name, get_expr=True)
|
||||
|
||||
# Spoof the positions in the generated ast...
|
||||
for node in ast.walk(_ast):
|
||||
@ -117,7 +117,8 @@ def write_hy_as_pyc(fname):
|
||||
st = os.stat(fname)
|
||||
timestamp = long_type(st.st_mtime)
|
||||
|
||||
_ast = import_file_to_ast(fname)
|
||||
_ast = import_file_to_ast(fname,
|
||||
os.path.basename(os.path.splitext(fname)[0]))
|
||||
code = ast_compile(_ast, fname, "exec")
|
||||
cfile = "%s.pyc" % fname[:-len(".hy")]
|
||||
|
||||
|
34
hy/macros.py
34
hy/macros.py
@ -23,27 +23,41 @@ from hy.models.string import HyString
|
||||
from hy.models.dict import HyDict
|
||||
from hy.models.list import HyList
|
||||
|
||||
_hy_macros = {}
|
||||
from collections import defaultdict
|
||||
|
||||
_hy_macros = defaultdict(dict)
|
||||
|
||||
|
||||
def macro(name):
|
||||
def _(fn):
|
||||
_hy_macros[name] = fn
|
||||
module_name = fn.__module__
|
||||
if module_name.startswith("hy.core"):
|
||||
module_name = None
|
||||
_hy_macros[module_name][name] = fn
|
||||
return fn
|
||||
return _
|
||||
|
||||
|
||||
def process(tree):
|
||||
def require(source_module_name, target_module_name):
|
||||
macros = _hy_macros[source_module_name]
|
||||
refs = _hy_macros[target_module_name]
|
||||
for name, macro in macros.items():
|
||||
refs[name] = macro
|
||||
|
||||
|
||||
def process(tree, module_name):
|
||||
if isinstance(tree, HyExpression):
|
||||
fn = tree[0]
|
||||
if fn in ("quote", "quasiquote"):
|
||||
return tree
|
||||
ntree = HyExpression([fn] + [process(x) for x in tree[1:]])
|
||||
ntree = HyExpression([fn] + [process(x, module_name) for x in tree[1:]])
|
||||
ntree.replace(tree)
|
||||
|
||||
if isinstance(fn, HyString):
|
||||
if fn in _hy_macros:
|
||||
m = _hy_macros[fn]
|
||||
m = _hy_macros[module_name].get(fn)
|
||||
if m is None:
|
||||
m = _hy_macros[None].get(fn)
|
||||
if m is not None:
|
||||
obj = m(*ntree[1:])
|
||||
obj.replace(tree)
|
||||
return obj
|
||||
@ -52,17 +66,19 @@ def process(tree):
|
||||
return ntree
|
||||
|
||||
if isinstance(tree, HyDict):
|
||||
obj = HyDict(dict((process(x), process(tree[x])) for x in tree))
|
||||
obj = HyDict(dict((process(x, module_name),
|
||||
process(tree[x], module_name))
|
||||
for x in tree))
|
||||
obj.replace(tree)
|
||||
return obj
|
||||
|
||||
if isinstance(tree, HyList):
|
||||
obj = HyList([process(x) for x in tree]) # NOQA
|
||||
obj = HyList([process(x, module_name) for x in tree]) # NOQA
|
||||
# flake8 thinks we're redefining from 52.
|
||||
obj.replace(tree)
|
||||
return obj
|
||||
|
||||
if isinstance(tree, list):
|
||||
return [process(x) for x in tree]
|
||||
return [process(x, module_name) for x in tree]
|
||||
|
||||
return tree
|
||||
|
@ -39,13 +39,13 @@ def _ast_spotcheck(arg, root, secondary):
|
||||
|
||||
|
||||
def can_compile(expr):
|
||||
return hy_compile(tokenize(expr))
|
||||
return hy_compile(tokenize(expr), "__main__")
|
||||
|
||||
|
||||
def cant_compile(expr):
|
||||
expr = tokenize(expr)
|
||||
try:
|
||||
hy_compile(expr)
|
||||
hy_compile(expr, "__main__")
|
||||
assert False
|
||||
except HyCompileError:
|
||||
pass
|
||||
@ -54,7 +54,7 @@ def cant_compile(expr):
|
||||
def test_ast_bad_type():
|
||||
"Make sure AST breakage can happen"
|
||||
try:
|
||||
hy_compile("foo")
|
||||
hy_compile("foo", "__main__")
|
||||
assert True is False
|
||||
except HyCompileError:
|
||||
pass
|
||||
@ -350,7 +350,7 @@ def test_ast_non_kwapplyable():
|
||||
code = tokenize("(kwapply foo bar)")
|
||||
code[0][2] = None
|
||||
try:
|
||||
hy_compile(code)
|
||||
hy_compile(code, "__main__")
|
||||
assert True is False
|
||||
except HyCompileError:
|
||||
pass
|
||||
@ -410,7 +410,7 @@ def test_ast_unicode_strings():
|
||||
hy_s.start_line = hy_s.end_line = 0
|
||||
hy_s.start_column = hy_s.end_column = 0
|
||||
|
||||
code = hy_compile([hy_s])
|
||||
code = hy_compile([hy_s], "__main__")
|
||||
|
||||
# code == ast.Module(body=[ast.Expr(value=ast.Str(s=xxx))])
|
||||
return code.body[0].value.s
|
||||
|
@ -46,7 +46,7 @@ class HyASTCompilerTest(unittest.TestCase):
|
||||
return h
|
||||
|
||||
def setUp(self):
|
||||
self.c = HyASTCompiler()
|
||||
self.c = HyASTCompiler('')
|
||||
|
||||
def test_fn_compiler_empty_function(self):
|
||||
ret = self.c.compile_function_def(
|
||||
|
@ -10,5 +10,5 @@ def test_basics():
|
||||
|
||||
def test_stringer():
|
||||
"Make sure the basics of the importer work"
|
||||
_ast = import_buffer_to_ast("(defn square [x] (* x x))")
|
||||
_ast = import_buffer_to_ast("(defn square [x] (* x x))", '')
|
||||
assert type(_ast.body[0]) == ast.FunctionDef
|
||||
|
@ -13,15 +13,15 @@ def tmac(*tree):
|
||||
|
||||
|
||||
def test_preprocessor_simple():
|
||||
""" Test basic macro expantion """
|
||||
obj = process(tokenize('(test "one" "two")')[0])
|
||||
""" Test basic macro expansion """
|
||||
obj = process(tokenize('(test "one" "two")')[0], __name__)
|
||||
assert obj == HyList(["one", "two"])
|
||||
assert type(obj) == HyList
|
||||
|
||||
|
||||
def test_preprocessor_expression():
|
||||
""" Test inner macro expantion """
|
||||
obj = process(tokenize('(test (test "one" "two"))')[0])
|
||||
""" Test inner macro expansion """
|
||||
obj = process(tokenize('(test (test "one" "two"))')[0], __name__)
|
||||
|
||||
assert type(obj) == HyList
|
||||
assert type(obj[0]) == HyList
|
||||
@ -30,4 +30,4 @@ def test_preprocessor_expression():
|
||||
|
||||
obj = HyList([HyString("one"), HyString("two")])
|
||||
obj = tokenize('(shill ["one" "two"])')[0][1]
|
||||
assert obj == process(obj)
|
||||
assert obj == process(obj, '')
|
||||
|
@ -699,7 +699,26 @@
|
||||
|
||||
(defn test-require-native []
|
||||
"NATIVE: test requiring macros from native code"
|
||||
(require tests.native_tests.native_macros)
|
||||
(setv x [])
|
||||
(assert (= "failure"
|
||||
(try
|
||||
(do (setv x [])
|
||||
(rev (.append x 1) (.append x 2) (.append x 3))
|
||||
(assert (= x [3 2 1])))
|
||||
(assert (= x [3 2 1]))
|
||||
"success")
|
||||
(except [NameError] "failure"))))
|
||||
(import tests.native_tests.native_macros)
|
||||
(assert (= "failure"
|
||||
(try
|
||||
(do (setv x [])
|
||||
(rev (.append x 1) (.append x 2) (.append x 3))
|
||||
(assert (= x [3 2 1]))
|
||||
"success")
|
||||
(except [NameError] "failure"))))
|
||||
(require tests.native_tests.native_macros)
|
||||
(assert (= "success"
|
||||
(try
|
||||
(do (setv x [])
|
||||
(rev (.append x 1) (.append x 2) (.append x 3))
|
||||
(assert (= x [3 2 1]))
|
||||
"success")
|
||||
(except [NameError] "failure")))))
|
||||
|
Loading…
x
Reference in New Issue
Block a user