gilch / hissp

It's Python with a Lissp.
https://gitter.im/hissp-lang/community
Apache License 2.0
387 stars 11 forks source link

REPL clobbers Python prompts #119

Closed gilch closed 3 years ago

gilch commented 3 years ago

Not a new issue, but I'm writing it down so I don't forget to address it. I knew this was going to happen when I wrote the REPL code, but couldn't see a good way to avoid it at the time. Python's REPL prompts are stored in sys.ps1 and sys.ps2. Lissp's REPL is based on code.InteractiveConsole and overwrites these to be the Lissp prompts, because the superclass reads them from there. An unfortunate design flaw.

That means that if you start a code.interact() session from within the Lissp REPL, you keep getting the Lissp prompts, which is confusing. It's nice to be able to drop into Python from the same environment, so it could come up. The resulting Python console is perfectly functional, so perhaps this is a minor issue.

The methods are also too large to override easily, short of copy/pasting library code. The best available approach may be monkeypatching with unittest.mock.patch as briefly and as close to use as possible. It's times like this when I wish Python had dynamic variables like Lisp. Contextvars come close, but they're not a drop-in replacement for a global, since you still have to call the .get() method to dereference. Monkeypatching like this is somewhat brittle, but using the prompts from sys is documented behavior in the code module, so this case should work.

gilch commented 3 years ago

From the Python docs on sys.ps1 and ps2:

Their initial values in this case are '>>> ' and '... '. If a non-string object is assigned to either variable, its str() is re-evaluated each time the interpreter prepares to read a new interactive command; this can be used to implement a dynamic prompt.

Looks like input() calls str() on its prompt object, so this is more dynamic than I thought. Any class with a __str__ override could call .get() on a contextvar. Still doesn't cooperate that well if some other code tries to assign that though.

I also found a better place to override the prompt: the raw_input() method. I initially glossed over this because I thought it was only for getting input, but it also outputs the prompts. Monkeypatching will not be required.