diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index 628bc0af0..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 @@ -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( @@ -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]} """ diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index 1a5b4b431..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("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 7d663f8be..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,7 +192,7 @@ 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 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 7bb938538..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 Rule +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("Rule", 2): + if is_rule(levelspec): 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 is_rule(pattern): 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 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/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..c570ae7fa 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, @@ -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(): @@ -309,11 +308,10 @@ 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, - 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( - Rule(pattern, parse_builtin_rule(replace), system=True) + RewriteRule(pattern, parse_builtin_rule(replace)) ) formatvalues.setdefault("_MakeBoxes", []).extend(box_rules) @@ -385,19 +383,17 @@ 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, ) for msg, value in self.messages.items() ] messages.append( - Rule( + 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(Rule(pattern, value, system=True)) + defaults.append(RewriteRule(pattern, value)) definition = Definition( name=name, @@ -1389,7 +1385,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..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 Rule + from mathics.core.rules import RewriteRule, is_rule from mathics.core.symbols import SymbolList list_expr = self.flatten_with_respect_to_head(SymbolList) @@ -875,9 +877,9 @@ 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 = Rule(item.elements[0], item.elements[1]) + rule = RewriteRule(item.elements[0], item.elements[1]) rules.append(rule) return rules @@ -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 7439279b3..1cc0e0bd4 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,19 +39,18 @@ 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. """ - 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 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 @@ -80,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 @@ -88,14 +105,14 @@ 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 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. @@ -104,15 +121,12 @@ class BaseRule(KeyComparable, ABC): 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 def apply( self, @@ -210,9 +224,6 @@ def apply_function( ): raise NotImplementedError - def get_replace_value(self) -> BaseElement: - raise ValueError - @property def element_order(self) -> tuple: """ @@ -220,7 +231,18 @@ 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 + + @property + def lhs(self) -> BasePattern: + """ + Lefthand side of a rule. Also known as its "pattern". + """ + return self.pattern @property def pattern_precedence(self) -> tuple: @@ -229,17 +251,27 @@ 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)) + + @property + def rhs(self): + """ + Right-hand side of a rule. Also known as its "replacement". + """ + raise NotImplementedError + + +class RewriteRule(BaseRule): + """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. -# 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): - """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 transforms an Expression into another Expression + based on the pattern and a replacement term; "pattern" and " + 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 @@ -247,30 +279,33 @@ class Rule(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]`` - 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 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 + 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(Rule, self).__init__( - pattern, system=system, evaluation=evaluation, attributes=attributes - ) + 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 ): @@ -311,8 +346,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: @@ -325,13 +366,19 @@ 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, ) ) + @property + def rhs(self): + return self.replace + class FunctionApplyRule(BaseRule): """ @@ -382,13 +429,9 @@ def __init__( pattern: Expression, 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 - ) + super(FunctionApplyRule, self).__init__(pattern, attributes=attributes) self.name = name self.location = self.function = function self.check_options = check_options @@ -431,3 +474,7 @@ def __getstate__(self): def __setstate__(self, dict): self.__dict__.update(dict) # update attributes + + @property + def rhs(self): + return self.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/assignments/assignment.py b/mathics/eval/assignments/assignment.py index 07cda6447..ef9804a8a 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) 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/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/inference.py b/mathics/eval/inference.py index b8cd61e42..1c249a2a2 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,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( - Rule(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( - Rule(pattern, parse_builtin_rule(replace), system=True) - ) + remove_not_rules.append(RewriteRule(pattern, parse_builtin_rule(replace))) return @@ -308,43 +306,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/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 7c5d24dc5..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 Rule +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 @@ -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 @@ -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) @@ -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 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) + "|>" 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):