Skip to content

gh-130698: Add safe methods to get prompts for new REPL #131110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

sergey-miryanov
Copy link
Contributor

@sergey-miryanov sergey-miryanov commented Mar 11, 2025

It fixes cases when ps1, ps2, ps3, ps4 raise exception from __str__.

This PR still misses tests for Reader.arg, but maybe you can review the whole solution - do I drive in the right direction?
Fixed for Reader.arg.

Comment on lines 8 to 11
DEFAULT_PS1 = ">>> "
DEFAULT_PS2 = "... "
DEFAULT_PS3 = "... "
DEFAULT_PS4 = "... "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these match?

ps1: str = "->> "
ps2: str = "/>> "
ps3: str = "|.. "
ps4: str = R"\__ "

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I believe this is should be like:

DEFAULT_PS1 = ">>> "
DEFAULT_PS2 = ">>> "
DEFAULT_PS3 = "... "
DEFAULT_PS4 = "... "

I had misread this

* ps1, ps2, ps3, ps4:
prompts. ps1 is the prompt for a one-line input; for a
multiline input it looks like:
ps2> first line of input goes here
ps3> second and further
ps3> lines get ps3
...
ps4> and the last one gets ps4
As with the usual top-level, you can set these to instances if

Comment on lines 288 to 303
def prepare_reader_keep_prompts(*args, **kwargs):
reader = prepare_reader(*args, **kwargs)
del reader.get_prompt
reader.ps1 = Prompt()
reader.ps2 = "... "
reader.ps3 = "... "
reader.ps4 = ""
reader.can_colorize = False
reader.paste_mode = False
return reader

events = code_to_events("a=1")
reader, _ = handle_events_narrow_console(
events,
prepare_reader=prepare_reader_keep_prompts,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use a slightly simpler setup code:

Suggested change
def prepare_reader_keep_prompts(*args, **kwargs):
reader = prepare_reader(*args, **kwargs)
del reader.get_prompt
reader.ps1 = Prompt()
reader.ps2 = "... "
reader.ps3 = "... "
reader.ps4 = ""
reader.can_colorize = False
reader.paste_mode = False
return reader
events = code_to_events("a=1")
reader, _ = handle_events_narrow_console(
events,
prepare_reader=prepare_reader_keep_prompts,
)
def prepare_reader(events):
console = FakeConsole(events)
config = ReadlineConfig(readline_completer=None)
reader = ReadlineAlikeReader(console=console, config=config)
reader.can_colorize = False
return reader
events = code_to_events("if some_condition:\nsome_function()")
reader = prepare_reader(events)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this doesn't simplify code. I have added more simpler one on my sight. Note, we should use handle_all_events (or handle_events_narrow_console) to process whole input string and get it in the reader.buffer to check if '\n' contains or not in buffer.

@@ -10,6 +10,12 @@
ZERO_WIDTH_TRANS = str.maketrans({"\x01": "", "\x02": ""})


DEFAULT_PS1 = ">>> "
DEFAULT_PS2 = ">>> "
Copy link
Member

@chris-eibl chris-eibl Apr 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
DEFAULT_PS2 = ">>> "
DEFAULT_PS2 = "... "

See https://docs.python.org/3/library/sys.html#sys.ps1

sys.ps1
sys.ps2
Strings specifying the primary and secondary prompt of the interpreter. These are only defined if the interpreter is in interactive mode. Their initial values in this case are '>>> ' and '... '.

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> def foo():
...     pass

Maybe here the DEFAULT_PS* shall be used, too? Are there more places?

ps1 = getattr(sys, "ps1", ">>> ")
ps2 = getattr(sys, "ps2", "... ")

IMHO, the comment in reader.py is wrong (or at least confusing):

* ps1, ps2, ps3, ps4:
prompts. ps1 is the prompt for a one-line input; for a
multiline input it looks like:
ps2> first line of input goes here
ps3> second and further
ps3> lines get ps3
...
ps4> and the last one gets ps4

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @tomasr8 pointed above

ps1: str = "->> "
ps2: str = "/>> "
ps3: str = "|.. "
ps4: str = R"\__ "

there are different defaults too. So IDK what is really default :)

Copy link
Contributor Author

@sergey-miryanov sergey-miryanov Apr 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe here the DEFAULT_PS* shall be used, too? Are there more places?

Yes, but I'm not sure that usage of DEFAULT_PS* approved overall.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK either, but IMHO we should stick with what is documented and the status quo, i.e. DEFAULT_PS2 = "... ".

The reason why

ps1: str = "->> "
ps2: str = "/>> "
ps3: str = "|.. "
ps4: str = R"\__ "

is not in effect:
# set sys.{ps1,ps2} just before invoking the interactive interpreter. This
# mimics what CPython does in pythonrun.c
if not hasattr(sys, "ps1"):
sys.ps1 = ">>> "
if not hasattr(sys, "ps2"):
sys.ps2 = "... "

and then later

ps1 = getattr(sys, "ps1", ">>> ")
ps2 = getattr(sys, "ps2", "... ")
try:
statement = multiline_input(more_lines, ps1, ps2)
except EOFError:
break

and then

def multiline_input(self, more_lines: MoreLinesCallable, ps1: str, ps2: str) -> str:
"""Read an input on possibly multiple lines, asking for more
lines as long as 'more_lines(unicodetext)' returns an object whose
boolean value is true.
"""
reader = self.get_reader()
saved = reader.more_lines
try:
reader.more_lines = more_lines
reader.ps1 = ps1
reader.ps2 = ps1
reader.ps3 = ps2
reader.ps4 = ""

So IMHO we should consistently use DEFAULT_PS1 and DEFAULT_PS2 in those code parts above, but let @pablogsal decide on it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover, this part

reader.ps1 = ps1 
reader.ps2 = ps1 
reader.ps3 = ps2 

IMHO explains this confusion

* ps1, ps2, ps3, ps4:
prompts. ps1 is the prompt for a one-line input; for a
multiline input it looks like:
ps2> first line of input goes here
ps3> second and further
ps3> lines get ps3
...
ps4> and the last one gets ps4

ISTM, this is an implementation detail, and ps2=ps1, etc, maps to what we are used to in the cPython REPL.

Most probably, because the new PYREPL comes from PyPy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover, this part

Yeah, I saw this and this is a reason that I changed DEFAULT_PS2 yesterday.

Most probably, because the new PYREPL comes from PyPy?

I'm not familiar with PyPy, so 🤷‍♂️

Co-authored-by: Chris Eibl <138194463+chris-eibl@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants