Skip to content

Commit

Permalink
bark when a rule in Builtin.rules cannot be loaded (#1006)
Browse files Browse the repository at this point in the history
This is related to #1000. There are some rules included in the `Builtin.rules` property that cannot be loaded. With this PR, if this happens, a warning is printed. In any case, the idea is to fix these cases before merging this PR.

---------

Co-authored-by: R. Bernstein <[email protected]>
  • Loading branch information
mmatera and rocky authored Oct 3, 2024
1 parent 9cc2331 commit e44ab00
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 23 deletions.
4 changes: 2 additions & 2 deletions mathics/builtin/box/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,11 @@ class InterpretationBox(BoxExpression):
summary_text = "box associated to an input expression"

def eval_to_expression(boxexpr, form, evaluation):
"""ToExpression[boxexpr_IntepretationBox, form___]"""
"""ToExpression[boxexpr_InterpretationBox, form___]"""
return boxexpr.elements[1]

def eval_display(boxexpr, evaluation):
"""DisplayForm[boxexpr_IntepretationBox]"""
"""DisplayForm[boxexpr_InterpretationBox]"""
return boxexpr.elements[0]


Expand Down
6 changes: 0 additions & 6 deletions mathics/builtin/datentime.py
Original file line number Diff line number Diff line change
Expand Up @@ -1201,12 +1201,6 @@ class TimeZone(Predefined):

summary_text = "gets the default time zone"

def eval(self, lhs, rhs, evaluation):
"lhs_ = rhs_"

self.assign(lhs, rhs, evaluation)
return rhs

def evaluate(self, evaluation) -> Real:
return self.value

Expand Down
2 changes: 2 additions & 0 deletions mathics/builtin/files_io/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,8 @@ class Find(Read):
= ...
"""

rules = {}

options = {
"AnchoredSearch": "False",
"IgnoreCase": "False",
Expand Down
144 changes: 129 additions & 15 deletions mathics/core/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from mathics.core.element import fully_qualified_symbol_name
from mathics.core.expression import Expression
from mathics.core.load_builtin import definition_contribute, mathics3_builtins_modules
from mathics.core.pattern import BasePattern, ExpressionPattern
from mathics.core.symbols import Atom, Symbol, strip_context
from mathics.core.systemsymbols import SymbolGet
from mathics.core.util import canonic_filename
Expand Down Expand Up @@ -725,25 +726,137 @@ def get_history_length(self):


def get_tag_position(pattern, name) -> Optional[str]:
"""
Determine the position of a pattern in
the definition of the symbol ``name``
"""
blanks = (
"System`Blank",
"System`BlankSequence",
"System`BlankNullSequence",
)

def strip_pattern_name_and_condition(pat: BasePattern) -> ExpressionPattern:
"""
In ``Pattern[name_, pattern_]`` and
``Condition[pattern_, cond_]``
the tag is determined by pat.
This function strips it to ensure that
``pat`` does not have that form.
"""

# Is "pat" as ExpressionPattern or an AtomPattern?
# Note: the below test could also be on ExpressionPattern or
# AtomPattern, but using hasattr is more flexible if more
# kinds of patterns are added.
if not hasattr(pat, "head"):
return pat

# We have to use get_head_name() below because
# pat can either SymbolCondition or <AtomPattern: System`Condition>.
# In the latter case, comparing to SymbolCondition is not sufficient.
if pat.get_head_name() == "System`Condition":
if len(pat.elements) > 1:
return strip_pattern_name_and_condition(pat.elements[0])
# The same kind of get_head_name() check is needed here as well and
# is not the same as testing against SymbolPattern.
if pat.get_head_name() == "System`Pattern":
if len(pat.elements) == 2:
return strip_pattern_name_and_condition(pat.elements[1])
return pat

def is_pattern_a_kind_of(pattern: ExpressionPattern, pattern_name: str) -> bool:
"""
Returns `True` if `pattern` or any of its alternates is a
pattern with name `pattern_name` and `False` otherwise."""

if pattern_name == pattern.get_lookup_name():
return True

# Try again after stripping Pattern and Condition wrappers:
head = strip_pattern_name_and_condition(pattern.get_head())
head_name = head.get_lookup_name()
if pattern_name == head_name:
return True

# The head is of the form ``_SymbolName|__SymbolName|___SymbolName``
# If name matches with SymbolName, then it is a kind of:
if head_name in blanks:
if isinstance(head, Symbol):
return False
sub_elements = head.elements
if len(sub_elements) == 1:
head_name = head.elements[0].get_name()
if head_name == pattern_name:
return True
return False

# If pattern is a Symbol, and coincides with
# name, it is an ownvalue:

if pattern.get_name() == name:
return "own"
elif isinstance(pattern, Atom):
# If pattern is an ``Atom``, does not have
# a position
if isinstance(pattern, Atom):
return None
else:
head_name = pattern.get_head_name()
if head_name == name:
return "down"
elif head_name == "System`N" and len(pattern.elements) == 2:

# The pattern is an Expression.
head_name = pattern.get_head_name()
# If the name is the head name, is a downvalue:
if head_name == name:
return "down"

# Handle special cases
if head_name == "System`N":
if len(pattern.elements) == 2:
return "n"
elif head_name == "System`Condition" and len(pattern.elements) > 0:
return get_tag_position(pattern.elements[0], name)
elif pattern.get_lookup_name() == name:
return "sub"
else:
for element in pattern.elements:
if element.get_lookup_name() == name:

# The pattern has the form `_SymbolName | __SymbolName | ___SymbolName`
# Then it only can be a downvalue
if head_name in blanks:
elements = pattern.elements
if len(elements) == 1:
head_name = elements[0].get_name()
return "down" if head_name == name else None

# TODO: Consider process format_values

if head_name != "":
# Check
strip_pattern = strip_pattern_name_and_condition(pattern)
if strip_pattern is not pattern:
return get_tag_position(strip_pattern, name)

# The head is not a symbol. Is pattern is "name" kind of pattern?
if is_pattern_a_kind_of(pattern, name):
return "sub"

# If we are here, pattern is not an Ownvalue, DownValue, SubValue or NValue
# Let's check the elements for UpValues
for element in pattern.elements:
lookup_name = element.get_lookup_name()
if lookup_name == name:
return "up"

# Strip Pattern and Condition wrappers and check again
if lookup_name in (
"System`Condition",
"System`Pattern",
):
element = strip_pattern_name_and_condition(element)
lookup_name = element.get_lookup_name()
if lookup_name == name:
return "up"
# Check if one of the elements is not a "Blank"

if element.get_head_name() in blanks:
sub_elements = element.elements
if len(sub_elements) == 1:
if sub_elements[0].get_name() == name:
return "up"
return None
# ``pattern`` does not have a tag position in the Definition
return None


def insert_rule(values, rule) -> None:
Expand Down Expand Up @@ -818,7 +931,8 @@ def __init__(
self.defaultvalues = defaultvalues
self.builtin = builtin
for rule in rules:
self.add_rule(rule)
if not self.add_rule(rule):
print(f"{rule.pattern.expr} could not be associated to {self.name}")

def get_values_list(self, pos):
assert pos.isalpha()
Expand Down
59 changes: 59 additions & 0 deletions test/core/test_definitions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""
Tests functions in mathics.core.definition
"""

import pytest

from mathics.core.definitions import get_tag_position
from mathics.core.parser import parse_builtin_rule


@pytest.mark.parametrize(
("pattern_str", "tag", "position"),
[
# None
("A", "B", None),
("A_", "B", None),
("A[c_]", "B", None),
("A[3]", "B", None),
("A[B][3]", "B", None),
("A[s[x_]][y]", "s", None),
# Ownvalues
("A", "A", "own"),
("A/;A>0", "A", "own"),
("s:(A/;A>0)", "A", "own"),
("(s:A)/;A>0", "A", "own"),
("s:A/;A>0", "A", "own"),
# Downvalues
("_A", "A", "down"),
("A[]", "A", "down"),
("_A", "A", "down"),
("A[p_, q]", "A", "down"),
("s:A[p_, q]", "A", "down"),
("A[p_, q]/;q>0", "A", "down"),
("(s:A[p_, q])/;q>0", "A", "down"),
# NValues
("N[A[x_], _]", "A", "n"),
("N[A[x_], _]/; x>0", "A", "n"),
# Subvalues
("_A[]", "A", "sub"),
("A[x][t]", "A", "sub"),
("(s:A[x])[t]", "A", "sub"),
("(x_A/;u>0)[p]", "A", "sub"),
# Upvalues
("S[x_, A]", "A", "up"),
("S[x_, _A]", "A", "up"),
("S[x_, s_A/;s>0]", "A", "up"),
("S[x_, q:A]", "A", "up"),
("S[x_, q:(A[t_]/;t>0)]", "A", "up"),
("A[x_][s[y]]", "s", "up"),
("DisplayForm[boxexpr_InterpretationBox]", "InterpretationBox", "up"),
("ToExpression[boxexpr_InterpretationBox, form___]", "InterpretationBox", "up"),
# Just one argument, must be an upvalue
("N[A[s_]]", "A", "up"),
],
)
def test_get_tag_position(pattern_str, tag, position):
pattern = parse_builtin_rule(pattern_str)
assert get_tag_position(pattern, f"System`{tag}") == position

0 comments on commit e44ab00

Please sign in to comment.