Source code for hissp.repl

# Copyright 2020, 2021, 2022, 2024 Matthew Egan Odendahl
# SPDX-License-Identifier: Apache-2.0
"""
The Lissp Read-Evaluate-Print Loop. For interactive use.
"""

import sys
from code import InteractiveConsole
from contextlib import suppress
from types import ModuleType

from hissp.compiler import CompileError
from hissp.reader import Lissp, SoftSyntaxError

ps1 = "#> "
"""String specifying the primary prompt of the `LisspREPL`."""

ps2 = "#.."
"""String specifying the secondary (continuation) prompt of the `LisspREPL`."""


[docs]class LisspREPL(InteractiveConsole): """Lissp's `Read-Evaluate-Print Loop`, layered on Python's. You can initialize the REPL with a locals dict, which is useful for debugging other modules. See `hissp.subrepl`. Call :func:`interact` to start. """ # locals shadows the builtin, but that's the name in the superclass. def __init__(self, locals=None, filename="<console>"): super().__init__(locals, filename) self.lissp = Lissp(locals.get("__name__", "__main__"), locals) self.locals = self.lissp.env def runsource(self, source, filename="<input>", symbol="single"): """:meta private:""" try: self.lissp.filename = filename source = self.lissp.compile(source) except SoftSyntaxError: return True except CompileError as e: print(f"{sys.ps1}# CompileError", file=sys.stderr) print(e, file=sys.stderr) return False except SyntaxError: self.showsyntaxerror() return False except BaseException: print(f"{sys.ps1}# Compilation failed!", file=sys.stderr) self.showtraceback() return False print(sys.ps1, source.replace("\n", f"\n{sys.ps2}"), sep="", file=sys.stderr) fn = f"<Compiled Hissp of {filename}:\n{self.lissp.compiler.linenos(source)}\n>" if super().runsource(source, fn, symbol): if super().runsource(source + "\n", fn, symbol): try: raise compile(source, fn, symbol) except SyntaxError: self.showsyntaxerror() return False def raw_input(self, prompt=""): """:meta private:""" prompt = {sys.ps2: ps2, sys.ps1: ps1}.get(prompt, prompt) return super().raw_input(prompt)
[docs] def interact(self, banner=None, exitmsg=None): """Imports `readline` if available, then ``super().interact()``.""" with suppress(ImportError): # noinspection PyUnresolvedReferences import readline return super().interact(banner, exitmsg)
[docs]def interact(locals=None): """Convenience function to start a `LisspREPL`. Uses the calling frame's globals and locals as ``locals`` if not provided. Unlike `hissp.repl.main`, no ``_macros_`` are added to the locals to avoid clobbering an existing namespace. """ if locals is None: import inspect frame = inspect.currentframe().f_back locals = {**frame.f_globals, **frame.f_locals} LisspREPL(locals=locals).interact()
def force_main(): """:meta private:""" # Creates a new ``__main__`` to take the place of the current # ``__main__`` module. __main__ = ModuleType("__main__") sys.modules["__main__"] = __main__ sys.path.insert(0, "") return __main__
[docs]def main(__main__): """`REPL` command-line entry point. Adds `_macro_` class (based on `hissp.macros._macro_`), making the bundled `macros` immediately available `unqualified`. """ repl = LisspREPL(locals=__main__.__dict__) add_macro_ = "class _macro_(__import__('hissp')._macro_):0\n" super(InteractiveConsole, repl).runsource(add_macro_) repl.interact()