From f3295417cdfe7a93986da714e45647706128c9c7 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 30 Jun 2026 13:07:36 -0400 Subject: [PATCH 1/7] Split out REPL for use in Dialog --- mathics/__main__.py | 361 +------------------ mathics/builtin/{ => tuning_debug}/trace.py | 0 mathics/repl.py | 364 ++++++++++++++++++++ 3 files changed, 370 insertions(+), 355 deletions(-) rename mathics/builtin/{ => tuning_debug}/trace.py (100%) create mode 100644 mathics/repl.py diff --git a/mathics/__main__.py b/mathics/__main__.py index 9f2222afa..b033d7edc 100755 --- a/mathics/__main__.py +++ b/mathics/__main__.py @@ -11,33 +11,20 @@ import argparse import atexit import cProfile -import locale -import os import os.path as osp -import re -import subprocess import sys -from typing import List, Optional +from typing import Optional -import mathics_scanner.location -from mathics_scanner.location import ContainerKind - -import mathics.core as mathics_core from mathics import __version__, license_string, settings, version_string -from mathics.builtin.trace import TraceBuiltins, traced_apply_function -from mathics.core.atoms import String +from mathics.builtin.tuning_debug.trace import TraceBuiltins, traced_apply_function from mathics.core.definitions import Definitions, Symbol -from mathics.core.evaluation import Evaluation, Output -from mathics.core.expression import Expression +from mathics.core.evaluation import Evaluation from mathics.core.load_builtin import import_and_load_builtins -from mathics.core.parser import MathicsFileLineFeeder, MathicsLineFeeder +from mathics.core.parser import MathicsFileLineFeeder from mathics.core.rules import FunctionApplyRule -from mathics.core.streams import stream_manager -from mathics.core.symbols import SymbolNull, strip_context +from mathics.core.symbols import SymbolNull from mathics.eval.files_io.files import set_input_var -from mathics.eval.files_io.read import channel_to_stream -from mathics.interrupt import setup_signal_handler -from mathics.session import autoload_files +from mathics.repl import TerminalOutput, TerminalShell, eval_loop, interactive_eval_loop from mathics.settings import DATA_DIR, USER_PACKAGE_DIR, ensure_directory from mathics.timing import show_lru_cache_statistics @@ -47,342 +34,6 @@ import_and_load_builtins() -try: - import readline -except ImportError: - - def user_write_history_file(): - return - - pass -else: - # Set up mathics3 configuration directory - CONFIGHOME = os.environ.get("XDG_CONFIG_HOME", osp.expanduser("~/.config")) - CONFIGDIR = osp.join(CONFIGHOME, "Mathics3") - os.makedirs(CONFIGDIR, exist_ok=True) - HISTFILE = os.environ.get("MATHICS3_HISTFILE", osp.join(CONFIGDIR, "history")) - - def user_write_history_file(): - try: - # print(f"Writing {HISTFILE}") - readline.write_history_file(HISTFILE) - except: # noqa - pass - - -def get_srcdir(): - filename = osp.normcase(osp.dirname(osp.abspath(__file__))) - return osp.realpath(filename) - - -def show_echo(query, evaluation): - echovar = evaluation.definitions.get_ownvalue("System`$Echo") - if not isinstance(echovar, Expression) or not echovar.has_form("List", None): - return - - for element in echovar.elements: - if isinstance(element, String) and element.value == "stdout": - stream = stream_manager.lookup_stream(1) - else: - strm = channel_to_stream(element, mode="w") - if strm is None: - continue - stream = stream_manager.lookup_stream(strm.elements[1].value) - if stream is None or stream.io is None or stream.io.closed: - continue - if stream is not None and stream.io is not None: - stream.io.write(query + "\n") - - -class TerminalShell(MathicsLineFeeder): - def __init__( - self, - definitions, - colors, - want_readline, - want_completion, - autoload=False, - in_prefix: str = "In", - out_prefix: str = "Out", - ): - super(TerminalShell, self).__init__([], ContainerKind.STREAM) - self.input_encoding = locale.getpreferredencoding() - - # is_inside_interrupt is set True when shell has been - # interrupted via an interrupt handler. - self.is_inside_interrupt = False - - self.lineno = 0 - self.in_prefix = in_prefix - self.out_prefix = out_prefix - - # Try importing readline to enable arrow keys support etc. - self.using_readline = False - try: - if want_readline: - try: - # Load history from file - readline.read_history_file(HISTFILE) - atexit.register(user_write_history_file) - except FileNotFoundError: - # Create an empty history file. - with open(HISTFILE, "w"): - pass - atexit.register(user_write_history_file) - except IOError: - pass - except: # noqa - # PyPy read_history_file fails - pass - self.using_readline = sys.stdin.isatty() and sys.stdout.isatty() - self.ansi_color_re = re.compile("\033\\[[0-9;]+m") - if want_completion: - readline.set_completer( - lambda text, state: self.complete_symbol_name(text, state) - ) - - # Make _ a delimiter, but not $ or ` - readline.set_completer_delims( - " \t\n_~!@#%^&*()-=+[{]}\\|;:'\",<>/?" - ) - - readline.parse_and_bind("tab: complete") - self.completion_candidates: List[str] = [] - - except ImportError: - pass - - # Try importing colorama to escape ansi sequences for cross platform - # colors - try: - from colorama import init as colorama_init - except ImportError: - colors = "NoColor" - else: - colorama_init() - if colors is None: - terminal_supports_color = ( - sys.stdout.isatty() and os.getenv("TERM") != "dumb" - ) - colors = "Linux" if terminal_supports_color else "NoColor" - - color_schemes = { - "NOCOLOR": (["", "", "", ""], ["", "", "", ""]), - "NONE": (["", "", "", ""], ["", "", "", ""]), - "LINUX": ( - ["\033[32m", "\033[1m", "\033[0m\033[32m", "\033[39m"], - ["\033[31m", "\033[1m", "\033[0m\033[31m", "\033[39m"], - ), - "LIGHTBG": ( - ["\033[34m", "\033[1m", "\033[22m", "\033[39m"], - ["\033[31m", "\033[1m", "\033[22m", "\033[39m"], - ), - } - - # Handle any case by using .upper() - term_colors = color_schemes.get(colors.upper()) - if term_colors is None: - out_msg = "The 'colors' argument must be {0} or None" - print(out_msg.format(repr(list(color_schemes.keys())))) - sys.exit() - - self.incolors, self.outcolors = term_colors - self.definitions = definitions - if autoload: - autoload_files(definitions, get_srcdir(), "autoload-cli") - - def get_last_line_number(self): - return self.definitions.get_line_no() - - def get_in_prompt(self): - next_line_number = self.get_last_line_number() + 1 - if self.lineno > 0: - return " " * len("{0}[{1}]:= ".format(self.in_prefix, next_line_number)) - else: - return "{2}{0}[{3}{1}{4}]:= {5}".format( - self.in_prefix, next_line_number, *self.incolors - ) - - def get_out_prompt(self, form=None): - line_number = self.get_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 to_output(self, text, form=None): - line_number = self.get_last_line_number() - newline = "\n" + " " * len("Out[{0}]= ".format(line_number)) - if form: - newline += (len(form) + 2) * " " - return newline.join(text.splitlines()) - - def out_callback(self, out, fmt=None): - print(self.to_output(str(out), fmt)) - - def read_line(self, prompt): - if self.using_readline: - return self.rl_read_line(prompt) - return input(prompt) - - def print_result(self, result, no_out_prompt=False, strict_wl_output=False): - if result is None or result.last_eval is SymbolNull: - # Following WMA CLI, if the result is `SymbolNull`, just print an empty line. - print("") - return - - form = result.form - last_eval = result.last_eval - - eval_type = None - if last_eval is not None: - try: - eval_type = last_eval.get_head_name() - except Exception: - print(sys.exc_info()[1]) - return - - out_str = str(result.result) - if eval_type == "System`String" and not strict_wl_output: - out_str = '"' + out_str.replace('"', r"\"") + '"' - if eval_type == "System`Graph": - out_str = "-Graph-" - - output = self.to_output(out_str, form) - mess = self.get_out_prompt(form) if not no_out_prompt else "" - print(mess + output + "\n") - - def rl_read_line(self, prompt): - # Wrap ANSI colour sequences in \001 and \002, so readline - # knows that they're nonprinting. - prompt = self.ansi_color_re.sub(lambda m: "\001" + m.group(0) + "\002", prompt) - - return input(prompt) - - def complete_symbol_name(self, text, state): - try: - return self._complete_symbol_name(text, state) - except Exception: - # any exception thrown inside the completer gets silently - # thrown away otherwise - print("Unhandled error in readline completion") - - def _complete_symbol_name(self, text, state): - # The readline module calls this function repeatedly, - # increasing 'state' each time and expecting one string to be - # returned per call. - - if state == 0: - self.completion_candidates = self.get_completion_candidates(text) - - try: - return self.completion_candidates[state] - except IndexError: - return None - - def get_completion_candidates(self, text): - matches = self.definitions.get_matching_names(text + "*") - if "`" not in text: - matches = [strip_context(m) for m in matches] - return matches - - def reset_lineno(self): - self.lineno = 0 - - def feed(self): - result = self.read_line(self.get_in_prompt()) + "\n" - if mathics_scanner.location.TRACK_LOCATIONS: - self.container.append(self.source_text) - if result == "\n": - return "" # end of input - self.lineno += 1 - return result - - def empty(self): - return False - - -class TerminalOutput(Output): - def max_stored_size(self, output_settings): - return None - - def __init__(self, shell): - self.shell = shell - - def out(self, out): - return self.shell.out_callback(out) - - -def eval_loop(feeder: MathicsFileLineFeeder, shell: TerminalShell): - """ - A read eval/loop for things having file input `feeder`. - `shell` is a shell session - """ - try: - while not feeder.empty(): - evaluation = Evaluation( - shell.definitions, - output=TerminalOutput(shell), - catch_interrupt=False, - ) - - query = evaluation.parse_feeder(feeder) - if query is None: - continue - evaluation.evaluate(query, timeout=settings.TIMEOUT) - except KeyboardInterrupt: - print("\nKeyboardInterrupt") - - -def interactive_eval_loop( - shell: TerminalShell, full_form: bool, strict_wl_output: bool -): - """ - A read eval/loop for an interactive session. - `shell` is a shell session - """ - setup_signal_handler() - while True: - try: - evaluation = Evaluation(shell.definitions, output=TerminalOutput(shell)) - - # Store shell into the evaluation so that an interrupt handler - # has access to this - evaluation.shell = shell - - 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) - - show_echo(source_code, evaluation) - if len(source_code) and source_code[0] == "!": - if not settings.ENABLE_SYSTEM_COMMANDS: - evaluation.message("Run", "dis") - else: - subprocess.run(source_code[1:], shell=True) - shell.definitions.increment_line_no(1) - continue - if query is None: - continue - if full_form: - print(query) - result = evaluation.evaluate(query, timeout=settings.TIMEOUT) - if result is not None: - shell.print_result(result, strict_wl_output=strict_wl_output) - except KeyboardInterrupt: - print("\nKeyboardInterrupt") - except EOFError: - print("\n\nGoodbye!\n") - break - except SystemExit: - # raise to pass the error code on, e.g. Quit[1] - raise - finally: - shell.reset_lineno() - class VersionAction(argparse.Action): def __init__(self, option_strings, version=Optional[str], **kwargs): diff --git a/mathics/builtin/trace.py b/mathics/builtin/tuning_debug/trace.py similarity index 100% rename from mathics/builtin/trace.py rename to mathics/builtin/tuning_debug/trace.py diff --git a/mathics/repl.py b/mathics/repl.py new file mode 100644 index 000000000..068d48496 --- /dev/null +++ b/mathics/repl.py @@ -0,0 +1,364 @@ +""" " +Terminal handling for command-line REPL. Also used by Dialog[] in mathics-core, +but not used by other front-ends. + +""" + +import atexit +import locale +import os +import os.path as osp +import re +import subprocess +import sys +from typing import List + +from mathics_scanner.location import ContainerKind + +import mathics.core as mathics_core +from mathics import settings +from mathics.core.atoms import String +from mathics.core.evaluation import Evaluation, Output +from mathics.core.expression import Expression +from mathics.core.parser import MathicsFileLineFeeder, MathicsLineFeeder +from mathics.core.streams import stream_manager +from mathics.core.symbols import SymbolNull, strip_context +from mathics.eval.files_io.read import channel_to_stream +from mathics.interrupt import setup_signal_handler +from mathics.session import autoload_files + +try: + import readline +except ImportError: + + def user_write_history_file(): + return + + pass +else: + # Set up mathics3 configuration directory + CONFIGHOME = os.environ.get("XDG_CONFIG_HOME", osp.expanduser("~/.config")) + CONFIGDIR = osp.join(CONFIGHOME, "Mathics3") + os.makedirs(CONFIGDIR, exist_ok=True) + HISTFILE = os.environ.get("MATHICS3_HISTFILE", osp.join(CONFIGDIR, "history")) + + def user_write_history_file(): + try: + # print(f"Writing {HISTFILE}") + readline.write_history_file(HISTFILE) + except: # noqa + pass + + +def get_srcdir(): + filename = osp.normcase(osp.dirname(osp.abspath(__file__))) + return osp.realpath(filename) + + +class TerminalShell(MathicsLineFeeder): + def __init__( + self, + definitions, + colors, + want_readline, + want_completion, + autoload=False, + in_prefix: str = "In", + out_prefix: str = "Out", + ): + super(TerminalShell, self).__init__([], ContainerKind.STREAM) + self.input_encoding = locale.getpreferredencoding() + + # is_inside_interrupt is set True when shell has been + # interrupted via an interrupt handler. + self.is_inside_interrupt = False + + self.lineno = 0 + self.in_prefix = in_prefix + self.out_prefix = out_prefix + + # Try importing readline to enable arrow keys support etc. + self.using_readline = False + try: + if want_readline: + try: + # Load history from file + readline.read_history_file(HISTFILE) + atexit.register(user_write_history_file) + except FileNotFoundError: + # Create an empty history file. + with open(HISTFILE, "w"): + pass + atexit.register(user_write_history_file) + except IOError: + pass + except: # noqa + # PyPy read_history_file fails + pass + self.using_readline = sys.stdin.isatty() and sys.stdout.isatty() + self.ansi_color_re = re.compile("\033\\[[0-9;]+m") + if want_completion: + readline.set_completer( + lambda text, state: self.complete_symbol_name(text, state) + ) + + # Make _ a delimiter, but not $ or ` + readline.set_completer_delims( + " \t\n_~!@#%^&*()-=+[{]}\\|;:'\",<>/?" + ) + + readline.parse_and_bind("tab: complete") + self.completion_candidates: List[str] = [] + + except ImportError: + pass + + # Try importing colorama to escape ansi sequences for cross platform + # colors + try: + from colorama import init as colorama_init + except ImportError: + colors = "NoColor" + else: + colorama_init() + if colors is None: + terminal_supports_color = ( + sys.stdout.isatty() and os.getenv("TERM") != "dumb" + ) + colors = "Linux" if terminal_supports_color else "NoColor" + + color_schemes = { + "NOCOLOR": (["", "", "", ""], ["", "", "", ""]), + "NONE": (["", "", "", ""], ["", "", "", ""]), + "LINUX": ( + ["\033[32m", "\033[1m", "\033[0m\033[32m", "\033[39m"], + ["\033[31m", "\033[1m", "\033[0m\033[31m", "\033[39m"], + ), + "LIGHTBG": ( + ["\033[34m", "\033[1m", "\033[22m", "\033[39m"], + ["\033[31m", "\033[1m", "\033[22m", "\033[39m"], + ), + } + + # Handle any case by using .upper() + term_colors = color_schemes.get(colors.upper()) + if term_colors is None: + out_msg = "The 'colors' argument must be {0} or None" + print(out_msg.format(repr(list(color_schemes.keys())))) + sys.exit() + + self.incolors, self.outcolors = term_colors + self.definitions = definitions + if autoload: + autoload_files(definitions, get_srcdir(), "autoload-cli") + + def get_last_line_number(self): + return self.definitions.get_line_no() + + def get_in_prompt(self): + next_line_number = self.get_last_line_number() + 1 + if self.lineno > 0: + return " " * len("{0}[{1}]:= ".format(self.in_prefix, next_line_number)) + else: + return "{2}{0}[{3}{1}{4}]:= {5}".format( + self.in_prefix, next_line_number, *self.incolors + ) + + def get_out_prompt(self, form=None): + line_number = self.get_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 to_output(self, text, form=None): + line_number = self.get_last_line_number() + newline = "\n" + " " * len("Out[{0}]= ".format(line_number)) + if form: + newline += (len(form) + 2) * " " + return newline.join(text.splitlines()) + + def out_callback(self, out, fmt=None): + print(self.to_output(str(out), fmt)) + + def read_line(self, prompt): + if self.using_readline: + return self.rl_read_line(prompt) + return input(prompt) + + def print_result(self, result, no_out_prompt=False, strict_wl_output=False): + if result is None or result.last_eval is SymbolNull: + # Following WMA CLI, if the result is `SymbolNull`, just print an empty line. + print("") + return + + form = result.form + last_eval = result.last_eval + + eval_type = None + if last_eval is not None: + try: + eval_type = last_eval.get_head_name() + except Exception: + print(sys.exc_info()[1]) + return + + out_str = str(result.result) + if eval_type == "System`String" and not strict_wl_output: + out_str = '"' + out_str.replace('"', r"\"") + '"' + if eval_type == "System`Graph": + out_str = "-Graph-" + + output = self.to_output(out_str, form) + mess = self.get_out_prompt(form) if not no_out_prompt else "" + print(mess + output + "\n") + + def rl_read_line(self, prompt): + # Wrap ANSI colour sequences in \001 and \002, so readline + # knows that they're nonprinting. + prompt = self.ansi_color_re.sub(lambda m: "\001" + m.group(0) + "\002", prompt) + + return input(prompt) + + def complete_symbol_name(self, text, state): + try: + return self._complete_symbol_name(text, state) + except Exception: + # any exception thrown inside the completer gets silently + # thrown away otherwise + print("Unhandled error in readline completion") + + def _complete_symbol_name(self, text, state): + # The readline module calls this function repeatedly, + # increasing 'state' each time and expecting one string to be + # returned per call. + + if state == 0: + self.completion_candidates = self.get_completion_candidates(text) + + try: + return self.completion_candidates[state] + except IndexError: + return None + + def get_completion_candidates(self, text): + matches = self.definitions.get_matching_names(text + "*") + if "`" not in text: + matches = [strip_context(m) for m in matches] + return matches + + def reset_lineno(self): + self.lineno = 0 + + def feed(self): + result = self.read_line(self.get_in_prompt()) + "\n" + if mathics_scanner.location.TRACK_LOCATIONS: + self.container.append(self.source_text) + if result == "\n": + return "" # end of input + self.lineno += 1 + return result + + def empty(self): + return False + + +class TerminalOutput(Output): + def max_stored_size(self, output_settings): + return None + + def __init__(self, shell): + self.shell = shell + + def out(self, out): + return self.shell.out_callback(out) + + +def eval_loop(feeder: MathicsFileLineFeeder, shell: TerminalShell): + """ + A read eval/loop for things having file input `feeder`. + `shell` is a shell session + """ + try: + while not feeder.empty(): + evaluation = Evaluation( + shell.definitions, + output=TerminalOutput(shell), + catch_interrupt=False, + ) + + query = evaluation.parse_feeder(feeder) + if query is None: + continue + evaluation.evaluate(query, timeout=settings.TIMEOUT) + except KeyboardInterrupt: + print("\nKeyboardInterrupt") + + +def interactive_eval_loop( + shell: TerminalShell, full_form: bool, strict_wl_output: bool +): + """ + A read eval/loop for an interactive session. + `shell` is a shell session + """ + setup_signal_handler() + while True: + try: + evaluation = Evaluation(shell.definitions, output=TerminalOutput(shell)) + + # Store shell into the evaluation so that an interrupt handler + # has access to this + evaluation.shell = shell + + 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) + + show_echo(source_code, evaluation) + if len(source_code) and source_code[0] == "!": + if not settings.ENABLE_SYSTEM_COMMANDS: + evaluation.message("Run", "dis") + else: + subprocess.run(source_code[1:], shell=True) + shell.definitions.increment_line_no(1) + continue + if query is None: + continue + if full_form: + print(query) + result = evaluation.evaluate(query, timeout=settings.TIMEOUT) + if result is not None: + shell.print_result(result, strict_wl_output=strict_wl_output) + except KeyboardInterrupt: + print("\nKeyboardInterrupt") + except EOFError: + print("\n\nGoodbye!\n") + break + except SystemExit: + # raise to pass the error code on, e.g. Quit[1] + raise + finally: + shell.reset_lineno() + + +def show_echo(query, evaluation): + echovar = evaluation.definitions.get_ownvalue("System`$Echo") + if not isinstance(echovar, Expression) or not echovar.has_form("List", None): + return + + for element in echovar.elements: + if isinstance(element, String) and element.value == "stdout": + stream = stream_manager.lookup_stream(1) + else: + strm = channel_to_stream(element, mode="w") + if strm is None: + continue + stream = stream_manager.lookup_stream(strm.elements[1].value) + if stream is None or stream.io is None or stream.io.closed: + continue + if stream is not None and stream.io is not None: + stream.io.write(query + "\n") From 4a1deb7421fab906f6a3fc79cb43da9b164ef051 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 1 Jul 2026 02:13:01 +0000 Subject: [PATCH 2/7] First cut at adding Dialog[] --- SYMBOLS_MANIFEST.txt | 1 + mathics/__main__.py | 11 +- mathics/builtin/tuning_debug/dialog.py | 26 ++++ mathics/eval/debug_tuning/dialog.py | 37 ++++++ mathics/repl.py | 160 ++++++++++++++----------- mathics/session.py | 51 +++++++- 6 files changed, 208 insertions(+), 78 deletions(-) create mode 100644 mathics/builtin/tuning_debug/dialog.py create mode 100644 mathics/eval/debug_tuning/dialog.py diff --git a/SYMBOLS_MANIFEST.txt b/SYMBOLS_MANIFEST.txt index d5af2742a..86ee80766 100644 --- a/SYMBOLS_MANIFEST.txt +++ b/SYMBOLS_MANIFEST.txt @@ -325,6 +325,7 @@ System`DesignMatrix System`Det System`Diagonal System`DiagonalMatrix +System`Dialog System`Diamond System`DiamondMatrix System`DiceDissimilarity diff --git a/mathics/__main__.py b/mathics/__main__.py index b033d7edc..97db6787f 100755 --- a/mathics/__main__.py +++ b/mathics/__main__.py @@ -11,18 +11,19 @@ import argparse import atexit import cProfile -import os.path as osp import sys from typing import Optional +import mathics.session from mathics import __version__, license_string, settings, version_string from mathics.builtin.tuning_debug.trace import TraceBuiltins, traced_apply_function -from mathics.core.definitions import Definitions, Symbol +from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation from mathics.core.load_builtin import import_and_load_builtins from mathics.core.parser import MathicsFileLineFeeder from mathics.core.rules import FunctionApplyRule from mathics.core.symbols import SymbolNull +from mathics.core.systemsymbols import SymbolAborted, SymbolOverflow from mathics.eval.files_io.files import set_input_var from mathics.repl import TerminalOutput, TerminalShell, eval_loop, interactive_eval_loop from mathics.settings import DATA_DIR, USER_PACKAGE_DIR, ensure_directory @@ -209,7 +210,7 @@ def dump_tracing_stats(): ) definitions.set_line_no(0) - shell = TerminalShell( + shell = mathics.session.shell_session = TerminalShell( definitions, args.colors, want_readline=not (args.no_readline), @@ -263,9 +264,9 @@ def run_it(): ) if evaluation.exc_result is SymbolNull: exit_rc = 0 - elif evaluation.exc_result is Symbol("$Aborted"): + elif evaluation.exc_result is SymbolAborted: exit_rc = -1 - elif evaluation.exc_result is Symbol("Overflow"): + elif evaluation.exc_result is SymbolOverflow: exit_rc = -2 else: exit_rc = -3 diff --git a/mathics/builtin/tuning_debug/dialog.py b/mathics/builtin/tuning_debug/dialog.py new file mode 100644 index 000000000..0ad5b794a --- /dev/null +++ b/mathics/builtin/tuning_debug/dialog.py @@ -0,0 +1,26 @@ +""" +Debugging +""" + +from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED +from mathics.core.builtin import Builtin +from mathics.core.evaluation import Evaluation +from mathics.eval.debug_tuning.dialog import eval_Dialog + + +class Dialog(Builtin): + r""" + :WMA link: https://reference.wolfram.com/language/ref/Dialog.html + +
+
'Dialog[]' +
Enters a \Mathics3 shell in the context that the call appears in a \Mathics3 expression. +
+ """ + + attributes = A_HOLD_ALL | A_PROTECTED + summary_text = "enter Mathics3 REPL shell in the context of the call" + + def eval(self, evaluation: Evaluation): + "Dialog[]" + return eval_Dialog() diff --git a/mathics/eval/debug_tuning/dialog.py b/mathics/eval/debug_tuning/dialog.py new file mode 100644 index 000000000..43b0186ce --- /dev/null +++ b/mathics/eval/debug_tuning/dialog.py @@ -0,0 +1,37 @@ +# Note: It is important we use mathics.session instead of +# from mathics.session import session. +# The former tracks changes made to the variable dynamically +# while the latter, uses the value at the time of "from ... import". +import mathics.session +from mathics.repl import interactive_eval_loop + +dialog_nesting_count: int = 0 + + +def eval_Dialog(): + + # Save prompt In/Out string state and line number. + shell = mathics.session.shell_session + + saved_line_number = shell.get_last_line_number() + saved_in_prefix = shell.in_prefix + saved_out_prefix = shell.out_prefix + + # Change In/Out prompt strings to reflect new nesting. + global dialog_nesting_count + dialog_nesting_count += 1 + shell.in_prefix = (" " * dialog_nesting_count) + shell.in_prefix + shell.out_prefix = (" " * dialog_nesting_count) + shell.out_prefix + + result = interactive_eval_loop(shell, False, False, init_signal_handler=False) + + # Restore prompt line number and In/Out prompt strings. + shell.in_prefix = saved_in_prefix + shell.lineno = saved_line_number + shell.out_prefix = saved_out_prefix + dialog_nesting_count -= 1 + + # Return last evaluation result if it exists. + if hasattr(result, "last_eval"): + return result.last_eval + return None diff --git a/mathics/repl.py b/mathics/repl.py index 068d48496..a16f7da63 100644 --- a/mathics/repl.py +++ b/mathics/repl.py @@ -1,4 +1,4 @@ -""" " +""" Terminal handling for command-line REPL. Also used by Dialog[] in mathics-core, but not used by other front-ends. @@ -11,8 +11,9 @@ import re import subprocess import sys -from typing import List +from typing import Any, List +import mathics_scanner.location from mathics_scanner.location import ContainerKind import mathics.core as mathics_core @@ -25,7 +26,7 @@ from mathics.core.symbols import SymbolNull, strip_context from mathics.eval.files_io.read import channel_to_stream from mathics.interrupt import setup_signal_handler -from mathics.session import autoload_files +from mathics.session import SessionShell, autoload_files try: import readline @@ -55,7 +56,7 @@ def get_srcdir(): return osp.realpath(filename) -class TerminalShell(MathicsLineFeeder): +class TerminalShell(MathicsLineFeeder, SessionShell): def __init__( self, definitions, @@ -152,19 +153,64 @@ def __init__( if autoload: autoload_files(definitions, get_srcdir(), "autoload-cli") - def get_last_line_number(self): - return self.definitions.get_line_no() + def complete_symbol_name(self, text, state): + try: + return self._complete_symbol_name(text, state) + except Exception: + # any exception thrown inside the completer gets silently + # thrown away otherwise + print("Unhandled error in readline completion") + + def _complete_symbol_name(self, text, state): + # The readline module calls this function repeatedly, + # increasing 'state' each time and expecting one string to be + # returned per call. + + if state == 0: + self.completion_candidates = self.get_completion_candidates(text) + + try: + return self.completion_candidates[state] + except IndexError: + return None + + def empty(self): + return False + + def feed(self): + result = self.read_line(self.get_in_prompt()) + "\n" + if mathics_scanner.location.TRACK_LOCATIONS: + self.container.append(self.source_text) + if result == "\n": + return "" # end of input + self.lineno += 1 + return result - def get_in_prompt(self): + def get_completion_candidates(self, text): + matches = self.definitions.get_matching_names(text + "*") + if "`" not in text: + matches = [strip_context(m) for m in matches] + return matches + + def get_in_prompt(self) -> str: + """ + Return the prompt string to be shown before reading input. + """ next_line_number = self.get_last_line_number() + 1 - if self.lineno > 0: - return " " * len("{0}[{1}]:= ".format(self.in_prefix, next_line_number)) - else: - return "{2}{0}[{3}{1}{4}]:= {5}".format( - self.in_prefix, next_line_number, *self.incolors - ) + return "{2}{0}[{3}{1}{4}]= {5}".format( + self.in_prefix, next_line_number, *self.incolors + ) + + def get_last_line_number(self): + """ + Return the line number associated with the next input to be read. + """ + return self.definitions.get_line_no() - def get_out_prompt(self, form=None): + def get_out_prompt(self, form=None) -> str: + """ + Return a prompt string to be shown before showing output. + """ line_number = self.get_last_line_number() if form: return "{3}{0}[{4}{1}{5}]//{2}= {6}".format( @@ -174,21 +220,9 @@ def get_out_prompt(self, form=None): self.out_prefix, line_number, *self.outcolors ) - def to_output(self, text, form=None): - line_number = self.get_last_line_number() - newline = "\n" + " " * len("Out[{0}]= ".format(line_number)) - if form: - newline += (len(form) + 2) * " " - return newline.join(text.splitlines()) - def out_callback(self, out, fmt=None): print(self.to_output(str(out), fmt)) - def read_line(self, prompt): - if self.using_readline: - return self.rl_read_line(prompt) - return input(prompt) - def print_result(self, result, no_out_prompt=False, strict_wl_output=False): if result is None or result.last_eval is SymbolNull: # Following WMA CLI, if the result is `SymbolNull`, just print an empty line. @@ -216,54 +250,30 @@ def print_result(self, result, no_out_prompt=False, strict_wl_output=False): mess = self.get_out_prompt(form) if not no_out_prompt else "" print(mess + output + "\n") + def reset_lineno(self): + self.lineno = 0 + + def read_line(self, prompt: str) -> str: + """ + Show prompt and read a line of input. + """ + if self.using_readline: + return self.rl_read_line(prompt) + return input(prompt) + def rl_read_line(self, prompt): - # Wrap ANSI colour sequences in \001 and \002, so readline + # Wrap ANSI color sequences in \001 and \002, so readline # knows that they're nonprinting. prompt = self.ansi_color_re.sub(lambda m: "\001" + m.group(0) + "\002", prompt) return input(prompt) - def complete_symbol_name(self, text, state): - try: - return self._complete_symbol_name(text, state) - except Exception: - # any exception thrown inside the completer gets silently - # thrown away otherwise - print("Unhandled error in readline completion") - - def _complete_symbol_name(self, text, state): - # The readline module calls this function repeatedly, - # increasing 'state' each time and expecting one string to be - # returned per call. - - if state == 0: - self.completion_candidates = self.get_completion_candidates(text) - - try: - return self.completion_candidates[state] - except IndexError: - return None - - def get_completion_candidates(self, text): - matches = self.definitions.get_matching_names(text + "*") - if "`" not in text: - matches = [strip_context(m) for m in matches] - return matches - - def reset_lineno(self): - self.lineno = 0 - - def feed(self): - result = self.read_line(self.get_in_prompt()) + "\n" - if mathics_scanner.location.TRACK_LOCATIONS: - self.container.append(self.source_text) - if result == "\n": - return "" # end of input - self.lineno += 1 - return result - - def empty(self): - return False + def to_output(self, text, form=None): + line_number = self.get_last_line_number() + newline = "\n" + " " * len("Out[{0}]= ".format(line_number)) + if form: + newline += (len(form) + 2) * " " + return newline.join(text.splitlines()) class TerminalOutput(Output): @@ -299,13 +309,20 @@ def eval_loop(feeder: MathicsFileLineFeeder, shell: TerminalShell): def interactive_eval_loop( - shell: TerminalShell, full_form: bool, strict_wl_output: bool -): + shell: TerminalShell, + full_form: bool, + strict_wl_output: bool, + init_signal_handler: bool = True, +) -> Any: """ A read eval/loop for an interactive session. `shell` is a shell session """ - setup_signal_handler() + + if init_signal_handler: + setup_signal_handler() + + result = None while True: try: evaluation = Evaluation(shell.definitions, output=TerminalOutput(shell)) @@ -343,6 +360,7 @@ def interactive_eval_loop( raise finally: shell.reset_lineno() + return result def show_echo(query, evaluation): diff --git a/mathics/session.py b/mathics/session.py index 030acce4e..89214f83a 100644 --- a/mathics/session.py +++ b/mathics/session.py @@ -11,8 +11,8 @@ import os import os.path as osp +from abc import ABC, abstractmethod from os.path import join as osp_join -from typing import Optional from mathics_scanner.location import ContainerKind @@ -23,6 +23,53 @@ from mathics.core.symbols import SymbolNull +class SessionShell(ABC): + @abstractmethod + def get_in_prompt(self) -> str: + """ + Return the prompt string to be shown before reading input. + """ + pass + + @abstractmethod + def get_last_line_number(self) -> int: + """ + Return the line number associated with the next input to be read. + """ + pass + + @abstractmethod + def get_out_prompt(self, form=None) -> str: + """ + Return a prompt string to be shown before showing output. + """ + line_number = self.get_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 + ) + + @abstractmethod + def read_line(self, prompt: str) -> str: + """ + Method that reads a line of input from the user, prompting with `prompt`. + The line input from the user returned. + + And example like EOF might get raised indicating the input stream has been closed or + finished. + """ + pass + + +# A common place for front-ends to store a session. +# This is picked up and used by the Mathics3 builtin function +# Dialog, and related functions. +shell_session: SessionShell | None = None + + def autoload_files( defs: Definitions, root_dir_path: str, @@ -120,7 +167,7 @@ def __init__( add_builtin=True, catch_interrupt=False, form="InputForm", - character_encoding: Optional[str] = None, + character_encoding: str | None = None, ): # FIXME: This import is needed because # the first time we call self.reset, From 23767e13f5ad7a03d922265a3ba75a3855567950 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 1 Jul 2026 17:30:54 +0000 Subject: [PATCH 3/7] Revise to use current evaluation in Dialog --- mathics/builtin/tuning_debug/dialog.py | 2 +- mathics/eval/debug_tuning/dialog.py | 14 ++++++-------- mathics/repl.py | 10 ++++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/mathics/builtin/tuning_debug/dialog.py b/mathics/builtin/tuning_debug/dialog.py index 0ad5b794a..73b1b864d 100644 --- a/mathics/builtin/tuning_debug/dialog.py +++ b/mathics/builtin/tuning_debug/dialog.py @@ -23,4 +23,4 @@ class Dialog(Builtin): def eval(self, evaluation: Evaluation): "Dialog[]" - return eval_Dialog() + return eval_Dialog(evaluation) diff --git a/mathics/eval/debug_tuning/dialog.py b/mathics/eval/debug_tuning/dialog.py index 43b0186ce..b4809840a 100644 --- a/mathics/eval/debug_tuning/dialog.py +++ b/mathics/eval/debug_tuning/dialog.py @@ -1,17 +1,13 @@ -# Note: It is important we use mathics.session instead of -# from mathics.session import session. -# The former tracks changes made to the variable dynamically -# while the latter, uses the value at the time of "from ... import". -import mathics.session +from mathics.core.evaluation import Evaluation from mathics.repl import interactive_eval_loop dialog_nesting_count: int = 0 -def eval_Dialog(): +def eval_Dialog(evaluation: Evaluation): # Save prompt In/Out string state and line number. - shell = mathics.session.shell_session + shell = evaluation.shell saved_line_number = shell.get_last_line_number() saved_in_prefix = shell.in_prefix @@ -23,7 +19,9 @@ def eval_Dialog(): shell.in_prefix = (" " * dialog_nesting_count) + shell.in_prefix shell.out_prefix = (" " * dialog_nesting_count) + shell.out_prefix - result = interactive_eval_loop(shell, False, False, init_signal_handler=False) + result = interactive_eval_loop( + shell, False, False, init_signal_handler=False, evaluation=evaluation + ) # Restore prompt line number and In/Out prompt strings. shell.in_prefix = saved_in_prefix diff --git a/mathics/repl.py b/mathics/repl.py index a16f7da63..fb7ad3417 100644 --- a/mathics/repl.py +++ b/mathics/repl.py @@ -313,6 +313,7 @@ def interactive_eval_loop( full_form: bool, strict_wl_output: bool, init_signal_handler: bool = True, + evaluation: Evaluation | None = None, ) -> Any: """ A read eval/loop for an interactive session. @@ -325,11 +326,12 @@ def interactive_eval_loop( result = None while True: try: - evaluation = Evaluation(shell.definitions, output=TerminalOutput(shell)) + if evaluation is None: + evaluation = Evaluation(shell.definitions, output=TerminalOutput(shell)) - # Store shell into the evaluation so that an interrupt handler - # has access to this - evaluation.shell = shell + # Store shell into the evaluation so that an interrupt handler + # has access to this + evaluation.shell = shell query, source_code = evaluation.parse_feeder_returning_code(shell) if mathics_core.PRE_EVALUATION_HOOK is not None: From d5269b07eb9e858770386e54f73962b3839360ba Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 2 Jul 2026 21:00:51 -0400 Subject: [PATCH 4/7] Set evaluation/definition variables properly on exit. In particular we need to reset $Line. And the evaluation stopped, and timeout variables. --- mathics/eval/debug_tuning/dialog.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/mathics/eval/debug_tuning/dialog.py b/mathics/eval/debug_tuning/dialog.py index b4809840a..1f6d798ec 100644 --- a/mathics/eval/debug_tuning/dialog.py +++ b/mathics/eval/debug_tuning/dialog.py @@ -1,15 +1,22 @@ from mathics.core.evaluation import Evaluation from mathics.repl import interactive_eval_loop +# dialog_nesting count controls the indentation +# for prompts In[] and Out[]. Each nesting +# adds two more spaces before the prompt text. dialog_nesting_count: int = 0 def eval_Dialog(evaluation: Evaluation): + """ + Evaluation method for Dialog[] + """ # Save prompt In/Out string state and line number. shell = evaluation.shell + definitions = evaluation.definitions - saved_line_number = shell.get_last_line_number() + saved_line_number = definitions.get_line_no() saved_in_prefix = shell.in_prefix saved_out_prefix = shell.out_prefix @@ -19,15 +26,26 @@ def eval_Dialog(evaluation: Evaluation): shell.in_prefix = (" " * dialog_nesting_count) + shell.in_prefix shell.out_prefix = (" " * dialog_nesting_count) + shell.out_prefix + # Here is the nested interactive evaluation loop. result = interactive_eval_loop( - shell, False, False, init_signal_handler=False, evaluation=evaluation + shell, + False, + False, + init_signal_handler=False, + evaluation=evaluation, ) # Restore prompt line number and In/Out prompt strings. + dialog_nesting_count -= 1 shell.in_prefix = saved_in_prefix - shell.lineno = saved_line_number shell.out_prefix = saved_out_prefix - dialog_nesting_count -= 1 + + definitions.set_line_no(saved_line_number) + + # Reset evaluation variables that might might + # cause abnormal evaluation termination. + evaluation.timeout = False + evaluation.stopped = False # Return last evaluation result if it exists. if hasattr(result, "last_eval"): From 7c260ca24cc9d4e83baee37a1feff4c3c1e02df1 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 2 Jul 2026 21:00:51 -0400 Subject: [PATCH 5/7] Set evaluation/definition variables properly on exit. In particular we need to reset $Line. And the evaluation stopped, and timeout variables. --- mathics/eval/debug_tuning/dialog.py | 26 ++++++++++++++++++++++---- mathics/repl.py | 1 + mathics/session.py | 10 ++-------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/mathics/eval/debug_tuning/dialog.py b/mathics/eval/debug_tuning/dialog.py index b4809840a..1f6d798ec 100644 --- a/mathics/eval/debug_tuning/dialog.py +++ b/mathics/eval/debug_tuning/dialog.py @@ -1,15 +1,22 @@ from mathics.core.evaluation import Evaluation from mathics.repl import interactive_eval_loop +# dialog_nesting count controls the indentation +# for prompts In[] and Out[]. Each nesting +# adds two more spaces before the prompt text. dialog_nesting_count: int = 0 def eval_Dialog(evaluation: Evaluation): + """ + Evaluation method for Dialog[] + """ # Save prompt In/Out string state and line number. shell = evaluation.shell + definitions = evaluation.definitions - saved_line_number = shell.get_last_line_number() + saved_line_number = definitions.get_line_no() saved_in_prefix = shell.in_prefix saved_out_prefix = shell.out_prefix @@ -19,15 +26,26 @@ def eval_Dialog(evaluation: Evaluation): shell.in_prefix = (" " * dialog_nesting_count) + shell.in_prefix shell.out_prefix = (" " * dialog_nesting_count) + shell.out_prefix + # Here is the nested interactive evaluation loop. result = interactive_eval_loop( - shell, False, False, init_signal_handler=False, evaluation=evaluation + shell, + False, + False, + init_signal_handler=False, + evaluation=evaluation, ) # Restore prompt line number and In/Out prompt strings. + dialog_nesting_count -= 1 shell.in_prefix = saved_in_prefix - shell.lineno = saved_line_number shell.out_prefix = saved_out_prefix - dialog_nesting_count -= 1 + + definitions.set_line_no(saved_line_number) + + # Reset evaluation variables that might might + # cause abnormal evaluation termination. + evaluation.timeout = False + evaluation.stopped = False # Return last evaluation result if it exists. if hasattr(result, "last_eval"): diff --git a/mathics/repl.py b/mathics/repl.py index fb7ad3417..b4f6384bd 100644 --- a/mathics/repl.py +++ b/mathics/repl.py @@ -57,6 +57,7 @@ def get_srcdir(): class TerminalShell(MathicsLineFeeder, SessionShell): + def __init__( self, definitions, diff --git a/mathics/session.py b/mathics/session.py index 89214f83a..f434816a8 100644 --- a/mathics/session.py +++ b/mathics/session.py @@ -24,6 +24,7 @@ class SessionShell(ABC): + @abstractmethod def get_in_prompt(self) -> str: """ @@ -43,14 +44,7 @@ def get_out_prompt(self, form=None) -> str: """ Return a prompt string to be shown before showing output. """ - line_number = self.get_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 - ) + pass @abstractmethod def read_line(self, prompt: str) -> str: From fd5b7da1019651f3e977627d236e1935d9c44053 Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 3 Jul 2026 11:26:11 -0400 Subject: [PATCH 6/7] Supporat expr form in Dialog[expr] --- mathics/builtin/tuning_debug/dialog.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mathics/builtin/tuning_debug/dialog.py b/mathics/builtin/tuning_debug/dialog.py index 73b1b864d..0cf9e8849 100644 --- a/mathics/builtin/tuning_debug/dialog.py +++ b/mathics/builtin/tuning_debug/dialog.py @@ -2,9 +2,14 @@ Debugging """ +from mathics.core.atoms import Integer from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED from mathics.core.builtin import Builtin +from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.rules import Rule +from mathics.core.systemsymbols import SymbolOut from mathics.eval.debug_tuning.dialog import eval_Dialog @@ -24,3 +29,12 @@ class Dialog(Builtin): def eval(self, evaluation: Evaluation): "Dialog[]" return eval_Dialog(evaluation) + + def eval(self, expr: BaseElement, evaluation: Evaluation): + "Dialog[expr_]" + definitions = evaluation.definitions + definitions.add_rule( + "Out", Rule(Expression(SymbolOut, Integer(definitions.get_line_no())), expr) + ) + + return eval_Dialog(evaluation) From bf891cebbb855858d187f462edf3320d9481282d Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 3 Jul 2026 16:10:18 -0400 Subject: [PATCH 7/7] Handle Dialog[expr] form --- mathics/builtin/tuning_debug/dialog.py | 15 +++--------- mathics/eval/debug_tuning/dialog.py | 33 ++++++++++++++++++++------ mathics/repl.py | 2 +- mathics/session.py | 7 ++++++ 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/mathics/builtin/tuning_debug/dialog.py b/mathics/builtin/tuning_debug/dialog.py index 0cf9e8849..baf874232 100644 --- a/mathics/builtin/tuning_debug/dialog.py +++ b/mathics/builtin/tuning_debug/dialog.py @@ -2,14 +2,10 @@ Debugging """ -from mathics.core.atoms import Integer from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED from mathics.core.builtin import Builtin from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation -from mathics.core.expression import Expression -from mathics.core.rules import Rule -from mathics.core.systemsymbols import SymbolOut from mathics.eval.debug_tuning.dialog import eval_Dialog @@ -28,13 +24,8 @@ class Dialog(Builtin): def eval(self, evaluation: Evaluation): "Dialog[]" - return eval_Dialog(evaluation) + return eval_Dialog(None, evaluation) - def eval(self, expr: BaseElement, evaluation: Evaluation): + def eval_with_expr(self, expr: BaseElement, evaluation: Evaluation): "Dialog[expr_]" - definitions = evaluation.definitions - definitions.add_rule( - "Out", Rule(Expression(SymbolOut, Integer(definitions.get_line_no())), expr) - ) - - return eval_Dialog(evaluation) + return eval_Dialog(expr, evaluation) diff --git a/mathics/eval/debug_tuning/dialog.py b/mathics/eval/debug_tuning/dialog.py index 1f6d798ec..a8d7fd84c 100644 --- a/mathics/eval/debug_tuning/dialog.py +++ b/mathics/eval/debug_tuning/dialog.py @@ -1,4 +1,10 @@ -from mathics.core.evaluation import Evaluation +from mathics import settings +from mathics.core.atoms import Integer +from mathics.core.element import BaseElement +from mathics.core.evaluation import Evaluation, Result +from mathics.core.expression import Expression +from mathics.core.rules import Rule +from mathics.core.systemsymbols import SymbolOut from mathics.repl import interactive_eval_loop # dialog_nesting count controls the indentation @@ -7,24 +13,37 @@ dialog_nesting_count: int = 0 -def eval_Dialog(evaluation: Evaluation): +def eval_Dialog(expr: BaseElement | None, evaluation: Evaluation): """ Evaluation method for Dialog[] """ - # Save prompt In/Out string state and line number. shell = evaluation.shell definitions = evaluation.definitions + # Save current line number saved_line_number = definitions.get_line_no() - saved_in_prefix = shell.in_prefix - saved_out_prefix = shell.out_prefix + + if expr is not None: + result = evaluation.evaluate(expr, timeout=settings.TIMEOUT) + definitions = evaluation.definitions + definitions.add_rule( + "Out", + Rule(Expression(SymbolOut, Integer(saved_line_number)), result.last_eval), + ) + shell.print_result(result) # Change In/Out prompt strings to reflect new nesting. global dialog_nesting_count dialog_nesting_count += 1 - shell.in_prefix = (" " * dialog_nesting_count) + shell.in_prefix - shell.out_prefix = (" " * dialog_nesting_count) + shell.out_prefix + + # Save prompt In/Out string state + if hasattr(shell, "in_prefix"): + saved_in_prefix = shell.in_prefix + shell.in_prefix = (" " * dialog_nesting_count) + saved_in_prefix + if hasattr(shell, "out_prefix"): + saved_out_prefix = shell.out_prefix + shell.out_prefix = (" " * dialog_nesting_count) + saved_out_prefix # Here is the nested interactive evaluation loop. result = interactive_eval_loop( diff --git a/mathics/repl.py b/mathics/repl.py index b4f6384bd..55fb253ba 100644 --- a/mathics/repl.py +++ b/mathics/repl.py @@ -198,7 +198,7 @@ def get_in_prompt(self) -> str: Return the prompt string to be shown before reading input. """ next_line_number = self.get_last_line_number() + 1 - return "{2}{0}[{3}{1}{4}]= {5}".format( + return "{2}{0}[{3}{1}{4}]:= {5}".format( self.in_prefix, next_line_number, *self.incolors ) diff --git a/mathics/session.py b/mathics/session.py index f434816a8..5e763854d 100644 --- a/mathics/session.py +++ b/mathics/session.py @@ -57,6 +57,13 @@ def read_line(self, prompt: str) -> str: """ pass + @abstractmethod + def print_result(self, result, no_out_prompt=False, strict_wl_output=False): + """ + Show result. Usually this is prefaced by "Out" if no_output_prompt is True. + """ + pass + # A common place for front-ends to store a session. # This is picked up and used by the Mathics3 builtin function