From 1bf99d262052d9d55bc59bece6f6c98d5902f0a7 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 3 Oct 2026 11:57:00 -0400 Subject: [PATCH 1/6] WIP --- mathics/builtin/symbol/properties.py | 217 ++---------------------- mathics/format/form_rule/information.py | 207 ++++++++++++++++++++++ 2 files changed, 217 insertions(+), 207 deletions(-) create mode 100644 mathics/format/form_rule/information.py diff --git a/mathics/builtin/symbol/properties.py b/mathics/builtin/symbol/properties.py index 4872574503..6673f53c69 100644 --- a/mathics/builtin/symbol/properties.py +++ b/mathics/builtin/symbol/properties.py @@ -2,48 +2,28 @@ Symbol Properties """ -from typing import Callable, Optional - -from mathics_scanner.tokeniser import NAMES_WILDCARDS - from mathics.core.atoms import String from mathics.core.attributes import ( A_HOLD_ALL, A_HOLD_FIRST, A_PROTECTED, A_READ_PROTECTED, - attributes_bitset_to_list, ) from mathics.core.builtin import Builtin, PrefixOperator, Test -from mathics.core.convert.expression import to_mathics_list from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.rules import RewriteRule, is_rule -from mathics.core.symbols import ( - Symbol, - SymbolFalse, - SymbolHoldForm, - SymbolNull, - SymbolTrue, - SymbolUpSet, -) +from mathics.core.rules import is_rule +from mathics.core.symbols import Symbol, SymbolFalse, SymbolNull, SymbolTrue from mathics.core.systemsymbols import ( - SymbolAttributes, - SymbolDefinition, - SymbolFormat, SymbolGrid, - SymbolInfix, SymbolInputForm, SymbolLeft, SymbolMissing, - SymbolOptions, SymbolRule, - SymbolSet, SymbolUnknownSymbol, ) -from mathics.doc.online import online_doc_string from mathics.eval.atomic.symbols import eval_SymbolQ from mathics.eval.symbol.properties import ( eval_Information, @@ -54,126 +34,6 @@ ) -# FIXME: gather_and_format_definition_rules is crap and needs to be revised, rewritten and put in -# mathics.eval.symbols.properties -def gather_and_format_definition_rules( - symbol: Symbol, evaluation: Evaluation -) -> Optional[list[Expression]]: - """Return a list of lines describing the definition of `symbol`""" - lines = [] - - def rhs_format(expr): - if expr.has_form(SymbolInfix, None): - expr = Expression(Expression(SymbolHoldForm, expr.head), *expr.elements) - return expr - - def format_rule( - rule: RewriteRule, - up: bool = False, - lhs: Callable = lambda k: k, - rhs: Callable = lambda r: r, - ): - """ - Add a line showing `rule` - """ - evaluation.check_stopped() - if isinstance(rule, RewriteRule): - lhs_pat = Expression(SymbolInputForm, lhs(rule.pattern.expr)) - repl_expr = rhs( - rule.replace.replace_vars( - {"System`Definition": Expression(SymbolHoldForm, SymbolDefinition)} - ) - ) - repl_expr = Expression(SymbolInputForm, repl_expr) - lines.append( - Expression( - SymbolHoldForm, - Expression(up and SymbolUpSet or SymbolSet, lhs_pat, repl_expr), - ) - ) - - def gather_rules(definition: Definition): - """ - Add to the description all the rules associated - to a definition object - """ - for rule in definition.ownvalues: - format_rule(rule) - for rule in definition.downvalues: - format_rule(rule) - for rule in definition.subvalues: - format_rule(rule) - for rule in definition.upvalues: - format_rule(rule, up=True) - for rule in definition.nvalues: - format_rule(rule) - formats = sorted(definition.formatvalues.items()) - for form_name, rules in formats: - for rule in rules: - - def lhs_format(expr): - return Expression(SymbolFormat, expr, Symbol(form_name)) - - format_rule(rule, lhs=lhs_format, rhs=rhs_format) - - name = symbol.get_name() - if not name: - evaluation.message("Definition", "sym", symbol, 1) - return - - try: - all = evaluation.definitions.get_definition(name) - attributes = all.attributes - all_options = all.options - all_defaultvalues = all.defaultvalues - - if attributes: - attributes_list = attributes_bitset_to_list(attributes) - lines.append( - Expression( - SymbolHoldForm, - Expression( - SymbolSet, - Expression(SymbolAttributes, symbol), - to_mathics_list( - *attributes_list, elements_conversion_fn=Symbol - ), - ), - ) - ) - except KeyError: - attributes = 0 - all_options = {} - all_defaultvalues = [] - - if not A_READ_PROTECTED & attributes: - try: - gather_rules(evaluation.definitions.get_user_definition(name, create=False)) - except KeyError: - pass - - for rule in all_defaultvalues: - format_rule(rule) - if all_options: - options = sorted(all_options.items()) - lines.append( - Expression( - SymbolHoldForm, - Expression( - SymbolSet, - Expression(SymbolOptions, symbol), - ListExpression( - *( - Expression(SymbolRule, Symbol(name), value) - for name, value in options - ) - ), - ), - ) - ) - return lines - - class Definition(Builtin): """ :WMA link: @@ -410,10 +270,6 @@ class Information(PrefixOperator): } summary_text = "get information about all assignments for a symbol" - def eval(self, expr, evaluation: Evaluation, options: dict): - "Information[expr_, OptionsPattern[Information]]" - return eval_Information(expr, evaluation) - def build_missing(self, expression: BaseElement) -> Expression: """Evaluate ?? F[x][y].. as -> Missing[UnknownSymbol, F][x][y]""" if isinstance(expression, Expression): @@ -447,6 +303,14 @@ def build_list_of_matching_symbols( result = Expression(Symbol("System`TableForm"), ListExpression(*rows)) return result + def eval(self, expr, evaluation: Evaluation): + "Information[expr___]" + return eval_Information(expr, evaluation) + + def eval_with_options(self, expr, evaluation: Evaluation, options: dict): + "Information[expr_, OptionsPattern[Information]]" + return eval_Information(expr, evaluation) + def eval_with_property(self, expr, prop, evaluation: Evaluation, options: dict): "Information[expr_, prop_, OptionsPattern[Information]]" if is_rule(prop): @@ -463,67 +327,6 @@ def eval_with_property(self, expr, prop, evaluation: Evaluation, options: dict): # TODO: the formatting part of this must be moved to `InformationData` # and `Information` should build this kind of expressions. - def format_information_generic( - self, - expr: BaseElement, - evaluation: Evaluation, - options: dict, - grid: bool = True, - ): - "(StandardForm,TraditionalForm,InputForm,OutputForm,): Information[expr_, OptionsPattern[Information]]" - # expr is not a Symbol. We should leave unchanged and let other formatting rules kick in. - return None - - def format_information_string( - self, strpat: String, evaluation: Evaluation, options: dict, grid: bool = True - ) -> Expression | Symbol: - "(StandardForm,TraditionalForm,InputForm,OutputForm,): Information[strpat_String, OptionsPattern[Information]]" - definitions = evaluation.definitions - string_str = strpat.value - if any(char in string_str for char in NAMES_WILDCARDS): - return self.build_list_of_matching_symbols( - string_str, evaluation, options, grid - ) - try: - symbol_name = definitions.get_definition( - string_str, only_if_exists=True - ).name - except KeyError: - return self.build_missing(strpat) - return self.format_information_symbol(Symbol(symbol_name), evaluation, options) - - def format_information_symbol( - self, symbol: Symbol, evaluation: Evaluation, options: dict, grid: bool = True - ) -> Expression | Symbol: - "(StandardForm,TraditionalForm,InputForm,OutputForm,): Information[symbol_Symbol, OptionsPattern[Information]]" - definitions = evaluation.definitions - try: - definitions.get_definition(symbol.name, True) - except KeyError: - return self.build_missing(symbol) - - lines: list[Expression | String] = [] - # Print the "usage" message if available. - # is_long_form = self.get_option(options, "LongForm", evaluation).to_python() - is_long_form = True # In WMA >=12.0 this option does not make much difference-- - usagetext = online_doc_string(symbol, evaluation.definitions, is_long_form) - if usagetext: - lines.append(String(usagetext)) - else: - lines.append(String(symbol.get_name())) - - if is_long_form and ( - info := gather_and_format_definition_rules(symbol, evaluation) - ): - lines.extend(info) - - infoshow = Expression( - SymbolGrid, - ListExpression(*(line for line in lines)), - Expression(SymbolRule, Symbol("ColumnAlignments"), SymbolLeft), - ) - return infoshow - class SymbolQ(Test): """ diff --git a/mathics/format/form_rule/information.py b/mathics/format/form_rule/information.py new file mode 100644 index 0000000000..6f3110f683 --- /dev/null +++ b/mathics/format/form_rule/information.py @@ -0,0 +1,207 @@ +from typing import Callable, Optional + +from mathics_scanner.tokeniser import NAMES_WILDCARDS + +from mathics.core.atoms import String +from mathics.core.attributes import A_READ_PROTECTED, attributes_bitset_to_list +from mathics.core.convert.expression import to_mathics_list +from mathics.core.element import BaseElement +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.rules import RewriteRule +from mathics.core.symbols import Symbol, SymbolUpSet +from mathics.core.systemsymbols import ( + SymbolAttributes, + SymbolDefinition, + SymbolFormat, + SymbolGrid, + SymbolHoldForm, + SymbolInfix, + SymbolInputForm, + SymbolLeft, + SymbolOptions, + SymbolRule, + SymbolSet, +) +from mathics.doc.online import online_doc_string + + +def format_information_generic( + expr: BaseElement, + evaluation: Evaluation, + grid: bool = True, +): + "(StandardForm,TraditionalForm,InputForm,OutputForm,): Information[expr_, OptionsPattern[Information]]" + # expr is not a Symbol. We should leave unchanged and let other formatting rules kick in. + return None + + +def format_information_string( + self, strpat: String, evaluation: Evaluation, options: dict, grid: bool = True +) -> Expression | Symbol: + "(StandardForm,TraditionalForm,InputForm,OutputForm,): Information[strpat_String, OptionsPattern[Information]]" + definitions = evaluation.definitions + string_str = strpat.value + if any(char in string_str for char in NAMES_WILDCARDS): + return self.build_list_of_matching_symbols( + string_str, evaluation, options, grid + ) + try: + symbol_name = definitions.get_definition(string_str, only_if_exists=True).name + except KeyError: + return self.build_missing(strpat) + return self.format_information_symbol(Symbol(symbol_name), evaluation, options) + + +def format_information_symbol( + self, symbol: Symbol, evaluation: Evaluation, options: dict, grid: bool = True +) -> Expression | Symbol: + "(StandardForm,TraditionalForm,InputForm,OutputForm,): Information[symbol_Symbol, OptionsPattern[Information]]" + definitions = evaluation.definitions + try: + definitions.get_definition(symbol.name, True) + except KeyError: + return self.build_missing(symbol) + + lines: list[Expression | String] = [] + # Print the "usage" message if available. + # is_long_form = self.get_option(options, "LongForm", evaluation).to_python() + is_long_form = True # In WMA >=12.0 this option does not make much difference-- + usagetext = online_doc_string(symbol, evaluation.definitions, is_long_form) + if usagetext: + lines.append(String(usagetext)) + else: + lines.append(String(symbol.get_name())) + + if is_long_form and ( + info := gather_and_format_definition_rules(symbol, evaluation) + ): + lines.extend(info) + + infoshow = Expression( + SymbolGrid, + ListExpression(*(line for line in lines)), + Expression(SymbolRule, Symbol("ColumnAlignments"), SymbolLeft), + ) + return infoshow + + +# FIXME: gather_and_format_definition_rules is crap and needs to be revised, rewritten and put in +# mathics.eval.symbols.properties +def gather_and_format_definition_rules( + symbol: Symbol, evaluation: Evaluation +) -> Optional[list[Expression]]: + """Return a list of lines describing the definition of `symbol`""" + lines = [] + + def rhs_format(expr): + if expr.has_form(SymbolInfix, None): + expr = Expression(Expression(SymbolHoldForm, expr.head), *expr.elements) + return expr + + def format_rule( + rule: RewriteRule, + up: bool = False, + lhs: Callable = lambda k: k, + rhs: Callable = lambda r: r, + ): + """ + Add a line showing `rule` + """ + evaluation.check_stopped() + if isinstance(rule, RewriteRule): + lhs_pat = Expression(SymbolInputForm, lhs(rule.pattern.expr)) + repl_expr = rhs( + rule.replace.replace_vars( + {"System`Definition": Expression(SymbolHoldForm, SymbolDefinition)} + ) + ) + repl_expr = Expression(SymbolInputForm, repl_expr) + lines.append( + Expression( + SymbolHoldForm, + Expression(up and SymbolUpSet or SymbolSet, lhs_pat, repl_expr), + ) + ) + + def gather_rules(definition): + """ + Add to the description all the rules associated + to a definition object + """ + for rule in definition.ownvalues: + format_rule(rule) + for rule in definition.downvalues: + format_rule(rule) + for rule in definition.subvalues: + format_rule(rule) + for rule in definition.upvalues: + format_rule(rule, up=True) + for rule in definition.nvalues: + format_rule(rule) + formats = sorted(definition.formatvalues.items()) + for form_name, rules in formats: + for rule in rules: + + def lhs_format(expr): + return Expression(SymbolFormat, expr, Symbol(form_name)) + + format_rule(rule, lhs=lhs_format, rhs=rhs_format) + + name = symbol.get_name() + if not name: + evaluation.message("Definition", "sym", symbol, 1) + return None + + try: + all = evaluation.definitions.get_definition(name) + attributes = all.attributes + all_options = all.options + all_defaultvalues = all.defaultvalues + + if attributes: + attributes_list = attributes_bitset_to_list(attributes) + lines.append( + Expression( + SymbolHoldForm, + Expression( + SymbolSet, + Expression(SymbolAttributes, symbol), + to_mathics_list( + *attributes_list, elements_conversion_fn=Symbol + ), + ), + ) + ) + except KeyError: + attributes = 0 + all_options = {} + all_defaultvalues = [] + + if not A_READ_PROTECTED & attributes: + try: + gather_rules(evaluation.definitions.get_user_definition(name, create=False)) + except KeyError: + pass + + for rule in all_defaultvalues: + format_rule(rule) + if all_options: + options = sorted(all_options.items()) + lines.append( + Expression( + SymbolHoldForm, + Expression( + SymbolSet, + Expression(SymbolOptions, symbol), + ListExpression( + *( + Expression(SymbolRule, Symbol(name), Symbol(value)) + for name, value in options + ) + ), + ), + ) + ) + return lines From e7eed3ab4d84199ab4873104762efbb655b986bc Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 3 Oct 2026 20:52:22 -0400 Subject: [PATCH 2/6] Correct and simplify ?? parsing. --- mathics/core/parser/parser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mathics/core/parser/parser.py b/mathics/core/parser/parser.py index 3fcf4dca26..f294cf579e 100644 --- a/mathics/core/parser/parser.py +++ b/mathics/core/parser/parser.py @@ -609,7 +609,6 @@ def parse_information_common(self, token: Token, want_long_form: bool) -> Node: return Node( "Information", pattern_arg, - Node("Rule", Symbol("LongForm"), long_form), ) @track_location From 297135c097210bca2558e944b565629bc06426d3 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 4 Oct 2026 07:34:33 -0400 Subject: [PATCH 3/6] WIP - Remove formatting code from Information This has been added elsewhere. --- mathics/builtin/symbol/properties.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/mathics/builtin/symbol/properties.py b/mathics/builtin/symbol/properties.py index 6673f53c69..ce88601363 100644 --- a/mathics/builtin/symbol/properties.py +++ b/mathics/builtin/symbol/properties.py @@ -141,31 +141,6 @@ class Definition(Builtin): attributes = A_HOLD_ALL | A_PROTECTED summary_text = "give values of a symbol in a form that can be stored in a package" - def format_definition( - self, symbol: Symbol, evaluation: Evaluation, grid: bool = True - ) -> Expression | Symbol: - "(StandardForm,TraditionalForm,OutputForm,): Definition[symbol_]" - - lines = gather_and_format_definition_rules(symbol, evaluation) - if lines: - if grid: - return Expression( - SymbolGrid, - ListExpression(*(ListExpression(line) for line in lines)), - Expression(SymbolRule, Symbol("ColumnAlignments"), SymbolLeft), - ) - else: - for line in lines: - evaluation.print_out(Expression(SymbolInputForm, line)) - - return SymbolNull - - def format_definition_input( - self, symbol: Symbol, evaluation: Evaluation - ) -> Expression | Symbol: - "(InputForm,): Definition[symbol_]" - return self.format_definition(symbol, evaluation, grid=False) - # In Mathematica 5, this appears under "Types of Values". class DownValues(Builtin): From b7c1c98020bd319220ecbbefa6fcbe87d120473d Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 4 Oct 2026 18:03:57 -0400 Subject: [PATCH 4/6] Revise ?? and Information... Revise parsing ?? so that it evaluates to a InformationData or InformationGridData. Evaluation methods moved out of Information class and into `mathics.eval.properties` The usual linting done of nearby code. --- mathics/builtin/symbol/properties.py | 96 +++++++++- mathics/core/atoms/associations.py | 2 +- mathics/core/builtin.py | 1 + mathics/eval/symbol/properties.py | 242 +++++++++++++++++++++++- mathics/format/form_rule/information.py | 144 +------------- test/core/parser/test_parser.py | 4 +- 6 files changed, 335 insertions(+), 154 deletions(-) diff --git a/mathics/builtin/symbol/properties.py b/mathics/builtin/symbol/properties.py index ce88601363..d7fc3361e6 100644 --- a/mathics/builtin/symbol/properties.py +++ b/mathics/builtin/symbol/properties.py @@ -2,6 +2,8 @@ Symbol Properties """ +from mathics_scanner.tokeniser import NAMES_WILDCARDS + from mathics.core.atoms import String from mathics.core.attributes import ( A_HOLD_ALL, @@ -24,11 +26,13 @@ SymbolRule, SymbolUnknownSymbol, ) +from mathics.doc.online import online_doc_string from mathics.eval.atomic.symbols import eval_SymbolQ from mathics.eval.symbol.properties import ( eval_Information, eval_Information_with_property, eval_values, + gather_and_format_definition_rules, get_matching_names, missing_symbol, ) @@ -141,6 +145,31 @@ class Definition(Builtin): attributes = A_HOLD_ALL | A_PROTECTED summary_text = "give values of a symbol in a form that can be stored in a package" + def format_definition( + self, symbol: Symbol, evaluation: Evaluation, grid: bool = True + ) -> Expression | Symbol: + "(StandardForm,TraditionalForm,OutputForm,): Definition[symbol_]" + + lines = gather_and_format_definition_rules(symbol, evaluation) + if lines: + if grid: + return Expression( + SymbolGrid, + ListExpression(*(ListExpression(line) for line in lines)), + Expression(SymbolRule, Symbol("ColumnAlignments"), SymbolLeft), + ) + else: + for line in lines: + evaluation.print_out(Expression(SymbolInputForm, line)) + + return SymbolNull + + def format_definition_input( + self, symbol: Symbol, evaluation: Evaluation + ) -> Expression | Symbol: + "(InputForm,): Definition[symbol_]" + return self.format_definition(symbol, evaluation, grid=False) + # In Mathematica 5, this appears under "Types of Values". class DownValues(Builtin): @@ -278,11 +307,7 @@ def build_list_of_matching_symbols( result = Expression(Symbol("System`TableForm"), ListExpression(*rows)) return result - def eval(self, expr, evaluation: Evaluation): - "Information[expr___]" - return eval_Information(expr, evaluation) - - def eval_with_options(self, expr, evaluation: Evaluation, options: dict): + def eval(self, expr, evaluation: Evaluation, options: dict): "Information[expr_, OptionsPattern[Information]]" return eval_Information(expr, evaluation) @@ -302,6 +327,67 @@ def eval_with_property(self, expr, prop, evaluation: Evaluation, options: dict): # TODO: the formatting part of this must be moved to `InformationData` # and `Information` should build this kind of expressions. + def format_information_generic( + self, + expr: BaseElement, + evaluation: Evaluation, + options: dict, + grid: bool = True, + ): + "(StandardForm,TraditionalForm,InputForm,OutputForm,): Information[expr_, OptionsPattern[Information]]" + # expr is not a Symbol. We should leave unchanged and let other formatting rules kick in. + return None + + def format_information_string( + self, strpat: String, evaluation: Evaluation, options: dict, grid: bool = True + ) -> Expression | Symbol: + "(StandardForm,TraditionalForm,InputForm,OutputForm,): Information[strpat_String, OptionsPattern[Information]]" + definitions = evaluation.definitions + string_str = strpat.value + if any(char in string_str for char in NAMES_WILDCARDS): + return self.build_list_of_matching_symbols( + string_str, evaluation, options, grid + ) + try: + symbol_name = definitions.get_definition( + string_str, only_if_exists=True + ).name + except KeyError: + return self.build_missing(strpat) + return self.format_information_symbol(Symbol(symbol_name), evaluation, options) + + def format_information_symbol( + self, symbol: Symbol, evaluation: Evaluation, options: dict, grid: bool = True + ) -> Expression | Symbol: + "(StandardForm,TraditionalForm,InputForm,OutputForm,): Information[symbol_Symbol, OptionsPattern[Information]]" + definitions = evaluation.definitions + try: + definitions.get_definition(symbol.name, True) + except KeyError: + return self.build_missing(symbol) + + lines: list[Expression | String] = [] + # Print the "usage" message if available. + # is_long_form = self.get_option(options, "LongForm", evaluation).to_python() + is_long_form = True # In WMA >=12.0 this option does not make much difference-- + usagetext = online_doc_string(symbol, evaluation.definitions, is_long_form) + if usagetext: + lines.append(String(usagetext)) + else: + lines.append(String(symbol.get_name())) + + if is_long_form and ( + info := gather_and_format_definition_rules(symbol, evaluation) + ): + lines.extend(info) + + infoshow = Expression( + SymbolGrid, + ListExpression(*(line for line in lines)), + Expression(SymbolRule, Symbol("ColumnAlignments"), SymbolLeft), + ) + return infoshow + class SymbolQ(Test): """ diff --git a/mathics/core/atoms/associations.py b/mathics/core/atoms/associations.py index cbacbc02b5..a0e84237cd 100644 --- a/mathics/core/atoms/associations.py +++ b/mathics/core/atoms/associations.py @@ -302,7 +302,7 @@ def update(self, e: Iterable): self._hash = None -def association_from_mathics3_kv_dict(d: dict[BaseElement, BaseElement]) -> Association: +def association_from_mathics3_kv_dict(d: dict) -> Association: """ Given a Python dictionary contianing Mathics3 keys and values, return and Assocation for that. diff --git a/mathics/core/builtin.py b/mathics/core/builtin.py index ac8eba81a2..cd48318b2a 100644 --- a/mathics/core/builtin.py +++ b/mathics/core/builtin.py @@ -223,6 +223,7 @@ def eval_with_options(x, evaluation: Evaluation, options: dict): name: Optional[str] = None options: dict[str, Any] = {} rules: dict[str, Any] = {} + summary_text = "" def __getnewargs_ex__(self): return tuple(), { diff --git a/mathics/eval/symbol/properties.py b/mathics/eval/symbol/properties.py index fda67b1b1c..70d4fd0594 100644 --- a/mathics/eval/symbol/properties.py +++ b/mathics/eval/symbol/properties.py @@ -3,7 +3,7 @@ """ from functools import cache -from typing import Final +from typing import Callable, Final, Optional from mathics.core.assignment import get_symbol_values from mathics.core.atoms import String @@ -11,24 +11,32 @@ Association, association_from_mathics3_kv_dict, ) -from mathics.core.attributes import A_READ_PROTECTED -from mathics.core.definitions import Definitions +from mathics.core.attributes import A_READ_PROTECTED, attributes_bitset_to_list +from mathics.core.convert.expression import to_mathics_list +from mathics.core.definitions import Definition, Definitions from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.rules import RewriteRule -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolUpSet from mathics.core.systemsymbols import ( SymbolAttributes, + SymbolDefinition, SymbolDownValues, + SymbolFormat, + SymbolHoldForm, + SymbolInfix, SymbolInformationData, SymbolInformationDataGrid, + SymbolInputForm, SymbolMessageName, SymbolMissing, SymbolNone, SymbolNValues, SymbolOptions, SymbolOwnValues, + SymbolRule, + SymbolSet, SymbolSubValues, SymbolUnknownProperty, SymbolUnknownSymbol, @@ -56,14 +64,88 @@ ) +def eval_Definition( + symbol: Symbol, evaluation: Evaluation +) -> Optional[list[Expression]]: + """Return a list of lines describing the definition of `symbol`""" + lines = [] + + def rhs_format(expr): + if expr.has_form(SymbolInfix, None): + expr = Expression(Expression(SymbolHoldForm, expr.head), *expr.elements) + return expr + + name = symbol.get_name() + if not name: + evaluation.message("Definition", "sym", symbol, 1) + return None + + try: + all = evaluation.definitions.get_definition(name) + attributes = all.attributes + all_options = all.options + all_defaultvalues = all.defaultvalues + + if attributes: + attributes_list = attributes_bitset_to_list(attributes) + lines.append( + Expression( + SymbolHoldForm, + Expression( + SymbolSet, + Expression(SymbolAttributes, symbol), + to_mathics_list( + *attributes_list, elements_conversion_fn=Symbol + ), + ), + ) + ) + except KeyError: + attributes = 0 + all_options = {} + all_defaultvalues = [] + + if not A_READ_PROTECTED & attributes: + try: + definition = evaluation.definitions.get_user_definition(name, create=False) + gather_rules(definition, evaluation, lines, rhs_format=rhs_format) + except KeyError: + pass + + for rule in all_defaultvalues: + format_rule(rule, evaluation, lines) + if all_options: + options = sorted(all_options.items()) + lines.append( + Expression( + SymbolHoldForm, + Expression( + SymbolSet, + Expression(SymbolOptions, symbol), + ListExpression( + *( + Expression(SymbolRule, Symbol(name), Symbol(value)) + for name, value in options + ) + ), + ), + ) + ) + return lines + + pass + + def eval_Information(name, evaluation: Evaluation): """ Evaluation routine for: Information[name] """ if isinstance(name, String): names: list[str] = get_matching_names(name.value, evaluation) - if len(names) > 1: + if (n := len(names)) > 1: return Expression(SymbolInformationDataGrid, *[String(n) for n in names]) + elif n == 0: + return None name_symbol = Symbol(names[0]) elif isinstance(name, Symbol): name_symbol = name @@ -188,6 +270,154 @@ def eval_values(name, evaluation: Evaluation, attribute: str): return get_symbol_values(name_symbol, attribute, attribute.lower(), evaluation) +def format_rule( + rule: RewriteRule, + evaluation: Evaluation, + lines, + up: bool = False, + lhs: Callable = lambda k: k, + rhs: Callable = lambda r: r, +): + """ + Add a line showing `rule` + """ + evaluation.check_stopped() + if isinstance(rule, RewriteRule): + lhs_pat = Expression(SymbolInputForm, lhs(rule.pattern.expr)) + repl_expr = rhs( + rule.replace.replace_vars( + {"System`Definition": Expression(SymbolHoldForm, SymbolDefinition)} + ) + ) + repl_expr = Expression(SymbolInputForm, repl_expr) + lines.append( + Expression( + SymbolHoldForm, + Expression(up and SymbolUpSet or SymbolSet, lhs_pat, repl_expr), + ) + ) + + +def gather_and_format_definition_rules( + symbol: Symbol, evaluation: Evaluation +) -> Optional[list[Expression]]: + """Return a list of lines describing the definition of `symbol`""" + lines = [] + + def rhs_format(expr): + if expr.has_form(SymbolInfix, None): + expr = Expression(Expression(SymbolHoldForm, expr.head), *expr.elements) + return expr + + def format_rule( + rule: RewriteRule, + up: bool = False, + lhs: Callable = lambda k: k, + rhs: Callable = lambda r: r, + ): + """ + Add a line showing `rule` + """ + evaluation.check_stopped() + if isinstance(rule, RewriteRule): + lhs_pat = Expression(SymbolInputForm, lhs(rule.pattern.expr)) + repl_expr = rhs( + rule.replace.replace_vars( + {"System`Definition": Expression(SymbolHoldForm, SymbolDefinition)} + ) + ) + repl_expr = Expression(SymbolInputForm, repl_expr) + lines.append( + Expression( + SymbolHoldForm, + Expression(up and SymbolUpSet or SymbolSet, lhs_pat, repl_expr), + ) + ) + + name = symbol.get_name() + if not name: + evaluation.message("Definition", "sym", symbol, 1) + return + + try: + all = evaluation.definitions.get_definition(name) + attributes = all.attributes + all_options = all.options + all_defaultvalues = all.defaultvalues + + if attributes: + attributes_list = attributes_bitset_to_list(attributes) + lines.append( + Expression( + SymbolHoldForm, + Expression( + SymbolSet, + Expression(SymbolAttributes, symbol), + to_mathics_list( + *attributes_list, elements_conversion_fn=Symbol + ), + ), + ) + ) + except KeyError: + attributes = 0 + all_options = {} + all_defaultvalues = [] + + if not A_READ_PROTECTED & attributes: + try: + definition = evaluation.definitions.get_user_definition(name, create=False) + gather_rules(definition, evaluation, lines, rhs_format=rhs_format) + except KeyError: + pass + + for rule in all_defaultvalues: + format_rule(rule) + if all_options: + options = sorted(all_options.items()) + lines.append( + Expression( + SymbolHoldForm, + Expression( + SymbolSet, + Expression(SymbolOptions, symbol), + ListExpression( + *( + Expression(SymbolRule, Symbol(name), value) + for name, value in options + ) + ), + ), + ) + ) + return lines + + +def gather_rules(definition: Definition, evaluation, lines, rhs_format: Callable): + """ + Add to the description all the rules associated + to a definition object + """ + for rule in definition.ownvalues: + format_rule(rule, evaluation, lines) + for rule in definition.downvalues: + format_rule(rule, evaluation, lines) + for rule in definition.subvalues: + format_rule(rule, evaluation, lines) + for rule in definition.upvalues: + format_rule(rule, evaluation, lines, up=True) + for rule in definition.nvalues: + format_rule(rule, evaluation, lines) + formats = sorted(definition.formatvalues.items()) + for form_name, rules in formats: + for rule in rules: + + def lhs_format(expr): + return Expression(SymbolFormat, expr, Symbol(form_name)) + + format_rule(rule, evaluation, lines, lhs=lhs_format, rhs=rhs_format) + + def get_matching_names(symbol_pat: str, evaluation: Evaluation) -> list[str]: """Return a list of symbols in eval.definitions matching `symbol_pat`""" return evaluation.definitions.get_matching_names(symbol_pat) @@ -280,7 +510,7 @@ def information_usage( # No "usage" message has been defined on this symbol definition. # If the symbol is a builtin-funciton, we should be able to get the "summary_text" # value from the Python builtin class that defines the Builtin Function. - if builtin_class := get_builtin_class(name_str, definitions): + if (builtin_class := get_builtin_class(name_str, definitions)) is not None: # I, rocky, take full responsibility for propagating # "summary_text", which at the time matched the crappy # Django homegrown documentation better. diff --git a/mathics/format/form_rule/information.py b/mathics/format/form_rule/information.py index 6f3110f683..667023814f 100644 --- a/mathics/format/form_rule/information.py +++ b/mathics/format/form_rule/information.py @@ -3,28 +3,14 @@ from mathics_scanner.tokeniser import NAMES_WILDCARDS from mathics.core.atoms import String -from mathics.core.attributes import A_READ_PROTECTED, attributes_bitset_to_list -from mathics.core.convert.expression import to_mathics_list from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.rules import RewriteRule -from mathics.core.symbols import Symbol, SymbolUpSet -from mathics.core.systemsymbols import ( - SymbolAttributes, - SymbolDefinition, - SymbolFormat, - SymbolGrid, - SymbolHoldForm, - SymbolInfix, - SymbolInputForm, - SymbolLeft, - SymbolOptions, - SymbolRule, - SymbolSet, -) +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import SymbolGrid, SymbolLeft, SymbolRule from mathics.doc.online import online_doc_string +from mathics.eval.symbol.properties import eval_Definition def format_information_generic( @@ -74,9 +60,7 @@ def format_information_symbol( else: lines.append(String(symbol.get_name())) - if is_long_form and ( - info := gather_and_format_definition_rules(symbol, evaluation) - ): + if is_long_form and (info := eval_Definition(symbol, evaluation)): lines.extend(info) infoshow = Expression( @@ -85,123 +69,3 @@ def format_information_symbol( Expression(SymbolRule, Symbol("ColumnAlignments"), SymbolLeft), ) return infoshow - - -# FIXME: gather_and_format_definition_rules is crap and needs to be revised, rewritten and put in -# mathics.eval.symbols.properties -def gather_and_format_definition_rules( - symbol: Symbol, evaluation: Evaluation -) -> Optional[list[Expression]]: - """Return a list of lines describing the definition of `symbol`""" - lines = [] - - def rhs_format(expr): - if expr.has_form(SymbolInfix, None): - expr = Expression(Expression(SymbolHoldForm, expr.head), *expr.elements) - return expr - - def format_rule( - rule: RewriteRule, - up: bool = False, - lhs: Callable = lambda k: k, - rhs: Callable = lambda r: r, - ): - """ - Add a line showing `rule` - """ - evaluation.check_stopped() - if isinstance(rule, RewriteRule): - lhs_pat = Expression(SymbolInputForm, lhs(rule.pattern.expr)) - repl_expr = rhs( - rule.replace.replace_vars( - {"System`Definition": Expression(SymbolHoldForm, SymbolDefinition)} - ) - ) - repl_expr = Expression(SymbolInputForm, repl_expr) - lines.append( - Expression( - SymbolHoldForm, - Expression(up and SymbolUpSet or SymbolSet, lhs_pat, repl_expr), - ) - ) - - def gather_rules(definition): - """ - Add to the description all the rules associated - to a definition object - """ - for rule in definition.ownvalues: - format_rule(rule) - for rule in definition.downvalues: - format_rule(rule) - for rule in definition.subvalues: - format_rule(rule) - for rule in definition.upvalues: - format_rule(rule, up=True) - for rule in definition.nvalues: - format_rule(rule) - formats = sorted(definition.formatvalues.items()) - for form_name, rules in formats: - for rule in rules: - - def lhs_format(expr): - return Expression(SymbolFormat, expr, Symbol(form_name)) - - format_rule(rule, lhs=lhs_format, rhs=rhs_format) - - name = symbol.get_name() - if not name: - evaluation.message("Definition", "sym", symbol, 1) - return None - - try: - all = evaluation.definitions.get_definition(name) - attributes = all.attributes - all_options = all.options - all_defaultvalues = all.defaultvalues - - if attributes: - attributes_list = attributes_bitset_to_list(attributes) - lines.append( - Expression( - SymbolHoldForm, - Expression( - SymbolSet, - Expression(SymbolAttributes, symbol), - to_mathics_list( - *attributes_list, elements_conversion_fn=Symbol - ), - ), - ) - ) - except KeyError: - attributes = 0 - all_options = {} - all_defaultvalues = [] - - if not A_READ_PROTECTED & attributes: - try: - gather_rules(evaluation.definitions.get_user_definition(name, create=False)) - except KeyError: - pass - - for rule in all_defaultvalues: - format_rule(rule) - if all_options: - options = sorted(all_options.items()) - lines.append( - Expression( - SymbolHoldForm, - Expression( - SymbolSet, - Expression(SymbolOptions, symbol), - ListExpression( - *( - Expression(SymbolRule, Symbol(name), Symbol(value)) - for name, value in options - ) - ), - ), - ) - ) - return lines diff --git a/test/core/parser/test_parser.py b/test/core/parser/test_parser.py index 91f681c5f6..06de8e84d9 100644 --- a/test/core/parser/test_parser.py +++ b/test/core/parser/test_parser.py @@ -634,8 +634,8 @@ def testInequality(self): ) def testInformation(self): - self.check("??a", 'Information["a", LongForm -> True]') - self.check("a ?? b", 'a Information["b", LongForm -> True]') + self.check("??a", 'Information["a"]') + self.check("a ?? b", 'a Information["b"]') class BoxTests(ParserTests): From ff2ca7bb3ed23c5e139bf6d70d8aa1b1ace4900b Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 4 Oct 2026 18:21:54 -0400 Subject: [PATCH 5/6] One small tweak Report missing symbol more often --- mathics/core/definitions.py | 2 +- mathics/eval/symbol/properties.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 906fccb904..ad78468b2f 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -76,7 +76,7 @@ def __init__( self.ownvalues = rules_dict.get("ownvalues", []) self.subvalues = rules_dict.get("subvalues", []) self.upvalues = rules_dict.get("upvalues", []) - self.options: dict[str, str] = rules_dict.get("options", {}) + self.options: dict[str, BaseElement] = rules_dict.get("options", {}) self.messages = rules_dict.get("messages", []) self.is_numeric = is_numeric diff --git a/mathics/eval/symbol/properties.py b/mathics/eval/symbol/properties.py index 70d4fd0594..19992e305c 100644 --- a/mathics/eval/symbol/properties.py +++ b/mathics/eval/symbol/properties.py @@ -145,7 +145,7 @@ def eval_Information(name, evaluation: Evaluation): if (n := len(names)) > 1: return Expression(SymbolInformationDataGrid, *[String(n) for n in names]) elif n == 0: - return None + return missing_symbol(name) name_symbol = Symbol(names[0]) elif isinstance(name, Symbol): name_symbol = name From 8240779288f31a52cc72a09e37f959aafdd22e97 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 5 Oct 2026 21:31:46 -0400 Subject: [PATCH 6/6] Reinstate LongForm -> {True,False} LongForm now just indicates how much data is put into InformationData. --- mathics/builtin/symbol/properties.py | 7 +- mathics/core/parser/ast.py | 21 ++++-- mathics/core/parser/parser.py | 10 +++ mathics/core/systemsymbols.py | 1 + mathics/eval/numbers/algebra/polynomial.py | 2 +- mathics/eval/symbol/properties.py | 85 +++++++++++++--------- test/core/parser/test_parser.py | 5 +- 7 files changed, 82 insertions(+), 49 deletions(-) diff --git a/mathics/builtin/symbol/properties.py b/mathics/builtin/symbol/properties.py index d7fc3361e6..3cc71dc9ee 100644 --- a/mathics/builtin/symbol/properties.py +++ b/mathics/builtin/symbol/properties.py @@ -309,13 +309,10 @@ def build_list_of_matching_symbols( def eval(self, expr, evaluation: Evaluation, options: dict): "Information[expr_, OptionsPattern[Information]]" - return eval_Information(expr, evaluation) + return eval_Information(expr, evaluation, options) def eval_with_property(self, expr, prop, evaluation: Evaluation, options: dict): - "Information[expr_, prop_, OptionsPattern[Information]]" - if is_rule(prop): - # FIXME we have an option here. - return + "Information[expr_, prop_String, OptionsPattern[Information]]" if not isinstance(prop, String): return Expression(SymbolMissing, SymbolUnknownSymbol, prop) diff --git a/mathics/core/parser/ast.py b/mathics/core/parser/ast.py index 292302f504..2ccede7cb4 100644 --- a/mathics/core/parser/ast.py +++ b/mathics/core/parser/ast.py @@ -13,7 +13,7 @@ So we have mathics.core.expression.Atom vs. mathics.core.parser.Atom """ -from typing import Optional +from typing import Final, Optional class Node: @@ -163,7 +163,9 @@ class String(Atom): The display of a String is surrounded by double quotes. """ - def __repr__(self): + value: str + + def __repr__(self) -> str: return '"' + self.value + '"' @@ -173,12 +175,15 @@ class Filename(Atom): However, like String, it doesn't have any other properties. """ - def __repr__(self): + value: str + + def __repr__(self) -> str: return self.value -# Some common literals -NullSymbol = Symbol("Null") -NullString = String("") -Number1 = Number("1") -NumberM1 = Number("1", sign=-1) +# Some literals. +LongForm: Final[Symbol] = Symbol("LongForm") +NullSymbol: Final[Symbol] = Symbol("Null") +NullString: Final[String] = String("") +Number1: Final[Number] = Number("1") +NumberM1: Final[Number] = Number("1", sign=-1) diff --git a/mathics/core/parser/parser.py b/mathics/core/parser/parser.py index f294cf579e..b391df007c 100644 --- a/mathics/core/parser/parser.py +++ b/mathics/core/parser/parser.py @@ -21,6 +21,7 @@ from mathics.core.convert.op import builtin_constants from mathics.core.parser.ast import ( Filename, + LongForm, Node, NullString, NullSymbol, @@ -592,6 +593,14 @@ def parse_expr(self, precedence: int) -> Optional[Node]: return result def parse_information_common(self, token: Token, want_long_form: bool) -> Node: + """Create an Information[] node expression. The variable " + + want_long_form" is "True" when given prefix expression "??" + is parsed, and it is False, when prefix expression "?" is + parsed. Although, in current WMA, the LongForm option is not + documented, it controls how much data filled in to a + InformationData structure. + """ self.consume() pattern_token = self.parse_name_pattern() @@ -609,6 +618,7 @@ def parse_information_common(self, token: Token, want_long_form: bool) -> Node: return Node( "Information", pattern_arg, + Node("Rule", LongForm, long_form), ) @track_location diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 75534875b3..167121b026 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -216,6 +216,7 @@ SymbolLog: Final[Symbol] = Symbol("System`Log") SymbolLog10: Final[Symbol] = Symbol("System`Log10") SymbolLogPlot: Final[Symbol] = Symbol("System`LogPlot") +SymbolLongForm: Final[Symbol] = Symbol("System`LongForm") SymbolLongest: Final[Symbol] = Symbol("System`Longest") SymbolMachinePrecision: Final[Symbol] = Symbol("System`MachinePrecision") SymbolMakeBoxes: Final[Symbol] = Symbol("System`MakeBoxes") diff --git a/mathics/eval/numbers/algebra/polynomial.py b/mathics/eval/numbers/algebra/polynomial.py index cf7220a773..126dc9ff2a 100644 --- a/mathics/eval/numbers/algebra/polynomial.py +++ b/mathics/eval/numbers/algebra/polynomial.py @@ -143,7 +143,7 @@ def coeff_power( # ###### Auxiliary functions ######### def key_powers(lst: list) -> Union[int, float]: key = Expression(SymbolPlus, *lst).evaluate(evaluation) - if key is not None and is_numeric(key, evaluation): + if is_numeric(key, evaluation): return key.to_python() return 0 diff --git a/mathics/eval/symbol/properties.py b/mathics/eval/symbol/properties.py index 19992e305c..ccdf812506 100644 --- a/mathics/eval/symbol/properties.py +++ b/mathics/eval/symbol/properties.py @@ -18,7 +18,7 @@ from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.rules import RewriteRule -from mathics.core.symbols import Symbol, SymbolUpSet +from mathics.core.symbols import Symbol, SymbolTrue, SymbolUpSet from mathics.core.systemsymbols import ( SymbolAttributes, SymbolDefinition, @@ -136,7 +136,7 @@ def rhs_format(expr): pass -def eval_Information(name, evaluation: Evaluation): +def eval_Information(name, evaluation: Evaluation, options: dict): """ Evaluation routine for: Information[name] """ @@ -153,37 +153,56 @@ def eval_Information(name, evaluation: Evaluation): return None # name is now a Symbol name_str = name_symbol.name - info_data = { - Symbol("ObjectType"): String("Symbol"), - Symbol("Usage"): information_usage( - name_symbol, name_str, evaluation.definitions - ), - Symbol("Documentation"): information_documentation( - name_str, evaluation.definitions - ), - Symbol("FullName"): String(name_str), - SymbolAttributes: eval_Attributes(name, evaluation), - SymbolOptions: eval_Options(name_symbol, evaluation, empty_is_none=True), - Symbol("DefaultValues"): information_values( - name_symbol, name_str, evaluation, "DefaultValues" - ), - SymbolDownValues: information_values( - name_symbol, name_str, evaluation, "DownValues" - ), - Symbol("FormatValues"): information_values( - name_symbol, name_str, evaluation, "FormatValues" - ), - SymbolNValues: information_values(name_symbol, name_str, evaluation, "NValues"), - SymbolOwnValues: information_values( - name_symbol, name_str, evaluation, "OwnValues" - ), - SymbolSubValues: information_values( - name_symbol, name_str, evaluation, "SubValues" - ), - SymbolUpValues: information_values( - name_symbol, name_str, evaluation, "UpValues" - ), - } + long_form = options.get("System`LongForm") is SymbolTrue + if long_form: + info_data = { + Symbol("ObjectType"): String("Symbol"), + Symbol("Usage"): information_usage( + name_symbol, name_str, evaluation.definitions + ), + Symbol("Documentation"): information_documentation( + name_str, evaluation.definitions + ), + Symbol("FullName"): String(name_str), + SymbolAttributes: eval_Attributes(name, evaluation), + SymbolOptions: eval_Options(name_symbol, evaluation, empty_is_none=True), + Symbol("DefaultValues"): information_values( + name_symbol, name_str, evaluation, "DefaultValues" + ), + SymbolDownValues: information_values( + name_symbol, name_str, evaluation, "DownValues" + ), + Symbol("FormatValues"): information_values( + name_symbol, name_str, evaluation, "FormatValues" + ), + SymbolNValues: information_values( + name_symbol, name_str, evaluation, "NValues" + ), + SymbolOwnValues: information_values( + name_symbol, name_str, evaluation, "OwnValues" + ), + SymbolSubValues: information_values( + name_symbol, name_str, evaluation, "SubValues" + ), + SymbolUpValues: information_values( + name_symbol, name_str, evaluation, "UpValues" + ), + } + else: + info_data = { + Symbol("Usage"): information_usage( + name_symbol, name_str, evaluation.definitions + ), + Symbol("FullName"): String(name_str), + SymbolAttributes: eval_Attributes(name, evaluation), + SymbolOptions: eval_Options(name_symbol, evaluation, empty_is_none=True), + SymbolDownValues: information_values( + name_symbol, name_str, evaluation, "DownValues" + ), + SymbolOwnValues: information_values( + name_symbol, name_str, evaluation, "OwnValues" + ), + } association: Association = association_from_mathics3_kv_dict(info_data) return Expression(SymbolInformationData, association) diff --git a/test/core/parser/test_parser.py b/test/core/parser/test_parser.py index 06de8e84d9..7105df3a72 100644 --- a/test/core/parser/test_parser.py +++ b/test/core/parser/test_parser.py @@ -634,8 +634,9 @@ def testInequality(self): ) def testInformation(self): - self.check("??a", 'Information["a"]') - self.check("a ?? b", 'a Information["b"]') + self.check("??a", 'Information["a", LongForm -> True]') + self.check("?a", 'Information["a", LongForm -> False]') + self.check("a ?? b", 'a Information["b", LongForm -> True]') class BoxTests(ParserTests):