Skip to content

Commit

Permalink
Go over operators (#1076)
Browse files Browse the repository at this point in the history
  • Loading branch information
rocky authored Aug 31, 2024
1 parent a3478d2 commit 5c95f0e
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 40 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/consistency-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
sudo apt update -qq && sudo apt install llvm-dev
sudo apt update -qq && sudo apt install llvm-dev remake
python -m pip install --upgrade pip
# We can comment out after next Mathics-Scanner release
python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
# python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
git clone https://github.com/Mathics3/mathics-scanner.git
cd mathics-scanner/
git checkout operator-tables-redux
pip install -e .
cd ..
- name: Install Mathics with minimum dependencies
run: |
make develop
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/osx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install OS dependencies
run: |
brew install llvm@14 tesseract
brew install llvm@14 tesseract remake
python -m pip install --upgrade pip
- name: Install Mathics3 with full Python dependencies
run: |
# We can comment out after next Mathics-Scanner release
python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
git clone https://github.com/Mathics3/mathics-scanner.git
cd mathics-scanner/
git checkout operator-tables-redux
pip install -e .
cd ..
# python -m pip install Mathics-Scanner[full]
make develop-full
remake -x develop-full
- name: Test Mathics3
run: |
make -j3 check
8 changes: 7 additions & 1 deletion .github/workflows/ubuntu-cython.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ jobs:
sudo apt-get update -qq && sudo apt-get install -qq liblapack-dev llvm-dev tesseract-ocr
python -m pip install --upgrade pip
# We can comment out after next Mathics-Scanner release
python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
# python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
git clone https://github.com/Mathics3/mathics-scanner.git
cd mathics-scanner/
git checkout operator-tables-redux
pip install -e .
cd ..
# python -m pip install Mathics-Scanner[full]
- name: Install Mathics with full dependencies
run: |
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,20 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install OS dependencies
run: |
sudo apt-get update -qq && sudo apt-get install -qq liblapack-dev llvm-dev tesseract-ocr
sudo apt-get update -qq && sudo apt-get install -qq liblapack-dev llvm-dev tesseract-ocr remake
- name: Install Mathics3 with full dependencies
run: |
python -m pip install --upgrade pip
# We can comment out after next Mathics-Scanner release
python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
# python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
git clone https://github.com/Mathics3/mathics-scanner.git
cd mathics-scanner/
git checkout operator-tables-redux
pip install -e .
cd ..
# python -m pip install Mathics-Scanner[full]
make develop-full
remake -x develop-full
- name: Test Mathics
run: |
make -j3 check
9 changes: 8 additions & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ jobs:
- name: Install Mathics3 with Python dependencies
run: |
# We can comment out after next Mathics-Scanner release
python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
# python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
git clone https://github.com/Mathics3/mathics-scanner.git
cd mathics-scanner
git checkout operator-tables-redux
pip install -e .
cd ..
# python -m pip install Mathics-Scanner[full]
make develop-full
- name: Test Mathics3
Expand Down
4 changes: 0 additions & 4 deletions mathics/builtin/assignments/assign_binaryop.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ class AddTo(BinaryOperator):
attributes = A_HOLD_FIRST | A_PROTECTED
grouping = "Right"
operator = "+="
precedence = 100

rules = {
"x_ += dx_": "x = x + dx",
Expand Down Expand Up @@ -68,7 +67,6 @@ class Decrement(PostfixOperator):
"""

operator = "--"
precedence = 660
attributes = A_HOLD_FIRST | A_PROTECTED | A_READ_PROTECTED

rules = {
Expand Down Expand Up @@ -100,7 +98,6 @@ class DivideBy(BinaryOperator):
attributes = A_HOLD_FIRST | A_PROTECTED
grouping = "Right"
operator = "/="
precedence = 100

rules = {
"x_ /= dx_": "x = x / dx",
Expand Down Expand Up @@ -131,7 +128,6 @@ class Increment(PostfixOperator):
"""

operator = "++"
precedence = 660
attributes = A_HOLD_FIRST | A_PROTECTED | A_READ_PROTECTED

rules = {
Expand Down
10 changes: 5 additions & 5 deletions mathics/builtin/atomic/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ class CharacterEncoding(Predefined):
Setting its value to 'None' restore the value to \
'$SystemCharacterEncoding':
>> $CharacterEncoding = None;
>> $CharacterEncoding = None;
>> $SystemCharacterEncoding == $CharacterEncoding
= True
Expand Down Expand Up @@ -411,14 +411,14 @@ class HexadecimalCharacter(Builtin):

# This isn't your normal Box class. We'll keep this here rather than
# in mathics.builtin.box for now.
class InterpretedBox(PrefixOperator):
class InterpretationBox(PrefixOperator):
r"""
<url>
:WMA link:
https://reference.wolfram.com/language/ref/InterpretedBox.html</url>
https://reference.wolfram.com/language/ref/InterpretationBox.html</url>
<dl>
<dt>'InterpretedBox[$box$]'
<dt>'InterpretationBox[$box$]'
<dd>is the ad hoc fullform for \! $box$. just for internal use...
</dl>
Expand All @@ -431,7 +431,7 @@ class InterpretedBox(PrefixOperator):
summary_text = "interpret boxes as an expression"

def eval(self, boxes, evaluation: Evaluation):
"""InterpretedBox[boxes_]"""
"""InterpretationBox[boxes_]"""
# TODO: the following is a very raw and dummy way to
# handle these expressions.
# In the first place, this should handle different kind
Expand Down
8 changes: 4 additions & 4 deletions mathics/builtin/exp_structure/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
from mathics.eval.parts import python_levelspec, walk_levels


class ApplyLevel(BinaryOperator):
class MapApply(BinaryOperator):
"""
<url>
:WMA link:
https://reference.wolfram.com/language/ref/ApplyLevel.html</url>
https://reference.wolfram.com/language/ref/MapApply.html</url>
<dl>
<dt>'ApplyLevel[$f$, $expr$]'
<dt>'MapApply[$f$, $expr$]'
<dt>'$f$ @@@ $expr$'
<dd>is equivalent to 'Apply[$f$, $expr$, {1}]'.
Expand All @@ -36,7 +36,7 @@ class ApplyLevel(BinaryOperator):
precedence = 620

rules = {
"ApplyLevel[f_, expr_]": "Apply[f, expr, {1}]",
"MapApply[f_, expr_]": "Apply[f, expr, {1}]",
}

summary_text = "apply a function to a list, at the top level"
Expand Down
70 changes: 52 additions & 18 deletions mathics/core/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import importlib
import os.path as osp
import re
from abc import ABC
from functools import lru_cache, total_ordering
from itertools import chain
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union, cast
Expand Down Expand Up @@ -77,12 +78,12 @@
ROOT_DIR = pkg_resources.resource_filename("mathics", "")

# Load the conversion tables from disk
characters_path = osp.join(ROOT_DIR, "data", "operator-tables.json")
operator_tables_path = osp.join(ROOT_DIR, "data", "operator-tables.json")
assert osp.exists(
characters_path
), f"Operator precedence tables are missing; expected to be in {characters_path}"
with open(characters_path, "r") as f:
operator_data = ujson.load(f)
operator_tables_path
), f"Internal error: Operator precedence tables are missing; expected to be in {operator_tables_path}"
with open(operator_tables_path, "r") as f:
OPERATOR_DATA = ujson.load(f)


class Builtin:
Expand Down Expand Up @@ -248,7 +249,7 @@ def contribute(self, definitions, is_pymodule=False):
check_options = None
else:
raise ValueError(
"illegal option mode %s; check $OptionSyntax." % option_syntax
f"illegal option mode {option_syntax}; check $OptionSyntax."
)

rules = []
Expand Down Expand Up @@ -518,7 +519,7 @@ def get_sympy_names(self) -> List[str]:

# This has to come before MPMathFunction
class SympyFunction(SympyObject):
def eval(self, z, evaluation):
def eval(self, z, evaluation: Evaluation):
# Note: we omit a docstring here, so as not to confuse
# function signature collector ``contribute``.

Expand Down Expand Up @@ -964,14 +965,29 @@ def eval_multi(self, expr, first, sequ, evaluation):
return to_expression(name, to_expression(name, expr, *sequ), first)


class Operator(Builtin):
class Operator(Builtin, ABC):
"""
Base Class for operators: binary, unary, nullary, prefix postfix, ...
"""

operator: Optional[str] = None
precedence: Optional[int] = None
precedence_parse = None
needs_verbatim = False

default_formats = True

def get_precedence(self, name: str) -> int:
operator_info = OPERATOR_DATA.get("operator-precedence")
assert isinstance(
operator_info, dict
), 'Internal error: "operator-precedence" should be found in operators.json'
precedence = operator_info.get(name)
assert isinstance(
precedence, int
), f'Internal error: "precedence" field for "{name}" should be an integer is {precedence}'
return precedence

def get_operator(self) -> Optional[str]:
return self.operator

Expand All @@ -995,9 +1011,14 @@ def get_functions(self, prefix="eval", is_pymodule=False) -> List[Callable]:


class UnaryOperator(Operator):
"""
Class for Unary Operators, (e.g. Not, Factorial)
"""

def __init__(self, format_function, *args, **kwargs):
super().__init__(*args, **kwargs)
name = self.get_name()
name = self.get_name(short=True)
self.precedence = self.get_precedence(name)
if self.needs_verbatim:
name = f"Verbatim[{name}"
if self.default_formats:
Expand All @@ -1008,57 +1029,70 @@ def __init__(self, format_function, *args, **kwargs):
form = '%s[{HoldForm[item]},"%s",%d]' % (
format_function,
operator,
operator_data["operator-precedence"].get(name, self.precedence),
self.precedence,
)
self.formats[op_pattern] = form


class PrefixOperator(UnaryOperator):
"""
Class for Bultin Prefix Unary Operators, e.g. Not ("¬")
"""

def __init__(self, *args, **kwargs):
super().__init__("Prefix", *args, **kwargs)


class PostfixOperator(UnaryOperator):
"""
Class for Bultin Postfix Unary Operators, e.g. Factorial (!)
"""

def __init__(self, *args, **kwargs):
super().__init__("Postfix", *args, **kwargs)


class BinaryOperator(Operator):
"""
Class for Builtin Binary Operators, e.g. Plus (+)
"""

grouping = "System`None" # NonAssociative, None, Left, Right

def __init__(self, *args, **kwargs):
super(BinaryOperator, self).__init__(*args, **kwargs)
name = self.get_name()
name = self.get_name(short=True)
self.precedence = self.get_precedence(name)

# Prevent pattern matching symbols from gaining meaning here using
# Verbatim
name = "Verbatim[%s]" % name
name = f"Verbatim[{name}]"

# For compatibility, allow grouping symbols in builtins to be
# specified without System`.
self.grouping = ensure_context(self.grouping)

if self.grouping in ("System`None", "System`NonAssociative"):
op_pattern = "%s[items__]" % name
op_pattern = f"{name}[items__]"
replace_items = "items"
else:
op_pattern = "%s[x_, y_]" % name
op_pattern = f"{name}[x_, y_]"
replace_items = "x, y"

operator = ascii_operator_to_symbol.get(self.operator, self.__class__.__name__)

if self.default_formats:
formatted = "MakeBoxes[Infix[{%s}, %s, %d,%s], form]" % (
replace_items,
operator,
operator_data["operator-precedence"].get(name, self.precedence),
self.precedence,
self.grouping,
)
default_rules = {
"MakeBoxes[{0}, form:StandardForm|TraditionalForm]".format(
op_pattern
): formatted,
"MakeBoxes[{0}, form:InputForm|OutputForm]".format(
op_pattern
): formatted,
f"MakeBoxes[{op_pattern}, form:InputForm|OutputForm]": formatted,
}
default_rules.update(self.rules)
self.rules = default_rules
Expand Down

0 comments on commit 5c95f0e

Please sign in to comment.