Use model patterns for import
and require
In the process, I've banned the syntax `(import [foo])` in favor of `(import foo)`.
This commit is contained in:
parent
11f1c149ef
commit
9368e4bc4e
1
NEWS.rst
1
NEWS.rst
@ -34,6 +34,7 @@ Other Breaking Changes
|
||||
* `HyKeyword` no longer inherits from the string type and has been
|
||||
made into its own object type.
|
||||
* `(except)` is no longer allowed. Use `(except [])` instead.
|
||||
* `(import [foo])` is no longer allowed. Use `(import foo)` instead.
|
||||
|
||||
New Features
|
||||
------------------------------
|
||||
|
@ -186,8 +186,8 @@ def ideas_macro(ETname):
|
||||
|
||||
""")])
|
||||
|
||||
require("hy.cmdline", "__console__", all_macros=True)
|
||||
require("hy.cmdline", "__main__", all_macros=True)
|
||||
require("hy.cmdline", "__console__", assignments="ALL")
|
||||
require("hy.cmdline", "__main__", assignments="ALL")
|
||||
|
||||
SIMPLE_TRACEBACKS = True
|
||||
|
||||
|
173
hy/compiler.py
173
hy/compiler.py
@ -1050,76 +1050,6 @@ class HyASTCompiler(object):
|
||||
node = asty.YieldFrom if expr[0] == "yield-from" else asty.Await
|
||||
return ret + node(expr, value=ret.force_expr)
|
||||
|
||||
@builds("import")
|
||||
def compile_import_expression(self, expr):
|
||||
expr = copy.deepcopy(expr)
|
||||
def _compile_import(expr, module, names=None, importer=asty.Import):
|
||||
if not names:
|
||||
names = [ast.alias(name=ast_str(module, piecewise=True), asname=None)]
|
||||
|
||||
ast_module = ast_str(module, piecewise=True)
|
||||
module = ast_module.lstrip(".")
|
||||
level = len(ast_module) - len(module)
|
||||
if not module:
|
||||
module = None
|
||||
|
||||
return Result() + importer(
|
||||
expr, module=module, names=names, level=level)
|
||||
|
||||
expr.pop(0) # index
|
||||
rimports = Result()
|
||||
while len(expr) > 0:
|
||||
iexpr = expr.pop(0)
|
||||
|
||||
if not isinstance(iexpr, (HySymbol, HyList)):
|
||||
raise HyTypeError(iexpr, "(import) requires a Symbol "
|
||||
"or a List.")
|
||||
|
||||
if isinstance(iexpr, HySymbol):
|
||||
rimports += _compile_import(expr, iexpr)
|
||||
continue
|
||||
|
||||
if isinstance(iexpr, HyList) and len(iexpr) == 1:
|
||||
rimports += _compile_import(expr, iexpr.pop(0))
|
||||
continue
|
||||
|
||||
if isinstance(iexpr, HyList) and iexpr:
|
||||
module = iexpr.pop(0)
|
||||
entry = iexpr[0]
|
||||
if entry == HyKeyword("as"):
|
||||
if not len(iexpr) == 2:
|
||||
raise HyTypeError(iexpr,
|
||||
"garbage after aliased import")
|
||||
iexpr.pop(0) # :as
|
||||
alias = iexpr.pop(0)
|
||||
names = [ast.alias(name=ast_str(module, piecewise=True),
|
||||
asname=ast_str(alias))]
|
||||
rimports += _compile_import(expr, ast_str(module), names)
|
||||
continue
|
||||
|
||||
if isinstance(entry, HyList):
|
||||
names = []
|
||||
while entry:
|
||||
sym = entry.pop(0)
|
||||
if entry and isinstance(entry[0], HyKeyword):
|
||||
entry.pop(0)
|
||||
alias = ast_str(entry.pop(0))
|
||||
else:
|
||||
alias = None
|
||||
names.append(ast.alias(name=(str(sym) if sym == "*" else ast_str(sym)),
|
||||
asname=alias))
|
||||
|
||||
rimports += _compile_import(expr, module,
|
||||
names, asty.ImportFrom)
|
||||
continue
|
||||
|
||||
raise HyTypeError(
|
||||
entry,
|
||||
"Unknown entry (`%s`) in the HyList" % (entry)
|
||||
)
|
||||
|
||||
return rimports
|
||||
|
||||
@special("get", [FORM, oneplus(FORM)])
|
||||
def compile_index_expression(self, expr, name, obj, indices):
|
||||
indices, ret, _ = self._compile_collect(indices)
|
||||
@ -1315,53 +1245,70 @@ class HyASTCompiler(object):
|
||||
|
||||
return operand
|
||||
|
||||
@builds("require")
|
||||
def compile_require(self, expression):
|
||||
@special(["import", "require"], [many(
|
||||
SYM |
|
||||
brackets(SYM, sym(":as"), SYM) |
|
||||
brackets(SYM, brackets(many(SYM + maybe(sym(":as") + SYM)))))])
|
||||
def compile_import_or_require(self, expr, root, entries):
|
||||
"""
|
||||
TODO: keep track of what we've imported in this run and then
|
||||
"unimport" it after we've completed `thing' so that we don't pollute
|
||||
other envs.
|
||||
TODO for `require`: keep track of what we've imported in this run and
|
||||
then "unimport" it after we've completed `thing' so that we don't
|
||||
pollute other envs.
|
||||
"""
|
||||
for entry in expression[1:]:
|
||||
ret = Result()
|
||||
|
||||
for entry in entries:
|
||||
assignments = "ALL"
|
||||
prefix = ""
|
||||
|
||||
if isinstance(entry, HySymbol):
|
||||
# e.g., (require foo)
|
||||
__import__(entry)
|
||||
require(entry, self.module_name, all_macros=True,
|
||||
prefix=entry)
|
||||
elif isinstance(entry, HyList) and len(entry) == 2:
|
||||
# e.g., (require [foo [bar baz :as MyBaz bing]])
|
||||
# or (require [foo [*]])
|
||||
module, names = entry
|
||||
if not isinstance(names, HyList):
|
||||
raise HyTypeError(names,
|
||||
"(require) name lists should be HyLists")
|
||||
__import__(module)
|
||||
if '*' in names:
|
||||
if len(names) != 1:
|
||||
raise HyTypeError(names, "* in a (require) name list "
|
||||
"must be on its own")
|
||||
require(module, self.module_name, all_macros=True)
|
||||
else:
|
||||
assignments = {}
|
||||
while names:
|
||||
if len(names) > 1 and names[1] == HyKeyword("as"):
|
||||
k, _, v = names[:3]
|
||||
del names[:3]
|
||||
assignments[k] = v
|
||||
else:
|
||||
symbol = names.pop(0)
|
||||
assignments[symbol] = symbol
|
||||
require(module, self.module_name, assignments=assignments)
|
||||
elif (isinstance(entry, HyList) and len(entry) == 3
|
||||
and entry[1] == HyKeyword("as")):
|
||||
# e.g., (require [foo :as bar])
|
||||
module, _, prefix = entry
|
||||
__import__(module)
|
||||
require(module, self.module_name, all_macros=True,
|
||||
prefix=prefix)
|
||||
# e.g., (import foo)
|
||||
module, prefix = entry, entry
|
||||
elif isinstance(entry, HyList) and isinstance(entry[1], HySymbol):
|
||||
# e.g., (import [foo :as bar])
|
||||
module, prefix = entry
|
||||
else:
|
||||
raise HyTypeError(entry, "unrecognized (require) syntax")
|
||||
return Result()
|
||||
# e.g., (import [foo [bar baz :as MyBaz bing]])
|
||||
# or (import [foo [*]])
|
||||
module, kids = entry
|
||||
kids = kids[0]
|
||||
if (HySymbol('*'), None) in kids:
|
||||
if len(kids) != 1:
|
||||
star = kids[kids.index((HySymbol('*'), None))][0]
|
||||
raise HyTypeError(star, "* in an import name list "
|
||||
"must be on its own")
|
||||
else:
|
||||
assignments = [(k, v or k) for k, v in kids]
|
||||
|
||||
if root == HySymbol("import"):
|
||||
ast_module = ast_str(module, piecewise=True)
|
||||
module = ast_module.lstrip(".")
|
||||
level = len(ast_module) - len(module)
|
||||
if assignments == "ALL" and prefix == "":
|
||||
node = asty.ImportFrom
|
||||
names = [ast.alias(name="*", asname=None)]
|
||||
elif assignments == "ALL":
|
||||
node = asty.Import
|
||||
names = [ast.alias(
|
||||
name=ast_module,
|
||||
asname=ast_str(prefix)
|
||||
if prefix and prefix != module
|
||||
else None)]
|
||||
else:
|
||||
node = asty.ImportFrom
|
||||
names = [
|
||||
ast.alias(
|
||||
name=ast_str(k),
|
||||
asname=None if v == k else ast_str(v))
|
||||
for k, v in assignments]
|
||||
ret += node(
|
||||
expr, module=module or None, names=names, level=level)
|
||||
else: # root == HySymbol("require")
|
||||
__import__(module)
|
||||
require(module, self.module_name,
|
||||
assignments=assignments, prefix=prefix)
|
||||
|
||||
return ret
|
||||
|
||||
@special(["and", "or"], [many(FORM)])
|
||||
def compile_logical_or_and_and_operator(self, expr, operator, args):
|
||||
|
14
hy/macros.py
14
hy/macros.py
@ -80,11 +80,10 @@ def tag(name):
|
||||
return _
|
||||
|
||||
|
||||
def require(source_module, target_module,
|
||||
all_macros=False, assignments={}, prefix=""):
|
||||
def require(source_module, target_module, assignments, prefix=""):
|
||||
"""Load macros from `source_module` in the namespace of
|
||||
`target_module`. `assignments` maps old names to new names, but is
|
||||
ignored if `all_macros` is true. If `prefix` is nonempty, it is
|
||||
`target_module`. `assignments` maps old names to new names, or
|
||||
should be the string "ALL". If `prefix` is nonempty, it is
|
||||
prepended to the name of each imported macro. (This means you get
|
||||
macros named things like "mymacromodule.mymacro", which looks like
|
||||
an attribute of a module, although it's actually just a symbol
|
||||
@ -97,17 +96,18 @@ def require(source_module, target_module,
|
||||
seen_names = set()
|
||||
if prefix:
|
||||
prefix += "."
|
||||
assignments = {mangle(str_type(k)): v for k, v in assignments.items()}
|
||||
if assignments != "ALL":
|
||||
assignments = {mangle(str_type(k)): v for k, v in assignments}
|
||||
|
||||
for d in _hy_macros, _hy_tag:
|
||||
for name, macro in d[source_module].items():
|
||||
seen_names.add(name)
|
||||
if all_macros:
|
||||
if assignments == "ALL":
|
||||
d[target_module][mangle(prefix + name)] = macro
|
||||
elif name in assignments:
|
||||
d[target_module][mangle(prefix + assignments[name])] = macro
|
||||
|
||||
if not all_macros:
|
||||
if assignments != "ALL":
|
||||
unseen = frozenset(assignments.keys()).difference(seen_names)
|
||||
if unseen:
|
||||
raise ImportError("cannot require names: " + repr(list(unseen)))
|
||||
|
@ -238,10 +238,10 @@ class HySequence(HyObject, list):
|
||||
An abstract type for sequence-like models to inherit from.
|
||||
"""
|
||||
|
||||
def replace(self, other):
|
||||
for x in self:
|
||||
replace_hy_obj(x, other)
|
||||
|
||||
def replace(self, other, recursive=True):
|
||||
if recursive:
|
||||
for x in self:
|
||||
replace_hy_obj(x, other)
|
||||
HyObject.replace(self, other)
|
||||
return self
|
||||
|
||||
|
@ -1396,8 +1396,6 @@
|
||||
(import [os.path [basename :as bn]])
|
||||
(assert (= bn basename))
|
||||
|
||||
(import [sys])
|
||||
|
||||
;; Multiple stuff to import
|
||||
(import sys [os.path [dirname]]
|
||||
[os.path :as op]
|
||||
|
Loading…
x
Reference in New Issue
Block a user