From be0abf841ba38d15579989ce5c03eba8be4cae63 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 13 Jul 2026 06:16:26 -0400 Subject: [PATCH 1/2] Fix bug introduced in REPL split and Dialog introduction --- mathics/__main__.py | 2 +- mathics/repl.py | 32 ++++++++++++++++++++++++------- test/test_repl.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 test/test_repl.py diff --git a/mathics/__main__.py b/mathics/__main__.py index 97db6787f..b251898d3 100755 --- a/mathics/__main__.py +++ b/mathics/__main__.py @@ -208,7 +208,7 @@ def dump_tracing_stats(): definitions = Definitions( add_builtin=True, extension_modules=tuple(extension_modules) ) - definitions.set_line_no(0) + definitions.set_line_no(1) shell = mathics.session.shell_session = TerminalShell( definitions, diff --git a/mathics/repl.py b/mathics/repl.py index a6b4b2dc7..e87288657 100644 --- a/mathics/repl.py +++ b/mathics/repl.py @@ -59,9 +59,9 @@ class TerminalShell(MathicsLineFeeder, SessionShell): def __init__( self, definitions, - colors, - want_readline, - want_completion, + colors: str, + want_readline: bool, + want_completion: bool, autoload=False, in_prefix: str = "In", out_prefix: str = "Out", @@ -77,6 +77,10 @@ def __init__( self.in_prefix = in_prefix self.out_prefix = out_prefix + # Keep track of whether input of a command spans more than one line. + # In prompting we omit "In[x]:= after the first line. + self.multiline_input = False + if want_readline: want_readline = have_readline @@ -183,7 +187,14 @@ def empty(self): return False def feed(self): + """ + Prompt for and read another line of input. + Keep track of the line number and note, after reading, + in self.multiline_input that if we need to read again, we have already prompted + for the input. + """ result = self.read_line(self.in_prompt) + "\n" + self.multiline_input = True if mathics_scanner.location.TRACK_LOCATIONS: self.container.append(self.source_text) if result == "\n": @@ -201,11 +212,17 @@ def get_completion_candidates(self, text): def in_prompt(self) -> str: """ Return the prompt string to be shown before reading input. + If this is a continuation line for some logical input, + the prefix returned contains spaces only. """ - next_line_number = self.last_line_number + 1 - return "{2}{0}[{3}{1}{4}]:= {5}".format( - self.in_prefix, next_line_number, *self.incolors - ) + line_number = self.last_line_number + if self.multiline_input: + indent = len(f"In[{line_number}]:= ") + return " " * indent + else: + return "{2}{0}[{3}{1}{4}]:= {5}".format( + self.in_prefix, line_number, *self.incolors + ) @property def last_line_number(self): @@ -340,6 +357,7 @@ def interactive_eval_loop( # has access to this evaluation.shell = shell + shell.multiline_input = False query, source_code = evaluation.parse_feeder_returning_code(shell) if mathics_core.PRE_EVALUATION_HOOK is not None: mathics_core.PRE_EVALUATION_HOOK(query, evaluation) diff --git a/test/test_repl.py b/test/test_repl.py new file mode 100644 index 000000000..ccb1243cb --- /dev/null +++ b/test/test_repl.py @@ -0,0 +1,46 @@ +""" +Test mathics.repl +""" + +from mathics.core.definitions import Definitions +from mathics.repl import TerminalShell + + +def fake_readline(prompt: str) -> str: + return "Fake readline" + + +definitions = Definitions(add_builtin=False, extension_modules=tuple()) +definitions.set_line_no(1) + +shell = TerminalShell( + definitions, + "NoColor", + want_readline=False, + want_completion=False, + autoload=False, +) + + +def test_in_prompt(): + shell.read_line = fake_readline + assert shell.in_prompt == "In[1]:= ", "We should start out with In[1]:=" + shell.feed() + assert ( + shell.in_prompt == " " + ), "Multiline prompt should be blanks of the length of 'In[1]:= '" + definitions.set_line_no(9) + shell.multiline_input = False + assert ( + shell.in_prompt == "In[9]:= " + ), "Setting line number to the last length-one digit" + shell.feed() + assert ( + shell.in_prompt == " " + ), "Multiline prompt should be blanks of the length of 'In[9]:= '" + definitions.set_line_no(10) + shell.multiline_input = False + shell.feed() + assert ( + shell.in_prompt == " " + ), "Multiline prompt should be blanks of the length of 'In[10]:= '" From 8381d8883d98c2ba599083cf2eb29c34658c5616 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 13 Jul 2026 21:01:39 -0400 Subject: [PATCH 2/2] Small tweaks. * Simplify line number setting. * Resort some property methods after removing get_ prefix * More repl's reset multiline input True --- mathics/__main__.py | 4 +-- mathics/builtin/fileformats/__init__.py | 4 +++ mathics/builtin/import_export/importexport.py | 23 ++++++++++++++++ mathics/repl.py | 27 ++++++++++--------- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/mathics/__main__.py b/mathics/__main__.py index b251898d3..19a0215e9 100755 --- a/mathics/__main__.py +++ b/mathics/__main__.py @@ -221,7 +221,7 @@ def dump_tracing_stats(): if args.initfile: feeder = MathicsFileLineFeeder(args.initfile) eval_loop(feeder, shell) - definitions.set_line_no(0) + definitions.set_line_no(1) if args.post_mortem: try: @@ -241,7 +241,7 @@ def dump_tracing_stats(): eval_loop(feeder, shell) if args.persist: - definitions.set_line_no(0) + definitions.set_line_no(1) elif not args.code: return exit_rc diff --git a/mathics/builtin/fileformats/__init__.py b/mathics/builtin/fileformats/__init__.py index 1614dbf19..dd9bddb28 100644 --- a/mathics/builtin/fileformats/__init__.py +++ b/mathics/builtin/fileformats/__init__.py @@ -38,3 +38,7 @@ # This tells documentation how to sort this module # Here we are also hiding "file_io" since this can erroneously appear at the top level. sort_order = "mathics.builtin.importing-export-file-formats" + +# The Built-in Functions are defined in a separate context under the +# System`. For example System`HTML` and System`XML. This is done to not +# pollute the System` namespace. diff --git a/mathics/builtin/import_export/importexport.py b/mathics/builtin/import_export/importexport.py index a57120ac5..1049411ed 100644 --- a/mathics/builtin/import_export/importexport.py +++ b/mathics/builtin/import_export/importexport.py @@ -28,6 +28,7 @@ does the corresponding thing for :Import: /doc/reference-of-built-in-symbols/inputoutput-files-and-filesystem/importing-and-exporting/import. + """ import base64 @@ -468,6 +469,28 @@ class Import(Builtin): r"read and convert to \Mathics3 some or all elements of structured file" ) + def import_setup(self, source, evaluation) -> tuple: + # Check filename + path = source.to_python() + if not (isinstance(path, str) and path[0] == path[-1] == '"'): + evaluation.message("Import", "chtype", source) + return SymbolFailed, None + + # Load local file + if path[0] == path[-1] == '"': + path = path[1:-1] + findfile = eval_FindFile(path) + + if findfile is None: + evaluation.message("Import", "nffil") + return SymbolFailed, None + + return findfile, eval_FileFormat(findfile.value).value + + def eval(self, source, evaluation, options={}): + "Import[source_, OptionsPattern[]]" + return self.eval_element_list(source, ListExpression(), evaluation, options) + def eval_elements_query(self, source, evaluation, options={}): """Import[source_String, "Elements", OptionsPattern[]]""" _, file_format = import_setup_check(source, evaluation) diff --git a/mathics/repl.py b/mathics/repl.py index e87288657..a09748a8e 100644 --- a/mathics/repl.py +++ b/mathics/repl.py @@ -208,6 +208,19 @@ def get_completion_candidates(self, text): matches = [strip_context(m) for m in matches] return matches + def get_out_prompt(self, form=None) -> str: + """ + Return a prompt string to be shown before showing output. + """ + line_number = self.last_line_number + if form: + return "{3}{0}[{4}{1}{5}]//{2}= {6}".format( + self.out_prefix, line_number, form, *self.outcolors + ) + return "{2}{0}[{3}{1}{4}]= {5}".format( + self.out_prefix, line_number, *self.outcolors + ) + @property def in_prompt(self) -> str: """ @@ -231,19 +244,6 @@ def last_line_number(self): """ return self.definitions.get_line_no() - def get_out_prompt(self, form=None) -> str: - """ - Return a prompt string to be shown before showing output. - """ - line_number = self.last_line_number - if form: - return "{3}{0}[{4}{1}{5}]//{2}= {6}".format( - self.out_prefix, line_number, form, *self.outcolors - ) - return "{2}{0}[{3}{1}{4}]= {5}".format( - self.out_prefix, line_number, *self.outcolors - ) - def out_callback(self, out, fmt=None): print(self.to_output(str(out), fmt)) @@ -324,6 +324,7 @@ def eval_loop(feeder: MathicsFileLineFeeder, shell: TerminalShell): catch_interrupt=False, ) + shell.multiline_input = False query = evaluation.parse_feeder(feeder) if query is None: continue