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:
Kodi Arfer 2018-04-18 11:59:01 -07:00
parent 11f1c149ef
commit 9368e4bc4e
6 changed files with 74 additions and 128 deletions

View File

@ -34,6 +34,7 @@ Other Breaking Changes
* `HyKeyword` no longer inherits from the string type and has been * `HyKeyword` no longer inherits from the string type and has been
made into its own object type. made into its own object type.
* `(except)` is no longer allowed. Use `(except [])` instead. * `(except)` is no longer allowed. Use `(except [])` instead.
* `(import [foo])` is no longer allowed. Use `(import foo)` instead.
New Features New Features
------------------------------ ------------------------------

View File

@ -186,8 +186,8 @@ def ideas_macro(ETname):
""")]) """)])
require("hy.cmdline", "__console__", all_macros=True) require("hy.cmdline", "__console__", assignments="ALL")
require("hy.cmdline", "__main__", all_macros=True) require("hy.cmdline", "__main__", assignments="ALL")
SIMPLE_TRACEBACKS = True SIMPLE_TRACEBACKS = True

View File

@ -1050,76 +1050,6 @@ class HyASTCompiler(object):
node = asty.YieldFrom if expr[0] == "yield-from" else asty.Await node = asty.YieldFrom if expr[0] == "yield-from" else asty.Await
return ret + node(expr, value=ret.force_expr) 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)]) @special("get", [FORM, oneplus(FORM)])
def compile_index_expression(self, expr, name, obj, indices): def compile_index_expression(self, expr, name, obj, indices):
indices, ret, _ = self._compile_collect(indices) indices, ret, _ = self._compile_collect(indices)
@ -1315,53 +1245,70 @@ class HyASTCompiler(object):
return operand return operand
@builds("require") @special(["import", "require"], [many(
def compile_require(self, expression): 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 TODO for `require`: keep track of what we've imported in this run and
"unimport" it after we've completed `thing' so that we don't pollute then "unimport" it after we've completed `thing' so that we don't
other envs. pollute other envs.
""" """
for entry in expression[1:]: ret = Result()
for entry in entries:
assignments = "ALL"
prefix = ""
if isinstance(entry, HySymbol): if isinstance(entry, HySymbol):
# e.g., (require foo) # e.g., (import foo)
__import__(entry) module, prefix = entry, entry
require(entry, self.module_name, all_macros=True, elif isinstance(entry, HyList) and isinstance(entry[1], HySymbol):
prefix=entry) # e.g., (import [foo :as bar])
elif isinstance(entry, HyList) and len(entry) == 2: module, prefix = entry
# e.g., (require [foo [bar baz :as MyBaz bing]]) else:
# or (require [foo [*]]) # e.g., (import [foo [bar baz :as MyBaz bing]])
module, names = entry # or (import [foo [*]])
if not isinstance(names, HyList): module, kids = entry
raise HyTypeError(names, kids = kids[0]
"(require) name lists should be HyLists") if (HySymbol('*'), None) in kids:
__import__(module) if len(kids) != 1:
if '*' in names: star = kids[kids.index((HySymbol('*'), None))][0]
if len(names) != 1: raise HyTypeError(star, "* in an import name list "
raise HyTypeError(names, "* in a (require) name list "
"must be on its own") "must be on its own")
require(module, self.module_name, all_macros=True)
else: else:
assignments = {} assignments = [(k, v or k) for k, v in kids]
while names:
if len(names) > 1 and names[1] == HyKeyword("as"): if root == HySymbol("import"):
k, _, v = names[:3] ast_module = ast_str(module, piecewise=True)
del names[:3] module = ast_module.lstrip(".")
assignments[k] = v 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: else:
symbol = names.pop(0) node = asty.ImportFrom
assignments[symbol] = symbol names = [
require(module, self.module_name, assignments=assignments) ast.alias(
elif (isinstance(entry, HyList) and len(entry) == 3 name=ast_str(k),
and entry[1] == HyKeyword("as")): asname=None if v == k else ast_str(v))
# e.g., (require [foo :as bar]) for k, v in assignments]
module, _, prefix = entry ret += node(
expr, module=module or None, names=names, level=level)
else: # root == HySymbol("require")
__import__(module) __import__(module)
require(module, self.module_name, all_macros=True, require(module, self.module_name,
prefix=prefix) assignments=assignments, prefix=prefix)
else:
raise HyTypeError(entry, "unrecognized (require) syntax") return ret
return Result()
@special(["and", "or"], [many(FORM)]) @special(["and", "or"], [many(FORM)])
def compile_logical_or_and_and_operator(self, expr, operator, args): def compile_logical_or_and_and_operator(self, expr, operator, args):

View File

@ -80,11 +80,10 @@ def tag(name):
return _ return _
def require(source_module, target_module, def require(source_module, target_module, assignments, prefix=""):
all_macros=False, assignments={}, prefix=""):
"""Load macros from `source_module` in the namespace of """Load macros from `source_module` in the namespace of
`target_module`. `assignments` maps old names to new names, but is `target_module`. `assignments` maps old names to new names, or
ignored if `all_macros` is true. If `prefix` is nonempty, it is should be the string "ALL". If `prefix` is nonempty, it is
prepended to the name of each imported macro. (This means you get prepended to the name of each imported macro. (This means you get
macros named things like "mymacromodule.mymacro", which looks like macros named things like "mymacromodule.mymacro", which looks like
an attribute of a module, although it's actually just a symbol 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() seen_names = set()
if prefix: if prefix:
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 d in _hy_macros, _hy_tag:
for name, macro in d[source_module].items(): for name, macro in d[source_module].items():
seen_names.add(name) seen_names.add(name)
if all_macros: if assignments == "ALL":
d[target_module][mangle(prefix + name)] = macro d[target_module][mangle(prefix + name)] = macro
elif name in assignments: elif name in assignments:
d[target_module][mangle(prefix + assignments[name])] = macro d[target_module][mangle(prefix + assignments[name])] = macro
if not all_macros: if assignments != "ALL":
unseen = frozenset(assignments.keys()).difference(seen_names) unseen = frozenset(assignments.keys()).difference(seen_names)
if unseen: if unseen:
raise ImportError("cannot require names: " + repr(list(unseen))) raise ImportError("cannot require names: " + repr(list(unseen)))

View File

@ -238,10 +238,10 @@ class HySequence(HyObject, list):
An abstract type for sequence-like models to inherit from. An abstract type for sequence-like models to inherit from.
""" """
def replace(self, other): def replace(self, other, recursive=True):
if recursive:
for x in self: for x in self:
replace_hy_obj(x, other) replace_hy_obj(x, other)
HyObject.replace(self, other) HyObject.replace(self, other)
return self return self

View File

@ -1396,8 +1396,6 @@
(import [os.path [basename :as bn]]) (import [os.path [basename :as bn]])
(assert (= bn basename)) (assert (= bn basename))
(import [sys])
;; Multiple stuff to import ;; Multiple stuff to import
(import sys [os.path [dirname]] (import sys [os.path [dirname]]
[os.path :as op] [os.path :as op]