From 72e3ee686a633a2ab9f50a3116a5ec775b50b407 Mon Sep 17 00:00:00 2001 From: gilch Date: Mon, 4 Oct 2021 16:21:48 -0600 Subject: [PATCH 1/3] Refactor transpile --- src/hissp/reader.py | 78 ++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 50 deletions(-) diff --git a/src/hissp/reader.py b/src/hissp/reader.py index d83a4ad2e..4295b1145 100644 --- a/src/hissp/reader.py +++ b/src/hissp/reader.py @@ -14,9 +14,7 @@ import ast import builtins -import os import re -import sys from collections import namedtuple from contextlib import contextmanager, nullcontext from functools import reduce @@ -24,10 +22,9 @@ from itertools import chain from keyword import iskeyword as _iskeyword from pathlib import Path, PurePath -from pprint import pformat, pprint +from pprint import pformat from threading import Lock -from types import ModuleType -from typing import Any, Iterable, Iterator, NewType, Optional, Tuple, Union +from typing import Any, Iterable, Iterator, NewType, Tuple, Union from hissp.compiler import Compiler, MAYBE, readerless from hissp.munger import force_qz_encode, munge @@ -437,54 +434,35 @@ def is_qualifiable(symbol): ) -def transpile(package: Optional[resources.Package], *modules: Union[str, PurePath]): - """ - Compiles the named modules to Python files. - If the package is None or "", it uses the current working directory without using a package. - Lissp files must know their package at compile time to resolve imports correctly. - """ - # TODO: allow pathname without + ".lissp"? - if package: - for module in modules: - transpile_module(package, module + ".lissp") - else: - for module in modules: - with open(module + ".lissp") as f: - code = f.read() - out = module + ".py" - _write_py(out, module, code) - - -def transpile_module( - package: resources.Package, - resource: Union[str, PurePath], - out: Union[None, str, bytes, Path] = None, -): - """Transpile a single submodule in a package.""" - code = resources.read_text(package, resource) - path: Path - with resources.path(package, resource) as path: - out = out or path.with_suffix(".py") - if isinstance(package, ModuleType): - package = package.__package__ - if isinstance(package, os.PathLike): - resource = resource.stem - _write_py(out, f"{package}.{resource.split('.')[0]}", code) +def transpile(package, *modules): + """Transpiles the named Python modules from Lissp. + A .lissp file of the same name must be present. If the package is + empty, `transpile` writes modules to the current working + directory without a package. + """ + t = transpile_package if package else transpile_file + for m in modules: + t(package, f"{m}.lissp") -def _write_py(out, qualname, code): - with open(out, "w") as f: - print(f"compiling {qualname} as", out, file=sys.stderr) - if code.startswith("#!"): # ignore shebang line - _, _, code = code.partition("\n") - f.write(Lissp(qualname, evaluate=True, filename=str(out)).compile(code)) +def transpile_package(package, resource): + """Locates & transpiles a packaged .lissp resource file to .py.""" + with resources.path(package, resource) as path: + transpile_file(package, path) -def main(): - """Calls `transpile` with arguments from `sys.argv`.""" - transpile(*sys.argv[1:]) +def transpile_file(package, path: Union[Path, str]): + """Transpiles a single .lissp file to .py in the same location. -if __name__ == "__main__": - # TODO: test CLI - main() + Code in .lissp files is executed upon compilation. This is necessary + because macro definitions can alter the compilation of subsequent + top-level forms. Lissp files must know their package at compile time + to resolve imports correctly. The package can be empty. + """ + path = Path(path).resolve(strict=True) + qualname = f"{package or ''}{'.' if package else ''}{PurePath(path.name).stem}" + python = Lissp( + qualname=qualname, evaluate=True, filename=str(path) + ).compile(re.sub(r'^#!.*\n', '', path.read_text())) + path.with_suffix('.py').write_text(python, 'utf8') From 2169f145492525a86d9f2d88a22dcce4eb058d3f Mon Sep 17 00:00:00 2001 From: gilch Date: Mon, 4 Oct 2021 20:59:01 -0600 Subject: [PATCH 2/3] Update docs about transpile --- docs/command_line_reference.rst | 26 ++++++++++++++------------ docs/faq.rst | 27 +++++++++++++++++++++------ 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/docs/command_line_reference.rst b/docs/command_line_reference.rst index 59598dc43..e23ecb7a7 100644 --- a/docs/command_line_reference.rst +++ b/docs/command_line_reference.rst @@ -32,15 +32,17 @@ whose minimal options were modeled after Python's most commonly used: The Lissp Compiler ------------------ -While the recommended way to compile Lissp modules is with -`transpile ` -calls in the REPL, the main module, and the package ``__init__.py`` files, -there is also a command-line interface in case that is needed by an external build system. - -The command usage is ``python -m hissp.reader package [module [module ...]]``, -and it takes the same arguments as ``transpile``. - -The package can be an empty string for top-level modules. -Remember to use the *module* names, not the *file* names. -E.g. ``python -m hissp.reader "" spam eggs``, -not ``python -m hissp.reader "" spam.lissp eggs.lissp``. +The recommended way to compile Lissp modules is with +`transpile ` calls in `__init__.py` files (or the main module). + +This can be done manually in the REPL. +However, an external build system may need to use shell commands. +It is possible to run transpile commands in the shell via `python -c` or `lissp -c`. + +For example, + +.. code-block:: shell + + $ alias lisspc="lissp -c '(hissp.reader..transpile_file None (getitem sys..argv -1))'" + $ lisspc spam.lissp + diff --git a/docs/faq.rst b/docs/faq.rst index 5183f136b..5f7a848cc 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -1222,12 +1222,27 @@ Or equivalently, in Python: transpile(__package__, "sausage", "bacon") -Consider putting the above in each package's ``__init__.py`` to -auto-compile each Hissp module in the package on package import during -development. You can disable it again on release, if desired, but this -gives you fine-grained control over what gets compiled when. Note that -you usually would want to recompile the whole project rather than only -the changed files like Python does, because macros run at compile time. +Consider putting the above in each package's ``__init__.py`` +to auto-compile each Hissp module in the package on package import during development. +This gives you fine-grained control over what gets compiled when. +If desired for a release, +you can remove these lines after compilation, +or disable them via some externally set parameter, +such as an environment variable, config file, or global "constant". + +For example, + +.. code-block:: python + + if __import__('os').getenv('AUTOCOMPILING_LISSP_PROJECT_FOO'): + from hissp.reader import transpile + + transpile(__package__, "sausage", "bacon") + + +Note that you usually *would* want to recompile the whole project +rather than only the changed files like Python does, +because macros run at compile time. Changing a macro in one file normally doesn't affect the code that uses it in other files until they are recompiled. From afe73727e1190ae4156f2ab967b39b300f0f846e Mon Sep 17 00:00:00 2001 From: gilch Date: Mon, 4 Oct 2021 21:00:43 -0600 Subject: [PATCH 3/3] Remove raw_input() from sphinx docs --- src/hissp/repl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hissp/repl.py b/src/hissp/repl.py index 6ca82045e..80808efd6 100644 --- a/src/hissp/repl.py +++ b/src/hissp/repl.py @@ -57,6 +57,7 @@ def runsource(self, source, filename="", symbol="single"): return super().runsource(source, filename, symbol) def raw_input(self, prompt=""): + """:meta private:""" prompt = {sys.ps2: ps2, sys.ps1: ps1}.get(prompt, prompt) return super().raw_input(prompt)