From 9368e4bc4ee6bf5b32faa39366e62020b45d9b06 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Wed, 18 Apr 2018 11:59:01 -0700 Subject: [PATCH] Use model patterns for `import` and `require` In the process, I've banned the syntax `(import [foo])` in favor of `(import foo)`. --- NEWS.rst | 1 + hy/cmdline.py | 4 +- hy/compiler.py | 173 ++++++++++++--------------------- hy/macros.py | 14 +-- hy/models.py | 8 +- tests/native_tests/language.hy | 2 - 6 files changed, 74 insertions(+), 128 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 5bf5c03..743835a 100644 --- a/NEWS.rst +++ b/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 ------------------------------ diff --git a/hy/cmdline.py b/hy/cmdline.py index d99a7aa..3f2bb1f 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -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 diff --git a/hy/compiler.py b/hy/compiler.py index f37359b..e5b8d78 100755 --- a/hy/compiler.py +++ b/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): diff --git a/hy/macros.py b/hy/macros.py index e4cafee..040b2f9 100644 --- a/hy/macros.py +++ b/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))) diff --git a/hy/models.py b/hy/models.py index a5e3b6e..690025f 100644 --- a/hy/models.py +++ b/hy/models.py @@ -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 diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index a92628f..8dc2bfd 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -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]