Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split mathics builtin patterns #1123

Merged
merged 20 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,921 changes: 0 additions & 1,921 deletions mathics/builtin/patterns.py

This file was deleted.

37 changes: 37 additions & 0 deletions mathics/builtin/patterns/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Rules and Patterns

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.
"""
203 changes: 203 additions & 0 deletions mathics/builtin/patterns/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
"""
Basic Pattern Objects

"""

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.blank-like"


class _Blank(PatternObject):
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
rocky marked this conversation as resolved.
Show resolved Hide resolved
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 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
rocky marked this conversation as resolved.
Show resolved Hide resolved
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
rocky marked this conversation as resolved.
Show resolved Hide resolved
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
rocky marked this conversation as resolved.
Show resolved Hide resolved
'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)


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)
Loading