From d58c2effe892af04368046131ad1f3a8f291c2f7 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jul 2026 11:16:53 -0400 Subject: [PATCH 1/7] class Rule -> RewriteRule --- mathics/builtin/atomic/symbols.py | 6 ++-- mathics/builtin/box/graphics.py | 2 +- .../builtin/functional/apply_fns_to_lists.py | 4 ++- mathics/builtin/list/eol.py | 12 ++++---- mathics/core/assignment.py | 10 +++---- mathics/core/builtin.py | 14 ++++----- mathics/core/definitions.py | 4 +-- mathics/core/evaluation.py | 7 +++-- mathics/core/expression.py | 4 +-- mathics/core/rules.py | 4 +-- mathics/eval/assignments/assignment.py | 16 +++++----- mathics/eval/debug_tuning/dialog.py | 8 +++-- mathics/eval/inference.py | 30 +++++++++---------- mathics/eval/rules.py | 10 +++---- 14 files changed, 67 insertions(+), 64 deletions(-) diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index 628bc0af0..bba36dcb6 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/atomic/symbols.py @@ -28,7 +28,7 @@ from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.rules import Rule +from mathics.core.rules import RewriteRule from mathics.core.symbols import ( Symbol, SymbolFalse, @@ -69,7 +69,7 @@ def rhs_format(expr): return expr def format_rule( - rule: Rule, + rule: RewriteRule, up: bool = False, lhs: Callable = lambda k: k, rhs: Callable = lambda r: r, @@ -78,7 +78,7 @@ def format_rule( Add a line showing `rule` """ evaluation.check_stopped() - if isinstance(rule, Rule): + if isinstance(rule, RewriteRule): lhs_pat = Expression(SymbolInputForm, lhs(rule.pattern.expr)) repl_expr = rhs( rule.replace.replace_vars( diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index 1a5b4b431..b510ceebe 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -763,7 +763,7 @@ def init(self, graphics, style, item=None): self.do_init(graphics, points) self.vertex_colors = None for element in item.elements[1:]: - if not element.has_form("Rule", 2): + if not element.has_form(("RewriteRule" "Rule"), 2): raise BoxExpressionError name = element.elements[0].get_name() self.process_option(name, element.elements[1]) diff --git a/mathics/builtin/functional/apply_fns_to_lists.py b/mathics/builtin/functional/apply_fns_to_lists.py index 7d663f8be..93a630685 100644 --- a/mathics/builtin/functional/apply_fns_to_lists.py +++ b/mathics/builtin/functional/apply_fns_to_lists.py @@ -191,7 +191,9 @@ def callback(level): # # Fixing this would require a different implementation of this eval_ method. # - if is_association and level.has_form(("Rule", "RuleDelayed"), 2): + if is_association and level.has_form( + ("Rule", "RewriteRule", "RuleDelayed"), 2 + ): return Expression( level.get_head(), level.elements[0], diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index 7bb938538..7e93ccea0 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -43,7 +43,7 @@ from mathics.core.expression import Expression from mathics.core.expression_predefined import MATHICS3_INFINITY from mathics.core.list import ListExpression -from mathics.core.rules import Rule +from mathics.core.rules import RewriteRule from mathics.core.symbols import Atom, Symbol, SymbolNull, SymbolTrue from mathics.core.systemsymbols import ( SymbolAppend, @@ -216,7 +216,7 @@ def eval(self, items, pattern, levelspec, evaluation, options): if isinstance(items, Atom): return ListExpression() - if levelspec.has_form("Rule", 2): + if levelspec.has_form(("RewriteRule", "Rule"), 2): if levelspec.elements[0].get_name() == "System`Heads": heads = levelspec.elements[1] is SymbolTrue levelspec = ListExpression(Integer1) @@ -234,9 +234,9 @@ def eval(self, items, pattern, levelspec, evaluation, options): results = [] - if pattern.has_form("Rule", 2) or pattern.has_form("RuleDelayed", 2): + if pattern.has_form(("RewriteRule", "Rule", "RuleDelayed"), 2): match = Matcher(pattern.elements[0], evaluation).match - rule = Rule(pattern.elements[0], pattern.elements[1]) + rule = RewriteRule(pattern.elements[0], pattern.elements[1]) def callback(level): if match(level, evaluation): @@ -1497,9 +1497,7 @@ def eval(self, expr, replacements, evaluation): new_expr = expr.copy() replacements = replacements.get_sequence() for replacement in replacements: - if not replacement.has_form("Rule", 2) and not replacement.has_form( # noqa - "RuleDelayed", 2 - ): + if not replacement.has_form(("RewriteRule", "Rule", "RuleDelayed"), 2): evaluation.message("ReplacePart", "reps", ListExpression(*replacements)) return position = replacement.elements[0] diff --git a/mathics/core/assignment.py b/mathics/core/assignment.py index 0a94f0fe9..a75fcd28a 100644 --- a/mathics/core/assignment.py +++ b/mathics/core/assignment.py @@ -14,7 +14,7 @@ from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.rules import Rule +from mathics.core.rules import RewriteRule from mathics.core.symbols import Atom, Symbol, SymbolList from mathics.core.systemsymbols import ( SymbolAnd, @@ -29,9 +29,9 @@ ) -def build_rulopc(optval: BaseElement) -> Rule: +def build_rulopc(optval: BaseElement) -> RewriteRule: """Build an option value rule for optval""" - return Rule( + return RewriteRule( Expression( SymbolOptionValue, Expression(SymbolPattern, Symbol("$cond$"), SymbolBlank), @@ -157,7 +157,7 @@ def get_symbol_values( return ListExpression(*elements) for rule in definition.get_values_list(position): - if isinstance(rule, Rule): + if isinstance(rule, RewriteRule): pattern = rule.pattern if pattern.has_form("HoldPattern", 1): expr_pattern = pattern.expr @@ -350,7 +350,7 @@ def unroll_patterns( # like # rhs = Expression(Symbol("System`Replace"), Rule(*rulerepl)) # TODO: check if this is the correct behavior. - rhs, _ = rhs.do_apply_rules([Rule(*rulerepl)], evaluation) + rhs, _ = rhs.do_apply_rules([RewriteRule(*rulerepl)], evaluation) name = lhs.get_head_name() elif name == "System`HoldPattern": lhs = lhs_elements[0] diff --git a/mathics/core/builtin.py b/mathics/core/builtin.py index 125cc9e5e..0479d3174 100644 --- a/mathics/core/builtin.py +++ b/mathics/core/builtin.py @@ -60,7 +60,7 @@ from mathics.core.parser.operators import OPERATOR_DATA from mathics.core.parser.util import PyMathicsDefinitions, SystemDefinitions from mathics.core.pattern import BasePattern, build_pattern_sort_key -from mathics.core.rules import BaseRule, FunctionApplyRule, Rule +from mathics.core.rules import BaseRule, FunctionApplyRule, RewriteRule from mathics.core.symbols import ( Atom, BaseElement, @@ -309,7 +309,7 @@ def contribute(self, definitions: Definitions, is_pymodule=False): replace_str = replace_str % {"name": name} pat_attr = attributes if pattern.get_head_name() == name else None rules.append( - Rule( + RewriteRule( pattern, parse_builtin_rule(replace_str), attributes=pat_attr, @@ -374,7 +374,7 @@ def contextify_form_name(f): pattern = parse_builtin_rule(pattern) replace = replace % {"name": name} formatvalues[form].append( - Rule(pattern, parse_builtin_rule(replace), system=True) + RewriteRule(pattern, parse_builtin_rule(replace), system=True) ) formatvalues.setdefault("_MakeBoxes", []).extend(box_rules) @@ -385,7 +385,7 @@ def contextify_form_name(f): if hasattr(self, "summary_text"): self.messages["usage"] = self.summary_text messages = [ - Rule( + RewriteRule( Expression(SymbolMessageName, Symbol(name), String(msg)), String(value), system=True, @@ -394,7 +394,7 @@ def contextify_form_name(f): ] messages.append( - Rule( + RewriteRule( Expression(SymbolMessageName, Symbol(name), String("optx")), String("`1` is not a supported option for `2`[]."), system=True, @@ -410,7 +410,7 @@ def contextify_form_name(f): elif isinstance(spec, int): pattern = Expression(SymbolDefault, Symbol(name), Integer(spec)) if pattern is not None: - defaults.append(Rule(pattern, value, system=True)) + defaults.append(RewriteRule(pattern, value, system=True)) definition = Definition( name=name, @@ -1389,7 +1389,7 @@ def __init__(self, *args, **kwargs): operator = ascii_operator_to_symbol.get(self.operator, self.__class__.__name__) if self.default_formats: - if name not in ("Rule", "RuleDelayed"): + if name not in ("RewriteRule", "Rule", "RuleDelayed"): formats = { op_pattern: "HoldForm[Infix[{%s}, %s, %d, %s]]" % (replace_items, operator, self.precedence, self.grouping) diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index c58c60869..234ab7194 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -18,7 +18,7 @@ from mathics.core.attributes import A_NO_ATTRIBUTES from mathics.core.convert.expression import to_mathics_list from mathics.core.element import BaseElement, fully_qualified_symbol_name -from mathics.core.rules import BaseRule, Rule +from mathics.core.rules import BaseRule, RewriteRule from mathics.core.symbols import Atom, Symbol, strip_context from mathics.core.util import canonic_filename from mathics.settings import ROOT_DIR @@ -771,7 +771,7 @@ def get_ownvalue(self, name: str) -> BaseElement: def set_ownvalue(self, name: str, value) -> None: """Set an ownvalue for name""" name = self.lookup_name(name) - self.add_rule(name, Rule(Symbol(name), value)) + self.add_rule(name, RewriteRule(Symbol(name), value)) self.clear_cache(name) def set_options(self, name: str, options) -> None: diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py index 06e0e3be3..c3c09dae0 100644 --- a/mathics/core/evaluation.py +++ b/mathics/core/evaluation.py @@ -214,7 +214,7 @@ def evaluate(self, query, timeout=None, format=None): """ from mathics.core.convert.expression import to_expression from mathics.core.expression import Expression - from mathics.core.rules import Rule + from mathics.core.rules import RewriteRule self.start_time = time.time() self.iteration_count = 0 @@ -242,7 +242,7 @@ def check_io_hook(hook): def evaluate(): if history_length > 0: self.definitions.add_rule( - "In", Rule(to_expression("In", line_no), query) + "In", RewriteRule(to_expression("In", line_no), query) ) if check_io_hook("System`$Pre"): self.last_eval = Expression(SymbolPre, query).evaluate(self) @@ -260,7 +260,8 @@ def evaluate(): stored_result = self.get_stored_result(out_result, output_forms) self.definitions.add_rule( - "Out", Rule(Expression(SymbolOut, Integer(line_no)), stored_result) + "Out", + RewriteRule(Expression(SymbolOut, Integer(line_no)), stored_result), ) if self.last_eval != self.SymbolNull: if check_io_hook("System`$PrePrint"): diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 11256aeb5..f68e8eaa1 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -864,7 +864,7 @@ def get_rules_list(self) -> Optional[list]: If the expression is of the form {pat1->expr1,... {pat_2,expr2},...} return a (python) list of rules. """ - from mathics.core.rules import Rule + from mathics.core.rules import RewriteRule from mathics.core.symbols import SymbolList list_expr = self.flatten_with_respect_to_head(SymbolList) @@ -877,7 +877,7 @@ def get_rules_list(self) -> Optional[list]: for item in list: if not item.has_form(("Rule", "RuleDelayed"), 2): return None - rule = Rule(item.elements[0], item.elements[1]) + rule = RewriteRule(item.elements[0], item.elements[1]) rules.append(rule) return rules diff --git a/mathics/core/rules.py b/mathics/core/rules.py index 7439279b3..bdd6f17e1 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -235,7 +235,7 @@ def pattern_precedence(self) -> tuple: # FIXME: Given what is stated in the docstring below, # the class name would be better called RewriteRule, instead of the # more generic term Rule. -class Rule(BaseRule): +class RewriteRule(BaseRule): """There are two kinds of Rules. This kind of is a rewrite rule and transforms an Expression into another Expression based on the pattern and a replacement term and doesn't involve function @@ -266,7 +266,7 @@ def __init__( evaluation: Optional[Evaluation] = None, attributes: Optional[int] = None, ) -> None: - super(Rule, self).__init__( + super(RewriteRule, self).__init__( pattern, system=system, evaluation=evaluation, attributes=attributes ) self.replace = replace diff --git a/mathics/eval/assignments/assignment.py b/mathics/eval/assignments/assignment.py index 07cda6447..ba061eda5 100644 --- a/mathics/eval/assignments/assignment.py +++ b/mathics/eval/assignments/assignment.py @@ -26,7 +26,7 @@ set_python_recursion_limit, ) from mathics.core.expression import Expression -from mathics.core.rules import Rule +from mathics.core.rules import RewriteRule from mathics.core.symbols import ( Atom, Symbol, @@ -401,7 +401,7 @@ def eval_assign_default( tags = process_tags_and_upset_dont_allow_custom( tags, upset, self, lhs, lhs_reference, evaluation ) - rule = Rule(lhs, rhs) + rule = RewriteRule(lhs, rhs) for tag in tags: if rejected_because_protected(self, lhs, tag, evaluation): continue @@ -536,7 +536,7 @@ def eval_assign_format( tags = process_tags_and_upset_dont_allow_custom( tags, upset, self, lhs, lhs_reference, evaluation ) - rule = Rule(lhs, rhs) + rule = RewriteRule(lhs, rhs) for tag in tags: if rejected_because_protected(self, lhs, tag, evaluation): continue @@ -692,7 +692,7 @@ def eval_assign_makeboxes( target, form = lhs.elements # Check second argument - makeboxes_rule = Rule(lhs, rhs, system=False) + makeboxes_rule = RewriteRule(lhs, rhs, system=False) tags = [] if tags is None else tags if upset: tags = tags + [target.get_lookup_name()] @@ -850,7 +850,7 @@ def eval_assign_messagename( tags = process_tags_and_upset_dont_allow_custom( tags, upset, self, lhs, lhs_reference, evaluation ) - rule = Rule(lhs, rhs) + rule = RewriteRule(lhs, rhs) for tag in tags: # Messages can be assigned even if the symbol is protected... # if rejected_because_protected(self, lhs, tag, evaluation): @@ -1059,7 +1059,7 @@ def eval_assign_n( tags, upset, self, lhs, lhs_reference, evaluation ) count = 0 - rule = Rule(lhs, rhs) + rule = RewriteRule(lhs, rhs) for tag in tags: if rejected_because_protected(self, lhs, tag, evaluation): continue @@ -1240,7 +1240,7 @@ def eval_assign_store_rules_by_tag( # some combinatorica tests fail. # Also, should not be at the beginning? count = 0 - rule = Rule(lhs, rhs) + rule = RewriteRule(lhs, rhs) position = "upvalues" if upset else None for tag in tags: if rejected_because_protected(self, lhs, tag, evaluation, False): @@ -1284,7 +1284,7 @@ def eval_assign_to_symbol( tag = lhs_reference.get_name() if rejected_because_protected(self, lhs, tag, evaluation, ignore_protection): return False - evaluation.definitions.add_rule(tag, Rule(lhs, rhs), position="ownvalues") + evaluation.definitions.add_rule(tag, RewriteRule(lhs, rhs), position="ownvalues") return True diff --git a/mathics/eval/debug_tuning/dialog.py b/mathics/eval/debug_tuning/dialog.py index a8d7fd84c..5f0305ea8 100644 --- a/mathics/eval/debug_tuning/dialog.py +++ b/mathics/eval/debug_tuning/dialog.py @@ -1,9 +1,9 @@ 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.evaluation import Evaluation from mathics.core.expression import Expression -from mathics.core.rules import Rule +from mathics.core.rules import RewriteRule from mathics.core.systemsymbols import SymbolOut from mathics.repl import interactive_eval_loop @@ -29,7 +29,9 @@ def eval_Dialog(expr: BaseElement | None, evaluation: Evaluation): definitions = evaluation.definitions definitions.add_rule( "Out", - Rule(Expression(SymbolOut, Integer(saved_line_number)), result.last_eval), + RewriteRule( + Expression(SymbolOut, Integer(saved_line_number)), result.last_eval + ), ) shell.print_result(result) diff --git a/mathics/eval/inference.py b/mathics/eval/inference.py index b8cd61e42..664696ac9 100644 --- a/mathics/eval/inference.py +++ b/mathics/eval/inference.py @@ -7,7 +7,7 @@ from mathics.core.expression import Expression from mathics.core.parser import parse_builtin_rule from mathics.core.parser.util import SystemDefinitions -from mathics.core.rules import Rule +from mathics.core.rules import RewriteRule from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue from mathics.core.systemsymbols import SymbolAnd, SymbolEqual, SymbolNot, SymbolOr @@ -98,13 +98,13 @@ def ensure_logical_algebraic_rules(): for pattern, replace in logical_algebraic_rules_spec.items(): pattern = parse_builtin_rule(pattern, SystemDefinitions()) logical_algebraic_rules.append( - Rule(pattern, parse_builtin_rule(replace), system=True) + RewriteRule(pattern, parse_builtin_rule(replace), system=True) ) remove_not_rules = [] for pattern, replace in remove_not_rules_spec.items(): pattern = parse_builtin_rule(pattern, SystemDefinitions()) remove_not_rules.append( - Rule(pattern, parse_builtin_rule(replace), system=True) + RewriteRule(pattern, parse_builtin_rule(replace), system=True) ) return @@ -308,43 +308,43 @@ def get_assumption_rules_dispatch(evaluation): if value: lhs, rhs = pat.elements if lhs.is_numeric(evaluation): - assumption_rules.append(Rule(rhs, lhs)) + assumption_rules.append(RewriteRule(rhs, lhs)) else: - assumption_rules.append(Rule(lhs, rhs)) + assumption_rules.append(RewriteRule(lhs, rhs)) else: - assumption_rules.append(Rule(pat, SymbolFalse)) + assumption_rules.append(RewriteRule(pat, SymbolFalse)) symm_pat = Expression(pat._head, pat.elements[1], pat.elements[0]) - assumption_rules.append(Rule(symm_pat, SymbolFalse)) + assumption_rules.append(RewriteRule(symm_pat, SymbolFalse)) elif pat.has_form("Equivalent", 2): - assumption_rules.append(Rule(pat, symbol_value)) + assumption_rules.append(RewriteRule(pat, symbol_value)) symm_pat = Expression(pat._head, pat.elements[1], pat.elements[0]) - assumption_rules.append(Rule(symm_pat, symbol_value)) + assumption_rules.append(RewriteRule(symm_pat, symbol_value)) elif pat.has_form("Less", 2): if value: - assumption_rules.append(Rule(pat, SymbolTrue)) + assumption_rules.append(RewriteRule(pat, SymbolTrue)) assumption_rules.append( - Rule( + RewriteRule( Expression(pat._head, pat.elements[1], pat.elements[0]), SymbolFalse, ) ) for head in (SymbolEqual, Symbol("Equivalent")): assumption_rules.append( - Rule( + RewriteRule( Expression(head, pat.elements[0], pat.elements[1]), SymbolFalse, ) ) assumption_rules.append( - Rule( + RewriteRule( Expression(head, pat.elements[1], pat.elements[0]), SymbolFalse, ) ) else: - assumption_rules.append(Rule(pat, SymbolFalse)) + assumption_rules.append(RewriteRule(pat, SymbolFalse)) else: - assumption_rules.append(Rule(pat, symbol_value)) + assumption_rules.append(RewriteRule(pat, symbol_value)) # TODO: expand the pred and assumptions into an standard, # atomized form, and then apply the rules... if len(assumption_rules) == 0: diff --git a/mathics/eval/rules.py b/mathics/eval/rules.py index 7c5d24dc5..07dfa5b31 100644 --- a/mathics/eval/rules.py +++ b/mathics/eval/rules.py @@ -5,7 +5,7 @@ from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.rules import Rule +from mathics.core.rules import RewriteRule from mathics.core.symbols import Atom, Symbol, SymbolList from mathics.core.systemsymbols import SymbolDispatch, SymbolRule, SymbolRuleDelayed from mathics.eval.parts import python_levelspec @@ -18,7 +18,7 @@ def create_rules( name: str, evaluation: Evaluation, extra_args: OptionalType[List] = None, -) -> Union[Tuple[Union[List[Rule], BaseElement, None], bool], "Dispatch"]: +) -> Union[Tuple[Union[List[RewriteRule], BaseElement, None], bool], "Dispatch"]: """ This function implements `Replace`, `ReplaceAll`, `ReplaceRepeated` and `ReplaceList` eval methods. @@ -91,7 +91,7 @@ def create_rules( ) return None, True - result.append(Rule(rule.elements[0], rule.elements[1])) + result.append(RewriteRule(rule.elements[0], rule.elements[1])) return result, False @@ -150,7 +150,7 @@ class Dispatch(Atom): class_head_name = "System`Dispatch" src: ListExpression - rules: List[Rule] + rules: List[RewriteRule] def __init__( self, rule_tuple: Tuple[Expression, ...], evaluation: Evaluation @@ -159,7 +159,7 @@ def __init__( self.src = ListExpression(*rule_tuple) try: self.rules = [ - Rule(rule.elements[0], rule.elements[1]) for rule in rule_tuple + RewriteRule(rule.elements[0], rule.elements[1]) for rule in rule_tuple ] except: raise From 5ff7d5a7944fc072c7b43b46868c2e533bea56a4 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jul 2026 11:49:43 -0400 Subject: [PATCH 2/7] Add is_rule function. Remove evaluation parameter. Go over comments --- mathics/builtin/box/graphics.py | 11 +-- .../builtin/functional/apply_fns_to_lists.py | 5 +- mathics/builtin/list/associations.py | 17 ++-- mathics/builtin/list/eol.py | 8 +- mathics/builtin/patterns/rules.py | 2 +- mathics/core/expression.py | 12 ++- mathics/core/rules.py | 82 +++++++++++++------ mathics/doc/online.py | 2 +- mathics/eval/functional/apply_fns_to_lists.py | 3 +- mathics/eval/list/associations.py | 7 +- mathics/eval/rules.py | 4 +- mathics/format/box/numberform.py | 3 +- mathics/format/form/outputform.py | 3 +- 13 files changed, 98 insertions(+), 61 deletions(-) diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index b510ceebe..2372ae5d0 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -4,7 +4,7 @@ """ from abc import ABC from math import atan2, cos, degrees, pi, sin -from typing import Any, Dict, Final, List, Optional, Tuple +from typing import Any, Final, Optional from mathics.builtin.box.expression import BoxExpression from mathics.builtin.colors.color_directives import ( @@ -31,6 +31,7 @@ from mathics.core.expression import Expression from mathics.core.formatter import lookup_method from mathics.core.list import ListExpression +from mathics.core.rules import is_rule from mathics.core.symbols import Symbol from mathics.core.systemsymbols import SymbolInsetBox, SymbolTraditionalForm from mathics.format.box import format_element @@ -56,7 +57,7 @@ def init(self, graphics, item=None, style={}, opacity=1.0): # GraphicsElementBox Builtin class that should not get added as a definition, # and therefore not added to to external documentation. -DOES_NOT_ADD_BUILTIN_DEFINITION: Final[List[BoxExpression]] = [GraphicsElementBox] +DOES_NOT_ADD_BUILTIN_DEFINITION: Final[list[BoxExpression]] = [GraphicsElementBox] class _Polyline(GraphicsElementBox): @@ -471,9 +472,9 @@ class GraphicsBox(BoxExpression): summary_text = "symbol used in boxing 'Graphics'" def init(self, *items, **kwargs): - self._elements: Optional[Tuple[BaseElement], ...] = None + self._elements: Optional[tuple[BaseElement], ...] = None self.content = items[0] - self.box_options: Dict[str, Any] = kwargs + self.box_options: dict[str, Any] = kwargs self.background_color = None self.tooltip_text: Optional[str] = None self.evaluation = kwargs.pop("_evaluation", None) @@ -763,7 +764,7 @@ def init(self, graphics, style, item=None): self.do_init(graphics, points) self.vertex_colors = None for element in item.elements[1:]: - if not element.has_form(("RewriteRule" "Rule"), 2): + if not is_rule(element): raise BoxExpressionError name = element.elements[0].get_name() self.process_option(name, element.elements[1]) diff --git a/mathics/builtin/functional/apply_fns_to_lists.py b/mathics/builtin/functional/apply_fns_to_lists.py index 93a630685..87eefff36 100644 --- a/mathics/builtin/functional/apply_fns_to_lists.py +++ b/mathics/builtin/functional/apply_fns_to_lists.py @@ -18,6 +18,7 @@ from mathics.core.exceptions import InvalidLevelspecError, MessageException from mathics.core.expression import Expression from mathics.core.list import ListExpression +from mathics.core.rules import is_rule from mathics.core.symbols import Atom, Symbol, SymbolNull, SymbolTrue from mathics.core.systemsymbols import SymbolMapThread from mathics.eval.functional.apply_fns_to_lists import eval_MapAt @@ -191,9 +192,7 @@ def callback(level): # # Fixing this would require a different implementation of this eval_ method. # - if is_association and level.has_form( - ("Rule", "RewriteRule", "RuleDelayed"), 2 - ): + if is_association and is_rule(level): return Expression( level.get_head(), level.elements[0], diff --git a/mathics/builtin/list/associations.py b/mathics/builtin/list/associations.py index b31adf502..ad5750cc2 100644 --- a/mathics/builtin/list/associations.py +++ b/mathics/builtin/list/associations.py @@ -15,6 +15,7 @@ from mathics.core.convert.expression import to_mathics_list from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression +from mathics.core.rules import is_rule from mathics.core.symbols import Symbol, SymbolTrue from mathics.core.systemsymbols import SymbolAssociation, SymbolMakeBoxes, SymbolMissing from mathics.eval.list.associations import ( @@ -68,7 +69,7 @@ def eval_makeboxes(self, rules, f, evaluation: Evaluation): def validate(exprs): for expr in exprs: - if expr.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(expr): pass elif expr.has_form(("List", "Association"), None): if not validate(expr.elements): @@ -96,7 +97,7 @@ def eval(self, rules, evaluation: Evaluation): def make_flatten(exprs, rules_dictionary: dict = {}): for expr in exprs: - if expr.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(expr): elements = expr.elements key = elements[0].evaluate(evaluation) value = elements[1].evaluate(evaluation) @@ -117,7 +118,7 @@ def eval_key(self, rules, key, evaluation: Evaluation): def find_key(exprs, rules_dictionary: dict = {}): for expr in exprs: - if expr.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(expr): if expr.elements[0] == key: rules_dictionary[key] = expr.elements[1] elif expr.has_form(("List", "Association"), None): @@ -158,7 +159,7 @@ class AssociationQ(Test): def test(self, expr) -> bool: def validate(elements): for element in elements: - if element.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(element): pass elif element.has_form(("List", "Association"), None): if not validate(element.elements): @@ -233,7 +234,7 @@ def eval(self, rules, evaluation: Evaluation): "Keys[rules_]" def get_keys(expr): - if expr.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(expr): return expr.elements[0] elif expr.has_form("List", None) or ( expr.has_form("Association", None) @@ -258,7 +259,7 @@ def eval_with_head(self, rules, head, evaluation: Evaluation): "Keys[rules_, head_]" def get_keys_with_head(expr, h): - if expr.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(expr): key = expr.elements[0] return Expression(h, key) elif expr.has_form("List", None) or ( @@ -419,7 +420,7 @@ def eval(self, rules, evaluation: Evaluation): "Values[rules_]" def get_values(expr): - if expr.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(expr): return expr.elements[1] elif expr.has_form("List", None) or ( expr.has_form("Association", None) @@ -445,7 +446,7 @@ def eval_with_head(self, rules, head, evaluation: Evaluation): "Values[rules_, head_]" def get_values_with_head(expr, h): - if expr.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(expr): value = expr.elements[1] return Expression(h, value) elif expr.has_form("List", None) or ( diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index 7e93ccea0..e84fd488a 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -43,7 +43,7 @@ from mathics.core.expression import Expression from mathics.core.expression_predefined import MATHICS3_INFINITY from mathics.core.list import ListExpression -from mathics.core.rules import RewriteRule +from mathics.core.rules import RewriteRule, is_rule from mathics.core.symbols import Atom, Symbol, SymbolNull, SymbolTrue from mathics.core.systemsymbols import ( SymbolAppend, @@ -216,7 +216,7 @@ def eval(self, items, pattern, levelspec, evaluation, options): if isinstance(items, Atom): return ListExpression() - if levelspec.has_form(("RewriteRule", "Rule"), 2): + if is_rule(levelspec): if levelspec.elements[0].get_name() == "System`Heads": heads = levelspec.elements[1] is SymbolTrue levelspec = ListExpression(Integer1) @@ -234,7 +234,7 @@ def eval(self, items, pattern, levelspec, evaluation, options): results = [] - if pattern.has_form(("RewriteRule", "Rule", "RuleDelayed"), 2): + if is_rule(pattern): match = Matcher(pattern.elements[0], evaluation).match rule = RewriteRule(pattern.elements[0], pattern.elements[1]) @@ -1497,7 +1497,7 @@ def eval(self, expr, replacements, evaluation): new_expr = expr.copy() replacements = replacements.get_sequence() for replacement in replacements: - if not replacement.has_form(("RewriteRule", "Rule", "RuleDelayed"), 2): + if not is_rule(replacement): evaluation.message("ReplacePart", "reps", ListExpression(*replacements)) return position = replacement.elements[0] diff --git a/mathics/builtin/patterns/rules.py b/mathics/builtin/patterns/rules.py index 1fb49428d..2327946cc 100644 --- a/mathics/builtin/patterns/rules.py +++ b/mathics/builtin/patterns/rules.py @@ -492,7 +492,7 @@ def eval_list( class Rule_(InfixOperator): """ - :WMA link:https://reference.wolfram.com/language/ref/Rule_.html + :WMA link:https://reference.wolfram.com/language/ref/Rule.html
'Rule'[$x$, $y$] diff --git a/mathics/core/expression.py b/mathics/core/expression.py index f68e8eaa1..a4d409d1a 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -838,7 +838,9 @@ def get_option_values( if allow_symbols and symbol_name: option_values.update(evaluation.definitions.get_options(symbol_name)) else: - if not option.has_form(("Rule", "RuleDelayed"), 2): + from mathics.core.rules import is_rule + + if not is_rule(option): if stop_on_error: return None else: @@ -864,7 +866,7 @@ def get_rules_list(self) -> Optional[list]: If the expression is of the form {pat1->expr1,... {pat_2,expr2},...} return a (python) list of rules. """ - from mathics.core.rules import RewriteRule + from mathics.core.rules import RewriteRule, is_rule from mathics.core.symbols import SymbolList list_expr = self.flatten_with_respect_to_head(SymbolList) @@ -875,7 +877,7 @@ def get_rules_list(self) -> Optional[list]: list.append(list_expr) rules = [] for item in list: - if not item.has_form(("Rule", "RuleDelayed"), 2): + if not is_rule(item): return None rule = RewriteRule(item.elements[0], item.elements[1]) rules.append(rule) @@ -1573,10 +1575,12 @@ def to_sympy(self, **kwargs): return expression_to_sympy(self, **kwargs) def process_style_box(self, options): + from mathics.core.rules import RewriteRule + if self.has_form("StyleBox", 1, None): rules = self._elements[1:] for rule in rules: - if rule.has_form("Rule", 2): + if isinstance(rule, RewriteRule): name = rule._elements[0].get_name() value = rule._elements[1] if name == "System`ShowStringCharacters": diff --git a/mathics/core/rules.py b/mathics/core/rules.py index bdd6f17e1..1b1fa7dd1 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -47,11 +47,10 @@ def eval_f(self, x, evaluation) -> Optional[Expression]: """ - from abc import ABC from inspect import signature from itertools import chain -from typing import Callable, Optional +from typing import Any, Callable, Optional from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation @@ -69,6 +68,24 @@ def function_arguments(f): return _python_function_arguments(f) +def is_rule(element: Any, include_delayed: bool = True) -> bool: + """ + Return True if element is a RewriteRule, typically called a "Rule" in WMA. + In Mathics3, a hard-coded FunctionApplyRule can invoke a Python function. + This kind of rule is not detected here. + + The parameter "included_delayed" indicates whether we allow Delayed Rules, + the default is True. + """ + # FIXME: remove the the test on has_form("Rule") when by fixing up + # class Rule_ in mathics.core.builtins. + return ( + isinstance(element, RewriteRule) or element.has_form(("Rule", "RuleDelayed"), 2) + if include_delayed + else isinstance(element, RewriteRule) + ) + + class StopGenerator_BaseRule(StopGenerator): """ Signals that there are no more rules to check for pattern matching @@ -88,7 +105,7 @@ class RuleApplicationFailed(Exception): class BaseRule(KeyComparable, ABC): """This is the base class from which the FunctionApplyRule and - Rule classes are derived from. + RewriteRule classes are derived from. Rules are part of the rewriting system of Mathics3. See https://en.wikipedia.org/wiki/Rewriting @@ -105,12 +122,11 @@ def __init__( self, pattern: BaseElement, system: bool = False, - evaluation: Optional[Evaluation] = None, attributes: Optional[int] = None, ) -> None: self.location: Optional[Callable] = None self.pattern = BasePattern.create( - pattern, attributes=attributes, evaluation=evaluation + pattern, attributes=attributes, evaluation=None ) self.system = system @@ -210,9 +226,6 @@ def apply_function( ): raise NotImplementedError - def get_replace_value(self) -> BaseElement: - raise ValueError - @property def element_order(self) -> tuple: """ @@ -222,6 +235,16 @@ def element_order(self) -> tuple: # FIXME: check if this makes sense: return tuple((self.system, self.pattern.element_order)) + def get_replace_value(self) -> BaseElement: + raise ValueError + + @property + def lhs(self) -> BaseElement: + """ + Lefthand side of a rule. Also known as its "pattern". + """ + return self.pattern + @property def pattern_precedence(self) -> tuple: """ @@ -232,14 +255,15 @@ def pattern_precedence(self) -> tuple: return tuple((self.system, self.pattern.pattern_precedence)) -# FIXME: Given what is stated in the docstring below, -# the class name would be better called RewriteRule, instead of the -# more generic term Rule. class RewriteRule(BaseRule): - """There are two kinds of Rules. This kind of is a rewrite rule - and transforms an Expression into another Expression based on the - pattern and a replacement term and doesn't involve function - application. + """A RewriteRule in WMA is called simply a "Rule". + + Rules that are user-visible. In Mathics3, we also have rules to + perform function application, FunctionApplyRule. + + A RewriteRule transforms an Expression into another Expression + based on the pattern and a replacement term; "pattern" and " + replacement" are somtimes called the lhs, and rhs respectively. In contrast to FunctionApplyRule[], rule application cannot force a reevaluation of the expression when the rewrite/apply/eval step @@ -247,28 +271,29 @@ class RewriteRule(BaseRule): Here is an example of a Rule:: - F[x_] -> x^2 (* The same thing as: Rule[x_, x^2] *) + F[x_] -> x^2 (* The same thing as: Rule[x_, x^2] *) ``F[x_]`` is a pattern and ``x^2`` is the replacement term. When - applied to the expression ``G[F[1.], F[a]]`` the result is - ``G[1.^2, a^2]`` + applied to the expression ``G[F[1.], F[a]]`` the result is ``G[1.^ + 2, a^2]`` - Note: we want Rules to be serializable so that we can dump and - restore Rules in order to make startup time faster. + Rewrite rules can also be mapping objects when the lhs is a + constant. These are individual key-value pairs that form an + Association Association-like data such as InformationData. + Note: we want Rules to be serializable so that we can dump and + restore Rules of classes of builtin function to be loaded via + lazy loading. """ def __init__( self, - pattern: BaseElement, + pattern: BaseElement, # Note: a constant value is also a "pattern". replace: BaseElement, system=False, - evaluation: Optional[Evaluation] = None, attributes: Optional[int] = None, ) -> None: - super(RewriteRule, self).__init__( - pattern, system=system, evaluation=evaluation, attributes=attributes - ) + super(RewriteRule, self).__init__(pattern, system=system, attributes=None) self.replace = replace def apply_rule( @@ -332,6 +357,10 @@ def pattern_precedence(self) -> tuple: ) ) + @property + def rhs(self) -> BaseElement: + return self.replace + class FunctionApplyRule(BaseRule): """ @@ -383,11 +412,10 @@ def __init__( function: Callable, check_options: Optional[Callable], system: bool = False, - evaluation: Optional[Evaluation] = None, attributes: Optional[int] = None, ) -> None: super(FunctionApplyRule, self).__init__( - pattern, system=system, attributes=attributes, evaluation=evaluation + pattern, system=system, attributes=attributes ) self.name = name self.location = self.function = function diff --git a/mathics/doc/online.py b/mathics/doc/online.py index d59d1dae6..10097bdae 100644 --- a/mathics/doc/online.py +++ b/mathics/doc/online.py @@ -24,7 +24,7 @@ def online_doc_string( # First look at user definitions: for rulemsg in ruleusage: if rulemsg.pattern.expr.get_elements()[1].__str__() == '"usage"': - usagetext = rulemsg.get_replace_value().to_python(string_quotes=False) + usagetext = rulemsg.rhs.to_python(string_quotes=False) if not is_long_form and usagetext: return usagetext diff --git a/mathics/eval/functional/apply_fns_to_lists.py b/mathics/eval/functional/apply_fns_to_lists.py index ce8015b90..b42ccdb23 100644 --- a/mathics/eval/functional/apply_fns_to_lists.py +++ b/mathics/eval/functional/apply_fns_to_lists.py @@ -10,6 +10,7 @@ from mathics.core.exceptions import PartRangeError from mathics.core.expression import Expression from mathics.core.list import ListExpression +from mathics.core.rules import is_rule from mathics.core.symbols import SymbolTrue from mathics.core.systemsymbols import SymbolMapAt from mathics.eval.testing_expressions import eval_ArrayQ @@ -38,7 +39,7 @@ def map_at_replace_one(elements: Iterable, index: ListExpression, i: int) -> lis raise PartRangeError new_elements = list(elements) replace_element = elements[j] - if replace_element.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(replace_element): new_elements[j] = Expression( replace_element.get_head(), replace_element.elements[0], diff --git a/mathics/eval/list/associations.py b/mathics/eval/list/associations.py index 81969c694..b369bf92c 100644 --- a/mathics/eval/list/associations.py +++ b/mathics/eval/list/associations.py @@ -1,6 +1,7 @@ from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression +from mathics.core.rules import is_rule from mathics.core.systemsymbols import SymbolKeyAbsent, SymbolMissing @@ -10,7 +11,7 @@ def eval_Lookup(assoc, key, default, evaluation: Evaluation): if assoc.has_form("Association", None): # Search through association elements (rules) for element in assoc.elements: - if element.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(element): if element.elements[0] == key: return element.elements[1] @@ -23,7 +24,7 @@ def eval_Lookup(assoc, key, default, evaluation: Evaluation): elif isinstance(assoc, ListExpression): # Search through list of rules for element in assoc.elements: - if element.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(element): if element.elements[0] == key: return element.elements[1] @@ -33,7 +34,7 @@ def eval_Lookup(assoc, key, default, evaluation: Evaluation): else: return Expression(SymbolMissing, SymbolKeyAbsent, key) - elif assoc.has_form(("Rule", "RuleDelayed"), 2): + elif is_rule(assoc): if assoc.elements[0] == key: return assoc.elements[1] return None diff --git a/mathics/eval/rules.py b/mathics/eval/rules.py index 07dfa5b31..deac6070d 100644 --- a/mathics/eval/rules.py +++ b/mathics/eval/rules.py @@ -5,7 +5,7 @@ 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.rules import RewriteRule, is_rule from mathics.core.symbols import Atom, Symbol, SymbolList from mathics.core.systemsymbols import SymbolDispatch, SymbolRule, SymbolRuleDelayed from mathics.eval.parts import python_levelspec @@ -118,7 +118,7 @@ def eval_dispatch_atom( rule = rule.evaluate(evaluation) if rule.has_form("List", None): flatten_list.extend(rule.elements) - elif rule.has_form(("Rule", "RuleDelayed"), 2): + elif is_rule(rule): flatten_list.append(rule) elif isinstance(rule, Dispatch): flatten_list.extend(rule.src.elements) diff --git a/mathics/format/box/numberform.py b/mathics/format/box/numberform.py index 81a040bf3..26448870b 100644 --- a/mathics/format/box/numberform.py +++ b/mathics/format/box/numberform.py @@ -27,6 +27,7 @@ convert_base, dps, ) +from mathics.core.rules import is_rule from mathics.core.symbols import Symbol, SymbolNull from mathics.core.systemsymbols import ( SymbolFullForm, @@ -373,7 +374,7 @@ def get_numberform_parameters( options.update(default_options) for elem in elements[::-1]: pos = pos - 1 - if elem.has_form(("Rule", "RuleDelayed"), 2): + if is_rule(elem): key, val = elem.elements if isinstance(key, Symbol): key_name = key.get_name() diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index b6697d080..a129d543f 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -34,6 +34,7 @@ from mathics.core.expression import BoxError, Expression from mathics.core.list import ListExpression from mathics.core.number import dps +from mathics.core.rules import is_rule from mathics.core.symbols import ( Atom, Symbol, @@ -162,7 +163,7 @@ def _association_outputform(expr: Expression, evaluation: Evaluation, **kwargs): elements = expr.elements parts = [] for element in elements: - if not element.has_form(("Rule", "RuleDelayed"), 2): + if not is_rule(element): raise _WrongFormattedExpression parts.append(rule_to_outputform_text(element, evaluation, **kwargs)) return "<|" + ", ".join(parts) + "|>" From 0e7f0dd2da8a7019edfe337aa4116d54940a6aa4 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jul 2026 14:03:52 -0400 Subject: [PATCH 3/7] Remove "system" paramter on Rule creation. Do we really need this? If so, why? --- mathics/core/builtin.py | 10 ++---- mathics/core/rules.py | 33 +++++++++++-------- mathics/eval/assignments/assignment.py | 2 +- mathics/eval/inference.py | 6 ++-- test/builtin/box/test_custom_boxexpression.py | 10 +++--- 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/mathics/core/builtin.py b/mathics/core/builtin.py index 0479d3174..c570ae7fa 100644 --- a/mathics/core/builtin.py +++ b/mathics/core/builtin.py @@ -300,7 +300,6 @@ def contribute(self, definitions: Definitions, is_pymodule=False): function, check_options, attributes=pat_attr, - system=True, ) ) for pattern_str, replace_str in self.rules.items(): @@ -313,7 +312,6 @@ def contribute(self, definitions: Definitions, is_pymodule=False): pattern, parse_builtin_rule(replace_str), attributes=pat_attr, - system=not is_pymodule, ) ) @@ -361,7 +359,7 @@ def contextify_form_name(f): formatvalues[form] = [] formatvalues[form].append( FunctionApplyRule( - name, pattern, function, None, attributes=pat_attr, system=True + name, pattern, function, None, attributes=pat_attr ) ) for pattern, replace in self.formats.items(): @@ -374,7 +372,7 @@ def contextify_form_name(f): pattern = parse_builtin_rule(pattern) replace = replace % {"name": name} formatvalues[form].append( - RewriteRule(pattern, parse_builtin_rule(replace), system=True) + RewriteRule(pattern, parse_builtin_rule(replace)) ) formatvalues.setdefault("_MakeBoxes", []).extend(box_rules) @@ -388,7 +386,6 @@ def contextify_form_name(f): RewriteRule( Expression(SymbolMessageName, Symbol(name), String(msg)), String(value), - system=True, ) for msg, value in self.messages.items() ] @@ -397,7 +394,6 @@ def contextify_form_name(f): RewriteRule( Expression(SymbolMessageName, Symbol(name), String("optx")), String("`1` is not a supported option for `2`[]."), - system=True, ) ) @@ -410,7 +406,7 @@ def contextify_form_name(f): elif isinstance(spec, int): pattern = Expression(SymbolDefault, Symbol(name), Integer(spec)) if pattern is not None: - defaults.append(RewriteRule(pattern, value, system=True)) + defaults.append(RewriteRule(pattern, value)) definition = Definition( name=name, diff --git a/mathics/core/rules.py b/mathics/core/rules.py index 1b1fa7dd1..0ad595177 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -121,14 +121,12 @@ class BaseRule(KeyComparable, ABC): def __init__( self, pattern: BaseElement, - system: bool = False, attributes: Optional[int] = None, ) -> None: self.location: Optional[Callable] = None self.pattern = BasePattern.create( pattern, attributes=attributes, evaluation=None ) - self.system = system def apply( self, @@ -233,7 +231,8 @@ def element_order(self) -> tuple: of an expression. The tuple is ultimately compared lexicographically. """ # FIXME: check if this makes sense: - return tuple((self.system, self.pattern.element_order)) + # True used to be self.system. Can we remove True? + return tuple((True, self.pattern.element_order)) def get_replace_value(self) -> BaseElement: raise ValueError @@ -252,7 +251,8 @@ def pattern_precedence(self) -> tuple: which pattern to select when several match. """ # FIXME: check if this makes sense: - return tuple((self.system, self.pattern.pattern_precedence)) + # True used to be self.system. Can we remove True? + return tuple((True, self.pattern.pattern_precedence)) class RewriteRule(BaseRule): @@ -290,12 +290,14 @@ def __init__( self, pattern: BaseElement, # Note: a constant value is also a "pattern". replace: BaseElement, - system=False, attributes: Optional[int] = None, ) -> None: - super(RewriteRule, self).__init__(pattern, system=system, attributes=None) + super(RewriteRule, self).__init__(pattern, attributes=None) self.replace = replace + def __repr__(self) -> str: + return " %s>" % (self.pattern, self.replace) + def apply_rule( self, expression: BaseElement, vars: dict, options: dict, evaluation: Evaluation ): @@ -336,8 +338,14 @@ def get_replace_value(self) -> BaseElement: """return the replace value""" return self.replace - def __repr__(self) -> str: - return " %s>" % (self.pattern, self.replace) + # This will probably be needed when we use with builtin Rule_ + # @property + # def is_literal(self): + # """ + # Mathics3 FunctionApply Rules are literal do not need to be revaluated. + # """ + # print("ReplaceRule WOOT") + # return self.pattern.is_literal and self.replace.is_literal @property def pattern_precedence(self) -> tuple: @@ -350,9 +358,11 @@ def pattern_precedence(self) -> tuple: sort_key_list = list(sort_key) sort_key_list[0] = sort_key_list[0] & PATTERN_SORT_KEY_CONDITIONAL sort_key = tuple(sort_key_list) + + # True used to be self.system. Can we remove True? return tuple( ( - self.system, + True, sort_key, ) ) @@ -411,12 +421,9 @@ def __init__( pattern: Expression, function: Callable, check_options: Optional[Callable], - system: bool = False, attributes: Optional[int] = None, ) -> None: - super(FunctionApplyRule, self).__init__( - pattern, system=system, attributes=attributes - ) + super(FunctionApplyRule, self).__init__(pattern, attributes=attributes) self.name = name self.location = self.function = function self.check_options = check_options diff --git a/mathics/eval/assignments/assignment.py b/mathics/eval/assignments/assignment.py index ba061eda5..ef9804a8a 100644 --- a/mathics/eval/assignments/assignment.py +++ b/mathics/eval/assignments/assignment.py @@ -692,7 +692,7 @@ def eval_assign_makeboxes( target, form = lhs.elements # Check second argument - makeboxes_rule = RewriteRule(lhs, rhs, system=False) + makeboxes_rule = RewriteRule(lhs, rhs) tags = [] if tags is None else tags if upset: tags = tags + [target.get_lookup_name()] diff --git a/mathics/eval/inference.py b/mathics/eval/inference.py index 664696ac9..1c249a2a2 100644 --- a/mathics/eval/inference.py +++ b/mathics/eval/inference.py @@ -98,14 +98,12 @@ def ensure_logical_algebraic_rules(): for pattern, replace in logical_algebraic_rules_spec.items(): pattern = parse_builtin_rule(pattern, SystemDefinitions()) logical_algebraic_rules.append( - RewriteRule(pattern, parse_builtin_rule(replace), system=True) + RewriteRule(pattern, parse_builtin_rule(replace)) ) remove_not_rules = [] for pattern, replace in remove_not_rules_spec.items(): pattern = parse_builtin_rule(pattern, SystemDefinitions()) - remove_not_rules.append( - RewriteRule(pattern, parse_builtin_rule(replace), system=True) - ) + remove_not_rules.append(RewriteRule(pattern, parse_builtin_rule(replace))) return diff --git a/test/builtin/box/test_custom_boxexpression.py b/test/builtin/box/test_custom_boxexpression.py index edeb4bdef..5a3d2fabf 100644 --- a/test/builtin/box/test_custom_boxexpression.py +++ b/test/builtin/box/test_custom_boxexpression.py @@ -55,7 +55,11 @@ def contribute(self, definitions, is_pymodule=True): for pattern, function in self.get_functions("makeboxes_"): mb_rule = FunctionApplyRule( - name, pattern, function, None, attributes=None, system=True + name, + pattern, + function, + None, + attributes=None, ) definitions.add_format("System`MakeBoxes", mb_rule, "_MakeBoxes") @@ -85,9 +89,7 @@ def contribute(self, definitions, is_pymodule=True): name = self.get_name() for pattern, function in self.get_functions("makeboxes_"): - mb_rule = FunctionApplyRule( - name, pattern, function, None, attributes=None, system=True - ) + mb_rule = FunctionApplyRule(name, pattern, function, None, attributes=None) definitions.add_format("System`MakeBoxes", mb_rule, "_MakeBoxes") def init(self, *elems, **options): From 50cb98d57c61c6a9b924bf9ed4ec50b825bdf51b Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jul 2026 15:05:20 -0400 Subject: [PATCH 4/7] mypy --- mathics/core/rules.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/mathics/core/rules.py b/mathics/core/rules.py index 0ad595177..57ea36127 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -238,7 +238,7 @@ def get_replace_value(self) -> BaseElement: raise ValueError @property - def lhs(self) -> BaseElement: + def lhs(self) -> BasePattern: """ Lefthand side of a rule. Also known as its "pattern". """ @@ -254,6 +254,13 @@ def pattern_precedence(self) -> tuple: # True used to be self.system. Can we remove True? return tuple((True, self.pattern.pattern_precedence)) + @property + def rhs(self): + """ + Lefthand side of a rule. Also known as its "pattern". + """ + raise NotImplementedError + class RewriteRule(BaseRule): """A RewriteRule in WMA is called simply a "Rule". @@ -368,7 +375,7 @@ def pattern_precedence(self) -> tuple: ) @property - def rhs(self) -> BaseElement: + def rhs(self): return self.replace @@ -466,3 +473,7 @@ def __getstate__(self): def __setstate__(self, dict): self.__dict__.update(dict) # update attributes + + @property + def rhs(self): + return self.function From 65c4fda173ab2bc0de613c235e6b36d8717ae93e Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jul 2026 19:58:42 -0400 Subject: [PATCH 5/7] Go over docstrings for spelling. Add rhs property --- mathics/core/rules.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/mathics/core/rules.py b/mathics/core/rules.py index 57ea36127..0d9f7feff 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -3,7 +3,7 @@ program. Expressions which are transformed by rewrite rules (AKA transformation -rules) are handed by the `Rule` class. +rules) are handled by the `Rule` class. There are also rules for how to match, assign function parameter arguments, and then apply a Python "evaluation" function to a Mathics3 Expression. @@ -15,7 +15,7 @@ For example, suppose that we try to apply rule `F[x_]->x^2` to the expression `F[2]`. The pattern part of the rule,`F[x_]` matches the expression, `Blank[x]` (or `x_`) is replaced by `2`, giving the substitution expression `2^2`. Evaluation then stops -looking for other rules to be applied over `F[2]`. +looking for other rules to be applied to `F[2]`. On the other hand, suppose that we define a `FunctionApplyRule` that associates `F[x_]` with the function: @@ -29,7 +29,7 @@ def eval_f(self, x, evaluation) -> Optional[Expression]: return Expression(SymbolPower, x, Integer2) return None -Then, if we apply the rule to `F[2]`, the function is evaluated returning `None`. Then, in the evaluation loop, we get the same +Then, if we apply the rule to `F[2]`, the function is evaluated, returning `None`. Then, in the evaluation loop, we get the same effect as if the pattern didn't match with the expression. The loop continues then with the next rule associated with `F`. Why do things this way? @@ -39,10 +39,10 @@ def eval_f(self, x, evaluation) -> Optional[Expression]: F[x_/;(G[x]>0)]:=G[x] with G[x] a computationally expensive function. To decide if G[x] is larger than 0, we need to evaluate it, -and once we have evaluated it, just need to return its value. +and once we have evaluated it, we just need to return its value. Also, this allows us to handle several rules in the same function, without relying on our very slow pattern-matching routines. -In particular, this is used for for some critical low-level tasks like building lists in iterators, processing arithmetic expressions, +In particular, this is used for some critical low-level tasks like building lists in iterators, processing arithmetic expressions, plotting functions, or evaluating derivatives and integrals. """ @@ -97,7 +97,7 @@ class StopGenerator_BaseRule(StopGenerator): class RuleApplicationFailed(Exception): """ Exception raised when a condition fails - in the RHS, indicating that the match have failed. + in the RHS, indicating that the match has failed. """ pass @@ -112,7 +112,7 @@ class BaseRule(KeyComparable, ABC): This class is not complete in of itself; subclasses must adapt or fill in what is needed. In particular either ``apply_rule()`` or - ``apply_function()`` need to be implemented. + ``apply_function()`` needs to be implemented. Note: we want Rules to be serializable so that we can dump and restore Rules in order to make startup time faster. @@ -257,7 +257,7 @@ def pattern_precedence(self) -> tuple: @property def rhs(self): """ - Lefthand side of a rule. Also known as its "pattern". + Right-hand side of a rule. Also known as its "replacement". """ raise NotImplementedError @@ -270,7 +270,8 @@ class RewriteRule(BaseRule): A RewriteRule transforms an Expression into another Expression based on the pattern and a replacement term; "pattern" and " - replacement" are somtimes called the lhs, and rhs respectively. + replacement" are sometimes called the "lhs" and "rhs", + respectively. In contrast to FunctionApplyRule[], rule application cannot force a reevaluation of the expression when the rewrite/apply/eval step @@ -281,12 +282,12 @@ class RewriteRule(BaseRule): F[x_] -> x^2 (* The same thing as: Rule[x_, x^2] *) ``F[x_]`` is a pattern and ``x^2`` is the replacement term. When - applied to the expression ``G[F[1.], F[a]]`` the result is ``G[1.^ - 2, a^2]`` + applied to the expression ``G[F[1.], F[a]]`` the result is + ``G[1.^2, a^2]`` Rewrite rules can also be mapping objects when the lhs is a - constant. These are individual key-value pairs that form an - Association Association-like data such as InformationData. + constant. These are individual key-value pairs that form in + Association-like data such as InformationData. Note: we want Rules to be serializable so that we can dump and restore Rules of classes of builtin function to be loaded via From 956c04abfe0dd7f12d6b79bfed7b8e79aecdefa4 Mon Sep 17 00:00:00 2001 From: "R. Bernstein" Date: Sat, 18 Jul 2026 21:45:01 -0400 Subject: [PATCH 6/7] Fix typos in comments and documentation --- mathics/builtin/atomic/symbols.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index bba36dcb6..c576905a2 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/atomic/symbols.py @@ -3,7 +3,7 @@ Symbol Handling Symbolic data. Every symbol has a unique name, exists in a certain context \ -or namespace, and can have a variety of type of values and attributes. +or namespace, and can have a variety of types of values and attributes. """ import re from typing import Callable, Optional @@ -363,7 +363,7 @@ class DownValues(Builtin): = {HoldPattern[f[x_]] ⧴ x ^ 2} Mathics3 will sort the rules you assign to a symbol according to \ - their specificity. If it cannot decide which rule is more special, \ + their specificity. If it cannot decide which rule is more specific, \ the newer one will get higher precedence. >> f[x_Integer] := 2 >> f[x_Real] := 3 @@ -420,7 +420,7 @@ class FormatValues(Builtin): >> FormatValues[F] = {HoldPattern[Subscript[x_, F]] ⧴ Subscript[x, F]} - The replacment pattern on the right in the delayed rule is formatted according to the top-level form. To see the rule input, we can use 'InputForm': + The replacement pattern on the right in the delayed rule is formatted according to the top-level form. To see the rule input, we can use 'InputForm': >> FormatValues[F] //InputForm = {HoldPattern[Format[F[x_], OutputForm]] ⧴ Subscript[x, F]} """ From 82bfd6c9b3b775be49f9384f5b140fd1809dfccc Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 19 Jul 2026 12:11:05 -0400 Subject: [PATCH 7/7] Fix a snort snort typo --- mathics/core/rules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/core/rules.py b/mathics/core/rules.py index 0d9f7feff..1cc0e0bd4 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -77,7 +77,7 @@ def is_rule(element: Any, include_delayed: bool = True) -> bool: The parameter "included_delayed" indicates whether we allow Delayed Rules, the default is True. """ - # FIXME: remove the the test on has_form("Rule") when by fixing up + # FIXME: remove the test on has_form("Rule") when by fixing up # class Rule_ in mathics.core.builtins. return ( isinstance(element, RewriteRule) or element.has_form(("Rule", "RuleDelayed"), 2)