From ce6d23806ac4c91e678c34f24d0010829fbbfc94 Mon Sep 17 00:00:00 2001 From: Joseph LaFreniere Date: Fri, 28 Jan 2022 18:29:57 -0600 Subject: [PATCH] Autosort all Python files using black and isort --- .isort.cfg | 1 - bin/py2ast | 3 +- conftest.py | 24 +- docs/conf.py | 121 +-- fastentrypoints.py | 46 +- get_version.py | 17 +- hy/__init__.py | 39 +- hy/_compat.py | 41 +- hy/cmdline.py | 463 ++++++----- hy/compiler.py | 314 ++++---- hy/completer.py | 28 +- hy/core/result_macros.py | 938 ++++++++++++++--------- hy/errors.py | 98 +-- hy/importer.py | 47 +- hy/lex/__init__.py | 126 +-- hy/lex/exceptions.py | 8 +- hy/lex/lexer.py | 56 +- hy/lex/parser.py | 248 +++--- hy/macros.py | 148 ++-- hy/model_patterns.py | 73 +- hy/models.py | 171 +++-- hy/scoping.py | 18 +- setup.py | 47 +- tests/compilers/test_ast.py | 164 ++-- tests/compilers/test_compiler.py | 28 +- tests/importer/test_importer.py | 131 ++-- tests/macros/test_macro_processor.py | 54 +- tests/resources/__init__.py | 3 + tests/resources/importer/foo/__init__.py | 4 +- tests/resources/importer/foo/some_mod.py | 4 +- tests/test_bin.py | 234 +++--- tests/test_completer.py | 7 +- tests/test_hy2py.py | 33 +- tests/test_lex.py | 328 +++++--- tests/test_models.py | 103 ++- 35 files changed, 2460 insertions(+), 1708 deletions(-) diff --git a/.isort.cfg b/.isort.cfg index 3ed241e57..837d69735 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,6 +1,5 @@ [settings] line_length = 88 profile = black -py_version = 39 skip_gitignore = true src_paths = hy,tests diff --git a/bin/py2ast b/bin/py2ast index 038041b2c..b23cc5776 100755 --- a/bin/py2ast +++ b/bin/py2ast @@ -1,7 +1,6 @@ #!/usr/bin/env python -import sys import ast - +import sys print(ast.dump(ast.parse(open(sys.argv[1], 'r').read()))) diff --git a/conftest.py b/conftest.py index b0af527cf..5a0d61a94 100644 --- a/conftest.py +++ b/conftest.py @@ -1,10 +1,11 @@ -import sys -import os import importlib -from operator import or_ +import os +import sys from functools import reduce +from operator import or_ import pytest + import hy from hy._compat import PY3_8, PY3_10 @@ -21,16 +22,21 @@ def pytest_ignore_collect(path, config): (PY3_10, "py3_10_only"), ] - return reduce( - or_, - (name in path.basename and not condition for condition, name in versions), - ) or None + return ( + reduce( + or_, + (name in path.basename and not condition for condition, name in versions), + ) + or None + ) def pytest_collect_file(parent, path): - if (path.ext == ".hy" + if ( + path.ext == ".hy" and NATIVE_TESTS in path.dirname + os.sep - and path.basename != "__init__.hy"): + and path.basename != "__init__.hy" + ): if hasattr(pytest.Module, "from_parent"): pytest_mod = pytest.Module.from_parent(parent, fspath=path) diff --git a/docs/conf.py b/docs/conf.py index a384609de..986817839 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,30 +1,34 @@ # This file is execfile()d with the current directory set to its containing dir. -import re, os, sys, time, html +import html +import os +import re +import sys +import time -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) extensions = [ - 'sphinx.ext.napoleon', - 'sphinx.ext.intersphinx', - 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', - 'sphinxcontrib.hydomain', + "sphinx.ext.napoleon", + "sphinx.ext.intersphinx", + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinxcontrib.hydomain", ] from get_version import __version__ as hy_version # Read the Docs might dirty its checkout, so strip the dirty flag. -hy_version = re.sub(r'[+.]dirty\Z', '', hy_version) +hy_version = re.sub(r"[+.]dirty\Z", "", hy_version) -templates_path = ['_templates'] -source_suffix = '.rst' +templates_path = ["_templates"] +source_suffix = ".rst" -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'hy' -copyright = '%s the authors' % time.strftime('%Y') +project = "hy" +copyright = "%s the authors" % time.strftime("%Y") # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -38,104 +42,107 @@ if "+" in hy_version: hy_descriptive_version += " (unstable)" -exclude_patterns = ['_build', 'coreteam.rst'] +exclude_patterns = ["_build", "coreteam.rst"] add_module_names = True -pygments_style = 'sphinx' +pygments_style = "sphinx" import sphinx_rtd_theme -html_theme = 'sphinx_rtd_theme' + +html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] html_use_smartypants = False html_show_sphinx = False html_context = dict( - hy_descriptive_version = hy_descriptive_version, - has_active_alpha = True, + hy_descriptive_version=hy_descriptive_version, + has_active_alpha=True, ) -highlight_language = 'clojure' +highlight_language = "clojure" intersphinx_mapping = dict( - py = ('https://docs.python.org/3/', None), - py3_10 = ('https://docs.python.org/3.10/', None), - hyrule = ('https://hyrule.readthedocs.io/en/master/', None)) + py=("https://docs.python.org/3/", None), + py3_10=("https://docs.python.org/3.10/", None), + hyrule=("https://hyrule.readthedocs.io/en/master/", None), +) # ** Generate Cheatsheet import json -from pathlib import Path from itertools import zip_longest +from pathlib import Path + def refize(spec): - role = ':hy:func:' + role = ":hy:func:" if isinstance(spec, dict): - _name = spec['name'] - uri = spec['uri'] - if spec.get('internal'): - role = ':ref:' + _name = spec["name"] + uri = spec["uri"] + if spec.get("internal"): + role = ":ref:" else: uri = spec - _name = str.split(uri, '.')[-1] - return '{}`{} <{}>`'.format(role, _name, uri) + _name = str.split(uri, ".")[-1] + return "{}`{} <{}>`".format(role, _name, uri) def format_refs(refs, indent): args = [iter(map(refize, refs))] ref_groups = zip_longest(*args, fillvalue="") return str.join( - ' \\\n' + ' ' * (indent + 3), - [str.join(' ', ref_group) for ref_group in ref_groups], + " \\\n" + " " * (indent + 3), + [str.join(" ", ref_group) for ref_group in ref_groups], ) def format_row(category, divider_loc): - return '{title: <{width}} | {methods}'.format( + return "{title: <{width}} | {methods}".format( width=divider_loc, - title=category['name'], - methods=format_refs(category['methods'], divider_loc) + title=category["name"], + methods=format_refs(category["methods"], divider_loc), ) def format_table(table_spec): - table_name = table_spec['name'] - categories = table_spec['categories'] - longest_cat_name = max(len(category['name']) for category in categories) + table_name = table_spec["name"] + categories = table_spec["categories"] + longest_cat_name = max(len(category["name"]) for category in categories) table = [ table_name, - '-' * len(table_name), - '', - '=' * longest_cat_name + ' ' + '=' * 25, + "-" * len(table_name), + "", + "=" * longest_cat_name + " " + "=" * 25, *(format_row(category, longest_cat_name) for category in categories), - '=' * longest_cat_name + ' ' + '=' * 25, - '' + "=" * longest_cat_name + " " + "=" * 25, + "", ] - return '\n'.join(table) + return "\n".join(table) # Modifications to the cheatsheet should be added in `cheatsheet.json` -cheatsheet_spec = json.loads(Path('./docs/cheatsheet.json').read_text()) +cheatsheet_spec = json.loads(Path("./docs/cheatsheet.json").read_text()) cheatsheet = [ - '..', - ' DO NOT MODIFY THIS FILE. IT IS AUTO GENERATED BY ``conf.py``', - ' If you need to change or add methods, modify ``cheatsheet_spec`` in ``conf.py``', - '', - '.. _cheatsheet:', - '', - 'Cheatsheet', - '==========', - '', + "..", + " DO NOT MODIFY THIS FILE. IT IS AUTO GENERATED BY ``conf.py``", + " If you need to change or add methods, modify ``cheatsheet_spec`` in ``conf.py``", + "", + ".. _cheatsheet:", + "", + "Cheatsheet", + "==========", + "", *map(format_table, cheatsheet_spec), ] -Path('./docs/cheatsheet.rst').write_text('\n'.join(cheatsheet)) +Path("./docs/cheatsheet.rst").write_text("\n".join(cheatsheet)) # ** Sphinx App Setup def setup(app): - app.add_css_file('overrides.css') + app.add_css_file("overrides.css") diff --git a/fastentrypoints.py b/fastentrypoints.py index 9707f74a3..4369fbecd 100644 --- a/fastentrypoints.py +++ b/fastentrypoints.py @@ -24,7 +24,7 @@ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -''' +""" Monkey patch setuptools to write faster console_scripts with this format: import sys @@ -35,10 +35,12 @@ (c) 2016, Aaron Christianson http://github.com/ninjaaron/fast-entry_points -''' -from setuptools.command import easy_install +""" import re -TEMPLATE = '''\ + +from setuptools.command import easy_install + +TEMPLATE = """\ # -*- coding: utf-8 -*- # EASY-INSTALL-ENTRY-SCRIPT: '{3}','{4}','{5}' __requires__ = '{3}' @@ -49,7 +51,7 @@ if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit({2}())''' + sys.exit({2}())""" @classmethod @@ -62,15 +64,15 @@ def get_args(cls, dist, header=None): # noqa: D205,D400 # pylint: disable=E1101 header = cls.get_header() spec = str(dist.as_requirement()) - for type_ in 'console', 'gui': - group = type_ + '_scripts' + for type_ in "console", "gui": + group = type_ + "_scripts" for name, ep in dist.get_entry_map(group).items(): # ensure_safe_name - if re.search(r'[\\/]', name): + if re.search(r"[\\/]", name): raise ValueError("Path separators not allowed in script names") script_text = TEMPLATE.format( - ep.module_name, ep.attrs[0], '.'.join(ep.attrs), - spec, group, name) + ep.module_name, ep.attrs[0], ".".join(ep.attrs), spec, group, name + ) # pylint: disable=E1101 args = cls._get_script_args(type_, name, header, script_text) for res in args: @@ -86,27 +88,29 @@ def main(): import re import shutil import sys - dests = sys.argv[1:] or ['.'] - filename = re.sub('\.pyc$', '.py', __file__) + + dests = sys.argv[1:] or ["."] + filename = re.sub("\.pyc$", ".py", __file__) for dst in dests: shutil.copy(filename, dst) - manifest_path = os.path.join(dst, 'MANIFEST.in') - setup_path = os.path.join(dst, 'setup.py') + manifest_path = os.path.join(dst, "MANIFEST.in") + setup_path = os.path.join(dst, "setup.py") # Insert the include statement to MANIFEST.in if not present - with open(manifest_path, 'a+') as manifest: + with open(manifest_path, "a+") as manifest: manifest.seek(0) manifest_content = manifest.read() - if 'include fastentrypoints.py' not in manifest_content: - manifest.write(('\n' if manifest_content else '') + - 'include fastentrypoints.py') + if "include fastentrypoints.py" not in manifest_content: + manifest.write( + ("\n" if manifest_content else "") + "include fastentrypoints.py" + ) # Insert the import statement to setup.py if not present - with open(setup_path, 'a+') as setup: + with open(setup_path, "a+") as setup: setup.seek(0) setup_content = setup.read() - if 'import fastentrypoints' not in setup_content: + if "import fastentrypoints" not in setup_content: setup.seek(0) setup.truncate() - setup.write('import fastentrypoints\n' + setup_content) + setup.write("import fastentrypoints\n" + setup_content) diff --git a/get_version.py b/get_version.py index c6af6db8c..6ee1e6664 100644 --- a/get_version.py +++ b/get_version.py @@ -1,4 +1,6 @@ -import os, subprocess, runpy +import os +import runpy +import subprocess # Try to get and update the version. @@ -9,16 +11,19 @@ if "HY_VERSION" in os.environ: __version__ = os.environ["HY_VERSION"] else: - __version__ = (subprocess.check_output - (["git", "describe", "--tags", "--dirty"]) - .decode('ASCII').strip() - .replace('-', '+', 1).replace('-', '.')) + __version__ = ( + subprocess.check_output(["git", "describe", "--tags", "--dirty"]) + .decode("ASCII") + .strip() + .replace("-", "+", 1) + .replace("-", ".") + ) with open(VERSIONFILE, "wt") as o: o.write("__version__ = {!r}\n".format(__version__)) except (subprocess.CalledProcessError, OSError): if os.path.exists(VERSIONFILE): - __version__ = runpy.run_path(VERSIONFILE)['__version__'] + __version__ = runpy.run_path(VERSIONFILE)["__version__"] else: __version__ = "unknown" diff --git a/hy/__init__.py b/hy/__init__.py index f8575db48..20be2a9fc 100644 --- a/hy/__init__.py +++ b/hy/__init__.py @@ -1,15 +1,17 @@ try: from hy.version import __version__ except ImportError: - __version__ = 'unknown' + __version__ = "unknown" def _initialize_env_var(env_var, default_val): import os + return bool(os.environ.get(env_var, default_val)) import hy.importer # NOQA + hy.importer._inject_builtins() # we import for side-effects. @@ -19,31 +21,34 @@ def _initialize_env_var(env_var, default_val): # to be loaded if they're not needed. _jit_imports = dict( - read = "hy.lex", - read_str = "hy.lex", - mangle = "hy.lex", - unmangle = "hy.lex", - eval = ["hy.compiler", "hy_eval"], - repr = ["hy.core.hy_repr", "hy_repr"], - repr_register = ["hy.core.hy_repr", "hy_repr_register"], - gensym = "hy.core.util", - macroexpand = "hy.core.util", - macroexpand_1 = "hy.core.util", - disassemble = "hy.core.util", - as_model = "hy.models") + read="hy.lex", + read_str="hy.lex", + mangle="hy.lex", + unmangle="hy.lex", + eval=["hy.compiler", "hy_eval"], + repr=["hy.core.hy_repr", "hy_repr"], + repr_register=["hy.core.hy_repr", "hy_repr_register"], + gensym="hy.core.util", + macroexpand="hy.core.util", + macroexpand_1="hy.core.util", + disassemble="hy.core.util", + as_model="hy.models", +) + def __getattr__(k): - if k == 'pyops': + if k == "pyops": global pyops import hy.pyops + pyops = hy.pyops return pyops if k not in _jit_imports: - raise AttributeError(f'module {__name__!r} has no attribute {k!r}') + raise AttributeError(f"module {__name__!r} has no attribute {k!r}") v = _jit_imports[k] module, original_name = v if isinstance(v, list) else (v, k) import importlib - globals()[k] = getattr( - importlib.import_module(module), original_name) + + globals()[k] = getattr(importlib.import_module(module), original_name) return globals()[k] diff --git a/hy/_compat.py b/hy/_compat.py index de8fd5984..c45fa816d 100644 --- a/hy/_compat.py +++ b/hy/_compat.py @@ -1,5 +1,5 @@ -import sys import platform +import sys PY3_8 = sys.version_info >= (3, 8) PY3_9 = sys.version_info >= (3, 9) @@ -9,26 +9,49 @@ if not PY3_9: # Shim `ast.unparse`. - import ast, astor.code_gen + import ast + + import astor.code_gen + ast.unparse = astor.code_gen.to_source if not PY3_8: # Shim `re.Pattern`. import re + re.Pattern = type(re.compile("")) # Provide a function substitute for `CodeType.replace`. if PY3_8: + def code_replace(code_obj, **kwargs): return code_obj.replace(**kwargs) + else: - _code_args = ['co_' + c for c in ( - 'argcount', 'kwonlyargcount', 'nlocals', 'stacksize', 'flags', - 'code', 'consts', 'names', 'varnames', 'filename', 'name', - 'firstlineno', 'lnotab', 'freevars', 'cellvars')] + _code_args = [ + "co_" + c + for c in ( + "argcount", + "kwonlyargcount", + "nlocals", + "stacksize", + "flags", + "code", + "consts", + "names", + "varnames", + "filename", + "name", + "firstlineno", + "lnotab", + "freevars", + "cellvars", + ) + ] + def code_replace(code_obj, **kwargs): - return type(code_obj)(*( - kwargs.get(k, getattr(code_obj, k)) - for k in _code_args)) + return type(code_obj)( + *(kwargs.get(k, getattr(code_obj, k)) for k in _code_args) + ) diff --git a/hy/cmdline.py b/hy/cmdline.py index f321e914f..4e1c8f6ff 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -1,37 +1,40 @@ import colorama + colorama.init() import argparse -import code import ast -import sys -import os +import builtins +import code +import codeop +import hashlib import importlib +import linecache +import os import py_compile -import traceback import runpy -import types +import sys import time -import linecache -import hashlib -import codeop -import builtins +import traceback +import types +from contextlib import contextmanager import hy - +from hy.compiler import HyASTCompiler, hy_ast_compile_flags, hy_compile, hy_eval +from hy.completer import Completer, completion +from hy.errors import ( + HyLanguageError, + HyMacroExpansionError, + HyRequireError, + filtered_hy_exceptions, + hy_exc_handler, +) +from hy.importer import HyLoader, runhy from hy.lex import hy_parse, mangle -from contextlib import contextmanager from hy.lex.exceptions import PrematureEndOfInput -from hy.compiler import (HyASTCompiler, hy_eval, hy_compile, - hy_ast_compile_flags) -from hy.errors import (HyLanguageError, HyRequireError, HyMacroExpansionError, - filtered_hy_exceptions, hy_exc_handler) -from hy.importer import runhy, HyLoader -from hy.completer import completion, Completer from hy.macros import macro, require from hy.models import Expression, String, Symbol - sys.last_type = None sys.last_value = None sys.last_traceback = None @@ -53,20 +56,25 @@ def __call__(self, code=None): pass raise SystemExit(code) + class HyHelper: def __repr__(self): - return ("Use (help) for interactive help, or (help object) for help " - "about object.") + return ( + "Use (help) for interactive help, or (help object) for help " + "about object." + ) def __call__(self, *args, **kwds): import pydoc + return pydoc.help(*args, **kwds) -builtins.quit = HyQuitter('quit') -builtins.exit = HyQuitter('exit') +builtins.quit = HyQuitter("quit") +builtins.exit = HyQuitter("exit") builtins.help = HyHelper() + @contextmanager def extend_linecache(add_cmdline_cache): _linecache_checkcache = linecache.checkcache @@ -93,7 +101,7 @@ def _hy_maybe_compile(compiler, source, filename, symbol): for line in source.split("\n"): line = line.strip() - if line and line[0] != ';': + if line and line[0] != ";": # Leave it alone (could do more with Hy syntax) break else: @@ -113,8 +121,9 @@ class HyCompile(codeop.Compile): `IPython.core.compilerop.CachingCompiler`. """ - def __init__(self, module, locals, ast_callback=None, - hy_compiler=None, cmdline_cache={}): + def __init__( + self, module, locals, ast_callback=None, hy_compiler=None, cmdline_cache={} + ): self.module = module self.locals = locals self.ast_callback = ast_callback @@ -127,30 +136,31 @@ def __init__(self, module, locals, ast_callback=None, self.cmdline_cache = cmdline_cache def _cache(self, source, name): - entry = (len(source), - time.time(), - [line + '\n' for line in source.splitlines()], - name) + entry = ( + len(source), + time.time(), + [line + "\n" for line in source.splitlines()], + name, + ) linecache.cache[name] = entry self.cmdline_cache[name] = entry def _update_exc_info(self): - self.locals['_hy_last_type'] = sys.last_type - self.locals['_hy_last_value'] = sys.last_value - # Skip our frame. - sys.last_traceback = getattr(sys.last_traceback, 'tb_next', - sys.last_traceback) - self.locals['_hy_last_traceback'] = sys.last_traceback + self.locals["_hy_last_type"] = sys.last_type + self.locals["_hy_last_value"] = sys.last_value + # Skip our frame. + sys.last_traceback = getattr(sys.last_traceback, "tb_next", sys.last_traceback) + self.locals["_hy_last_traceback"] = sys.last_traceback def __call__(self, source, filename="", symbol="single"): - if source == 'pass': + if source == "pass": # We need to return a no-op to signal that no more input is needed. return (compile(source, filename, symbol),) * 2 hash_digest = hashlib.sha1(source.encode("utf-8").strip()).hexdigest() - name = '{}-{}'.format(filename.strip('<>'), hash_digest) + name = "{}-{}".format(filename.strip("<>"), hash_digest) try: hy_ast = hy_parse(source, filename=name) @@ -164,23 +174,28 @@ def __call__(self, source, filename="", symbol="single"): try: hy_ast = hy_parse(source, filename=filename) - root_ast = ast.Interactive if symbol == 'single' else ast.Module + root_ast = ast.Interactive if symbol == "single" else ast.Module # Our compiler doesn't correspond to a real, fixed source file, so # we need to [re]set these. self.hy_compiler.filename = filename self.hy_compiler.source = source - exec_ast, eval_ast = hy_compile(hy_ast, self.module, root=root_ast, - get_expr=True, - compiler=self.hy_compiler, - filename=filename, source=source, - import_stdlib=False) + exec_ast, eval_ast = hy_compile( + hy_ast, + self.module, + root=root_ast, + get_expr=True, + compiler=self.hy_compiler, + filename=filename, + source=source, + import_stdlib=False, + ) if self.ast_callback: self.ast_callback(exec_ast, eval_ast) exec_code = super().__call__(exec_ast, name, symbol) - eval_code = super().__call__(eval_ast, name, 'eval') + eval_code = super().__call__(eval_ast, name, "eval") except HyLanguageError: # Hy will raise exceptions during compile-time that Python would @@ -191,9 +206,9 @@ def __call__(self, source, filename="", symbol="single"): sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info() self._update_exc_info() exec_code = super().__call__( - 'raise _hy_last_value.with_traceback(_hy_last_traceback)', - name, symbol) - eval_code = super().__call__('None', name, 'eval') + "raise _hy_last_value.with_traceback(_hy_last_traceback)", name, symbol + ) + eval_code = super().__call__("None", name, "eval") except SyntaxError: # Capture and save the error before we get to the superclass handler @@ -223,22 +238,19 @@ def __call__(self, *args, **kwargs): class HyREPL(code.InteractiveConsole): - def __init__(self, spy=False, output_fn=None, locals=None, - filename=""): + def __init__(self, spy=False, output_fn=None, locals=None, filename=""): # Create a proper module for this REPL so that we can obtain it easily # (e.g. using `importlib.import_module`). # We let `InteractiveConsole` initialize `self.locals` when it's # `None`. - super().__init__(locals=locals, - filename=filename) + super().__init__(locals=locals, filename=filename) - module_name = self.locals.get('__name__', '__console__') + module_name = self.locals.get("__name__", "__console__") # Make sure our newly created module is properly introduced to # `sys.modules`, and consistently use its namespace as `self.locals` # from here on. - self.module = sys.modules.setdefault(module_name, - types.ModuleType(module_name)) + self.module = sys.modules.setdefault(module_name, types.ModuleType(module_name)) self.module.__dict__.update(self.locals) self.locals = self.module.__dict__ @@ -250,8 +262,8 @@ def __init__(self, spy=False, output_fn=None, locals=None, sys.modules.setdefault(mod.__name__, mod) loader.exec_module(mod) imports = mod.__dict__.get( - '__all__', - [name for name in mod.__dict__ if not name.startswith("_")] + "__all__", + [name for name in mod.__dict__ if not name.startswith("_")], ) imports = {name: mod.__dict__[name] for name in imports} spy = spy or imports.get("repl_spy") @@ -261,20 +273,22 @@ def __init__(self, spy=False, output_fn=None, locals=None, self.locals.update(imports) # load module macros - require(mod, self.module, assignments='ALL') + require(mod, self.module, assignments="ALL") except Exception as e: print(e) # Load cmdline-specific macros. - require('hy.cmdline', self.module, assignments='ALL') + require("hy.cmdline", self.module, assignments="ALL") self.hy_compiler = HyASTCompiler(self.module) self.cmdline_cache = {} - self.compile = HyCommandCompiler(self.module, - self.locals, - ast_callback=self.ast_callback, - hy_compiler=self.hy_compiler, - cmdline_cache=self.cmdline_cache) + self.compile = HyCommandCompiler( + self.module, + self.locals, + ast_callback=self.ast_callback, + hy_compiler=self.hy_compiler, + cmdline_cache=self.cmdline_cache, + ) self.spy = spy self.last_value = None @@ -286,7 +300,7 @@ def __init__(self, spy=False, output_fn=None, locals=None, self.output_fn = output_fn elif "." in output_fn: parts = [mangle(x) for x in output_fn.split(".")] - module, f = '.'.join(parts[:-1]), parts[-1] + module, f = ".".join(parts[:-1]), parts[-1] self.output_fn = getattr(importlib.import_module(module), f) else: self.output_fn = getattr(builtins, mangle(output_fn)) @@ -296,10 +310,12 @@ def __init__(self, spy=False, output_fn=None, locals=None, self.locals.update({sym: None for sym in self._repl_results_symbols}) # Allow access to the running REPL instance - self.locals['_hy_repl'] = self + self.locals["_hy_repl"] = self # Compile an empty statement to load the standard prelude - exec_ast = hy_compile(hy_parse(''), self.module, compiler=self.hy_compiler, import_stdlib=True) + exec_ast = hy_compile( + hy_parse(""), self.module, compiler=self.hy_compiler, import_stdlib=True + ) if self.ast_callback: self.ast_callback(exec_ast, None) @@ -309,12 +325,13 @@ def ast_callback(self, exec_ast, eval_ast): # Mush the two AST chunks into a single module for # conversion into Python. new_ast = ast.Module( - exec_ast.body + ([] if eval_ast is None else [ast.Expr(eval_ast.body)]), - type_ignores=[]) + exec_ast.body + + ([] if eval_ast is None else [ast.Expr(eval_ast.body)]), + type_ignores=[], + ) print(ast.unparse(new_ast)) except Exception: - msg = 'Exception in AST callback:\n{}\n'.format( - traceback.format_exc()) + msg = "Exception in AST callback:\n{}\n".format(traceback.format_exc()) self.write(msg) def _error_wrap(self, error_fn, exc_info_override=False, *args, **kwargs): @@ -322,10 +339,11 @@ def _error_wrap(self, error_fn, exc_info_override=False, *args, **kwargs): if exc_info_override: # Use a traceback that doesn't have the REPL frames. - sys.last_type = self.locals.get('_hy_last_type', sys.last_type) - sys.last_value = self.locals.get('_hy_last_value', sys.last_value) - sys.last_traceback = self.locals.get('_hy_last_traceback', - sys.last_traceback) + sys.last_type = self.locals.get("_hy_last_type", sys.last_type) + sys.last_value = self.locals.get("_hy_last_value", sys.last_value) + sys.last_traceback = self.locals.get( + "_hy_last_traceback", sys.last_traceback + ) sys.excepthook(sys.last_type, sys.last_value, sys.last_traceback) @@ -335,9 +353,9 @@ def showsyntaxerror(self, filename=None): if filename is None: filename = self.filename - self._error_wrap(super().showsyntaxerror, - exc_info_override=True, - filename=filename) + self._error_wrap( + super().showsyntaxerror, exc_info_override=True, filename=filename + ) def showtraceback(self): self._error_wrap(super().showtraceback) @@ -355,7 +373,7 @@ def runcode(self, code): self.print_last_value = False self.showtraceback() - def runsource(self, source, filename='', symbol='exec'): + def runsource(self, source, filename="", symbol="exec"): try: res = super().runsource(source, filename, symbol) except (HyMacroExpansionError, HyRequireError): @@ -390,8 +408,11 @@ def runsource(self, source, filename='', symbol='exec'): @macro("koan") def koan_macro(ETname): - return Expression([Symbol('print'), - String(""" + return Expression( + [ + Symbol("print"), + String( + """ Ummon asked the head monk, "What sutra are you lecturing on?" "The Nirvana Sutra." "The Nirvana Sutra has the Four Virtues, hasn't it?" @@ -403,13 +424,19 @@ def koan_macro(ETname): Ummon struck the cup and asked, "You understand?" "No," said the monk. "Then," said Ummon, "You'd better go on with your lectures on the sutra." -""")]) +""" + ), + ] + ) @macro("ideas") def ideas_macro(ETname): - return Expression([Symbol('print'), - String(r""" + return Expression( + [ + Symbol("print"), + String( + r""" => (import [sh [figlet]]) => (figlet "Hi, Hy!") @@ -438,21 +465,24 @@ def ideas_macro(ETname): ;;; swaggin' functional bits (Python rulez) (max (map (fn [x] (len x)) ["hi" "my" "name" "is" "paul"])) -""")]) +""" + ), + ] + ) def set_path(filename): """Emulate Python cmdline behavior by setting `sys.path` relative to the executed file's location.""" path = os.path.realpath(os.path.dirname(filename)) - if sys.path[0] == '': + if sys.path[0] == "": sys.path[0] = path else: sys.path.insert(0, path) def run_command(source, filename=None): - __main__ = importlib.import_module('__main__') + __main__ = importlib.import_module("__main__") require("hy.cmdline", __main__, assignments="ALL") try: tree = hy_parse(source, filename=filename) @@ -467,6 +497,7 @@ def run_command(source, filename=None): def run_repl(hr=None, **kwargs): import platform + sys.ps1 = "=> " sys.ps2 = "... " @@ -474,17 +505,19 @@ def run_repl(hr=None, **kwargs): hr = HyREPL(**kwargs) namespace = hr.locals - with filtered_hy_exceptions(), \ - extend_linecache(hr.cmdline_cache), \ - completion(Completer(namespace)): - hr.interact("Hy {version} using " - "{py}({build}) {pyversion} on {os}".format( - version=hy.__version__, - py=platform.python_implementation(), - build=platform.python_build()[0], - pyversion=platform.python_version(), - os=platform.system() - )) + with filtered_hy_exceptions(), extend_linecache(hr.cmdline_cache), completion( + Completer(namespace) + ): + hr.interact( + "Hy {version} using " + "{py}({build}) {pyversion} on {os}".format( + version=hy.__version__, + py=platform.python_implementation(), + build=platform.python_build()[0], + pyversion=platform.python_version(), + os=platform.system(), + ) + ) return 0 @@ -492,11 +525,11 @@ def run_repl(hr=None, **kwargs): def run_icommand(source, **kwargs): if os.path.exists(source): set_path(source) - with open(source, "r", encoding='utf-8') as f: + with open(source, "r", encoding="utf-8") as f: source = f.read() filename = source else: - filename = '' + filename = "" hr = HyREPL(**kwargs) with filtered_hy_exceptions(): @@ -521,32 +554,64 @@ def run_icommand(source, **kwargs): arguments passed to program in sys.argv[1:] """ -class HyArgError(Exception): pass + +class HyArgError(Exception): + pass + def cmdline_handler(scriptname, argv): # We need to terminate interpretation of options after certain # options, such as `-c`. So, we can't use `argparse`. defs = [ - dict(name=["-h", "--help"], action="help", - help="show this help message and exit"), - dict(name=["-c"], dest="command", terminate=True, - help="program passed in as a string"), - dict(name=["-m"], dest="mod", terminate=True, - help="module to run, passed in as a string"), - dict(name=["-E"], action='store_true', - help="ignore PYTHON* environment variables"), - dict(name=["-B"], action='store_true', - help="don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x"), - dict(name=["-i"], dest="icommand", terminate=True, - help="program passed in as a string, then stay in REPL"), - dict(name=["--spy"], action="store_true", - help="print equivalent Python code before executing"), - dict(name=["--repl-output-fn"], dest="repl_output_fn", + dict( + name=["-h", "--help"], action="help", help="show this help message and exit" + ), + dict( + name=["-c"], + dest="command", + terminate=True, + help="program passed in as a string", + ), + dict( + name=["-m"], + dest="mod", + terminate=True, + help="module to run, passed in as a string", + ), + dict( + name=["-E"], + action="store_true", + help="ignore PYTHON* environment variables", + ), + dict( + name=["-B"], + action="store_true", + help="don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x", + ), + dict( + name=["-i"], + dest="icommand", + terminate=True, + help="program passed in as a string, then stay in REPL", + ), + dict( + name=["--spy"], + action="store_true", + help="print equivalent Python code before executing", + ), + dict( + name=["--repl-output-fn"], + dest="repl_output_fn", help="function for printing REPL output " - "(e.g., hy.contrib.hy-repr.hy-repr)"), - dict(name=["-v", "--version"], action="version", - help="show program's version number and exit")] + "(e.g., hy.contrib.hy-repr.hy-repr)", + ), + dict( + name=["-v", "--version"], + action="version", + help="show program's version number and exit", + ), + ] # Get the path of the Hy cmdline executable and swap it with # `sys.executable` (saving the original, just in case). @@ -562,95 +627,99 @@ def cmdline_handler(scriptname, argv): options = {} def err(fmt, *args): - raise HyArgError('hy: ' + fmt.format(*args)) + raise HyArgError("hy: " + fmt.format(*args)) def proc_opt(opt, arg=None, item=None, i=None): - matches = [o for o in defs if opt in o['name']] + matches = [o for o in defs if opt in o["name"]] if not matches: - err('unrecognized option: {}', opt) + err("unrecognized option: {}", opt) [match] = matches - if 'dest' in match: + if "dest" in match: if arg: pass elif i is not None and i + 1 < len(item): - arg = item[i + 1 + (item[i + 1] == '='):] + arg = item[i + 1 + (item[i + 1] == "=") :] elif argv: arg = argv.pop(0) else: - err('option {}: expected one argument', opt) - options[match['dest']] = arg + err("option {}: expected one argument", opt) + options[match["dest"]] = arg else: - options[match['name'][-1].lstrip('-')] = True - if 'terminate' in match: - return 'terminate' - return 'dest' in match + options[match["name"][-1].lstrip("-")] = True + if "terminate" in match: + return "terminate" + return "dest" in match # Collect options. while argv: item = argv.pop(0) - if item == '--': + if item == "--": break - elif item.startswith('--'): + elif item.startswith("--"): # One double-hyphen option. - opt, _, arg = item.partition('=') - if proc_opt(opt, arg=arg) == 'terminate': + opt, _, arg = item.partition("=") + if proc_opt(opt, arg=arg) == "terminate": break - elif item.startswith('-') and item != '-': + elif item.startswith("-") and item != "-": # One or more single-hyphen options. for i in range(1, len(item)): - x = proc_opt('-' + item[i], item=item, i=i) + x = proc_opt("-" + item[i], item=item, i=i) if x: break - if x == 'terminate': + if x == "terminate": break else: # We're done with options. Add the item back. argv.insert(0, item) break - if 'E' in options: + if "E" in options: _remove_python_envs() - if 'B' in options: + if "B" in options: sys.dont_write_bytecode = True - if 'help' in options: - print('usage:', USAGE) - print('') - print('optional arguments:') + if "help" in options: + print("usage:", USAGE) + print("") + print("optional arguments:") for o in defs: - print(', '.join(o['name']) + - ('=' + o['dest'].upper() if 'dest' in o else '')) - print(' ' + o['help'] + - (' (terminates option list)' - if o.get('terminate') - else '')) + print( + ", ".join(o["name"]) + ("=" + o["dest"].upper() if "dest" in o else "") + ) + print( + " " + + o["help"] + + (" (terminates option list)" if o.get("terminate") else "") + ) print(EPILOG) return 0 - if 'version' in options: + if "version" in options: print(VERSION) return 0 - if 'command' in options: - sys.argv = ['-c'] + argv - return run_command(options['command'], filename='') + if "command" in options: + sys.argv = ["-c"] + argv + return run_command(options["command"], filename="") - if 'mod' in options: - set_path('') + if "mod" in options: + set_path("") sys.argv = [program] + argv - runpy.run_module(options['mod'], run_name='__main__', alter_sys=True) + runpy.run_module(options["mod"], run_name="__main__", alter_sys=True) return 0 - if 'icommand' in options: - return run_icommand(options['icommand'], - spy=options.get('spy'), - output_fn=options.get('repl_output_fn')) + if "icommand" in options: + return run_icommand( + options["icommand"], + spy=options.get("spy"), + output_fn=options.get("repl_output_fn"), + ) if argv: if argv[0] == "-": # Read the program from stdin - return run_command(sys.stdin.read(), filename='') + return run_command(sys.stdin.read(), filename="") else: # User did "hy " @@ -660,19 +729,21 @@ def proc_opt(opt, arg=None, item=None, i=None): try: sys.argv = argv with filtered_hy_exceptions(): - runhy.run_path(filename, run_name='__main__') + runhy.run_path(filename, run_name="__main__") return 0 except FileNotFoundError as e: - print("hy: Can't open file '{}': [Errno {}] {}".format( - e.filename, e.errno, e.strerror), file=sys.stderr) + print( + "hy: Can't open file '{}': [Errno {}] {}".format( + e.filename, e.errno, e.strerror + ), + file=sys.stderr, + ) sys.exit(e.errno) except HyLanguageError: hy_exc_handler(*sys.exc_info()) sys.exit(1) - return run_repl( - spy=options.get('spy'), - output_fn=options.get('repl_output_fn')) + return run_repl(spy=options.get("spy"), output_fn=options.get("repl_output_fn")) # entry point for cmd line script "hy" @@ -687,21 +758,23 @@ def hy_main(): def hyc_main(): parser = argparse.ArgumentParser(prog="hyc") - parser.add_argument("files", metavar="FILE", nargs='*', - help=('File(s) to compile (use STDIN if only' - ' "-" or nothing is provided)')) + parser.add_argument( + "files", + metavar="FILE", + nargs="*", + help=("File(s) to compile (use STDIN if only" ' "-" or nothing is provided)'), + ) parser.add_argument("-v", action="version", version=VERSION) options = parser.parse_args(sys.argv[1:]) rv = 0 - if len(options.files) == 0 or ( - len(options.files) == 1 and options.files[0] == '-'): + if len(options.files) == 0 or (len(options.files) == 1 and options.files[0] == "-"): while True: filename = sys.stdin.readline() if not filename: break - filename = filename.rstrip('\n') + filename = filename.rstrip("\n") set_path(filename) try: py_compile.compile(filename, doraise=True) @@ -728,30 +801,44 @@ def hyc_main(): # entry point for cmd line script "hy2py" def hy2py_main(): - options = dict(prog="hy2py", usage="%(prog)s [options] [FILE]", - formatter_class=argparse.RawDescriptionHelpFormatter) + options = dict( + prog="hy2py", + usage="%(prog)s [options] [FILE]", + formatter_class=argparse.RawDescriptionHelpFormatter, + ) parser = argparse.ArgumentParser(**options) - parser.add_argument("FILE", type=str, nargs='?', - help="Input Hy code (use STDIN if \"-\" or " - "not provided)") - parser.add_argument("--with-source", "-s", action="store_true", - help="Show the parsed source structure") - parser.add_argument("--with-ast", "-a", action="store_true", - help="Show the generated AST") - parser.add_argument("--without-python", "-np", action="store_true", - help=("Do not show the Python code generated " - "from the AST")) + parser.add_argument( + "FILE", + type=str, + nargs="?", + help='Input Hy code (use STDIN if "-" or ' "not provided)", + ) + parser.add_argument( + "--with-source", + "-s", + action="store_true", + help="Show the parsed source structure", + ) + parser.add_argument( + "--with-ast", "-a", action="store_true", help="Show the generated AST" + ) + parser.add_argument( + "--without-python", + "-np", + action="store_true", + help=("Do not show the Python code generated " "from the AST"), + ) options = parser.parse_args(sys.argv[1:]) - if options.FILE is None or options.FILE == '-': + if options.FILE is None or options.FILE == "-": sys.path.insert(0, "") - filename = '' + filename = "" source = sys.stdin.read() else: filename = options.FILE set_path(filename) - with open(options.FILE, 'r', encoding='utf-8') as source_file: + with open(options.FILE, "r", encoding="utf-8") as source_file: source = source_file.read() with filtered_hy_exceptions(): @@ -763,7 +850,7 @@ def hy2py_main(): print() with filtered_hy_exceptions(): - _ast = hy_compile(hst, '__main__', filename=filename, source=source) + _ast = hy_compile(hst, "__main__", filename=filename, source=source) if options.with_ast: print(ast.dump(_ast)) diff --git a/hy/compiler.py b/hy/compiler.py index 634f5d874..1f44f35ce 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1,18 +1,37 @@ -import ast, copy, importlib, inspect, keyword -import traceback, types +import ast +import copy +import importlib +import inspect +import keyword +import traceback +import types from funcparserlib.parser import NoParseError, many -from hy.models import (Object, Expression, Keyword, Integer, Complex, - String, FComponent, FString, Bytes, Symbol, Float, List, Set, - Dict, as_model, is_unpack) -from hy.model_patterns import (FORM, KEYWORD, unpack) -from hy.errors import (HyCompileError, HyLanguageError, HySyntaxError) +from hy.errors import HyCompileError, HyLanguageError, HySyntaxError from hy.lex import mangle from hy.macros import macroexpand +from hy.model_patterns import FORM, KEYWORD, unpack +from hy.models import ( + Bytes, + Complex, + Dict, + Expression, + FComponent, + Float, + FString, + Integer, + Keyword, + List, + Object, + Set, + String, + Symbol, + as_model, + is_unpack, +) from hy.scoping import ScopeGlobal - hy_ast_compile_flags = 0 @@ -47,7 +66,7 @@ def calling_module(n=1): module = inspect.getmodule(frame_up) if module is None: # This works for modules like `__main__` - module_name = frame_up.f_globals.get('__name__', None) + module_name = frame_up.f_globals.get("__name__", None) if module_name: try: module = importlib.import_module(module_name) @@ -58,12 +77,15 @@ def calling_module(n=1): _model_compilers = {} + def builds_model(*model_types): "Assign the decorated method to _model_compilers for the given types." + def _dec(fn): for t in model_types: _model_compilers[t] = fn return fn + return _dec @@ -74,17 +96,16 @@ def _dec(fn): # copies x's position data onto the parse result. class Asty: POS_ATTRS = { - 'lineno': 'start_line', - 'col_offset': 'start_column', - 'end_lineno': 'end_line', - 'end_col_offset': 'end_column', + "lineno": "start_line", + "col_offset": "start_column", + "end_lineno": "end_line", + "end_col_offset": "end_column", } @staticmethod def _get_pos(node): return { - attr: getattr(node, hy_attr, - getattr(node, attr, None)) + attr: getattr(node, hy_attr, getattr(node, attr, None)) for attr, hy_attr in Asty.POS_ATTRS.items() } @@ -102,10 +123,16 @@ def parse(self, x, *args, **kwargs): return res def __getattr__(self, name): - setattr(Asty, name, staticmethod( - lambda x, **kwargs: getattr(ast, name)(**Asty._get_pos(x), **kwargs))) + setattr( + Asty, + name, + staticmethod( + lambda x, **kwargs: getattr(ast, name)(**Asty._get_pos(x), **kwargs) + ), + ) return getattr(Asty, name) + asty = Asty() @@ -133,6 +160,7 @@ class Result: The Result object is interoperable with python AST objects: when an AST object gets added to a Result object, it gets converted on-the-fly. """ + __slots__ = ("stmts", "temp_variables", "_expr", "__used_expr") def __init__(self, *, stmts=(), expr=None, temp_variables=()): @@ -199,7 +227,8 @@ def force_expr(self): return ast.Constant( value=None, lineno=self.stmts[-1].lineno if self.stmts else 0, - col_offset=self.stmts[-1].col_offset if self.stmts else 0) + col_offset=self.stmts[-1].col_offset if self.stmts else 0, + ) def expr_as_stmt(self): """Convert the Result's expression context to a statement @@ -230,8 +259,9 @@ def rename(self, compiler, new_name): elif isinstance(var, (ast.FunctionDef, ast.AsyncFunctionDef)): var.name = new_name else: - raise TypeError("Don't know how to rename a %s!" % ( - var.__class__.__name__)) + raise TypeError( + "Don't know how to rename a %s!" % (var.__class__.__name__) + ) self.temp_variables = [] def __add__(self, other): @@ -252,9 +282,11 @@ def __add__(self, other): # Check for expression context clobbering if self.expr and not self.__used_expr: traceback.print_stack() - print("Bad boy clobbered expr {} with {}".format( - ast.dump(self.expr), - ast.dump(other.expr))) + print( + "Bad boy clobbered expr {} with {}".format( + ast.dump(self.expr), ast.dump(other.expr) + ) + ) # Fairly obvious addition result = Result() @@ -265,29 +297,29 @@ def __add__(self, other): return result def __str__(self): - return ( - "Result(stmts=[%s], expr=%s)" - % ( + return "Result(stmts=[%s], expr=%s)" % ( ", ".join(ast.dump(x) for x in self.stmts), - ast.dump(self.expr) if self.expr else None - )) + ast.dump(self.expr) if self.expr else None, + ) def make_hy_model(outer, x, rest): - return outer( - [Symbol(a) if type(a) is str else - a[0] if type(a) is list else a - for a in x] + - (rest or [])) + return outer( + [Symbol(a) if type(a) is str else a[0] if type(a) is list else a for a in x] + + (rest or []) + ) + + def mkexpr(*items, **kwargs): - return make_hy_model(Expression, items, kwargs.get('rest')) + return make_hy_model(Expression, items, kwargs.get("rest")) + + def mklist(*items, **kwargs): - return make_hy_model(List, items, kwargs.get('rest')) + return make_hy_model(List, items, kwargs.get("rest")) def is_annotate_expression(model): - return (isinstance(model, Expression) and model - and model[0] == Symbol("annotate")) + return isinstance(model, Expression) and model and model[0] == Symbol("annotate") class HyASTCompiler: @@ -317,12 +349,12 @@ def __init__(self, module, filename=None, source=None): self.source = source self.this = None - # Set in `macroexpand` to the current expression being - # macro-expanded, so it can be accessed as `&compiler.this`. + # Set in `macroexpand` to the current expression being + # macro-expanded, so it can be accessed as `&compiler.this`. # Hy expects this to be present, so we prep the module for Hy # compilation. - self.module.__dict__.setdefault('__macros__', {}) + self.module.__dict__.setdefault("__macros__", {}) self.scope = ScopeGlobal(self) @@ -379,8 +411,7 @@ def _compile_collect(self, exprs, with_kwargs=False, dict_display=False): compiled_exprs.append(None) compiled_exprs.append(ret.force_expr) elif with_kwargs: - keywords.append(asty.keyword( - expr, arg=None, value=ret.force_expr)) + keywords.append(asty.keyword(expr, arg=None, value=ret.force_expr)) elif with_kwargs and isinstance(expr, Keyword): if keyword.iskeyword(expr.name): @@ -391,19 +422,22 @@ def _compile_collect(self, exprs, with_kwargs=False, dict_display=False): try: value = next(exprs_iter) except StopIteration: - raise self._syntax_error(expr, - "Keyword argument {kw} needs a value.".format(kw=expr)) + raise self._syntax_error( + expr, "Keyword argument {kw} needs a value.".format(kw=expr) + ) if not expr: - raise self._syntax_error(expr, - "Can't call a function with the empty keyword") + raise self._syntax_error( + expr, "Can't call a function with the empty keyword" + ) compiled_value = self.compile(value) ret += compiled_value arg = str(expr)[1:] - keywords.append(asty.keyword( - expr, arg=mangle(arg), value=compiled_value.force_expr)) + keywords.append( + asty.keyword(expr, arg=mangle(arg), value=compiled_value.force_expr) + ) else: ret += self.compile(expr) @@ -434,8 +468,9 @@ def _storeize(self, expr, name, func=None): if isinstance(name, Result): if not name.is_expr(): - raise self._syntax_error(expr, - "Can't assign or delete a non-expression") + raise self._syntax_error( + expr, "Can't assign or delete a non-expression" + ) name = name.expr if isinstance(name, (ast.Tuple, ast.List)): @@ -453,13 +488,17 @@ def _storeize(self, expr, name, func=None): elif isinstance(name, ast.Attribute): new_name = ast.Attribute(value=name.value, attr=name.attr) elif isinstance(name, ast.Starred): - new_name = ast.Starred( - value=self._storeize(expr, name.value, func)) + new_name = ast.Starred(value=self._storeize(expr, name.value, func)) else: - raise self._syntax_error(expr, "Can't assign or delete a " + ( - "constant" - if isinstance(name, ast.Constant) - else type(expr).__name__)) + raise self._syntax_error( + expr, + "Can't assign or delete a " + + ( + "constant" + if isinstance(name, ast.Constant) + else type(expr).__name__ + ), + ) new_name.ctx = func() ast.copy_location(new_name, name) @@ -482,8 +521,9 @@ def compile_expression(self, expr, *, allow_annotation_expression=False): return self.compile(expr) if not expr: - raise self._syntax_error(expr, - "empty expressions are not allowed at top level") + raise self._syntax_error( + expr, "empty expressions are not allowed at top level" + ) args = list(expr) root = args.pop(0) @@ -496,11 +536,10 @@ def compile_expression(self, expr, *, allow_annotation_expression=False): # Get the method name (the last named attribute # in the chain of attributes) attrs = [ - Symbol(a).replace(root) if a else None - for a in root.split(".")[1:]] + Symbol(a).replace(root) if a else None for a in root.split(".")[1:] + ] if not all(attrs): - raise self._syntax_error(expr, - "cannot access empty attribute") + raise self._syntax_error(expr, "cannot access empty attribute") root = attrs.pop() # Get the object we're calling the method on @@ -508,26 +547,22 @@ def compile_expression(self, expr, *, allow_annotation_expression=False): # Skip past keywords and their arguments. try: kws, obj, rest = ( - many(KEYWORD + FORM | unpack("mapping")) + - FORM + - many(FORM)).parse(args) + many(KEYWORD + FORM | unpack("mapping")) + FORM + many(FORM) + ).parse(args) except NoParseError: - raise self._syntax_error(expr, - "attribute access requires object") + raise self._syntax_error(expr, "attribute access requires object") # Reconstruct `args` to exclude `obj`. args = [x for p in kws for x in p] + list(rest) if is_unpack("iterable", obj): - raise self._syntax_error(obj, - "can't call a method on an unpacking form") - func = self.compile(Expression( - [Symbol(".").replace(root), obj] + - attrs)) + raise self._syntax_error( + obj, "can't call a method on an unpacking form" + ) + func = self.compile(Expression([Symbol(".").replace(root), obj] + attrs)) # And get the method - func += asty.Attribute(root, - value=func.force_expr, - attr=mangle(root), - ctx=ast.Load()) + func += asty.Attribute( + root, value=func.force_expr, attr=mangle(root), ctx=ast.Load() + ) if is_annotate_expression(root): # Flatten and compile the annotation expression. @@ -539,14 +574,13 @@ def compile_expression(self, expr, *, allow_annotation_expression=False): args, ret, keywords = self._compile_collect(args, with_kwargs=True) - return func + ret + asty.Call( - expr, func=func.expr, args=args, keywords=keywords) + return ( + func + ret + asty.Call(expr, func=func.expr, args=args, keywords=keywords) + ) @builds_model(Integer, Float, Complex) def compile_numeric_literal(self, x): - f = {Integer: int, - Float: float, - Complex: complex}[type(x)] + f = {Integer: int, Float: float, Complex: complex}[type(x)] return asty.Num(x, n=f(x)) @builds_model(Symbol) @@ -555,45 +589,45 @@ def compile_symbol(self, symbol): glob, local = symbol.rsplit(".", 1) if not glob: - raise self._syntax_error(symbol, - 'cannot access attribute on anything other than a name (in order to get attributes of expressions, use `(. {attr})` or `(.{attr} )`)'.format(attr=local)) + raise self._syntax_error( + symbol, + "cannot access attribute on anything other than a name (in order to get attributes of expressions, use `(. {attr})` or `(.{attr} )`)".format( + attr=local + ), + ) if not local: - raise self._syntax_error(symbol, - 'cannot access empty attribute') + raise self._syntax_error(symbol, "cannot access empty attribute") glob = Symbol(glob).replace(symbol) ret = self.compile_symbol(glob) - return asty.Attribute( - symbol, - value=ret, - attr=mangle(local), - ctx=ast.Load()) + return asty.Attribute(symbol, value=ret, attr=mangle(local), ctx=ast.Load()) if mangle(symbol) in ("None", "False", "True"): - return asty.Constant(symbol, value = - ast.literal_eval(mangle(symbol))) + return asty.Constant(symbol, value=ast.literal_eval(mangle(symbol))) - return self.scope.access(asty.Name( - symbol, id=mangle(symbol), ctx=ast.Load())) + return self.scope.access(asty.Name(symbol, id=mangle(symbol), ctx=ast.Load())) @builds_model(Keyword) def compile_keyword(self, obj): ret = Result() ret += asty.Call( obj, - func=asty.Attribute(obj, - value=asty.Attribute( - obj, - value=asty.Name(obj, id="hy", ctx=ast.Load()), - attr="models", - ctx=ast.Load() - ), - attr="Keyword", - ctx=ast.Load()), + func=asty.Attribute( + obj, + value=asty.Attribute( + obj, + value=asty.Name(obj, id="hy", ctx=ast.Load()), + attr="models", + ctx=ast.Load(), + ), + attr="Keyword", + ctx=ast.Load(), + ), args=[asty.Str(obj, s=obj.name)], - keywords=[]) + keywords=[], + ) return ret @builds_model(String, Bytes) @@ -612,8 +646,13 @@ def compile_fcomponent(self, fcomponent): spec = asty.JoinedStr(fcomponent, values=elts) else: spec = None - return value + ret + asty.FormattedValue( - fcomponent, value=value.expr, conversion=conversion, format_spec=spec) + return ( + value + + ret + + asty.FormattedValue( + fcomponent, value=value.expr, conversion=conversion, format_spec=spec + ) + ) @builds_model(FString) def compile_fstring(self, fstring): @@ -637,10 +676,10 @@ def get_compiler_module(module=None, compiler=None, calling_frame=False): string name of a module, and (optionally) the calling frame; otherwise, raise an error.""" - module = getattr(compiler, 'module', None) or module + module = getattr(compiler, "module", None) or module if isinstance(module, str): - if module.startswith('<') and module.endswith('>'): + if module.startswith("<") and module.endswith(">"): module = types.ModuleType(module) else: module = importlib.import_module(mangle(module)) @@ -649,7 +688,7 @@ def get_compiler_module(module=None, compiler=None, calling_frame=False): module = calling_module(n=2) if not inspect.ismodule(module): - raise TypeError('Invalid module type: {}'.format(type(module))) + raise TypeError("Invalid module type: {}".format(type(module))) return module @@ -733,23 +772,27 @@ def hy_eval( raise TypeError("Locals must be a dictionary") # Does the Hy AST object come with its own information? - filename = getattr(hytree, 'filename', filename) or '' - source = getattr(hytree, 'source', source) - - _ast, expr = hy_compile(hytree, module, get_expr=True, - compiler=compiler, filename=filename, - source=source, import_stdlib=import_stdlib) + filename = getattr(hytree, "filename", filename) or "" + source = getattr(hytree, "source", source) + + _ast, expr = hy_compile( + hytree, + module, + get_expr=True, + compiler=compiler, + filename=filename, + source=source, + import_stdlib=import_stdlib, + ) if ast_callback: ast_callback(_ast, expr) # Two-step eval: eval() the body of the exec call - eval(ast_compile(_ast, filename, "exec"), - module.__dict__, locals) + eval(ast_compile(_ast, filename, "exec"), module.__dict__, locals) # Then eval the expression context and return that - return eval(ast_compile(expr, filename, "eval"), - module.__dict__, locals) + return eval(ast_compile(expr, filename, "eval"), module.__dict__, locals) def hy_compile( @@ -788,27 +831,30 @@ def hy_compile( module = get_compiler_module(module, compiler, False) if isinstance(module, str): - if module.startswith('<') and module.endswith('>'): + if module.startswith("<") and module.endswith(">"): module = types.ModuleType(module) else: module = importlib.import_module(mangle(module)) if not inspect.ismodule(module): - raise TypeError('Invalid module type: {}'.format(type(module))) + raise TypeError("Invalid module type: {}".format(type(module))) - filename = getattr(tree, 'filename', filename) - source = getattr(tree, 'source', source) + filename = getattr(tree, "filename", filename) + source = getattr(tree, "source", source) tree = as_model(tree) if not isinstance(tree, Object): - raise TypeError("`tree` must be a hy.models.Object or capable of " - "being promoted to one") + raise TypeError( + "`tree` must be a hy.models.Object or capable of " "being promoted to one" + ) compiler = compiler or HyASTCompiler(module, filename=filename, source=source) if import_stdlib: # Import hy for compile time, but save the compiled AST. - stdlib_ast = compiler.compile(mkexpr("eval-and-compile", mkexpr("import", "hy"))) + stdlib_ast = compiler.compile( + mkexpr("eval-and-compile", mkexpr("import", "hy")) + ) with compiler.scope: result = compiler.compile(tree) @@ -821,16 +867,20 @@ def hy_compile( if issubclass(root, ast.Module): # Pull out a single docstring and prepend to the resulting body. - if (result.stmts and - isinstance(result.stmts[0], ast.Expr) and - isinstance(result.stmts[0].value, ast.Str)): + if ( + result.stmts + and isinstance(result.stmts[0], ast.Expr) + and isinstance(result.stmts[0].value, ast.Str) + ): body += [result.stmts.pop(0)] # Pull out any __future__ imports, since they are required to be at the beginning. - while (result.stmts and - isinstance(result.stmts[0], ast.ImportFrom) and - result.stmts[0].module == '__future__'): + while ( + result.stmts + and isinstance(result.stmts[0], ast.ImportFrom) + and result.stmts[0].module == "__future__" + ): body += [result.stmts.pop(0)] diff --git a/hy/completer.py b/hy/completer.py index c2673cb1e..bc9fc1264 100644 --- a/hy/completer.py +++ b/hy/completer.py @@ -1,16 +1,17 @@ +import builtins import contextlib import os import re import sys -import builtins -import hy.macros import hy.compiler - +import hy.macros # Lazily import `readline` to work around # https://bugs.python.org/issue2675#msg265564 readline = None + + def init_readline(): global readline try: @@ -24,17 +25,15 @@ def init_readline(): class Completer: - def __init__(self, namespace={}): if not isinstance(namespace, dict): - raise TypeError('namespace must be a dictionary') + raise TypeError("namespace must be a dictionary") self.namespace = namespace - self.path = [builtins.__dict__, - namespace] + self.path = [builtins.__dict__, namespace] - namespace.setdefault('__macros__', {}) + namespace.setdefault("__macros__", {}) - self.path.append(namespace['__macros__']) + self.path.append(namespace["__macros__"]) def attr_matches(self, text): # Borrowed from IPython's completer @@ -57,8 +56,9 @@ def attr_matches(self, text): matches = [] for w in words: if w[:n] == attr: - matches.append("{}.{}".format( - expr.replace("_", "-"), w.replace("_", "-"))) + matches.append( + "{}.{}".format(expr.replace("_", "-"), w.replace("_", "-")) + ) return matches def global_matches(self, text): @@ -81,6 +81,7 @@ def complete(self, text, state): except IndexError: return None + @contextlib.contextmanager def completion(completer=None): delims = "()[]{} " @@ -94,7 +95,7 @@ def completion(completer=None): if not completer: completer = Completer() - if sys.platform == 'darwin' and 'libedit' in readline.__doc__: + if sys.platform == "darwin" and "libedit" in readline.__doc__: readline_bind = "bind ^I rl_complete" else: readline_bind = "tab: complete" @@ -102,8 +103,7 @@ def completion(completer=None): readline.set_completer(completer.complete) readline.set_completer_delims(delims) - history = os.environ.get( - "HY_HISTORY", os.path.expanduser("~/.hy-history")) + history = os.environ.get("HY_HISTORY", os.path.expanduser("~/.hy-history")) readline.parse_and_bind("set blink-matching-paren on") try: diff --git a/hy/core/result_macros.py b/hy/core/result_macros.py index 095c611e6..3697ff70f 100644 --- a/hy/core/result_macros.py +++ b/hy/core/result_macros.py @@ -8,34 +8,71 @@ # * Imports # ------------------------------------------------ -import ast, keyword, textwrap -from itertools import dropwhile +import ast +import keyword +import textwrap from contextlib import nullcontext +from itertools import dropwhile -from funcparserlib.parser import (some, many, oneplus, maybe, - forward_decl) +from funcparserlib.parser import forward_decl, many, maybe, oneplus, some -from hy.models import (Expression, Keyword, Integer, Complex, String, - FComponent, FString, Bytes, Symbol, Float, List, Dict, Sequence, - is_unpack) -from hy.model_patterns import (FORM, SYM, KEYWORD, STR, LITERAL, sym, - brackets, notpexpr, dolike, pexpr, times, Tag, tag, unpack, braces, - keepsym) +from hy.compiler import Result, asty, hy_eval, mkexpr +from hy.errors import HyEvalError, HyInternalError, HyTypeError from hy.lex import mangle, unmangle from hy.macros import pattern_macro, require -from hy.errors import (HyTypeError, HyEvalError, HyInternalError) -from hy.compiler import Result, asty, mkexpr, hy_eval -from hy.scoping import ScopeFn, ScopeGen, ScopeGlobal, ScopeLet, is_inside_function_scope +from hy.model_patterns import ( + FORM, + KEYWORD, + LITERAL, + STR, + SYM, + Tag, + braces, + brackets, + dolike, + keepsym, + notpexpr, + pexpr, + sym, + tag, + times, + unpack, +) +from hy.models import ( + Bytes, + Complex, + Dict, + Expression, + FComponent, + Float, + FString, + Integer, + Keyword, + List, + Sequence, + String, + Symbol, + is_unpack, +) +from hy.scoping import ( + ScopeFn, + ScopeGen, + ScopeGlobal, + ScopeLet, + is_inside_function_scope, +) # ------------------------------------------------ # * Helpers # ------------------------------------------------ -Inf = float('inf') +Inf = float("inf") + def pvalue(root, wanted): return pexpr(sym(root) + wanted) >> (lambda x: x[0]) + # Parse an annotation setting. OPTIONAL_ANNOTATION = maybe(pvalue("annotate", FORM)) @@ -43,21 +80,25 @@ def pvalue(root, wanted): # * Fundamentals # ------------------------------------------------ + @pattern_macro("do", [many(FORM)]) def compile_do(self, expr, root, body): return self._compile_branch(body) + @pattern_macro(["eval-and-compile", "eval-when-compile"], [many(FORM)]) def compile_eval_and_compile(compiler, expr, root, body): new_expr = Expression([Symbol("do").replace(expr[0])]).replace(expr) try: - hy_eval(new_expr + body, - compiler.module.__dict__, - compiler.module, - filename=compiler.filename, - source=compiler.source, - import_stdlib=False) + hy_eval( + new_expr + body, + compiler.module.__dict__, + compiler.module, + filename=compiler.filename, + source=compiler.source, + import_stdlib=False, + ) except HyInternalError: # Unexpected "meta" compilation errors need to be treated # like normal (unexpected) compilation errors at this level @@ -71,9 +112,12 @@ def compile_eval_and_compile(compiler, expr, root, body): # We wrap these exceptions and pass them through. raise HyEvalError(str(e), compiler.filename, body, compiler.source) - return (compiler._compile_branch(body) - if mangle(root) == "eval_and_compile" - else Result()) + return ( + compiler._compile_branch(body) + if mangle(root) == "eval_and_compile" + else Result() + ) + @pattern_macro(["py", "pys"], [STR]) def compile_inline_python(compiler, expr, root, code): @@ -84,25 +128,29 @@ def compile_inline_python(compiler, expr, root, code): expr, textwrap.dedent(code) if exec_mode else code, compiler.filename, - 'exec' if exec_mode else 'eval').body + "exec" if exec_mode else "eval", + ).body except (SyntaxError, ValueError) as e: raise compiler._syntax_error( - expr, - "Python parse error in '{}': {}".format(root, e)) + expr, "Python parse error in '{}': {}".format(root, e) + ) return Result(stmts=o) if exec_mode else o + # ------------------------------------------------ # * Quoting # ------------------------------------------------ + @pattern_macro(["quote", "quasiquote"], [FORM]) def compile_quote(compiler, expr, root, arg): - level = Inf if root == "quote" else 0 # Only quasiquotes can unquote + level = Inf if root == "quote" else 0 # Only quasiquotes can unquote stmts, _ = render_quoted_form(compiler, arg, level) ret = compiler.compile(stmts) return ret + def render_quoted_form(compiler, form, level): """ Render a quoted form as a new hy Expression. @@ -122,8 +170,13 @@ def render_quoted_form(compiler, form, level): op = unmangle(mangle(form[0])) if level == 0 and op in ("unquote", "unquote-splice"): if len(form) != 2: - raise HyTypeError("`%s' needs 1 argument, got %s" % op, len(form) - 1, - compiler.filename, form, compiler.source) + raise HyTypeError( + "`%s' needs 1 argument, got %s" % op, + len(form) - 1, + compiler.filename, + form, + compiler.source, + ) return form[1], op == "unquote-splice" elif op == "quasiquote": level += 1 @@ -139,8 +192,12 @@ def render_quoted_form(compiler, form, level): for x in form: f_contents, splice = render_quoted_form(compiler, x, level) if splice: - f_contents = Expression([Symbol("unpack-iterable"), - Expression([Symbol("or"), f_contents, List()])]) + f_contents = Expression( + [ + Symbol("unpack-iterable"), + Expression([Symbol("or"), f_contents, List()]), + ] + ) contents.append(f_contents) body = [List(contents)] @@ -162,22 +219,22 @@ def render_quoted_form(compiler, form, level): ret = Expression([Symbol(name), *body]).replace(form) return ret, False + # ------------------------------------------------ # * Python operators # ------------------------------------------------ -@pattern_macro(["not", "~"], [FORM], shadow = True) + +@pattern_macro(["not", "~"], [FORM], shadow=True) def compile_unary_operator(compiler, expr, root, arg): - ops = {"not": ast.Not, - "~": ast.Invert} + ops = {"not": ast.Not, "~": ast.Invert} operand = compiler.compile(arg) - return operand + asty.UnaryOp( - expr, op=ops[root](), operand=operand.force_expr) + return operand + asty.UnaryOp(expr, op=ops[root](), operand=operand.force_expr) + -@pattern_macro(["and", "or"], [many(FORM)], shadow = True) +@pattern_macro(["and", "or"], [many(FORM)], shadow=True) def compile_logical_or_and_and_operator(compiler, expr, operator, args): - ops = {"and": (ast.And, True), - "or": (ast.Or, None)} + ops = {"and": (ast.And, True), "or": (ast.Or, None)} opnode, default = ops[operator] osym = expr[0] if len(args) == 0: @@ -194,11 +251,9 @@ def compile_logical_or_and_and_operator(compiler, expr, operator, args): temp_variables = [name, expr_name] def make_assign(value, node=None): - positioned_name = asty.Name( - node or osym, id=var, ctx=ast.Store()) + positioned_name = asty.Name(node or osym, id=var, ctx=ast.Store()) temp_variables.append(positioned_name) - return asty.Assign( - node or osym, targets=[positioned_name], value=value) + return asty.Assign(node or osym, targets=[positioned_name], value=value) current = root = [] for i, value in enumerate(values): @@ -208,7 +263,7 @@ def make_assign(value, node=None): else: node = value.expr current.append(make_assign(value.force_expr, value.force_expr)) - if i == len(values)-1: + if i == len(values) - 1: # Skip a redundant 'if'. break if operator == "and": @@ -220,38 +275,44 @@ def make_assign(value, node=None): ret = sum(root, ret) ret += Result(expr=expr_name, temp_variables=temp_variables) else: - ret += asty.BoolOp(osym, - op=opnode(), - values=[value.force_expr for value in values]) + ret += asty.BoolOp( + osym, op=opnode(), values=[value.force_expr for value in values] + ) return ret -c_ops = {"=": ast.Eq, "!=": ast.NotEq, - "<": ast.Lt, "<=": ast.LtE, - ">": ast.Gt, ">=": ast.GtE, - "is": ast.Is, "is-not": ast.IsNot, - "in": ast.In, "not-in": ast.NotIn} + +c_ops = { + "=": ast.Eq, + "!=": ast.NotEq, + "<": ast.Lt, + "<=": ast.LtE, + ">": ast.Gt, + ">=": ast.GtE, + "is": ast.Is, + "is-not": ast.IsNot, + "in": ast.In, + "not-in": ast.NotIn, +} c_ops = {mangle(k): v for k, v in c_ops.items()} + def get_c_op(compiler, sym): k = mangle(sym) if k not in c_ops: - raise compiler._syntax_error(sym, - "Illegal comparison operator: " + str(sym)) + raise compiler._syntax_error(sym, "Illegal comparison operator: " + str(sym)) return c_ops[k]() -@pattern_macro(["=", "is", "<", "<=", ">", ">="], [oneplus(FORM)], - shadow = True) -@pattern_macro(["!=", "is-not", "in", "not-in"], [times(2, Inf, FORM)], - shadow = True) + +@pattern_macro(["=", "is", "<", "<=", ">", ">="], [oneplus(FORM)], shadow=True) +@pattern_macro(["!=", "is-not", "in", "not-in"], [times(2, Inf, FORM)], shadow=True) def compile_compare_op_expression(compiler, expr, root, args): if len(args) == 1: - return (compiler.compile(args[0]) + - asty.Constant(expr, value=True)) + return compiler.compile(args[0]) + asty.Constant(expr, value=True) ops = [get_c_op(compiler, root) for _ in args[1:]] exprs, ret, _ = compiler._compile_collect(args) - return ret + asty.Compare( - expr, left=exprs[0], ops=ops, comparators=exprs[1:]) + return ret + asty.Compare(expr, left=exprs[0], ops=ops, comparators=exprs[1:]) + @pattern_macro("chainc", [FORM, many(SYM + FORM)]) def compile_chained_comparison(compiler, expr, root, arg1, args): @@ -259,37 +320,38 @@ def compile_chained_comparison(compiler, expr, root, arg1, args): arg1 = ret.force_expr ops = [get_c_op(compiler, op) for op, _ in args] - args, ret2, _ = compiler._compile_collect( - [x for _, x in args]) + args, ret2, _ = compiler._compile_collect([x for _, x in args]) + + return ret + ret2 + asty.Compare(expr, left=arg1, ops=ops, comparators=args) - return ret + ret2 + asty.Compare(expr, - left=arg1, ops=ops, comparators=args) # The second element of each tuple below is an aggregation operator # that's used for augmented assignment with three or more arguments. -m_ops = {"+": (ast.Add, "+"), - "/": (ast.Div, "*"), - "//": (ast.FloorDiv, "*"), - "*": (ast.Mult, "*"), - "-": (ast.Sub, "+"), - "%": (ast.Mod, None), - "**": (ast.Pow, "**"), - "<<": (ast.LShift, "+"), - ">>": (ast.RShift, "+"), - "|": (ast.BitOr, "|"), - "^": (ast.BitXor, None), - "&": (ast.BitAnd, "&"), - "@": (ast.MatMult, "@")} - -@pattern_macro(["+", "*", "|"], [many(FORM)], shadow = True) -@pattern_macro(["-", "/", "&", "@"], [oneplus(FORM)], shadow = True) -@pattern_macro(["**", "//", "<<", ">>"], [times(2, Inf, FORM)], shadow = True) -@pattern_macro(["%", "^"], [times(2, 2, FORM)], shadow = True) +m_ops = { + "+": (ast.Add, "+"), + "/": (ast.Div, "*"), + "//": (ast.FloorDiv, "*"), + "*": (ast.Mult, "*"), + "-": (ast.Sub, "+"), + "%": (ast.Mod, None), + "**": (ast.Pow, "**"), + "<<": (ast.LShift, "+"), + ">>": (ast.RShift, "+"), + "|": (ast.BitOr, "|"), + "^": (ast.BitXor, None), + "&": (ast.BitAnd, "&"), + "@": (ast.MatMult, "@"), +} + + +@pattern_macro(["+", "*", "|"], [many(FORM)], shadow=True) +@pattern_macro(["-", "/", "&", "@"], [oneplus(FORM)], shadow=True) +@pattern_macro(["**", "//", "<<", ">>"], [times(2, Inf, FORM)], shadow=True) +@pattern_macro(["%", "^"], [times(2, 2, FORM)], shadow=True) def compile_maths_expression(compiler, expr, root, args): if len(args) == 0: # Return the identity element for this operator. - return asty.Num(expr, n=( - {"+": 0, "|": 0, "*": 1}[root])) + return asty.Num(expr, n=({"+": 0, "|": 0, "*": 1}[root])) if len(args) == 1: if root == "/": @@ -307,8 +369,7 @@ def compile_maths_expression(compiler, expr, root, args): op = m_ops[root][0] right_associative = root == "**" ret = compiler.compile(args[-1 if right_associative else 0]) - for child in args[-2 if right_associative else 1 :: - -1 if right_associative else 1]: + for child in args[-2 if right_associative else 1 :: -1 if right_associative else 1]: left_expr = ret.force_expr ret += compiler.compile(child) right_expr = ret.force_expr @@ -318,25 +379,33 @@ def compile_maths_expression(compiler, expr, root, args): return ret + a_ops = {x + "=": v for x, v in m_ops.items()} -@pattern_macro([x for x, (_, v) in a_ops.items() if v is not None], [FORM, oneplus(FORM)]) -@pattern_macro([x for x, (_, v) in a_ops.items() if v is None], [FORM, times(1, 1, FORM)]) + +@pattern_macro( + [x for x, (_, v) in a_ops.items() if v is not None], [FORM, oneplus(FORM)] +) +@pattern_macro( + [x for x, (_, v) in a_ops.items() if v is None], [FORM, times(1, 1, FORM)] +) def compile_augassign_expression(compiler, expr, root, target, values): if len(values) > 1: - return compiler.compile(mkexpr(root, [target], - mkexpr(a_ops[root][1], rest=values)).replace(expr)) + return compiler.compile( + mkexpr(root, [target], mkexpr(a_ops[root][1], rest=values)).replace(expr) + ) op = a_ops[root][0] target = compiler._storeize(target, compiler.compile(target)) ret = compiler.compile(values[0]) - return ret + asty.AugAssign( - expr, target=target, value=ret.force_expr, op=op()) + return ret + asty.AugAssign(expr, target=target, value=ret.force_expr, op=op()) + # ------------------------------------------------ # * Assignment, mutation, and annotation # ------------------------------------------------ + @pattern_macro("setv", [many(OPTIONAL_ANNOTATION + FORM + FORM)]) @pattern_macro(((3, 8), "setx"), [times(1, 1, SYM + FORM)]) def compile_def_expression(compiler, expr, root, decls): @@ -352,15 +421,20 @@ def compile_def_expression(compiler, expr, root, decls): else: ann, name, value = decl - result += compile_assign(compiler, ann, name, value, - is_assignment_expr=is_assignment_expr) + result += compile_assign( + compiler, ann, name, value, is_assignment_expr=is_assignment_expr + ) return result + @pattern_macro(["annotate"], [FORM, FORM]) def compile_basic_annotation(compiler, expr, root, ann, target): return compile_assign(compiler, ann, target, None) -def compile_assign(compiler, ann, name, value, *, is_assignment_expr = False, let_scope = None): + +def compile_assign( + compiler, ann, name, value, *, is_assignment_expr=False, let_scope=None +): # Ensure that assignment expressions have a result and no annotation. assert not is_assignment_expr or (value is not None and ann is None) @@ -375,9 +449,7 @@ def compile_assign(compiler, ann, name, value, *, is_assignment_expr = False, le ld_name = compiler.compile(name) - if (result.temp_variables - and isinstance(name, Symbol) - and '.' not in name): + if result.temp_variables and isinstance(name, Symbol) and "." not in name: result.rename(compiler, compiler._nonconst(name)) if not is_assignment_expr: # Throw away .expr to ensure that (setv ...) returns None. @@ -392,19 +464,25 @@ def compile_assign(compiler, ann, name, value, *, is_assignment_expr = False, le if is_assignment_expr: node = asty.NamedExpr elif ann is not None: - node = lambda x, **kw: asty.AnnAssign(x, annotation=ann_result.force_expr, - simple=int(isinstance(name, Symbol)), - **kw) + node = lambda x, **kw: asty.AnnAssign( + x, + annotation=ann_result.force_expr, + simple=int(isinstance(name, Symbol)), + **kw, + ) else: node = asty.Assign result += node( name if hasattr(name, "start_line") else result, value=result.force_expr if not annotate_only else None, - target=st_name, targets=[st_name]) + target=st_name, + targets=[st_name], + ) return result + @pattern_macro(["global", "nonlocal"], [oneplus(SYM)]) def compile_global_or_nonlocal(compiler, expr, root, syms): node = asty.Global if root == "global" else asty.Nonlocal @@ -417,6 +495,7 @@ def compile_global_or_nonlocal(compiler, expr, root, syms): return ret if ret.names else Result() + @pattern_macro("del", [many(FORM)]) def compile_del_expression(compiler, expr, name, args): if not args: @@ -427,31 +506,29 @@ def compile_del_expression(compiler, expr, name, args): for target in args: compiled_target = compiler.compile(target) ret += compiled_target - del_targets.append(compiler._storeize(target, compiled_target, - ast.Del)) + del_targets.append(compiler._storeize(target, compiled_target, ast.Del)) return ret + asty.Delete(expr, targets=del_targets) + # ------------------------------------------------ # * Subsetting # ------------------------------------------------ -@pattern_macro("get", [FORM, oneplus(FORM)], shadow = True) + +@pattern_macro("get", [FORM, oneplus(FORM)], shadow=True) def compile_index_expression(compiler, expr, name, obj, indices): indices, ret, _ = compiler._compile_collect(indices) ret += compiler.compile(obj) for ix in indices: ret += asty.Subscript( - expr, - value=ret.force_expr, - slice=ast.Index(value=ix), - ctx=ast.Load()) + expr, value=ret.force_expr, slice=ast.Index(value=ix), ctx=ast.Load() + ) return ret - notsym = lambda *dissallowed: some( lambda x: isinstance(x, Symbol) and str(x) not in dissallowed ) @@ -503,9 +580,11 @@ def compile_attribute_access(compiler, expr, name, invocant, keys): return ret + @pattern_macro("cut", [FORM, maybe(FORM), maybe(FORM), maybe(FORM)]) def compile_cut_expression(compiler, expr, name, obj, lower, upper, step): ret = [Result()] + def c(e): ret[0] += compiler.compile(e) return ret[0].force_expr @@ -514,26 +593,29 @@ def c(e): # cut with single index is an upper bound, # this is consistent with slice and islice upper = lower - lower = Symbol('None') + lower = Symbol("None") s = asty.Subscript( expr, value=c(obj), - slice=asty.Slice(expr, - lower=c(lower), upper=c(upper), step=c(step)), - ctx=ast.Load()) + slice=asty.Slice(expr, lower=c(lower), upper=c(upper), step=c(step)), + ctx=ast.Load(), + ) return ret[0] + s + @pattern_macro("unpack-iterable", [FORM]) def compile_unpack_iterable(compiler, expr, root, arg): ret = compiler.compile(arg) ret += asty.Starred(expr, value=ret.force_expr, ctx=ast.Load()) return ret + # ------------------------------------------------ # * `if` # ------------------------------------------------ + @pattern_macro("if", [FORM, FORM, maybe(FORM)]) def compile_if(compiler, expr, _, cond, body, orel_expr): cond = compiler.compile(cond) @@ -542,8 +624,11 @@ def compile_if(compiler, expr, _, cond, body, orel_expr): nested = root = False orel = Result() if orel_expr is not None: - if isinstance(orel_expr, Expression) and isinstance(orel_expr[0], - Symbol) and orel_expr[0] == Symbol('if*'): + if ( + isinstance(orel_expr, Expression) + and isinstance(orel_expr[0], Symbol) + and orel_expr[0] == Symbol("if*") + ): # Nested ifs: don't waste temporaries root = compiler.temp_if is None nested = True @@ -553,19 +638,15 @@ def compile_if(compiler, expr, _, cond, body, orel_expr): if not cond.stmts and isinstance(cond.force_expr, ast.Name): name = cond.force_expr.id branch = None - if name == 'True': + if name == "True": branch = body - elif name in ('False', 'None'): + elif name in ("False", "None"): branch = orel if branch is not None: if compiler.temp_if and branch.stmts: - name = asty.Name(expr, - id=mangle(compiler.temp_if), - ctx=ast.Store()) + name = asty.Name(expr, id=mangle(compiler.temp_if), ctx=ast.Store()) - branch += asty.Assign(expr, - targets=[name], - value=body.force_expr) + branch += asty.Assign(expr, targets=[name], value=body.force_expr) return branch @@ -576,27 +657,17 @@ def compile_if(compiler, expr, _, cond, body, orel_expr): # We have statements in our bodies # Get a temporary variable for the result storage var = compiler.temp_if or compiler.get_anon_var() - name = asty.Name(expr, - id=mangle(var), - ctx=ast.Store()) + name = asty.Name(expr, id=mangle(var), ctx=ast.Store()) # Store the result of the body - body += asty.Assign(expr, - targets=[name], - value=body.force_expr) + body += asty.Assign(expr, targets=[name], value=body.force_expr) # and of the else clause - if not nested or not orel.stmts or (not root and - var != compiler.temp_if): - orel += asty.Assign(expr, - targets=[name], - value=orel.force_expr) + if not nested or not orel.stmts or (not root and var != compiler.temp_if): + orel += asty.Assign(expr, targets=[name], value=orel.force_expr) # Then build the if - ret += asty.If(expr, - test=ret.force_expr, - body=body.stmts, - orelse=orel.stmts) + ret += asty.If(expr, test=ret.force_expr, body=body.stmts, orelse=orel.stmts) # And make our expression context our temp variable expr_name = asty.Name(expr, id=mangle(var), ctx=ast.Load()) @@ -604,38 +675,42 @@ def compile_if(compiler, expr, _, cond, body, orel_expr): ret += Result(expr=expr_name, temp_variables=[expr_name, name]) else: # Just make that an if expression - ret += asty.IfExp(expr, - test=ret.force_expr, - body=body.force_expr, - orelse=orel.force_expr) + ret += asty.IfExp( + expr, test=ret.force_expr, body=body.force_expr, orelse=orel.force_expr + ) if root: compiler.temp_if = None return ret + # ------------------------------------------------ # * The `for` family # ------------------------------------------------ loopers = many( - tag('setv', sym(":setv") + FORM + FORM) | - tag('if', sym(":if") + FORM) | - tag('do', sym(":do") + FORM) | - tag('afor', sym(":async") + FORM + FORM) | - tag('for', FORM + FORM)) - -@pattern_macro(["for"], [brackets(loopers), - many(notpexpr("else")) + maybe(dolike("else"))]) + tag("setv", sym(":setv") + FORM + FORM) + | tag("if", sym(":if") + FORM) + | tag("do", sym(":do") + FORM) + | tag("afor", sym(":async") + FORM + FORM) + | tag("for", FORM + FORM) +) + + +@pattern_macro( + ["for"], [brackets(loopers), many(notpexpr("else")) + maybe(dolike("else"))] +) @pattern_macro(["lfor", "sfor", "gfor"], [loopers, FORM]) @pattern_macro(["dfor"], [loopers, brackets(FORM, FORM)]) def compile_comprehension(compiler, expr, root, parts, final): node_class = { - "for": asty.For, + "for": asty.For, "lfor": asty.ListComp, "dfor": asty.DictComp, "sfor": asty.SetComp, - "gfor": asty.GeneratorExp}[root] + "gfor": asty.GeneratorExp, + }[root] is_for = root == "for" ctx = nullcontext() if is_for else compiler.scope.create(ScopeGen) @@ -645,21 +720,29 @@ def compile_comprehension(compiler, expr, root, parts, final): if is_for: parts = parts[0] if not parts: - return Result(expr=asty.parse( - expr, { - asty.For: "None", - asty.ListComp: "[]", - asty.DictComp: "{}", - asty.SetComp: "{1}.__class__()", - asty.GeneratorExp: "(_ for _ in [])" - }[node_class]).body[0].value) + return Result( + expr=asty.parse( + expr, + { + asty.For: "None", + asty.ListComp: "[]", + asty.DictComp: "{}", + asty.SetComp: "{1}.__class__()", + asty.GeneratorExp: "(_ for _ in [])", + }[node_class], + ) + .body[0] + .value + ) new_parts = [] for p in parts: if p.tag in ("if", "do"): tag_value = compiler.compile(p.value) else: - tag_value = [compiler._storeize(p.value[0], compiler.compile(p.value[0])), - compiler.compile(p.value[1])] + tag_value = [ + compiler._storeize(p.value[0], compiler.compile(p.value[0])), + compiler.compile(p.value[1]), + ] if not is_for: scope.iterator(tag_value[0]) new_parts.append(Tag(p.tag, tag_value)) @@ -682,9 +765,20 @@ def compile_comprehension(compiler, expr, root, parts, final): elt = compiler.compile(final) # Produce a result. - if (is_for or elt.stmts or (key is not None and key.stmts) or - any(p.tag == 'do' or (p.value[1].stmts if p.tag in ("for", "afor", "setv") else p.value.stmts) - for p in parts)): + if ( + is_for + or elt.stmts + or (key is not None and key.stmts) + or any( + p.tag == "do" + or ( + p.value[1].stmts + if p.tag in ("for", "afor", "setv") + else p.value.stmts + ) + for p in parts + ) + ): # The desired comprehension can't be expressed as a # real Python comprehension. We'll write it as a nested # loop in a function instead. @@ -700,30 +794,38 @@ def f(parts): if node_class is asty.DictComp: ret = key + elt val = asty.Tuple( - key, ctx=ast.Load(), - elts=[key.force_expr, elt.force_expr]) + key, ctx=ast.Load(), elts=[key.force_expr, elt.force_expr] + ) else: ret = elt val = elt.force_expr - return ret + asty.Expr( - elt, value=asty.Yield(elt, value=val)) + return ret + asty.Expr(elt, value=asty.Yield(elt, value=val)) (tagname, v), parts = parts[0], parts[1:] if tagname in ("for", "afor"): orelse = orel and orel.pop().stmts node = asty.AsyncFor if tagname == "afor" else asty.For return v[1] + node( - v[1], target=v[0], iter=v[1].force_expr, body=f(parts).stmts, - orelse=orelse) + v[1], + target=v[0], + iter=v[1].force_expr, + body=f(parts).stmts, + orelse=orelse, + ) elif tagname == "setv": - return v[1] + asty.Assign( - v[1], targets=[v[0]], value=v[1].force_expr) + f(parts) + return ( + v[1] + + asty.Assign(v[1], targets=[v[0]], value=v[1].force_expr) + + f(parts) + ) elif tagname == "if": return v + asty.If( - v, test=v.force_expr, body=f(parts).stmts, orelse=[]) + v, test=v.force_expr, body=f(parts).stmts, orelse=[] + ) elif tagname == "do": return v + v.expr_as_stmt() + f(parts) else: raise ValueError("can't happen") + if is_for: return f(parts) fname = compiler.get_anon_var() @@ -749,9 +851,12 @@ def f(parts): ) assignments = asty.Tuple( expr, - elts=[asty.Name(expr, id=var, ctx=ast.Store()) - for var in assignment_names], - ctx=ast.Store()) + elts=[ + asty.Name(expr, id=var, ctx=ast.Store()) + for var in assignment_names + ], + ctx=ast.Store(), + ) if_body.append( asty.Assign( expr, @@ -759,54 +864,84 @@ def f(parts): value=asty.Constant(expr, value=None), ) ) - ret += asty.If(expr, test=asty.Constant(expr, value=False), body=if_body, orelse=[]) + ret += asty.If( + expr, test=asty.Constant(expr, value=False), body=if_body, orelse=[] + ) ret += asty.FunctionDef( expr, name=fname, args=ast.arguments( - args=[], vararg=None, kwarg=None, posonlyargs=[], - kwonlyargs=[], kw_defaults=[], defaults=[]), + args=[], + vararg=None, + kwarg=None, + posonlyargs=[], + kwonlyargs=[], + kw_defaults=[], + defaults=[], + ), body=stmts + f(parts).stmts, - decorator_list=[]) + decorator_list=[], + ) # Immediately call the new function. Unless the user asked # for a generator, wrap the call in `[].__class__(...)` or # `{}.__class__(...)` or `{1}.__class__(...)` to get the # right type. We don't want to just use e.g. `list(...)` # because the name `list` might be rebound. - return ret + Result(expr=asty.parse( - expr, - "{}({}())".format( - {asty.ListComp: "[].__class__", - asty.DictComp: "{}.__class__", - asty.SetComp: "{1}.__class__", - asty.GeneratorExp: ""}[node_class], - fname)).body[0].value) + return ret + Result( + expr=asty.parse( + expr, + "{}({}())".format( + { + asty.ListComp: "[].__class__", + asty.DictComp: "{}.__class__", + asty.SetComp: "{1}.__class__", + asty.GeneratorExp: "", + }[node_class], + fname, + ), + ) + .body[0] + .value + ) # We can produce a real comprehension. generators = [] for tagname, v in parts: if tagname in ("for", "afor"): - generators.append(ast.comprehension( - target=v[0], iter=v[1].expr, ifs=[], - is_async=int(tagname == "afor"))) + generators.append( + ast.comprehension( + target=v[0], + iter=v[1].expr, + ifs=[], + is_async=int(tagname == "afor"), + ) + ) elif tagname == "setv": - generators.append(ast.comprehension( - target=v[0], - iter=asty.Tuple(v[1], elts=[v[1].expr], ctx=ast.Load()), - ifs=[], is_async=0)) + generators.append( + ast.comprehension( + target=v[0], + iter=asty.Tuple(v[1], elts=[v[1].expr], ctx=ast.Load()), + ifs=[], + is_async=0, + ) + ) elif tagname == "if": generators[-1].ifs.append(v.expr) else: raise ValueError("can't happen") if node_class is asty.DictComp: - return asty.DictComp(expr, key=key.expr, value=elt.expr, generators=generators) + return asty.DictComp( + expr, key=key.expr, value=elt.expr, generators=generators + ) return node_class(expr, elt=elt.expr, generators=generators) + # ------------------------------------------------ # * More looping # ------------------------------------------------ + @pattern_macro(["while"], [FORM, many(notpexpr("else")), maybe(dolike("else"))]) def compile_while_expression(compiler, expr, root, cond, body, else_expr): cond_compiled = compiler.compile(cond) @@ -826,22 +961,31 @@ def compile_while_expression(compiler, expr, root, cond, body, else_expr): # if anon_var: # while loop body cond_var = asty.Name(cond, id=compiler.get_anon_var(), ctx=ast.Load()) + def make_not(operand): return asty.UnaryOp(cond, op=ast.Not(), operand=operand) body_stmts = cond_compiled.stmts + [ - asty.Assign(cond, targets=[compiler._storeize(cond, cond_var)], - # Cast the condition to a bool in case it's mutable and - # changes its truth value, but use (not (not ...)) instead of - # `bool` in case `bool` has been redefined. - value=make_not(make_not(cond_compiled.force_expr))), + asty.Assign( + cond, + targets=[compiler._storeize(cond, cond_var)], + # Cast the condition to a bool in case it's mutable and + # changes its truth value, but use (not (not ...)) instead of + # `bool` in case `bool` has been redefined. + value=make_not(make_not(cond_compiled.force_expr)), + ), asty.If(cond, test=cond_var, body=body_stmts, orelse=[]), ] - cond_compiled = (Result() - + asty.Assign(cond, targets=[compiler._storeize(cond, cond_var)], - value=asty.Constant(cond, value=True)) - + cond_var) + cond_compiled = ( + Result() + + asty.Assign( + cond, + targets=[compiler._storeize(cond, cond_var)], + value=asty.Constant(cond, value=True), + ) + + cond_var + ) orel = Result() if else_expr is not None: @@ -849,24 +993,30 @@ def make_not(operand): orel += orel.expr_as_stmt() ret = cond_compiled + asty.While( - expr, test=cond_compiled.force_expr, - body=body_stmts, - orelse=orel.stmts) + expr, test=cond_compiled.force_expr, body=body_stmts, orelse=orel.stmts + ) return ret + @pattern_macro(["break", "continue"], []) def compile_break_or_continue_expression(compiler, expr, root): return (asty.Break if root == "break" else asty.Continue)(expr) + # ------------------------------------------------ # * `with` # ------------------------------------------------ -@pattern_macro(["with", "with/a"], [ - brackets(oneplus(FORM + FORM)) | - brackets(FORM >> (lambda x: [(Symbol('_'), x)])), - many(FORM)]) + +@pattern_macro( + ["with", "with/a"], + [ + brackets(oneplus(FORM + FORM)) + | brackets(FORM >> (lambda x: [(Symbol("_"), x)])), + many(FORM), + ], +) def compile_with_expression(compiler, expr, root, args, body): body = compiler._compile_branch(body) @@ -877,19 +1027,22 @@ def compile_with_expression(compiler, expr, root, args, body): # Initialize the tempvar to None in case the `with` exits # early with an exception. initial_assign = asty.Assign( - expr, targets=[name], value=asty.Constant(expr, value=None)) + expr, targets=[name], value=asty.Constant(expr, value=None) + ) ret = Result(stmts=[initial_assign]) items = [] for variable, ctx in args[0]: ctx = compiler.compile(ctx) ret += ctx - variable = (None - if variable == Symbol('_') - else compiler._storeize(variable, compiler.compile(variable))) - items.append(asty.withitem(expr, - context_expr=ctx.force_expr, - optional_vars=variable)) + variable = ( + None + if variable == Symbol("_") + else compiler._storeize(variable, compiler.compile(variable)) + ) + items.append( + asty.withitem(expr, context_expr=ctx.force_expr, optional_vars=variable) + ) node = asty.With if root == "with" else asty.AsyncWith ret += node(expr, body=body.stmts, items=items) @@ -904,6 +1057,7 @@ def compile_with_expression(compiler, expr, root, args, body): return ret + # ------------------------------------------------ # * `switch` # ------------------------------------------------ @@ -917,9 +1071,7 @@ def compile_with_expression(compiler, expr, root, args, body): | pexpr(keepsym("."), many(SYM)) | pexpr(keepsym("|"), many(_pattern)) | pexpr(keepsym(","), many(_pattern)) - | braces( - many(LITERAL + _pattern), maybe(pvalue("unpack-mapping", SYM)) - ) + | braces(many(LITERAL + _pattern), maybe(pvalue("unpack-mapping", SYM))) | pexpr( notsym(".", "|", ",", "unpack-mapping", "unpack-iterable"), many(_pattern), @@ -930,9 +1082,8 @@ def compile_with_expression(compiler, expr, root, args, body): ) match_clause = _pattern + maybe(sym(":if") + FORM) -@pattern_macro( - ((3, 10), "match"), [FORM, many(match_clause + FORM)] -) + +@pattern_macro(((3, 10), "match"), [FORM, many(match_clause + FORM)]) def compile_match_expression(compiler, expr, root, subject, clauses): subject = compiler.compile(subject) return_var = asty.Name(expr, id=mangle(compiler.get_anon_var()), ctx=ast.Store()) @@ -985,7 +1136,9 @@ def compile_match_expression(compiler, expr, root, subject, clauses): temp_variables=[return_var], ) ret = Result() + subject - ret += asty.Assign(expr, targets=[return_var], value=asty.Constant(expr, value=None)) + ret += asty.Assign( + expr, targets=[return_var], value=asty.Constant(expr, value=None) + ) if not match_cases: return ret + returnable @@ -994,14 +1147,17 @@ def compile_match_expression(compiler, expr, root, subject, clauses): ret += asty.Match(expr, subject=subject.force_expr, cases=match_cases) return ret + returnable + def compile_pattern(compiler, pattern): value, assignment = pattern if assignment is not None: - return compiler.scope.assign(asty.MatchAs( - value, - pattern=compile_pattern(compiler, (value, None)), - name=mangle(compiler._nonconst(assignment)) - )) + return compiler.scope.assign( + asty.MatchAs( + value, + pattern=compile_pattern(compiler, (value, None)), + name=mangle(compiler._nonconst(assignment)), + ) + ) if str(value) in ("None", "True", "False"): return asty.MatchSingleton( @@ -1051,12 +1207,14 @@ def compile_pattern(compiler, pattern): kvs, rest = value keys, values = zip(*kvs) if kvs else ([], []) # Call `scope.assign` for the assignment to `rest`. - return compiler.scope.assign(asty.MatchMapping( - value, - keys=[compiler.compile(key).expr for key in keys], - patterns=[compile_pattern(compiler, v) for v in values], - rest=mangle(rest) if rest else None, - )) + return compiler.scope.assign( + asty.MatchMapping( + value, + keys=[compiler.compile(key).expr for key in keys], + patterns=[compile_pattern(compiler, v) for v in values], + rest=mangle(rest) if rest else None, + ) + ) elif isinstance(value, Expression): root, args, kwargs = value keywords, values = zip(*kwargs) if kwargs else ([], []) @@ -1070,10 +1228,12 @@ def compile_pattern(compiler, pattern): else: raise compiler._syntax_error(value, "unsupported") + # ------------------------------------------------ # * `raise` and `try` # ------------------------------------------------ + @pattern_macro("raise", [maybe(FORM), maybe(sym(":from") + FORM)]) def compile_raise_expression(compiler, expr, root, exc, cause): ret = Result() @@ -1089,35 +1249,43 @@ def compile_raise_expression(compiler, expr, root, exc, cause): cause = cause.force_expr return ret + asty.Raise( - expr, type=ret.expr, exc=exc, - inst=None, tback=None, cause=cause) - -@pattern_macro("try", - [many(notpexpr("except", "else", "finally")), - many(pexpr(sym("except"), - brackets() | brackets(FORM) | brackets(SYM, FORM), - many(FORM))), - maybe(dolike("else")), - maybe(dolike("finally"))]) + expr, type=ret.expr, exc=exc, inst=None, tback=None, cause=cause + ) + + +@pattern_macro( + "try", + [ + many(notpexpr("except", "else", "finally")), + many( + pexpr( + sym("except"), + brackets() | brackets(FORM) | brackets(SYM, FORM), + many(FORM), + ) + ), + maybe(dolike("else")), + maybe(dolike("finally")), + ], +) def compile_try_expression(compiler, expr, root, body, catchers, orelse, finalbody): body = compiler._compile_branch(body) - return_var = asty.Name( - expr, id=mangle(compiler.get_anon_var()), ctx=ast.Store()) + return_var = asty.Name(expr, id=mangle(compiler.get_anon_var()), ctx=ast.Store()) handler_results = Result() handlers = [] for catcher in catchers: handler_results += compile_catch_expression( - compiler, catcher, return_var, *catcher) + compiler, catcher, return_var, *catcher + ) handlers.append(handler_results.stmts.pop()) if orelse is None: orelse = [] else: orelse = compiler._compile_branch(orelse) - orelse += asty.Assign(expr, targets=[return_var], - value=orelse.force_expr) + orelse += asty.Assign(expr, targets=[return_var], value=orelse.force_expr) orelse += orelse.expr_as_stmt() orelse = orelse.stmts @@ -1130,28 +1298,28 @@ def compile_try_expression(compiler, expr, root, body, catchers, orelse, finalbo # Using (else) without (except) is verboten! if orelse and not handlers: - raise compiler._syntax_error(expr, - "`try' cannot have `else' without `except'") + raise compiler._syntax_error(expr, "`try' cannot have `else' without `except'") # Likewise a bare (try) or (try BODY). if not (handlers or finalbody): - raise compiler._syntax_error(expr, - "`try' must have an `except' or `finally' clause") + raise compiler._syntax_error( + expr, "`try' must have an `except' or `finally' clause" + ) returnable = Result( expr=asty.Name(expr, id=return_var.id, ctx=ast.Load()), - temp_variables=[return_var]) - body += body.expr_as_stmt() if orelse else asty.Assign( - expr, targets=[return_var], value=body.force_expr) + temp_variables=[return_var], + ) + body += ( + body.expr_as_stmt() + if orelse + else asty.Assign(expr, targets=[return_var], value=body.force_expr) + ) body = body.stmts or [asty.Pass(expr)] - x = asty.Try( - expr, - body=body, - handlers=handlers, - orelse=orelse, - finalbody=finalbody) + x = asty.Try(expr, body=body, handlers=handlers, orelse=orelse, finalbody=finalbody) return handler_results + x + returnable + def compile_catch_expression(compiler, expr, var, exceptions, body): # exceptions catch should be either: # [[list of exceptions]] @@ -1190,8 +1358,9 @@ def compile_catch_expression(compiler, expr, var, exceptions, body): body += body.expr_as_stmt() return types + asty.ExceptHandler( - expr, type=types.expr, name=name, - body=body.stmts or [asty.Pass(expr)]) + expr, type=types.expr, name=name, body=body.stmts or [asty.Pass(expr)] + ) + # ------------------------------------------------ # * Functions and macros @@ -1202,11 +1371,13 @@ def compile_catch_expression(compiler, expr, var, exceptions, body): varargs = lambda unpack_type, wanted: OPTIONAL_ANNOTATION + pvalue(unpack_type, wanted) kwonly_delim = some(lambda x: x == Symbol("*")) lambda_list = brackets( - maybe(many(argument) + sym("/")), - many(argument), - maybe(kwonly_delim | varargs("unpack-iterable", NASYM)), - many(argument), - maybe(varargs("unpack-mapping", NASYM))) + maybe(many(argument) + sym("/")), + many(argument), + maybe(kwonly_delim | varargs("unpack-iterable", NASYM)), + many(argument), + maybe(varargs("unpack-mapping", NASYM)), +) + @pattern_macro(["fn", "fn/a"], [OPTIONAL_ANNOTATION, lambda_list, many(FORM)]) def compile_function_lambda(compiler, expr, root, returns, params, body): @@ -1231,6 +1402,7 @@ def compile_function_lambda(compiler, expr, root, returns, params, body): # return its name as the final expr return ret + Result(expr=ret.temp_variables[0]) + @pattern_macro(["defn", "defn/a"], [OPTIONAL_ANNOTATION, SYM, lambda_list, many(FORM)]) def compile_function_def(compiler, expr, root, returns, name, params, body): node = asty.FunctionDef if root == "defn" else asty.AsyncFunctionDef @@ -1240,10 +1412,8 @@ def compile_function_def(compiler, expr, root, returns, name, params, body): with compiler.scope.create(ScopeFn, args): body = compiler._compile_branch(body) - return ret + compile_function_node( - compiler, expr, node, - name, args, returns, body - ) + return ret + compile_function_node(compiler, expr, node, name, args, returns, body) + def compile_function_node(compiler, expr, node, name, args, returns, body): ret = Result() @@ -1263,6 +1433,7 @@ def compile_function_node(compiler, expr, node, name, args, returns, body): ast_name = asty.Name(expr, id=name, ctx=ast.Load()) return ret + Result(temp_variables=[ast_name, ret.stmts[-1]]) + @pattern_macro("defmacro", [SYM | STR, lambda_list, many(FORM)]) def compile_macro_def(compiler, expr, root, name, params, body): _, _, rest, _, kwargs = params @@ -1274,23 +1445,34 @@ def compile_macro_def(compiler, expr, root, name, params, body): if kwargs is not None: raise compiler._syntax_error(kwargs, "macros cannot use '#**'") - ret = Result() + compiler.compile(Expression([ - Symbol("eval-and-compile"), - Expression([ - Expression([ - Symbol("hy.macros.macro"), - str(name), - ]), - Expression([ - Symbol("fn"), - List([Symbol("&compiler")] + list(expr[2])), - *body - ]) - ]) - ]).replace(expr)) + ret = Result() + compiler.compile( + Expression( + [ + Symbol("eval-and-compile"), + Expression( + [ + Expression( + [ + Symbol("hy.macros.macro"), + str(name), + ] + ), + Expression( + [ + Symbol("fn"), + List([Symbol("&compiler")] + list(expr[2])), + *body, + ] + ), + ] + ), + ] + ).replace(expr) + ) return ret + ret.expr_as_stmt() + def compile_lambda_list(compiler, params): ret = Result() posonly_parms, args_parms, rest_parms, kwonly_parms, kwargs_parms = params @@ -1324,38 +1506,51 @@ def compile_lambda_list(compiler, params): is_positional_arg = lambda x: isinstance(x[1], Symbol) invalid_non_default = next( - (arg - for arg in dropwhile(is_positional_arg, posonly_parms + args_parms) - if is_positional_arg(arg)), - None + ( + arg + for arg in dropwhile(is_positional_arg, posonly_parms + args_parms) + if is_positional_arg(arg) + ), + None, ) if invalid_non_default: raise compiler._syntax_error( invalid_non_default[1], "non-default argument follows default argument" ) - posonly_ast, posonly_defaults, ret = compile_arguments_set(compiler, posonly_parms, ret) + posonly_ast, posonly_defaults, ret = compile_arguments_set( + compiler, posonly_parms, ret + ) args_ast, args_defaults, ret = compile_arguments_set(compiler, args_parms, ret) - kwonly_ast, kwonly_defaults, ret = compile_arguments_set(compiler, kwonly_parms, ret, True) + kwonly_ast, kwonly_defaults, ret = compile_arguments_set( + compiler, kwonly_parms, ret, True + ) rest_ast = kwargs_ast = None - if rest_parms == Symbol("*"): # rest is a positional only marker + if rest_parms == Symbol("*"): # rest is a positional only marker if not kwonly_parms: - raise compiler._syntax_error(rest_parms, "named arguments must follow bare *") + raise compiler._syntax_error( + rest_parms, "named arguments must follow bare *" + ) rest_ast = None - elif rest_parms: # rest is capturing varargs + elif rest_parms: # rest is capturing varargs [rest_ast], _, ret = compile_arguments_set(compiler, [rest_parms], ret) if kwargs_parms: [kwargs_ast], _, ret = compile_arguments_set(compiler, [kwargs_parms], ret) - return ast.arguments( - args=args_ast, - defaults=[*posonly_defaults, *args_defaults], - vararg=rest_ast, - posonlyargs=posonly_ast, - kwonlyargs=kwonly_ast, - kw_defaults=kwonly_defaults, - kwarg=kwargs_ast), ret + return ( + ast.arguments( + args=args_ast, + defaults=[*posonly_defaults, *args_defaults], + vararg=rest_ast, + posonlyargs=posonly_ast, + kwonlyargs=kwonly_ast, + kw_defaults=kwonly_defaults, + kwarg=kwargs_ast, + ), + ret, + ) + def compile_arguments_set(compiler, decls, ret, is_kwonly=False): args_ast = [] @@ -1390,23 +1585,26 @@ def compile_arguments_set(compiler, decls, ret, is_kwonly=False): # positional args. args_defaults.append(None) - args_ast.append(asty.arg( - sym, arg=mangle(compiler._nonconst(sym)), annotation=ann_ast)) + args_ast.append( + asty.arg(sym, arg=mangle(compiler._nonconst(sym)), annotation=ann_ast) + ) return args_ast, args_defaults, ret + _decoratables = (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef) + @pattern_macro("with-decorator", [oneplus(FORM)]) def compile_decorate_expression(compiler, expr, name, args): decs, fn = args[:-1], compiler.compile(args[-1]) if not fn.stmts or not isinstance(fn.stmts[-1], _decoratables): - raise compiler._syntax_error(args[-1], - "Decorated a non-function") + raise compiler._syntax_error(args[-1], "Decorated a non-function") decs, ret, _ = compiler._compile_collect(decs) fn.stmts[-1].decorator_list = decs + fn.stmts[-1].decorator_list return ret + fn + @pattern_macro("return", [maybe(FORM)]) def compile_return(compiler, expr, root, arg): ret = Result() @@ -1415,6 +1613,7 @@ def compile_return(compiler, expr, root, arg): ret += compiler.compile(arg) return ret + asty.Return(expr, value=ret.force_expr) + @pattern_macro("yield", [maybe(FORM)]) def compile_yield_expression(compiler, expr, root, arg): ret = Result() @@ -1422,24 +1621,26 @@ def compile_yield_expression(compiler, expr, root, arg): ret += compiler.compile(arg) return ret + asty.Yield(expr, value=ret.force_expr) + @pattern_macro(["yield-from", "await"], [FORM]) def compile_yield_from_or_await_expression(compiler, expr, root, arg): ret = Result() + compiler.compile(arg) node = asty.YieldFrom if root == "yield-from" else asty.Await return ret + node(expr, value=ret.force_expr) + # ------------------------------------------------ # * `defclass` # ------------------------------------------------ -@pattern_macro("defclass", [ - SYM, - maybe(brackets(many(FORM)) + maybe(STR) + many(FORM))]) + +@pattern_macro("defclass", [SYM, maybe(brackets(many(FORM)) + maybe(STR) + many(FORM))]) def compile_class_expression(compiler, expr, root, name, rest): base_list, docstring, body = rest or ([[]], None, []) - bases_expr, bases, keywords = ( - compiler._compile_collect(base_list[0], with_kwargs=True)) + bases_expr, bases, keywords = compiler._compile_collect( + base_list[0], with_kwargs=True + ) bodyr = Result() @@ -1461,19 +1662,28 @@ def compile_class_expression(compiler, expr, root, name, rest): starargs=None, kwargs=None, bases=bases_expr, - body=bodyr.stmts or [asty.Pass(expr)]) + body=bodyr.stmts or [asty.Pass(expr)], + ) + # ------------------------------------------------ # * `import` and `require` # ------------------------------------------------ + def importlike(*name_types): name = some(lambda x: isinstance(x, name_types) and "." not in x) - return [many( - SYM + maybe(keepsym("*") - | (keepsym(":as") + name) - | brackets(many( - name + maybe(sym(":as") + name)))))] + return [ + many( + SYM + + maybe( + keepsym("*") + | (keepsym(":as") + name) + | brackets(many(name + maybe(sym(":as") + name))) + ) + ) + ] + @pattern_macro("import", importlike(Symbol)) @pattern_macro("require", importlike(Symbol, String)) @@ -1509,10 +1719,13 @@ def compile_import_or_require(compiler, expr, root, entries): elif assignments == "ALL": compiler.scope.define(mangle(prefix)) node = asty.Import - names = [asty.alias( - module, - name=ast_module, - asname=mangle(prefix) if prefix != module else None)] + names = [ + asty.alias( + module, + name=ast_module, + asname=mangle(prefix) if prefix != module else None, + ) + ] else: node = asty.ImportFrom names = [] @@ -1520,40 +1733,51 @@ def compile_import_or_require(compiler, expr, root, entries): compiler.scope.define(mangle(v)) names.append( asty.alias( - module, - name=mangle(k), - asname=None if v == k else mangle(v))) - ret += node( - expr, module=module_name or None, names=names, level=level) - - elif require(ast_module, compiler.module, assignments=assignments, - prefix=prefix): + module, name=mangle(k), asname=None if v == k else mangle(v) + ) + ) + ret += node(expr, module=module_name or None, names=names, level=level) + + elif require( + ast_module, compiler.module, assignments=assignments, prefix=prefix + ): # Actually calling `require` is necessary for macro expansions # occurring during compilation. # The `require` we're creating in AST is the same as above, but used at # run-time (e.g. when modules are loaded via bytecode). - ret += compiler.compile(Expression([ - Symbol('hy.macros.require'), - String(ast_module), - Symbol('None'), - Keyword('assignments'), - (String("ALL") if assignments == "ALL" else - [[String(k), String(v)] for k, v in assignments]), - Keyword('prefix'), - String(prefix)]).replace(expr)) + ret += compiler.compile( + Expression( + [ + Symbol("hy.macros.require"), + String(ast_module), + Symbol("None"), + Keyword("assignments"), + ( + String("ALL") + if assignments == "ALL" + else [[String(k), String(v)] for k, v in assignments] + ), + Keyword("prefix"), + String(prefix), + ] + ).replace(expr) + ) ret += ret.expr_as_stmt() return ret + # ------------------------------------------------ # * Miscellany # ------------------------------------------------ + @pattern_macro(",", [many(FORM)]) def compile_tuple(compiler, expr, root, args): elts, ret, _ = compiler._compile_collect(args) return ret + asty.Tuple(expr, elts=elts, ctx=ast.Load()) + @pattern_macro("assert", [FORM, maybe(FORM)]) def compile_assert_expression(compiler, expr, root, test, msg): if msg is None or type(msg) is Symbol: @@ -1561,17 +1785,23 @@ def compile_assert_expression(compiler, expr, root, test, msg): return ret + asty.Assert( expr, test=ret.force_expr, - msg=(None if msg is None else compiler.compile(msg).force_expr)) + msg=(None if msg is None else compiler.compile(msg).force_expr), + ) # The `msg` part may involve statements, which we only # want to be executed if the assertion fails. Rewrite the # form to set `msg` to a variable. msg_var = compiler.get_anon_var() - return compiler.compile(mkexpr( - 'if', mkexpr('and', '__debug__', mkexpr('not', [test])), - mkexpr('do', - mkexpr('setv', msg_var, [msg]), - mkexpr('assert', 'False', msg_var))).replace(expr)) + return compiler.compile( + mkexpr( + "if", + mkexpr("and", "__debug__", mkexpr("not", [test])), + mkexpr( + "do", mkexpr("setv", msg_var, [msg]), mkexpr("assert", "False", msg_var) + ), + ).replace(expr) + ) + @pattern_macro("let", [brackets(many(OPTIONAL_ANNOTATION + FORM + FORM)), many(FORM)]) def compile_let(compiler, expr, root, bindings, body): @@ -1583,4 +1813,4 @@ def compile_let(compiler, expr, root, bindings, body): res += compile_assign(compiler, ann, target, value, let_scope=scope) with scope: - return res + compiler.compile(mkexpr("do", *body).replace(expr)) + return res + compiler.compile(mkexpr("do", *body).replace(expr)) diff --git a/hy/errors.py b/hy/errors.py index 76aeeb813..e687d263b 100644 --- a/hy/errors.py +++ b/hy/errors.py @@ -1,18 +1,18 @@ import os +import pkgutil import re import sys import traceback -import pkgutil - +from contextlib import contextmanager from functools import reduce + from colorama import Fore -from contextlib import contextmanager + from hy import _initialize_env_var from hy._compat import PYPY -_hy_filter_internal_errors = _initialize_env_var('HY_FILTER_INTERNAL_ERRORS', - True) -COLORED = _initialize_env_var('HY_COLORED_ERRORS', False) +_hy_filter_internal_errors = _initialize_env_var("HY_FILTER_INTERNAL_ERRORS", True) +COLORED = _initialize_env_var("HY_COLORED_ERRORS", False) class HyError(Exception): @@ -59,8 +59,7 @@ def __init__( self.compute_lineinfo(expression, filename, source, lineno, colno) if isinstance(self, SyntaxError): - syntax_error_args = (self.filename, self.lineno, self.offset, - self.text) + syntax_error_args = (self.filename, self.lineno, self.offset, self.text) super().__init__(message, syntax_error_args) else: super().__init__(message) @@ -69,20 +68,19 @@ def compute_lineinfo(self, expression, filename, source, lineno, colno): # NOTE: We use `SyntaxError`'s field names (i.e. `text`, `offset`, # `msg`) for compatibility and print-outs. - self.text = getattr(expression, 'source', source) - self.filename = getattr(expression, 'filename', filename) + self.text = getattr(expression, "source", source) + self.filename = getattr(expression, "filename", filename) if self.text: lines = self.text.splitlines() - self.lineno = getattr(expression, 'start_line', lineno) - self.offset = getattr(expression, 'start_column', colno) - end_column = getattr(expression, 'end_column', - len(lines[self.lineno-1])) - end_line = getattr(expression, 'end_line', self.lineno) + self.lineno = getattr(expression, "start_line", lineno) + self.offset = getattr(expression, "start_column", colno) + end_column = getattr(expression, "end_column", len(lines[self.lineno - 1])) + end_line = getattr(expression, "end_line", self.lineno) # Trim the source down to the essentials. - self.text = '\n'.join(lines[self.lineno-1:end_line]) + self.text = "\n".join(lines[self.lineno - 1 : end_line]) if end_column: if self.lineno == end_line: @@ -115,38 +113,39 @@ def __str__(self): # Re-purpose Python's builtin syntax error formatting. output = traceback.format_exception_only( SyntaxError, - SyntaxError(self.msg, (self.filename, self.lineno, self.offset, - self.text))) + SyntaxError(self.msg, (self.filename, self.lineno, self.offset, self.text)), + ) - arrow_idx, _ = next(((i, x) for i, x in enumerate(output) - if x.strip() == '^'), - (None, None)) + arrow_idx, _ = next( + ((i, x) for i, x in enumerate(output) if x.strip() == "^"), (None, None) + ) if arrow_idx: msg_idx = arrow_idx + 1 else: - msg_idx, _ = next((i, x) for i, x in enumerate(output) - if x.startswith('SyntaxError: ')) + msg_idx, _ = next( + (i, x) for i, x in enumerate(output) if x.startswith("SyntaxError: ") + ) # Get rid of erroneous error-type label. - output[msg_idx] = re.sub('^SyntaxError: ', '', output[msg_idx]) + output[msg_idx] = re.sub("^SyntaxError: ", "", output[msg_idx]) # Extend the text arrow, when given enough source info. if arrow_idx and self.arrow_offset: - output[arrow_idx] = '{}{}^\n'.format(output[arrow_idx].rstrip('\n'), - '-' * (self.arrow_offset - 1)) + output[arrow_idx] = "{}{}^\n".format( + output[arrow_idx].rstrip("\n"), "-" * (self.arrow_offset - 1) + ) if COLORED: output[msg_idx:] = [Fore.YELLOW + o + Fore.RESET for o in output[msg_idx:]] if arrow_idx: output[arrow_idx] = Fore.GREEN + output[arrow_idx] + Fore.RESET for idx, line in enumerate(output[::msg_idx]): - if line.strip().startswith( - 'File "{}", line'.format(self.filename)): + if line.strip().startswith('File "{}", line'.format(self.filename)): output[idx] = Fore.RED + line + Fore.RESET # This resulting string will come after a ":" prompt, so # put it down a line. - output.insert(0, '\n') + output.insert(0, "\n") # Avoid "...expected str instance, ColoredString found" return reduce(lambda x, y: x + y, output) @@ -190,7 +189,7 @@ class HyEvalError(HyLanguageError): class HyIOError(HyInternalError, IOError): - """ Subclass used to distinguish between IOErrors raised by Hy itself as + """Subclass used to distinguish between IOErrors raised by Hy itself as opposed to Hy programs. """ @@ -225,19 +224,30 @@ def _module_filter_name(module_name): else: # Normalize filename endings, because tracebacks will use `pyc` when # the loader says `py`. - return filename.replace('.pyc', '.py') + return filename.replace(".pyc", ".py") except Exception: return None -_tb_hidden_modules = {m for m in map(_module_filter_name, - ['hy.compiler', 'hy.lex', - 'hy.cmdline', 'hy.lex.parser', - 'hy.importer', 'hy._compat', - 'hy.macros', 'hy.models', - 'hy.core.result_macros', - 'rply']) - if m is not None} +_tb_hidden_modules = { + m + for m in map( + _module_filter_name, + [ + "hy.compiler", + "hy.lex", + "hy.cmdline", + "hy.lex.parser", + "hy.importer", + "hy._compat", + "hy.macros", + "hy.models", + "hy.core.result_macros", + "rply", + ], + ) + if m is not None +} # We can't derive these easily from just their module names due # to missing magic attributes in internal importlib modules @@ -260,8 +270,10 @@ def hy_exc_filter(exc_type, exc_value, exc_traceback): # frame = (filename, line number, function name*, text) new_tb = [] for frame in traceback.extract_tb(exc_traceback): - if not (frame[0].replace('.pyc', '.py') in _tb_hidden_modules or - os.path.dirname(frame[0]) in _tb_hidden_modules): + if not ( + frame[0].replace(".pyc", ".py") in _tb_hidden_modules + or os.path.dirname(frame[0]) in _tb_hidden_modules + ): new_tb += [frame] lines = traceback.format_list(new_tb) @@ -269,7 +281,7 @@ def hy_exc_filter(exc_type, exc_value, exc_traceback): lines.insert(0, "Traceback (most recent call last):\n") lines.extend(traceback.format_exception_only(exc_type, exc_value)) - output = ''.join(lines) + output = "".join(lines) return output @@ -278,7 +290,7 @@ def hy_exc_handler(exc_type, exc_value, exc_traceback): """A `sys.excepthook` handler that uses `hy_exc_filter` to remove internal Hy frames from a traceback print-out. """ - if os.environ.get('HY_DEBUG', False): + if os.environ.get("HY_DEBUG", False): return sys.__excepthook__(exc_type, exc_value, exc_traceback) try: diff --git a/hy/importer.py b/hy/importer.py index b9d715e90..4a94b3131 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -1,13 +1,12 @@ -import sys -import os +import builtins +import importlib import inspect +import os import pkgutil +import sys import types -import importlib -import builtins - -from functools import partial from contextlib import contextmanager +from functools import partial import hy from hy.compiler import hy_compile @@ -48,8 +47,7 @@ def loader_module_obj(loader): if module is None: tmp_mod = True - module = sys.modules.setdefault(loader.name, - types.ModuleType(loader.name)) + module = sys.modules.setdefault(loader.name, types.ModuleType(loader.name)) module.__file__ = loader.path module.__name__ = loader.name @@ -78,8 +76,7 @@ def _hy_code_from_file(filename, loader_type=None): return code -def _get_code_from_file(run_name, fname=None, - hy_src_check=lambda x: x.endswith('.hy')): +def _get_code_from_file(run_name, fname=None, hy_src_check=lambda x: x.endswith(".hy")): """A patch of `runpy._get_code_from_file` that will also run and cache Hy code. """ @@ -98,20 +95,24 @@ def _get_code_from_file(run_name, fname=None, with open(fname, "rb") as f: # This code differs from `runpy`'s only in that we # force decoding into UTF-8. - source = f.read().decode('utf-8') - code = compile(source, fname, 'exec') + source = f.read().decode("utf-8") + code = compile(source, fname, "exec") return (code, fname) -importlib.machinery.SOURCE_SUFFIXES.insert(0, '.hy') +importlib.machinery.SOURCE_SUFFIXES.insert(0, ".hy") _py_source_to_code = importlib.machinery.SourceFileLoader.source_to_code + def _could_be_hy_src(filename): - return (os.path.isfile(filename) and - (filename.endswith('.hy') or - not any(filename.endswith(ext) - for ext in importlib.machinery.SOURCE_SUFFIXES[1:]))) + return os.path.isfile(filename) and ( + filename.endswith(".hy") + or not any( + filename.endswith(ext) for ext in importlib.machinery.SOURCE_SUFFIXES[1:] + ) + ) + def _hy_source_to_code(self, data, path, _optimize=-1): if _could_be_hy_src(path): @@ -122,6 +123,7 @@ def _hy_source_to_code(self, data, path, _optimize=-1): return _py_source_to_code(self, data, path, _optimize=_optimize) + importlib.machinery.SourceFileLoader.source_to_code = _hy_source_to_code # This is actually needed; otherwise, pre-created finders assigned to the @@ -139,14 +141,13 @@ def _hy_source_to_code(self, data, path, _optimize=-1): # We create a separate version of runpy, "runhy", that prefers Hy source over # Python. -runhy = importlib.import_module('runpy') +runhy = importlib.import_module("runpy") -runhy._get_code_from_file = partial(_get_code_from_file, - hy_src_check=_could_be_hy_src) +runhy._get_code_from_file = partial(_get_code_from_file, hy_src_check=_could_be_hy_src) -del sys.modules['runpy'] +del sys.modules["runpy"] -runpy = importlib.import_module('runpy') +runpy = importlib.import_module("runpy") _runpy_get_code_from_file = runpy._get_code_from_file runpy._get_code_from_file = _get_code_from_file @@ -162,7 +163,7 @@ def _import_from_path(name, path): def _inject_builtins(): """Inject the Hy core macros into Python's builtins if necessary""" - if hasattr(builtins, '__hy_injected__'): + if hasattr(builtins, "__hy_injected__"): return hy.macros.load_macros(builtins) # Set the marker so we don't inject again. diff --git a/hy/lex/__init__.py b/hy/lex/__init__.py index 891d93593..3c440f7b1 100644 --- a/hy/lex/__init__.py +++ b/hy/lex/__init__.py @@ -3,7 +3,7 @@ import sys import unicodedata -from hy.lex.exceptions import PrematureEndOfInput, LexException # NOQA +from hy.lex.exceptions import LexException, PrematureEndOfInput # NOQA from hy.models import Expression, Symbol try: @@ -12,7 +12,7 @@ from StringIO import StringIO -def hy_parse(source, filename=''): +def hy_parse(source, filename=""): """Parse a Hy source string. Args: @@ -22,10 +22,8 @@ def hy_parse(source, filename=''): Returns: Expression: the parsed models wrapped in an hy.models.Expression """ - _source = re.sub(r'\A#!.*', '', source) - res = Expression([Symbol("do")] + - tokenize(_source + "\n", - filename=filename)) + _source = re.sub(r"\A#!.*", "", source) + res = Expression([Symbol("do")] + tokenize(_source + "\n", filename=filename)) res.source = source res.filename = filename return res @@ -38,7 +36,7 @@ def __init__(self, source, filename): def tokenize(source, filename=None): - """ Tokenize a Lisp file or string buffer into internal Hy objects. + """Tokenize a Lisp file or string buffer into internal Hy objects. Args: source (str): The source to tokenize. @@ -47,18 +45,23 @@ def tokenize(source, filename=None): Returns: typing.List[Object]: list of hy object models """ + from rply.errors import LexingError + from hy.lex.lexer import lexer from hy.lex.parser import parser - from rply.errors import LexingError + try: - return parser.parse(lexer.lex(source), - state=ParserState(source, filename)) + return parser.parse(lexer.lex(source), state=ParserState(source, filename)) except LexingError as e: pos = e.getsourcepos() - raise LexException("Could not identify the next token.", - None, filename, source, - max(pos.lineno, 1), - max(pos.colno, 1)) + raise LexException( + "Could not identify the next token.", + None, + filename, + source, + max(pos.lineno, 1), + max(pos.colno, 1), + ) except LexException as e: raise e @@ -67,30 +70,38 @@ def parse_one_thing(src_string): """Parse the first form from the string. Return it and the remainder of the string.""" import re + + from rply.errors import LexingError + from hy.lex.lexer import lexer from hy.lex.parser import parser - from rply.errors import LexingError + tokens = [] err = None for token in lexer.lex(src_string): tokens.append(token) try: - model, = parser.parse( - iter(tokens), - state=ParserState(src_string, filename=None)) + (model,) = parser.parse( + iter(tokens), state=ParserState(src_string, filename=None) + ) except (LexingError, LexException) as e: err = e else: - return model, src_string[re.match( - r'.+\n' * (model.end_line - 1) - + '.' * model.end_column, - src_string).end():] + return ( + model, + src_string[ + re.match( + r".+\n" * (model.end_line - 1) + "." * model.end_column, + src_string, + ).end() : + ], + ) if err: raise err raise ValueError("No form found") -mangle_delim = 'X' +mangle_delim = "X" def mangle(s): @@ -118,12 +129,19 @@ def mangle(s): => (hy.mangle '<--) "hyx_XlessHthan_signX__" """ + def unicode_char_to_hex(uchr): # Covert a unicode char to hex string, without prefix if len(uchr) == 1 and ord(uchr) < 128: - return format(ord(uchr), 'x') - return (uchr.encode('unicode-escape').decode('utf-8') - .lstrip('\\U').lstrip('\\u').lstrip('\\x').lstrip('0')) + return format(ord(uchr), "x") + return ( + uchr.encode("unicode-escape") + .decode("utf-8") + .lstrip("\\U") + .lstrip("\\u") + .lstrip("\\x") + .lstrip("0") + ) assert s s = str(s) @@ -132,8 +150,8 @@ def unicode_char_to_hex(uchr): return ".".join(mangle(x) if x else "" for x in s.split(".")) # Step 1: Remove and save leading underscores - s2 = s.lstrip('_') - leading_underscores = '_' * (len(s) - len(s2)) + s2 = s.lstrip("_") + leading_underscores = "_" * (len(s) - len(s2)) s = s2 # Step 2: Convert hyphens without introducing a new leading underscore @@ -141,21 +159,23 @@ def unicode_char_to_hex(uchr): # Step 3: Convert trailing `?` to leading `is_` if s.endswith("?"): - s = 'is_' + s[:-1] + s = "is_" + s[:-1] # Step 4: Convert invalid characters or reserved words if not isidentifier(leading_underscores + s): # Replace illegal characters with their Unicode character # names, or hexadecimal if they don't have one. - s = 'hyx_' + ''.join( - c - if c != mangle_delim and isidentifier('S' + c) - # We prepend the "S" because some characters aren't - # allowed at the start of an identifier. - else '{0}{1}{0}'.format(mangle_delim, - unicodedata.name(c, '').lower().replace('-', 'H').replace(' ', '_') - or 'U{}'.format(unicode_char_to_hex(c))) - for c in s) + s = "hyx_" + "".join( + c if c != mangle_delim and isidentifier("S" + c) + # We prepend the "S" because some characters aren't + # allowed at the start of an identifier. + else "{0}{1}{0}".format( + mangle_delim, + unicodedata.name(c, "").lower().replace("-", "H").replace(" ", "_") + or "U{}".format(unicode_char_to_hex(c)), + ) + for c in s + ) # Step 5: Add back leading underscores s = leading_underscores + s @@ -198,21 +218,23 @@ def unmangle(s): prefix = "" suffix = "" - m = re.fullmatch(r'(_+)(.*?)(_*)', s, re.DOTALL) + m = re.fullmatch(r"(_+)(.*?)(_*)", s, re.DOTALL) if m: prefix, s, suffix = m.groups() - if s.startswith('hyx_'): - s = re.sub('{0}(U)?([_a-z0-9H]+?){0}'.format(mangle_delim), - lambda mo: - chr(int(mo.group(2), base=16)) - if mo.group(1) - else unicodedata.lookup( - mo.group(2).replace('_', ' ').replace('H', '-').upper()), - s[len('hyx_'):]) - if s.startswith('is_'): - s = s[len("is_"):] + "?" - s = s.replace('_', '-') + if s.startswith("hyx_"): + s = re.sub( + "{0}(U)?([_a-z0-9H]+?){0}".format(mangle_delim), + lambda mo: chr(int(mo.group(2), base=16)) + if mo.group(1) + else unicodedata.lookup( + mo.group(2).replace("_", " ").replace("H", "-").upper() + ), + s[len("hyx_") :], + ) + if s.startswith("is_"): + s = s[len("is_") :] + "?" + s = s.replace("_", "-") return prefix + s + suffix @@ -302,12 +324,12 @@ def read_str(input): => (hy.eval (hy.read-str "(print 1)")) 1 - """ + """ return read(StringIO(str(input))) def isidentifier(x): - if x in ('True', 'False', 'None'): + if x in ("True", "False", "None"): return True if keyword.iskeyword(x): return False diff --git a/hy/lex/exceptions.py b/hy/lex/exceptions.py index c2e36801a..8f2d2c2be 100644 --- a/hy/lex/exceptions.py +++ b/hy/lex/exceptions.py @@ -2,7 +2,6 @@ class LexException(HySyntaxError): - @classmethod def from_lexer(cls, message, state, token): lineno = None @@ -24,12 +23,7 @@ def from_lexer(cls, message, state, token): lineno = lineno or 1 colno = colno or 1 - return cls(message, - None, - state.filename, - source, - lineno, - colno) + return cls(message, None, state.filename, source, lineno, colno) class PrematureEndOfInput(LexException): diff --git a/hy/lex/lexer.py b/hy/lex/lexer.py index dec6475c2..abd48195c 100755 --- a/hy/lex/lexer.py +++ b/hy/lex/lexer.py @@ -1,41 +1,43 @@ from rply import LexerGenerator - lg = LexerGenerator() # A regexp for something that should end a quoting/unquoting operator # i.e. a space or a closing brace/paren/curly -end_quote_set = r'\s\)\]\}' -end_quote = r'(?![%s])' % end_quote_set +end_quote_set = r"\s\)\]\}" +end_quote = r"(?![%s])" % end_quote_set identifier = r'[^()\[\]{}\'"\s;]+' -lg.add('LPAREN', r'\(') -lg.add('RPAREN', r'\)') -lg.add('LBRACKET', r'\[') -lg.add('RBRACKET', r'\]') -lg.add('LCURLY', r'\{') -lg.add('RCURLY', r'\}') -lg.add('HLCURLY', r'#\{') -lg.add('QUOTE', r'\'%s' % end_quote) -lg.add('QUASIQUOTE', r'`%s' % end_quote) -lg.add('UNQUOTESPLICE', r'~@%s' % end_quote) -lg.add('UNQUOTE', r'~%s' % end_quote) -lg.add('ANNOTATION', r'\^(?![=%s])' % end_quote_set) -lg.add('DISCARD', r'#_') -lg.add('HASHSTARS', r'#\*+') -lg.add('BRACKETSTRING', r'''(?x) +lg.add("LPAREN", r"\(") +lg.add("RPAREN", r"\)") +lg.add("LBRACKET", r"\[") +lg.add("RBRACKET", r"\]") +lg.add("LCURLY", r"\{") +lg.add("RCURLY", r"\}") +lg.add("HLCURLY", r"#\{") +lg.add("QUOTE", r"\'%s" % end_quote) +lg.add("QUASIQUOTE", r"`%s" % end_quote) +lg.add("UNQUOTESPLICE", r"~@%s" % end_quote) +lg.add("UNQUOTE", r"~%s" % end_quote) +lg.add("ANNOTATION", r"\^(?![=%s])" % end_quote_set) +lg.add("DISCARD", r"#_") +lg.add("HASHSTARS", r"#\*+") +lg.add( + "BRACKETSTRING", + r"""(?x) \# \[ ( [^\[\]]* ) \[ # Opening delimiter \n? # A single leading newline will be ignored ((?:\n|.)*?) # Content of the string \] \1 \] # Closing delimiter - ''') -lg.add('HASHOTHER', r'#%s' % identifier) + """, +) +lg.add("HASHOTHER", r"#%s" % identifier) # A regexp which matches incomplete strings, used to support # multi-line strings in the interpreter -partial_string = r'''(?x) +partial_string = r"""(?x) (?:u|r|ur|ru|b|br|rb|f|fr|rf)? # prefix " # start string (?: @@ -45,16 +47,16 @@ | \\u[0-9a-fA-F]{4} # or unicode escape | \\U[0-9a-fA-F]{8} # or long unicode escape )* # one or more times -''' +""" -lg.add('STRING', r'%s"' % partial_string) -lg.add('PARTIAL_STRING', partial_string) +lg.add("STRING", r'%s"' % partial_string) +lg.add("PARTIAL_STRING", partial_string) -lg.add('IDENTIFIER', identifier) +lg.add("IDENTIFIER", identifier) -lg.ignore(r';.*(?=\r|\n|$)') -lg.ignore(r'\s+') +lg.ignore(r";.*(?=\r|\n|$)") +lg.ignore(r"\s+") lexer = lg.build() diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 7e0b7680a..b7aa226b5 100755 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -3,17 +3,30 @@ from rply import ParserGenerator -from hy.models import (Bytes, Complex, Dict, Expression, FComponent, FString, - Float, Integer, Keyword, List, Set, String, Symbol) -from .lexer import lexer -from .exceptions import LexException, PrematureEndOfInput +from hy.models import ( + Bytes, + Complex, + Dict, + Expression, + FComponent, + Float, + FString, + Integer, + Keyword, + List, + Set, + String, + Symbol, +) +from .exceptions import LexException, PrematureEndOfInput +from .lexer import lexer -pg = ParserGenerator([rule.name for rule in lexer.rules] + ['$end']) +pg = ParserGenerator([rule.name for rule in lexer.rules] + ["$end"]) def sym(x): - return Symbol(x, from_parser = True) + return Symbol(x, from_parser=True) def set_boundaries(fun): @@ -29,11 +42,12 @@ def wrapped(state, p): ret.end_column = end.colno else: v = p[0].value - ret.end_line = start.lineno + v.count('\n') - ret.end_column = (len(v) - v.rindex('\n') - 1 - if '\n' in v - else start.colno + len(v) - 1) + ret.end_line = start.lineno + v.count("\n") + ret.end_column = ( + len(v) - v.rindex("\n") - 1 if "\n" in v else start.colno + len(v) - 1 + ) return ret + return wrapped @@ -47,6 +61,7 @@ def wrapped(state, p): ret.end_line = p[-1].end_line ret.end_column = p[-1].end_column return ret + return wrapped @@ -150,7 +165,9 @@ def term_hashstars(state, p): raise LexException.from_lexer( "Too many stars in `#*` construct (if you want to unpack a symbol " "beginning with a star, separate it with whitespace)", - state, p[0]) + state, + p[0], + ) return Expression([sym(s), p[1]]) @@ -202,7 +219,7 @@ def t_empty_list(state, p): def t_string(state, p): s = p[0].value # Detect any "f" prefix. - if s.startswith('f') or s.startswith('rf'): + if s.startswith("f") or s.startswith("rf"): return t_fstring(state, p) # Replace the single double quotes with triple double quotes to allow # embedded newlines. @@ -214,15 +231,14 @@ def t_string(state, p): state, p[0], ) - return (String(s) - if isinstance(s, str) - else Bytes(s)) + return String(s) if isinstance(s, str) else Bytes(s) + def t_fstring(state, p): s = p[0].value - assert s.startswith('f') or s.startswith('rf') + assert s.startswith("f") or s.startswith("rf") assert isinstance(s, str) - s = s.replace('f', '', 1) + s = s.replace("f", "", 1) # Replace the single double quotes with triple double quotes to allow # embedded newlines. try: @@ -237,6 +253,7 @@ def t_fstring(state, p): values = _format_string(state, p, s) return FString(values) + def _format_string(state, p, rest, allow_recursion=True): """ Produces a list of elements @@ -245,86 +262,85 @@ def _format_string(state, p, rest, allow_recursion=True): values = [] while True: - # Look for the next replacement field, and get the - # plain text before it. - match = re.search(r'\{\{?|\}\}?', rest) - if match: - literal_chars = rest[: match.start()] - if match.group() == '}': - raise LexException.from_lexer( - "f-string: single '}' is not allowed", - state, p[0]) - if match.group() in ('{{', '}}'): - # Doubled braces just add a single brace to the text. - literal_chars += match.group()[0] - rest = rest[match.end() :] - else: - literal_chars = rest - rest = "" - if literal_chars: - values.append(String(literal_chars)) - if not rest: - break - if match.group() != '{': - continue - - # Look for the end of the replacement field, allowing - # one more level of matched braces, but no deeper, and only - # if we can recurse. - match = re.match( - r'(?: \{ [^{}]* \} | [^{}]+ )* \}' - if allow_recursion - else r'[^{}]* \}', - rest, re.VERBOSE) - if not match: - raise LexException.from_lexer('f-string: mismatched braces', state, p[0]) - item = rest[: match.end() - 1] - rest = rest[match.end() :] - - # Parse the first form. - try: - from . import parse_one_thing - model, remainder = parse_one_thing(item) - f_expression = item[:-len(remainder)] - item = remainder - except LexException: - raise - except ValueError as e: - raise LexException.from_lexer("f-string: " + str(e), state, p[0]) - subnodes = [model] - - # Check for '=' debugging syntax, reproduce whitespace in output - eq_sign_match = re.match(r'\s*=\s*', item) - if eq_sign_match: - values.append(String(f_expression + eq_sign_match.group())) - item = item[eq_sign_match.end():] - else: - item = item.lstrip() - - # Look for a conversion character. - conversion = None - if item.startswith('!'): - conversion = item[1] - item = item[2:].lstrip() - - # Look for a format specifier. - if item.startswith(':'): - if allow_recursion: - format_spec = _format_string(state, p, - item[1:], - allow_recursion=False) - subnodes.extend(format_spec) - else: - subnodes.append(String(item[1:])) - elif item: - raise LexException.from_lexer( - "f-string: trailing junk in field", - state, p[0]) - elif eq_sign_match and not conversion: - # Python has a special default conversion in this case. - conversion = "r" - - values.append(FComponent(subnodes, conversion=conversion)) + # Look for the next replacement field, and get the + # plain text before it. + match = re.search(r"\{\{?|\}\}?", rest) + if match: + literal_chars = rest[: match.start()] + if match.group() == "}": + raise LexException.from_lexer( + "f-string: single '}' is not allowed", state, p[0] + ) + if match.group() in ("{{", "}}"): + # Doubled braces just add a single brace to the text. + literal_chars += match.group()[0] + rest = rest[match.end() :] + else: + literal_chars = rest + rest = "" + if literal_chars: + values.append(String(literal_chars)) + if not rest: + break + if match.group() != "{": + continue + + # Look for the end of the replacement field, allowing + # one more level of matched braces, but no deeper, and only + # if we can recurse. + match = re.match( + r"(?: \{ [^{}]* \} | [^{}]+ )* \}" if allow_recursion else r"[^{}]* \}", + rest, + re.VERBOSE, + ) + if not match: + raise LexException.from_lexer("f-string: mismatched braces", state, p[0]) + item = rest[: match.end() - 1] + rest = rest[match.end() :] + + # Parse the first form. + try: + from . import parse_one_thing + + model, remainder = parse_one_thing(item) + f_expression = item[: -len(remainder)] + item = remainder + except LexException: + raise + except ValueError as e: + raise LexException.from_lexer("f-string: " + str(e), state, p[0]) + subnodes = [model] + + # Check for '=' debugging syntax, reproduce whitespace in output + eq_sign_match = re.match(r"\s*=\s*", item) + if eq_sign_match: + values.append(String(f_expression + eq_sign_match.group())) + item = item[eq_sign_match.end() :] + else: + item = item.lstrip() + + # Look for a conversion character. + conversion = None + if item.startswith("!"): + conversion = item[1] + item = item[2:].lstrip() + + # Look for a format specifier. + if item.startswith(":"): + if allow_recursion: + format_spec = _format_string(state, p, item[1:], allow_recursion=False) + subnodes.extend(format_spec) + else: + subnodes.append(String(item[1:])) + elif item: + raise LexException.from_lexer( + "f-string: trailing junk in field", state, p[0] + ) + elif eq_sign_match and not conversion: + # Python has a special default conversion in this case. + conversion = "r" + + values.append(FComponent(subnodes, conversion=conversion)) return values @@ -335,16 +351,18 @@ def t_partial_string(state, p): raise PrematureEndOfInput.from_lexer("Partial string literal", state, p[0]) -bracket_string_re = next(r.re for r in lexer.rules if r.name == 'BRACKETSTRING') +bracket_string_re = next(r.re for r in lexer.rules if r.name == "BRACKETSTRING") + + @pg.production("string : BRACKETSTRING") @set_boundaries def t_bracket_string(state, p): m = bracket_string_re.match(p[0].value) delim, content = m.groups() - if delim == 'f' or delim.startswith('f-'): + if delim == "f" or delim.startswith("f-"): values = _format_string(state, p, content) return FString(values, brackets=delim) - return String(content, brackets = delim) + return String(content, brackets=delim) @pg.production("identifier : IDENTIFIER") @@ -359,10 +377,12 @@ def t_identifier(state, p): if "." in obj and symbol_like(obj.split(".", 1)[0]) is not None: # E.g., `5.attr` or `:foo.attr` raise LexException.from_lexer( - 'Cannot access attribute on anything other than a name (in ' - 'order to get attributes of expressions, use ' - '`(. )` or `(. )`)', - state, p[0]) + "Cannot access attribute on anything other than a name (in " + "order to get attributes of expressions, use " + "`(. )` or `(. )`)", + state, + p[0], + ) return sym(obj) @@ -375,11 +395,10 @@ def symbol_like(obj): except ValueError: pass - if '/' in obj: + if "/" in obj: try: - lhs, rhs = obj.split('/') - return Expression([sym('hy._Fraction'), Integer(lhs), - Integer(rhs)]) + lhs, rhs = obj.split("/") + return Expression([sym("hy._Fraction"), Integer(lhs), Integer(rhs)]) except ValueError: pass @@ -388,26 +407,25 @@ def symbol_like(obj): except ValueError: pass - if obj not in ('j', 'J'): + if obj not in ("j", "J"): try: return Complex(obj) except ValueError: pass if obj.startswith(":") and "." not in obj: - return Keyword(obj[1:], from_parser = True) + return Keyword(obj[1:], from_parser=True) @pg.error def error_handler(state, token): tokentype = token.gettokentype() - if tokentype == '$end': - raise PrematureEndOfInput.from_lexer("Premature end of input", state, - token) + if tokentype == "$end": + raise PrematureEndOfInput.from_lexer("Premature end of input", state, token) else: raise LexException.from_lexer( - "Ran into a %s where it wasn't expected." % tokentype, state, - token) + "Ran into a %s where it wasn't expected." % tokentype, state, token + ) parser = pg.build() diff --git a/hy/macros.py b/hy/macros.py index 091d712a7..c57501a04 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -1,58 +1,60 @@ -import sys -import os import builtins import importlib import inspect +import os import pkgutil +import sys import traceback from ast import AST from funcparserlib.parser import NoParseError +import hy.compiler from hy._compat import code_replace -from hy.model_patterns import whole -from hy.models import replace_hy_obj, Expression, Symbol, as_model, is_unpack +from hy.errors import ( + HyLanguageError, + HyMacroExpansionError, + HyRequireError, + HyTypeError, +) from hy.lex import mangle, unmangle -from hy.errors import (HyLanguageError, HyMacroExpansionError, HyTypeError, - HyRequireError) -import hy.compiler +from hy.model_patterns import whole +from hy.models import Expression, Symbol, as_model, is_unpack, replace_hy_obj EXTRA_MACROS = ["hy.core.result_macros", "hy.core.macros"] def macro(name): - """Decorator to define a macro called `name`. - """ + """Decorator to define a macro called `name`.""" return lambda fn: install_macro(name, fn, fn) -def pattern_macro(names, pattern, shadow = None): +def pattern_macro(names, pattern, shadow=None): pattern = whole(pattern) py_version_required = None if isinstance(names, tuple): py_version_required, names = names def dec(fn): - def wrapper_maker(name): def wrapper(hy_compiler, *args): - if (shadow and - any(is_unpack("iterable", x) for x in args)): + if shadow and any(is_unpack("iterable", x) for x in args): # Try a shadow function call with this name instead. - return Expression([ - Symbol('hy.pyops.' + name), - *args]).replace(hy_compiler.this) + return Expression([Symbol("hy.pyops." + name), *args]).replace( + hy_compiler.this + ) expr = hy_compiler.this root = unmangle(expr[0]) - if (py_version_required and - sys.version_info < py_version_required): - raise hy_compiler._syntax_error(expr, - '`{}` requires Python {} or later'.format( - root, - '.'.join(map(str, py_version_required)))) + if py_version_required and sys.version_info < py_version_required: + raise hy_compiler._syntax_error( + expr, + "`{}` requires Python {} or later".format( + root, ".".join(map(str, py_version_required)) + ), + ) try: parse_tree = pattern.parse(args) @@ -60,11 +62,14 @@ def wrapper(hy_compiler, *args): raise hy_compiler._syntax_error( expr[min(e.state.pos + 1, len(expr) - 1)], "parse error for pattern macro '{}': {}".format( - root, e.msg.replace("", "end of form"))) + root, e.msg.replace("", "end of form") + ), + ) return fn(hy_compiler, expr, root, *parse_tree) + return wrapper - for name in ([names] if isinstance(names, str) else names): + for name in [names] if isinstance(names, str) else names: install_macro(name, wrapper_maker(name), fn) return fn @@ -74,8 +79,7 @@ def wrapper(hy_compiler, *args): def install_macro(name, fn, module_of): name = mangle(name) fn = rename_function(fn, name) - (inspect.getmodule(module_of).__dict__ - .setdefault('__macros__', {})[name]) = fn + (inspect.getmodule(module_of).__dict__.setdefault("__macros__", {})[name]) = fn return fn @@ -107,8 +111,11 @@ def _get_filename(module): source_filename = _get_filename(source_module) target_filename = _get_filename(target_module) - return (source_filename and target_filename and - os.path.samefile(source_filename, target_filename)) + return ( + source_filename + and target_filename + and os.path.samefile(source_filename, target_filename) + ) def require(source_module, target_module, assignments, prefix=""): @@ -133,15 +140,16 @@ def require(source_module, target_module, assignments, prefix=""): if target_module is None: parent_frame = inspect.stack()[1][0] target_namespace = parent_frame.f_globals - target_module = target_namespace.get('__name__', None) + target_module = target_namespace.get("__name__", None) elif isinstance(target_module, str): target_module = importlib.import_module(target_module) target_namespace = target_module.__dict__ elif inspect.ismodule(target_module): target_namespace = target_module.__dict__ else: - raise HyTypeError('`target_module` is not a recognized type: {}'.format( - type(target_module))) + raise HyTypeError( + "`target_module` is not a recognized type: {}".format(type(target_module)) + ) # Let's do a quick check to make sure the source module isn't actually # the module being compiled (e.g. when `runpy` executes a module's code @@ -155,11 +163,10 @@ def require(source_module, target_module, assignments, prefix=""): try: if source_module.startswith("."): source_dirs = source_module.split(".") - target_dirs = (getattr(target_module, "__name__", target_module) - .split(".")) - while (len(source_dirs) > 1 - and source_dirs[0] == "" - and target_dirs): + target_dirs = getattr(target_module, "__name__", target_module).split( + "." + ) + while len(source_dirs) > 1 and source_dirs[0] == "" and target_dirs: source_dirs.pop(0) target_dirs.pop() package = ".".join(target_dirs + source_dirs[:-1]) @@ -169,25 +176,29 @@ def require(source_module, target_module, assignments, prefix=""): except ImportError as e: raise HyRequireError(e.args[0]).with_traceback(None) - source_macros = source_module.__dict__.setdefault('__macros__', {}) + source_macros = source_module.__dict__.setdefault("__macros__", {}) if not source_module.__macros__: if assignments != "ALL": for name, alias in assignments: try: - require(f"{source_module.__name__}.{mangle(name)}", - target_module, - "ALL", - prefix=alias) + require( + f"{source_module.__name__}.{mangle(name)}", + target_module, + "ALL", + prefix=alias, + ) except HyRequireError as e: - raise HyRequireError(f"Cannot import name '{name}'" - f" from '{source_module.__name__}'" - f" ({source_module.__file__})") + raise HyRequireError( + f"Cannot import name '{name}'" + f" from '{source_module.__name__}'" + f" ({source_module.__file__})" + ) return True else: return False - target_macros = target_namespace.setdefault('__macros__', {}) + target_macros = target_namespace.setdefault("__macros__", {}) if prefix: prefix += "." @@ -199,14 +210,17 @@ def require(source_module, target_module, assignments, prefix=""): for name, alias in name_assigns: _name = mangle(name) - alias = mangle('#' + prefix + unmangle(alias)[1:] - if unmangle(alias).startswith('#') - else prefix + alias) + alias = mangle( + "#" + prefix + unmangle(alias)[1:] + if unmangle(alias).startswith("#") + else prefix + alias + ) if _name in source_module.__macros__: target_macros[alias] = source_macros[_name] else: - raise HyRequireError('Could not require name {} from {}'.format( - _name, source_module)) + raise HyRequireError( + "Could not require name {} from {}".format(_name, source_module) + ) return True @@ -223,11 +237,11 @@ def load_macros(module): builtin_mod = importlib.import_module(builtin_mod_name) # This may overwrite macros in the module. - if hasattr(builtin_mod, '__macros__'): - module.__macros__.update(getattr(builtin_mod, '__macros__', {})) + if hasattr(builtin_mod, "__macros__"): + module.__macros__.update(getattr(builtin_mod, "__macros__", {})) -class MacroExceptions(): +class MacroExceptions: """wrap non ``HyLanguageError``'s in ``HyMacroExpansionError`` preserving stack trace used in lieu of ``@contextmanager`` to ensure stack trace contains only internal hy @@ -253,8 +267,9 @@ def __exit__(self, exc_type, exc_value, exc_traceback): filename = None source = None - exc_msg = ' '.join(traceback.format_exception_only( - sys.exc_info()[0], sys.exc_info()[1])) + exc_msg = " ".join( + traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1]) + ) msg = "expanding macro {}\n ".format(str(self.macro_tree[0])) msg += exc_msg @@ -304,15 +319,18 @@ def macroexpand(tree, module, compiler=None, once=False, result_ok=True): break fn = mangle(fn) - expr_modules = (([] if not hasattr(tree, 'module') else [tree.module]) - + [module]) + expr_modules = ([] if not hasattr(tree, "module") else [tree.module]) + [module] expr_modules.append(builtins) # Choose the first namespace with the macro. - m = next((mod.__macros__[fn] - for mod in expr_modules - if fn in getattr(mod, '__macros__', ())), - None) + m = next( + ( + mod.__macros__[fn] + for mod in expr_modules + if fn in getattr(mod, "__macros__", ()) + ), + None, + ) if not m: break @@ -344,7 +362,11 @@ def macroexpand_1(tree, module, compiler=None): def rename_function(f, new_name): """Create a copy of a function, but with a new name.""" f = type(f)( - code_replace(f.__code__, co_name = new_name), f.__globals__, - str(new_name), f.__defaults__, f.__closure__) + code_replace(f.__code__, co_name=new_name), + f.__globals__, + str(new_name), + f.__defaults__, + f.__closure__, + ) f.__dict__.update(f.__dict__) return f diff --git a/hy/model_patterns.py b/hy/model_patterns.py index eaeeb7106..f163d1f9a 100644 --- a/hy/model_patterns.py +++ b/hy/model_patterns.py @@ -1,13 +1,34 @@ "Parser combinators for pattern-matching Hy model trees." -from hy.models import Expression, Symbol, Keyword, String, List, Dict, Integer, Float, Complex, Bytes -from funcparserlib.parser import ( - some, skip, many, finished, a, Parser, NoParseError, State) +from collections import namedtuple from functools import reduce from itertools import repeat -from collections import namedtuple -from operator import add from math import isinf +from operator import add + +from funcparserlib.parser import ( + NoParseError, + Parser, + State, + a, + finished, + many, + skip, + some, +) + +from hy.models import ( + Bytes, + Complex, + Dict, + Expression, + Float, + Integer, + Keyword, + List, + String, + Symbol, +) FORM = some(lambda _: True) SYM = some(lambda x: isinstance(x, Symbol)) @@ -20,15 +41,18 @@ def sym(wanted): "Parse and skip the given symbol or keyword." return _sym(wanted, skip) + def keepsym(wanted): "Parse the given symbol or keyword." return _sym(wanted) -def _sym(wanted, f = lambda x: x): + +def _sym(wanted, f=lambda x: x): if wanted.startswith(":"): return f(a(Keyword(wanted[1:]))) return f(some(lambda x: x == Symbol(wanted))) + def whole(parsers): """Parse the parsers in the given list one after another, then expect the end of the input.""" @@ -38,45 +62,55 @@ def whole(parsers): return parsers[0] + finished >> (lambda x: x[:-1]) return reduce(add, parsers) + skip(finished) -def _grouped(group_type, parsers): return ( - some(lambda x: isinstance(x, group_type)) >> - (lambda x: group_type(whole(parsers).parse(x)).replace(x, recursive=False))) + +def _grouped(group_type, parsers): + return some(lambda x: isinstance(x, group_type)) >> ( + lambda x: group_type(whole(parsers).parse(x)).replace(x, recursive=False) + ) + def brackets(*parsers): "Parse the given parsers inside square brackets." return _grouped(List, parsers) + def braces(*parsers): "Parse the given parsers inside curly braces" return _grouped(Dict, parsers) + def pexpr(*parsers): "Parse the given parsers inside a parenthesized expression." return _grouped(Expression, parsers) + def dolike(head): "Parse a `do`-like form." return pexpr(sym(head), many(FORM)) + def notpexpr(*disallowed_heads): """Parse any object other than a hy.models.Expression beginning with a hy.models.Symbol equal to one of the disallowed_heads.""" disallowed_heads = list(map(Symbol, disallowed_heads)) - return some(lambda x: not ( - isinstance(x, Expression) and - x and - x[0] in disallowed_heads)) + return some( + lambda x: not (isinstance(x, Expression) and x and x[0] in disallowed_heads) + ) + def unpack(kind): "Parse an unpacking form, returning it unchanged." - return some(lambda x: - isinstance(x, Expression) + return some( + lambda x: isinstance(x, Expression) and len(x) > 0 - and x[0] == Symbol("unpack-" + kind)) + and x[0] == Symbol("unpack-" + kind) + ) + def times(lo, hi, parser): """Parse `parser` several times (`lo` to `hi`) in a row. `hi` can be float('inf'). The result is a list no matter the number of instances.""" + @Parser def f(tokens, s): result = [] @@ -85,15 +119,18 @@ def f(tokens, s): result.append(v) end = s.max try: - for _ in (repeat(1) if isinf(hi) else range(hi - lo)): + for _ in repeat(1) if isinf(hi) else range(hi - lo): (v, s) = parser.run(tokens, s) result.append(v) except NoParseError as e: end = e.state.max return result, State(s.pos, end) + return f -Tag = namedtuple('Tag', ['tag', 'value']) + +Tag = namedtuple("Tag", ["tag", "value"]) + def tag(tag_name, parser): """Matches the given parser and produces a named tuple `(Tag tag value)` diff --git a/hy/models.py b/hy/models.py index f4ac9dac8..53160d1cd 100644 --- a/hy/models.py +++ b/hy/models.py @@ -1,16 +1,18 @@ -from contextlib import contextmanager +import operator import re -from math import isnan, isinf -from hy import _initialize_env_var -from hy.errors import HyWrapperError +from contextlib import contextmanager from fractions import Fraction -import operator -from itertools import groupby from functools import reduce +from itertools import groupby +from math import isinf, isnan + from colorama import Fore +from hy import _initialize_env_var +from hy.errors import HyWrapperError + PRETTY = True -COLORED = _initialize_env_var('HY_COLORED_AST_OBJECTS', False) +COLORED = _initialize_env_var("HY_COLORED_AST_OBJECTS", False) @contextmanager @@ -49,8 +51,8 @@ class Object: `abc` starting at the first column would have `start_column` 1 and `end_column` 3. """ - properties = ["module", "_start_line", "_end_line", "_start_column", - "_end_column"] + + properties = ["module", "_start_line", "_end_line", "_start_column", "_end_column"] def replace(self, other, recursive=False): if isinstance(other, Object): @@ -58,7 +60,11 @@ def replace(self, other, recursive=False): if not hasattr(self, attr) and hasattr(other, attr): setattr(self, attr, getattr(other, attr)) else: - raise TypeError("Can't replace a non Hy object '{}' with a Hy object '{}'".format(repr(other), repr(self))) + raise TypeError( + "Can't replace a non Hy object '{}' with a Hy object '{}'".format( + repr(other), repr(self) + ) + ) return self @@ -95,8 +101,9 @@ def end_column(self, value): self._end_column = value def __repr__(self): - return (f"hy.models.{self.__class__.__name__}" - f"({super(Object, self).__repr__()})") + return ( + f"hy.models.{self.__class__.__name__}" f"({super(Object, self).__repr__()})" + ) def __eq__(self, other): if type(self) != type(other): @@ -111,6 +118,7 @@ def __hash__(self): _wrappers = {} _seen = set() + def as_model(x): """Recurisvely promote an object ``x`` into its canonical model form. @@ -163,9 +171,7 @@ def repr_indent(obj): def is_unpack(kind, x): - return (isinstance(x, Expression) - and len(x) > 0 - and x[0] == Symbol("unpack-" + kind)) + return isinstance(x, Expression) and len(x) > 0 and x[0] == Symbol("unpack-" + kind) class String(Object, str): @@ -183,10 +189,10 @@ def __new__(cls, s=None, brackets=None): return value def __repr__(self): - return 'hy.models.String({}{})'.format( + return "hy.models.String({}{})".format( super(Object, self).__repr__(), - '' if self.brackets is None else - f', brackets={self.brackets!r}') + "" if self.brackets is None else f", brackets={self.brackets!r}", + ) _wrappers[str] = String @@ -197,8 +203,10 @@ class Bytes(Object, bytes): Generic Hy Bytes object. It's either a ``bytes`` or a ``str``, depending on the Python version. """ + pass + _wrappers[bytes] = Bytes @@ -207,16 +215,18 @@ class Symbol(Object, str): Hy Symbol. Basically a string. """ - def __new__(cls, s, from_parser = False): + def __new__(cls, s, from_parser=False): s = str(s) if not from_parser: # Check that the symbol is syntactically legal. from hy.lex.lexer import identifier from hy.lex.parser import symbol_like + if not re.fullmatch(identifier, s) or symbol_like(s) is not None: - raise ValueError(f'Syntactically illegal symbol: {s!r}') + raise ValueError(f"Syntactically illegal symbol: {s!r}") return super().__new__(cls, s) + _wrappers[bool] = lambda x: Symbol("True") if x else Symbol("False") _wrappers[type(None)] = lambda foo: Symbol("None") @@ -224,13 +234,14 @@ def __new__(cls, s, from_parser = False): class Keyword(Object): """Generic Hy Keyword object.""" - __slots__ = ['name'] + __slots__ = ["name"] - def __init__(self, value, from_parser = False): + def __init__(self, value, from_parser=False): value = str(value) if not from_parser: # Check that the keyword is syntactically legal. from hy.lex.lexer import identifier + if value and (not re.fullmatch(identifier, value) or "." in value): raise ValueError(f'Syntactically illegal keyword: {":" + value!r}') self.name = value @@ -261,6 +272,7 @@ def __bool__(self): def __call__(self, data, default=_sentinel): from hy.lex import mangle + try: return data[mangle(self.name)] except KeyError: @@ -271,19 +283,25 @@ def __call__(self, data, default=_sentinel): # __getstate__ and __setstate__ are required for Pickle protocol # 0, because we have __slots__. def __getstate__(self): - return {k: getattr(self, k) + return { + k: getattr(self, k) for k in self.properties + self.__slots__ - if hasattr(self, k)} + if hasattr(self, k) + } + def __setstate__(self, state): for k, v in state.items(): setattr(self, k, v) + def strip_digit_separators(number): # Don't strip a _ or , if it's the first character, as _42 and # ,42 aren't valid numbers - return (number[0] + number[1:].replace("_", "").replace(",", "") - if isinstance(number, str) and len(number) > 1 - else number) + return ( + number[0] + number[1:].replace("_", "").replace(",", "") + if isinstance(number, str) and len(number) > 1 + else number + ) class Integer(Object, int): @@ -332,6 +350,7 @@ def __new__(cls, num, *args, **kwargs): check_inf_nan_cap(num, value) return value + _wrappers[float] = Float @@ -343,9 +362,7 @@ class Complex(Object, complex): def __new__(cls, real, imag=0, *args, **kwargs): if isinstance(real, str): - value = super().__new__( - cls, strip_digit_separators(real) - ) + value = super().__new__(cls, strip_digit_separators(real)) p1, _, p2 = real.lstrip("+-").replace("-", "+").partition("+") check_inf_nan_cap(p1, value.imag if "j" in p1 else value.real) if p2: @@ -353,6 +370,7 @@ def __new__(cls, real, imag=0, *args, **kwargs): return value return super().__new__(cls, real, imag) + _wrappers[complex] = Complex @@ -369,8 +387,9 @@ def replace(self, other, recursive=True): return self def __add__(self, other): - return self.__class__(super().__add__( - tuple(other) if isinstance(other, list) else other)) + return self.__class__( + super().__add__(tuple(other) if isinstance(other, list) else other) + ) def __getslice__(self, start, end): return self.__class__(super().__getslice__(start, end)) @@ -394,12 +413,14 @@ def __str__(self): def _pretty_str(self): with pretty(): if self: - return self._colored("hy.models.{}{}\n {}{}".format( - self._colored(self.__class__.__name__), - self._colored("(["), - self._colored(",\n ").join(map(repr_indent, self)), - self._colored("])"), - )) + return self._colored( + "hy.models.{}{}\n {}{}".format( + self._colored(self.__class__.__name__), + self._colored("(["), + self._colored(",\n ").join(map(repr_indent, self)), + self._colored("])"), + ) + ) else: return self._colored(f"hy.models.{self.__class__.__name__}()") @@ -410,6 +431,7 @@ class FComponent(Sequence): The first node in the contained sequence is the value being formatted, the rest of the sequence contains the nodes in the format spec (if any). """ + def __new__(cls, s=None, conversion=None): value = super().__new__(cls, s) value.conversion = conversion @@ -422,9 +444,9 @@ def replace(self, other, recursive=True): return self def __repr__(self): - return 'hy.models.FComponent({})'.format( - super(Object, self).__repr__() + - ', conversion=' + repr(self.conversion)) + return "hy.models.FComponent({})".format( + super(Object, self).__repr__() + ", conversion=" + repr(self.conversion) + ) def _string_in_node(string, node): @@ -441,15 +463,22 @@ class FString(Sequence): Generic Hy F-String object, for smarter f-string handling. Mimics ast.JoinedStr, but using String and FComponent. """ + def __new__(cls, s=None, brackets=None): - value = super().__new__(cls, - # Join adjacent string nodes for the sake of equality - # testing. - (node - for is_string, components in groupby(s, - lambda x: isinstance(x, String)) - for node in ([reduce(lambda left, right: String(left + right), components)] - if is_string else components))) + value = super().__new__( + cls, + # Join adjacent string nodes for the sake of equality + # testing. + ( + node + for is_string, components in groupby(s, lambda x: isinstance(x, String)) + for node in ( + [reduce(lambda left, right: String(left + right), components)] + if is_string + else components + ) + ), + ) if brackets is not None and _string_in_node(f"]{brackets}]", value): raise ValueError(f"Syntactically illegal bracket string: {s!r}") @@ -458,15 +487,18 @@ def __new__(cls, s=None, brackets=None): def __repr__(self): return self._suffixize(super().__repr__()) + def __str__(self): return self._suffixize(super().__str__()) + def _suffixize(self, x): if self.brackets is None: - return x - return '{}{}brackets={!r})'.format( - x[:-1], # Clip off the final close paren - '' if x[-2] == '(' else ', ', - self.brackets) + return x + return "{}{}brackets={!r})".format( + x[:-1], # Clip off the final close paren + "" if x[-2] == "(" else ", ", + self.brackets, + ) class List(Sequence): @@ -474,7 +506,6 @@ class List(Sequence): def recwrap(f): - def lambda_to_return(l): _seen.add(id(l)) try: @@ -484,6 +515,7 @@ def lambda_to_return(l): return lambda_to_return + _wrappers[FComponent] = recwrap(FComponent) _wrappers[FString] = lambda fstr: FString( (as_model(x) for x in fstr), brackets=fstr.brackets @@ -497,25 +529,29 @@ class Dict(Sequence, _ColoredModel): """ Dict (just a representation of a dict) """ + color = Fore.GREEN def _pretty_str(self): with pretty(): if self: pairs = [] - for k, v in zip(self[::2],self[1::2]): + for k, v in zip(self[::2], self[1::2]): k, v = repr_indent(k), repr_indent(v) pairs.append( - ("{0}{c}\n {1}\n " - if '\n' in k+v - else "{0}{c} {1}").format(k, v, c=self._colored(','))) + ("{0}{c}\n {1}\n " if "\n" in k + v else "{0}{c} {1}").format( + k, v, c=self._colored(",") + ) + ) if len(self) % 2 == 1: - pairs.append("{} {}\n".format( - repr_indent(self[-1]), self._colored("# odd"))) + pairs.append( + "{} {}\n".format(repr_indent(self[-1]), self._colored("# odd")) + ) return "{}\n {}{}".format( self._colored("hy.models.Dict(["), - "{c}\n ".format(c=self._colored(',')).join(pairs), - self._colored("])")) + "{c}\n ".format(c=self._colored(",")).join(pairs), + self._colored("])"), + ) else: return self._colored("hy.models.Dict()") @@ -528,6 +564,7 @@ def values(self): def items(self): return list(zip(self.keys(), self.values())) + def _dict_wrapper(d): _seen.add(id(d)) try: @@ -535,6 +572,7 @@ def _dict_wrapper(d): finally: _seen.remove(id(d)) + _wrappers[Dict] = recwrap(Dict) _wrappers[dict] = _dict_wrapper @@ -543,18 +581,23 @@ class Expression(Sequence): """ Hy S-Expression. Basically just a list. """ + color = Fore.YELLOW + _wrappers[Expression] = recwrap(Expression) _wrappers[Fraction] = lambda e: Expression( - [Symbol("hy._Fraction"), as_model(e.numerator), as_model(e.denominator)]) + [Symbol("hy._Fraction"), as_model(e.numerator), as_model(e.denominator)] +) class Set(Sequence): """ Hy set (just a representation of a set) """ + color = Fore.RED + _wrappers[Set] = recwrap(Set) _wrappers[set] = recwrap(Set) diff --git a/hy/scoping.py b/hy/scoping.py index d2cae1bed..86edfe2c4 100644 --- a/hy/scoping.py +++ b/hy/scoping.py @@ -237,11 +237,12 @@ def add(self, target, new_name=None): raise ValueError("cannot specify name for compound targets") if isinstance(target, List): return List(map(self.add, target)).replace(target) - if (isinstance(target, Expression) and target and - target[0] in (Symbol(","), Symbol("unpack-iterable"))): - return Expression([target[0], *map(self.add, target[1:])]).replace( - target - ) + if ( + isinstance(target, Expression) + and target + and target[0] in (Symbol(","), Symbol("unpack-iterable")) + ): + return Expression([target[0], *map(self.add, target[1:])]).replace(target) raise ValueError(f"invalid binding target: {type(target)}") @@ -291,8 +292,11 @@ def define(self, name): self.defined.add(name) def define_nonlocal(self, node, root): - ((self.nonlocal_vars if root == "nonlocal" else self.defined) - .update(node.names)) + ( + (self.nonlocal_vars if root == "nonlocal" else self.defined).update( + node.names + ) + ) for n in self.seen: if n.name in node.names: raise SyntaxError( diff --git a/setup.py b/setup.py index 8758e3dc2..357368a1c 100755 --- a/setup.py +++ b/setup.py @@ -2,10 +2,9 @@ import os -from setuptools import find_packages, setup -import fastentrypoints # Monkey-patches setuptools. - +import fastentrypoints # Monkey-patches setuptools. from get_version import __version__ +from setuptools import find_packages, setup os.chdir(os.path.split(os.path.abspath(__file__))[0]) @@ -19,39 +18,37 @@ name=PKG, version=__version__, install_requires=[ - 'rply>=0.7.7', - 'funcparserlib>=1.0.0a0', - 'colorama', + "rply>=0.7.7", + "funcparserlib>=1.0.0a0", + "colorama", 'astor>=0.8 ; python_version < "3.9"', ], - python_requires = '>= 3.7, < 3.11', + python_requires=">= 3.7, < 3.11", entry_points={ - 'console_scripts': [ - 'hy = hy.cmdline:hy_main', - 'hy3 = hy.cmdline:hy_main', - 'hyc = hy.cmdline:hyc_main', - 'hyc3 = hy.cmdline:hyc_main', - 'hy2py = hy.cmdline:hy2py_main', - 'hy2py3 = hy.cmdline:hy2py_main', + "console_scripts": [ + "hy = hy.cmdline:hy_main", + "hy3 = hy.cmdline:hy_main", + "hyc = hy.cmdline:hyc_main", + "hyc3 = hy.cmdline:hyc_main", + "hy2py = hy.cmdline:hy2py_main", + "hy2py3 = hy.cmdline:hy2py_main", ] }, - packages=find_packages(exclude=['tests*']), + packages=find_packages(exclude=["tests*"]), package_data={ - 'hy': ['*.hy', '__pycache__/*'], - 'hy.contrib': ['*.hy', '__pycache__/*'], - 'hy.core': ['*.hy', '__pycache__/*'], - 'hy.extra': ['*.hy', '__pycache__/*'], + "hy": ["*.hy", "__pycache__/*"], + "hy.contrib": ["*.hy", "__pycache__/*"], + "hy.core": ["*.hy", "__pycache__/*"], + "hy.extra": ["*.hy", "__pycache__/*"], }, - data_files=[ - ('get_version', ['get_version.py']) - ], + data_files=[("get_version", ["get_version.py"])], author="Paul Tagliamonte", author_email="tag@pault.ag", long_description=long_description, - description='Lisp and Python love each other.', + description="Lisp and Python love each other.", license="Expat", url="http://hylang.org/", - platforms=['any'], + platforms=["any"], classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", @@ -72,5 +69,5 @@ project_urls={ "Documentation": "https://docs.hylang.org/", "Source": "https://github.com/hylang/hy", - } + }, ) diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index fae8297cb..ee1e545f5 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -1,18 +1,17 @@ +import ast + +import pytest + from hy.compiler import hy_compile, hy_eval -from hy.errors import HyLanguageError, HyError +from hy.errors import HyError, HyLanguageError from hy.lex import hy_parse from hy.lex.exceptions import LexException, PrematureEndOfInput -import ast -import pytest - def _ast_spotcheck(arg, root, secondary): if "." in arg: local, full = arg.split(".", 1) - return _ast_spotcheck(full, - getattr(root, local), - getattr(secondary, local)) + return _ast_spotcheck(full, getattr(root, local), getattr(secondary, local)) assert getattr(root, arg) == getattr(secondary, arg) @@ -40,11 +39,12 @@ def s(x): def test_ast_bad_type(): "Make sure AST breakage can happen" + class C: pass with pytest.raises(TypeError): - hy_compile(C(), __name__, filename='', source='') + hy_compile(C(), __name__, filename="", source="") def test_empty_expr(): @@ -166,11 +166,11 @@ def test_ast_good_assert(): """Make sure AST can compile valid asserts. Asserts may or may not include a label.""" can_compile("(assert 1)") - can_compile("(assert 1 \"Assert label\")") - can_compile("(assert 1 (+ \"spam \" \"eggs\"))") + can_compile('(assert 1 "Assert label")') + can_compile('(assert 1 (+ "spam " "eggs"))') can_compile("(assert 1 12345)") can_compile("(assert 1 None)") - can_compile("(assert 1 (+ 2 \"incoming eggsception\"))") + can_compile('(assert 1 (+ 2 "incoming eggsception"))') def test_ast_bad_assert(): @@ -209,8 +209,8 @@ def test_ast_good_defclass(): can_compile("(defclass a)") can_compile("(defclass a [])") can_compile("(defclass a [] None 42)") - can_compile("(defclass a [] None \"test\")") - can_compile("(defclass a [] None (print \"foo\"))") + can_compile('(defclass a [] None "test")') + can_compile('(defclass a [] None (print "foo"))') def test_ast_good_defclass_with_metaclass(): @@ -282,16 +282,18 @@ def test_ast_require(): def test_ast_import_require_dotted(): """As in Python, it should be a compile-time error to attempt to -import a dotted name.""" + import a dotted name.""" cant_compile("(import spam [foo.bar])") cant_compile("(require spam [foo.bar])") def test_ast_multi_require(): # https://github.com/hylang/hy/issues/1903 - x = can_compile("""(require + x = can_compile( + """(require tests.resources.tlib [qplah] - tests.resources.macros [test-macro])""") + tests.resources.macros [test-macro])""" + ) assert sum(1 for stmt in x.body if isinstance(stmt, ast.Expr)) == 2 dump = ast.dump(x) assert "qplah" in dump @@ -349,66 +351,66 @@ def test_nullary_break_continue(): def test_ast_expression_basics(): - """ Ensure basic AST expression conversion works. """ + """Ensure basic AST expression conversion works.""" code = can_compile("(foo bar)").body[0] - tree = ast.Expr(value=ast.Call( - func=ast.Name( - id="foo", - ctx=ast.Load(), - ), - args=[ - ast.Name(id="bar", ctx=ast.Load()) - ], - keywords=[], - starargs=None, - kwargs=None, - )) + tree = ast.Expr( + value=ast.Call( + func=ast.Name( + id="foo", + ctx=ast.Load(), + ), + args=[ast.Name(id="bar", ctx=ast.Load())], + keywords=[], + starargs=None, + kwargs=None, + ) + ) _ast_spotcheck("value.func.id", code, tree) def test_ast_anon_fns_basics(): - """ Ensure anon fns work. """ + """Ensure anon fns work.""" code = can_compile("(fn [x] (* x x))").body[0].value assert type(code) == ast.Lambda - code = can_compile("(fn [x] (print \"multiform\") (* x x))").body[0] + code = can_compile('(fn [x] (print "multiform") (* x x))').body[0] assert type(code) == ast.FunctionDef can_compile("(fn [x])") cant_compile("(fn)") def test_ast_non_decoratable(): - """ Ensure decorating garbage breaks """ + """Ensure decorating garbage breaks""" cant_compile("(with-decorator (foo) (* x x))") def test_ast_lambda_lists(): """Ensure the compiler chokes on invalid lambda-lists""" - cant_compile('(fn [[a b c]] a)') - cant_compile('(fn [[1 2]] (list 1 2))') + cant_compile("(fn [[a b c]] a)") + cant_compile("(fn [[1 2]] (list 1 2))") def test_ast_print(): - code = can_compile("(print \"foo\")").body[0] + code = can_compile('(print "foo")').body[0] assert type(code.value) == ast.Call def test_ast_tuple(): - """ Ensure tuples work. """ + """Ensure tuples work.""" code = can_compile("(, 1 2 3)").body[0].value assert type(code) == ast.Tuple def test_lambda_list_keywords_rest(): - """ Ensure we can compile functions with lambda list keywords.""" + """Ensure we can compile functions with lambda list keywords.""" can_compile("(fn [x #* xs] (print xs))") cant_compile("(fn [x #* xs #* ys] (print xs))") can_compile("(fn [[a None] #* xs] (print xs))") def test_lambda_list_keywords_kwargs(): - """ Ensure we can compile functions with #** kwargs.""" + """Ensure we can compile functions with #** kwargs.""" can_compile("(fn [x #** kw] (list x kw))") cant_compile("(fn [x #** xs #** ys] (list x xs ys))") can_compile("(fn [[x None] #** kw] (list x kw))") @@ -417,18 +419,17 @@ def test_lambda_list_keywords_kwargs(): def test_lambda_list_keywords_kwonly(): kwonly_demo = "(fn [* a [b 2]] (print 1) (print a b))" code = can_compile(kwonly_demo) - for i, kwonlyarg_name in enumerate(('a', 'b')): + for i, kwonlyarg_name in enumerate(("a", "b")): assert kwonlyarg_name == code.body[0].args.kwonlyargs[i].arg assert code.body[0].args.kw_defaults[0] is None assert code.body[0].args.kw_defaults[1].n == 2 def test_lambda_list_keywords_mixed(): - """ Ensure we can mix them up.""" + """Ensure we can mix them up.""" can_compile("(fn [x #* xs #** kw] (list x xs kw))") - cant_compile("(fn [x #* xs &fasfkey {bar \"baz\"}])") - can_compile("(fn [x #* xs kwoxs #** kwxs]" - " (list x xs kwxs kwoxs))") + cant_compile('(fn [x #* xs &fasfkey {bar "baz"}])') + can_compile("(fn [x #* xs kwoxs #** kwxs]" " (list x xs kwxs kwoxs))") def test_missing_keyword_argument_value(): @@ -444,7 +445,9 @@ def test_ast_unicode_strings(): def _compile_string(s): hy_s = hy.models.String(s) - code = hy_compile([hy_s], __name__, filename='', source=s, import_stdlib=False) + code = hy_compile( + [hy_s], __name__, filename="", source=s, import_stdlib=False + ) # We put hy_s in a list so it isn't interpreted as a docstring. # code == ast.Module(body=[ast.Expr(value=ast.List(elts=[ast.Str(s=xxx)]))]) @@ -475,23 +478,33 @@ def test_format_string(): def test_ast_bracket_string(): - assert s(r'#[[empty delims]]') == 'empty delims' - assert s(r'#[my delim[fizzle]my delim]') == 'fizzle' - assert s(r'#[[]]') == '' - assert s(r'#[my delim[]my delim]') == '' - assert type(s('#[X[hello]X]')) is str - assert s(r'#[X[raw\nstring]X]') == 'raw\\nstring' - assert s(r'#[foozle[aa foozli bb ]foozle]') == 'aa foozli bb ' - assert s(r'#[([unbalanced](]') == 'unbalanced' - assert s(r'#[(1💯@)} {a![hello world](1💯@)} {a!]') == 'hello world' - assert (s(r'''#[X[ + assert s(r"#[[empty delims]]") == "empty delims" + assert s(r"#[my delim[fizzle]my delim]") == "fizzle" + assert s(r"#[[]]") == "" + assert s(r"#[my delim[]my delim]") == "" + assert type(s("#[X[hello]X]")) is str + assert s(r"#[X[raw\nstring]X]") == "raw\\nstring" + assert s(r"#[foozle[aa foozli bb ]foozle]") == "aa foozli bb " + assert s(r"#[([unbalanced](]") == "unbalanced" + assert s(r"#[(1💯@)} {a![hello world](1💯@)} {a!]") == "hello world" + assert ( + s( + r"""#[X[ Remove the leading newline, please. -]X]''') == 'Remove the leading newline, please.\n') - assert (s(r'''#[X[ +]X]""" + ) + == "Remove the leading newline, please.\n" + ) + assert ( + s( + r"""#[X[ Only one leading newline should be removed. -]X]''') == '\n\nOnly one leading newline should be removed.\n') +]X]""" + ) + == "\n\nOnly one leading newline should be removed.\n" + ) def test_compile_error(): @@ -531,7 +544,7 @@ def test_attribute_empty(): cant_compile("foo.") cant_compile(".foo") cant_compile('"bar".foo') - cant_compile('[2].foo') + cant_compile("[2].foo") def test_bad_setv(): @@ -541,7 +554,7 @@ def test_bad_setv(): def test_defn(): """Ensure that defn works correctly in various corner cases""" - cant_compile("(defn \"hy\" [] 1)") + cant_compile('(defn "hy" [] 1)') cant_compile("(defn :hy [] 1)") can_compile("(defn &hy [] 1)") cant_compile('(defn hy "foo")') @@ -551,13 +564,15 @@ def test_setv_builtins(): """Ensure that assigning to a builtin fails, unless in a class""" cant_compile("(setv None 42)") can_compile("(defclass A [] (defn get [self] 42))") - can_compile(""" + can_compile( + """ (defclass A [] (defn get [self] 42) (defclass B [] (defn get [self] 42)) (defn if [self] 0)) - """) + """ + ) def test_top_level_unquote(): @@ -599,12 +614,14 @@ def test_eval_generator_with_return(): def test_futures_imports(): """Make sure __future__ imports go first.""" hy_ast = can_compile( - '(import __future__ [print_function])' - '(import sys)' - '(setv some [1 2])' - '(print (cut some 1 None))') + "(import __future__ [print_function])" + "(import sys)" + "(setv some [1 2])" + "(print (cut some 1 None))" + ) + + assert hy_ast.body[0].module == "__future__" - assert hy_ast.body[0].module == '__future__' def test_inline_python(): can_compile('(py "1 + 1")') @@ -622,20 +639,19 @@ def test_bad_tag_macros(): def test_models_accessible(): # https://github.com/hylang/hy/issues/1045 - can_eval('hy.models.Symbol') - can_eval('hy.models.List') - can_eval('hy.models.Dict') + can_eval("hy.models.Symbol") + can_eval("hy.models.List") + can_eval("hy.models.Dict") def test_module_prelude(): """Make sure the hy prelude appears at the top of a compiled module.""" - hy_ast = can_compile('', import_stdlib=True) + hy_ast = can_compile("", import_stdlib=True) assert len(hy_ast.body) == 1 assert isinstance(hy_ast.body[0], ast.Import) - assert hy_ast.body[0].module == 'hy' + assert hy_ast.body[0].module == "hy" - hy_ast = can_compile('(setv flag (- hy.models.Symbol 1))', - import_stdlib=True) + hy_ast = can_compile("(setv flag (- hy.models.Symbol 1))", import_stdlib=True) assert len(hy_ast.body) == 2 assert isinstance(hy_ast.body[0], ast.Import) - assert hy_ast.body[0].module == 'hy' + assert hy_ast.body[0].module == "hy" diff --git a/tests/compilers/test_compiler.py b/tests/compilers/test_compiler.py index a56cec092..64fd2a01e 100644 --- a/tests/compilers/test_compiler.py +++ b/tests/compilers/test_compiler.py @@ -1,8 +1,8 @@ import ast +import types from hy import compiler -from hy.models import Expression, List, Symbol, Integer -import types +from hy.models import Expression, Integer, List, Symbol def make_expression(*args): @@ -18,11 +18,8 @@ def test_compiler_bare_names(): """ Check that the compiler doesn't drop bare names from code branches """ - e = make_expression(Symbol("do"), - Symbol("a"), - Symbol("b"), - Symbol("c")) - ret = compiler.HyASTCompiler(types.ModuleType('test')).compile(e) + e = make_expression(Symbol("do"), Symbol("a"), Symbol("b"), Symbol("c")) + ret = compiler.HyASTCompiler(types.ModuleType("test")).compile(e) # We expect two statements and a final expr. @@ -44,17 +41,16 @@ def test_compiler_yield_return(): should not generate a return statement. From 3.3 onwards a return value should be generated. """ - e = make_expression(Symbol("fn"), - List(), - Expression([Symbol("yield"), - Integer(2)]), - Expression([Symbol("+"), - Integer(1), - Integer(1)])) - ret = compiler.HyASTCompiler(types.ModuleType('test')).compile_atom(e) + e = make_expression( + Symbol("fn"), + List(), + Expression([Symbol("yield"), Integer(2)]), + Expression([Symbol("+"), Integer(1), Integer(1)]), + ) + ret = compiler.HyASTCompiler(types.ModuleType("test")).compile_atom(e) assert len(ret.stmts) == 1 - stmt, = ret.stmts + (stmt,) = ret.stmts assert isinstance(stmt, ast.FunctionDef) body = stmt.body assert len(body) == 2 diff --git a/tests/importer/test_importer.py b/tests/importer/test_importer.py index 0e8b974a0..99373a863 100644 --- a/tests/importer/test_importer.py +++ b/tests/importer/test_importer.py @@ -1,60 +1,59 @@ -import sys import ast -import runpy import importlib - -from pathlib import Path +import runpy +import sys from fractions import Fraction from importlib import reload +from pathlib import Path import pytest import hy -from hy.lex import hy_parse +from hy.compiler import hy_compile, hy_eval from hy.errors import HyLanguageError, hy_exc_handler -from hy.lex.exceptions import PrematureEndOfInput -from hy.compiler import hy_eval, hy_compile from hy.importer import HyLoader +from hy.lex import hy_parse +from hy.lex.exceptions import PrematureEndOfInput def test_basics(): "Make sure the basics of the importer work" - resources_mod = importlib.import_module('tests.resources') - assert hasattr(resources_mod, 'kwtest') + resources_mod = importlib.import_module("tests.resources") + assert hasattr(resources_mod, "kwtest") - bin_mod = importlib.import_module('tests.resources.bin') - assert hasattr(bin_mod, '_null_fn_for_import_test') + bin_mod = importlib.import_module("tests.resources.bin") + assert hasattr(bin_mod, "_null_fn_for_import_test") def test_runpy(): # `runpy` won't update cached bytecode. It's not clear if that's # intentional. - basic_ns = runpy.run_path('tests/resources/importer/basic.hy') - assert 'square' in basic_ns + basic_ns = runpy.run_path("tests/resources/importer/basic.hy") + assert "square" in basic_ns - main_ns = runpy.run_path('tests/resources/bin') - assert main_ns['visited_main'] == 1 + main_ns = runpy.run_path("tests/resources/bin") + assert main_ns["visited_main"] == 1 del main_ns - main_ns = runpy.run_module('tests.resources.bin') - assert main_ns['visited_main'] == 1 + main_ns = runpy.run_module("tests.resources.bin") + assert main_ns["visited_main"] == 1 with pytest.raises(IOError): - runpy.run_path('tests/resources/foobarbaz.py') + runpy.run_path("tests/resources/foobarbaz.py") def test_stringer(): - _ast = hy_compile(hy_parse("(defn square [x] (* x x))"), __name__, import_stdlib=False) + _ast = hy_compile( + hy_parse("(defn square [x] (* x x))"), __name__, import_stdlib=False + ) assert type(_ast.body[0]) == ast.FunctionDef def test_imports(): - testLoader = HyLoader( - "tests.resources.importer.a", - "tests/resources/importer/a.hy") + testLoader = HyLoader("tests.resources.importer.a", "tests/resources/importer/a.hy") spec = importlib.util.spec_from_loader(testLoader.name, testLoader) mod = importlib.util.module_from_spec(spec) @@ -68,56 +67,57 @@ def test_import_error_reporting(): "Make sure that (import) reports errors correctly." with pytest.raises(HyLanguageError): - hy_compile(hy_parse("(import \"sys\")"), __name__) + hy_compile(hy_parse('(import "sys")'), __name__) def test_import_error_cleanup(): "Failed initial imports should not leave dead modules in `sys.modules`." with pytest.raises(hy.errors.HyMacroExpansionError): - importlib.import_module('tests.resources.fails') + importlib.import_module("tests.resources.fails") - assert 'tests.resources.fails' not in sys.modules + assert "tests.resources.fails" not in sys.modules -@pytest.mark.skipif(sys.dont_write_bytecode, - reason="Bytecode generation is suppressed") +@pytest.mark.skipif(sys.dont_write_bytecode, reason="Bytecode generation is suppressed") def test_import_autocompiles(tmp_path): "Test that (import) byte-compiles the module." - p = tmp_path / 'mymodule.hy' + p = tmp_path / "mymodule.hy" p.write_text('(defn pyctest [s] (+ "X" s "Y"))') def import_from_path(path): - spec = importlib.util.spec_from_file_location('mymodule', path) + spec = importlib.util.spec_from_file_location("mymodule", path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module - assert import_from_path(p).pyctest('flim') == 'XflimY' + assert import_from_path(p).pyctest("flim") == "XflimY" assert Path(importlib.util.cache_from_source(p)).exists() # Try running the bytecode. assert ( - import_from_path(importlib.util.cache_from_source(p)) - .pyctest('flam') - == 'XflamY') + import_from_path(importlib.util.cache_from_source(p)).pyctest("flam") + == "XflamY" + ) + def test_eval(): def eval_str(s): - return hy_eval(hy.read_str(s), filename='', source=s) - - assert eval_str('[1 2 3]') == [1, 2, 3] - assert eval_str('{"dog" "bark" "cat" "meow"}') == { - 'dog': 'bark', 'cat': 'meow'} - assert eval_str('(, 1 2 3)') == (1, 2, 3) - assert eval_str('#{3 1 2}') == {1, 2, 3} - assert eval_str('1/2') == Fraction(1, 2) - assert eval_str('(.strip " fooooo ")') == 'fooooo' - assert eval_str( - '(if True "this is if true" "this is if false")') == "this is if true" - assert eval_str('(lfor num (range 100) :if (= (% num 2) 1) (pow num 2))') == [ - pow(num, 2) for num in range(100) if num % 2 == 1] + return hy_eval(hy.read_str(s), filename="", source=s) + + assert eval_str("[1 2 3]") == [1, 2, 3] + assert eval_str('{"dog" "bark" "cat" "meow"}') == {"dog": "bark", "cat": "meow"} + assert eval_str("(, 1 2 3)") == (1, 2, 3) + assert eval_str("#{3 1 2}") == {1, 2, 3} + assert eval_str("1/2") == Fraction(1, 2) + assert eval_str('(.strip " fooooo ")') == "fooooo" + assert ( + eval_str('(if True "this is if true" "this is if false")') == "this is if true" + ) + assert eval_str("(lfor num (range 100) :if (= (% num 2) 1) (pow num 2))") == [ + pow(num, 2) for num in range(100) if num % 2 == 1 + ] def test_reload(tmp_path, monkeypatch): @@ -135,7 +135,7 @@ def unlink(filename): if Path(bytecode).is_file(): Path(bytecode).unlink() - TESTFN = 'testfn' + TESTFN = "testfn" source = tmp_path / (TESTFN + ".hy") source.write_text("(setv a 1) (setv b 2)") @@ -218,38 +218,38 @@ def test_reload_reexecute(capsys): https://github.com/hylang/hy/issues/712""" import tests.resources.hello_world - assert capsys.readouterr().out == 'hello world\n' - assert capsys.readouterr().out == '' + + assert capsys.readouterr().out == "hello world\n" + assert capsys.readouterr().out == "" reload(tests.resources.hello_world) - assert capsys.readouterr().out == 'hello world\n' + assert capsys.readouterr().out == "hello world\n" def test_circular(monkeypatch): """Test circular imports by creating a temporary file/module that calls a function that imports itself.""" - monkeypatch.syspath_prepend('tests/resources/importer') - assert runpy.run_module('circular')['f']() == 1 + monkeypatch.syspath_prepend("tests/resources/importer") + assert runpy.run_module("circular")["f"]() == 1 def test_shadowed_basename(monkeypatch): """Make sure Hy loads `.hy` files instead of their `.py` counterparts (.e.g `__init__.py` and `__init__.hy`). """ - monkeypatch.syspath_prepend('tests/resources/importer') - foo = importlib.import_module('foo') - assert Path(foo.__file__).name == '__init__.hy' - assert foo.ext == 'hy' - some_mod = importlib.import_module('foo.some_mod') - assert Path(some_mod.__file__).name == 'some_mod.hy' - assert some_mod.ext == 'hy' + monkeypatch.syspath_prepend("tests/resources/importer") + foo = importlib.import_module("foo") + assert Path(foo.__file__).name == "__init__.hy" + assert foo.ext == "hy" + some_mod = importlib.import_module("foo.some_mod") + assert Path(some_mod.__file__).name == "some_mod.hy" + assert some_mod.ext == "hy" def test_docstring(monkeypatch): """Make sure a module's docstring is loaded.""" - monkeypatch.syspath_prepend('tests/resources/importer') - mod = importlib.import_module('docstring') - expected_doc = ("This module has a docstring.\n\n" - "It covers multiple lines, too!\n") + monkeypatch.syspath_prepend("tests/resources/importer") + mod = importlib.import_module("docstring") + expected_doc = "This module has a docstring.\n\n" "It covers multiple lines, too!\n" assert mod.__doc__ == expected_doc assert mod.a == 1 @@ -262,8 +262,9 @@ def test_hy_python_require(): def test_filtered_importlib_frames(capsys): testLoader = HyLoader( - "tests.resources.importer.compiler_error", - "tests/resources/importer/compiler_error.hy") + "tests.resources.importer.compiler_error", + "tests/resources/importer/compiler_error.hy", + ) spec = importlib.util.spec_from_loader(testLoader.name, testLoader) mod = importlib.util.module_from_spec(spec) diff --git a/tests/macros/test_macro_processor.py b/tests/macros/test_macro_processor.py index 4fb839d51..46e66ea6d 100644 --- a/tests/macros/test_macro_processor.py +++ b/tests/macros/test_macro_processor.py @@ -1,41 +1,37 @@ -from hy.macros import macro, macroexpand, macroexpand_1 -from hy.lex import tokenize - -from hy.models import String, List, Symbol, Expression, Float -from hy.errors import HyMacroExpansionError +import pytest from hy.compiler import HyASTCompiler, mangle - -import pytest +from hy.errors import HyMacroExpansionError +from hy.lex import tokenize +from hy.macros import macro, macroexpand, macroexpand_1 +from hy.models import Expression, Float, List, String, Symbol @macro("test") def tmac(ETname, *tree): - """ Turn an expression into a list """ + """Turn an expression into a list""" return List(tree) def test_preprocessor_simple(): - """ Test basic macro expansion """ - obj = macroexpand(tokenize('(test "one" "two")')[0], - __name__, - HyASTCompiler(__name__)) + """Test basic macro expansion""" + obj = macroexpand( + tokenize('(test "one" "two")')[0], __name__, HyASTCompiler(__name__) + ) assert obj == List([String("one"), String("two")]) assert type(obj) == List def test_preprocessor_expression(): - """ Test that macro expansion doesn't recurse""" - obj = macroexpand(tokenize('(test (test "one" "two"))')[0], - __name__, - HyASTCompiler(__name__)) + """Test that macro expansion doesn't recurse""" + obj = macroexpand( + tokenize('(test (test "one" "two"))')[0], __name__, HyASTCompiler(__name__) + ) assert type(obj) == List assert type(obj[0]) == Expression - assert obj[0] == Expression([Symbol("test"), - String("one"), - String("two")]) + assert obj[0] == Expression([Symbol("test"), String("one"), String("two")]) obj = List([String("one"), String("two")]) obj = tokenize('(shill ["one" "two"])')[0][1] @@ -43,23 +39,25 @@ def test_preprocessor_expression(): def test_preprocessor_exceptions(): - """ Test that macro expansion raises appropriate exceptions""" + """Test that macro expansion raises appropriate exceptions""" with pytest.raises(HyMacroExpansionError) as excinfo: - macroexpand(tokenize('(when)')[0], __name__, HyASTCompiler(__name__)) + macroexpand(tokenize("(when)")[0], __name__, HyASTCompiler(__name__)) assert "_hy_anon_" not in excinfo.value.msg def test_macroexpand_nan(): - # https://github.com/hylang/hy/issues/1574 - import math - NaN = float('nan') - x = macroexpand(Float(NaN), __name__, HyASTCompiler(__name__)) - assert type(x) is Float - assert math.isnan(x) + # https://github.com/hylang/hy/issues/1574 + import math + + NaN = float("nan") + x = macroexpand(Float(NaN), __name__, HyASTCompiler(__name__)) + assert type(x) is Float + assert math.isnan(x) + def test_macroexpand_source_data(): # https://github.com/hylang/hy/issues/1944 - ast = Expression([Symbol('#@'), String('a')]) + ast = Expression([Symbol("#@"), String("a")]) ast.start_line = 3 ast.start_column = 5 bad = macroexpand_1(ast, "hy.core.macros") diff --git a/tests/resources/__init__.py b/tests/resources/__init__.py index 4735d8498..fe698d49a 100644 --- a/tests/resources/__init__.py +++ b/tests/resources/__init__.py @@ -9,14 +9,17 @@ def function_with_a_dash(): class AsyncWithTest: def __init__(self, val): self.val = val + async def __aenter__(self): return self.val + async def __aexit__(self, exc_type, exc, traceback): self.val = None async def async_loop(items): import asyncio + for x in items: yield x await asyncio.sleep(0) diff --git a/tests/resources/importer/foo/__init__.py b/tests/resources/importer/foo/__init__.py index 5ea361511..7e89b4607 100644 --- a/tests/resources/importer/foo/__init__.py +++ b/tests/resources/importer/foo/__init__.py @@ -1,2 +1,2 @@ -print('This is __init__.py') -ext = 'py' +print("This is __init__.py") +ext = "py" diff --git a/tests/resources/importer/foo/some_mod.py b/tests/resources/importer/foo/some_mod.py index d9533b297..40c80e5c4 100644 --- a/tests/resources/importer/foo/some_mod.py +++ b/tests/resources/importer/foo/some_mod.py @@ -1,2 +1,2 @@ -print('This is test_mod.py') -ext = 'py' +print("This is test_mod.py") +ext = "py" diff --git a/tests/test_bin.py b/tests/test_bin.py index a9f7a0bf9..8a930a26c 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -1,17 +1,15 @@ #!/usr/bin/env python +import builtins import os import re import shlex import subprocess -import builtins - from importlib.util import cache_from_source import pytest - -hy_dir = os.environ.get('HY_DIR', '') +hy_dir = os.environ.get("HY_DIR", "") def pyr(s=""): @@ -27,13 +25,15 @@ def run_cmd(cmd, stdin_data=None, expect=0, dontwritebytecode=False): cmd = shlex.split(cmd) cmd[0] = os.path.join(hy_dir, cmd[0]) - p = subprocess.Popen(cmd, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, - shell=False, - env=env) + p = subprocess.Popen( + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + shell=False, + env=env, + ) output = p.communicate(input=stdin_data) assert p.wait() == expect return output @@ -54,15 +54,15 @@ def test_bin_hy(): def test_bin_hy_stdin(): - output, _ = run_cmd("hy", '(koan)') + output, _ = run_cmd("hy", "(koan)") assert "monk" in output - output, _ = run_cmd("hy --spy", '(koan)') + output, _ = run_cmd("hy --spy", "(koan)") assert "monk" in output assert "\n Ummon" in output # --spy should work even when an exception is thrown - output, _ = run_cmd("hy --spy", '(foof)') + output, _ = run_cmd("hy --spy", "(foof)") assert "foof()" in output @@ -72,25 +72,31 @@ def test_bin_hy_stdin_multiline(): def test_bin_hy_history(): - output, _ = run_cmd("hy", '''(+ "a" "b") + output, _ = run_cmd( + "hy", + """(+ "a" "b") (+ "c" "d") (+ "e" "f") - (.format "*1: {}, *2: {}, *3: {}," *1 *2 *3)''') + (.format "*1: {}, *2: {}, *3: {}," *1 *2 *3)""", + ) assert '"*1: ef, *2: cd, *3: ab,"' in output - output, _ = run_cmd("hy", '''(raise (Exception "TEST ERROR")) - (+ "err: " (str *e))''') + output, _ = run_cmd( + "hy", + """(raise (Exception "TEST ERROR")) + (+ "err: " (str *e))""", + ) assert '"err: TEST ERROR"' in output def test_bin_hy_stdin_comments(): - _, err_empty = run_cmd("hy", '') + _, err_empty = run_cmd("hy", "") output, err = run_cmd("hy", '(+ "a" "b") ; "c"') assert '"ab"' in output assert err == err_empty - _, err = run_cmd("hy", '; 1') + _, err = run_cmd("hy", "; 1") assert err == err_empty @@ -111,9 +117,14 @@ def test_bin_hy_stdin_assignment(): def test_bin_hy_multi_setv(): # https://github.com/hylang/hy/issues/1255 - output, _ = run_cmd("hy", """(do + output, _ = run_cmd( + "hy", + """(do (setv it 0 it (+ it 1) it (+ it 1)) - it)""".replace("\n", " ")) + it)""".replace( + "\n", " " + ), + ) assert re.match(r"=>\s+2\s+=>", output) @@ -125,19 +136,26 @@ def test_bin_hy_stdin_error_underline_alignment(): err_parts = err[msg_idx:].splitlines() assert err_parts[1].startswith(" ^----------^") assert err_parts[2].startswith("expanding macro mabcdefghi") - assert (err_parts[3].startswith(" TypeError: mabcdefghi") or - # PyPy can use a function's `__name__` instead of - # `__code__.co_name`. - err_parts[3].startswith(" TypeError: (mabcdefghi)")) + assert ( + err_parts[3].startswith(" TypeError: mabcdefghi") + or + # PyPy can use a function's `__name__` instead of + # `__code__.co_name`. + err_parts[3].startswith(" TypeError: (mabcdefghi)") + ) def test_bin_hy_stdin_except_do(): # https://github.com/hylang/hy/issues/533 - output, _ = run_cmd("hy", '(try (/ 1 0) (except [ZeroDivisionError] "hello"))') # noqa + output, _ = run_cmd( + "hy", '(try (/ 1 0) (except [ZeroDivisionError] "hello"))' + ) # noqa assert "hello" in output - output, _ = run_cmd("hy", '(try (/ 1 0) (except [ZeroDivisionError] "aaa" "bbb" "ccc"))') # noqa + output, _ = run_cmd( + "hy", '(try (/ 1 0) (except [ZeroDivisionError] "aaa" "bbb" "ccc"))' + ) # noqa assert "aaa" not in output assert "bbb" not in output assert "ccc" in output @@ -152,9 +170,12 @@ def test_bin_hy_stdin_unlocatable_hytypeerror(): # https://github.com/hylang/hy/issues/1412 # The chief test of interest here is the returncode assertion # inside run_cmd. - _, err = run_cmd("hy", """ + _, err = run_cmd( + "hy", + """ (import hy.errors) - (raise (hy.errors.HyTypeError (+ "A" "Z") None '[] None))""") + (raise (hy.errors.HyTypeError (+ "A" "Z") None '[] None))""", + ) assert "AZ" in err @@ -185,13 +206,15 @@ def test_bin_hy_error_parts_length(): assert msg_idx err_parts = err[msg_idx:].splitlines()[1:] - expected = [' File "", line 3', - ' \'a 2 3', - ' ^^', - 'this', - 'is', - 'a', - 'message'] + expected = [ + ' File "", line 3', + " 'a 2 3", + " ^^", + "this", + "is", + "a", + "message", + ] for obs, exp in zip(err_parts, expected): assert obs.startswith(exp) @@ -202,7 +225,7 @@ def test_bin_hy_error_parts_length(): msg_idx = err.rindex("HyLanguageError:") assert msg_idx err_parts = err[msg_idx:].splitlines()[1:] - assert err_parts[2] == ' ^' + assert err_parts[2] == " ^" # Make sure lines are printed in between arrows separated by more than one # character. @@ -212,47 +235,51 @@ def test_bin_hy_error_parts_length(): msg_idx = err.rindex("HyLanguageError:") assert msg_idx err_parts = err[msg_idx:].splitlines()[1:] - assert err_parts[2] == ' ^----^' + assert err_parts[2] == " ^----^" def test_bin_hy_syntax_errors(): # https://github.com/hylang/hy/issues/2004 _, err = run_cmd("hy", "(defn foo [/])\n(defn bar [a a])") - assert 'SyntaxError: duplicate argument' in err + assert "SyntaxError: duplicate argument" in err # https://github.com/hylang/hy/issues/2014 _, err = run_cmd("hy", "(defn foo []\n(import re *))") - assert 'SyntaxError: import * only allowed' in err - assert 'PrematureEndOfInput' not in err + assert "SyntaxError: import * only allowed" in err + assert "PrematureEndOfInput" not in err def test_bin_hy_stdin_bad_repr(): # https://github.com/hylang/hy/issues/1389 - output, err = run_cmd("hy", """ + output, err = run_cmd( + "hy", + """ (defclass BadRepr [] (defn __repr__ [self] (/ 0))) (BadRepr) - (+ "A" "Z")""") + (+ "A" "Z")""", + ) assert "ZeroDivisionError" in err assert "AZ" in output def test_bin_hy_stdin_py_repr(): - output, _ = run_cmd("hy", '(+ [1] [2])') + output, _ = run_cmd("hy", "(+ [1] [2])") assert "[1 2]" in output - output, _ = run_cmd(pyr(), '(+ [1] [2])') + output, _ = run_cmd(pyr(), "(+ [1] [2])") assert "[1, 2]" in output - output, _ = run_cmd(pyr("--spy"), '(+ [1] [2])') - assert "[1]+[2]" in output.replace(' ', '') + output, _ = run_cmd(pyr("--spy"), "(+ [1] [2])") + assert "[1]+[2]" in output.replace(" ", "") assert "[1, 2]" in output # --spy should work even when an exception is thrown - output, _ = run_cmd(pyr("--spy"), '(+ [1] [2] (foof))') - assert "[1]+[2]" in output.replace(' ', '') + output, _ = run_cmd(pyr("--spy"), "(+ [1] [2] (foof))") + assert "[1]+[2]" in output.replace(" ", "") + def test_bin_hy_ignore_python_env(): - os.environ.update({"PYTHONTEST": '0'}) + os.environ.update({"PYTHONTEST": "0"}) output, _ = run_cmd("hy -c '(print (do (import os) (. os environ)))'") assert "PYTHONTEST" in output output, _ = run_cmd("hy -m tests.resources.bin.printenv") @@ -262,33 +289,44 @@ def test_bin_hy_ignore_python_env(): output, _ = run_cmd("hy -E -c '(print (do (import os) (. os environ)))'") assert "PYTHONTEST" not in output - os.environ.update({"PYTHONTEST": '0'}) + os.environ.update({"PYTHONTEST": "0"}) output, _ = run_cmd("hy -E -m tests.resources.bin.printenv") assert "PYTHONTEST" not in output - os.environ.update({"PYTHONTEST": '0'}) + os.environ.update({"PYTHONTEST": "0"}) output, _ = run_cmd("hy -E tests/resources/bin/printenv.hy") assert "PYTHONTEST" not in output + def test_bin_hy_cmd(): - output, _ = run_cmd("hy -c \"(koan)\"") + output, _ = run_cmd('hy -c "(koan)"') assert "monk" in output - _, err = run_cmd("hy -c \"(koan\"", expect=1) + _, err = run_cmd('hy -c "(koan"', expect=1) assert "Premature end of input" in err # https://github.com/hylang/hy/issues/1879 output, _ = run_cmd( - """hy -c '(setv x "bing") (defn f [] (+ "fiz" x)) (print (f))'""") - assert 'fizbing' in output + """hy -c '(setv x "bing") (defn f [] (+ "fiz" x)) (print (f))'""" + ) + assert "fizbing" in output # https://github.com/hylang/hy/issues/1894 - output, _ = run_cmd(' '.join(('hy -c ', - repr('(import sys) (print (+ "<" (.join "|" sys.argv) ">"))'), - 'AA', 'ZZ', '-m'))) - assert '<-c|AA|ZZ|-m>' in output + output, _ = run_cmd( + " ".join( + ( + "hy -c ", + repr('(import sys) (print (+ "<" (.join "|" sys.argv) ">"))'), + "AA", + "ZZ", + "-m", + ) + ) + ) + assert "<-c|AA|ZZ|-m>" in output + def test_bin_hy_icmd(): - output, _ = run_cmd("hy -i \"(koan)\"", "(ideas)") + output, _ = run_cmd('hy -i "(koan)"', "(ideas)") assert "monk" in output assert "figlet" in output @@ -299,7 +337,7 @@ def test_bin_hy_icmd_file(): def test_bin_hy_icmd_and_spy(): - output, _ = run_cmd("hy --spy -i \"(+ [] [])\"", "(+ 1 1)") + output, _ = run_cmd('hy --spy -i "(+ [] [])"', "(+ 1 1)") assert "[] + []" in output @@ -312,15 +350,17 @@ def test_bin_hy_file_with_args(): assert "usage" in run_cmd("hy tests/resources/argparse_ex.hy -h")[0] assert "got c" in run_cmd("hy tests/resources/argparse_ex.hy -c bar")[0] assert "foo" in run_cmd("hy tests/resources/argparse_ex.hy -i foo")[0] - assert "foo" in run_cmd("hy tests/resources/argparse_ex.hy -i foo -c bar")[0] # noqa + assert ( + "foo" in run_cmd("hy tests/resources/argparse_ex.hy -i foo -c bar")[0] + ) # noqa def test_bin_hyc(): _, err = run_cmd("hyc", expect=0) - assert err == '' + assert err == "" _, err = run_cmd("hyc -", expect=0) - assert err == '' + assert err == "" output, _ = run_cmd("hyc -h") assert "usage" in output @@ -341,6 +381,7 @@ def test_bin_hy_builtins(): # hy.cmdline replaces builtins.exit and builtins.quit # for use by hy's repl. import hy.cmdline # NOQA + # this test will fail if run from IPython because IPython deletes # builtins.exit and builtins.quit assert str(builtins.exit) == "Use (exit) or Ctrl-D (i.e. EOF) to exit" @@ -354,20 +395,26 @@ def test_bin_hy_no_main(): assert "This Should Still Work" in output -@pytest.mark.parametrize('scenario', ["normal", "prevent_by_force", - "prevent_by_env", "prevent_by_option"]) -@pytest.mark.parametrize('cmd_fmt', [['hy', '{fpath}'], - ['hy', '-m', '{modname}'], - ['hy', '-c', "'(import {modname})'"]]) +@pytest.mark.parametrize( + "scenario", ["normal", "prevent_by_force", "prevent_by_env", "prevent_by_option"] +) +@pytest.mark.parametrize( + "cmd_fmt", + [ + ["hy", "{fpath}"], + ["hy", "-m", "{modname}"], + ["hy", "-c", "'(import {modname})'"], + ], +) def test_bin_hy_byte_compile(scenario, cmd_fmt): modname = "tests.resources.bin.bytecompile" fpath = modname.replace(".", "/") + ".hy" - if scenario == 'prevent_by_option': - cmd_fmt.insert(1, '-B') + if scenario == "prevent_by_option": + cmd_fmt.insert(1, "-B") - cmd = ' '.join(cmd_fmt).format(**locals()) + cmd = " ".join(cmd_fmt).format(**locals()) rm(cache_from_source(fpath)) @@ -416,18 +463,19 @@ def test_bin_hy_file_sys_path(): adopts the file's location in `sys.path`, instead of the runner's current dir (e.g. '' in `sys.path`). """ - file_path, _ = os.path.split('tests/resources/relative_import.hy') + file_path, _ = os.path.split("tests/resources/relative_import.hy") file_relative_path = os.path.realpath(file_path) output, _ = run_cmd("hy tests/resources/relative_import.hy") assert repr(file_relative_path) in output + def test_bin_hyc_file_sys_path(): # similar to test_bin_hy_file_sys_path, test hyc and hy2py to make sure # they can find the relative import at compile time # https://github.com/hylang/hy/issues/2021 - test_file = 'tests/resources/relative_import_compile_time.hy' + test_file = "tests/resources/relative_import_compile_time.hy" file_relative_path = os.path.realpath(os.path.dirname(test_file)) for binary in ("hy", "hyc", "hy2py"): @@ -446,7 +494,7 @@ def test_bin_hy_module_no_main(): def test_bin_hy_sys_executable(): output, _ = run_cmd("hy -c '(do (import sys) (print sys.executable))'") - assert os.path.basename(output.strip()) == 'hy' + assert os.path.basename(output.strip()) == "hy" def test_bin_hy_file_no_extension(): @@ -471,6 +519,7 @@ def test_bin_hy_circular_macro_require(): output, _ = run_cmd("hy {}".format(test_file)) assert output.strip() == "WOWIE" + def test_bin_hy_macro_require(): """Confirm that a `require` will load macros into the non-module namespace (i.e. `exec(code, locals)`) used by `runpy.run_path`. @@ -494,23 +543,22 @@ def test_bin_hy_tracebacks(): """Make sure the printed tracebacks are correct.""" # We want the filtered tracebacks. - os.environ['HY_DEBUG'] = '' + os.environ["HY_DEBUG"] = "" def req_err(x): - assert (x == 'hy.errors.HyRequireError: No module named ' - "'not_a_real_module'") + assert x == "hy.errors.HyRequireError: No module named " "'not_a_real_module'" # Modeled after # > python -c 'import not_a_real_module' # Traceback (most recent call last): # File "", line 1, in # ImportError: No module named not_a_real_module - _, error = run_cmd('hy', '(require not-a-real-module)') + _, error = run_cmd("hy", "(require not-a-real-module)") error_lines = error.splitlines() - if error_lines[-1] == '': + if error_lines[-1] == "": del error_lines[-1] assert len(error_lines) <= 10 - # Rough check for the internal traceback filtering + # Rough check for the internal traceback filtering req_err(error_lines[4]) _, error = run_cmd('hy -c "(require not-a-real-module)"', expect=1) @@ -519,7 +567,7 @@ def req_err(x): req_err(error_lines[-1]) output, error = run_cmd('hy -i "(require not-a-real-module)"') - assert output.startswith('=> ') + assert output.startswith("=> ") print(error.splitlines()) req_err(error.splitlines()[2]) @@ -531,11 +579,12 @@ def req_err(x): # SyntaxError: EOL while scanning string literal _, error = run_cmd(r'hy -c "(print \""', expect=1) peoi_re = ( - r'Traceback \(most recent call last\):\n' + r"Traceback \(most recent call last\):\n" r' File "(?:|string-[0-9a-f]+)", line 1\n' r' \(print "\n' - r' \^\n' - r'hy.lex.exceptions.PrematureEndOfInput: Partial string literal\n') + r" \^\n" + r"hy.lex.exceptions.PrematureEndOfInput: Partial string literal\n" + ) assert re.search(peoi_re, error) # Modeled after @@ -546,7 +595,7 @@ def req_err(x): # SyntaxError: EOL while scanning string literal # >>> output, error = run_cmd(r'hy -i "(print \""') - assert output.startswith('=> ') + assert output.startswith("=> ") assert re.match(peoi_re, error) # Modeled after @@ -558,8 +607,9 @@ def req_err(x): error_lines = error.splitlines() assert error_lines[3] == ' File "", line 1, in ' # PyPy will add "global" to this error message, so we work around that. - assert error_lines[-1].strip().replace(' global', '') == ( - "NameError: name 'a' is not defined") + assert error_lines[-1].strip().replace(" global", "") == ( + "NameError: name 'a' is not defined" + ) # Modeled after # > python -c 'compile()' @@ -569,7 +619,7 @@ def req_err(x): output, error = run_cmd('hy -c "(compile)"', expect=1) error_lines = error.splitlines() assert error_lines[-2] == ' File "", line 1, in ' - assert error_lines[-1].startswith('TypeError') + assert error_lines[-1].startswith("TypeError") def test_hystartup(): @@ -595,4 +645,4 @@ def test_hystartup(): assert "[1, 2]" in output assert "[1,~2]" in output - del os.environ['HYSTARTUP'] + del os.environ["HYSTARTUP"] diff --git a/tests/test_completer.py b/tests/test_completer.py index d468b5aa0..951d9ee7f 100644 --- a/tests/test_completer.py +++ b/tests/test_completer.py @@ -1,13 +1,16 @@ import os import sys + import pytest + import hy.completer hy.completer.init_readline() + @pytest.mark.skipif( - not hy.completer.readline, - reason="Module 'readline' is not available.") + not hy.completer.readline, reason="Module 'readline' is not available." +) def test_history_custom_location(tmp_path): import readline diff --git a/tests/test_hy2py.py b/tests/test_hy2py.py index a2839938c..b72ae8e58 100644 --- a/tests/test_hy2py.py +++ b/tests/test_hy2py.py @@ -1,32 +1,42 @@ -import os, math, itertools, asyncio -from hy import mangle +import asyncio +import itertools +import math +import os + import hy.importer +from hy import mangle def test_direct_import(): import tests.resources.pydemo + assert_stuff(tests.resources.pydemo) def test_hy2py_import(tmpdir): import subprocess + path = tmpdir.join("pydemo.py") with open(path, "wb") as o: subprocess.check_call( ["hy2py", "tests/resources/pydemo.hy"], - stdout = o, - env = {**os.environ, 'PYTHONIOENCODING': 'UTF-8'}) + stdout=o, + env={**os.environ, "PYTHONIOENCODING": "UTF-8"}, + ) assert_stuff(hy.importer._import_from_path("pydemo", path)) def assert_stuff(m): # This makes sure that automatically imported builtins go after docstrings. - assert m.__doc__ == 'This is a module docstring.' + assert m.__doc__ == "This is a module docstring." assert m.mystring == "foofoofoo" - assert m.long_string == "This is a very long string literal, which would surely exceed any limitations on how long a line or a string literal can be. The string literal alone exceeds 256 characters. It also has a character outside the Basic Multilingual Plane: 😂. Here's a double quote: \". Here are some escaped newlines:\n\n\nHere is a literal newline:\nCall me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires a strong moral principle to prevent me from deliberately stepping into the street, and methodically knocking people’s hats off—then, I account it high time to get to sea as soon as I can. This is my substitute for pistol and ball. With a philosophical flourish Cato throws himself upon his sword; I quietly take to the ship. There is nothing surprising in this. If they but knew it, almost all men in their degree, some time or other, cherish very nearly the same feelings towards the ocean with me." + assert ( + m.long_string + == "This is a very long string literal, which would surely exceed any limitations on how long a line or a string literal can be. The string literal alone exceeds 256 characters. It also has a character outside the Basic Multilingual Plane: 😂. Here's a double quote: \". Here are some escaped newlines:\n\n\nHere is a literal newline:\nCall me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires a strong moral principle to prevent me from deliberately stepping into the street, and methodically knocking people’s hats off—then, I account it high time to get to sea as soon as I can. This is my substitute for pistol and ball. With a philosophical flourish Cato throws himself upon his sword; I quietly take to the ship. There is nothing surprising in this. If they but knew it, almost all men in their degree, some time or other, cherish very nearly the same feelings towards the ocean with me." + ) assert getattr(m, mangle("identifier-that-has☝️💯☝️-to-be-mangled")) == "ponies" @@ -87,6 +97,7 @@ def assert_stuff(m): assert m.sqrt is math.sqrt assert m.sine is math.sin import datetime + assert m.timedelta is datetime.timedelta assert m.if_block == "cd" @@ -100,17 +111,17 @@ def assert_stuff(m): assert type(m.fun) is type(lambda x: x) assert m.fun.__doc__ == "function docstring" - assert m.funcall1 == [ - 1, 2, 3, 4, ("a", "b", "c"), [("k1", "v1"), ("k2", "v2")]] - assert m.funcall2 == [ - 7, 8, 9, 10, (11,), [("x1", "y1"), ("x2", "y2")]] + assert m.funcall1 == [1, 2, 3, 4, ("a", "b", "c"), [("k1", "v1"), ("k2", "v2")]] + assert m.funcall2 == [7, 8, 9, 10, (11,), [("x1", "y1"), ("x2", "y2")]] assert m.myret == 1 assert m.myyield == ["a", "b", "c"] assert m.mydecorated.newattr == "hello" assert m.myglobal == 103 - class C: pass + class C: + pass + assert type(m.C1) is type(C) assert m.C2.__doc__ == "class docstring" diff --git a/tests/test_lex.py b/tests/test_lex.py index e320d7c7b..6cb40c78d 100644 --- a/tests/test_lex.py +++ b/tests/test_lex.py @@ -1,112 +1,142 @@ import sys import traceback +from math import isnan import pytest -from math import isnan -from hy.models import (Expression, Integer, Float, Complex, Symbol, - String, Dict, List, Set, Keyword) +from hy.errors import hy_exc_handler from hy.lex import tokenize from hy.lex.exceptions import LexException, PrematureEndOfInput -from hy.errors import hy_exc_handler +from hy.models import ( + Complex, + Dict, + Expression, + Float, + Integer, + Keyword, + List, + Set, + String, + Symbol, +) + -def peoi(): return pytest.raises(PrematureEndOfInput) -def lexe(): return pytest.raises(LexException) +def peoi(): + return pytest.raises(PrematureEndOfInput) + + +def lexe(): + return pytest.raises(LexException) def check_ex(einfo, expected): - assert ( - [x.rstrip() for x in - traceback.format_exception_only(einfo.type, einfo.value)] - == expected) + assert [ + x.rstrip() for x in traceback.format_exception_only(einfo.type, einfo.value) + ] == expected def check_trace_output(capsys, execinfo, expected): - sys.__excepthook__(execinfo.type, execinfo.value, execinfo.tb) - captured_wo_filtering = capsys.readouterr()[-1].strip('\n') + sys.__excepthook__(execinfo.type, execinfo.value, execinfo.tb) + captured_wo_filtering = capsys.readouterr()[-1].strip("\n") - hy_exc_handler(execinfo.type, execinfo.value, execinfo.tb) - captured_w_filtering = capsys.readouterr()[-1].strip('\n') + hy_exc_handler(execinfo.type, execinfo.value, execinfo.tb) + captured_w_filtering = capsys.readouterr()[-1].strip("\n") - output = [x.rstrip() for x in captured_w_filtering.split('\n')] + output = [x.rstrip() for x in captured_w_filtering.split("\n")] - # Make sure the filtered frames aren't the same as the unfiltered ones. - assert output != captured_wo_filtering.split('\n') - # Remove the origin frame lines. - assert output[3:] == expected + # Make sure the filtered frames aren't the same as the unfiltered ones. + assert output != captured_wo_filtering.split("\n") + # Remove the origin frame lines. + assert output[3:] == expected def test_lex_exception(): - """ Ensure tokenize throws a fit on a partial input """ - with peoi(): tokenize("(foo") - with peoi(): tokenize("{foo bar") - with peoi(): tokenize("(defn foo [bar]") - with peoi(): tokenize("(foo \"bar") + """Ensure tokenize throws a fit on a partial input""" + with peoi(): + tokenize("(foo") + with peoi(): + tokenize("{foo bar") + with peoi(): + tokenize("(defn foo [bar]") + with peoi(): + tokenize('(foo "bar') def test_unbalanced_exception(): """Ensure the tokenization fails on unbalanced expressions""" - with lexe(): tokenize("(bar))") - with lexe(): tokenize("(baz [quux]])") + with lexe(): + tokenize("(bar))") + with lexe(): + tokenize("(baz [quux]])") def test_lex_single_quote_err(): - "Ensure tokenizing \"' \" throws a LexException that can be stringified" + 'Ensure tokenizing "\' " throws a LexException that can be stringified' # https://github.com/hylang/hy/issues/1252 with lexe() as execinfo: tokenize("' ") - check_ex(execinfo, [ - ' File "", line 1', - " '", - ' ^', - 'hy.lex.exceptions.LexException: Could not identify the next token.']) + check_ex( + execinfo, + [ + ' File "", line 1', + " '", + " ^", + "hy.lex.exceptions.LexException: Could not identify the next token.", + ], + ) def test_lex_expression_symbols(): - """ Make sure that expressions produce symbols """ + """Make sure that expressions produce symbols""" objs = tokenize("(foo bar)") assert objs == [Expression([Symbol("foo"), Symbol("bar")])] def test_lex_expression_strings(): - """ Test that expressions can produce strings """ - objs = tokenize("(foo \"bar\")") + """Test that expressions can produce strings""" + objs = tokenize('(foo "bar")') assert objs == [Expression([Symbol("foo"), String("bar")])] def test_lex_expression_integer(): - """ Make sure expressions can produce integers """ + """Make sure expressions can produce integers""" objs = tokenize("(foo 2)") assert objs == [Expression([Symbol("foo"), Integer(2)])] def test_lex_symbols(): - """ Make sure that symbols are valid expressions""" + """Make sure that symbols are valid expressions""" objs = tokenize("foo ") assert objs == [Symbol("foo")] def test_lex_strings(): - """ Make sure that strings are valid expressions""" + """Make sure that strings are valid expressions""" objs = tokenize('"foo"') assert objs == [String("foo")] # Make sure backslash-escaped newlines work (see issue #831) - objs = tokenize(r""" + objs = tokenize( + r""" "a\ bc" -""") +""" + ) assert objs == [String("abc")] def test_lex_strings_exception(): - """ Make sure tokenize throws when codec can't decode some bytes""" + """Make sure tokenize throws when codec can't decode some bytes""" with lexe() as execinfo: - tokenize('\"\\x8\"') - check_ex(execinfo, [ - ' File "", line 1', - ' "\\x8"', - ' ^', - 'hy.lex.exceptions.LexException: Can\'t convert "\\x8" to a hy.models.String']) + tokenize('"\\x8"') + check_ex( + execinfo, + [ + ' File "", line 1', + ' "\\x8"', + " ^", + 'hy.lex.exceptions.LexException: Can\'t convert "\\x8" to a hy.models.String', + ], + ) def test_lex_bracket_strings(): @@ -121,25 +151,25 @@ def test_lex_bracket_strings(): def test_lex_integers(): - """ Make sure that integers are valid expressions""" + """Make sure that integers are valid expressions""" objs = tokenize("42 ") assert objs == [Integer(42)] def test_lex_fractions(): - """ Make sure that fractions are valid expressions""" + """Make sure that fractions are valid expressions""" objs = tokenize("1/2") assert objs == [Expression([Symbol("hy._Fraction"), Integer(1), Integer(2)])] def test_lex_expression_float(): - """ Make sure expressions can produce floats """ + """Make sure expressions can produce floats""" objs = tokenize("(foo 2.)") - assert objs == [Expression([Symbol("foo"), Float(2.)])] + assert objs == [Expression([Symbol("foo"), Float(2.0)])] objs = tokenize("(foo -0.5)") assert objs == [Expression([Symbol("foo"), Float(-0.5)])] objs = tokenize("(foo 1.e7)") - assert objs == [Expression([Symbol("foo"), Float(1.e7)])] + assert objs == [Expression([Symbol("foo"), Float(1.0e7)])] def test_lex_big_float(): @@ -165,13 +195,15 @@ def test_lex_nan_and_inf(): def test_lex_expression_complex(): - """ Make sure expressions can produce complex """ + """Make sure expressions can produce complex""" - def t(x): return tokenize("(foo {})".format(x)) + def t(x): + return tokenize("(foo {})".format(x)) - def f(x): return [Expression([Symbol("foo"), x])] + def f(x): + return [Expression([Symbol("foo"), x])] - assert t("2.j") == f(Complex(2.j)) + assert t("2.j") == f(Complex(2.0j)) assert t("-0.5j") == f(Complex(-0.5j)) assert t("1.e7j") == f(Complex(1e7j)) assert t("j") == f(Symbol("j")) @@ -190,48 +222,63 @@ def test_lex_digit_separators(): assert tokenize("1,000_000") == [Integer(1000000)] assert tokenize("1_000,000") == [Integer(1000000)] - assert tokenize("0x_af") == [Integer(0xaf)] - assert tokenize("0x,af") == [Integer(0xaf)] + assert tokenize("0x_af") == [Integer(0xAF)] + assert tokenize("0x,af") == [Integer(0xAF)] assert tokenize("0b_010") == [Integer(0b010)] assert tokenize("0b,010") == [Integer(0b010)] assert tokenize("0o_373") == [Integer(0o373)] assert tokenize("0o,373") == [Integer(0o373)] - assert tokenize('1_2.3,4') == [Float(12.34)] - assert tokenize('1_2e3,4') == [Float(12e34)] - assert (tokenize("1,2/3_4") == - [Expression([Symbol("hy._Fraction"), Integer(12), Integer(34)])]) + assert tokenize("1_2.3,4") == [Float(12.34)] + assert tokenize("1_2e3,4") == [Float(12e34)] + assert tokenize("1,2/3_4") == [ + Expression([Symbol("hy._Fraction"), Integer(12), Integer(34)]) + ] assert tokenize("1,0_00j") == [Complex(1000j)] assert tokenize("1,,,,___,____,,__,,2__,,,__") == [Integer(12)] - assert (tokenize("_1,,,,___,____,,__,,2__,,,__") == - [Symbol("_1,,,,___,____,,__,,2__,,,__")]) - assert (tokenize("1,,,,___,____,,__,,2__,q,__") == - [Symbol("1,,,,___,____,,__,,2__,q,__")]) + assert tokenize("_1,,,,___,____,,__,,2__,,,__") == [ + Symbol("_1,,,,___,____,,__,,2__,,,__") + ] + assert tokenize("1,,,,___,____,,__,,2__,q,__") == [ + Symbol("1,,,,___,____,,__,,2__,q,__") + ] def test_lex_bad_attrs(): with lexe() as execinfo: tokenize("1.foo") - check_ex(execinfo, [ - ' File "", line 1', - ' 1.foo', - ' ^', - 'hy.lex.exceptions.LexException: Cannot access attribute on anything other' - ' than a name (in order to get attributes of expressions,' - ' use `(. )` or `(. )`)']) - - with lexe(): tokenize("0.foo") - with lexe(): tokenize("1.5.foo") - with lexe(): tokenize("1e3.foo") - with lexe(): tokenize("5j.foo") - with lexe(): tokenize("3+5j.foo") - with lexe(): tokenize("3.1+5.1j.foo") + check_ex( + execinfo, + [ + ' File "", line 1', + " 1.foo", + " ^", + "hy.lex.exceptions.LexException: Cannot access attribute on anything other" + " than a name (in order to get attributes of expressions," + " use `(. )` or `(. )`)", + ], + ) + + with lexe(): + tokenize("0.foo") + with lexe(): + tokenize("1.5.foo") + with lexe(): + tokenize("1e3.foo") + with lexe(): + tokenize("5j.foo") + with lexe(): + tokenize("3+5j.foo") + with lexe(): + tokenize("3.1+5.1j.foo") assert tokenize("j.foo") - with lexe(): tokenize("3/4.foo") + with lexe(): + tokenize("3/4.foo") assert tokenize("a/1.foo") assert tokenize("1/a.foo") - with lexe(): tokenize(":hello.foo") + with lexe(): + tokenize(":hello.foo") def test_lex_column_counting(): @@ -269,11 +316,13 @@ def test_lex_column_counting_with_literal_newline(): def test_lex_line_counting_multi(): - """ Make sure we can do multi-line tokenization """ - entries = tokenize(""" + """Make sure we can do multi-line tokenization""" + entries = tokenize( + """ (foo (one two)) (foo bar) -""") +""" + ) entry = entries[0] @@ -292,9 +341,11 @@ def test_lex_line_counting_multi(): def test_lex_line_counting_multi_inner(): - """ Make sure we can do multi-line tokenization (inner) """ - entry = tokenize("""(foo - bar)""")[0] + """Make sure we can do multi-line tokenization (inner)""" + entry = tokenize( + """(foo + bar)""" + )[0] inner = entry[0] assert inner.start_line == 1 @@ -307,35 +358,49 @@ def test_lex_line_counting_multi_inner(): def test_dicts(): - """ Ensure that we can tokenize a dict. """ + """Ensure that we can tokenize a dict.""" objs = tokenize("{foo bar bar baz}") assert objs == [Dict([Symbol("foo"), Symbol("bar"), Symbol("bar"), Symbol("baz")])] objs = tokenize("(bar {foo bar bar baz})") - assert objs == [Expression([Symbol("bar"), - Dict([Symbol("foo"), Symbol("bar"), - Symbol("bar"), Symbol("baz")])])] + assert objs == [ + Expression( + [ + Symbol("bar"), + Dict([Symbol("foo"), Symbol("bar"), Symbol("bar"), Symbol("baz")]), + ] + ) + ] objs = tokenize("{(foo bar) (baz quux)}") - assert objs == [Dict([ - Expression([Symbol("foo"), Symbol("bar")]), - Expression([Symbol("baz"), Symbol("quux")]) - ])] + assert objs == [ + Dict( + [ + Expression([Symbol("foo"), Symbol("bar")]), + Expression([Symbol("baz"), Symbol("quux")]), + ] + ) + ] def test_sets(): - """ Ensure that we can tokenize a set. """ + """Ensure that we can tokenize a set.""" objs = tokenize("#{1 2}") assert objs == [Set([Integer(1), Integer(2)])] objs = tokenize("(bar #{foo bar baz})") - assert objs == [Expression([Symbol("bar"), - Set([Symbol("foo"), Symbol("bar"), Symbol("baz")])])] + assert objs == [ + Expression([Symbol("bar"), Set([Symbol("foo"), Symbol("bar"), Symbol("baz")])]) + ] objs = tokenize("#{(foo bar) (baz quux)}") - assert objs == [Set([ - Expression([Symbol("foo"), Symbol("bar")]), - Expression([Symbol("baz"), Symbol("quux")]) - ])] + assert objs == [ + Set( + [ + Expression([Symbol("foo"), Symbol("bar")]), + Expression([Symbol("baz"), Symbol("quux")]), + ] + ) + ] # Duplicate items in a literal set should be okay (and should # be preserved). @@ -349,7 +414,7 @@ def test_sets(): def test_nospace(): - """ Ensure we can tokenize without spaces if we have to """ + """Ensure we can tokenize without spaces if we have to""" entry = tokenize("(foo(one two))")[0] assert entry.start_line == 1 @@ -367,7 +432,7 @@ def test_nospace(): def test_escapes(): - """ Ensure we can escape things """ + """Ensure we can escape things""" entry = tokenize(r"""(foo "foo\n")""")[0] assert entry[1] == String("foo\n") @@ -466,7 +531,9 @@ def test_discard(): assert tokenize("(#_foo)") == [Expression()] assert tokenize("(#_foo bar)") == [Expression([Symbol("bar")])] assert tokenize("(foo #_bar)") == [Expression([Symbol("foo")])] - assert tokenize("(foo :bar 1)") == [Expression([Symbol("foo"), Keyword("bar"), Integer(1)])] + assert tokenize("(foo :bar 1)") == [ + Expression([Symbol("foo"), Keyword("bar"), Integer(1)]) + ] assert tokenize("(foo #_:bar 1)") == [Expression([Symbol("foo"), Integer(1)])] assert tokenize("(foo :bar #_1)") == [Expression([Symbol("foo"), Keyword("bar")])] # discard term with nesting @@ -475,9 +542,18 @@ def test_discard(): ] # discard with other prefix syntax assert tokenize("a #_'b c") == [Symbol("a"), Symbol("c")] - assert tokenize("a '#_b c") == [Symbol("a"), Expression([Symbol("quote"), Symbol("c")])] - assert tokenize("a '#_b #_c d") == [Symbol("a"), Expression([Symbol("quote"), Symbol("d")])] - assert tokenize("a '#_ #_b c d") == [Symbol("a"), Expression([Symbol("quote"), Symbol("d")])] + assert tokenize("a '#_b c") == [ + Symbol("a"), + Expression([Symbol("quote"), Symbol("c")]), + ] + assert tokenize("a '#_b #_c d") == [ + Symbol("a"), + Expression([Symbol("quote"), Symbol("d")]), + ] + assert tokenize("a '#_ #_b c d") == [ + Symbol("a"), + Expression([Symbol("quote"), Symbol("d")]), + ] def test_lex_exception_filtering(capsys): @@ -486,19 +562,29 @@ def test_lex_exception_filtering(capsys): # First, test for PrematureEndOfInput with peoi() as execinfo: tokenize(" \n (foo\n \n") - check_trace_output(capsys, execinfo, [ - ' File "", line 2', - ' (foo', - ' ^', - 'hy.lex.exceptions.PrematureEndOfInput: Premature end of input']) + check_trace_output( + capsys, + execinfo, + [ + ' File "", line 2', + " (foo", + " ^", + "hy.lex.exceptions.PrematureEndOfInput: Premature end of input", + ], + ) # Now, for a generic LexException with lexe() as execinfo: tokenize(" \n\n 1.foo ") - check_trace_output(capsys, execinfo, [ - ' File "", line 3', - ' 1.foo', - ' ^', - 'hy.lex.exceptions.LexException: Cannot access attribute on anything other' - ' than a name (in order to get attributes of expressions,' - ' use `(. )` or `(. )`)']) + check_trace_output( + capsys, + execinfo, + [ + ' File "", line 3', + " 1.foo", + " ^", + "hy.lex.exceptions.LexException: Cannot access attribute on anything other" + " than a name (in order to get attributes of expressions," + " use `(. )` or `(. )`)", + ], + ) diff --git a/tests/test_models.py b/tests/test_models.py index 401829c9f..b0e50bdd6 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,10 +1,26 @@ import copy + import pytest + import hy from hy.errors import HyWrapperError from hy.models import ( - FComponent, FString, as_model, replace_hy_obj, Symbol, Keyword, String, Integer, - List, Dict, Set, Expression, Complex, Float, pretty) + Complex, + Dict, + Expression, + FComponent, + Float, + FString, + Integer, + Keyword, + List, + Set, + String, + Symbol, + as_model, + pretty, + replace_hy_obj, +) hy.models.COLORED = False @@ -14,21 +30,24 @@ def test_symbol_or_keyword(): assert str(Symbol(x)) == x assert Keyword(x).name == x for x in ("", ":foo", "5"): - with pytest.raises(ValueError): Symbol(x) + with pytest.raises(ValueError): + Symbol(x) assert Keyword(x).name == x for x in ("foo bar", "fib()"): - with pytest.raises(ValueError): Symbol(x) - with pytest.raises(ValueError): Keyword(x) + with pytest.raises(ValueError): + Symbol(x) + with pytest.raises(ValueError): + Keyword(x) def test_wrap_int(): - """ Test conversion of integers.""" + """Test conversion of integers.""" wrapped = as_model(0) assert type(wrapped) == Integer def test_wrap_tuple(): - """ Test conversion of tuples.""" + """Test conversion of tuples.""" wrapped = as_model((Integer(0),)) assert type(wrapped) == List assert type(wrapped[0]) == Integer @@ -36,7 +55,7 @@ def test_wrap_tuple(): def test_wrap_nested_expr(): - """ Test conversion of Expressions with embedded non-HyObjects.""" + """Test conversion of Expressions with embedded non-HyObjects.""" wrapped = as_model(Expression([0])) assert type(wrapped) == Expression assert type(wrapped[0]) == Integer @@ -44,7 +63,7 @@ def test_wrap_nested_expr(): def test_replace_int(): - """ Test replacing integers.""" + """Test replacing integers.""" replaced = replace_hy_obj(0, Integer(13)) assert replaced == Integer(0) @@ -69,8 +88,8 @@ def test_replace_string_type(): def test_replace_tuple(): - """ Test replacing tuples.""" - replaced = replace_hy_obj((0, ), Integer(13)) + """Test replacing tuples.""" + replaced = replace_hy_obj((0,), Integer(13)) assert type(replaced) == List assert type(replaced[0]) == Integer assert replaced == List([Integer(0)]) @@ -121,21 +140,22 @@ def test_set(): def test_number_model_copy(): i = Integer(42) - assert (i == copy.copy(i)) - assert (i == copy.deepcopy(i)) + assert i == copy.copy(i) + assert i == copy.deepcopy(i) - f = Float(42.) - assert (f == copy.copy(f)) - assert (f == copy.deepcopy(f)) + f = Float(42.0) + assert f == copy.copy(f) + assert f == copy.deepcopy(f) c = Complex(42j) - assert (c == copy.copy(c)) - assert (c == copy.deepcopy(c)) + assert c == copy.copy(c) + assert c == copy.deepcopy(c) PRETTY_STRINGS = { - k % ('[1.0] {1.0} (1.0) #{1.0}',): - v.format(""" + k + % ("[1.0] {1.0} (1.0) #{1.0}",): v.format( + """ hy.models.List([ hy.models.Float(1.0)]), hy.models.Dict([ @@ -144,13 +164,14 @@ def test_number_model_copy(): hy.models.Expression([ hy.models.Float(1.0)]), hy.models.Set([ - hy.models.Float(1.0)])""") - for k, v in {'[%s]': 'hy.models.List([{}])', - '#{%s}': 'hy.models.Set([{}])'}.items()} - -PRETTY_STRINGS.update({ - '{[1.0] {1.0} (1.0) #{1.0}}': - """hy.models.Dict([ + hy.models.Float(1.0)])""" + ) + for k, v in {"[%s]": "hy.models.List([{}])", "#{%s}": "hy.models.Set([{}])"}.items() +} + +PRETTY_STRINGS.update( + { + "{[1.0] {1.0} (1.0) #{1.0}}": """hy.models.Dict([ hy.models.List([ hy.models.Float(1.0)]), hy.models.Dict([ @@ -161,19 +182,15 @@ def test_number_model_copy(): hy.models.Float(1.0)]), hy.models.Set([ hy.models.Float(1.0)]) - ])""" - , - '[1.0 1j [] {} () #{}]': - """hy.models.List([ + ])""", + "[1.0 1j [] {} () #{}]": """hy.models.List([ hy.models.Float(1.0), hy.models.Complex(1j), hy.models.List(), hy.models.Dict(), hy.models.Expression(), - hy.models.Set()])""" - , - '{{1j 2j} {1j 2j [][1j]} {[1j][] 1j 2j} {[1j][1j]}}': - """hy.models.Dict([ + hy.models.Set()])""", + "{{1j 2j} {1j 2j [][1j]} {[1j][] 1j 2j} {[1j][1j]}}": """hy.models.Dict([ hy.models.Dict([ hy.models.Complex(1j), hy.models.Complex(2j)]), hy.models.Dict([ @@ -195,7 +212,9 @@ def test_number_model_copy(): hy.models.List([ hy.models.Complex(1j)]) ]) - ])"""}) + ])""", + } +) def test_compound_model_repr(): @@ -220,7 +239,7 @@ def test_compound_model_repr(): def test_recursive_model_detection(): - """ Check for self-references: + """Check for self-references: https://github.com/hylang/hy/issues/2153 """ self_ref_list = [1, 2, 3] @@ -233,10 +252,12 @@ def test_recursive_model_detection(): mutually_ref_list[1] = mutually_ref_dict mutually_ref_dict[2] = mutually_ref_list - for structure in [self_ref_list, - self_ref_dict, - mutually_ref_list, - mutually_ref_dict]: + for structure in [ + self_ref_list, + self_ref_dict, + mutually_ref_list, + mutually_ref_dict, + ]: with pytest.raises(HyWrapperError) as exc: as_model(structure) assert "Self-referential" in str(exc)