From 7e633d77b693a25c5b16585e033c000c89689af7 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 12 Mar 2025 01:03:22 +0500 Subject: [PATCH 01/10] gh-130698: Add safe methods to get prompts for new REPL --- Lib/_pyrepl/reader.py | 26 +++++-- Lib/_pyrepl/utils.py | 6 ++ Lib/test/test_pyrepl/test_reader.py | 101 ++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 5 deletions(-) diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 4795c51296a500..68ab667ea02e89 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -31,6 +31,7 @@ from . import commands, console, input from .utils import ANSI_ESCAPE_SEQUENCE, wlen, str_width +from .utils import DEFAULT_PS1, DEFAULT_PS2, DEFAULT_PS3, DEFAULT_PS4 from .trace import trace @@ -531,22 +532,37 @@ def get_arg(self, default: int = 1) -> int: return default return self.arg + @staticmethod + def __get_prompt_str(prompt, default_prompt) -> str: + """ + Convert prompt object to string. + + If prompt raise BaseException, MemoryError and SystemError then stop + the REPL. For other exceptions return default_prompt. + """ + try: + return str(prompt) + except (MemoryError, SystemError): + raise + except Exception: + return default_prompt + def get_prompt(self, lineno: int, cursor_on_line: bool) -> str: """Return what should be in the left-hand margin for line 'lineno'.""" if self.arg is not None and cursor_on_line: - prompt = f"(arg: {self.arg}) " + prompt = self.__get_prompt_str(f"(arg: {self.arg}) ", DEFAULT_PS1) elif self.paste_mode and not self.in_bracketed_paste: prompt = "(paste) " elif "\n" in self.buffer: if lineno == 0: - prompt = self.ps2 + prompt = self.__get_prompt_str(self.ps2, DEFAULT_PS2) elif self.ps4 and lineno == self.buffer.count("\n"): - prompt = self.ps4 + prompt = self.__get_prompt_str(self.ps4, DEFAULT_PS4) else: - prompt = self.ps3 + prompt = self.__get_prompt_str(self.ps3, DEFAULT_PS3) else: - prompt = self.ps1 + prompt = self.__get_prompt_str(self.ps1, DEFAULT_PS1) if self.can_colorize: prompt = f"{ANSIColors.BOLD_MAGENTA}{prompt}{ANSIColors.RESET}" diff --git a/Lib/_pyrepl/utils.py b/Lib/_pyrepl/utils.py index 4651717bd7e121..4d30e7bdb992a3 100644 --- a/Lib/_pyrepl/utils.py +++ b/Lib/_pyrepl/utils.py @@ -5,6 +5,12 @@ ANSI_ESCAPE_SEQUENCE = re.compile(r"\x1b\[[ -@]*[A-~]") +DEFAULT_PS1 = ">>> " +DEFAULT_PS2 = "... " +DEFAULT_PS3 = "... " +DEFAULT_PS4 = "... " + + @functools.cache def str_width(c: str) -> int: if ord(c) < 128: diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py index 468c0c5e68d165..7d993d308c1629 100644 --- a/Lib/test/test_pyrepl/test_reader.py +++ b/Lib/test/test_pyrepl/test_reader.py @@ -7,6 +7,7 @@ from .support import handle_all_events, handle_events_narrow_console, code_to_events, prepare_reader, prepare_console from _pyrepl.console import Event from _pyrepl.reader import Reader +from _pyrepl.utils import DEFAULT_PS1, DEFAULT_PS2, DEFAULT_PS3, DEFAULT_PS4 class TestReader(TestCase): @@ -279,6 +280,106 @@ def test_prompt_length(self): self.assertEqual(prompt, "\033[0;32m樂>\033[0m> ") self.assertEqual(l, 5) + def test_prompt_ps1_raise_exception(self): + # Handles simple ASCII prompt + class Prompt: + def __str__(self): 1/0 + + 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, + ) + + prompt = reader.get_prompt(0, False) + self.assertEqual(prompt, DEFAULT_PS1) + + def test_prompt_ps2_raise_exception(self): + # Handles simple ASCII prompt + class Prompt: + def __str__(self): 1/0 + + def prepare_reader_keep_prompts(*args, **kwargs): + reader = prepare_reader(*args, **kwargs) + del reader.get_prompt + reader.ps1 = "+++ " + reader.ps2 = Prompt() + reader.ps3 = "--- " + reader.ps4 = "~~~ " + reader.can_colorize = False + reader.paste_mode = False + return reader + + events = code_to_events("if some_condition:\nsome_function()") + reader, _ = handle_events_narrow_console( + events, + prepare_reader=prepare_reader_keep_prompts, + ) + + prompt = reader.get_prompt(0, False) + self.assertEqual(prompt, DEFAULT_PS2) + + def test_prompt_ps3_raise_exception(self): + # Handles simple ASCII prompt + class Prompt: + def __str__(self): 1/0 + + def prepare_reader_keep_prompts(*args, **kwargs): + reader = prepare_reader(*args, **kwargs) + del reader.get_prompt + reader.ps1 = "+++ " + reader.ps2 = "--- " + reader.ps3 = Prompt() + reader.ps4 = "" + reader.can_colorize = False + reader.paste_mode = False + return reader + + events = code_to_events("if some_condition:\nsome_function()") + reader, _ = handle_events_narrow_console( + events, + prepare_reader=prepare_reader_keep_prompts, + ) + + prompt = reader.get_prompt(1, False) + self.assertEqual(prompt, DEFAULT_PS3) + + def test_prompt_ps4_raise_exception(self): + # Handles simple ASCII prompt + class Prompt: + def __str__(self): 1/0 + + def prepare_reader_keep_prompts(*args, **kwargs): + reader = prepare_reader(*args, **kwargs) + del reader.get_prompt + reader.ps1 = "+++ " + reader.ps2 = "--- " + reader.ps3 = "~~~ " + reader.ps4 = Prompt() + reader.can_colorize = False + reader.paste_mode = False + return reader + + events = code_to_events("if some_condition:\nsome_function()") + reader, _ = handle_events_narrow_console( + events, + prepare_reader=prepare_reader_keep_prompts, + ) + + prompt = reader.get_prompt(1, False) + self.assertEqual(prompt, DEFAULT_PS4) + def test_completions_updated_on_key_press(self): namespace = {"itertools": itertools} code = "itertools." From 707bd38ef63f90a90cbd4923094ef18b00e6ab6a Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 12 Mar 2025 01:08:59 +0500 Subject: [PATCH 02/10] Fix missing types --- Lib/_pyrepl/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 68ab667ea02e89..ca5f281934dfea 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -533,7 +533,7 @@ def get_arg(self, default: int = 1) -> int: return self.arg @staticmethod - def __get_prompt_str(prompt, default_prompt) -> str: + def __get_prompt_str(prompt: str, default_prompt: str) -> str: """ Convert prompt object to string. From ab84ee6c223ec231175c7f2df548447e04c5f1b8 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 13 Mar 2025 00:23:22 +0500 Subject: [PATCH 03/10] Fix handling of arg prompt and add tests for it - and for `unrecoverable` exceptions --- Lib/_pyrepl/reader.py | 5 +- Lib/test/test_pyrepl/test_reader.py | 83 ++++++++++++++++------------- 2 files changed, 49 insertions(+), 39 deletions(-) diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index ca5f281934dfea..51be592ae25584 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -551,7 +551,10 @@ def get_prompt(self, lineno: int, cursor_on_line: bool) -> str: """Return what should be in the left-hand margin for line 'lineno'.""" if self.arg is not None and cursor_on_line: - prompt = self.__get_prompt_str(f"(arg: {self.arg}) ", DEFAULT_PS1) + prompt = DEFAULT_PS1 + arg = self.__get_prompt_str(self.arg, "") + if arg: + prompt = f"(arg: {self.arg}) " elif self.paste_mode and not self.in_bracketed_paste: prompt = "(paste) " elif "\n" in self.buffer: diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py index 7d993d308c1629..b12578677f7af9 100644 --- a/Lib/test/test_pyrepl/test_reader.py +++ b/Lib/test/test_pyrepl/test_reader.py @@ -281,7 +281,7 @@ def test_prompt_length(self): self.assertEqual(l, 5) def test_prompt_ps1_raise_exception(self): - # Handles simple ASCII prompt + # Handles exceptions from ps1 prompt class Prompt: def __str__(self): 1/0 @@ -289,7 +289,7 @@ def prepare_reader_keep_prompts(*args, **kwargs): reader = prepare_reader(*args, **kwargs) del reader.get_prompt reader.ps1 = Prompt() - reader.ps2 = "+++ " + reader.ps2 = "... " reader.ps3 = "... " reader.ps4 = "" reader.can_colorize = False @@ -305,23 +305,23 @@ def prepare_reader_keep_prompts(*args, **kwargs): prompt = reader.get_prompt(0, False) self.assertEqual(prompt, DEFAULT_PS1) - def test_prompt_ps2_raise_exception(self): - # Handles simple ASCII prompt + def test_prompt_ps2_ps3_ps4_raise_exception(self): + # Handles exceptions from ps2, ps3 and ps4 prompts class Prompt: def __str__(self): 1/0 def prepare_reader_keep_prompts(*args, **kwargs): reader = prepare_reader(*args, **kwargs) del reader.get_prompt - reader.ps1 = "+++ " + reader.ps1 = Prompt() reader.ps2 = Prompt() - reader.ps3 = "--- " - reader.ps4 = "~~~ " + reader.ps3 = Prompt() + reader.ps4 = Prompt() reader.can_colorize = False reader.paste_mode = False return reader - events = code_to_events("if some_condition:\nsome_function()") + events = code_to_events("if some_condition:\nsome_function()\nsome_function()") reader, _ = handle_events_narrow_console( events, prepare_reader=prepare_reader_keep_prompts, @@ -330,43 +330,22 @@ def prepare_reader_keep_prompts(*args, **kwargs): prompt = reader.get_prompt(0, False) self.assertEqual(prompt, DEFAULT_PS2) - def test_prompt_ps3_raise_exception(self): - # Handles simple ASCII prompt - class Prompt: - def __str__(self): 1/0 - - def prepare_reader_keep_prompts(*args, **kwargs): - reader = prepare_reader(*args, **kwargs) - del reader.get_prompt - reader.ps1 = "+++ " - reader.ps2 = "--- " - reader.ps3 = Prompt() - reader.ps4 = "" - reader.can_colorize = False - reader.paste_mode = False - return reader - - events = code_to_events("if some_condition:\nsome_function()") - reader, _ = handle_events_narrow_console( - events, - prepare_reader=prepare_reader_keep_prompts, - ) - prompt = reader.get_prompt(1, False) self.assertEqual(prompt, DEFAULT_PS3) - def test_prompt_ps4_raise_exception(self): - # Handles simple ASCII prompt + prompt = reader.get_prompt(2, False) + self.assertEqual(prompt, DEFAULT_PS4) + + def test_prompt_arg_raise_exception(self): + # Handles exceptions from arg prompt class Prompt: def __str__(self): 1/0 + def __rmul__(self, b): return b + def prepare_reader_keep_prompts(*args, **kwargs): reader = prepare_reader(*args, **kwargs) del reader.get_prompt - reader.ps1 = "+++ " - reader.ps2 = "--- " - reader.ps3 = "~~~ " - reader.ps4 = Prompt() reader.can_colorize = False reader.paste_mode = False return reader @@ -377,8 +356,36 @@ def prepare_reader_keep_prompts(*args, **kwargs): prepare_reader=prepare_reader_keep_prompts, ) - prompt = reader.get_prompt(1, False) - self.assertEqual(prompt, DEFAULT_PS4) + reader.arg = Prompt() + prompt = reader.get_prompt(0, True) + self.assertEqual(prompt, DEFAULT_PS1) + + def test_prompt_raise_exception(self): + # Tests unrecoverable exceptions from prompts + cases = [ + (MemoryError, "No memory for prompt"), + (SystemError, "System error for prompt"), + ] + for cls, msg in cases: + with self.subTest(msg): + + class Prompt: + def __str__(self): raise cls(msg) + + def prepare_reader_keep_prompts(*args, **kwargs): + reader = prepare_reader(*args, **kwargs) + del reader.get_prompt + reader.ps1 = Prompt() + reader.can_colorize = False + reader.paste_mode = False + return reader + + with self.assertRaisesRegex(cls, msg): + events = code_to_events("a=1") + handle_events_narrow_console( + events, + prepare_reader=prepare_reader_keep_prompts, + ) def test_completions_updated_on_key_press(self): namespace = {"itertools": itertools} From 5d91db77e0d57444e47b4b6a8b0424c1f8d8095b Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 13 Mar 2025 00:30:26 +0500 Subject: [PATCH 04/10] Make mypy happy again --- Lib/_pyrepl/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 51be592ae25584..9200f5df7682c8 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -533,7 +533,7 @@ def get_arg(self, default: int = 1) -> int: return self.arg @staticmethod - def __get_prompt_str(prompt: str, default_prompt: str) -> str: + def __get_prompt_str(prompt: str | int, default_prompt: str) -> str: """ Convert prompt object to string. From e0b474bdbc60168af8de8ede81e7d69e356c2418 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 13 Mar 2025 00:40:13 +0500 Subject: [PATCH 05/10] Add news entry --- .../next/Library/2025-03-13-00-39-54.gh-issue-130698.o2aU3e.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-03-13-00-39-54.gh-issue-130698.o2aU3e.rst diff --git a/Misc/NEWS.d/next/Library/2025-03-13-00-39-54.gh-issue-130698.o2aU3e.rst b/Misc/NEWS.d/next/Library/2025-03-13-00-39-54.gh-issue-130698.o2aU3e.rst new file mode 100644 index 00000000000000..62a189b9fdea1b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-13-00-39-54.gh-issue-130698.o2aU3e.rst @@ -0,0 +1 @@ +Avoid exiting the new REPL when prompt object raises exception. From 02e7e84e1738ecc8a11b4e0a44f3adce55a15698 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sun, 20 Apr 2025 00:54:39 +0500 Subject: [PATCH 06/10] Update Misc/NEWS.d/next/Library/2025-03-13-00-39-54.gh-issue-130698.o2aU3e.rst Co-authored-by: Tomas R. --- .../next/Library/2025-03-13-00-39-54.gh-issue-130698.o2aU3e.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-03-13-00-39-54.gh-issue-130698.o2aU3e.rst b/Misc/NEWS.d/next/Library/2025-03-13-00-39-54.gh-issue-130698.o2aU3e.rst index 62a189b9fdea1b..609345dddc04fa 100644 --- a/Misc/NEWS.d/next/Library/2025-03-13-00-39-54.gh-issue-130698.o2aU3e.rst +++ b/Misc/NEWS.d/next/Library/2025-03-13-00-39-54.gh-issue-130698.o2aU3e.rst @@ -1 +1 @@ -Avoid exiting the new REPL when prompt object raises exception. +Avoid exiting the new REPL when prompt object raises an exception. From 4fc68532aa50b862e38c2763d5bef42396267b0c Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sun, 20 Apr 2025 01:44:45 +0500 Subject: [PATCH 07/10] Simplify tests for reader with buggy prompt --- Lib/test/test_pyrepl/test_reader.py | 93 +++++++++++++---------------- 1 file changed, 43 insertions(+), 50 deletions(-) diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py index e24f4adb0fafc7..0f652d494f68fe 100644 --- a/Lib/test/test_pyrepl/test_reader.py +++ b/Lib/test/test_pyrepl/test_reader.py @@ -12,6 +12,23 @@ from _pyrepl.utils import DEFAULT_PS1, DEFAULT_PS2, DEFAULT_PS3, DEFAULT_PS4 +def prepare_reader_with_prompt( + console, ps1=DEFAULT_PS1, ps2=DEFAULT_PS2, ps3=DEFAULT_PS3, ps4=DEFAULT_PS4): + reader = prepare_reader( + console, + can_colorize=False, + paste_mode=False, + ps1=ps1, + ps2=ps2, + ps3=ps3, + ps4=ps4 + ) + + # we should use original get_prompt from reader to get exceptions + del reader.get_prompt + return reader + + class TestReader(ScreenEqualMixin, TestCase): def test_calc_screen_wrap_simple(self): events = code_to_events(10 * "a") @@ -304,21 +321,14 @@ def test_prompt_ps1_raise_exception(self): class Prompt: def __str__(self): 1/0 - 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 + _prepare_reader = functools.partial( + prepare_reader_with_prompt, + ps1=Prompt(), + ) - events = code_to_events("a=1") - reader, _ = handle_events_narrow_console( - events, - prepare_reader=prepare_reader_keep_prompts, + reader, _ = handle_all_events( + events=code_to_events("a=1"), + prepare_reader=_prepare_reader ) prompt = reader.get_prompt(0, False) @@ -329,21 +339,17 @@ def test_prompt_ps2_ps3_ps4_raise_exception(self): class Prompt: def __str__(self): 1/0 - def prepare_reader_keep_prompts(*args, **kwargs): - reader = prepare_reader(*args, **kwargs) - del reader.get_prompt - reader.ps1 = Prompt() - reader.ps2 = Prompt() - reader.ps3 = Prompt() - reader.ps4 = Prompt() - reader.can_colorize = False - reader.paste_mode = False - return reader + _prepare_reader = functools.partial( + prepare_reader_with_prompt, + ps1=Prompt(), + ps2=Prompt(), + ps3=Prompt(), + ps4=Prompt(), + ) - events = code_to_events("if some_condition:\nsome_function()\nsome_function()") - reader, _ = handle_events_narrow_console( - events, - prepare_reader=prepare_reader_keep_prompts, + reader, _ = handle_all_events( + events=code_to_events("if cond:\nfunc()\nfunc()"), + prepare_reader=_prepare_reader ) prompt = reader.get_prompt(0, False) @@ -359,20 +365,11 @@ def test_prompt_arg_raise_exception(self): # Handles exceptions from arg prompt class Prompt: def __str__(self): 1/0 - def __rmul__(self, b): return b - def prepare_reader_keep_prompts(*args, **kwargs): - reader = prepare_reader(*args, **kwargs) - del reader.get_prompt - reader.can_colorize = False - reader.paste_mode = False - return reader - - events = code_to_events("if some_condition:\nsome_function()") - reader, _ = handle_events_narrow_console( - events, - prepare_reader=prepare_reader_keep_prompts, + reader, _ = handle_all_events( + events=code_to_events("if some_condition:\nsome_function()"), + prepare_reader=prepare_reader_with_prompt, ) reader.arg = Prompt() @@ -391,19 +388,15 @@ def test_prompt_raise_exception(self): class Prompt: def __str__(self): raise cls(msg) - def prepare_reader_keep_prompts(*args, **kwargs): - reader = prepare_reader(*args, **kwargs) - del reader.get_prompt - reader.ps1 = Prompt() - reader.can_colorize = False - reader.paste_mode = False - return reader + _prepare_reader = functools.partial( + prepare_reader_with_prompt, + ps1=Prompt(), + ) with self.assertRaisesRegex(cls, msg): - events = code_to_events("a=1") handle_events_narrow_console( - events, - prepare_reader=prepare_reader_keep_prompts, + events=code_to_events("a=1"), + prepare_reader=_prepare_reader, ) def test_completions_updated_on_key_press(self): From ba8633b349e097f77974de7b4f1761246ed6f1f1 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sun, 20 Apr 2025 01:44:56 +0500 Subject: [PATCH 08/10] Fix DEFAULT_PS2 string --- Lib/_pyrepl/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/utils.py b/Lib/_pyrepl/utils.py index e16d13a0f741ec..34b1ae083f393d 100644 --- a/Lib/_pyrepl/utils.py +++ b/Lib/_pyrepl/utils.py @@ -11,7 +11,7 @@ DEFAULT_PS1 = ">>> " -DEFAULT_PS2 = "... " +DEFAULT_PS2 = ">>> " DEFAULT_PS3 = "... " DEFAULT_PS4 = "... " From cac161dfec951623931805b1875358847e88aaac Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sun, 20 Apr 2025 01:55:11 +0500 Subject: [PATCH 09/10] Make __get_prompt_str less stricter --- Lib/_pyrepl/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 8bd7e659b2939d..7125d31c3c7876 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -476,7 +476,7 @@ def get_arg(self, default: int = 1) -> int: return self.arg @staticmethod - def __get_prompt_str(prompt: str | int, default_prompt: str) -> str: + def __get_prompt_str(prompt: object, default_prompt: str) -> str: """ Convert prompt object to string. From 60c189f53e9c9690a85d2fdc3abb6233ed1b640e Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sun, 20 Apr 2025 12:10:30 +0500 Subject: [PATCH 10/10] Update docstring for __get_prompt_str Co-authored-by: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> --- Lib/_pyrepl/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 7125d31c3c7876..79450536120196 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -480,7 +480,7 @@ def __get_prompt_str(prompt: object, default_prompt: str) -> str: """ Convert prompt object to string. - If prompt raise BaseException, MemoryError and SystemError then stop + If str(prompt) raises BaseException, MemoryError or SystemError then stop the REPL. For other exceptions return default_prompt. """ try: