From 5c95f0e238af98bc0238f78b22de99033c811597 Mon Sep 17 00:00:00 2001 From: "R. Bernstein" Date: Fri, 30 Aug 2024 23:05:42 -0400 Subject: [PATCH] Go over operators (#1076) --- .github/workflows/consistency-checks.yml | 10 ++- .github/workflows/osx.yml | 9 ++- .github/workflows/ubuntu-cython.yml | 8 ++- .github/workflows/ubuntu.yml | 12 +++- .github/workflows/windows.yml | 9 ++- .../builtin/assignments/assign_binaryop.py | 4 -- mathics/builtin/atomic/strings.py | 10 +-- mathics/builtin/exp_structure/general.py | 8 +-- mathics/core/builtin.py | 70 ++++++++++++++----- 9 files changed, 100 insertions(+), 40 deletions(-) diff --git a/.github/workflows/consistency-checks.yml b/.github/workflows/consistency-checks.yml index 433c3b169..92fb4e691 100644 --- a/.github/workflows/consistency-checks.yml +++ b/.github/workflows/consistency-checks.yml @@ -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 diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index ef920051d..b61736336 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -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 diff --git a/.github/workflows/ubuntu-cython.yml b/.github/workflows/ubuntu-cython.yml index a2c46f313..38680337b 100644 --- a/.github/workflows/ubuntu-cython.yml +++ b/.github/workflows/ubuntu-cython.yml @@ -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: | diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 3d72f36e7..a288b0efa 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -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 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0b1e451bd..0ffb29722 100755 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -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 diff --git a/mathics/builtin/assignments/assign_binaryop.py b/mathics/builtin/assignments/assign_binaryop.py index d452def4c..865bb377d 100644 --- a/mathics/builtin/assignments/assign_binaryop.py +++ b/mathics/builtin/assignments/assign_binaryop.py @@ -40,7 +40,6 @@ class AddTo(BinaryOperator): attributes = A_HOLD_FIRST | A_PROTECTED grouping = "Right" operator = "+=" - precedence = 100 rules = { "x_ += dx_": "x = x + dx", @@ -68,7 +67,6 @@ class Decrement(PostfixOperator): """ operator = "--" - precedence = 660 attributes = A_HOLD_FIRST | A_PROTECTED | A_READ_PROTECTED rules = { @@ -100,7 +98,6 @@ class DivideBy(BinaryOperator): attributes = A_HOLD_FIRST | A_PROTECTED grouping = "Right" operator = "/=" - precedence = 100 rules = { "x_ /= dx_": "x = x / dx", @@ -131,7 +128,6 @@ class Increment(PostfixOperator): """ operator = "++" - precedence = 660 attributes = A_HOLD_FIRST | A_PROTECTED | A_READ_PROTECTED rules = { diff --git a/mathics/builtin/atomic/strings.py b/mathics/builtin/atomic/strings.py index 1f6d1bc8c..5a184a276 100644 --- a/mathics/builtin/atomic/strings.py +++ b/mathics/builtin/atomic/strings.py @@ -337,7 +337,7 @@ class CharacterEncoding(Predefined): Setting its value to 'None' restore the value to \ '$SystemCharacterEncoding': - >> $CharacterEncoding = None; + >> $CharacterEncoding = None; >> $SystemCharacterEncoding == $CharacterEncoding = True @@ -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""" :WMA link: - https://reference.wolfram.com/language/ref/InterpretedBox.html + https://reference.wolfram.com/language/ref/InterpretationBox.html
-
'InterpretedBox[$box$]' +
'InterpretationBox[$box$]'
is the ad hoc fullform for \! $box$. just for internal use...
@@ -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 diff --git a/mathics/builtin/exp_structure/general.py b/mathics/builtin/exp_structure/general.py index 8c973b3a8..68efd0070 100644 --- a/mathics/builtin/exp_structure/general.py +++ b/mathics/builtin/exp_structure/general.py @@ -14,14 +14,14 @@ from mathics.eval.parts import python_levelspec, walk_levels -class ApplyLevel(BinaryOperator): +class MapApply(BinaryOperator): """ :WMA link: - https://reference.wolfram.com/language/ref/ApplyLevel.html + https://reference.wolfram.com/language/ref/MapApply.html
-
'ApplyLevel[$f$, $expr$]' +
'MapApply[$f$, $expr$]'
'$f$ @@@ $expr$'
is equivalent to 'Apply[$f$, $expr$, {1}]'. @@ -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" diff --git a/mathics/core/builtin.py b/mathics/core/builtin.py index ebe03edb5..b97f0cbd6 100644 --- a/mathics/core/builtin.py +++ b/mathics/core/builtin.py @@ -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 @@ -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: @@ -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 = [] @@ -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``. @@ -964,7 +965,11 @@ 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 @@ -972,6 +977,17 @@ class Operator(Builtin): 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 @@ -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: @@ -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