Skip to content

Commit

Permalink
Split mathics builtin patterns (#1123)
Browse files Browse the repository at this point in the history
Just split this long module into smaller parts.

---------

Co-authored-by: R. Bernstein <[email protected]>
  • Loading branch information
mmatera and rocky authored Oct 7, 2024
1 parent c9dfc7d commit a71bbd6
Show file tree
Hide file tree
Showing 17 changed files with 2,175 additions and 1,931 deletions.
1,921 changes: 0 additions & 1,921 deletions mathics/builtin/patterns.py

This file was deleted.

41 changes: 41 additions & 0 deletions mathics/builtin/patterns/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Rules and Patterns
<url>:WMA link:
https://reference.wolfram.com/language/guide/RulesAndPatterns.html</url>
The concept of transformation rules for arbitrary symbolic patterns is key \
in \\Mathics.
Also, functions can get applied or transformed depending on whether or not \
functions arguments match.
Some examples:
>> a + b + c /. a + b -> t
= c + t
>> a + 2 + b + c + x * y /. n_Integer + s__Symbol + rest_ -> {n, s, rest}
= {2, a, b + c + x y}
>> f[a, b, c, d] /. f[first_, rest___] -> {first, {rest}}
= {a, {b, c, d}}
Tests and Conditions:
>> f[4] /. f[x_?(# > 0&)] -> x ^ 2
= 16
>> f[4] /. f[x_] /; x > 0 -> x ^ 2
= 16
Elements in the beginning of a pattern rather match fewer elements:
>> f[a, b, c, d] /. f[start__, end__] -> {{start}, {end}}
= {{a}, {b, c, d}}
Optional arguments using 'Optional':
>> f[a] /. f[x_, y_:3] -> {x, y}
= {a, 3}
Options using 'OptionsPattern' and 'OptionValue':
>> f[y, a->3] /. f[x_, OptionsPattern[{a->2, b->5}]] -> {x, OptionValue[a], OptionValue[b]}
= {y, 3, 5}
The attributes 'Flat', 'Orderless', and 'OneIdentity' affect pattern matching.
"""
204 changes: 204 additions & 0 deletions mathics/builtin/patterns/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# -*- coding: utf-8 -*-
"""
Basic Pattern Objects
"""

from abc import ABC
from typing import Optional as OptionalType

from mathics.core.builtin import PatternObject
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression

# This tells documentation how to sort this module
sort_order = "mathics.builtin.rules-and-patterns.basic"


class _Blank(PatternObject, ABC):
arg_counts = [0, 1]

_instance = None

def __new__(cls, *args, **kwargs):
if kwargs.get("expression", None) is False:
return super().__new__(cls, *args, **kwargs)

num_elem = len(args[0].elements)
assert num_elem < 2, f"{cls} should have at most an element."

if num_elem != 0:
return super().__new__(cls, *args, **kwargs)
# no arguments. Use the singleton
if cls._instance is None:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance

def init(
self, expr: Expression, evaluation: OptionalType[Evaluation] = None
) -> None:
super().init(expr, evaluation=evaluation)
if expr.elements:
self.head = expr.elements[0]
else:
# FIXME: elswhere, some code wants to
# get the attributes of head.
# So is this really the best thing to do here?
self.head = None


class Blank(_Blank):
"""
<url>:WMA link:https://reference.wolfram.com/language/ref/Blank.html</url>
<dl>
<dt>'Blank[]'
<dt>'_'
<dd>represents any single expression in a pattern.
<dt>'Blank[$h$]'
<dt>'_$h$'
<dd>represents any expression with head $h$.
</dl>
>> MatchQ[a + b, _]
= True
Patterns of the form '_'$h$ can be used to test the types of \
objects:
>> MatchQ[42, _Integer]
= True
>> MatchQ[1.0, _Integer]
= False
>> {42, 1.0, x} /. {_Integer -> "integer", _Real -> "real"} // InputForm
= {"integer", "real", x}
'Blank' only matches a single expression:
>> MatchQ[f[1, 2], f[_]]
= False
"""

rules = {
(
"MakeBoxes[Verbatim[Blank][], "
"f:StandardForm|TraditionalForm|OutputForm|InputForm]"
): '"_"',
(
"MakeBoxes[Verbatim[Blank][head_Symbol], "
"f:StandardForm|TraditionalForm|OutputForm|InputForm]"
): ('"_" <> MakeBoxes[head, f]'),
}
summary_text = "match to any single expression"

def match(self, expression: Expression, pattern_context: dict):
vars_dict = pattern_context["vars_dict"]
yield_func = pattern_context["yield_func"]

if not expression.has_form("Sequence", 0):
if self.head is not None:
if expression.get_head().sameQ(self.head):
yield_func(vars_dict, None)
else:
yield_func(vars_dict, None)


class BlankNullSequence(_Blank):
"""
<url>:WMA link:https://reference.wolfram.com/language/ref/BlankNullSequence.html</url>
<dl>
<dt>'BlankNullSequence[]'
<dt>'___'
<dd>represents any sequence of expression elements in a pattern, \
including an empty sequence.
</dl>
'BlankNullSequence' is like 'BlankSequence', except it can match an \
empty sequence:
>> MatchQ[f[], f[___]]
= True
"""

rules = {
"MakeBoxes[Verbatim[BlankNullSequence][], f:StandardForm|TraditionalForm|OutputForm|InputForm]": '"___"',
"MakeBoxes[Verbatim[BlankNullSequence][head_Symbol], f:StandardForm|TraditionalForm|OutputForm|InputForm]": '"___" <> MakeBoxes[head, f]',
}
summary_text = "match to a sequence of zero or more elements"

def match(self, expression: Expression, pattern_context: dict):
"""Match with a BlankNullSequence"""
vars_dict = pattern_context["vars_dict"]
yield_func = pattern_context["yield_func"]
elements = expression.get_sequence()
if self.head:
ok = True
for element in elements:
if element.get_head() != self.head:
ok = False
break
if ok:
yield_func(vars_dict, None)
else:
yield_func(vars_dict, None)

def get_match_count(self, vars_dict: OptionalType[dict] = None) -> tuple:
return (0, None)


class BlankSequence(_Blank):
"""
<url>:WMA link:https://reference.wolfram.com/language/ref/BlankSequence.html</url>
<dl>
<dt>'BlankSequence[]'
<dt>'__'
<dd>represents any non-empty sequence of expression elements in \
a pattern.
<dt>'BlankSequence[$h$]'
<dt>'__$h$'
<dd>represents any sequence of elements, all of which have head $h$.
</dl>
Use a 'BlankSequence' pattern to stand for a non-empty sequence of \
arguments:
>> MatchQ[f[1, 2, 3], f[__]]
= True
>> MatchQ[f[], f[__]]
= False
'__'$h$ will match only if all elements have head $h$:
>> MatchQ[f[1, 2, 3], f[__Integer]]
= True
>> MatchQ[f[1, 2.0, 3], f[__Integer]]
= False
The value captured by a named 'BlankSequence' pattern is a \
'Sequence' object:
>> f[1, 2, 3] /. f[x__] -> x
= Sequence[1, 2, 3]
"""

rules = {
"MakeBoxes[Verbatim[BlankSequence][], f:StandardForm|TraditionalForm|OutputForm|InputForm]": '"__"',
"MakeBoxes[Verbatim[BlankSequence][head_Symbol], f:StandardForm|TraditionalForm|OutputForm|InputForm]": '"__" <> MakeBoxes[head, f]',
}
summary_text = "match to a non-empty sequence of elements"

def match(self, expression: Expression, pattern_context: dict):
vars_dict = pattern_context["vars_dict"]
yield_func = pattern_context["yield_func"]
elements = expression.get_sequence()
if not elements:
return
if self.head:
ok = True
for element in elements:
if element.get_head() != self.head:
ok = False
break
if ok:
yield_func(vars_dict, None)
else:
yield_func(vars_dict, None)

def get_match_count(self, vars_dict: OptionalType[dict] = None) -> tuple:
return (1, None)
Loading

0 comments on commit a71bbd6

Please sign in to comment.