diff --git a/.github/workflows/consistency-checks.yml b/.github/workflows/consistency-checks.yml
index 03dada2f0..433c3b169 100644
--- a/.github/workflows/consistency-checks.yml
+++ b/.github/workflows/consistency-checks.yml
@@ -13,9 +13,9 @@ jobs:
matrix:
python-version: ['3.11']
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
diff --git a/.github/workflows/isort-and-black-checks.yml b/.github/workflows/isort-and-black-checks.yml
index 37cde2a21..00bd2362b 100644
--- a/.github/workflows/isort-and-black-checks.yml
+++ b/.github/workflows/isort-and-black-checks.yml
@@ -9,13 +9,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python 3.11
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Install click, black and isort
- run: pip install 'click==8.0.4' 'black==22.3.0' 'isort==5.10.1'
+ run: pip install 'click==8.0.4' 'black==23.12.1' 'isort==5.13.2'
- name: Run isort --check .
run: isort --check .
- name: Run black --check .
diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml
index 09d594949..82feb3523 100644
--- a/.github/workflows/osx.yml
+++ b/.github/workflows/osx.yml
@@ -9,24 +9,23 @@ on:
jobs:
build:
env:
- LDFLAGS: "-L/usr/local/opt/llvm@11/lib"
- CPPFLAGS: "-I/usr/local/opt/llvm@11/include"
+ LDFLAGS: "-L/usr/local/opt/llvm@14/lib"
+ CPPFLAGS: "-I/usr/local/opt/llvm@14/include"
runs-on: macos-latest
strategy:
matrix:
os: [macOS]
python-version: ['3.9', '3.10']
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install OS dependencies
run: |
- brew install llvm@11 tesseract
+ brew install llvm@14 tesseract
python -m pip install --upgrade pip
- LLVM_CONFIG=/usr/local/Cellar/llvm@11/11.1.0/bin/llvm-config pip install llvmlite
- name: Install Mathics3 with full Python dependencies
run: |
# We can comment out after next Mathics-Scanner release
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
index e32d9b54b..2f6a24960 100644
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -13,9 +13,9 @@ jobs:
matrix:
python-version: ['3.11', '3.8', '3.9', '3.10']
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install OS dependencies
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index d2a6bc21e..beae3c047 100755
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -12,11 +12,13 @@ jobs:
strategy:
matrix:
os: [windows]
- python-version: ['3.10', '3.11']
+ # "make doctest" on MS Windows fails without showing much of a
+ # trace of where things went wrong on Python before 3.11.
+ python-version: ['3.11']
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install OS dependencies
@@ -27,7 +29,7 @@ jobs:
# so we will be safe here. Another possibility would be check and install
# conditionally.
choco install --force llvm
- choco install tesseract
+ # choco install tesseract
set LLVM_DIR="C:\Program Files\LLVM"
- name: Install Mathics3 with Python dependencies
run: |
@@ -46,8 +48,6 @@ jobs:
run: |
pip install pyocr # from full
pip install -e .[dev]
- set PYTEST_WORKERS="-n3"
- # Until we can't figure out what's up with TextRecognize:
make pytest gstest
- make doctest o="--exclude TextRecognize"
+ make doctest
# make check
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3d0c39a88..b51fd5936 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,7 +2,7 @@ default_language_version:
python: python
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.0.1
+ rev: v4.5.0
hooks:
- id: check-merge-conflict
- id: debug-statements
@@ -10,12 +10,12 @@ repos:
- id: end-of-file-fixer
stages: [commit]
- repo: https://github.com/pycqa/isort
- rev: 5.10.1
+ rev: 5.13.2
hooks:
- id: isort
stages: [commit]
- repo: https://github.com/psf/black
- rev: 22.3.0
+ rev: 23.12.1
hooks:
- id: black
language_version: python3
diff --git a/AUTHORS.txt b/AUTHORS.txt
index 9dc803a42..03cdcf44b 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -36,6 +36,8 @@ Additional contributions were made by:
- Pablo Emilio Escobar Gaviria @GarkGarcia
- Rocky Bernstein @rocky
- Tiago Cavalcante Trindade @TiagoCavalcante
+- Li Xiang @Li-Xiang-Ideal
+- Kevin Cao @kejcao
Thanks to the authors of all projects that are used in Mathics:
- Django
diff --git a/CHANGES.rst b/CHANGES.rst
index 6bfe2990d..c233ec27a 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -7,16 +7,23 @@ CHANGES
New Builtins
++++++++++++
+* ``$MaxLengthIntStringConversion``
+* ``Elements``
+* ``ConjugateTranspose``
+* ``LeviCivitaTensor``
+* ``RealAbs`` and ``RealSign``
+* ``RealValuedNumberQ``
-* `Elements`
-* `RealAbs` and `RealSign`
Compatibility
-------------
* ``*Plot`` does not show messages during the evaluation.
-
-
+* ``Range[]`` now handles a negative ``di`` PR #951
+* Improved support for ``DirectedInfinity`` and ``Indeterminate``.
+* ``Graphics`` and ``Graphics3D`` including wrong primitives and directives
+ are shown with a pink background. In the Mathics-Django interface, a tooltip
+ error message is also shown.
Internals
---
@@ -24,15 +31,24 @@ Internals
* ``eval_abs`` and ``eval_sign`` extracted from ``Abs`` and ``Sign`` and added to ``mathics.eval.arithmetic``.
* Maximum number of digits allowed in a string set to 7000 and can be adjusted using environment variable
``MATHICS_MAX_STR_DIGITS`` on Python versions that don't adjust automatically (like pyston).
-* Real number comparisons implemented is based now in the internal implementation of `RealSign`.
+* Real number comparisons implemented is based now in the internal implementation of ``RealSign``.
+* For Python 3.11, the variable ``$MaxLengthIntStringConversion`` controls the maximum size of
+ the literal conversion between large integers and Strings.
+* Older style non-appearing and non-pedagogical doctests have been converted to pytest
+* Built-in code is directed explicitly rather than implicitly. This facilitates the ability to lazy load
+ builtins or "autoload" them a la GNU Emacs autoload.
Bugs
----
-* Improved support for ``DirectedInfinity`` and ``Indeterminate``.
* ``Definitions`` is compatible with ``pickle``.
-
-
+* Improved support for ``Quantity`` expressions, including conversions, formatting and arithmetic operations.
+* ``Background`` option for ``Graphics`` and ``Graphics3D`` is operative again.
+* ``Switch[]`` involving ``Infinity`` Issue #956
+* ``Outer[]`` on ``SparseArray`` Issue #939
+* ``ArrayQ[]`` detects ``SparseArray`` PR #947
+* Numeric comparisons against expressions involving ``String``s (Issue #797).
+
Package updates
+++++++++++++++
@@ -131,7 +147,7 @@ Documentation
#. "Exponential Functional" split out from "Trigonometry Functions"
#. "Functional Programming" section split out.
#. "Image Manipulation" has been split off from Graphics and Drawing and turned into a guide section.
-#. Image examples now appear in the LaTeX and therfore the PDF doc
+#. Image examples now appear in the LaTeX and therefore the PDF doc
#. "Logic and Boolean Algebra" section reinstated.
#. "Forms of Input and Output" is its own guide section.
#. More URL links to Wiki pages added; more internal cross links added.
@@ -168,7 +184,7 @@ Bugs
#. Better handling of ``Infinite`` quantities.
#. Improved ``Precision`` and ``Accuracy``compatibility with WMA. In particular, ``Precision[0.]`` and ``Accuracy[0.]``
#. Accuracy in numbers using the notation ``` n.nnn``acc ``` now is properly handled.
-#. numeric precision in mpmath was not reset after operations that changed these. This cause huges slowdowns after an operation that set the mpmath precison high. This was the source of several-minute slowdowns in testing.
+#. numeric precision in mpmath was not reset after operations that changed these. This cause huges slowdowns after an operation that set the mpmath precision high. This was the source of several-minute slowdowns in testing.
#. GIF87a (```MadTeaParty.gif`` or ExampleData) image loading fixed
#. Replace non-free Leena image with a a freely distributable image. Issue #728
@@ -1046,7 +1062,7 @@ New features (50+ builtins)
#. ``SubsetQ`` and ``Delete[]`` #688, #784,
#. ``Subsets`` #685
#. ``SystemTimeZone`` and correct ``TimeZone`` #924
-#. ``System\`Byteordering`` and ``System\`Environemnt`` #859
+#. ``System\`Byteordering`` and ``System\`Environment`` #859
#. ``$UseSansSerif`` #908
#. ``randchoice`` option for ``NoNumPyRandomEnv`` #820
#. Support for ``MATHICS_MAX_RECURSION_DEPTH``
@@ -1396,7 +1412,7 @@ New features
#. ``PolarPlot``
#. IPython style (coloured) input
#. ``VectorAnalysis`` Package
-#. More special functions (Bessel functions and othogonal polynomials)
+#. More special functions (Bessel functions and orthogonal polynomials)
#. More NumberTheory functions
#. ``Import``, ``Export``, ``Get``, ``Needs`` and other IO related functions
#. PyPy compatibility
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 18c914718..b3f31aecc 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -5,7 +5,7 @@
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
-identity and expression, level of experience, education, socio-economic status,
+identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
diff --git a/FUTURE.rst b/FUTURE.rst
index e37758f22..291e016d2 100644
--- a/FUTURE.rst
+++ b/FUTURE.rst
@@ -2,9 +2,11 @@
.. contents::
-The following 2023 road map that appears the 6.0.0 hasn't gone through enough discussion. This provisional.
-Check the github repository for updates.
+2024 Roadmap
+============
+
+To be decided...
2023 Roadmap
============
diff --git a/Makefile b/Makefile
index 9b84d559e..8d0f2da9c 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,8 @@ PYTHON ?= python3
PIP ?= pip3
BASH ?= bash
RM ?= rm
+PYTEST_OPTIONS ?=
+DOCTEST_OPTIONS ?=
# Variable indicating Mathics3 Modules you have available on your system, in latex2doc option format
MATHICS3_MODULE_OPTION ?= --load-module pymathics.graph,pymathics.natlang
@@ -73,7 +75,7 @@ develop-full-cython: mathics/data/op-tables.json
$(PIP) install -e .[dev,full,cython]
-#: Make distirbution: wheels, eggs, tarball
+#: Make distribution: wheels, eggs, tarball
dist:
./admin-tools/make-dist.sh
@@ -84,13 +86,16 @@ install:
#: Run the most extensive set of tests
check: pytest gstest doctest
+#: Run the most extensive set of tests
+check-for-Windows: pytest-for-windows gstest doctest
+
#: Build and check manifest of Builtins
check-builtin-manifest:
$(PYTHON) admin-tools/build_and_check_manifest.py
#: Run pytest consistency and style checks
check-consistency-and-style:
- MATHICS_LINT=t $(PYTHON) -m pytest test/consistency-and-style
+ MATHICS_LINT=t $(PYTHON) -m pytest $(PYTEST_OPTIONS) test/consistency-and-style
check-full: check-builtin-manifest check-builtin-manifest check
@@ -113,9 +118,9 @@ clean: clean-cython clean-cache
rm -f mathics/data/op-tables || true; \
rm -rf build || true
-#: Run py.test tests. Use environment variable "o" for pytest options
+#: Run pytest tests. Use environment variable "PYTEST_OPTIONS" for pytest options
pytest:
- MATHICS_CHARACTER_ENCODING="ASCII" $(PYTHON) -m pytest $(PYTEST_WORKERS) test $o
+ MATHICS_CHARACTER_ENCODING="ASCII" $(PYTHON) -m pytest $(PYTEST_OPTIONS) $(PYTEST_WORKERS) test
#: Run a more extensive pattern-matching test
@@ -128,9 +133,9 @@ gstest:
doctest-data: mathics/builtin/*.py mathics/doc/documentation/*.mdoc mathics/doc/documentation/images/*
MATHICS_CHARACTER_ENCODING="UTF-8" $(PYTHON) mathics/docpipeline.py --output --keep-going $(MATHICS3_MODULE_OPTION)
-#: Run tests that appear in docstring in the code.
+#: Run tests that appear in docstring in the code. Use environment variable "DOCTEST_OPTIONS" for doctest options
doctest:
- MATHICS_CHARACTER_ENCODING="ASCII" SANDBOX=$(SANDBOX) $(PYTHON) mathics/docpipeline.py $o
+ MATHICS_CHARACTER_ENCODING="ASCII" SANDBOX=$(SANDBOX) $(PYTHON) mathics/docpipeline.py $(DOCTEST_OPTIONS)
#: Make Mathics PDF manual via Asymptote and LaTeX
latexdoc texdoc doc:
diff --git a/PAST.rst b/PAST.rst
index 20d41b9c7..39d8fead9 100644
--- a/PAST.rst
+++ b/PAST.rst
@@ -12,7 +12,7 @@ A fair bit of code refactoring has gone on so that we might be able to
scale the code, get it to be more performant, and more in line with
other interpreters. There is Greater use of Symbols as opposed to strings.
-The buitin Functions have been organized into grouping akind to what is found in WMA.
+The builtin Functions have been organized into grouping akind to what is found in WMA.
This is not just for documentation purposes, but it better modularizes the code and keep
the modules smaller while suggesting where functions below as we scale.
@@ -34,14 +34,14 @@ Boxing and Formatting
While some work on formatting is done has been made and the change in API reflects a little of this.
However a lot more work needs to be done.
-Excecution Performance
+Execution Performance
----------------------
This has improved a slight bit, but not because it has been a focus, but
rather because in going over the code organization, we are doing this
less dumb, e.g. using Symbols more where symbols are intended. Or
fixing bugs like resetting mpmath numeric precision on operations that
-need to chnage it temporarily.
+need to change it temporarily.
Simpler Things
--------------
@@ -50,6 +50,6 @@ A number of items here remain, but should not be thought as independent items, b
"Forms, Boxing and Formatting".
"Making StandardOutput of polynomials match WMA" is really are Forms, Boxing and Formatting issue;
-"Working on Jupyter integrations" is also very dependant this.
+"Working on Jupyter integrations" is also very dependent this.
So the next major refactor will be on Forms, Boxing and Formatting.
diff --git a/SYMBOLS_MANIFEST.txt b/SYMBOLS_MANIFEST.txt
index d3ecf4204..43c6e2162 100644
--- a/SYMBOLS_MANIFEST.txt
+++ b/SYMBOLS_MANIFEST.txt
@@ -574,6 +574,7 @@ System`LessEqual
System`LetterCharacter
System`LetterNumber
System`LetterQ
+System`LeviCivitaTensor
System`Level
System`LevelQ
System`LightBlue
diff --git a/admin-tools/build_and_check_manifest.py b/admin-tools/build_and_check_manifest.py
index 0feb64af9..9a8400068 100755
--- a/admin-tools/build_and_check_manifest.py
+++ b/admin-tools/build_and_check_manifest.py
@@ -2,7 +2,7 @@
import sys
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
from mathics.core.load_builtin import (
import_and_load_builtins,
modules,
diff --git a/admin-tools/make-dist.sh b/admin-tools/make-dist.sh
index 015477da8..d2f045dd0 100755
--- a/admin-tools/make-dist.sh
+++ b/admin-tools/make-dist.sh
@@ -25,7 +25,8 @@ for pyversion in $PYVERSIONS; do
exit $?
fi
rm -fr build
- python setup.py bdist_egg
+ # PYPI no longer supports eggs
+ # python setup.py bdist_egg
python setup.py bdist_wheel
done
diff --git a/admin-tools/pyenv-versions b/admin-tools/pyenv-versions
index 9b7641862..c18408d7b 100644
--- a/admin-tools/pyenv-versions
+++ b/admin-tools/pyenv-versions
@@ -5,4 +5,4 @@ if [[ $0 == ${BASH_SOURCE[0]} ]] ; then
echo "This script should be *sourced* rather than run directly through bash"
exit 1
fi
-export PYVERSIONS='3.6.15 3.7.16 pyston-2.3.5 pypy3.9-7.3.11 3.8.16 3.9.16 3.10.10'
+export PYVERSIONS='3.6.15 3.7.16 pyston-2.3.5 pypy3.9-7.3.11 3.8.17 3.9.18 3.10.13 3.11.7'
diff --git a/examples/symbolic_logic/gries_schneider/GS1.m b/examples/symbolic_logic/gries_schneider/GS1.m
index adaa372a8..41ec8bb50 100644
--- a/examples/symbolic_logic/gries_schneider/GS1.m
+++ b/examples/symbolic_logic/gries_schneider/GS1.m
@@ -617,7 +617,7 @@
right-hand side of the rule now, while parsing the rule itself, only later,
after doing the pattern substitutions specified by the rule."
- Remember, evaluation is really aggressive. When you write a rule withe "->",
+ Remember, evaluation is really aggressive. When you write a rule with a "->",
mathics will try to evaluate the right-hand side.
Sometimes, it doesn't matter which of the two you use. In the example
diff --git a/examples/symbolic_logic/gries_schneider/GS2.m b/examples/symbolic_logic/gries_schneider/GS2.m
index e455f58f6..86d831e2a 100644
--- a/examples/symbolic_logic/gries_schneider/GS2.m
+++ b/examples/symbolic_logic/gries_schneider/GS2.m
@@ -31,7 +31,7 @@
<< "../../test_driver.m"
-(* Chaper 2, Boolean Expressions, page 25
+(* Chapter 2, Boolean Expressions, page 25
Section 2.1, Syntax and evaluation of Boolean expression, page 25
___ _ ___ _
@@ -110,7 +110,7 @@ target f(a). The number of different ways to assign ||B|| values to ||A||
there are 2 ** 4 == sixteen different binary functions.
I start with inert "true" and "false" to avoid evaluation leaks, i.e., to
- prevent mathics from reducing expessions that have active "True" and
+ prevent mathics from reducing expressions that have active "True" and
"False".
*************************************************************************** *)
diff --git a/examples/symbolic_logic/gries_schneider/GS3.m b/examples/symbolic_logic/gries_schneider/GS3.m
index 5aa12ac6f..5a6fdc8ee 100644
--- a/examples/symbolic_logic/gries_schneider/GS3.m
+++ b/examples/symbolic_logic/gries_schneider/GS3.m
@@ -29,7 +29,7 @@
*************************************************************************** *)
-(* Chaper 3, Propositional Calculus, page 41 **********************************
+(* Chapter 3, Propositional Calculus, page 41 **********************************
___ _ _ _ _
| _ \_ _ ___ _ __ ___ __(_) |_(_)___ _ _ __ _| |
| _/ '_/ _ \ '_ \/ _ (_-< | _| / _ \ ' \/ _` | |
diff --git a/mathics/__init__.py b/mathics/__init__.py
index ff26e6b2e..3684c6c29 100644
--- a/mathics/__init__.py
+++ b/mathics/__init__.py
@@ -14,7 +14,7 @@
# version_info contains a list of Python packages
# and the versions infsalled or "Not installed"
# if the package is not installed and "No version information"
-# if we can't get version infomation.
+# if we can't get version information.
version_info: Dict[str, str] = {
"mathics": __version__,
"mpmath": mpmath.__version__,
@@ -58,7 +58,7 @@
license_string = """\
-Copyright (C) 2011-2023 The Mathics Team.
+Copyright (C) 2011-2024 The Mathics Team.
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions.
diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py
index 63752d4ab..acaa343f4 100644
--- a/mathics/builtin/arithfns/basic.py
+++ b/mathics/builtin/arithfns/basic.py
@@ -2,7 +2,8 @@
"""
Basic Arithmetic
-The functions here are the basic arithmetic operations that you might find on a calculator.
+The functions here are the basic arithmetic operations that you might find \
+on a calculator.
"""
@@ -32,6 +33,13 @@
A_PROTECTED,
A_READ_PROTECTED,
)
+from mathics.core.builtin import (
+ BinaryOperator,
+ Builtin,
+ MPMathFunction,
+ PrefixOperator,
+ SympyFunction,
+)
from mathics.core.convert.expression import to_expression
from mathics.core.convert.sympy import from_sympy
from mathics.core.expression import Expression
@@ -79,25 +87,6 @@ class CubeRoot(Builtin):
>> CubeRoot[16]
= 2 2 ^ (1 / 3)
-
- #> CubeRoot[-5]
- = -5 ^ (1 / 3)
-
- #> CubeRoot[-510000]
- = -10 510 ^ (1 / 3)
-
- #> CubeRoot[-5.1]
- = -1.7213
-
- #> CubeRoot[b]
- = b ^ (1 / 3)
-
- #> CubeRoot[-0.5]
- = -0.793701
-
- #> CubeRoot[3 + 4 I]
- : The parameter 3 + 4 I should be real valued.
- = (3 + 4 I) ^ (1 / 3)
"""
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED | A_READ_PROTECTED
@@ -164,14 +153,6 @@ class Divide(BinaryOperator):
= a d / (b c e)
>> a / (b ^ 2 * c ^ 3 / e)
= a e / (b ^ 2 c ^ 3)
-
- #> 1 / 4.0
- = 0.25
- #> 10 / 3 // FullForm
- = Rational[10, 3]
- #> a / b // FullForm
- = Times[a, Power[b, -1]]
-
"""
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
@@ -292,26 +273,6 @@ class Plus(BinaryOperator, SympyFunction):
The sum of 2 red circles and 3 red circles is...
>> 2 Graphics[{Red,Disk[]}] + 3 Graphics[{Red,Disk[]}]
= 5 -Graphics-
-
- #> -2a - 2b
- = -2 a - 2 b
- #> -4+2x+2*Sqrt[3]
- = -4 + 2 Sqrt[3] + 2 x
- #> 2a-3b-c
- = 2 a - 3 b - c
- #> 2a+5d-3b-2c-e
- = 2 a - 3 b - 2 c + 5 d - e
-
- #> 1 - I * Sqrt[3]
- = 1 - I Sqrt[3]
-
- #> Head[3 + 2 I]
- = Complex
-
- #> N[Pi, 30] + N[E, 30]
- = 5.85987448204883847382293085463
- #> % // Precision
- = 30.
"""
attributes = (
@@ -395,7 +356,7 @@ def eval(self, items, evaluation):
return eval_Plus(*items_tuple)
-class Power(BinaryOperator, _MPMathFunction):
+class Power(BinaryOperator, MPMathFunction):
"""
:Exponentiation:
@@ -441,47 +402,6 @@ class Power(BinaryOperator, _MPMathFunction):
= -3.68294 + 6.95139 I
>> (1.5 + 1.0 I) ^ (3.5 + 1.5 I)
= -3.19182 + 0.645659 I
-
- #> 1/0
- : Infinite expression 1 / 0 encountered.
- = ComplexInfinity
- #> 0 ^ -2
- : Infinite expression 1 / 0 ^ 2 encountered.
- = ComplexInfinity
- #> 0 ^ (-1/2)
- : Infinite expression 1 / Sqrt[0] encountered.
- = ComplexInfinity
- #> 0 ^ -Pi
- : Infinite expression 1 / 0 ^ 3.14159 encountered.
- = ComplexInfinity
- #> 0 ^ (2 I E)
- : Indeterminate expression 0 ^ (0. + 5.43656 I) encountered.
- = Indeterminate
- #> 0 ^ - (Pi + 2 E I)
- : Infinite expression 0 ^ (-3.14159 - 5.43656 I) encountered.
- = ComplexInfinity
-
- #> 0 ^ 0
- : Indeterminate expression 0 ^ 0 encountered.
- = Indeterminate
-
- #> Sqrt[-3+2. I]
- = 0.550251 + 1.81735 I
- #> Sqrt[-3+2 I]
- = Sqrt[-3 + 2 I]
- #> (3/2+1/2I)^2
- = 2 + 3 I / 2
- #> I ^ I
- = (-1) ^ (I / 2)
-
- #> 2 ^ 2.0
- = 4.
-
- #> Pi ^ 4.
- = 97.4091
-
- #> a ^ b
- = a ^ b
"""
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_ONE_IDENTITY | A_PROTECTED
@@ -635,9 +555,6 @@ class Sqrt(SympyFunction):
>> Plot[Sqrt[a^2], {a, -2, 2}]
= -Graphics-
-
- #> N[Sqrt[2], 50]
- = 1.4142135623730950488016887242096980785696718753769
"""
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
@@ -723,56 +640,6 @@ class Times(BinaryOperator, SympyFunction):
= {HoldPattern[Default[Times]] :> 1}
>> a /. n_. * x_ :> {n, x}
= {1, a}
-
- #> -a*b // FullForm
- = Times[-1, a, b]
- #> -(x - 2/3)
- = 2 / 3 - x
- #> -x*2
- = -2 x
- #> -(h/2) // FullForm
- = Times[Rational[-1, 2], h]
-
- #> x / x
- = 1
- #> 2x^2 / x^2
- = 2
-
- #> 3. Pi
- = 9.42478
-
- #> Head[3 * I]
- = Complex
-
- #> Head[Times[I, 1/2]]
- = Complex
-
- #> Head[Pi * I]
- = Times
-
- #> 3 * a //InputForm
- = 3*a
- #> 3 * a //OutputForm
- = 3 a
-
- #> -2.123456789 x
- = -2.12346 x
- #> -2.123456789 I
- = 0. - 2.12346 I
-
- #> N[Pi, 30] * I
- = 3.14159265358979323846264338328 I
- #> N[I Pi, 30]
- = 3.14159265358979323846264338328 I
-
- #> N[Pi * E, 30]
- = 8.53973422267356706546355086955
- #> N[Pi, 30] * N[E, 30]
- = 8.53973422267356706546355086955
- #> N[Pi, 30] * E
- = 8.53973422267356706546355086955
- #> % // Precision
- = 30.
"""
attributes = (
@@ -828,7 +695,6 @@ def inverse(item):
and isinstance(item.elements[1], (Integer, Rational, Real))
and item.elements[1].to_sympy() < 0
): # nopep8
-
negative.append(inverse(item))
elif isinstance(item, Rational):
numerator = item.numerator()
diff --git a/mathics/builtin/arithfns/sums.py b/mathics/builtin/arithfns/sums.py
index 79b33ed4f..50f571a06 100644
--- a/mathics/builtin/arithfns/sums.py
+++ b/mathics/builtin/arithfns/sums.py
@@ -6,7 +6,7 @@
"""
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
class Accumulate(Builtin):
diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py
index 3ed71b068..f73d0699f 100644
--- a/mathics/builtin/arithmetic.py
+++ b/mathics/builtin/arithmetic.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-# cython: language_level=3
"""
Mathematical Functions
@@ -7,21 +6,12 @@
Basic arithmetic functions, including complex number arithmetic.
"""
-from functools import lru_cache
from typing import Optional
-import mpmath
import sympy
-from mathics.builtin.base import (
- Builtin,
- IterationFunction,
- Predefined,
- SympyFunction,
- SympyObject,
- Test,
-)
-from mathics.builtin.inference import evaluate_predicate, get_assumptions_list
+from mathics.builtin.inference import get_assumptions_list
+from mathics.builtin.numeric import Abs
from mathics.builtin.scoping import dynamic_scoping
from mathics.core.atoms import (
MATHICS3_COMPLEX_I,
@@ -31,20 +21,26 @@
Integer0,
Integer1,
IntegerM1,
- Number,
Rational,
Real,
String,
)
from mathics.core.attributes import (
- A_HOLD_ALL,
A_HOLD_REST,
A_LISTABLE,
A_NO_ATTRIBUTES,
A_NUMERIC_FUNCTION,
A_PROTECTED,
)
-from mathics.core.convert.expression import to_expression
+from mathics.core.builtin import (
+ Builtin,
+ IterationFunction,
+ MPMathFunction,
+ Predefined,
+ SympyFunction,
+ SympyObject,
+ Test,
+)
from mathics.core.convert.sympy import SympyExpression, from_sympy, sympy_symbol_prefix
from mathics.core.element import BaseElement
from mathics.core.evaluation import Evaluation
@@ -58,7 +54,6 @@
PredefinedExpression,
)
from mathics.core.list import ListExpression
-from mathics.core.number import dps, min_prec
from mathics.core.symbols import (
Atom,
Symbol,
@@ -72,20 +67,12 @@
SymbolAnd,
SymbolDirectedInfinity,
SymbolInfix,
- SymbolPiecewise,
SymbolPossibleZeroQ,
SymbolTable,
SymbolUndefined,
)
-from mathics.eval.arithmetic import (
- eval_Abs,
- eval_mpmath_function,
- eval_negate_number,
- eval_RealSign,
- eval_Sign,
-)
+from mathics.eval.arithmetic import eval_Sign
from mathics.eval.nevaluator import eval_N
-from mathics.eval.numerify import numerify
# This tells documentation how to sort this module
sort_order = "mathics.builtin.mathematical-functions"
@@ -99,86 +86,6 @@
}
-class _MPMathFunction(SympyFunction):
-
- # These below attributes are the default attributes:
- #
- # * functions take lists as an argument
- # * functions take numeric values only
- # * functions can't be changed
- #
- # However hey are not correct for some derived classes, like
- # InverseErf or InverseErfc.
- # So those classes should expclicitly set/override this.
- attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
-
- mpmath_name = None
- nargs = {1}
-
- @lru_cache(maxsize=1024)
- def get_mpmath_function(self, args):
- if self.mpmath_name is None or len(args) not in self.nargs:
- return None
- return getattr(mpmath, self.mpmath_name)
-
- def eval(self, z, evaluation: Evaluation):
- "%(name)s[z__]"
-
- args = numerify(z, evaluation).get_sequence()
-
- # if no arguments are inexact attempt to use sympy
- if all(not x.is_inexact() for x in args):
- result = to_expression(self.get_name(), *args).to_sympy()
- result = self.prepare_mathics(result)
- result = from_sympy(result)
- # evaluate elements to convert e.g. Plus[2, I] -> Complex[2, 1]
- return result.evaluate_elements(evaluation)
-
- if not all(isinstance(arg, Number) for arg in args):
- return
-
- mpmath_function = self.get_mpmath_function(tuple(args))
- if mpmath_function is None:
- return
-
- if any(arg.is_machine_precision() for arg in args):
- prec = None
- else:
- prec = min_prec(*args)
- d = dps(prec)
- args = [arg.round(d) for arg in args]
-
- return eval_mpmath_function(mpmath_function, *args, prec=prec)
-
-
-class _MPMathMultiFunction(_MPMathFunction):
-
- sympy_names = None
- mpmath_names = None
-
- def get_sympy_names(self):
- if self.sympy_names is None:
- return [self.sympy_name]
- return self.sympy_names.values()
-
- def get_function(self, module, names, fallback_name, elements):
- try:
- name = fallback_name
- if names is not None:
- name = names[len(elements)]
- if name is None:
- return None
- return getattr(module, name)
- except KeyError:
- return None
-
- def get_sympy_function(self, elements):
- return self.get_function(sympy, self.sympy_names, self.sympy_name, elements)
-
- def get_mpmath_function(self, elements):
- return self.get_function(mpmath, self.mpmath_names, self.mpmath_name, elements)
-
-
def create_infix(items, operator, prec, grouping):
if len(items) == 1:
return items[0]
@@ -192,55 +99,7 @@ def create_infix(items, operator, prec, grouping):
)
-class Abs(_MPMathFunction):
- """
-
- :Absolute value:
- https://en.wikipedia.org/wiki/Absolute_value (
- :SymPy:
- https://docs.sympy.org/latest/modules/functions/
- elementary.html#sympy.functions.elementary.complexes.Abs,
- :WMA: https://reference.wolfram.com/language/ref/Abs)
-
-
-
'Abs[$x$]'
-
returns the absolute value of $x$.
-
-
- >> Abs[-3]
- = 3
-
- >> Plot[Abs[x], {x, -4, 4}]
- = -Graphics-
-
- 'Abs' returns the magnitude of complex numbers:
- >> Abs[3 + I]
- = Sqrt[10]
- >> Abs[3.0 + I]
- = 3.16228
-
- All of the below evaluate to Infinity:
-
- >> Abs[Infinity] == Abs[I Infinity] == Abs[ComplexInfinity]
- = True
- """
-
- mpmath_name = "fabs" # mpmath actually uses python abs(x) / x.__abs__()
- rules = {
- "Abs[Undefined]": "Undefined",
- }
- summary_text = "absolute value of a number"
- sympy_name = "Abs"
-
- def eval(self, x, evaluation: Evaluation):
- "Abs[x_]"
- result = eval_Abs(x)
- if result is not None:
- return result
- return super(Abs, self).eval(x, evaluation)
-
-
-class Arg(_MPMathFunction):
+class Arg(MPMathFunction):
"""
:Argument (complex analysis):
https://en.wikipedia.org/wiki/Argument_(complex_analysis) (
@@ -255,7 +114,8 @@ class Arg(_MPMathFunction):
'Arg'[$z$] is left unevaluated if $z$ is not a numeric quantity.
'Arg'[$z$] gives the phase angle of $z$ in radians.
The result from 'Arg'[$z$] is always between -Pi and +Pi.
-
'Arg'[$z$] has a branch cut discontinuity in the complex $z$ plane running from -Infinity to 0.
+
'Arg'[$z$] has a branch cut discontinuity in the complex $z$ plane running \
+ from -Infinity to 0.
return -1, 0, or 1 depending on whether $x$ is negative, zero, or positive.
-
-
- >> Sign[19]
- = 1
- >> Sign[-6]
- = -1
- >> Sign[0]
- = 0
- >> Sign[{-5, -10, 15, 20, 0}]
- = {-1, -1, 1, 1, 0}
- #> Sign[{1, 2.3, 4/5, {-6.7, 0}, {8/9, -10}}]
- = {1, 1, 1, {-1, 0}, {1, -1}}
- >> Sign[3 - 4*I]
- = 3 / 5 - 4 I / 5
- #> Sign[1 - 4*I] == (1/17 - 4 I/17) Sqrt[17]
- = True
- #> Sign[4, 5, 6]
- : Sign called with 3 arguments; 1 argument is expected.
- = Sign[4, 5, 6]
- #> Sign["20"]
- = Sign[20]
- """
-
- summary_text = "complex sign of a number"
- sympy_name = "sign"
- # mpmath_name = 'sign'
-
- attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
-
- messages = {
- "argx": "Sign called with `1` arguments; 1 argument is expected.",
- }
-
- rules = {
- "Sign[Power[a_, b_]]": "Power[Sign[a], b]",
- }
-
- def eval(self, x, evaluation: Evaluation):
- "%(name)s[x_]"
- result = eval_Sign(x)
- if result is not None:
- return result
- # return None
-
- sympy_x = x.to_sympy()
- if sympy_x is None:
- return None
- # Unhandled cases. Use sympy
- return super(Sign, self).eval(x, evaluation)
-
- def eval_error(self, x, seqs, evaluation: Evaluation):
- "Sign[x_, seqs__]"
- evaluation.message("Sign", "argx", Integer(len(seqs.get_sequence()) + 1))
+ return (
+ isinstance(expr, (Integer, Rational, Real))
+ or expr.has_form("Underflow", 0)
+ or expr.has_form("Overflow", 0)
+ )
class Sum(IterationFunction, SympyFunction):
@@ -1460,20 +958,6 @@ class Sum(IterationFunction, SympyFunction):
Verify algebraic identities:
>> Sum[x ^ 2, {x, 1, y}] - y * (y + 1) * (2 * y + 1) / 6
= 0
-
- ## >> (-1 + a^n) Sum[a^(k n), {k, 0, m-1}] // Simplify
- ## = -1 + (a ^ n) ^ m # this is what I am getting
- ## = Piecewise[{{m (-1 + a ^ n), a ^ n == 1}, {-1 + (a ^ n) ^ m, True}}]
-
- #> a=Sum[x^k*Sum[y^l,{l,0,4}],{k,0,4}]]
- : "a=Sum[x^k*Sum[y^l,{l,0,4}],{k,0,4}]" cannot be followed by "]" (line 1 of "").
-
- ## Issue #302
- ## The sum should not converge since the first term is 1/0.
- #> Sum[i / Log[i], {i, 1, Infinity}]
- = Sum[i / Log[i], {i, 1, Infinity}]
- #> Sum[Cos[Pi i], {i, 1, Infinity}]
- = Sum[Cos[i Pi], {i, 1, Infinity}]
"""
summary_text = "discrete sum"
diff --git a/mathics/builtin/assignments/assign_binaryop.py b/mathics/builtin/assignments/assign_binaryop.py
index d204bb456..645b56b45 100644
--- a/mathics/builtin/assignments/assign_binaryop.py
+++ b/mathics/builtin/assignments/assign_binaryop.py
@@ -16,8 +16,8 @@
"""
-from mathics.builtin.base import BinaryOperator, PostfixOperator, PrefixOperator
from mathics.core.attributes import A_HOLD_FIRST, A_PROTECTED, A_READ_PROTECTED
+from mathics.core.builtin import BinaryOperator, PostfixOperator, PrefixOperator
class AddTo(BinaryOperator):
diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py
index f960691c5..43f39d775 100644
--- a/mathics/builtin/assignments/assignment.py
+++ b/mathics/builtin/assignments/assignment.py
@@ -4,7 +4,6 @@
"""
-from mathics.builtin.base import BinaryOperator, Builtin
from mathics.core.assignment import (
ASSIGNMENT_FUNCTION_MAP,
AssignmentException,
@@ -18,6 +17,7 @@
A_PROTECTED,
A_SEQUENCE_HOLD,
)
+from mathics.core.builtin import BinaryOperator, Builtin
from mathics.core.symbols import SymbolNull
from mathics.core.systemsymbols import SymbolFailed
from mathics.eval.pymathics import PyMathicsLoadException, eval_LoadModule
@@ -53,7 +53,6 @@ def assign(self, lhs, rhs, evaluation, tags=None, upset=False):
return assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset)
except AssignmentException:
-
return False
@@ -158,8 +157,6 @@ class Set(BinaryOperator, _SetOperator):
>> B[[1;;2, 2;;-1]] = {{t, u}, {y, z}};
>> B
= {{1, t, u}, {4, y, z}, {7, 8, 9}}
-
- #> x = Infinity;
"""
attributes = A_HOLD_FIRST | A_PROTECTED | A_SEQUENCE_HOLD
@@ -276,14 +273,12 @@ class TagSet(Builtin, _SetOperator):
Create an upvalue without using 'UpSet':
- >> x /: f[x] = 2
- = 2
- >> f[x]
- = 2
- >> DownValues[f]
+ >> square /: area[square[s_]] := s^2
+ >> DownValues[square]
= {}
- >> UpValues[x]
- = {HoldPattern[f[x]] :> 2}
+
+ >> UpValues[square]
+ = {HoldPattern[area[square[s_]]] :> s ^ 2}
The symbol $f$ must appear as the ultimate head of $lhs$ or as the head of an element in $lhs$:
>> x /: f[g[x]] = 3;
@@ -371,12 +366,6 @@ class UpSet(BinaryOperator, _SetOperator):
= custom
>> UpValues[r]
= {}
-
- #> f[g, a + b, h] ^= 2
- : Tag Plus in f[g, a + b, h] is Protected.
- = 2
- #> UpValues[h]
- = {HoldPattern[f[g, a + b, h]] :> 2}
"""
attributes = A_HOLD_FIRST | A_PROTECTED | A_SEQUENCE_HOLD
@@ -413,12 +402,6 @@ class UpSetDelayed(UpSet):
= 2
>> UpValues[b]
= {HoldPattern[a[b]] :> x}
-
- #> f[g, a + b, h] ^:= 2
- : Tag Plus in f[g, a + b, h] is Protected.
- #> f[a+b] ^:= 2
- : Tag Plus in f[a + b] is Protected.
- = $Failed
"""
attributes = A_HOLD_ALL | A_PROTECTED | A_SEQUENCE_HOLD
diff --git a/mathics/builtin/assignments/clear.py b/mathics/builtin/assignments/clear.py
index c5640f7da..7ef50cb15 100644
--- a/mathics/builtin/assignments/clear.py
+++ b/mathics/builtin/assignments/clear.py
@@ -4,7 +4,6 @@
"""
-from mathics.builtin.base import Builtin, PostfixOperator
from mathics.core.assignment import is_protected
from mathics.core.atoms import String
from mathics.core.attributes import (
@@ -16,6 +15,7 @@
A_PROTECTED,
A_READ_PROTECTED,
)
+from mathics.core.builtin import Builtin, PostfixOperator
from mathics.core.expression import Expression
from mathics.core.symbols import Atom, Symbol, SymbolNull, symbol_set
from mathics.core.systemsymbols import (
@@ -245,35 +245,6 @@ class Unset(PostfixOperator):
>> a = b = 3;
>> {a, {b}} =.
= {Null, {Null}}
-
- #> x = 2;
- #> OwnValues[x] =.
- #> x
- = x
- #> f[a][b] = 3;
- #> SubValues[f] =.
- #> f[a][b]
- = f[a][b]
- #> PrimeQ[p] ^= True
- = True
- #> PrimeQ[p]
- = True
- #> UpValues[p] =.
- #> PrimeQ[p]
- = False
-
- #> a + b ^= 5;
- #> a =.
- #> a + b
- = 5
- #> {UpValues[a], UpValues[b]} =.
- = {Null, Null}
- #> a + b
- = a + b
-
- #> Unset[Messages[1]]
- : First argument in Messages[1] is not a symbol or a string naming a symbol.
- = $Failed
"""
attributes = A_HOLD_FIRST | A_LISTABLE | A_PROTECTED | A_READ_PROTECTED
diff --git a/mathics/builtin/assignments/types.py b/mathics/builtin/assignments/types.py
index b0188efa7..71ddd557f 100644
--- a/mathics/builtin/assignments/types.py
+++ b/mathics/builtin/assignments/types.py
@@ -11,9 +11,9 @@
"""
-from mathics.builtin.base import Builtin
from mathics.core.assignment import get_symbol_values
from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED
+from mathics.core.builtin import Builtin
class DefaultValues(Builtin):
diff --git a/mathics/builtin/assignments/upvalues.py b/mathics/builtin/assignments/upvalues.py
index ac05393ac..7580aef7f 100644
--- a/mathics/builtin/assignments/upvalues.py
+++ b/mathics/builtin/assignments/upvalues.py
@@ -2,22 +2,22 @@
"""
UpValue-related assignments
-An UpValue is a definition associated with a symbols that does not appear directly its head.
+An UpValue is a definition associated with a symbols that does not appear directly its head.
See
:Associating Definitions with Different Symbols:
https://reference.wolfram.com/language/tutorial/TransformationRulesAndDefinitions.html#6972.
"""
-from mathics.builtin.base import Builtin
from mathics.core.assignment import get_symbol_values
from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED
+from mathics.core.builtin import Builtin
# In Mathematica 5, this appears under "Types of Values".
class UpValues(Builtin):
"""
- :WMA: https://reference.wolfram.com/language/ref/UpValues.html
+ :WMA link: https://reference.wolfram.com/language/ref/UpValues.html
'UpValues[$symbol$]'
gives the list of transformation rules corresponding to upvalues \
diff --git a/mathics/builtin/atomic/atomic.py b/mathics/builtin/atomic/atomic.py
index d86d156fc..9c57460ce 100644
--- a/mathics/builtin/atomic/atomic.py
+++ b/mathics/builtin/atomic/atomic.py
@@ -3,8 +3,8 @@
Atomic Primitives
"""
-from mathics.builtin.base import Builtin, Test
from mathics.core.atoms import Atom
+from mathics.core.builtin import Builtin, Test
class AtomQ(Test):
diff --git a/mathics/builtin/atomic/numbers.py b/mathics/builtin/atomic/numbers.py
index 1c4cd851b..93b6c6807 100644
--- a/mathics/builtin/atomic/numbers.py
+++ b/mathics/builtin/atomic/numbers.py
@@ -22,9 +22,9 @@
import mpmath
import sympy
-from mathics.builtin.base import Builtin, Predefined
from mathics.core.atoms import Integer, Integer0, Integer10, MachineReal, Rational
from mathics.core.attributes import A_LISTABLE, A_PROTECTED
+from mathics.core.builtin import Builtin, Predefined
from mathics.core.convert.python import from_python
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
@@ -45,7 +45,7 @@
SymbolRound,
)
from mathics.eval.nevaluator import eval_N
-from mathics.eval.numbers import eval_Accuracy, eval_Precision
+from mathics.eval.numbers.numbers import eval_Accuracy, eval_Precision
SymbolIntegerDigits = Symbol("IntegerDigits")
SymbolIntegerExponent = Symbol("IntegerExponent")
@@ -103,7 +103,6 @@ def convert_repeating_decimal(numerator, denominator, base):
def convert_float_base(x, base, precision=10):
-
length_of_int = 0 if x == 0 else int(mpmath.log(x, base))
# iexps = list(range(length_of_int, -1, -1))
@@ -153,6 +152,7 @@ class Accuracy(Builtin):
examines the number of significant digits of $expr$ after the \
decimal point in the number x.
+
Notice that the result could be slightly different than the obtained \
in WMA, due to differencs in the internal representation of the real numbers.
@@ -320,9 +320,6 @@ class IntegerLength(Builtin):
'0' is a special case:
>> IntegerLength[0]
= 0
-
- #> IntegerLength /@ (10 ^ Range[100] - 1) == Range[1, 100]
- = True
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -414,39 +411,9 @@ class RealDigits(Builtin):
>> RealDigits[123.45, 10, 18]
= {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Indeterminate, Indeterminate}, 3}
- #> RealDigits[-1.25, -1]
- : Base -1 is not a real number greater than 1.
- = RealDigits[-1.25, -1]
-
Return 25 digits of in base 10:
>> RealDigits[Pi, 10, 25]
= {{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3}, 1}
-
- #> RealDigits[-Pi]
- : The number of digits to return cannot be determined.
- = RealDigits[-Pi]
-
- #> RealDigits[I, 7]
- : The value I is not a real number.
- = RealDigits[I, 7]
-
- #> RealDigits[Pi]
- : The number of digits to return cannot be determined.
- = RealDigits[Pi]
-
- #> RealDigits[3 + 4 I]
- : The value 3 + 4 I is not a real number.
- = RealDigits[3 + 4 I]
-
-
- #> RealDigits[3.14, 10, 1.5]
- : Non-negative machine-sized integer expected at position 3 in RealDigits[3.14, 10, 1.5].
- = RealDigits[3.14, 10, 1.5]
-
- #> RealDigits[3.14, 10, 1, 1.5]
- : Machine-sized integer expected at position 4 in RealDigits[3.14, 10, 1, 1.5].
- = RealDigits[3.14, 10, 1, 1.5]
-
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -662,28 +629,6 @@ class MaxPrecision(Predefined):
>> N[Pi, 11]
: Requested precision 11 is larger than $MaxPrecision. Using current $MaxPrecision of 10. instead. $MaxPrecision = Infinity specifies that any precision should be allowed.
= 3.141592654
-
- #> N[Pi, 10]
- = 3.141592654
-
- #> $MaxPrecision = x
- : Cannot set $MaxPrecision to x; value must be a positive number or Infinity.
- = x
- #> $MaxPrecision = -Infinity
- : Cannot set $MaxPrecision to -Infinity; value must be a positive number or Infinity.
- = -Infinity
- #> $MaxPrecision = 0
- : Cannot set $MaxPrecision to 0; value must be a positive number or Infinity.
- = 0
- #> $MaxPrecision = Infinity;
-
- #> $MinPrecision = 15;
- #> $MaxPrecision = 10
- : Cannot set $MaxPrecision such that $MaxPrecision < $MinPrecision.
- = 10
- #> $MaxPrecision
- = Infinity
- #> $MinPrecision = 0;
"""
is_numeric = False
@@ -765,12 +710,6 @@ class MachinePrecision(Predefined):
= 15.9546
>> N[MachinePrecision, 30]
= 15.9545897701910033463281614204
-
- #> N[E, MachinePrecision]
- = 2.71828
-
- #> Round[MachinePrecision]
- = 16
"""
is_numeric = True
@@ -802,28 +741,6 @@ class MinPrecision(Builtin):
>> N[Pi, 9]
: Requested precision 9 is smaller than $MinPrecision. Using current $MinPrecision of 10. instead.
= 3.141592654
-
- #> N[Pi, 10]
- = 3.141592654
-
- #> $MinPrecision = x
- : Cannot set $MinPrecision to x; value must be a non-negative number.
- = x
- #> $MinPrecision = -Infinity
- : Cannot set $MinPrecision to -Infinity; value must be a non-negative number.
- = -Infinity
- #> $MinPrecision = -1
- : Cannot set $MinPrecision to -1; value must be a non-negative number.
- = -1
- #> $MinPrecision = 0;
-
- #> $MaxPrecision = 10;
- #> $MinPrecision = 15
- : Cannot set $MinPrecision such that $MaxPrecision < $MinPrecision.
- = 15
- #> $MinPrecision
- = 0
- #> $MaxPrecision = Infinity;
"""
messages = {
@@ -844,14 +761,15 @@ class Precision(Builtin):
"""
:Precision:
- https://en.wikipedia.org/wiki/Accuracy_and_precision (
- :WMA:
- https://reference.wolfram.com/language/ref/Precision.html)
+ https://en.wikipedia.org/wiki/Accuracy_and_precision
examines the number of significant digits of $expr$.
+
Note that the result could be slightly different than the obtained \
in WMA, due to differencs in the internal representation of the real numbers.
diff --git a/mathics/builtin/atomic/strings.py b/mathics/builtin/atomic/strings.py
index ebd2052f4..99fcc5d24 100644
--- a/mathics/builtin/atomic/strings.py
+++ b/mathics/builtin/atomic/strings.py
@@ -12,9 +12,9 @@
from mathics_scanner import TranslateError
-from mathics.builtin.base import Builtin, Predefined, PrefixOperator, Test
from mathics.core.atoms import Integer, Integer0, Integer1, String
from mathics.core.attributes import A_LISTABLE, A_PROTECTED
+from mathics.core.builtin import Builtin, Predefined, PrefixOperator, Test
from mathics.core.convert.expression import to_mathics_list
from mathics.core.convert.python import from_bool
from mathics.core.convert.regex import to_regex
@@ -439,10 +439,6 @@ class LetterNumber(Builtin):
>> LetterNumber[{"P", "Pe", "P1", "eck"}]
= {16, {16, 5}, {16, 0}, {5, 3, 11}}
- #> LetterNumber[4]
- : The argument 4 is not a string.
- = LetterNumber[4]
-
>> LetterNumber["\[Beta]", "Greek"]
= 2
@@ -579,7 +575,6 @@ def eval(self, s, evaluation: Evaluation):
class _StringFind(Builtin):
-
options = {
"IgnoreCase": "False",
"MetaCharacters": "None",
@@ -720,63 +715,12 @@ class StringContainsQ(Builtin):
>> StringContainsQ["mathics", "a" ~~ __ ~~ "m"]
= False
- #> StringContainsQ["Hello", "o"]
- = True
-
- #> StringContainsQ["a"]["abcd"]
- = True
-
- #> StringContainsQ["Mathics", "ma", IgnoreCase -> False]
- = False
-
- >> StringContainsQ["Mathics", "MA" , IgnoreCase -> True]
- = True
-
- #> StringContainsQ["", "Empty String"]
- = False
-
- #> StringContainsQ["", ___]
- = True
-
- #> StringContainsQ["Empty Pattern", ""]
- = True
-
- #> StringContainsQ[notastring, "n"]
- : String or list of strings expected at position 1 in StringContainsQ[notastring, n].
- = StringContainsQ[notastring, n]
-
- #> StringContainsQ["Welcome", notapattern]
- : Element notapattern is not a valid string or pattern element in notapattern.
- = StringContainsQ[Welcome, notapattern]
-
>> StringContainsQ[{"g", "a", "laxy", "universe", "sun"}, "u"]
= {False, False, False, True, True}
- #> StringContainsQ[{}, "list of string is empty"]
- = {}
>> StringContainsQ["e" ~~ ___ ~~ "u"] /@ {"The Sun", "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"}
= {True, True, True, False, False, False, False, False, True}
-
- ## special cases, Mathematica allows list of patterns
- #> StringContainsQ[{"A", "Galaxy", "Far", "Far", "Away"}, {"F" ~~ __ ~~ "r", "aw" ~~ ___}]
- = {False, False, True, True, False}
-
- #> StringContainsQ[{"A", "Galaxy", "Far", "Far", "Away"}, {"F" ~~ __ ~~ "r", "aw" ~~ ___}, IgnoreCase -> True]
- = {False, False, True, True, True}
-
- #> StringContainsQ[{"A", "Galaxy", "Far", "Far", "Away"}, {}]
- = {False, False, False, False, False}
-
- #> StringContainsQ[{"A", Galaxy, "Far", "Far", Away}, {"F" ~~ __ ~~ "r", "aw" ~~ ___}]
- : String or list of strings expected at position 1 in StringContainsQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}].
- = StringContainsQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}]
-
- #> StringContainsQ[{"A", "Galaxy", "Far", "Far", "Away"}, {F ~~ __ ~~ "r", aw ~~ ___}]
- : Element F ~~ __ ~~ r is not a valid string or pattern element in {F ~~ __ ~~ r, aw ~~ ___}.
- = StringContainsQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}]
- ## Mathematica can detemine correct invalid element in the pattern, it reports error:
- ## Element F is not a valid string or pattern element in {F ~~ __ ~~ r, aw ~~ ___}.
"""
messages = {
@@ -843,10 +787,6 @@ class StringRepeat(Builtin):
>> StringRepeat["abc", 10, 7]
= abcabca
-
- #> StringRepeat["x", 0]
- : A positive integer is expected at position 2 in StringRepeat[x, 0].
- = StringRepeat[x, 0]
"""
messages = {
@@ -937,17 +877,6 @@ class ToExpression(Builtin):
second-line value.
>> ToExpression["2\[NewLine]3"]
= 3
-
- #> ToExpression["log(x)", InputForm]
- = log x
-
- #> ToExpression["1+"]
- : Incomplete expression; more input is needed (line 1 of "ToExpression['1+']").
- = $Failed
-
- #> ToExpression[]
- : ToExpression called with 0 arguments; between 1 and 3 arguments are expected.
- = ToExpression[]
"""
# TODO: Other forms
@@ -956,8 +885,6 @@ class ToExpression(Builtin):
= Log[x]
>> ToExpression["log(x)", TraditionalForm]
= Log[x]
- #> ToExpression["log(x)", StandardForm]
- = log x
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -1001,7 +928,6 @@ def eval(self, seq, evaluation: Evaluation):
# Apply the different forms
if form is SymbolInputForm:
if isinstance(inp, String):
-
# TODO: turn the below up into a function and call that.
s = inp.value
short_s = s[:15] + "..." if len(s) > 16 else s
diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py
index 417f22c15..7c190e53f 100644
--- a/mathics/builtin/atomic/symbols.py
+++ b/mathics/builtin/atomic/symbols.py
@@ -10,7 +10,6 @@
from mathics_scanner import is_symbol_name
-from mathics.builtin.base import Builtin, PrefixOperator, Test
from mathics.core.assignment import get_symbol_values
from mathics.core.atoms import String
from mathics.core.attributes import (
@@ -22,6 +21,7 @@
A_SEQUENCE_HOLD,
attributes_bitset_to_list,
)
+from mathics.core.builtin import Builtin, PrefixOperator, Test
from mathics.core.convert.expression import to_mathics_list
from mathics.core.convert.regex import to_regex
from mathics.core.evaluation import Evaluation
@@ -95,7 +95,8 @@ def _get_usage_string(symbol, evaluation, is_long_form: bool, htmlout=False):
class Context(Builtin):
r"""
- :WMA: https://reference.wolfram.com/language/ref/Context.html
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Context.html
'Context[$symbol$]'
yields the name of the context where $symbol$ is defined in.
@@ -111,21 +112,6 @@ class Context(Builtin):
>> InputForm[Context[]]
= "Global`"
-
- ## placeholder for general context-related tests
- #> x === Global`x
- = True
- #> `x === Global`x
- = True
- #> a`x === Global`x
- = False
- #> a`x === a`x
- = True
- #> a`x === b`x
- = False
- ## awkward parser cases
- #> FullForm[a`b_]
- = Pattern[a`b, Blank[]]
"""
attributes = A_HOLD_FIRST | A_PROTECTED
@@ -148,7 +134,8 @@ def eval(self, symbol, evaluation):
class Definition(Builtin):
"""
- :WMA: https://reference.wolfram.com/language/ref/Definition.html
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Definition.html
'Definition[$symbol$]'
prints as the definitions given for $symbol$.
@@ -367,14 +354,14 @@ def format_definition_input(self, symbol, evaluation):
# In Mathematica 5, this appears under "Types of Values".
class DownValues(Builtin):
"""
- :WMA: https://reference.wolfram.com/language/ref/DownValues.html
+ :WMA link: https://reference.wolfram.com/language/ref/DownValues.html
'DownValues[$symbol$]'
gives the list of downvalues associated with $symbol$.
'DownValues' uses 'HoldPattern' and 'RuleDelayed' to protect the \
- downvalues from being evaluated. Moreover, it has attribute \
+ downvalues from being evaluated, and it has attribute \
'HoldAll' to get the specified symbol instead of its value.
>> f[x_] := x ^ 2
@@ -423,7 +410,8 @@ def eval(self, symbol, evaluation):
class Information(PrefixOperator):
"""
- :WMA: https://reference.wolfram.com/language/ref/Information.html
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Information.html
'Information[$symbol$]'
Prints information about a $symbol$
@@ -431,25 +419,6 @@ class Information(PrefixOperator):
'Information' does not print information for 'ReadProtected' symbols.
'Information' uses 'InputForm' to format values.
-
- #> a = 2;
- #> Information[a]
- | a = 2
- .
- = Null
-
- #> f[x_] := x ^ 2;
- #> g[f] ^:= 2;
- #> f::usage = "f[x] returns the square of x";
- #> Information[f]
- | f[x] returns the square of x
- .
- . f[x_] = x ^ 2
- .
- . g[f] ^= 2
- .
- = Null
-
"""
attributes = A_HOLD_ALL | A_SEQUENCE_HOLD | A_PROTECTED | A_READ_PROTECTED
@@ -596,7 +565,8 @@ def format_definition_input(self, symbol, evaluation: Evaluation, options: dict)
class Names(Builtin):
"""
- :WMA: https://reference.wolfram.com/language/ref/Names.html
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Names.html
'Names["$pattern$"]'
returns the list of names matching $pattern$.
@@ -620,9 +590,6 @@ class Names(Builtin):
The number of built-in symbols:
>> Length[Names["System`*"]]
= ...
-
- #> Length[Names["System`*"]] > 350
- = True
"""
summary_text = "find a list of symbols with names matching a pattern"
@@ -651,7 +618,8 @@ def eval(self, pattern, evaluation):
# In Mathematica 5, this appears under "Types of Values".
class OwnValues(Builtin):
"""
- :WMA: https://reference.wolfram.com/language/ref/OwnValues.html
+ :WMA link:
+ https://reference.wolfram.com/language/ref/OwnValues.html
'OwnValues[$symbol$]'
gives the list of ownvalue associated with $symbol$.
@@ -684,7 +652,8 @@ def eval(self, symbol, evaluation):
class Symbol_(Builtin):
"""
- :WMA: https://reference.wolfram.com/language/ref/Symbol.html
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Symbol.html
'Symbol'
is the head of symbols.
@@ -695,9 +664,6 @@ class Symbol_(Builtin):
You can use 'Symbol' to create symbols from strings:
>> Symbol["x"] + Symbol["x"]
= 2 x
-
- #> {\\[Eta], \\[CapitalGamma]\\[Beta], Z\\[Infinity], \\[Angle]XYZ, \\[FilledSquare]r, i\\[Ellipsis]j}
- = {\u03b7, \u0393\u03b2, Z\u221e, \u2220XYZ, \u25a0r, i\u2026j}
"""
attributes = A_LOCKED | A_PROTECTED
@@ -726,7 +692,8 @@ def eval(self, string, evaluation):
class SymbolName(Builtin):
"""
- :WMA: https://reference.wolfram.com/language/ref/SymbolName.html
+ :WMA link:
+ https://reference.wolfram.com/language/ref/SymbolName.html
'SymbolName[$s$]'
returns the name of the symbol $s$ (without any leading \
@@ -735,9 +702,6 @@ class SymbolName(Builtin):
>> SymbolName[x] // InputForm
= "x"
-
- #> SymbolName[a`b`x] // InputForm
- = "x"
"""
summary_text = "give the name of a symbol as a string"
@@ -752,7 +716,8 @@ def eval(self, symbol, evaluation):
class SymbolQ(Test):
"""
- :WMA: https://reference.wolfram.com/language/ref/SymbolName.html
+ :WMA link:
+ https://reference.wolfram.com/language/ref/SymbolName.html
'SymbolQ[$x$]'
is 'True' if $x$ is a symbol, or 'False' otherwise.
@@ -774,7 +739,8 @@ def test(self, expr) -> bool:
class ValueQ(Builtin):
"""
- :WMA: https://reference.wolfram.com/language/ref/ValueQ.html
+ :WMA link:
+ https://reference.wolfram.com/language/ref/ValueQ.html
represents a color with the specified red, green and blue
- components.
+
represents a color with the specified red, green and blue \
+ components. These values should be a number between 0 and 1. \
+ Unless specified using the form below or using
+ :Opacity:
+ /doc/reference-of-built-in-symbols/colors/color-directives/opacity,\
+ default opacity is 1, a solid opaque color.
+
+
'RGBColor[$r$, $g$, $b$, $a$]'
+
Same as above but an opacity value is specified. $a$ must have \
+ value between 0 and 1. \
+ 'RGBColor[$r$,$g$,$b$,$a$]' is equivalent to '{RGBColor[$r$,$g$,$b$],Opacity[$a$]}.'
- >> Graphics[MapIndexed[{RGBColor @@ #1, Disk[2*#2 ~Join~ {0}]} &, IdentityMatrix[3]], ImageSize->Small]
- = -Graphics-
+ A swatch of color green:
>> RGBColor[0, 1, 0]
= RGBColor[0, 1, 0]
+ Let's show what goes on in the process of boxing the above to make this display:
+
>> RGBColor[0, 1, 0] // ToBoxes
= StyleBox[GraphicsBox[...], ...]
+
+ A swatch of color green which is 1/8 opaque:
+ >> RGBColor[0, 1, 0, 0.125]
+ = RGBColor[0, 1, 0, 0.125]
+
+ A series of small disks of the primary colors:
+
+ >> Graphics[MapIndexed[{RGBColor @@ #1, Disk[2*#2 ~Join~ {0}]} &, IdentityMatrix[3]], ImageSize->Small]
+ = -Graphics-
+
"""
color_space = "RGB"
diff --git a/mathics/builtin/colors/color_operations.py b/mathics/builtin/colors/color_operations.py
index 3fced32b8..76c374025 100644
--- a/mathics/builtin/colors/color_operations.py
+++ b/mathics/builtin/colors/color_operations.py
@@ -9,11 +9,11 @@
import itertools
from math import floor
-from mathics.builtin.base import Builtin
from mathics.builtin.colors.color_directives import ColorError, RGBColor, _ColorObject
from mathics.builtin.colors.color_internals import convert_color
from mathics.builtin.image.base import Image
from mathics.core.atoms import Integer, MachineReal, Rational, Real, String
+from mathics.core.builtin import Builtin
from mathics.core.convert.expression import to_expression, to_mathics_list
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
@@ -205,7 +205,7 @@ def eval(self, input, colorspace, evaluation: Evaluation):
class ColorNegate(Builtin):
"""
Color Inversion (
- :WMA:
+ :WMA link:
https://reference.wolfram.com/language/ref/ColorNegate.html)
@@ -454,7 +454,7 @@ def result():
yield to_expression(
Symbol(out_palette_head),
*prototype,
- elements_conversion_fn=MachineReal
+ elements_conversion_fn=MachineReal,
)
return to_mathics_list(*itertools.islice(result(), 0, at_most))
diff --git a/mathics/builtin/colors/named_colors.py b/mathics/builtin/colors/named_colors.py
index cd5008c0e..309053290 100644
--- a/mathics/builtin/colors/named_colors.py
+++ b/mathics/builtin/colors/named_colors.py
@@ -4,7 +4,7 @@
Mathics has definitions for the most common color names which can be used in a graphics or style specification.
"""
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
from mathics.core.symbols import strip_context
diff --git a/mathics/builtin/compilation.py b/mathics/builtin/compilation.py
index 7b33f5437..c25fa7c6b 100644
--- a/mathics/builtin/compilation.py
+++ b/mathics/builtin/compilation.py
@@ -10,10 +10,10 @@
import ctypes
from types import FunctionType
-from mathics.builtin.base import Builtin
from mathics.builtin.box.compilation import CompiledCodeBox
from mathics.core.atoms import Integer, String
from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED
+from mathics.core.builtin import Builtin
from mathics.core.convert.expression import to_mathics_list
from mathics.core.convert.function import (
CompileDuplicateArgName,
@@ -57,32 +57,12 @@ class Compile(Builtin):
= CompiledFunction[{x}, Sin[x], -CompiledCode-]
>> cf[1.4]
= 0.98545
- #> cf[1/2]
- = 0.479426
- #> cf[4]
- = -0.756802
- #> cf[x]
- : Invalid argument x should be Integer, Real or boolean.
- = CompiledFunction[{x}, Sin[x], -CompiledCode-][x]
- #> cf = Compile[{{x, _Real}, {x, _Integer}}, Sin[x + y]]
- : Duplicate parameter x found in {{x, _Real}, {x, _Integer}}.
- = Compile[{{x, _Real}, {x, _Integer}}, Sin[x + y]]
- #> cf = Compile[{{x, _Real}, {y, _Integer}}, Sin[x + z]]
- = CompiledFunction[{x, y}, Sin[x + z], -PythonizedCode-]
- #> cf = Compile[{{x, _Real}, {y, _Integer}}, Sin[x + y]]
- = CompiledFunction[{x, y}, Sin[x + y], -CompiledCode-]
- #> cf[1, 2]
- = 0.14112
- #> cf[x + y]
- = CompiledFunction[{x, y}, Sin[x + y], -CompiledCode-][x + y]
Compile supports basic flow control:
>> cf = Compile[{{x, _Real}, {y, _Integer}}, If[x == 0.0 && y <= 0, 0.0, Sin[x ^ y] + 1 / Min[x, 0.5]] + 0.5]
= CompiledFunction[{x, y}, ..., -CompiledCode-]
>> cf[3.5, 2]
= 2.18888
- #> cf[0, -2]
- = 0.5
Loops and variable assignments are supported usinv Python builtin "compile" function:
>> Compile[{{a, _Integer}, {b, _Integer}}, While[b != 0, {a, b} = {b, Mod[a, b]}]; a] (* GCD of a, b *)
diff --git a/mathics/builtin/compress.py b/mathics/builtin/compress.py
index 340c2d3ae..1385a4193 100644
--- a/mathics/builtin/compress.py
+++ b/mathics/builtin/compress.py
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
-
+"""
+Compress Functions
+"""
import base64
import zlib
-from mathics.builtin.base import Builtin
from mathics.core.atoms import String
+from mathics.core.builtin import Builtin
from mathics.core.evaluation import Evaluation
diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py
index 0ff8f0e38..0f31fef91 100644
--- a/mathics/builtin/datentime.py
+++ b/mathics/builtin/datentime.py
@@ -16,7 +16,6 @@
import dateutil.parser
-from mathics.builtin.base import Builtin, Predefined
from mathics.core.atoms import Integer, Real, String
from mathics.core.attributes import (
A_HOLD_ALL,
@@ -24,6 +23,7 @@
A_PROTECTED,
A_READ_PROTECTED,
)
+from mathics.core.builtin import Builtin, Predefined
from mathics.core.convert.expression import to_expression, to_mathics_list
from mathics.core.convert.python import from_python
from mathics.core.element import ImmutableValueMixin
@@ -375,10 +375,6 @@ class AbsoluteTime(_DateFormat):
>> AbsoluteTime[{"6-6-91", {"Day", "Month", "YearShort"}}]
= 2885155200
-
- ## Mathematica Bug - Mathics gets it right
- #> AbsoluteTime[1000]
- = 1000
"""
summary_text = "get absolute time in seconds"
@@ -834,10 +830,6 @@ class DateList(_DateFormat):
: The interpretation of 1/10/1991 is ambiguous.
= {1991, 1, 10, 0, 0, 0.}
- #> DateList["7/8/9"]
- : The interpretation of 7/8/9 is ambiguous.
- = {2009, 7, 8, 0, 0, 0.}
-
>> DateList[{"31/10/91", {"Day", "Month", "YearShort"}}]
= {1991, 10, 31, 0, 0, 0.}
@@ -912,22 +904,6 @@ class DateString(_DateFormat):
Non-integer values are accepted too:
>> DateString[{1991, 6, 6.5}]
= Thu 6 Jun 1991 12:00:00
-
- ## Check Leading 0
- #> DateString[{1979, 3, 14}, {"DayName", " ", "MonthShort", "-", "YearShort"}]
- = Wednesday 3-79
-
- #> DateString[{"DayName", " ", "Month", "/", "YearShort"}]
- = ...
-
- ## Assumed separators
- #> DateString[{"06/06/1991", {"Month", "Day", "Year"}}]
- = Thu 6 Jun 1991 00:00:00
-
- ## Specified separators
- #> DateString[{"06/06/1991", {"Month", "/", "Day", "/", "Year"}}]
- = Thu 6 Jun 1991 00:00:00
-
"""
attributes = A_READ_PROTECTED | A_PROTECTED
diff --git a/mathics/builtin/directories/directory_names.py b/mathics/builtin/directories/directory_names.py
index 893f8561f..21129c10a 100644
--- a/mathics/builtin/directories/directory_names.py
+++ b/mathics/builtin/directories/directory_names.py
@@ -5,8 +5,8 @@
import os
import os.path as osp
-from mathics.builtin.base import Builtin
from mathics.core.atoms import String
+from mathics.core.builtin import Builtin
from mathics.core.convert.expression import to_expression
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
@@ -30,23 +30,6 @@ class DirectoryName(Builtin):
>> DirectoryName["a/b/c", 2]
= a
-
- #> DirectoryName["a/b/c", 3] // InputForm
- = ""
- #> DirectoryName[""] // InputForm
- = ""
-
- #> DirectoryName["a/b/c", x]
- : Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, x].
- = DirectoryName[a/b/c, x]
-
- #> DirectoryName["a/b/c", -1]
- : Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, -1].
- = DirectoryName[a/b/c, -1]
-
- #> DirectoryName[x]
- : String expected at position 1 in DirectoryName[x].
- = DirectoryName[x]
"""
messages = {
@@ -104,12 +87,6 @@ class DirectoryQ(Builtin):
= True
>> DirectoryQ["ExampleData/MythicalSubdir/"]
= False
-
- #> DirectoryQ["ExampleData"]
- = True
-
- #> DirectoryQ["ExampleData/MythicalSubdir/NestedDir/"]
- = False
"""
messages = {
@@ -150,12 +127,6 @@ class FileNameDepth(Builtin):
>> FileNameDepth["a/b/c/"]
= 3
-
- #> FileNameDepth[x]
- = FileNameDepth[x]
-
- #> FileNameDepth[$RootDirectory]
- = 0
"""
options = {
@@ -254,10 +225,6 @@ class FileNameSplit(Builtin):
>> FileNameSplit["example/path/file.txt"]
= {example, path, file.txt}
-
- #> FileNameSplit["example/path", OperatingSystem -> x]
- : The value of option OperatingSystem -> x must be one of "MacOSX", "Windows", or "Unix".
- = {example, path}
"""
messages = {
diff --git a/mathics/builtin/directories/directory_operations.py b/mathics/builtin/directories/directory_operations.py
index 7d8ab1762..ce27bf5bf 100644
--- a/mathics/builtin/directories/directory_operations.py
+++ b/mathics/builtin/directories/directory_operations.py
@@ -7,9 +7,9 @@
import shutil
import tempfile
-from mathics.builtin.base import Builtin
from mathics.core.atoms import String
from mathics.core.attributes import A_LISTABLE, A_PROTECTED
+from mathics.core.builtin import Builtin
from mathics.core.convert.expression import to_expression
from mathics.core.evaluation import Evaluation
from mathics.core.symbols import SymbolNull
diff --git a/mathics/builtin/directories/system_directories.py b/mathics/builtin/directories/system_directories.py
index 185a2509a..15b0e5352 100644
--- a/mathics/builtin/directories/system_directories.py
+++ b/mathics/builtin/directories/system_directories.py
@@ -2,9 +2,9 @@
System File Directories
"""
-from mathics.builtin.base import Predefined
from mathics.core.atoms import String
from mathics.core.attributes import A_NO_ATTRIBUTES
+from mathics.core.builtin import Predefined
from mathics.core.evaluation import Evaluation
from mathics.core.streams import ROOT_DIR
from mathics.eval.directories import INITIAL_DIR, SYS_ROOT_DIR, TMP_DIR
diff --git a/mathics/builtin/directories/user_directories.py b/mathics/builtin/directories/user_directories.py
index 0a9e86bef..03281c43a 100644
--- a/mathics/builtin/directories/user_directories.py
+++ b/mathics/builtin/directories/user_directories.py
@@ -4,9 +4,9 @@
import os
-from mathics.builtin.base import Predefined
from mathics.core.atoms import String
from mathics.core.attributes import A_NO_ATTRIBUTES
+from mathics.core.builtin import Predefined
from mathics.core.convert.expression import to_mathics_list
from mathics.core.evaluation import Evaluation
from mathics.core.streams import HOME_DIR, PATH_VAR
diff --git a/mathics/builtin/distance/clusters.py b/mathics/builtin/distance/clusters.py
index 37771f7f5..382fce982 100644
--- a/mathics/builtin/distance/clusters.py
+++ b/mathics/builtin/distance/clusters.py
@@ -13,9 +13,9 @@
kmeans,
optimize,
)
-from mathics.builtin.base import Builtin
from mathics.builtin.options import options_to_rules
from mathics.core.atoms import FP_MANTISA_BINARY_DIGITS, Integer, Real, String, min_prec
+from mathics.core.builtin import Builtin
from mathics.core.convert.expression import to_mathics_list
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
@@ -35,6 +35,7 @@
)
from mathics.eval.nevaluator import eval_N
from mathics.eval.parts import walk_levels
+from mathics.eval.tensors import get_default_distance
class _LazyDistances(LazyDistances):
@@ -139,8 +140,6 @@ def _cluster(self, p, k, mode, evaluation, options, expr):
options, "DistanceFunction", evaluation
)
if distance_function_string == "Automatic":
- from mathics.builtin.tensors import get_default_distance
-
distance_function = get_default_distance(dist_p)
if distance_function is None:
name_of_builtin = strip_context(self.get_name())
@@ -462,8 +461,6 @@ def eval(
options, "DistanceFunction", evaluation
)
if distance_function_string == "Automatic":
- from mathics.builtin.tensors import get_default_distance
-
distance_function = get_default_distance(dist_p)
if distance_function is None:
evaluation.message(
diff --git a/mathics/builtin/distance/numeric.py b/mathics/builtin/distance/numeric.py
index c5d78d968..04d22270c 100644
--- a/mathics/builtin/distance/numeric.py
+++ b/mathics/builtin/distance/numeric.py
@@ -2,8 +2,8 @@
Numerical Data
"""
-from mathics.builtin.base import Builtin
from mathics.core.atoms import Integer1, Integer2
+from mathics.core.builtin import Builtin
from mathics.core.expression import Evaluation, Expression
from mathics.core.symbols import (
SymbolAbs,
diff --git a/mathics/builtin/distance/stringdata.py b/mathics/builtin/distance/stringdata.py
index cb9562f06..e4099c6b5 100644
--- a/mathics/builtin/distance/stringdata.py
+++ b/mathics/builtin/distance/stringdata.py
@@ -6,8 +6,8 @@
import unicodedata
from typing import Callable
-from mathics.builtin.base import Builtin
from mathics.core.atoms import Integer, String, Symbol
+from mathics.core.builtin import Builtin
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.symbols import SymbolTrue
diff --git a/mathics/builtin/drawing/drawing_options.py b/mathics/builtin/drawing/drawing_options.py
index 5b88f680c..464bd2417 100644
--- a/mathics/builtin/drawing/drawing_options.py
+++ b/mathics/builtin/drawing/drawing_options.py
@@ -15,7 +15,7 @@
# builtins.
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
# This tells documentation how to sort this module
sort_order = "mathics.builtin.graphing-and-drawing.drawing-options-and-option-values"
@@ -78,6 +78,32 @@ class Axis(Builtin):
summary_text = "graph option value to fill plot from curve to the axis"
+class Background(Builtin):
+ """
+ :WMA link:https://reference.wolfram.com/language/ref/Background.html
+
+
+
'Background'
+
is an option that specifies the color of the background.
+
+
+ The specification must be a Color specification or 'Automatic':
+
+ >> Graphics3D[{Arrow[{{0,0,0},{1,0,1},{0,-1,0},{1,1,1}}]}, Background -> Red]
+ = -Graphics3D-
+
+ Notice that opacity cannot be specified by passing a 'List' containing 'Opacity' \
+ together with a color specification like '{Red, Opacity[.1]}'. Use a color \
+ directive with an alpha channel instead:
+
+ >> Plot[{Sin[x], Cos[x], x / 3}, {x, -Pi, Pi}, Background -> RGBColor[0.5, .5, .5, 0.1]]
+ = -Graphics-
+
+ """
+
+ summary_text = "graphic option for the color of the background"
+
+
class Bottom(Builtin):
"""
:WMA link:https://reference.wolfram.com/language/ref/Bottom.html
diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py
index f50ac9bae..6a6b7c4c3 100644
--- a/mathics/builtin/drawing/graphics3d.py
+++ b/mathics/builtin/drawing/graphics3d.py
@@ -5,7 +5,6 @@
Functions for working with 3D graphics.
"""
-from mathics.builtin.base import Builtin
from mathics.builtin.colors.color_directives import RGBColor
from mathics.builtin.graphics import (
CoordinatesError,
@@ -14,6 +13,7 @@
_GraphicsElements,
)
from mathics.core.atoms import Integer, Rational, Real
+from mathics.core.builtin import Builtin
from mathics.core.expression import Evaluation, Expression
from mathics.core.symbols import SymbolN
from mathics.eval.nevaluator import eval_N
@@ -77,6 +77,10 @@ class Graphics3D(Graphics):
>> Graphics3D[Polygon[{{0,0,0}, {0,1,1}, {1,0,0}}]]
= -Graphics3D-
+ The 'Background' option allows to set the color of the background:
+ >> Graphics3D[Sphere[], Background->RGBColor[.6, .7, 1.]]
+ = -Graphics3D-
+
In 'TeXForm', 'Graphics3D' creates Asymptote figures:
>> Graphics3D[Sphere[]] // TeXForm
= #<--#
@@ -101,30 +105,6 @@ class Graphics3D(Graphics):
. draw(((-1,1,-1)--(-1,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. draw(((1,1,-1)--(1,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
. \end{asy}
-
- #> Graphics3D[Point[Table[{Sin[t], Cos[t], 0}, {t, 0, 2. Pi, Pi / 15.}]]] // TeXForm
- = #<--#
- . \begin{asy}
- . import three;
- . import solids;
- . size(6.6667cm, 6.6667cm);
- . currentprojection=perspective(2.6,-4.8,4.0);
- . currentlight=light(rgb(0.5,0.5,1), specular=red, (2,0,2), (2,2,2), (0,2,2));
- . // Point3DBox
- . path3 g=(0,1,0)--(0.20791,0.97815,0)--(0.40674,0.91355,0)--(0.58779,0.80902,0)--(0.74314,0.66913,0)--(0.86603,0.5,0)--(0.95106,0.30902,0)--(0.99452,0.10453,0)--(0.99452,-0.10453,0)--(0.95106,-0.30902,0)--(0.86603,-0.5,0)--(0.74314,-0.66913,0)--(0.58779,-0.80902,0)--(0.40674,-0.91355,0)--(0.20791,-0.97815,0)--(5.6655e-16,-1,0)--(-0.20791,-0.97815,0)--(-0.40674,-0.91355,0)--(-0.58779,-0.80902,0)--(-0.74314,-0.66913,0)--(-0.86603,-0.5,0)--(-0.95106,-0.30902,0)--(-0.99452,-0.10453,0)--(-0.99452,0.10453,0)--(-0.95106,0.30902,0)--(-0.86603,0.5,0)--(-0.74314,0.66913,0)--(-0.58779,0.80902,0)--(-0.40674,0.91355,0)--(-0.20791,0.97815,0)--(1.5314e-15,1,0)--cycle;dot(g, rgb(0, 0, 0));
- . draw(((-0.99452,-1,-1)--(0.99452,-1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . draw(((-0.99452,1,-1)--(0.99452,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . draw(((-0.99452,-1,1)--(0.99452,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . draw(((-0.99452,1,1)--(0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . draw(((-0.99452,-1,-1)--(-0.99452,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . draw(((0.99452,-1,-1)--(0.99452,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . draw(((-0.99452,-1,1)--(-0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . draw(((0.99452,-1,1)--(0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . draw(((-0.99452,-1,-1)--(-0.99452,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . draw(((0.99452,-1,-1)--(0.99452,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . draw(((-0.99452,1,-1)--(-0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . draw(((0.99452,1,-1)--(0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));
- . \end{asy}
"""
summary_text = "a three-dimensional graphics image wrapper"
options = Graphics.options.copy()
diff --git a/mathics/builtin/drawing/graphics_internals.py b/mathics/builtin/drawing/graphics_internals.py
index 3f2c009d6..3dcceff7a 100644
--- a/mathics/builtin/drawing/graphics_internals.py
+++ b/mathics/builtin/drawing/graphics_internals.py
@@ -4,8 +4,8 @@
# Also no docstring which may confuse the doc system
-from mathics.builtin.base import BuiltinElement
from mathics.builtin.box.expression import BoxExpression
+from mathics.core.builtin import BuiltinElement
from mathics.core.exceptions import BoxExpressionError
from mathics.core.symbols import Symbol, system_symbols_dict
diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py
index 87789651c..9d3c796b3 100644
--- a/mathics/builtin/drawing/plot.py
+++ b/mathics/builtin/drawing/plot.py
@@ -14,12 +14,12 @@
import palettable
-from mathics.builtin.base import Builtin
from mathics.builtin.drawing.graphics3d import Graphics3D
from mathics.builtin.graphics import Graphics
from mathics.builtin.options import options_to_rules
from mathics.core.atoms import Integer, Integer0, Integer1, MachineReal, Real, String
from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED, A_READ_PROTECTED
+from mathics.core.builtin import Builtin
from mathics.core.convert.expression import to_expression, to_mathics_list
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
@@ -383,7 +383,6 @@ def colors(self):
class _Plot(Builtin):
-
attributes = A_HOLD_ALL | A_PROTECTED | A_READ_PROTECTED
expect_list = False
@@ -565,7 +564,6 @@ def get_plotrange(self, plotrange, start, stop):
def process_function_and_options(
self, functions, x, start, stop, evaluation: Evaluation, options: dict
) -> tuple:
-
if isinstance(functions, Symbol) and functions.name is not x.get_name():
rules = evaluation.definitions.get_ownvalues(functions.name)
for rule in rules:
@@ -656,7 +654,7 @@ def eval(
functions,
xexpr_limits,
yexpr_limits,
- *options_to_rules(options)
+ *options_to_rules(options),
)
functions = self.get_functions_param(functions)
@@ -1503,7 +1501,7 @@ def final_graphics(self, graphics, options):
return Expression(
SymbolGraphics,
ListExpression(*graphics),
- *options_to_rules(options, Graphics.options)
+ *options_to_rules(options, Graphics.options),
)
@@ -1938,7 +1936,7 @@ def auto_bins():
return Expression(
SymbolGraphics,
ListExpression(*graphics),
- *options_to_rules(options, Graphics.options)
+ *options_to_rules(options, Graphics.options),
)
@@ -2383,21 +2381,6 @@ class Plot(_Plot):
A constant function:
>> Plot[3, {x, 0, 1}]
= -Graphics-
-
- #> Plot[1 / x, {x, -1, 1}]
- = -Graphics-
- #> Plot[x, {y, 0, 2}]
- = -Graphics-
-
- #> Plot[{f[x],-49x/12+433/108},{x,-6,6}, PlotRange->{-10,10}, AspectRatio->{1}]
- = -Graphics-
-
- #> Plot[Sin[t], {t, 0, 2 Pi}, PlotPoints -> 1]
- : Value of option PlotPoints -> 1 is not an integer >= 2.
- = Plot[Sin[t], {t, 0, 2 Pi}, PlotPoints -> 1]
-
- #> Plot[x*y, {x, -1, 1}]
- = -Graphics-
"""
summary_text = "plot curves of one or more functions"
@@ -2583,37 +2566,8 @@ class Plot3D(_Plot3D):
>> Plot3D[Log[x + y^2], {x, -1, 1}, {y, -1, 1}]
= -Graphics3D-
-
- #> Plot3D[z, {x, 1, 20}, {y, 1, 10}]
- = -Graphics3D-
-
- ## MaxRecursion Option
- #> Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> 0]
- = -Graphics3D-
- #> Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> 15]
- = -Graphics3D-
- #> Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> 16]
- : MaxRecursion must be a non-negative integer; the recursion value is limited to 15. Using MaxRecursion -> 15.
- = -Graphics3D-
- #> Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> -1]
- : MaxRecursion must be a non-negative integer; the recursion value is limited to 15. Using MaxRecursion -> 0.
- = -Graphics3D-
- #> Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> a]
- : MaxRecursion must be a non-negative integer; the recursion value is limited to 15. Using MaxRecursion -> 0.
- = -Graphics3D-
- #> Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> Infinity]
- : MaxRecursion must be a non-negative integer; the recursion value is limited to 15. Using MaxRecursion -> 15.
- = -Graphics3D-
-
- #> Plot3D[x ^ 2 + 1 / y, {x, -1, 1}, {y, 1, z}]
- : Limiting value z in {y, 1, z} is not a machine-size real number.
- = Plot3D[x ^ 2 + 1 / y, {x, -1, 1}, {y, 1, z}]
"""
- # FIXME: This test passes but the result is 511 lines long !
- """
- #> Plot3D[x + 2y, {x, -2, 2}, {y, -2, 2}] // TeXForm
- """
attributes = A_HOLD_ALL | A_PROTECTED
options = Graphics.options.copy()
@@ -2668,5 +2622,5 @@ def final_graphics(self, graphics, options: dict):
return Expression(
SymbolGraphics3D,
ListExpression(*graphics),
- *options_to_rules(options, Graphics3D.options)
+ *options_to_rules(options, Graphics3D.options),
)
diff --git a/mathics/builtin/drawing/splines.py b/mathics/builtin/drawing/splines.py
index 5f98f7614..99281cac6 100644
--- a/mathics/builtin/drawing/splines.py
+++ b/mathics/builtin/drawing/splines.py
@@ -9,8 +9,8 @@
# Here we are also hiding "drawing" since this can erroneously appear at the top level.
sort_order = "mathics.builtin.splines"
-from mathics.builtin.base import Builtin
from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED
+from mathics.core.builtin import Builtin
# For a more generic implementation in Python using scipy,
diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py
index 03f6d10e9..fafa36b37 100644
--- a/mathics/builtin/drawing/uniform_polyhedra.py
+++ b/mathics/builtin/drawing/uniform_polyhedra.py
@@ -11,7 +11,7 @@
# Here we are also hiding "drawing" since this can erroneously appear at the top level.
sort_order = "mathics.builtin.uniform-polyhedra"
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
from mathics.core.evaluation import Evaluation
uniform_polyhedra_names = "tetrahedron, octahedron, dodecahedron, icosahedron"
diff --git a/mathics/builtin/evaluation.py b/mathics/builtin/evaluation.py
index 0e743586b..9ac176fef 100644
--- a/mathics/builtin/evaluation.py
+++ b/mathics/builtin/evaluation.py
@@ -1,14 +1,16 @@
# -*- coding: utf-8 -*-
+"""Evaluation Control
-from mathics.builtin.base import Builtin, Predefined
+Mathics3 takes an expression that it is given, and evaluates it. Built \
+into the evaluation are primitives that allow finer control over the \
+process of evaluation in cases where it is needed.
+"""
+
from mathics.core.atoms import Integer
from mathics.core.attributes import A_HOLD_ALL, A_HOLD_ALL_COMPLETE, A_PROTECTED
-from mathics.core.evaluation import (
- MAX_RECURSION_DEPTH,
- Evaluation,
- set_python_recursion_limit,
-)
+from mathics.core.builtin import Builtin, Predefined
+from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit
class RecursionLimit(Predefined):
@@ -38,28 +40,6 @@ class RecursionLimit(Predefined):
>> a = a + a
: Recursion depth of 512 exceeded.
= $Aborted
-
- #> $RecursionLimit = 20
- = 20
- #> a = a + a
- : Recursion depth of 20 exceeded.
- = $Aborted
-
- #> $RecursionLimit = 200
- = 200
-
- #> ClearAll[f];
- #> f[x_, 0] := x; f[x_, n_] := f[x + 1, n - 1];
- #> Block[{$RecursionLimit = 20}, f[0, 100]]
- = 100
- #> ClearAll[f];
-
- #> ClearAll[f];
- #> f[x_, 0] := x; f[x_, n_] := Module[{y = x + 1}, f[y, n - 1]];
- #> Block[{$RecursionLimit = 20}, f[0, 100]]
- : Recursion depth of 20 exceeded.
- = $Aborted
- #> ClearAll[f];
"""
name = "$RecursionLimit"
@@ -105,28 +85,6 @@ class IterationLimit(Predefined):
> $IterationLimit
= 1000
- #> ClearAll[f]; f[x_] := f[x + 1];
- #> f[x]
- : Iteration limit of 1000 exceeded.
- = $Aborted
- #> ClearAll[f];
-
- #> $IterationLimit = x;
- : Cannot set $IterationLimit to x; value must be an integer between 20 and Infinity.
-
- #> ClearAll[f];
- #> f[x_, 0] := x; f[x_, n_] := f[x + 1, n - 1];
- #> Block[{$IterationLimit = 20}, f[0, 100]]
- : Iteration limit of 20 exceeded.
- = $Aborted
- #> ClearAll[f];
-
- # FIX Later
- # #> ClearAll[f];
- # #> f[x_, 0] := x; f[x_, n_] := Module[{y = x + 1}, f[y, n - 1]];
- # #> Block[{$IterationLimit = 20}, f[0, 100]]
- # = 100
- # #> ClearAll[f];
"""
name = "$IterationLimit"
@@ -280,10 +238,6 @@ class Unevaluated(Builtin):
>> g[Unevaluated[Sequence[a, b, c]]]
= g[Unevaluated[Sequence[a, b, c]]]
- #> Attributes[h] = Flat;
- #> h[items___] := Plus[items]
- #> h[1, Unevaluated[Sequence[Unevaluated[2], 3]], Sequence[4, Unevaluated[5]]]
- = 15
"""
attributes = A_HOLD_ALL_COMPLETE | A_PROTECTED
@@ -351,38 +305,3 @@ class Sequence(Builtin):
summary_text = (
"a sequence of arguments that will automatically be spliced into any function"
)
-
-
-class Quit(Builtin):
- """
- :WMA link:https://reference.wolfram.com/language/ref/Quit.html
-
-
-
'Quit'[]
-
Terminates the Mathics session.
-
-
'Quit[$n$]'
-
Terminates the mathics session with exit code $n$.
-
-
-
-
'Exit'[]
-
Terminates the Mathics session.
-
-
'Exit[$n$]'
-
Terminates the mathics session with exit code $n$.
-
-
- """
-
- rules = {
- "Exit[n___]": "Quit[n]",
- }
- summary_text = "terminate the session"
-
- def eval(self, evaluation: Evaluation, n):
- "%(name)s[n___]"
- exitcode = 0
- if isinstance(n, Integer):
- exitcode = n.get_int_value()
- raise SystemExit(exitcode)
diff --git a/mathics/builtin/exp_structure/general.py b/mathics/builtin/exp_structure/general.py
index f2ae816a3..f18663edd 100644
--- a/mathics/builtin/exp_structure/general.py
+++ b/mathics/builtin/exp_structure/general.py
@@ -3,8 +3,8 @@
General Structural Expression Functions
"""
-from mathics.builtin.base import BinaryOperator, Builtin, Predefined
from mathics.core.atoms import Integer, Integer0, Integer1, Rational
+from mathics.core.builtin import BinaryOperator, Builtin, Predefined
from mathics.core.exceptions import InvalidLevelspecError
from mathics.core.expression import Evaluation, Expression
from mathics.core.list import ListExpression
@@ -364,10 +364,6 @@ class Operate(Builtin):
With $n$=0, 'Operate' acts like 'Apply':
>> Operate[p, f[a][b][c], 0]
= p[f[a][b][c]]
-
- #> Operate[p, f, -1]
- : Non-negative integer expected at position 3 in Operate[p, f, -1].
- = Operate[p, f, -1]
"""
summary_text = "apply a function to the head of an expression"
diff --git a/mathics/builtin/exp_structure/size_and_sig.py b/mathics/builtin/exp_structure/size_and_sig.py
index 16b01e4e0..e54bc37a7 100644
--- a/mathics/builtin/exp_structure/size_and_sig.py
+++ b/mathics/builtin/exp_structure/size_and_sig.py
@@ -5,9 +5,9 @@
import platform
import zlib
-from mathics.builtin.base import Builtin
from mathics.core.atoms import ByteArrayAtom, Integer, String
from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED
+from mathics.core.builtin import Builtin
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.systemsymbols import SymbolByteArray
@@ -165,17 +165,6 @@ class LeafCount(Builtin):
>> LeafCount[100!]
= 1
-
- #> LeafCount[f[a, b][x, y]]
- = 5
-
- #> NestList[# /. s[x_][y_][z_] -> x[z][y[z]] &, s[s][s][s[s]][s][s], 4];
- #> LeafCount /@ %
- = {7, 8, 8, 11, 11}
-
- #> LeafCount[1 / 3, 1 + I]
- : LeafCount called with 2 arguments; 1 argument is expected.
- = LeafCount[1 / 3, 1 + I]
"""
messages = {
diff --git a/mathics/builtin/file_operations/file_properties.py b/mathics/builtin/file_operations/file_properties.py
index a665b6c32..87f0e8725 100644
--- a/mathics/builtin/file_operations/file_properties.py
+++ b/mathics/builtin/file_operations/file_properties.py
@@ -6,11 +6,11 @@
import os.path as osp
import time
-from mathics.builtin.base import Builtin, MessageException
from mathics.builtin.exp_structure.size_and_sig import Hash
from mathics.builtin.files_io.files import MathicsOpen
from mathics.core.atoms import Real, String
from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED
+from mathics.core.builtin import Builtin, MessageException
from mathics.core.convert.expression import to_expression
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
@@ -48,17 +48,6 @@ class FileDate(Builtin):
>> FileDate["ExampleData/sunflowers.jpg", "Rules"]
= ...
-
- #> FileDate["MathicsNonExistantExample"]
- : File not found during FileDate[MathicsNonExistantExample].
- = FileDate[MathicsNonExistantExample]
- #> FileDate["MathicsNonExistantExample", "Modification"]
- : File not found during FileDate[MathicsNonExistantExample, Modification].
- = FileDate[MathicsNonExistantExample, Modification]
-
- #> FileDate["ExampleData/sunflowers.jpg", "Fail"]
- : Date type Fail should be "Access", "Modification", "Creation" (Windows only), "Change" (Macintosh and Unix only), or "Rules".
- = FileDate[ExampleData/sunflowers.jpg, Fail]
"""
messages = {
@@ -155,24 +144,6 @@ class FileHash(Builtin):
>> FileHash["ExampleData/sunflowers.jpg", "SHA256"]
= 111619807552579450300684600241129773909359865098672286468229443390003894913065
-
- #> FileHash["ExampleData/sunflowers.jpg", "CRC32"]
- = 933095683
- #> FileHash["ExampleData/sunflowers.jpg", "SHA"]
- = 851696818771101405642332645949480848295550938123
- #> FileHash["ExampleData/sunflowers.jpg", "SHA224"]
- = 8723805623766373862936267623913366865806344065103917676078120867011
- #> FileHash["ExampleData/sunflowers.jpg", "SHA384"]
- = 28288410602533803613059815846847184383722061845493818218404754864571944356226472174056863474016709057507799332611860
- #> FileHash["ExampleData/sunflowers.jpg", "SHA512"]
- = 10111462070211820348006107532340854103555369343736736045463376555356986226454343186097958657445421102793096729074874292511750542388324853755795387877480102
-
- #> FileHash["ExampleData/sunflowers.jpg", xyzsymbol]
- = FileHash[ExampleData/sunflowers.jpg, xyzsymbol]
- #> FileHash["ExampleData/sunflowers.jpg", "xyzstr"]
- = FileHash[ExampleData/sunflowers.jpg, xyzstr, Integer]
- #> FileHash[xyzsymbol]
- = FileHash[xyzsymbol]
"""
attributes = A_PROTECTED | A_READ_PROTECTED
@@ -221,10 +192,6 @@ class FileType(Builtin):
= Directory
>> FileType["ExampleData/nonexistent"]
= None
-
- #> FileType[x]
- : File specification x is not a string of one or more characters.
- = FileType[x]
"""
messages = {
@@ -275,19 +242,7 @@ class SetFileDate(Builtin):
>> FileDate[tmpfilename, "Access"]
= {2002, 1, 1, 0, 0, 0.}
- #> SetFileDate[tmpfilename, {2002, 1, 1, 0, 0, 0.}];
- #> FileDate[tmpfilename, "Access"]
- = {2002, 1, 1, 0, 0, 0.}
-
- #> SetFileDate[tmpfilename]
- #> FileDate[tmpfilename, "Access"]
- = {...}
-
#> DeleteFile[tmpfilename]
-
- #> SetFileDate["MathicsNonExample"]
- : File not found during SetFileDate[MathicsNonExample].
- = $Failed
"""
messages = {
diff --git a/mathics/builtin/file_operations/file_utilities.py b/mathics/builtin/file_operations/file_utilities.py
index 30e243f36..a341994f0 100644
--- a/mathics/builtin/file_operations/file_utilities.py
+++ b/mathics/builtin/file_operations/file_utilities.py
@@ -2,8 +2,8 @@
File Utilities
"""
-from mathics.builtin.base import Builtin, MessageException
from mathics.builtin.files_io.files import MathicsOpen
+from mathics.core.builtin import Builtin, MessageException
from mathics.core.convert.expression import to_expression
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
@@ -28,17 +28,11 @@ class FindList(Builtin):
>> stream = FindList["ExampleData/EinsteinSzilLetter.txt", "uranium"];
- #> Length[stream]
+ >> Length[stream]
= 7
>> FindList["ExampleData/EinsteinSzilLetter.txt", "uranium", 1]
= {in manuscript, leads me to expect that the element uranium may be turned into}
-
- #> FindList["ExampleData/EinsteinSzilLetter.txt", "project"]
- = {}
-
- #> FindList["ExampleData/EinsteinSzilLetter.txt", "uranium", 0]
- = $Failed
"""
messages = {
diff --git a/mathics/builtin/fileformats/htmlformat.py b/mathics/builtin/fileformats/htmlformat.py
index 7fc1a798a..6cd5e6589 100644
--- a/mathics/builtin/fileformats/htmlformat.py
+++ b/mathics/builtin/fileformats/htmlformat.py
@@ -10,9 +10,9 @@
import re
from io import BytesIO
-from mathics.builtin.base import Builtin, MessageException
from mathics.builtin.files_io.files import MathicsOpen
from mathics.core.atoms import String
+from mathics.core.builtin import Builtin, MessageException
from mathics.core.convert.expression import to_expression, to_mathics_list
from mathics.core.convert.python import from_python
from mathics.core.expression import Expression
@@ -80,7 +80,7 @@ def xml_object(tree):
return Expression(
Expression(SymbolXMLObject, String("Document")),
to_mathics_list(*declaration),
- *node_to_xml_element(tree.getroot())
+ *node_to_xml_element(tree.getroot()),
)
diff --git a/mathics/builtin/fileformats/xmlformat.py b/mathics/builtin/fileformats/xmlformat.py
index 181b2153c..caa9fdf51 100644
--- a/mathics/builtin/fileformats/xmlformat.py
+++ b/mathics/builtin/fileformats/xmlformat.py
@@ -10,9 +10,9 @@
import re
from io import BytesIO
-from mathics.builtin.base import Builtin, MessageException
from mathics.builtin.files_io.files import MathicsOpen
from mathics.core.atoms import String
+from mathics.core.builtin import Builtin, MessageException
from mathics.core.convert.expression import to_expression, to_mathics_list
from mathics.core.convert.python import from_python
from mathics.core.expression import Evaluation, Expression
@@ -143,7 +143,7 @@ def xml_object(root):
return Expression(
to_expression("XMLObject", String("Document")),
to_mathics_list(*declaration),
- *node_to_xml_element(root)
+ *node_to_xml_element(root),
)
diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py
index fa60a823d..9263b6124 100644
--- a/mathics/builtin/files_io/files.py
+++ b/mathics/builtin/files_io/files.py
@@ -1,33 +1,29 @@
# -*- coding: utf-8 -*-
-# cython: language_level=3
"""
File and Stream Operations
"""
+import builtins
import io
import os.path as osp
import tempfile
from io import BytesIO
-from mathics_scanner import TranslateError
-
-import mathics
-from mathics.builtin.base import (
+import mathics.eval.files_io.files
+from mathics.core.atoms import Integer, String, SymbolString
+from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED
+from mathics.core.builtin import (
BinaryOperator,
Builtin,
MessageException,
Predefined,
PrefixOperator,
)
-from mathics.core import read
-from mathics.core.atoms import Integer, String, SymbolString
-from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED
from mathics.core.convert.expression import to_expression, to_mathics_list
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
from mathics.core.expression import BoxError, Expression
-from mathics.core.parser import MathicsFileLineFeeder, parse
from mathics.core.read import (
READ_TYPES,
MathicsOpen,
@@ -44,18 +40,15 @@
SymbolFailed,
SymbolHold,
SymbolInputForm,
+ SymbolInputStream,
SymbolOutputForm,
+ SymbolOutputStream,
SymbolReal,
)
from mathics.eval.directories import TMP_DIR
+from mathics.eval.files_io.files import eval_Get
from mathics.eval.makeboxes import do_format, format_element
-INPUT_VAR = ""
-
-SymbolInputStream = Symbol("InputStream")
-SymbolOutputStream = Symbol("OutputStream")
-SymbolPath = Symbol("$Path")
-
# TODO: Improve docs for these Read[] arguments.
# ## FIXME: All of this is related to Read[]
@@ -64,7 +57,7 @@
class Input_(Predefined):
"""
- :WMA link:https://reference.wolfram.com/language/ref/Input_.html
+ :WMA link:https://reference.wolfram.com/language/ref/$Input.html
'$Input'
@@ -79,13 +72,11 @@ class Input_(Predefined):
name = "$Input"
summary_text = "the name of the current input stream"
- def evaluate(self, evaluation):
- global INPUT_VAR
- return String(INPUT_VAR)
+ def evaluate(self, evaluation: Evaluation) -> String:
+ return String(mathics.eval.files_io.files.INPUT_VAR)
class _OpenAction(Builtin):
-
# BinaryFormat: 'False',
# CharacterEncoding :> Automatic,
# DOSTextFormat :> True,
@@ -107,6 +98,8 @@ class _OpenAction(Builtin):
),
}
+ mode = "r" # A default; this is changed in subclassing.
+
def eval_empty(self, evaluation: Evaluation, options: dict):
"%(name)s[OptionsPattern[]]"
@@ -204,10 +197,6 @@ class Close(Builtin):
Closing a file doesn't delete it from the filesystem
>> DeleteFile[file];
- #> Close["abc"]
- : abc is not open.
- = Close[abc]
-
#> Clear[file]
"""
@@ -216,9 +205,10 @@ class Close(Builtin):
"closex": "`1`.",
}
- def eval(self, channel, evaluation):
+ def eval(self, channel, evaluation: Evaluation):
"Close[channel_]"
+ n = name = None
if channel.has_form(("InputStream", "OutputStream"), 2):
[name, n] = channel.elements
py_n = n.get_int_value()
@@ -277,18 +267,6 @@ class FilePrint(Builtin):
prints the raw contents of $file$.
- #> exp = Sin[1];
- #> FilePrint[exp]
- : File specification Sin[1] is not a string of one or more characters.
- = FilePrint[Sin[1]]
-
- #> FilePrint["somenonexistentpath_h47sdmk^&h4"]
- : Cannot open somenonexistentpath_h47sdmk^&h4.
- = FilePrint[somenonexistentpath_h47sdmk^&h4]
-
- #> FilePrint[""]
- : File specification is not a string of one or more characters.
- = FilePrint[]
"""
messages = {
@@ -314,7 +292,7 @@ def eval(self, path, evaluation: Evaluation, options: dict):
):
evaluation.message("FilePrint", "fstr", path)
return
- pypath, is_temporary_file = path_search(pypath[1:-1])
+ pypath, _ = path_search(pypath[1:-1])
# Options
record_separators = options["System`RecordSeparators"].to_python()
@@ -394,16 +372,6 @@ class Get(PrefixOperator):
## TODO: Requires EndPackage implemented
## 'Get' can also load packages:
## >> << "VectorAnalysis`"
-
- #> Get["SomeTypoPackage`"]
- : Cannot open SomeTypoPackage`.
- = $Failed
-
- ## Parser Tests
- #> Hold[<< ~/some_example/dir/] // FullForm
- = Hold[Get["~/some_example/dir/"]]
- #> Hold[<<`/.\-_:$*~?] // FullForm
- = Hold[Get["`/.\\\\-_:$*~?"]]
"""
operator = "<<"
options = {
@@ -412,59 +380,21 @@ class Get(PrefixOperator):
precedence = 720
summary_text = "read in a file and evaluate commands in it"
- def eval(self, path, evaluation: Evaluation, options: dict):
+ def eval(self, path: String, evaluation: Evaluation, options: dict):
"Get[path_String, OptionsPattern[Get]]"
- def check_options(options):
- # Options
- # TODO Proper error messages
-
- result = {}
- trace_get = evaluation.parse("Settings`$TraceGet")
- if (
- options["System`Trace"].to_python()
- or trace_get.evaluate(evaluation) is SymbolTrue
- ):
- import builtins
-
- result["TraceFn"] = builtins.print
- else:
- result["TraceFn"] = None
-
- return result
+ trace_fn = None
+ trace_get = evaluation.parse("Settings`$TraceGet")
+ if (
+ options["System`Trace"].to_python()
+ or trace_get.evaluate(evaluation) is SymbolTrue
+ ):
+ trace_fn = builtins.print
- py_options = check_options(options)
- trace_fn = py_options["TraceFn"]
- result = None
- pypath = path.get_string_value()
- definitions = evaluation.definitions
- mathics.core.streams.PATH_VAR = SymbolPath.evaluate(evaluation).to_python(
- string_quotes=False
- )
- try:
- if trace_fn:
- trace_fn(pypath)
- with MathicsOpen(pypath, "r") as f:
- feeder = MathicsFileLineFeeder(f, trace_fn)
- while not feeder.empty():
- try:
- query = parse(definitions, feeder)
- except TranslateError:
- return SymbolNull
- finally:
- feeder.send_messages(evaluation)
- if query is None: # blank line / comment
- continue
- result = query.evaluate(evaluation)
- except IOError:
- evaluation.message("General", "noopen", path)
- return SymbolFailed
- except MessageException as e:
- e.message(evaluation)
- return SymbolFailed
- return result
+ # perform the actual evaluation
+ return eval_Get(path.value, evaluation, trace_fn)
- def eval_default(self, filename, evaluation):
+ def eval_default(self, filename, evaluation: Evaluation):
"Get[filename_]"
expr = to_expression("Get", filename)
evaluation.message("General", "stream", filename)
@@ -492,7 +422,7 @@ class InputFileName_(Predefined):
name = "$InputFileName"
def evaluate(self, evaluation):
- return String(read.INPUTFILE_VAR)
+ return String(evaluation.definitions.get_inputfile())
class InputStream(Builtin):
@@ -528,29 +458,11 @@ class OpenRead(_OpenAction):
>> OpenRead["ExampleData/EinsteinSzilLetter.txt", CharacterEncoding->"UTF8"]
= InputStream[...]
- #> Close[%];
- S> Close[OpenRead["https://raw.githubusercontent.com/Mathics3/mathics-core/master/README.rst"]];
+ The stream must be closed after using it to release the resource:
+ >> Close[%];
- #> OpenRead[]
- : OpenRead called with 0 arguments; 1 argument is expected.
- = OpenRead[]
-
- #> OpenRead[y]
- : File specification y is not a string of one or more characters.
- = OpenRead[y]
-
- #> OpenRead[""]
- : File specification is not a string of one or more characters.
- = OpenRead[]
-
- #> OpenRead["MathicsNonExampleFile"]
- : Cannot open MathicsNonExampleFile.
- = OpenRead[MathicsNonExampleFile]
-
- #> OpenRead["ExampleData/EinsteinSzilLetter.txt", BinaryFormat -> True, CharacterEncoding->"UTF8"]
- = InputStream[...]
- #> Close[%];
+ S> Close[OpenRead["https://raw.githubusercontent.com/Mathics3/mathics-core/master/README.rst"]];
"""
summary_text = "open a file for reading"
@@ -569,11 +481,7 @@ class OpenWrite(_OpenAction):
>> OpenWrite[]
= OutputStream[...]
- #> DeleteFile[Close[%]];
-
- #> OpenWrite[BinaryFormat -> True]
- = OutputStream[...]
- #> DeleteFile[Close[%]];
+ >> DeleteFile[Close[%]];
"""
summary_text = (
@@ -594,14 +502,8 @@ class OpenAppend(_OpenAction):
>> OpenAppend[]
= OutputStream[...]
- #> DeleteFile[Close[%]];
-
- #> appendFile = OpenAppend["MathicsNonExampleFile"]
- = OutputStream[MathicsNonExampleFile, ...]
+ >> DeleteFile[Close[%]];
- #> Close[appendFile]
- = MathicsNonExampleFile
- #> DeleteFile["MathicsNonExampleFile"]
"""
mode = "a"
@@ -757,17 +659,7 @@ class PutAppend(BinaryOperator):
| 265252859812191058636308480000000
| 8320987112741390144276341183223364380754172606361245952449277696409600000000000000
| "string"
- #> DeleteFile["factorials"];
-
- ## writing to dir
- #> x >>> /var/
- : Cannot open /var/.
- = x >>> /var/
-
- ## writing to read only file
- #> x >>> /proc/uptime
- : Cannot open /proc/uptime.
- = x >>> /proc/uptime
+ >> DeleteFile["factorials"];
"""
operator = ">>>"
@@ -842,20 +734,12 @@ class Read(Builtin):
Word
- ## Malformed InputString
- #> Read[InputStream[String], {Word, Number}]
- = Read[InputStream[String], {Word, Number}]
-
- ## Correctly formed InputString but not open
- #> Read[InputStream[String, -1], {Word, Number}]
- : InputStream[String, -1] is not open.
- = Read[InputStream[String, -1], {Word, Number}]
## Reading Strings
>> stream = StringToStream["abc123"];
>> Read[stream, String]
= abc123
- #> Read[stream, String]
+ >> Read[stream, String]
= EndOfFile
#> Close[stream];
@@ -865,60 +749,19 @@ class Read(Builtin):
= abc
>> Read[stream, Word]
= 123
- #> Read[stream, Word]
- = EndOfFile
- #> Close[stream];
- #> stream = StringToStream[""];
- #> Read[stream, Word]
- = EndOfFile
- #> Read[stream, Word]
+ >> Read[stream, Word]
= EndOfFile
#> Close[stream];
-
## Number
>> stream = StringToStream["123, 4"];
>> Read[stream, Number]
= 123
>> Read[stream, Number]
= 4
- #> Read[stream, Number]
+ >> Read[stream, Number]
= EndOfFile
#> Close[stream];
- #> stream = StringToStream["123xyz 321"];
- #> Read[stream, Number]
- = 123
- #> Quiet[Read[stream, Number]]
- = $Failed
-
- ## Real
- #> stream = StringToStream["123, 4abc"];
- #> Read[stream, Real]
- = 123.
- #> Read[stream, Real]
- = 4.
- #> Quiet[Read[stream, Number]]
- = $Failed
- #> Close[stream];
- #> stream = StringToStream["1.523E-19"]; Read[stream, Real]
- = 1.523×10^-19
- #> Close[stream];
- #> stream = StringToStream["-1.523e19"]; Read[stream, Real]
- = -1.523×10^19
- #> Close[stream];
- #> stream = StringToStream["3*^10"]; Read[stream, Real]
- = 3.×10^10
- #> Close[stream];
- #> stream = StringToStream["3.*^10"]; Read[stream, Real]
- = 3.×10^10
- #> Close[stream];
-
- ## Expression
- #> stream = StringToStream["x + y Sin[z]"]; Read[stream, Expression]
- = x + y Sin[z]
- #> Close[stream];
- ## #> stream = Quiet[StringToStream["Sin[1 123"]; Read[stream, Expression]]
- ## = $Failed
## HoldExpression:
>> stream = StringToStream["2+2\\n2+3"];
@@ -930,7 +773,7 @@ class Read(Builtin):
>> Read[stream, Expression]
= 5
- >> Close[stream];
+ #> Close[stream];
Reading a comment however will return the empty list:
>> stream = StringToStream["(* ::Package:: *)"];
@@ -938,28 +781,16 @@ class Read(Builtin):
>> Read[stream, Hold[Expression]]
= {}
- >> Close[stream];
+ #> Close[stream];
## Multiple types
>> stream = StringToStream["123 abc"];
>> Read[stream, {Number, Word}]
= {123, abc}
- #> Read[stream, {Number, Word}]
+ >> Read[stream, {Number, Word}]
= EndOfFile
- #> lose[stream];
-
- #> stream = StringToStream["123 abc"];
- #> Quiet[Read[stream, {Word, Number}]]
- = $Failed
- #> Close[stream];
-
- #> stream = StringToStream["123 123"]; Read[stream, {Real, Number}]
- = {123., 123}
#> Close[stream];
- #> Quiet[Read[stream, {Real}]]
- = Read[InputStream[String, ...], {Real}]
-
Multiple lines:
>> stream = StringToStream["\\"Tengo una\\nvaca lechera.\\""]; Read[stream]
= Tengo una
@@ -1232,15 +1063,7 @@ class ReadList(Read):
= {abc123}
>> InputForm[%]
= {"abc123"}
-
- #> ReadList[stream, "Invalid"]
- : Invalid is not a valid format specification.
- = ReadList[..., Invalid]
#> Close[stream];
-
-
- #> ReadList[StringToStream["a 1 b 2"], {Word, Number}, 1]
- = {{a, 1}}
"""
# TODO
@@ -1398,10 +1221,6 @@ class SetStreamPosition(Builtin):
>> Read[stream, Word]
= is
- #> SetStreamPosition[stream, -5]
- : Invalid I/O Seek.
- = 10
-
>> SetStreamPosition[stream, Infinity]
= 16
"""
@@ -1490,7 +1309,7 @@ class Skip(Read):
>> Skip[stream, Word, 2]
>> Read[stream, Word]
= d
- #> Skip[stream, Word]
+ >> Skip[stream, Word]
= EndOfFile
#> Close[stream];
"""
@@ -1555,7 +1374,7 @@ class Find(Read):
= in manuscript, leads me to expect that the element uranium may be turned into
>> Find[stream, "uranium"]
= become possible to set up a nuclear chain reaction in a large mass of uranium,
- >> Close[stream]
+ #> Close[stream]
= ...
>> stream = OpenRead["ExampleData/EinsteinSzilLetter.txt", CharacterEncoding->"UTF8"];
@@ -1563,7 +1382,7 @@ class Find(Read):
= a new and important source of energy in the immediate future. Certain aspects
>> Find[stream, {"energy", "power"} ]
= by which vast amounts of power and large quantities of new radium-like
- >> Close[stream]
+ #> Close[stream]
= ...
"""
@@ -1649,14 +1468,8 @@ class StringToStream(Builtin):
>> strm = StringToStream["abc 123"]
= InputStream[String, ...]
- #> Read[strm, Word]
- = abc
-
- #> Read[strm, Number]
- = 123
-
- #> Close[strm]
- = String
+ The stream must be closed after using it, to release the resource:
+ >> Close[strm];
"""
summary_text = "open an input stream for reading from a string"
@@ -1685,14 +1498,6 @@ class Streams(Builtin):
>> Streams["stdout"]
= ...
-
- #> OpenWrite[]
- = ...
- #> Streams[%[[1]]]
- = {OutputStream[...]}
-
- #> Streams["some_nonexistent_name"]
- = {}
"""
summary_text = "list currently open streams"
@@ -1764,11 +1569,12 @@ class Write(Builtin):
= ...
>> Write[stream, 10 x + 15 y ^ 2]
>> Write[stream, 3 Sin[z]]
+ The stream must be closed in order to use the file again:
>> Close[stream];
>> stream = OpenRead[%];
>> ReadList[stream]
= {10 x + 15 y ^ 2, 3 Sin[z]}
- #> DeleteFile[Close[stream]];
+ >> DeleteFile[Close[stream]];
"""
summary_text = "write a sequence of expressions to a stream, ending the output with a newline (line feed)"
@@ -1817,7 +1623,7 @@ class WriteString(Builtin):
>> FilePrint[%]
| This is a test 1This is also a test 2
- #> DeleteFile[pathname];
+ >> DeleteFile[pathname];
>> stream = OpenWrite[];
>> WriteString[stream, "This is a test 1", "This is also a test 2"]
>> pathname = Close[stream]
@@ -1825,29 +1631,7 @@ class WriteString(Builtin):
>> FilePrint[%]
| This is a test 1This is also a test 2
- #> DeleteFile[pathname];
- #> stream = OpenWrite[];
- #> WriteString[stream, 100, 1 + x + y, Sin[x + y]]
- #> pathname = Close[stream]
- = ...
- #> FilePrint[%]
- | 1001 + x + ySin[x + y]
-
- #> DeleteFile[pathname];
- #> stream = OpenWrite[];
- #> WriteString[stream]
- #> pathame = Close[stream]
- = ...
- #> FilePrint[%]
-
- #> WriteString[%%, abc]
- #> Streams[%%%][[1]]
- = ...
- #> pathname = Close[%];
- #> FilePrint[%]
- | abc
- #> DeleteFile[pathname];
- #> Clear[pathname];
+ >> DeleteFile[pathname];
If stream is the string "stdout" or "stderr", writes to the system standard output/ standard error channel:
diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py
index 24e4c250d..f0faf505e 100644
--- a/mathics/builtin/files_io/filesystem.py
+++ b/mathics/builtin/files_io/filesystem.py
@@ -11,10 +11,10 @@
import shutil
from typing import List
-from mathics.builtin.base import Builtin, MessageException, Predefined
from mathics.builtin.files_io.files import MathicsOpen
from mathics.core.atoms import Integer, String
from mathics.core.attributes import A_LISTABLE, A_LOCKED, A_PROTECTED
+from mathics.core.builtin import Builtin, MessageException, Predefined
from mathics.core.convert.expression import to_expression, to_mathics_list
from mathics.core.convert.python import from_python
from mathics.core.convert.regex import to_regex
@@ -53,9 +53,6 @@ class AbsoluteFileName(Builtin):
>> AbsoluteFileName["ExampleData/sunflowers.jpg"]
= ...
- #> AbsoluteFileName["Some/NonExistant/Path.ext"]
- : File not found during AbsoluteFileName[Some/NonExistant/Path.ext].
- = $Failed
"""
messages = {
@@ -363,23 +360,6 @@ class DirectoryName(Builtin):
>> DirectoryName["a/b/c", 2]
= a
-
- #> DirectoryName["a/b/c", 3] // InputForm
- = ""
- #> DirectoryName[""] // InputForm
- = ""
-
- #> DirectoryName["a/b/c", x]
- : Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, x].
- = DirectoryName[a/b/c, x]
-
- #> DirectoryName["a/b/c", -1]
- : Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, -1].
- = DirectoryName[a/b/c, -1]
-
- #> DirectoryName[x]
- : String expected at position 1 in DirectoryName[x].
- = DirectoryName[x]
"""
messages = {
@@ -508,12 +488,6 @@ class FileBaseName(Builtin):
>> FileBaseName["file.tar.gz"]
= file.tar
-
- #> FileBaseName["file."]
- = file
-
- #> FileBaseName["file"]
- = file
"""
options = {
@@ -627,11 +601,6 @@ class FileExtension(Builtin):
>> FileExtension["file.tar.gz"]
= gz
-
- #> FileExtension["file."]
- = #<--#
- #> FileExtension["file"]
- = #<--#
"""
options = {
@@ -660,9 +629,6 @@ class FileInformation(Builtin):
>> FileInformation["ExampleData/sunflowers.jpg"]
= {File -> ..., FileType -> File, ByteCount -> 142286, Date -> ...}
-
- #> FileInformation["ExampleData/missing_file.jpg"]
- = {}
"""
rules = {
@@ -688,9 +654,6 @@ class FindFile(Builtin):
>> FindFile["VectorAnalysis`VectorAnalysis`"]
= ...
-
- #> FindFile["SomeTypoPackage`"]
- = $Failed
"""
messages = {
@@ -938,97 +901,6 @@ class Needs(Builtin):
, \
while
:$ImportFormats:
-/doc/reference-of-built-in-symbols/importing-and-exporting/$importformats \
+/doc/reference-of-built-in-symbols/inputoutput-files-and-filesystem/importing-and-exporting/$importformats \
does the corresponding thing for
:Import:
-/doc/reference-of-built-in-symbols/importing-and-exporting/import.
+/doc/reference-of-built-in-symbols/inputoutput-files-and-filesystem/importing-and-exporting/import.
"""
import base64
@@ -25,10 +25,10 @@
from itertools import chain
from urllib.error import HTTPError, URLError
-from mathics.builtin.base import Builtin, Integer, Predefined, String, get_option
from mathics.builtin.pymimesniffer import magic
from mathics.core.atoms import ByteArrayAtom
from mathics.core.attributes import A_NO_ATTRIBUTES, A_PROTECTED, A_READ_PROTECTED
+from mathics.core.builtin import Builtin, Integer, Predefined, String, get_option
from mathics.core.convert.expression import to_mathics_list
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
@@ -1197,7 +1197,7 @@ class RegisterExport(Builtin):
>> FilePrint["sample.txt"]
| Encode this string!
- #> DeleteFile["sample.txt"]
+ >> DeleteFile["sample.txt"]
Very basic encrypted text exporter:
>> ExampleExporter2[filename_, data_, opts___] := Module[{strm = OpenWrite[filename], char}, (* TODO: Check data *) char = FromCharacterCode[Mod[ToCharacterCode[data] - 84, 26] + 97]; WriteString[strm, char]; Close[strm]]
@@ -1209,7 +1209,7 @@ class RegisterExport(Builtin):
>> FilePrint["sample.txt"]
| rapbqrguvffgevat
- #> DeleteFile["sample.txt"]
+ >> DeleteFile["sample.txt"]
"""
summary_text = "register an exporter for a file format"
@@ -1247,13 +1247,6 @@ class URLFetch(Builtin):
'URLFetch[$URL$]'
Returns the content of $URL$ as a string.
-
-
- #> Quiet[URLFetch["https:////", {}]]
- = $Failed
-
- ##> Quiet[URLFetch["https://www.example.com", {}]]
- # = ...
"""
summary_text = "fetch data from a URL"
@@ -1340,38 +1333,16 @@ class Import(Builtin):
Terminates the mathics session with exit code $n$.
+
+ 'Quit' is the same thing as 'Exit'.
+ """
+
+ summary_text = "terminate the session"
+
+ def eval(self, evaluation: Evaluation, n):
+ "%(name)s[n___]"
+ exitcode = 0
+ if isinstance(n, Integer):
+ exitcode = n.get_int_value()
+ raise SystemExit(exitcode)
+
+
+class Exit(Quit):
+ """
+ :WMA link:https://reference.wolfram.com/language/ref/Exit.html
+
+
+
'Exit'[]
+
Terminates the Mathics session.
+
+
'Exit[$n$]'
+
Terminates the mathics session with exit code $n$.
+
+ 'Exit' is the same thing as 'Quit'.
+ """
diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py
index 0397b09b3..5efc4d325 100644
--- a/mathics/builtin/layout.py
+++ b/mathics/builtin/layout.py
@@ -12,11 +12,11 @@
"""
-from mathics.builtin.base import BinaryOperator, Builtin, Operator
from mathics.builtin.box.layout import GridBox, RowBox, to_boxes
from mathics.builtin.makeboxes import MakeBoxes
from mathics.builtin.options import options_to_rules
from mathics.core.atoms import Real, String
+from mathics.core.builtin import BinaryOperator, Builtin, Operator
from mathics.core.expression import Evaluation, Expression
from mathics.core.list import ListExpression
from mathics.core.symbols import Symbol
@@ -177,12 +177,6 @@ class Infix(Builtin):
>> Infix[{a, b, c}, {"+", "-"}]
= a + b - c
-
- #> Format[r[items___]] := Infix[If[Length[{items}] > 1, {items}, {ab}], "~"]
- #> r[1, 2, 3]
- = 1 ~ 2 ~ 3
- #> r[1]
- = ab
"""
messages = {
diff --git a/mathics/builtin/list/associations.py b/mathics/builtin/list/associations.py
index 9c32c9386..ed0a7b6b7 100644
--- a/mathics/builtin/list/associations.py
+++ b/mathics/builtin/list/associations.py
@@ -9,10 +9,10 @@
"""
-from mathics.builtin.base import Builtin, Test
from mathics.builtin.box.layout import RowBox
from mathics.core.atoms import Integer
from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_PROTECTED
+from mathics.core.builtin import Builtin, Test
from mathics.core.convert.expression import to_mathics_list
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
@@ -46,38 +46,6 @@ class Association(Builtin):
Associations can be nested:
>> <|a -> x, b -> y, <|a -> z, d -> t|>|>
= <|a -> z, b -> y, d -> t|>
-
- #> <|a -> x, b -> y, c -> <|d -> t|>|>
- = <|a -> x, b -> y, c -> <|d -> t|>|>
- #> %["s"]
- = Missing[KeyAbsent, s]
-
- #> <|a -> x, b + c -> y, {<|{}|>, a -> {z}}|>
- = <|a -> {z}, b + c -> y|>
- #> %[a]
- = {z}
-
- #> <|"x" -> 1, {y} -> 1|>
- = <|x -> 1, {y} -> 1|>
- #> %["x"]
- = 1
-
- #> <|<|a -> v|> -> x, <|b -> y, a -> <|c -> z|>, {}, <||>|>, {d}|>[c]
- = Association[Association[a -> v] -> x, Association[b -> y, a -> Association[c -> z], {}, Association[]], {d}][c]
-
- #> <|<|a -> v|> -> x, <|b -> y, a -> <|c -> z|>, {d}|>, {}, <||>|>[a]
- = Association[Association[a -> v] -> x, Association[b -> y, a -> Association[c -> z], {d}], {}, Association[]][a]
-
- #> <|<|a -> v|> -> x, <|b -> y, a -> <|c -> z, {d}|>, {}, <||>|>, {}, <||>|>
- = <|<|a -> v|> -> x, b -> y, a -> Association[c -> z, {d}]|>
- #> %[a]
- = Association[c -> z, {d}]
-
- #> <|a -> x, b -> y, c -> <|d -> t|>|> // ToBoxes
- = RowBox[{<|, RowBox[{RowBox[{a, ->, x}], ,, RowBox[{b, ->, y}], ,, RowBox[{c, ->, RowBox[{<|, RowBox[{d, ->, t}], |>}]}]}], |>}]
-
- #> Association[a -> x, b -> y, c -> Association[d -> t, Association[e -> u]]] // ToBoxes
- = RowBox[{<|, RowBox[{RowBox[{a, ->, x}], ,, RowBox[{b, ->, y}], ,, RowBox[{c, ->, RowBox[{<|, RowBox[{RowBox[{d, ->, t}], ,, RowBox[{e, ->, u}]}], |>}]}]}], |>}]
"""
error_idx = 0
@@ -237,41 +205,6 @@ class Keys(Builtin):
Keys are listed in the order of their appearance:
>> Keys[{c -> z, b -> y, a -> x}]
= {c, b, a}
-
- #> Keys[a -> x]
- = a
-
- #> Keys[{a -> x, a -> y, {a -> z, <|b -> t|>, <||>, {}}}]
- = {a, a, {a, {b}, {}, {}}}
-
- #> Keys[{a -> x, a -> y, <|a -> z, {b -> t}, <||>, {}|>}]
- = {a, a, {a, b}}
-
- #> Keys[<|a -> x, a -> y, <|a -> z, <|b -> t|>, <||>, {}|>|>]
- = {a, b}
-
- #> Keys[<|a -> x, a -> y, {a -> z, {b -> t}, <||>, {}}|>]
- = {a, b}
-
- #> Keys[<|a -> x, <|a -> y, b|>|>]
- : The argument Association[a -> x, Association[a -> y, b]] is not a valid Association or a list of rules.
- = Keys[Association[a -> x, Association[a -> y, b]]]
-
- #> Keys[<|a -> x, {a -> y, b}|>]
- : The argument Association[a -> x, {a -> y, b}] is not a valid Association or a list of rules.
- = Keys[Association[a -> x, {a -> y, b}]]
-
- #> Keys[{a -> x, <|a -> y, b|>}]
- : The argument Association[a -> y, b] is not a valid Association or a list of rules.
- = Keys[{a -> x, Association[a -> y, b]}]
-
- #> Keys[{a -> x, {a -> y, b}}]
- : The argument b is not a valid Association or a list of rules.
- = Keys[{a -> x, {a -> y, b}}]
-
- #> Keys[a -> x, b -> y]
- : Keys called with 2 arguments; 1 argument is expected.
- = Keys[a -> x, b -> y]
"""
attributes = A_PROTECTED
@@ -372,40 +305,6 @@ class Values(Builtin):
>> Values[{c -> z, b -> y, a -> x}]
= {z, y, x}
- #> Values[a -> x]
- = x
-
- #> Values[{a -> x, a -> y, {a -> z, <|b -> t|>, <||>, {}}}]
- = {x, y, {z, {t}, {}, {}}}
-
- #> Values[{a -> x, a -> y, <|a -> z, {b -> t}, <||>, {}|>}]
- = {x, y, {z, t}}
-
- #> Values[<|a -> x, a -> y, <|a -> z, <|b -> t|>, <||>, {}|>|>]
- = {z, t}
-
- #> Values[<|a -> x, a -> y, {a -> z, {b -> t}, <||>, {}}|>]
- = {z, t}
-
- #> Values[<|a -> x, <|a -> y, b|>|>]
- : The argument Association[a -> x, Association[a -> y, b]] is not a valid Association or a list of rules.
- = Values[Association[a -> x, Association[a -> y, b]]]
-
- #> Values[<|a -> x, {a -> y, b}|>]
- : The argument Association[a -> x, {a -> y, b}] is not a valid Association or a list of rules.
- = Values[Association[a -> x, {a -> y, b}]]
-
- #> Values[{a -> x, <|a -> y, b|>}]
- : The argument {a -> x, Association[a -> y, b]} is not a valid Association or a list of rules.
- = Values[{a -> x, Association[a -> y, b]}]
-
- #> Values[{a -> x, {a -> y, b}}]
- : The argument {a -> x, {a -> y, b}} is not a valid Association or a list of rules.
- = Values[{a -> x, {a -> y, b}}]
-
- #> Values[a -> x, b -> y]
- : Values called with 2 arguments; 1 argument is expected.
- = Values[a -> x, b -> y]
"""
attributes = A_PROTECTED
diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py
index 93e20f267..f84ac6232 100644
--- a/mathics/builtin/list/constructing.py
+++ b/mathics/builtin/list/constructing.py
@@ -10,10 +10,10 @@
from itertools import permutations
-from mathics.builtin.base import Builtin, IterationFunction, Pattern
from mathics.builtin.box.layout import RowBox
from mathics.core.atoms import Integer, is_integer_rational_or_real
from mathics.core.attributes import A_HOLD_FIRST, A_LISTABLE, A_LOCKED, A_PROTECTED
+from mathics.core.builtin import Builtin, IterationFunction, Pattern
from mathics.core.convert.expression import to_expression
from mathics.core.convert.sympy import from_sympy
from mathics.core.element import ElementsProperties
@@ -212,7 +212,13 @@ class Range(Builtin):
returns a list of integers from 1 to $n$.
'Range[$a$, $b$]'
-
returns a list of integers from $a$ to $b$.
+
returns a list of (Integer, Rational, Real) numbers from $a$ to $b$.
+
+
'Range[$a$, $b$, $di$]'
+
returns a list of numbers from $a$ to $b$ using step $di$.
+ More specifically, 'Range' starts from $a$ and successively adds \
+ increments of $di$ until the result is greater (if $di$ > 0) or \
+ less (if $di$ < 0) than $b$.
>> Range[5]
@@ -221,6 +227,9 @@ class Range(Builtin):
>> Range[-3, 2]
= {-3, -2, -1, 0, 1, 2}
+ >> Range[5, 1, -2]
+ = {5, 3, 1}
+
>> Range[1.0, 2.3]
= {1., 2.}
@@ -258,7 +267,8 @@ def eval(self, imin, imax, di, evaluation: Evaluation):
and isinstance(imax, Integer)
and isinstance(di, Integer)
):
- result = [Integer(i) for i in range(imin.value, imax.value + 1, di.value)]
+ pm = 1 if di.value >= 0 else -1
+ result = [Integer(i) for i in range(imin.value, imax.value + pm, di.value)]
return ListExpression(
*result, elements_properties=range_list_elements_properties
)
@@ -266,9 +276,13 @@ def eval(self, imin, imax, di, evaluation: Evaluation):
imin = imin.to_sympy()
imax = imax.to_sympy()
di = di.to_sympy()
+
+ def compare_type(a, b):
+ return a <= b if di >= 0 else a >= b
+
index = imin
result = []
- while index <= imax:
+ while compare_type(index, imax):
evaluation.check_stopped()
result.append(from_sympy(index))
index += di
diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py
index 79c9c0176..24439eddd 100644
--- a/mathics/builtin/list/eol.py
+++ b/mathics/builtin/list/eol.py
@@ -9,7 +9,6 @@
from itertools import chain
-from mathics.builtin.base import BinaryOperator, Builtin
from mathics.builtin.box.layout import RowBox
from mathics.core.atoms import Integer, Integer0, Integer1, String
from mathics.core.attributes import (
@@ -19,6 +18,7 @@
A_PROTECTED,
A_READ_PROTECTED,
)
+from mathics.core.builtin import BinaryOperator, Builtin
from mathics.core.convert.expression import to_mathics_list
from mathics.core.convert.python import from_python
from mathics.core.exceptions import (
@@ -83,10 +83,6 @@ class Append(Builtin):
Unlike 'Join', 'Append' does not flatten lists in $item$:
>> Append[{a, b}, {c, d}]
= {a, b, {c, d}}
-
- #> Append[a, b]
- : Nonatomic expression expected.
- = Append[a, b]
"""
summary_text = "add an element at the end of an expression"
@@ -128,14 +124,6 @@ class AppendTo(Builtin):
= f[x]
>> y
= f[x]
-
- #> AppendTo[{}, 1]
- : {} is not a variable with a value, so its value cannot be changed.
- = AppendTo[{}, 1]
-
- #> AppendTo[a, b]
- : a is not a variable with a value, so its value cannot be changed.
- = AppendTo[a, b]
"""
attributes = A_HOLD_FIRST | A_PROTECTED
@@ -188,28 +176,6 @@ class Cases(Builtin):
Also include the head of the expression in the previous search:
>> Cases[{b, 6, \[Pi]}, _Symbol, Heads -> True]
= {List, b, Pi}
-
- #> Cases[1, 2]
- = {}
-
- #> Cases[f[1, 2], 2]
- = {2}
-
- #> Cases[f[f[1, 2], f[2]], 2]
- = {}
- #> Cases[f[f[1, 2], f[2]], 2, 2]
- = {2, 2}
- #> Cases[f[f[1, 2], f[2], 2], 2, Infinity]
- = {2, 2, 2}
-
- #> Cases[{1, f[2], f[3, 3, 3], 4, f[5, 5]}, f[x__] :> Plus[x]]
- = {2, 9, 10}
- #> Cases[{1, f[2], f[3, 3, 3], 4, f[5, 5]}, f[x__] -> Plus[x]]
- = {2, 3, 3, 3, 5, 5}
-
- ## Issue 531
- #> z = f[x, y]; x = 1; Cases[z, _Symbol, Infinity]
- = {y}
"""
rules = {
@@ -246,7 +212,6 @@ def eval(self, items, pattern, ls, evaluation, options):
results = []
if pattern.has_form("Rule", 2) or pattern.has_form("RuleDelayed", 2):
-
match = Matcher(pattern.elements[0]).match
rule = Rule(pattern.elements[0], pattern.elements[1])
@@ -342,16 +307,6 @@ class Delete(Builtin):
>> Delete[{a, b, c}, 0]
= Sequence[a, b, c]
- #> Delete[1 + x ^ (a + b + c), {2, 2, 3}]
- = 1 + x ^ (a + b)
-
- #> Delete[f[a, g[b, c], d], {{2}, {2, 1}}]
- = f[a, d]
-
- #> Delete[f[a, g[b, c], d], m + n]
- : The expression m + n cannot be used as a part specification. Use Key[m + n] instead.
- = Delete[f[a, g[b, c], d], m + n]
-
Delete without the position:
>> Delete[{a, b, c, d}]
: Delete called with 1 argument; 2 arguments are expected.
@@ -375,14 +330,6 @@ class Delete(Builtin):
>> Delete[{a, b, c, d}, {1, n}]
: Position specification n in {a, b, c, d} is not a machine-sized integer or a list of machine-sized integers.
= Delete[{a, b, c, d}, {1, n}]
-
- #> Delete[{a, b, c, d}, {{1}, n}]
- : Position specification {n, {1}} in {a, b, c, d} is not a machine-sized integer or a list of machine-sized integers.
- = Delete[{a, b, c, d}, {{1}, n}]
-
- #> Delete[{a, b, c, d}, {{1}, {n}}]
- : Position specification n in {a, b, c, d} is not a machine-sized integer or a list of machine-sized integers.
- = Delete[{a, b, c, d}, {{1}, {n}}]
"""
messages = {
@@ -475,10 +422,6 @@ class DeleteCases(Builtin):
>> DeleteCases[{a, b, 1, c, 2, 3}, _Symbol]
= {1, 2, 3}
-
- ## Issue 531
- #> z = {x, y}; x = 1; DeleteCases[z, _Symbol]
- = {1}
"""
messages = {
@@ -575,15 +518,6 @@ class Drop(Builtin):
= {{11, 12, 13, 14}, {21, 22, 23, 24}, {31, 32, 33, 34}, {41, 42, 43, 44}}
>> Drop[A, {2, 3}, {2, 3}]
= {{11, 14}, {41, 44}}
-
- #> Drop[Range[10], {-2, -6, -3}]
- = {1, 2, 3, 4, 5, 7, 8, 10}
- #> Drop[Range[10], {10, 1, -3}]
- = {2, 3, 5, 6, 8, 9}
-
- #> Drop[Range[6], {-5, -2, -2}]
- : Cannot drop positions -5 through -2 in {1, 2, 3, 4, 5, 6}.
- = Drop[{1, 2, 3, 4, 5, 6}, {-5, -2, -2}]
"""
messages = {
@@ -742,49 +676,6 @@ class FirstPosition(Builtin):
Find the first position at which x^2 to appears:
>> FirstPosition[{1 + x^2, 5, x^4, a + (1 + x^2)^2}, x^2]
= {1, 2}
-
- #> FirstPosition[{1, 2, 3}, _?StringQ, "NoStrings"]
- = NoStrings
-
- #> FirstPosition[a, a]
- = {}
-
- #> FirstPosition[{{{1, 2}, {2, 3}, {3, 1}}, {{1, 2}, {2, 3}, {3, 1}}},3]
- = {1, 2, 2}
-
- #> FirstPosition[{{1, {2, 1}}, {2, 3}, {3, 1}}, 2, Missing["NotFound"],2]
- = {2, 1}
-
- #> FirstPosition[{{1, {2, 1}}, {2, 3}, {3, 1}}, 2, Missing["NotFound"],4]
- = {1, 2, 1}
-
- #> FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing["NotFound"], {1}]
- = Missing[NotFound]
-
- #> FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing["NotFound"], 0]
- = Missing[NotFound]
-
- #> FirstPosition[{{1, 2}, {1, {2, 1}}, {2, 3}}, 2, Missing["NotFound"], {3}]
- = {2, 2, 1}
-
- #> FirstPosition[{{1, 2}, {1, {2, 1}}, {2, 3}}, 2, Missing["NotFound"], 3]
- = {1, 2}
-
- #> FirstPosition[{{1, 2}, {1, {2, 1}}, {2, 3}}, 2, Missing["NotFound"], {}]
- = {1, 2}
-
- #> FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing["NotFound"], {1, 2, 3}]
- : Level specification {1, 2, 3} is not of the form n, {n}, or {m, n}.
- = FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing[NotFound], {1, 2, 3}]
-
- #> FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing["NotFound"], a]
- : Level specification a is not of the form n, {n}, or {m, n}.
- = FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing[NotFound], a]
-
- #> FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing["NotFound"], {1, a}]
- : Level specification {1, a} is not of the form n, {n}, or {m, n}.
- = FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing[NotFound], {1, a}]
-
"""
messages = {
@@ -1017,11 +908,6 @@ class Most(Builtin):
>> Most[x]
: Nonatomic expression expected.
= Most[x]
-
- #> A[x__] := 7 /; Length[{x}] == 3;
- #> Most[A[1, 2, 3, 4]]
- = 7
- #> ClearAll[A];
"""
summary_text = "remove the last element"
@@ -1121,30 +1007,6 @@ class Part(Builtin):
Of course, part specifications have precedence over most arithmetic operations:
>> A[[1]] + B[[2]] + C[[3]] // Hold // FullForm
= Hold[Plus[Part[A, 1], Part[B, 2], Part[C, 3]]]
-
- #> a = {2,3,4}; i = 1; a[[i]] = 0; a
- = {0, 3, 4}
-
- ## Negative step
- #> {1,2,3,4,5}[[3;;1;;-1]]
- = {3, 2, 1}
-
- #> {1, 2, 3, 4, 5}[[;; ;; -1]] (* MMA bug *)
- = {5, 4, 3, 2, 1}
-
- #> Range[11][[-3 ;; 2 ;; -2]]
- = {9, 7, 5, 3}
- #> Range[11][[-3 ;; -7 ;; -3]]
- = {9, 6}
- #> Range[11][[7 ;; -7;; -2]]
- = {7, 5}
-
- #> {1, 2, 3, 4}[[1;;3;;-1]]
- : Cannot take positions 1 through 3 in {1, 2, 3, 4}.
- = {1, 2, 3, 4}[[1 ;; 3 ;; -1]]
- #> {1, 2, 3, 4}[[3;;1]]
- : Cannot take positions 3 through 1 in {1, 2, 3, 4}.
- = {1, 2, 3, 4}[[3 ;; 1]]
"""
attributes = A_N_HOLD_REST | A_PROTECTED | A_READ_PROTECTED
@@ -1353,10 +1215,6 @@ class Prepend(Builtin):
Unlike 'Join', 'Prepend' does not flatten lists in $item$:
>> Prepend[{c, d}, {a, b}]
= {{a, b}, c, d}
-
- #> Prepend[a, b]
- : Nonatomic expression expected.
- = Prepend[a, b]
"""
summary_text = "add an element at the beginning"
@@ -1403,19 +1261,6 @@ class PrependTo(Builtin):
= f[x, a, b, c]
>> y
= f[x, a, b, c]
-
- #> PrependTo[{a, b}, 1]
- : {a, b} is not a variable with a value, so its value cannot be changed.
- = PrependTo[{a, b}, 1]
-
- #> PrependTo[a, b]
- : a is not a variable with a value, so its value cannot be changed.
- = PrependTo[a, b]
-
- #> x = 1 + 2;
- #> PrependTo[x, {3, 4}]
- : Nonatomic expression expected at position 1 in PrependTo[x, {3, 4}].
- = PrependTo[x, {3, 4}]
"""
attributes = A_HOLD_FIRST | A_PROTECTED
@@ -1596,11 +1441,6 @@ class Select(Builtin):
>> Select[a, True]
: Nonatomic expression expected.
= Select[a, True]
-
- #> A[x__] := 31415 /; Length[{x}] == 3;
- #> Select[A[5, 2, 7, 1], OddQ]
- = 31415
- #> ClearAll[A];
"""
summary_text = "pick elements according to a criterion"
@@ -1636,32 +1476,6 @@ class Span(BinaryOperator):
= Span[2, -2]
>> ;;3 // FullForm
= Span[1, 3]
-
- ## Parsing: 8 cases to consider
- #> a ;; b ;; c // FullForm
- = Span[a, b, c]
- #> ;; b ;; c // FullForm
- = Span[1, b, c]
- #> a ;; ;; c // FullForm
- = Span[a, All, c]
- #> ;; ;; c // FullForm
- = Span[1, All, c]
- #> a ;; b // FullForm
- = Span[a, b]
- #> ;; b // FullForm
- = Span[1, b]
- #> a ;; // FullForm
- = Span[a, All]
- #> ;; // FullForm
- = Span[1, All]
-
- ## Formatting
- #> a ;; b ;; c
- = a ;; b ;; c
- #> a ;; b
- = a ;; b
- #> a ;; b ;; c ;; d
- = (1 ;; d) (a ;; b ;; c)
"""
operator = ";;"
@@ -1693,34 +1507,6 @@ class Take(Builtin):
Take a single column:
>> Take[A, All, {2}]
= {{b}, {e}}
-
- #> Take[Range[10], {8, 2, -1}]
- = {8, 7, 6, 5, 4, 3, 2}
- #> Take[Range[10], {-3, -7, -2}]
- = {8, 6, 4}
-
- #> Take[Range[6], {-5, -2, -2}]
- : Cannot take positions -5 through -2 in {1, 2, 3, 4, 5, 6}.
- = Take[{1, 2, 3, 4, 5, 6}, {-5, -2, -2}]
-
- #> Take[l, {-1}]
- : Nonatomic expression expected at position 1 in Take[l, {-1}].
- = Take[l, {-1}]
-
- ## Empty case
- #> Take[{1, 2, 3, 4, 5}, {-1, -2}]
- = {}
- #> Take[{1, 2, 3, 4, 5}, {0, -1}]
- = {}
- #> Take[{1, 2, 3, 4, 5}, {1, 0}]
- = {}
- #> Take[{1, 2, 3, 4, 5}, {2, 1}]
- = {}
- #> Take[{1, 2, 3, 4, 5}, {1, 0, 2}]
- = {}
- #> Take[{1, 2, 3, 4, 5}, {1, 0, -1}]
- : Cannot take positions 1 through 0 in {1, 2, 3, 4, 5}.
- = Take[{1, 2, 3, 4, 5}, {1, 0, -1}]
"""
messages = {
diff --git a/mathics/builtin/list/math.py b/mathics/builtin/list/math.py
index ad2a5f9b2..a0452d37d 100644
--- a/mathics/builtin/list/math.py
+++ b/mathics/builtin/list/math.py
@@ -3,7 +3,7 @@
"""
import heapq
-from mathics.builtin.base import Builtin, CountableInteger, NegativeIntegerException
+from mathics.core.builtin import Builtin, CountableInteger, NegativeIntegerException
from mathics.core.exceptions import MessageException
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
diff --git a/mathics/builtin/list/predicates.py b/mathics/builtin/list/predicates.py
index 80b9053fa..d45f52fbb 100644
--- a/mathics/builtin/list/predicates.py
+++ b/mathics/builtin/list/predicates.py
@@ -2,9 +2,9 @@
Predicates on Lists
"""
-from mathics.builtin.base import Builtin
from mathics.builtin.options import options_to_rules
from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED
+from mathics.core.builtin import Builtin
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue
@@ -32,22 +32,9 @@ class ContainsOnly(Builtin):
>> ContainsOnly[{}, {a, b, c}]
= True
- #> ContainsOnly[1, {1, 2, 3}]
- : List or association expected instead of 1.
- = ContainsOnly[1, {1, 2, 3}]
-
- #> ContainsOnly[{1, 2, 3}, 4]
- : List or association expected instead of 4.
- = ContainsOnly[{1, 2, 3}, 4]
-
Use Equal as the comparison function to have numerical tolerance:
>> ContainsOnly[{a, 1.0}, {1, a, b}, {SameTest -> Equal}]
= True
-
- #> ContainsOnly[{c, a}, {a, b, c}, IgnoreCase -> True]
- : Unknown option IgnoreCase -> True in ContainsOnly.
- : Unknown option IgnoreCase in .
- = True
"""
attributes = A_PROTECTED | A_READ_PROTECTED
diff --git a/mathics/builtin/list/rearrange.py b/mathics/builtin/list/rearrange.py
index 952859860..d69a88c88 100644
--- a/mathics/builtin/list/rearrange.py
+++ b/mathics/builtin/list/rearrange.py
@@ -10,9 +10,9 @@
from itertools import chain
from typing import Callable
-from mathics.builtin.base import Builtin, MessageException
from mathics.core.atoms import Integer, Integer0
from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_PROTECTED
+from mathics.core.builtin import Builtin, MessageException
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression, structure
from mathics.core.expression_predefined import MATHICS3_INFINITY
@@ -542,17 +542,6 @@ class Complement(_SetOperation):
= f[w, y]
>> Complement[{c, b, a}]
= {a, b, c}
-
- #> Complement[a, b]
- : Non-atomic expression expected at position 1 in Complement[a, b].
- = Complement[a, b]
- #> Complement[f[a], g[b]]
- : Heads f and g at positions 1 and 2 are expected to be the same.
- = Complement[f[a], g[b]]
- #> Complement[{a, b, c}, {a, c}, SameTest->(True&)]
- = {}
- #> Complement[{a, b, c}, {a, c}, SameTest->(False&)]
- = {a, b, c}
"""
summary_text = "find the complement with respect to a universal set"
@@ -586,12 +575,6 @@ class DeleteDuplicates(_GatherOperation):
>> DeleteDuplicates[{3,2,1,2,3,4}, Less]
= {3, 2, 1}
-
- #> DeleteDuplicates[{3,2,1,2,3,4}, Greater]
- = {3, 3, 4}
-
- #> DeleteDuplicates[{}]
- = {}
"""
summary_text = "delete duplicate elements in a list"
@@ -658,38 +641,6 @@ class Flatten(Builtin):
Flatten also works in irregularly shaped arrays
>> Flatten[{{1, 2, 3}, {4}, {6, 7}, {8, 9, 10}}, {{2}, {1}}]
= {{1, 4, 6, 8}, {2, 7, 9}, {3, 10}}
-
- #> Flatten[{{1, 2}, {3, 4}}, {{-1, 2}}]
- : Levels to be flattened together in {{-1, 2}} should be lists of positive integers.
- = Flatten[{{1, 2}, {3, 4}}, {{-1, 2}}, List]
-
- #> Flatten[{a, b}, {{1}, {2}}]
- : Level 2 specified in {{1}, {2}} exceeds the levels, 1, which can be flattened together in {a, b}.
- = Flatten[{a, b}, {{1}, {2}}, List]
-
- ## Check `n` completion
- #> m = {{{1, 2}, {3}}, {{4}, {5, 6}}};
- #> Flatten[m, {{2}, {1}, {3}, {4}}]
- : Level 4 specified in {{2}, {1}, {3}, {4}} exceeds the levels, 3, which can be flattened together in {{{1, 2}, {3}}, {{4}, {5, 6}}}.
- = Flatten[{{{1, 2}, {3}}, {{4}, {5, 6}}}, {{2}, {1}, {3}, {4}}, List]
-
- ## Test from issue #251
- #> m = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
- #> Flatten[m, {3}]
- : Level 3 specified in {3} exceeds the levels, 2, which can be flattened together in {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}.
- = Flatten[{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, {3}, List]
-
- ## Reproduce strange head behaviour
- #> Flatten[{{1}, 2}, {1, 2}]
- : Level 2 specified in {1, 2} exceeds the levels, 1, which can be flattened together in {{1}, 2}.
- = Flatten[{{1}, 2}, {1, 2}, List]
- #> Flatten[a[b[1, 2], b[3]], {1, 2}, b] (* MMA BUG: {{1, 2}} not {1, 2} *)
- : Level 1 specified in {1, 2} exceeds the levels, 0, which can be flattened together in a[b[1, 2], b[3]].
- = Flatten[a[b[1, 2], b[3]], {1, 2}, b]
-
- #> Flatten[{{1, 2}, {3, {4}}}, {{1, 2, 3}}]
- : Level 3 specified in {{1, 2, 3}} exceeds the levels, 2, which can be flattened together in {{1, 2}, {3, {4}}}.
- = Flatten[{{1, 2}, {3, {4}}}, {{1, 2, 3}}, List]
"""
messages = {
@@ -899,16 +850,6 @@ class Join(Builtin):
>> Join[a + b, c * d]
: Heads Plus and Times are expected to be the same.
= Join[a + b, c d]
-
- #> Join[x, y]
- = Join[x, y]
- #> Join[x + y, z]
- = Join[x + y, z]
- #> Join[x + y, y z, a]
- : Heads Plus and Times are expected to be the same.
- = Join[x + y, y z, a]
- #> Join[x, y + z, y z]
- = Join[x, y + z, y z]
"""
attributes = A_FLAT | A_ONE_IDENTITY | A_PROTECTED
@@ -1032,9 +973,6 @@ class Partition(Builtin):
>> Partition[{a, b, c, d, e, f}, 3, 1]
= {{a, b, c}, {b, c, d}, {c, d, e}, {d, e, f}}
-
- #> Partition[{a, b, c, d, e}, 2]
- = {{a, b}, {c, d}}
"""
# TODO: Nested list length specifications
@@ -1206,20 +1144,6 @@ class Riffle(Builtin):
= {a, x, b, y, c, z}
>> Riffle[{a, b, c, d, e, f}, {x, y, z}]
= {a, x, b, y, c, z, d, x, e, y, f}
-
- #> Riffle[{1, 2, 3, 4}, {x, y, z, t}]
- = {1, x, 2, y, 3, z, 4, t}
- #> Riffle[{1, 2}, {1, 2, 3}]
- = {1, 1, 2}
- #> Riffle[{1, 2}, {1, 2}]
- = {1, 1, 2, 2}
-
- #> Riffle[{a,b,c}, {}]
- = {a, {}, b, {}, c}
- #> Riffle[{}, {}]
- = {}
- #> Riffle[{}, {a,b}]
- = {}
"""
summary_text = "intersperse additional elements"
@@ -1313,9 +1237,6 @@ class Split(Builtin):
>> Split[{x, x, x, y, x, y, y, z}]
= {{x, x, x}, {y}, {x}, {y, y}, {z}}
- #> Split[{x, x, x, y, x, y, y, z}, x]
- = {{x}, {x}, {x}, {y}, {x}, {y}, {y}, {z}}
-
Split into increasing or decreasing runs of elements
>> Split[{1, 5, 6, 3, 6, 1, 6, 3, 4, 5, 4}, Less]
= {{1, 5, 6}, {3, 6}, {1, 6}, {3, 4, 5}, {4}}
@@ -1326,14 +1247,6 @@ class Split(Builtin):
Split based on first element
>> Split[{x -> a, x -> y, 2 -> a, z -> c, z -> a}, First[#1] === First[#2] &]
= {{x -> a, x -> y}, {2 -> a}, {z -> c, z -> a}}
-
- #> Split[{}]
- = {}
-
- #> A[x__] := 321 /; Length[{x}] == 5;
- #> Split[A[x, x, x, y, x, y, y, z]]
- = 321
- #> ClearAll[A];
"""
rules = {
@@ -1387,9 +1300,6 @@ class SplitBy(Builtin):
>> SplitBy[{1, 2, 1, 1.2}, {Round, Identity}]
= {{{1}}, {{2}}, {{1}, {1.2}}}
-
- #> SplitBy[Tuples[{1, 2}, 3], First]
- = {{{1, 1, 1}, {1, 1, 2}, {1, 2, 1}, {1, 2, 2}}, {{2, 1, 1}, {2, 1, 2}, {2, 2, 1}, {2, 2, 2}}}
"""
messages = {
@@ -1494,9 +1404,6 @@ class Union(_SetOperation):
>> Union[{1, 2, 3}, {2, 3, 4}, SameTest->Less]
= {1, 2, 2, 3, 4}
-
- #> Union[{1, -1, 2}, {-2, 3}, SameTest -> (Abs[#1] == Abs[#2] &)]
- = {-2, 1, 3}
"""
summary_text = "enumerate all distinct elements in a list"
@@ -1533,9 +1440,6 @@ class Intersection(_SetOperation):
>> Intersection[{1, 2, 3}, {2, 3, 4}, SameTest->Less]
= {3}
-
- #> Intersection[{1, -1, -2, 2, -3}, {1, -2, 2, 3}, SameTest -> (Abs[#1] == Abs[#2] &)]
- = {-3, -2, 1}
"""
summary_text = "enumerate common elements"
diff --git a/mathics/builtin/mainloop.py b/mathics/builtin/mainloop.py
index 63d4ab110..e56194e3e 100644
--- a/mathics/builtin/mainloop.py
+++ b/mathics/builtin/mainloop.py
@@ -23,8 +23,8 @@
input, the second step listed above.
"""
-from mathics.builtin.base import Builtin
from mathics.core.attributes import A_LISTABLE, A_NO_ATTRIBUTES, A_PROTECTED
+from mathics.core.builtin import Builtin
# This tells documentation how to sort this module
sort_order = "mathics.builtin.the-main-loop"
@@ -222,56 +222,3 @@ class Line(Builtin):
name = "$Line"
summary_text = "current line number"
-
-
-class Out(Builtin):
- """
- :WMA: https://reference.wolfram.com/language/ref/$Out
-
-
'Out[$k$]'
-
'%$k$'
-
gives the result of the $k$th input line.
-
-
'%', '%%', etc.
-
gives the result of the previous input line, of the line before the previous input line, etc.
-
-
- >> 42
- = 42
- >> %
- = 42
- >> 43;
- >> %
- = 43
- >> 44
- = 44
- >> %1
- = 42
- >> %%
- = 44
- >> Hold[Out[-1]]
- = Hold[%]
- >> Hold[%4]
- = Hold[%4]
- >> Out[0]
- = Out[0]
-
- #> 10
- = 10
- #> Out[-1] + 1
- = 11
- #> Out[] + 1
- = 12
- """
-
- attributes = A_LISTABLE | A_PROTECTED
-
- rules = {
- "Out[k_Integer?Negative]": "Out[$Line + k]",
- "Out[]": "Out[$Line - 1]",
- "MakeBoxes[Out[k_Integer?((-10 <= # < 0)&)],"
- " f:StandardForm|TraditionalForm|InputForm|OutputForm]": r'StringJoin[ConstantArray["%%", -k]]',
- "MakeBoxes[Out[k_Integer?Positive],"
- " f:StandardForm|TraditionalForm|InputForm|OutputForm]": r'"%%" <> ToString[k]',
- }
- summary_text = "result of the Kth input line"
diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py
index 2c25b84db..afdd32408 100644
--- a/mathics/builtin/makeboxes.py
+++ b/mathics/builtin/makeboxes.py
@@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
"""
-Low level Format definitions
+Low-level Format definitions
"""
-from typing import Union
+from typing import Optional, Tuple, Union
import mpmath
-from mathics.builtin.base import Builtin, Predefined
from mathics.builtin.box.layout import RowBox, to_boxes
from mathics.core.atoms import Integer, Integer1, Real, String
from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_READ_PROTECTED
+from mathics.core.builtin import Builtin, Predefined
from mathics.core.convert.op import operator_to_ascii, operator_to_unicode
from mathics.core.element import BaseElement, BoxElementMixin
from mathics.core.evaluation import Evaluation
@@ -27,16 +27,22 @@
)
-def int_to_s_exp(expr, n):
- n = expr.get_int_value()
- if n < 0:
- nonnegative = 0
- s = str(-n)
+def int_to_tuple_info(integer: Integer) -> Tuple[str, int, bool]:
+ """
+ Convert ``integer`` to a tuple representing that value. The tuple consists of:
+ * the string absolute value of ``integer``.
+ * the exponent, base 10, to be used, and
+ * True if the value is nonnegative or False otherwise.
+ """
+ value = integer.value
+ if value < 0:
+ is_nonnegative = False
+ value = -value
else:
- nonnegative = 1
- s = str(n)
- exp = len(s) - 1
- return s, exp, nonnegative
+ is_nonnegative = True
+ s = str(value)
+ exponent = len(s) - 1
+ return s, exponent, is_nonnegative
# FIXME: op should be a string, so remove the Union.
@@ -65,95 +71,120 @@ def make_boxes_infix(
return Expression(SymbolRowBox, ListExpression(*result))
-def real_to_s_exp(expr, n):
- if expr.is_zero:
+def real_to_tuple_info(real: Real, digits: Optional[int]) -> Tuple[str, int, bool]:
+ """
+ Convert ``real`` to a tuple representing that value. The tuple consists of:
+ * the string absolute value of ``integer`` with decimal point removed from the string;
+ the position of the decimal point is determined by the exponent below,
+ * the exponent, base 10, to be used, and
+ * True if the value is nonnegative or False otherwise.
+
+ If ``digits`` is None, we use the default precision.
+ """
+ if real.is_zero:
s = "0"
- if expr.is_machine_precision():
- exp = 0
+ if real.is_machine_precision():
+ exponent = 0
else:
- p = expr.get_precision()
- exp = -dps(p)
- nonnegative = 1
+ p = real.get_precision()
+ exponent = -dps(p)
+ is_nonnegative = True
else:
- if n is None:
- if expr.is_machine_precision():
- value = expr.get_float_value()
+ if digits is None:
+ if real.is_machine_precision():
+ value = real.value
s = repr(value)
else:
- with mpmath.workprec(expr.get_precision()):
- value = expr.to_mpmath()
- s = mpmath.nstr(value, dps(expr.get_precision()) + 1)
+ with mpmath.workprec(real.get_precision()):
+ value = real.to_mpmath()
+ s = mpmath.nstr(value, dps(real.get_precision()) + 1)
else:
- with mpmath.workprec(expr.get_precision()):
- value = expr.to_mpmath()
- s = mpmath.nstr(value, n)
+ with mpmath.workprec(real.get_precision()):
+ value = real.to_mpmath()
+ s = mpmath.nstr(value, digits)
- # sign prefix
+ # Set sign prefix.
if s[0] == "-":
assert value < 0
- nonnegative = 0
+ is_nonnegative = False
s = s[1:]
else:
assert value >= 0
- nonnegative = 1
-
- # exponent (exp is actual, pexp is printed)
+ is_nonnegative = True
+ # Set exponent. ``exponent`` is actual, ``pexp`` of ``NumberForm_to_string()`` is printed.
if "e" in s:
- s, exp = s.split("e")
- exp = int(exp)
+ s, exponent = s.split("e")
+ exponent = int(exponent)
if len(s) > 1 and s[1] == ".":
# str(float) doesn't always include '.' if 'e' is present.
s = s[0] + s[2:].rstrip("0")
else:
- exp = s.index(".") - 1
- s = s[: exp + 1] + s[exp + 2 :].rstrip("0")
+ exponent = s.index(".") - 1
+ s = s[: exponent + 1] + s[exponent + 2 :].rstrip("0")
- # consume leading '0's.
+ # Normalize exponent: remove leading '0's after the decimal point
+ # and adjust the exponent accordingly.
i = 0
- while s[i] == "0":
+ while i < len(s) and s[i] == "0":
i += 1
- exp -= 1
+ exponent -= 1
s = s[i:]
- # add trailing zeros for precision reals
- if n is not None and not expr.is_machine_precision() and len(s) < n:
- s = s + "0" * (n - len(s))
- return s, exp, nonnegative
-
-
-def number_form(expr, n, f, evaluation: Evaluation, options: dict):
+ # Add trailing zeros for precision reals.
+ if digits is not None and not real.is_machine_precision() and len(s) < digits:
+ s = s + "0" * (digits - len(s))
+ return s, exponent, is_nonnegative
+
+
+# FIXME: the return type should be a NumberForm, not a String.
+# when this is fixed, rename the function.
+def NumberForm_to_String(
+ value: Union[Real, Integer],
+ digits: Optional[int],
+ digits_after_decimal_point: Optional[int],
+ evaluation: Evaluation,
+ options: dict,
+) -> String:
"""
- Converts a Real or Integer instance to Boxes.
+ Converts a Real or Integer value to a String.
- n digits of precision with f (can be None) digits after the decimal point.
- evaluation (can be None) is used for messages.
+ ``digits`` is the number of digits of precision and
+ ``digits_after_decimal_point`` is the number of digits after the
+ decimal point. ``evaluation`` is used for messages.
- The allowed options are python versions of the options permitted to
+ The allowed options are Python versions of the options permitted to
NumberForm and must be supplied. See NumberForm or Real.make_boxes
for correct option examples.
+
+ If ``digits`` is None, use the default precision. If
+ ``digits_after_decimal_points`` is None, use all the digits we get
+ from the converted number, that is, otherwise the number may be
+ padded on the right-hand side with zeros.
"""
- assert isinstance(n, int) and n > 0 or n is None
- assert f is None or (isinstance(f, int) and f >= 0)
+ assert isinstance(digits, int) and digits > 0 or digits is None
+ assert digits_after_decimal_point is None or (
+ isinstance(digits_after_decimal_point, int) and digits_after_decimal_point >= 0
+ )
is_int = False
- if isinstance(expr, Integer):
- assert n is not None
- s, exp, nonnegative = int_to_s_exp(expr, n)
- if f is None:
+ if isinstance(value, Integer):
+ assert digits is not None
+ s, exp, is_nonnegative = int_to_tuple_info(value)
+ if digits_after_decimal_point is None:
is_int = True
- elif isinstance(expr, Real):
- if n is not None:
- n = min(n, dps(expr.get_precision()) + 1)
- s, exp, nonnegative = real_to_s_exp(expr, n)
- if n is None:
- n = len(s)
+ elif isinstance(value, Real):
+ if digits is not None:
+ digits = min(digits, dps(value.get_precision()) + 1)
+ s, exp, is_nonnegative = real_to_tuple_info(value, digits)
+ if digits is None:
+ digits = len(s)
else:
raise ValueError("Expected Real or Integer.")
- assert isinstance(n, int) and n > 0
+ assert isinstance(digits, int) and digits > 0
- sign_prefix = options["NumberSigns"][nonnegative]
+ sign_prefix = options["NumberSigns"][1 if is_nonnegative else 0]
# round exponent to ExponentStep
rexp = (exp // options["ExponentStep"]) * options["ExponentStep"]
@@ -198,14 +229,18 @@ def _round(number, ndigits):
return number
# pad with NumberPadding
- if f is not None:
- if len(right) < f:
+ if digits_after_decimal_point is not None:
+ if len(right) < digits_after_decimal_point:
# pad right
- right = right + (f - len(right)) * options["NumberPadding"][1]
- elif len(right) > f:
+ right = (
+ right
+ + (digits_after_decimal_point - len(right))
+ * options["NumberPadding"][1]
+ )
+ elif len(right) > digits_after_decimal_point:
# round right
tmp = int(left + right)
- tmp = _round(tmp, f - len(right))
+ tmp = _round(tmp, digits_after_decimal_point - len(right))
tmp = str(tmp)
left, right = tmp[: exp + 1], tmp[exp + 1 :]
@@ -227,8 +262,8 @@ def split_string(s, start, step):
left_padding = 0
max_sign_len = max(len(options["NumberSigns"][0]), len(options["NumberSigns"][1]))
i = len(sign_prefix) + len(left) + len(right) - max_sign_len
- if i < n:
- left_padding = n - i
+ if i < digits:
+ left_padding = digits - i
elif len(sign_prefix) < max_sign_len:
left_padding = max_sign_len - len(sign_prefix)
left_padding = left_padding * options["NumberPadding"][0]
diff --git a/mathics/builtin/manipulate.py b/mathics/builtin/manipulate.py
index fd10692eb..87a3215c1 100644
--- a/mathics/builtin/manipulate.py
+++ b/mathics/builtin/manipulate.py
@@ -8,7 +8,7 @@
# from mathics import settings
-# from mathics.builtin.base import Builtin
+# from mathics.core.builtin import Builtin
# from mathics.core.atoms import Integer, String
# from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED
# from mathics.core.convert.python import from_python
@@ -88,8 +88,8 @@
# "System`Private`ManipulateParameter[{{s_Symbol, d_}, r__}]": "System`Private`ManipulateParameter[{Symbol -> s, Default -> d, Label -> s}, {r}]",
# "System`Private`ManipulateParameter[{{s_Symbol, d_, l_}, r__}]": "System`Private`ManipulateParameter[{Symbol -> s, Default -> d, Label -> l}, {r}]",
# # detect different kinds of widgets. on the use of the duplicate key "Default ->", see _WidgetInstantiator.add()
-# "System`Private`ManipulateParameter[var_, {min_?RealNumberQ, max_?RealNumberQ}]": 'Join[{Type -> "Continuous", Minimum -> min, Maximum -> max, Default -> min}, var]',
-# "System`Private`ManipulateParameter[var_, {min_?RealNumberQ, max_?RealNumberQ, step_?RealNumberQ}]": 'Join[{Type -> "Discrete", Minimum -> min, Maximum -> max, Step -> step, Default -> min}, var]',
+# "System`Private`ManipulateParameter[var_, {min_?RealValuedNumberQ, max_?RealValuedNumberQ}]": 'Join[{Type -> "Continuous", Minimum -> min, Maximum -> max, Default -> min}, var]',
+# "System`Private`ManipulateParameter[var_, {min_?RealValuedNumberQ, max_?RealValuedNumberQ, step_?RealValuedNumberQ}]": 'Join[{Type -> "Discrete", Minimum -> min, Maximum -> max, Step -> step, Default -> min}, var]',
# "System`Private`ManipulateParameter[var_, {opt_List}] /; Length[opt] > 0": 'Join[{Type -> "Options", Options -> opt, Default -> Part[opt, 1]}, var]',
# }
diff --git a/mathics/builtin/matrices/constrmatrix.py b/mathics/builtin/matrices/constrmatrix.py
index 9f5b24328..1bd4600a3 100644
--- a/mathics/builtin/matrices/constrmatrix.py
+++ b/mathics/builtin/matrices/constrmatrix.py
@@ -6,8 +6,8 @@
"""
import math
-from mathics.builtin.base import Builtin
from mathics.core.atoms import Integer0, Integer1, is_integer_rational_or_real
+from mathics.core.builtin import Builtin
from mathics.core.evaluation import Evaluation
from mathics.core.list import ListExpression
@@ -101,7 +101,7 @@ class DiamondMatrix(Builtin):
summary_text = "create a matrix with 1 in a diamond-shaped region, and 0 outside"
def eval(self, r, evaluation: Evaluation):
- "DiamondMatrix[r_?RealNumberQ]"
+ "DiamondMatrix[r_?RealValuedNumberQ]"
py_r = abs(r.round_to_float())
t = int(math.floor(0.5 + py_r))
@@ -138,7 +138,7 @@ class DiskMatrix(Builtin):
summary_text = "create a matrix with 1 in a disk-shaped region, and 0 outside"
def eval(self, r, evaluation: Evaluation):
- "DiskMatrix[r_?RealNumberQ]"
+ "DiskMatrix[r_?RealValuedNumberQ]"
py_r = abs(r.round_to_float())
s = int(math.floor(0.5 + py_r))
diff --git a/mathics/builtin/matrices/partmatrix.py b/mathics/builtin/matrices/partmatrix.py
index e9ace7d84..f87a3fe0b 100644
--- a/mathics/builtin/matrices/partmatrix.py
+++ b/mathics/builtin/matrices/partmatrix.py
@@ -7,7 +7,7 @@
"""
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
from mathics.core.evaluation import Evaluation
from mathics.core.list import ListExpression
diff --git a/mathics/builtin/messages.py b/mathics/builtin/messages.py
index 8c9c1b4b1..8e9815ee9 100644
--- a/mathics/builtin/messages.py
+++ b/mathics/builtin/messages.py
@@ -6,9 +6,9 @@
import typing
from typing import Any
-from mathics.builtin.base import BinaryOperator, Builtin, Predefined
from mathics.core.atoms import String
-from mathics.core.attributes import A_HOLD_ALL, A_HOLD_FIRST, A_PROTECTED
+from mathics.core.attributes import A_HOLD_ALL, A_HOLD_FIRST, A_LOCKED, A_PROTECTED
+from mathics.core.builtin import BinaryOperator, Builtin, Predefined
from mathics.core.evaluation import Evaluation, Message as EvaluationMessage
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
@@ -26,6 +26,7 @@ class Aborted(Predefined):
"""
+ attributes = A_LOCKED | A_PROTECTED
summary_text = "return value for aborted evaluations"
name = "$Aborted"
@@ -49,9 +50,6 @@ class Check(Builtin):
: Infinite expression 1 / 0 encountered.
= err
- #> Check[1^0, err]
- = 1
-
Check only for specific messages:
>> Check[Sin[0^0], err, Sin::argx]
: Indeterminate expression 0 ^ 0 encountered.
@@ -61,49 +59,7 @@ class Check(Builtin):
: Infinite expression 1 / 0 encountered.
= err
- #> Check[1 + 2]
- : Check called with 1 argument; 2 or more arguments are expected.
- = Check[1 + 2]
-
- #> Check[1 + 2, err, 3 + 1]
- : Message name 3 + 1 is not of the form symbol::name or symbol::name::language.
- = Check[1 + 2, err, 3 + 1]
-
- #> Check[1 + 2, err, hello]
- : Message name hello is not of the form symbol::name or symbol::name::language.
- = Check[1 + 2, err, hello]
-
- #> Check[1/0, err, Compile::cpbool]
- : Infinite expression 1 / 0 encountered.
- = ComplexInfinity
-
- #> Check[{0^0, 1/0}, err]
- : Indeterminate expression 0 ^ 0 encountered.
- : Infinite expression 1 / 0 encountered.
- = err
- #> Check[0^0/0, err, Power::indet]
- : Indeterminate expression 0 ^ 0 encountered.
- : Infinite expression 1 / 0 encountered.
- = err
-
- #> Check[{0^0, 3/0}, err, Power::indet]
- : Indeterminate expression 0 ^ 0 encountered.
- : Infinite expression 1 / 0 encountered.
- = err
-
- #> Check[1 + 2, err, {a::b, 2 + 5}]
- : Message name 2 + 5 is not of the form symbol::name or symbol::name::language.
- = Check[1 + 2, err, {a::b, 2 + 5}]
-
- #> Off[Power::infy]
- #> Check[1 / 0, err]
- = ComplexInfinity
-
- #> On[Power::infy]
- #> Check[1 / 0, err]
- : Infinite expression 1 / 0 encountered.
- = err
"""
attributes = A_HOLD_ALL | A_PROTECTED
@@ -175,10 +131,6 @@ class Failed(Predefined):
'$Failed'
is returned by some functions in the event of an error.
-
- #> Get["nonexistent_file.m"]
- : Cannot open nonexistent_file.m.
- = $Failed
"""
summary_text = "retrieved result for failed evaluations"
@@ -406,12 +358,6 @@ class Off(Builtin):
>> Off[Power::indet, Syntax::com]
>> {0 ^ 0,}
= {Indeterminate, Null}
-
- #> Off[1]
- : Message name 1 is not of the form symbol::name or symbol::name::language.
- #> Off[Message::name, 1]
-
- #> On[Power::infy, Power::indet, Syntax::com]
"""
attributes = A_HOLD_ALL | A_PROTECTED
@@ -457,11 +403,6 @@ class On(Builtin):
= ComplexInfinity
"""
- # TODO
- """
- #> On[f::x]
- : Message f::x not found.
- """
attributes = A_HOLD_ALL | A_PROTECTED
summary_text = "turn on a message for printing"
@@ -523,9 +464,6 @@ class Quiet(Builtin):
: Hello
= 2 x
- #> Quiet[expr, All, All]
- : Arguments 2 and 3 of Quiet[expr, All, All] should not both be All.
- = Quiet[expr, All, All]
>> Quiet[x + x, {a::b}, {a::b}]
: In Quiet[x + x, {a::b}, {a::b}] the message name(s) {a::b} appear in both the list of messages to switch off and the list of messages to switch on.
= Quiet[x + x, {a::b}, {a::b}]
@@ -638,76 +576,9 @@ class Syntax(Builtin):
>> 1.5``
: "1.5`" cannot be followed by "`" (line 1 of "").
-
- #> (x]
- : "(x" cannot be followed by "]" (line 1 of "").
-
- #> (x,)
- : "(x" cannot be followed by ",)" (line 1 of "").
-
- #> {x]
- : "{x" cannot be followed by "]" (line 1 of "").
-
- #> f[x)
- : "f[x" cannot be followed by ")" (line 1 of "").
-
- #> a[[x)]
- : "a[[x" cannot be followed by ")]" (line 1 of "").
-
- #> x /: y , z
- : "x /: y " cannot be followed by ", z" (line 1 of "").
-
- #> a :: 1
- : "a :: " cannot be followed by "1" (line 1 of "").
-
- #> a ? b ? c
- : "a ? b " cannot be followed by "? c" (line 1 of "").
-
- #> \:000G
- : 4 hexadecimal digits are required after \: to construct a 16-bit character (line 1 of "").
- : Expression cannot begin with "\:000G" (line 1 of "").
-
- #> \:000
- : 4 hexadecimal digits are required after \: to construct a 16-bit character (line 1 of "").
- : Expression cannot begin with "\:000" (line 1 of "").
-
- #> \009
- : 3 octal digits are required after \ to construct an 8-bit character (line 1 of "").
- : Expression cannot begin with "\009" (line 1 of "").
-
- #> \00
- : 3 octal digits are required after \ to construct an 8-bit character (line 1 of "").
- : Expression cannot begin with "\00" (line 1 of "").
-
- #> \.0G
- : 2 hexadecimal digits are required after \. to construct an 8-bit character (line 1 of "").
- : Expression cannot begin with "\.0G" (line 1 of "").
-
- #> \.0
- : 2 hexadecimal digits are required after \. to construct an 8-bit character (line 1 of "").
- : Expression cannot begin with "\.0" (line 1 of "").
-
- #> "abc \[fake]"
- : Unknown unicode longname "fake" (line 1 of "").
- = abc \[fake]
-
- #> a ~ b + c
- : "a ~ b " cannot be followed by "+ c" (line 1 of "").
-
- #> {1,}
- : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").
- = {1, Null}
- #> {, 1}
- : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").
- = {Null, 1}
- #> {,,}
- : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").
- : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").
- : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").
- = {Null, Null, Null}
"""
- # Extension: MMA does not provide lineno and filename in its error messages
+ # Extension: WMA does not provide lineno and filename in its error messages
messages = {
"snthex": r"4 hexadecimal digits are required after \: to construct a 16-bit character (line `4` of `5`).",
"sntoct1": r"3 octal digits are required after \ to construct an 8-bit character (line `4` of `5`).",
diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py
index 5cbbe3871..c62df6312 100644
--- a/mathics/builtin/numbers/algebra.py
+++ b/mathics/builtin/numbers/algebra.py
@@ -17,13 +17,12 @@
import sympy
-from mathics.algorithm.simplify import default_complexity_function
-from mathics.builtin.base import Builtin
from mathics.builtin.inference import evaluate_predicate
from mathics.builtin.options import options_to_rules
from mathics.builtin.scoping import dynamic_scoping
from mathics.core.atoms import Integer, Integer0, Integer1, Number, RationalOneHalf
from mathics.core.attributes import A_LISTABLE, A_PROTECTED
+from mathics.core.builtin import Builtin
from mathics.core.convert.python import from_bool
from mathics.core.convert.sympy import from_sympy, sympy_symbol_prefix
from mathics.core.element import BaseElement
@@ -64,7 +63,8 @@
SymbolTable,
SymbolTanh,
)
-from mathics.eval.numbers import cancel, sympy_factor
+from mathics.eval.numbers.algebra.simplify import default_complexity_function
+from mathics.eval.numbers.numbers import cancel, sympy_factor
from mathics.eval.parts import walk_parts
from mathics.eval.patterns import match
@@ -373,12 +373,6 @@ class Apart(Builtin):
But it does not touch other expressions:
>> Sin[1 / (x ^ 2 - y ^ 2)] // Apart
= Sin[1 / (x ^ 2 - y ^ 2)]
-
- #> Attributes[f] = {HoldAll}; Apart[f[x + x]]
- = f[x + x]
-
- #> Attributes[f] = {}; Apart[f[x + x]]
- = f[2 x]
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -504,25 +498,9 @@ class Coefficient(Builtin):
>> Coefficient[a x^2 + b y^3 + c x + d y + 5, x, 0]
= 5 + b y ^ 3 + d y
- ## Errors:
- #> Coefficient[x + y + 3]
- : Coefficient called with 1 argument; 2 or 3 arguments are expected.
- = Coefficient[3 + x + y]
- #> Coefficient[x + y + 3, 5]
- : 5 is not a valid variable.
- = Coefficient[3 + x + y, 5]
-
- ## This is known bug of Sympy 1.0, next Sympy version will fix it by this commit
- ## https://github.com/sympy/sympy/commit/25bf64b64d4d9a2dc563022818d29d06bc740d47
- ## #> Coefficient[x * y, z, 0]
- ## = x y
- ## ## Sympy 1.0 retuns 0
-
## ## TODO: Support Modulus
## >> Coefficient[(x + 2)^3 + (x + 3)^2, x, 0, Modulus -> 3]
## = 2
- ## #> Coefficient[(x + 2)^3 + (x + 3)^2, x, 0, {Modulus -> 3, Modulus -> 2, Modulus -> 10}]
- ## = {2, 1, 7}
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -867,7 +845,7 @@ def eval_list(self, polys, varlist, evaluation: Evaluation, options: dict):
SymbolTable,
Integer(0),
ListExpression(Integer(dim1)),
- *its2
+ *its2,
)
else:
newtable = Expression(SymbolTable, Integer(0), *its2)
@@ -910,21 +888,11 @@ class CoefficientList(Builtin):
= {2 / (-3 + y), 1 / (-3 + y) + 1 / (-2 + y)}
>> CoefficientList[(x + y)^3, z]
= {(x + y) ^ 3}
- #> CoefficientList[x + y, 5]
- : 5 is not a valid variable.
- = CoefficientList[x + y, 5]
-
## Form 2 CoefficientList[poly, {var1, var2, ...}]
>> CoefficientList[a x^2 + b y^3 + c x + d y + 5, {x, y}]
= {{5, d, 0, b}, {c, 0, 0, 0}, {a, 0, 0, 0}}
>> CoefficientList[(x - 2 y + 3 z)^3, {x, y, z}]
= {{{0, 0, 0, 27}, {0, 0, -54, 0}, {0, 36, 0, 0}, {-8, 0, 0, 0}}, {{0, 0, 27, 0}, {0, -36, 0, 0}, {12, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 9, 0, 0}, {-6, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{1, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}}
- #> CoefficientList[(x - 2 y)^4, {x, 2}]
- : 2 is not a valid variable.
- = CoefficientList[(x - 2 y) ^ 4, {x, 2}]
- #> CoefficientList[x / y, {x, y}]
- : x / y is not a polynomial.
- = CoefficientList[x / y, {x, y}]
"""
messages = {
@@ -984,7 +952,7 @@ def eval(self, expr, form, evaluation):
self.__class__.__name__, expr, form, Integer(n), evaluation
)
for n in range(dimensions[0] + 1)
- ]
+ ],
)
elif form.has_form("List", 1):
form = form.elements[0]
@@ -995,7 +963,7 @@ def eval(self, expr, form, evaluation):
self.__class__.__name__, expr, form, Integer(n), evaluation
)
for n in range(dimensions[0] + 1)
- ]
+ ],
)
else:
@@ -1098,7 +1066,6 @@ def eval(self, expr, evaluation):
class _Expand(Builtin):
-
options = {
"Trig": "False",
"Modulus": "0",
@@ -1182,22 +1149,6 @@ class Expand(_Expand):
>> Expand[(1 + a)^12, Modulus -> 4]
= 1 + 2 a ^ 2 + 3 a ^ 4 + 3 a ^ 8 + 2 a ^ 10 + a ^ 12
-
- #> Expand[x, Modulus -> -1] (* copy odd MMA behaviour *)
- = 0
- #> Expand[x, Modulus -> x]
- : Value of option Modulus -> x should be an integer.
- = Expand[x, Modulus -> x]
-
- #> a(b(c+d)+e) // Expand
- = a b c + a b d + a e
-
- #> (y^2)^(1/2)/(2x+2y)//Expand
- = Sqrt[y ^ 2] / (2 x + 2 y)
-
-
- #> 2(3+2x)^2/(5+x^2+3x)^3 // Expand
- = 24 x / (5 + 3 x + x ^ 2) ^ 3 + 8 x ^ 2 / (5 + 3 x + x ^ 2) ^ 3 + 18 / (5 + 3 x + x ^ 2) ^ 3
"""
summary_text = "expand out products and powers"
@@ -1303,15 +1254,6 @@ class ExpandDenominator(_Expand):
>> ExpandDenominator[(a + b) ^ 2 / ((c + d)^2 (e + f))]
= (a + b) ^ 2 / (c ^ 2 e + c ^ 2 f + 2 c d e + 2 c d f + d ^ 2 e + d ^ 2 f)
-
- ## Modulus option
- #> ExpandDenominator[1 / (x + y)^3, Modulus -> 3]
- = 1 / (x ^ 3 + y ^ 3)
- #> ExpandDenominator[1 / (x + y)^6, Modulus -> 4]
- = 1 / (x ^ 6 + 2 x ^ 5 y + 3 x ^ 4 y ^ 2 + 3 x ^ 2 y ^ 4 + 2 x y ^ 5 + y ^ 6)
-
- #> ExpandDenominator[2(3+2x)^2/(5+x^2+3x)^3]
- = 2 (3 + 2 x) ^ 2 / (125 + 225 x + 210 x ^ 2 + 117 x ^ 3 + 42 x ^ 4 + 9 x ^ 5 + x ^ 6)
"""
summary_text = "expand just the denominator of a rational expression"
@@ -1354,11 +1296,6 @@ class Exponent(Builtin):
= -Infinity
>> Exponent[1, x]
= 0
-
- ## errors:
- #> Exponent[x^2]
- : Exponent called with 1 argument; 2 or 3 arguments are expected.
- = Exponent[x ^ 2]
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -1422,10 +1359,6 @@ class Factor(Builtin):
You can use Factor to find when a polynomial is zero:
>> x^2 - x == 0 // Factor
= x (-1 + x) == 0
-
- ## Issue659
- #> Factor[{x+x^2}]
- = {x (1 + x)}
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -1467,9 +1400,6 @@ class FactorTermsList(Builtin):
= {2, -1 + x ^ 2}
>> FactorTermsList[x^2 - 2 x + 1]
= {1, 1 - 2 x + x ^ 2}
- #> FactorTermsList[2 x^2 - 2, x]
- = {2, 1, -1 + x ^ 2}
-
>> f = 3 (-1 + 2 x) (-1 + y) (1 - a)
= 3 (-1 + 2 x) (-1 + y) (1 - a)
>> FactorTermsList[f]
@@ -1775,17 +1705,6 @@ class MinimalPolynomial(Builtin):
= -2 - 2 x ^ 2 + x ^ 4
>> MinimalPolynomial[Sqrt[I + Sqrt[6]], x]
= 49 - 10 x ^ 4 + x ^ 8
-
- #> MinimalPolynomial[7a, x]
- : 7 a is not an explicit algebraic number.
- = MinimalPolynomial[7 a, x]
- #> MinimalPolynomial[3x^3 + 2x^2 + y^2 + ab, x]
- : ab + 2 x ^ 2 + 3 x ^ 3 + y ^ 2 is not an explicit algebraic number.
- = MinimalPolynomial[ab + 2 x ^ 2 + 3 x ^ 3 + y ^ 2, x]
-
- ## PurePoly
- #> MinimalPolynomial[Sqrt[2 + Sqrt[3]]]
- = 1 - 4 #1 ^ 2 + #1 ^ 4
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -1874,37 +1793,6 @@ class PolynomialQ(Builtin):
= True
>> PolynomialQ[x^2 + axy^2 - bSin[c], {a, b, c}]
= False
-
- #> PolynomialQ[x, x, y]
- : PolynomialQ called with 3 arguments; 1 or 2 arguments are expected.
- = PolynomialQ[x, x, y]
-
- ## Always return True if argument is Null
- #> PolynomialQ[x^3 - 2 x/y + 3xz,]
- : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").
- = True
- #> PolynomialQ[, {x, y, z}]
- : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").
- = True
- #> PolynomialQ[, ]
- : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").
- : Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").
- = True
-
- ## TODO: MMA and Sympy handle these cases differently
- ## #> PolynomialQ[x^(1/2) + 6xyz]
- ## : No variable is not supported in PolynomialQ.
- ## = True
- ## #> PolynomialQ[x^(1/2) + 6xyz, {}]
- ## : No variable is not supported in PolynomialQ.
- ## = True
-
- ## #> PolynomialQ[x^3 - 2 x/y + 3xz]
- ## : No variable is not supported in PolynomialQ.
- ## = False
- ## #> PolynomialQ[x^3 - 2 x/y + 3xz, {}]
- ## : No variable is not supported in PolynomialQ.
- ## = False
"""
messages = {
@@ -1994,9 +1882,6 @@ class Together(Builtin):
But it does not touch other functions:
>> Together[f[a / c + b / c]]
= f[a / c + b / c]
-
- #> f[x]/x+f[x]/x^2//Together
- = f[x] (1 + x) / x ^ 2
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -2030,9 +1915,6 @@ class Variables(Builtin):
= {a, b, c, x, y}
>> Variables[x + Sin[y]]
= {x, Sin[y]}
- ## failing test case from MMA docs
- #> Variables[E^x]
- = {}
"""
summary_text = "list of variables in a polynomial"
diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py
index 3287d35c7..1a4661883 100644
--- a/mathics/builtin/numbers/calculus.py
+++ b/mathics/builtin/numbers/calculus.py
@@ -15,19 +15,6 @@
import numpy as np
import sympy
-from mathics.algorithm.integrators import (
- _fubini,
- _internal_adaptative_simpsons_rule,
- decompose_domain,
- eval_D_to_Integral,
-)
-from mathics.algorithm.series import (
- build_series,
- series_derivative,
- series_plus_series,
- series_times_series,
-)
-from mathics.builtin.base import Builtin, PostfixOperator, SympyFunction
from mathics.builtin.scoping import dynamic_scoping
from mathics.core.atoms import (
Atom,
@@ -49,6 +36,7 @@
A_PROTECTED,
A_READ_PROTECTED,
)
+from mathics.core.builtin import Builtin, PostfixOperator, SympyFunction
from mathics.core.convert.expression import to_expression, to_mathics_list
from mathics.core.convert.function import expression_to_callable_and_args
from mathics.core.convert.python import from_python
@@ -90,6 +78,18 @@
)
from mathics.eval.makeboxes import format_element
from mathics.eval.nevaluator import eval_N
+from mathics.eval.numbers.calculus.integrators import (
+ _fubini,
+ _internal_adaptative_simpsons_rule,
+ decompose_domain,
+ eval_D_to_Integral,
+)
+from mathics.eval.numbers.calculus.series import (
+ build_series,
+ series_derivative,
+ series_plus_series,
+ series_times_series,
+)
# These should be used in lower-level formatting
SymbolDifferentialD = Symbol("System`DifferentialD")
@@ -172,24 +172,6 @@ class D(SympyFunction):
Hesse matrix:
>> D[Sin[x] * Cos[y], {{x,y}, 2}]
= {{-Cos[y] Sin[x], -Cos[x] Sin[y]}, {-Cos[x] Sin[y], -Cos[y] Sin[x]}}
-
- #> D[2/3 Cos[x] - 1/3 x Cos[x] Sin[x] ^ 2,x]//Expand
- = -2 x Cos[x] ^ 2 Sin[x] / 3 + x Sin[x] ^ 3 / 3 - 2 Sin[x] / 3 - Cos[x] Sin[x] ^ 2 / 3
-
- #> D[f[#1], {#1,2}]
- = f''[#1]
- #> D[(#1&)[t],{t,4}]
- = 0
-
- #> Attributes[f] ={HoldAll}; Apart[f''[x + x]]
- = f''[2 x]
-
- #> Attributes[f] = {}; Apart[f''[x + x]]
- = f''[2 x]
-
- ## Issue #375
- #> D[{#^2}, #]
- = {2 #1}
"""
# TODO
@@ -416,16 +398,6 @@ class Derivative(PostfixOperator, SympyFunction):
= Derivative[2, 1][h]
>> Derivative[2, 0, 1, 0][h[g]]
= Derivative[2, 0, 1, 0][h[g]]
-
- ## Parser Tests
- #> Hold[f''] // FullForm
- = Hold[Derivative[2][f]]
- #> Hold[f ' '] // FullForm
- = Hold[Derivative[2][f]]
- #> Hold[f '' ''] // FullForm
- = Hold[Derivative[4][f]]
- #> Hold[Derivative[x][4] '] // FullForm
- = Hold[Derivative[1][Derivative[x][4]]]
"""
attributes = A_N_HOLD_ALL
@@ -445,29 +417,59 @@ class Derivative(PostfixOperator, SympyFunction):
"Derivative[0...][f_]": "f",
"Derivative[n__Integer][Derivative[m__Integer][f_]] /; Length[{m}] "
"== Length[{n}]": "Derivative[Sequence @@ ({n} + {m})][f]",
- # This would require at least some comments...
+ "Derivative[n__Integer][Alternatives[_Integer|_Rational|_Real|_Complex]]": "0 &",
+ # The following rule tries to evaluate a derivative of a pure function by applying it to a list
+ # of symbolic elements and use the rules in `D`.
+ # The rule just applies if f is not a locked symbol, and it does not have a previous definition
+ # for its `Derivative`.
+ # The main drawback of this implementation is that it requires to compute two times the derivative,
+ # just because the way in which the evaluation loop works, and the lack of a working `Unevaluated`
+ # symbol. In our current implementation, the a better way to implement this would be through a builtin
+ # rule (i.e., an eval_ method).
"""Derivative[n__Integer][f_Symbol] /; Module[{t=Sequence@@Slot/@Range[Length[{n}]], result, nothing, ft=f[t]},
- If[Head[ft] === f
+ If[
+ (*If the head of ft is f, and it does not have a previos defintion of derivative, and the context is `System,
+ the rule fails:
+ *)
+ Head[ft] === f
&& FreeQ[Join[UpValues[f], DownValues[f], SubValues[f]], Derivative|D]
&& Context[f] != "System`",
False,
- (* else *)
+ (* else, evaluate ft, set the order n derivative of f to "nothing" and try to evaluate it *)
ft = f[t];
Block[{f},
- Unprotect[f];
- (*Derivative[1][f] ^= nothing;*)
- Derivative[n][f] ^= nothing;
- Derivative[n][nothing] ^= nothing;
+ (*
+ The idea of the test is to set `Derivative[n][f]` to `nothing`. Then, the derivative is
+ evaluated. If it is not possible to find an explicit expression for the derivative,
+ then their occurencies are replaced by `nothing`. Therefore, if the resulting expression
+ if free of `nothing`, then we can use the result. Otherwise, the rule does not work.
+
+ Differently from `True` and `False`, `List` does not produce an infinite recurrence,
+ but since is a protected symbol, the following test produces error messages.
+ Let's put this inside Quiet to avoid the warnings.
+ *)
+ Quiet[Unprotect[f];
+ Derivative[n][f] ^= nothing;
+ Derivative[n][nothing] ^= nothing;
+ ];
result = D[ft, Sequence@@Table[{Slot[i], {n}[[i]]}, {i, Length[{n}]}]];
];
+ (*The rule applies if `nothing` disappeared in the result*)
FreeQ[result, nothing]
]
- ]""": """Module[{t=Sequence@@Slot/@Range[Length[{n}]], result, nothing, ft},
+ ]""": """
+ (*
+ Provided the assumptions, the derivative of F[#1,#2,...] is evaluated,
+ and returned a an anonymous function.
+ *)
+ Module[{t=Sequence@@Slot/@Range[Length[{n}]], result, nothing, ft},
ft = f[t];
Block[{f},
- Unprotect[f];
- Derivative[n][f] ^= nothing;
- Derivative[n][nothing] ^= nothing;
+ Quiet[
+ Unprotect[f];
+ Derivative[n][f] ^= nothing;
+ Derivative[n][nothing] ^= nothing;
+ ];
result = D[ft, Sequence@@Table[{Slot[i], {n}[[i]]}, {i, Length[{n}]}]];
];
Function @@ {result}
@@ -483,6 +485,17 @@ class Derivative(PostfixOperator, SympyFunction):
def __init__(self, *args, **kwargs):
super(Derivative, self).__init__(*args, **kwargs)
+ def eval_locked_symbols(self, n, **kwargs):
+ """Derivative[n__Integer][Alternatives[True|False|Symbol|TooBig|$Aborted|Removed|Locked|$PrintLiteral|$Off]]"""
+ # Prevents the evaluation for True, False, and other Locked symbols
+ # as function names. This produces a recursion error in the evaluation rule for Derivative.
+ # See
+ # https://github.com/Mathics3/mathics-core/issues/971#issuecomment-1902814462
+ # in issue #971
+ # An alternative would be to reformulate the long rule.
+ # TODO: Add other locked symbols producing the same error.
+ return
+
def to_sympy(self, expr, **kwargs):
inner = expr
exprs = [inner]
@@ -697,7 +710,6 @@ def diff(evaluation):
def eval_with_x_tuple(self, f, xtuple, evaluation: Evaluation, options: dict):
"%(name)s[f_, xtuple_, OptionsPattern[]]"
f_val = f.evaluate(evaluation)
-
if f_val.has_form("Equal", 2):
f = Expression(SymbolPlus, f_val.elements[0], f_val.elements[1])
@@ -749,7 +761,9 @@ class FindMaximum(_BaseFinder):
messages = _BaseFinder.messages.copy()
summary_text = "local maximum optimization"
try:
- from mathics.algorithm.optimizers import native_local_optimizer_methods
+ from mathics.eval.numbers.calculus.optimizers import (
+ native_local_optimizer_methods,
+ )
methods.update(native_local_optimizer_methods)
except Exception:
@@ -798,7 +812,7 @@ class FindMinimum(_BaseFinder):
messages = _BaseFinder.messages.copy()
summary_text = "local minimum optimization"
try:
- from mathics.algorithm.optimizers import (
+ from mathics.eval.numbers.calculus.optimizers import (
native_local_optimizer_methods,
native_optimizer_messages,
)
@@ -865,12 +879,8 @@ class FindRoot(_BaseFinder):
= FindRoot[Sin[x] - x, {x, 0}]
- #> FindRoot[2.5==x,{x,0}]
- = {x -> 2.5}
-
>> FindRoot[x^2 - 2, {x, 1,3}, Method->"Secant"]
= {x -> 1.41421}
-
"""
rules = {
@@ -884,7 +894,7 @@ class FindRoot(_BaseFinder):
)
try:
- from mathics.algorithm.optimizers import (
+ from mathics.eval.numbers.calculus.optimizers import (
native_findroot_messages,
native_findroot_methods,
)
@@ -971,20 +981,6 @@ class Integrate(SympyFunction):
>> Integrate[f[x], {x, a, b}] // TeXForm
= \int_a^b f\left[x\right] \, dx
- #> DownValues[Integrate]
- = {}
- #> Definition[Integrate]
- = Attributes[Integrate] = {Protected, ReadProtected}
- .
- . Options[Integrate] = {Assumptions -> $Assumptions, GenerateConditions -> Automatic, PrincipalValue -> False}
- #> Integrate[Hold[x + x], {x, a, b}]
- = Integrate[Hold[x + x], {x, a, b}]
- #> Integrate[sin[x], x]
- = Integrate[sin[x], x]
-
- #> Integrate[x ^ 3.5 + x, x]
- = x ^ 2 / 2 + 0.222222 x ^ 4.5
-
Sometimes there is a loss of precision during integration.
You can check the precision of your result with the following sequence
of commands.
@@ -993,20 +989,6 @@ class Integrate(SympyFunction):
>> % // Precision
= MachinePrecision
- #> Integrate[1/(x^5+1), x]
- = RootSum[1 + 5 #1 + 25 #1 ^ 2 + 125 #1 ^ 3 + 625 #1 ^ 4&, Log[x + 5 #1] #1&] + Log[1 + x] / 5
-
- #> Integrate[ArcTan(x), x]
- = x ^ 2 ArcTan / 2
- #> Integrate[E[x], x]
- = Integrate[E[x], x]
-
- #> Integrate[Exp[-(x/2)^2],{x,-Infinity,+Infinity}]
- = 2 Sqrt[Pi]
-
- #> Integrate[Exp[-1/(x^2)], x]
- = x E ^ (-1 / x ^ 2) + Sqrt[Pi] Erf[1 / x]
-
>> Integrate[ArcSin[x / 3], x]
= x ArcSin[x / 3] + Sqrt[9 - x ^ 2]
@@ -1350,7 +1332,7 @@ class NIntegrate(Builtin):
try:
# builtin integrators
- from mathics.algorithm.integrators import (
+ from mathics.eval.numbers.calculus.integrators import (
integrator_messages,
integrator_methods,
)
@@ -2217,7 +2199,7 @@ class Solve(Builtin):
rules = {
"Solve[eqs_, vars_, Complexes]": "Solve[eqs, vars]",
"Solve[eqs_, vars_, Reals]": (
- "Cases[Solve[eqs, vars], {Rule[x_,y_?RealNumberQ]}]"
+ "Cases[Solve[eqs, vars], {Rule[x_,y_?RealValuedNumberQ]}]"
),
"Solve[eqs_, vars_, Integers]": (
"Cases[Solve[eqs, vars], {Rule[x_,y_Integer]}]"
@@ -2240,7 +2222,6 @@ def eval(self, eqs, vars, evaluation: Evaluation):
or head_name in ("System`Plus", "System`Times", "System`Power") # noqa
or A_CONSTANT & var.get_attributes(evaluation.definitions)
):
-
evaluation.message("Solve", "ivar", vars_original)
return
if eqs.get_head_name() in ("System`List", "System`And"):
diff --git a/mathics/builtin/numbers/constants.py b/mathics/builtin/numbers/constants.py
index 73779ff6a..119f94330 100644
--- a/mathics/builtin/numbers/constants.py
+++ b/mathics/builtin/numbers/constants.py
@@ -16,9 +16,9 @@
import numpy
import sympy
-from mathics.builtin.base import Builtin, Predefined, SympyObject
from mathics.core.atoms import NUMERICAL_CONSTANTS, MachineReal, PrecisionReal
from mathics.core.attributes import A_CONSTANT, A_PROTECTED, A_READ_PROTECTED
+from mathics.core.builtin import Builtin, Predefined, SympyObject
from mathics.core.element import BaseElement
from mathics.core.evaluation import Evaluation
from mathics.core.number import MACHINE_DIGITS, PrecisionValueError, get_precision, prec
@@ -246,7 +246,7 @@ class ComplexInfinity(_SympyConstant):
is an infinite number in the complex plane whose complex argument \
is unknown or undefined. (
:SymPy:
- https://docs.sympy.org/latest/modules/core.html?highlight=zoo#complexinfinity,
+ https://docs.sympy.org/latest/modules/core.html#sympy.core.numbers.ComplexInfinity,
:MathWorld:
https://mathworld.wolfram.com/ComplexInfinity.html,
:WMA:
@@ -257,20 +257,21 @@ class ComplexInfinity(_SympyConstant):
represents an infinite complex quantity of undetermined direction.
+ ComplexInfinity can appear as the result of a computation such as dividing by zero:
+ >> 1 / 0
+ : Infinite expression 1 / 0 encountered.
+ = ComplexInfinity
+
+ But it can be used as an explicit value in an expression:
>> 1 / ComplexInfinity
= 0
+
>> ComplexInfinity * Infinity
= ComplexInfinity
+
+ ComplexInfinity though is a special case of DirectedInfinity:
>> FullForm[ComplexInfinity]
= DirectedInfinity[]
-
- ## Issue689
- #> ComplexInfinity + ComplexInfinity
- : Indeterminate expression ComplexInfinity + ComplexInfinity encountered.
- = Indeterminate
- #> ComplexInfinity + Infinity
- : Indeterminate expression ComplexInfinity + Infinity encountered.
- = Indeterminate
"""
summary_text = "infinite complex quantity of undetermined direction"
@@ -302,15 +303,6 @@ class Degree(_MPMathConstant, _NumpyConstant, _SympyConstant):
>> N[\\[Degree]] == N[Degree]
= True
-
- #> Cos[Degree[x]]
- = Cos[Degree[x]]
-
-
- #> N[Degree]
- = 0.0174533
- #> N[Degree, 30]
- = 0.0174532925199432957692369076849
"""
summary_text = "conversion factor from radians to degrees"
@@ -367,9 +359,6 @@ class E(_MPMathConstant, _NumpyConstant, _SympyConstant):
= 2.71828
>> N[E, 50]
= 2.7182818284590452353602874713526624977572470937000
-
- #> 5. E
- = 13.5914
"""
summary_text = "exponential constant E ≃ 2.7182"
@@ -512,16 +501,6 @@ class Infinity(_SympyConstant):
Use 'Infinity' in sum and limit calculations:
>> Sum[1/x^2, {x, 1, Infinity}]
= Pi ^ 2 / 6
-
- #> FullForm[Infinity]
- = DirectedInfinity[1]
- #> (2 + 3.5*I) / Infinity
- = 0.
- #> Infinity + Infinity
- = Infinity
- #> Infinity / Infinity
- : Indeterminate expression 0 Infinity encountered.
- = Indeterminate
"""
sympy_name = "oo"
diff --git a/mathics/builtin/numbers/diffeqns.py b/mathics/builtin/numbers/diffeqns.py
index 9f158f48c..1b1d5b26c 100644
--- a/mathics/builtin/numbers/diffeqns.py
+++ b/mathics/builtin/numbers/diffeqns.py
@@ -6,7 +6,7 @@
import sympy
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
from mathics.core.convert.sympy import from_sympy
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
@@ -42,48 +42,6 @@ class DSolve(Builtin):
>> DSolve[D[y[x, t], t] + 2 D[y[x, t], x] == 0, y[x, t], {x, t}]
= {{y[x, t] -> C[1][x - 2 t]}}
-
- ## FIXME: sympy solves this as `Function[{x}, C[1] + Integrate[ArcSin[f[2 x]], x]]`
- ## #> Attributes[f] = {HoldAll};
- ## #> DSolve[f[x + x] == Sin[f'[x]], f, x]
- ## : To avoid possible ambiguity, the arguments of the dependent variable in f[x + x] == Sin[f'[x]] should literally match the independent variables.
- ## = DSolve[f[x + x] == Sin[f'[x]], f, x]
-
- ## #> Attributes[f] = {};
- ## #> DSolve[f[x + x] == Sin[f'[x]], f, x]
- ## : To avoid possible ambiguity, the arguments of the dependent variable in f[2 x] == Sin[f'[x]] should literally match the independent variables.
- ## = DSolve[f[2 x] == Sin[f'[x]], f, x]
-
- #> DSolve[f'[x] == f[x], f, x] // FullForm
- = {{Rule[f, Function[{x}, Times[C[1], Power[E, x]]]]}}
-
- #> DSolve[f'[x] == f[x], f, x] /. {C[1] -> 1}
- = {{f -> (Function[{x}, 1 E ^ x])}}
-
- #> DSolve[f'[x] == f[x], f, x] /. {C -> D}
- = {{f -> (Function[{x}, D[1] E ^ x])}}
-
- #> DSolve[f'[x] == f[x], f, x] /. {C[1] -> C[0]}
- = {{f -> (Function[{x}, C[0] E ^ x])}}
-
- #> DSolve[f[x] == 0, f, {}]
- : {} cannot be used as a variable.
- = DSolve[f[x] == 0, f, {}]
-
- ## Order of arguments shoudn't matter
- #> DSolve[D[f[x, y], x] == D[f[x, y], y], f, {x, y}]
- = {{f -> (Function[{x, y}, C[1][-x - y]])}}
- #> DSolve[D[f[x, y], x] == D[f[x, y], y], f[x, y], {x, y}]
- = {{f[x, y] -> C[1][-x - y]}}
- #> DSolve[D[f[x, y], x] == D[f[x, y], y], f[x, y], {y, x}]
- = {{f[x, y] -> C[1][-x - y]}}
- """
-
- # XXX sympy #11669 test
- """
- #> DSolve[\\[Gamma]'[x] == 0, \\[Gamma], x]
- : Hit sympy bug #11669.
- = ...
"""
# TODO: GeneratedParameters option
@@ -202,7 +160,7 @@ def eval(self, eqn, y, x, evaluation: Evaluation):
Expression(
SymbolFunction,
function_form,
- *from_sympy(soln).elements[1:]
+ *from_sympy(soln).elements[1:],
),
),
)
diff --git a/mathics/builtin/numbers/exp.py b/mathics/builtin/numbers/exp.py
index 9ad654462..7156ef212 100644
--- a/mathics/builtin/numbers/exp.py
+++ b/mathics/builtin/numbers/exp.py
@@ -3,7 +3,8 @@
"""
Exponential Functions
-Numerical values and derivatives can be computed; however, most special exact values and simplification rules are not implemented yet.
+Numerical values and derivatives can be computed; however, most special exact values \
+and simplification rules are not implemented yet.
"""
import math
@@ -13,10 +14,9 @@
import mpmath
-from mathics.builtin.arithmetic import _MPMathFunction
-from mathics.builtin.base import Builtin
from mathics.core.atoms import Real
from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED
+from mathics.core.builtin import Builtin, MPMathFunction
from mathics.core.convert.python import from_python
from mathics.core.expression import Expression
from mathics.core.symbols import Symbol, SymbolPower
@@ -157,7 +157,7 @@ def converted_operands():
init = y
-class Exp(_MPMathFunction):
+class Exp(MPMathFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/Exp.html
@@ -176,9 +176,6 @@ class Exp(_MPMathFunction):
>> Plot[Exp[x], {x, 0, 3}]
= -Graphics-
- #> Exp[1.*^20]
- : Overflow occurred in computation.
- = Overflow[]
"""
rules = {
@@ -191,7 +188,7 @@ def from_sympy(self, sympy_name, elements):
return Expression(SymbolPower, SymbolE, elements[0])
-class Log(_MPMathFunction):
+class Log(MPMathFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/Log.html
@@ -206,21 +203,6 @@ class Log(_MPMathFunction):
= Indeterminate
>> Plot[Log[x], {x, 0, 5}]
= -Graphics-
-
- #> Log[1000] / Log[10] // Simplify
- = 3
-
- #> Log[1.4]
- = 0.336472
-
- #> Log[Exp[1.4]]
- = 1.4
-
- #> Log[-1.4]
- = 0.336472 + 3.14159 I
-
- #> N[Log[10], 30]
- = 2.30258509299404568401799145468
"""
summary_text = "logarithm function"
@@ -316,9 +298,6 @@ class LogisticSigmoid(Builtin):
>> LogisticSigmoid[{-0.2, 0.1, 0.3}]
= {0.450166, 0.524979, 0.574443}
-
- #> LogisticSigmoid[I Pi]
- = LogisticSigmoid[I Pi]
"""
summary_text = "logistic function"
diff --git a/mathics/builtin/numbers/hyperbolic.py b/mathics/builtin/numbers/hyperbolic.py
index 44670b15c..9884e619c 100644
--- a/mathics/builtin/numbers/hyperbolic.py
+++ b/mathics/builtin/numbers/hyperbolic.py
@@ -14,9 +14,8 @@
from typing import Optional
-from mathics.builtin.arithmetic import _MPMathFunction
-from mathics.builtin.base import Builtin, SympyFunction
from mathics.core.atoms import IntegerM1
+from mathics.core.builtin import Builtin, MPMathFunction, SympyFunction
from mathics.core.convert.sympy import SympyExpression
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
@@ -30,7 +29,7 @@
SymbolSinh = Symbol("Sinh")
-class ArcCosh(_MPMathFunction):
+class ArcCosh(MPMathFunction):
"""
:Inverse hyperbolic cosine:
@@ -53,8 +52,6 @@ class ArcCosh(_MPMathFunction):
= 0. + 1.5708 I
>> ArcCosh[0.00000000000000000000000000000000000000]
= 1.5707963267948966192313216916397514421 I
- #> ArcCosh[1.4]
- = 0.867015
"""
mpmath_name = "acosh"
@@ -70,7 +67,7 @@ class ArcCosh(_MPMathFunction):
sympy_name = "acosh"
-class ArcCoth(_MPMathFunction):
+class ArcCoth(MPMathFunction):
"""
:Inverse hyperbolic cotangent:
@@ -95,9 +92,6 @@ class ArcCoth(_MPMathFunction):
= 0. + 1.5708 I
>> ArcCoth[0.5]
= 0.549306 - 1.5708 I
-
- #> ArcCoth[0.000000000000000000000000000000000000000]
- = 1.57079632679489661923132169163975144210 I
"""
summary_text = "inverse hyperbolic cotangent function"
@@ -111,7 +105,7 @@ class ArcCoth(_MPMathFunction):
}
-class ArcCsch(_MPMathFunction):
+class ArcCsch(MPMathFunction):
"""
:Inverse hyperbolic cosecant:
@@ -153,7 +147,7 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]:
).to_sympy()
-class ArcSech(_MPMathFunction):
+class ArcSech(MPMathFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/ArcSech.html
@@ -189,7 +183,7 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]:
).to_sympy()
-class ArcSinh(_MPMathFunction):
+class ArcSinh(MPMathFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/ArcSinh.html
@@ -216,7 +210,7 @@ class ArcSinh(_MPMathFunction):
}
-class ArcTanh(_MPMathFunction):
+class ArcTanh(MPMathFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/ArcTanh.html
@@ -309,7 +303,7 @@ def eval_with_complex_vars(self, expr, vars, evaluation: Evaluation):
return eval_ComplexExpand(expr, vars)
-class Cosh(_MPMathFunction):
+class Cosh(MPMathFunction):
"""
:WMA link:
@@ -334,7 +328,7 @@ class Cosh(_MPMathFunction):
summary_text = "hyperbolic cosine function"
-class Coth(_MPMathFunction):
+class Coth(MPMathFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/Coth.html
@@ -438,7 +432,7 @@ class InverseGudermannian(Builtin):
summary_text = "inverse Gudermannian function gd^-1(z)"
-class Sech(_MPMathFunction):
+class Sech(MPMathFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/Sech.html
@@ -467,7 +461,7 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]:
).to_sympy()
-class Sinh(_MPMathFunction):
+class Sinh(MPMathFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/Sinh.html
@@ -489,7 +483,7 @@ class Sinh(_MPMathFunction):
}
-class Tanh(_MPMathFunction):
+class Tanh(MPMathFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/Tanh.html
diff --git a/mathics/builtin/numbers/integer.py b/mathics/builtin/numbers/integer.py
index 9b15fc606..0d9ce034d 100644
--- a/mathics/builtin/numbers/integer.py
+++ b/mathics/builtin/numbers/integer.py
@@ -8,9 +8,9 @@
import sympy
-from mathics.builtin.base import Builtin, SympyFunction
from mathics.core.atoms import Integer, Integer0, String
from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED
+from mathics.core.builtin import Builtin, SympyFunction
from mathics.core.convert.expression import to_mathics_list
from mathics.core.convert.sympy import from_sympy
from mathics.core.expression import Expression
@@ -256,10 +256,6 @@ class FromDigits(Builtin):
= 0
>> FromDigits[""]
= 0
-
- #> FromDigits[x]
- : The input must be a string of digits or a list.
- = FromDigits[x, 10]
"""
summary_text = "integer from a list of digits"
diff --git a/mathics/builtin/numbers/linalg.py b/mathics/builtin/numbers/linalg.py
index 287b8b9bf..d670c4727 100644
--- a/mathics/builtin/numbers/linalg.py
+++ b/mathics/builtin/numbers/linalg.py
@@ -8,8 +8,8 @@
import sympy
from sympy import im, re
-from mathics.builtin.base import Builtin
from mathics.core.atoms import Integer, Integer0
+from mathics.core.builtin import Builtin
from mathics.core.convert.expression import to_mathics_list
from mathics.core.convert.matrix import matrix_data
from mathics.core.convert.mpmath import from_mpmath, to_mpmath_matrix
@@ -124,10 +124,6 @@ class Eigenvalues(Builtin):
>> Eigenvalues[{{7, 1}, {-4, 3}}]
= {5, 5}
-
- #> Eigenvalues[{{1, 0}, {0}}]
- : Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.
- = Eigenvalues[{{1, 0}, {0}}]
"""
messages = {
@@ -221,9 +217,6 @@ class Eigenvectors(Builtin):
>> Eigenvectors[{{0.1, 0.2}, {0.8, 0.5}}]
= ...
### = {{-0.355518, -1.15048}, {-0.62896, 0.777438}}
-
- #> Eigenvectors[{{-2, 1, -1}, {-3, 2, 1}, {-1, 1, 0}}]
- = {{1, 7, 3}, {1, 1, 0}, {0, 0, 0}}
"""
messages = {
@@ -365,18 +358,6 @@ class LeastSquares(Builtin):
>> LeastSquares[{{1, 1, 1}, {1, 1, 2}}, {1, 3}]
: Solving for underdetermined system not implemented.
= LeastSquares[{{1, 1, 1}, {1, 1, 2}}, {1, 3}]
-
- ## Inconsistent system - ideally we'd print a different message
- #> LeastSquares[{{1, 1, 1}, {1, 1, 1}}, {1, 0}]
- : Solving for underdetermined system not implemented.
- = LeastSquares[{{1, 1, 1}, {1, 1, 1}}, {1, 0}]
-
- #> LeastSquares[{1, {2}}, {1, 2}]
- : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.
- = LeastSquares[{1, {2}}, {1, 2}]
- #> LeastSquares[{{1, 2}, {3, 4}}, {1, {2}}]
- : Argument {1, {2}} at position 2 is not a non-empty rectangular matrix.
- = LeastSquares[{{1, 2}, {3, 4}}, {1, {2}}]
"""
messages = {
@@ -510,13 +491,6 @@ class LinearSolve(Builtin):
>> LinearSolve[{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, {1, -2, 3}]
: Linear equation encountered that has no solution.
= LinearSolve[{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, {1, -2, 3}]
-
- #> LinearSolve[{1, {2}}, {1, 2}]
- : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.
- = LinearSolve[{1, {2}}, {1, 2}]
- #> LinearSolve[{{1, 2}, {3, 4}}, {1, {2}}]
- : Argument {1, {2}} at position 2 is not a non-empty rectangular matrix.
- = LinearSolve[{{1, 2}, {3, 4}}, {1, {2}}]
"""
messages = {
@@ -582,13 +556,6 @@ class MatrixExp(Builtin):
>> MatrixExp[{{1.5, 0.5}, {0.5, 2.0}}]
= {{5.16266, 3.02952}, {3.02952, 8.19218}}
-
- #> MatrixExp[{{a, 0}, {0, b}}]
- = {{E ^ a, 0}, {0, E ^ b}}
-
- #> MatrixExp[{{1, 0}, {0}}]
- : Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.
- = MatrixExp[{{1, 0}, {0}}]
"""
messages = {
@@ -628,13 +595,6 @@ class MatrixPower(Builtin):
>> MatrixPower[{{1, 2}, {2, 5}}, -3]
= {{169, -70}, {-70, 29}}
-
- #> MatrixPower[{{0, x}, {0, 0}}, n]
- = MatrixPower[{{0, x}, {0, 0}}, n]
-
- #> MatrixPower[{{1, 0}, {0}}, 2]
- : Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.
- = MatrixPower[{{1, 0}, {0}}, 2]
"""
messages = {
@@ -681,10 +641,6 @@ class MatrixRank(Builtin):
= 3
>> MatrixRank[{{a, b}, {3 a, 3 b}}]
= 1
-
- #> MatrixRank[{{1, 0}, {0}}]
- : Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.
- = MatrixRank[{{1, 0}, {0}}]
"""
messages = {
@@ -721,10 +677,6 @@ class NullSpace(Builtin):
= {}
>> MatrixRank[A]
= 3
-
- #> NullSpace[{1, {2}}]
- : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.
- = NullSpace[{1, {2}}]
"""
messages = {
@@ -764,10 +716,6 @@ class PseudoInverse(Builtin):
>> PseudoInverse[{{1.0, 2.5}, {2.5, 1.0}}]
= {{-0.190476, 0.47619}, {0.47619, -0.190476}}
-
- #> PseudoInverse[{1, {2}}]
- : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.
- = PseudoInverse[{1, {2}}]
"""
messages = {
@@ -798,10 +746,6 @@ class QRDecomposition(Builtin):
>> QRDecomposition[{{1, 2}, {3, 4}, {5, 6}}]
= {{{Sqrt[35] / 35, 3 Sqrt[35] / 35, Sqrt[35] / 7}, {13 Sqrt[210] / 210, 2 Sqrt[210] / 105, -Sqrt[210] / 42}}, {{Sqrt[35], 44 Sqrt[35] / 35}, {0, 2 Sqrt[210] / 35}}}
-
- #> QRDecomposition[{1, {2}}]
- : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.
- = QRDecomposition[{1, {2}}]
"""
messages = {
@@ -844,10 +788,6 @@ class RowReduce(Builtin):
. 0 1 2
.
. 0 0 0
-
- #> RowReduce[{{1, 0}, {0}}]
- : Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.
- = RowReduce[{{1, 0}, {0}}]
"""
messages = {
@@ -881,15 +821,6 @@ class SingularValueDecomposition(Builtin):
>> SingularValueDecomposition[{{1.5, 2.0}, {2.5, 3.0}}]
= {{{0.538954, 0.842335}, {0.842335, -0.538954}}, {{4.63555, 0.}, {0., 0.107862}}, {{0.628678, 0.777666}, {-0.777666, 0.628678}}}
-
-
- #> SingularValueDecomposition[{{3/2, 2}, {5/2, 3}}]
- : Symbolic SVD is not implemented, performing numerically.
- = {{{0.538954, 0.842335}, {0.842335, -0.538954}}, {{4.63555, 0.}, {0., 0.107862}}, {{0.628678, 0.777666}, {-0.777666, 0.628678}}}
-
- #> SingularValueDecomposition[{1, {2}}]
- : Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.
- = SingularValueDecomposition[{1, {2}}]
"""
# Sympy lacks symbolic SVD
diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py
index 05431cee2..f0c734f49 100644
--- a/mathics/builtin/numbers/numbertheory.py
+++ b/mathics/builtin/numbers/numbertheory.py
@@ -3,11 +3,10 @@
"""
Number theoretic functions
"""
-
import mpmath
import sympy
+from packaging.version import Version
-from mathics.builtin.base import Builtin, SympyFunction
from mathics.core.atoms import Integer, Integer0, Integer10, Rational, Real
from mathics.core.attributes import (
A_LISTABLE,
@@ -16,6 +15,7 @@
A_PROTECTED,
A_READ_PROTECTED,
)
+from mathics.core.builtin import Builtin, SympyFunction
from mathics.core.convert.expression import to_mathics_list
from mathics.core.convert.python import from_bool, from_python
from mathics.core.convert.sympy import SympyPrime, from_sympy
@@ -91,14 +91,6 @@ class Divisors(Builtin):
= {1, 2, 4, 8, 11, 16, 22, 32, 44, 64, 88, 176, 352, 704}
>> Divisors[{87, 106, 202, 305}]
= {{1, 3, 29, 87}, {1, 2, 53, 106}, {1, 2, 101, 202}, {1, 5, 61, 305}}
- #> Divisors[0]
- = Divisors[0]
- #> Divisors[{-206, -502, -1702, 9}]
- = {{1, 2, 103, 206}, {1, 2, 251, 502}, {1, 2, 23, 37, 46, 74, 851, 1702}, {1, 3, 9}}
- #> Length[Divisors[1000*369]]
- = 96
- #> Length[Divisors[305*176*369*100]]
- = 672
"""
# TODO: support GaussianIntegers
@@ -275,21 +267,6 @@ class FractionalPart(Builtin):
>> FractionalPart[-5.25]
= -0.25
-
- #> FractionalPart[b]
- = FractionalPart[b]
-
- #> FractionalPart[{-2.4, -2.5, -3.0}]
- = {-0.4, -0.5, 0.}
-
- #> FractionalPart[14/32]
- = 7 / 16
-
- #> FractionalPart[4/(1 + 3 I)]
- = 2 / 5 - I / 5
-
- #> FractionalPart[Pi^20]
- = -8769956796 + Pi ^ 20
"""
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_READ_PROTECTED | A_PROTECTED
@@ -370,47 +347,6 @@ class MantissaExponent(Builtin):
>> MantissaExponent[10, b]
= MantissaExponent[10, b]
-
- #> MantissaExponent[E, Pi]
- = {E / Pi, 1}
-
- #> MantissaExponent[Pi, Pi]
- = {1 / Pi, 2}
-
- #> MantissaExponent[5/2 + 3, Pi]
- = {11 / (2 Pi ^ 2), 2}
-
- #> MantissaExponent[b]
- = MantissaExponent[b]
-
- #> MantissaExponent[17, E]
- = {17 / E ^ 3, 3}
-
- #> MantissaExponent[17., E]
- = {0.84638, 3}
-
- #> MantissaExponent[Exp[Pi], 2]
- = {E ^ Pi / 32, 5}
-
- #> MantissaExponent[3 + 2 I, 2]
- : The value 3 + 2 I is not a real number
- = MantissaExponent[3 + 2 I, 2]
-
- #> MantissaExponent[25, 0.4]
- : Base 0.4 is not a real number greater than 1.
- = MantissaExponent[25, 0.4]
-
- #> MantissaExponent[0.0000124]
- = {0.124, -4}
-
- #> MantissaExponent[0.0000124, 2]
- = {0.812646, -16}
-
- #> MantissaExponent[0]
- = {0, 0}
-
- #> MantissaExponent[0, 2]
- = {0, 0}
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -540,7 +476,8 @@ def to_int_value(x):
result = n.to_python()
for i in range(-py_k):
try:
- result = sympy.ntheory.prevprime(result)
+ # from sympy 1.13, the previous prime to 2 fails...
+ result = -2 if result == 2 else sympy.ntheory.prevprime(result)
except ValueError:
# No earlier primes
return Integer(-1 * sympy.ntheory.nextprime(0, py_k - i))
@@ -564,7 +501,11 @@ class PartitionsP(SympyFunction):
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_ORDERLESS | A_PROTECTED
summary_text = "number of unrestricted partitions"
- sympy_name = "npartitions"
+ # The name of this function changed in Sympy version 1.13.0.
+ # This supports backward compatibility.
+ sympy_name = (
+ "npartitions" if Version(sympy.__version__) < Version("1.13.0") else "partition"
+ )
def eval(self, n, evaluation: Evaluation):
"PartitionsP[n_Integer]"
@@ -644,13 +585,13 @@ class PrimePi(SympyFunction):
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
mpmath_name = "primepi"
summary_text = "amount of prime numbers less than or equal"
- sympy_name = "ntheory.primepi"
+ sympy_name = "primepi"
# TODO: Traditional Form
def eval(self, n, evaluation: Evaluation):
"PrimePi[n_?NumericQ]"
- result = sympy.ntheory.primepi(eval_N(n, evaluation).to_python())
+ result = sympy.primepi(eval_N(n, evaluation).to_python())
return Integer(result)
@@ -674,9 +615,6 @@ class PrimePowerQ(Builtin):
>> PrimePowerQ[371293]
= True
-
- #> PrimePowerQ[1]
- = False
"""
attributes = A_LISTABLE | A_PROTECTED | A_READ_PROTECTED
@@ -687,19 +625,19 @@ class PrimePowerQ(Builtin):
# TODO: GaussianIntegers option
"""
- #> PrimePowerQ[5, GaussianIntegers -> True]
+ ##> PrimePowerQ[5, GaussianIntegers -> True]
= False
"""
# TODO: Complex args
"""
- #> PrimePowerQ[{3 + I, 3 - 2 I, 3 + 4 I, 9 + 7 I}]
+ ##> PrimePowerQ[{3 + I, 3 - 2 I, 3 + 4 I, 9 + 7 I}]
= {False, True, True, False}
"""
# TODO: Gaussian rationals
"""
- #> PrimePowerQ[2/125 - 11 I/125]
+ ##> PrimePowerQ[2/125 - 11 I/125]
= True
"""
@@ -740,12 +678,6 @@ class RandomPrime(Builtin):
>> RandomPrime[{10,30}, {2,5}]
= ...
-
- #> RandomPrime[{10,12}, {2,2}]
- = {{11, 11}, {11, 11}}
-
- #> RandomPrime[2, {3,2}]
- = {{2, 2}, {2, 2}, {2, 2}}
"""
messages = {
diff --git a/mathics/builtin/numbers/randomnumbers.py b/mathics/builtin/numbers/randomnumbers.py
index 100da9b1e..77910941e 100644
--- a/mathics/builtin/numbers/randomnumbers.py
+++ b/mathics/builtin/numbers/randomnumbers.py
@@ -16,9 +16,9 @@
import numpy
-from mathics.builtin.base import Builtin
from mathics.builtin.numpy_utils import instantiate_elements, stack
from mathics.core.atoms import Complex, Integer, Real, String
+from mathics.core.builtin import Builtin
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull
@@ -334,25 +334,15 @@ class RandomComplex(Builtin):
>> RandomComplex[]
= ...
- #> 0 <= Re[%] <= 1 && 0 <= Im[%] <= 1
- = True
>> RandomComplex[{1+I, 5+5I}]
= ...
- #> 1 <= Re[%] <= 5 && 1 <= Im[%] <= 5
- = True
>> RandomComplex[1+I, 5]
= {..., ..., ..., ..., ...}
>> RandomComplex[{1+I, 2+2I}, {2, 2}]
= {{..., ...}, {..., ...}}
-
- #> RandomComplex[{6, 2 Pi + I}]
- = 6...
-
- #> RandomComplex[{6.3, 2.5 I}] // FullForm
- = Complex[..., ...]
"""
messages = {
@@ -463,8 +453,6 @@ class RandomInteger(Builtin):
>> RandomInteger[{1, 5}]
= ...
- #> 1 <= % <= 5
- = True
>> RandomInteger[100, {2, 3}] // TableForm
= ... ... ...
@@ -542,21 +530,8 @@ class RandomReal(Builtin):
>> RandomReal[]
= ...
- #> 0 <= % <= 1
- = True
-
>> RandomReal[{1, 5}]
= ...
-
- ## needs too much horizontal space in TeX form
- #> RandomReal[100, {2, 3}] // TableForm
- = ... ... ...
- .
- . ... ... ...
-
- #> RandomReal[{0, 1}, {1, -1}]
- : The array dimensions {1, -1} given in position 2 of RandomReal[{0, 1}, {1, -1}] should be a list of non-negative machine-sized integers giving the dimensions for the result.
- = RandomReal[{0, 1}, {1, -1}]
"""
messages = {
@@ -691,10 +666,6 @@ class SeedRandom(Builtin):
>> SeedRandom[]
>> RandomInteger[100]
= ...
-
- #> SeedRandom[x]
- : Argument x should be an integer or string.
- = SeedRandom[x]
"""
messages = {
diff --git a/mathics/builtin/numbers/trig.py b/mathics/builtin/numbers/trig.py
index 079976d72..8ea925163 100644
--- a/mathics/builtin/numbers/trig.py
+++ b/mathics/builtin/numbers/trig.py
@@ -14,9 +14,8 @@
import mpmath
-from mathics.builtin.arithmetic import _MPMathFunction
-from mathics.builtin.base import Builtin
from mathics.core.atoms import Integer, Integer0, IntegerM1, Real
+from mathics.core.builtin import Builtin, MPMathFunction
from mathics.core.convert.python import from_python
from mathics.core.exceptions import IllegalStepSpecification
from mathics.core.expression import Expression
@@ -334,7 +333,7 @@ def _fold(self, state, steps, math):
yield x, y, phi
-class ArcCos(_MPMathFunction):
+class ArcCos(MPMathFunction):
"""
Inverse cosine,
:arccosine:
@@ -371,7 +370,7 @@ class ArcCos(_MPMathFunction):
sympy_name = "acos"
-class ArcCot(_MPMathFunction):
+class ArcCot(MPMathFunction):
"""
Inverse cotangent,
:arccotangent:
@@ -406,7 +405,7 @@ class ArcCot(_MPMathFunction):
sympy_name = "acot"
-class ArcCsc(_MPMathFunction):
+class ArcCsc(MPMathFunction):
"""
Inverse cosecant,
:arccosecant:
@@ -447,7 +446,7 @@ def to_sympy(self, expr, **kwargs):
).to_sympy()
-class ArcSec(_MPMathFunction):
+class ArcSec(MPMathFunction):
"""
Inverse secant,
:arcsecant:
@@ -489,7 +488,7 @@ def to_sympy(self, expr, **kwargs):
).to_sympy()
-class ArcSin(_MPMathFunction):
+class ArcSin(MPMathFunction):
"""
Inverse sine,
:arcsine:
@@ -525,7 +524,7 @@ class ArcSin(_MPMathFunction):
sympy_name = "asin"
-class ArcTan(_MPMathFunction):
+class ArcTan(MPMathFunction):
"""
Inverse tangent,
:arctangent:
@@ -551,21 +550,6 @@ class ArcTan(_MPMathFunction):
>> ArcTan[1, 1]
= Pi / 4
- #> ArcTan[-1, 1]
- = 3 Pi / 4
- #> ArcTan[1, -1]
- = -Pi / 4
- #> ArcTan[-1, -1]
- = -3 Pi / 4
-
- #> ArcTan[1, 0]
- = 0
- #> ArcTan[-1, 0]
- = Pi
- #> ArcTan[0, 1]
- = Pi / 2
- #> ArcTan[0, -1]
- = -Pi / 2
"""
mpmath_name = "atan"
@@ -576,7 +560,7 @@ class ArcTan(_MPMathFunction):
"ArcTan[Undefined]": "Undefined",
"ArcTan[Undefined, x_]": "Undefined",
"ArcTan[y_, Undefined]": "Undefined",
- "ArcTan[x_?RealNumberQ, y_?RealNumberQ]": """If[x == 0, If[y == 0, 0, If[y > 0, Pi/2, -Pi/2]], If[x > 0,
+ "ArcTan[x_?RealValuedNumberQ, y_?RealValuedNumberQ]": """If[x == 0, If[y == 0, 0, If[y > 0, Pi/2, -Pi/2]], If[x > 0,
ArcTan[y/x], If[y >= 0, ArcTan[y/x] + Pi, ArcTan[y/x] - Pi]]]""",
"Derivative[1][ArcTan]": "1/(1+#^2)&",
}
@@ -585,7 +569,7 @@ class ArcTan(_MPMathFunction):
sympy_name = "atan"
-class Cos(_MPMathFunction):
+class Cos(MPMathFunction):
"""
:Cosine:
@@ -604,9 +588,6 @@ class Cos(_MPMathFunction):
>> Cos[3 Pi]
= -1
-
- #> Cos[1.5 Pi]
- = -1.83697×10^-16
"""
mpmath_name = "cos"
@@ -624,7 +605,7 @@ class Cos(_MPMathFunction):
sympy_name = "cos"
-class Cot(_MPMathFunction):
+class Cot(MPMathFunction):
"""
:Cotangent:
@@ -659,7 +640,7 @@ class Cot(_MPMathFunction):
sympy_name = "cot"
-class Csc(_MPMathFunction):
+class Csc(MPMathFunction):
"""
:Cosecant:
@@ -702,7 +683,7 @@ def to_sympy(self, expr, **kwargs):
).to_sympy()
-class Haversine(_MPMathFunction):
+class Haversine(MPMathFunction):
"""
:WMA link:
@@ -724,7 +705,7 @@ class Haversine(_MPMathFunction):
summary_text = "Haversine function"
-class InverseHaversine(_MPMathFunction):
+class InverseHaversine(MPMathFunction):
"""
:WMA link:
@@ -746,7 +727,7 @@ class InverseHaversine(_MPMathFunction):
summary_text = "inverse Haversine function"
-class Sec(_MPMathFunction):
+class Sec(MPMathFunction):
"""
:Secant:
@@ -788,7 +769,7 @@ def to_sympy(self, expr, **kwargs):
).to_sympy()
-class Sin(_MPMathFunction):
+class Sin(MPMathFunction):
"""
:Sine:
@@ -816,9 +797,6 @@ class Sin(_MPMathFunction):
>> Plot[Sin[x], {x, -Pi, Pi}]
= -Graphics-
-
- #> N[Sin[1], 40]
- = 0.8414709848078965066525023216302989996226
"""
mpmath_name = "sin"
@@ -835,7 +813,7 @@ class Sin(_MPMathFunction):
sympy_name = "sin"
-class Tan(_MPMathFunction):
+class Tan(MPMathFunction):
"""
:Tangent:
@@ -856,9 +834,6 @@ class Tan(_MPMathFunction):
= 0
>> Tan[Pi / 2]
= ComplexInfinity
-
- #> Tan[0.5 Pi]
- = 1.63312×10^16
"""
mpmath_name = "tan"
diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py
index d0bb14222..73d625284 100644
--- a/mathics/builtin/numeric.py
+++ b/mathics/builtin/numeric.py
@@ -1,4 +1,3 @@
-# cython: language_level=3
# -*- coding: utf-8 -*-
# Note: docstring is flowed in documentation. Line breaks in the
@@ -12,16 +11,47 @@
in algebraic or symbolic form.
"""
+from typing import Optional
+
import sympy
-from mathics.builtin.base import Builtin
-from mathics.core.atoms import Complex, Integer, Integer0, Rational, Real
-from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED
+from mathics.builtin.inference import evaluate_predicate
+from mathics.core.atoms import (
+ Complex,
+ Integer,
+ Integer0,
+ IntegerM1,
+ Number,
+ Rational,
+ Real,
+)
+from mathics.core.attributes import (
+ A_HOLD_ALL,
+ A_LISTABLE,
+ A_NUMERIC_FUNCTION,
+ A_PROTECTED,
+)
+from mathics.core.builtin import Builtin, MPMathFunction, SympyFunction
from mathics.core.convert.sympy import from_sympy
+from mathics.core.element import BaseElement
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.number import MACHINE_EPSILON
-from mathics.core.symbols import SymbolDivide, SymbolMachinePrecision, SymbolTimes
+from mathics.core.symbols import (
+ Symbol,
+ SymbolDivide,
+ SymbolFalse,
+ SymbolMachinePrecision,
+ SymbolTimes,
+ SymbolTrue,
+)
+from mathics.core.systemsymbols import SymbolPiecewise
+from mathics.eval.arithmetic import (
+ eval_Abs,
+ eval_negate_number,
+ eval_RealSign,
+ eval_Sign,
+)
from mathics.eval.nevaluator import eval_NValues
@@ -45,6 +75,54 @@ def chop(expr, delta=10.0 ** (-10.0)):
return expr
+class Abs(MPMathFunction):
+ """
+
+ :Absolute value:
+ https://en.wikipedia.org/wiki/Absolute_value (
+ :SymPy:
+ https://docs.sympy.org/latest/modules/functions
+ /elementary.html#sympy.functions.elementary.complexes.Abs,
+ :WMA: https://reference.wolfram.com/language/ref/Abs)
+
+
represents a piecewise function with default 'expr'.
+
+
+ Heaviside function
+ >> Piecewise[{{0, x <= 0}}, 1]
+ = Piecewise[{{0, x <= 0}}, 1]
+
+ ## D[%, x]
+ ## Piecewise({{0, Or[x < 0, x > 0]}}, Indeterminate).
+
+ >> Integrate[Piecewise[{{1, x <= 0}, {-1, x > 0}}], x]
+ = Piecewise[{{x, x <= 0}}, -x]
+
+ >> Integrate[Piecewise[{{1, x <= 0}, {-1, x > 0}}], {x, -1, 2}]
+ = -1
+
+ Piecewise defaults to 0 if no other case is matching.
+ >> Piecewise[{{1, False}}]
+ = 0
+
+ >> Plot[Piecewise[{{Log[x], x > 0}, {x*-0.5, x < 0}}], {x, -1, 1}]
+ = -Graphics-
+
+ >> Piecewise[{{0 ^ 0, False}}, -1]
+ = -1
+ """
+
+ summary_text = "an arbitrary piecewise function"
+ sympy_name = "Piecewise"
+
+ attributes = A_HOLD_ALL | A_PROTECTED
+
+ def eval(self, items, evaluation: Evaluation):
+ "%(name)s[items__]"
+ result = self.to_sympy(
+ Expression(SymbolPiecewise, *items.get_sequence()), evaluation=evaluation
+ )
+ if result is None:
+ return
+ if not isinstance(result, sympy.Piecewise):
+ result = from_sympy(result)
+ return result
+
+ def to_sympy(self, expr, **kwargs):
+ elements = expr.elements
+ evaluation = kwargs.get("evaluation", None)
+ if len(elements) not in (1, 2):
+ return
+
+ sympy_cases = []
+ for case in elements[0].elements:
+ if case.get_head_name() != "System`List":
+ return
+ if len(case.elements) != 2:
+ return
+ then, cond = case.elements
+ if evaluation:
+ cond = evaluate_predicate(cond, evaluation)
+
+ sympy_cond = None
+ if isinstance(cond, Symbol):
+ if cond is SymbolTrue:
+ sympy_cond = True
+ elif cond is SymbolFalse:
+ sympy_cond = False
+ if sympy_cond is None:
+ sympy_cond = cond.to_sympy(**kwargs)
+ if not (sympy_cond.is_Relational or sympy_cond.is_Boolean):
+ return
+
+ sympy_cases.append((then.to_sympy(**kwargs), sympy_cond))
+
+ if len(elements) == 2: # default case
+ sympy_cases.append((elements[1].to_sympy(**kwargs), True))
+ else:
+ sympy_cases.append((Integer0.to_sympy(**kwargs), True))
+
+ return sympy.Piecewise(*sympy_cases)
+
+ def from_sympy(self, sympy_name, args):
+ # Hack to get around weird sympy.Piecewise 'otherwise' behaviour
+ if str(args[-1].elements[1]).startswith("System`_True__Dummy_"):
+ args[-1].elements[1] = SymbolTrue
+ return Expression(self.get_name(), args)
+
+
class Rationalize(Builtin):
"""
:WMA link:
@@ -281,18 +439,6 @@ class Rationalize(Builtin):
Find the exact rational representation of 'N[Pi]'
>> Rationalize[N[Pi], 0]
= 245850922 / 78256779
-
- #> Rationalize[N[Pi] + 0.8 I, x]
- : Tolerance specification x must be a non-negative number.
- = Rationalize[3.14159 + 0.8 I, x]
-
- #> Rationalize[N[Pi] + 0.8 I, -1]
- : Tolerance specification -1 must be a non-negative number.
- = Rationalize[3.14159 + 0.8 I, -1]
-
- #> Rationalize[x, y]
- : Tolerance specification y must be a non-negative number.
- = Rationalize[x, y]
"""
messages = {
@@ -395,12 +541,93 @@ def approx_interval_continued_fraction(xmin, xmax):
return result
+class RealAbs(Builtin):
+ """
+ :Abs (Real):
+ https://en.wikipedia.org/wiki/Absolute_value (
+ :WMA link:
+ https://reference.wolfram.com/language/ref/RealAbs.html)
+
+
+
'RealAbs[$x$]'
+
returns the absolute value of a real number $x$.
+
+ 'RealAbs' is also known as modulus. It is evaluated if $x$ can be compared \
+ with $0$.
+
+ >> RealAbs[-3.]
+ = 3.
+ 'RealAbs[$z$]' is left unevaluated for complex $z$:
+ >> RealAbs[2. + 3. I]
+ = RealAbs[2. + 3. I]
+ >> D[RealAbs[x ^ 2], x]
+ = 2 x ^ 3 / RealAbs[x ^ 2]
+ """
+
+ attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
+ rules = {
+ "D[RealAbs[x_],x_]": "x/RealAbs[x]",
+ "Integrate[RealAbs[x_],x_]": "1/2 x RealAbs[x]",
+ "Integrate[RealAbs[u_],{u_,a_,b_}]": "1/2 b RealAbs[b]-1/2 a RealAbs[a]",
+ }
+ summary_text = "real absolute value"
+
+ def eval(self, x: BaseElement, evaluation: Evaluation):
+ """RealAbs[x_]"""
+ real_sign = eval_RealSign(x)
+ if real_sign is IntegerM1:
+ return eval_negate_number(x)
+ if real_sign is None:
+ return
+ return x
+
+
+class RealSign(Builtin):
+ """
+ :Sign function:
+ https://en.wikipedia.org/wiki/Sign_function (
+ :WMA link:
+ https://reference.wolfram.com/language/ref/RealSign.html)
+
+
+
'RealSign[$x$]'
+
returns -1, 0 or 1 depending on whether $x$ is negative,
+ zero or positive.
+
+ 'RealSign' is also known as $sgn$ or $signum$ function.
+
+ >> RealSign[-3.]
+ = -1
+ 'RealSign[$z$]' is left unevaluated for complex $z$:
+ >> RealSign[2. + 3. I]
+ = RealSign[2. + 3. I]
+
+ >> D[RealSign[x^2],x]
+ = 2 x Piecewise[{{0, x ^ 2 != 0}}, Indeterminate]
+ >> Integrate[RealSign[u],{u,0,x}]
+ = RealAbs[x]
+ """
+
+ attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
+ rules = {
+ "D[RealSign[x_],x_]": "Piecewise[{{0, x!=0}}, Indeterminate]",
+ "Integrate[RealSign[x_],x_]": "RealAbs[x]",
+ "Integrate[RealSign[u_],{u_, a_, b_}]": "RealAbs[b]-RealSign[a]",
+ }
+ summary_text = "real sign"
+
+ def eval(self, x: Number, evaluation: Evaluation) -> Optional[Integer]:
+ """RealSign[x_]"""
+ return eval_RealSign(x)
+
+
class RealValuedNumberQ(Builtin):
# No docstring since this is internal and it will mess up documentation.
# FIXME: Perhaps in future we will have a more explicite way to indicate not
# to add something to the docs.
+ no_doc = True
context = "Internal`"
-
+ summary_text = "test whether an expression is a real number"
rules = {
"Internal`RealValuedNumberQ[x_Real]": "True",
"Internal`RealValuedNumberQ[x_Integer]": "True",
@@ -413,6 +640,7 @@ class RealValuedNumericQ(Builtin):
# No docstring since this is internal and it will mess up documentation.
# FIXME: Perhaps in future we will have a more explicite way to indicate not
# to add something to the docs.
+ no_doc = True
context = "Internal`"
rules = {
@@ -472,7 +700,7 @@ class Round(Builtin):
rules = {
"Round[expr_?NumericQ]": "Round[Re[expr], 1] + I * Round[Im[expr], 1]",
- "Round[expr_Complex, k_?RealNumberQ]": (
+ "Round[expr_Complex, k_?RealValuedNumberQ]": (
"Round[Re[expr], k] + I * Round[Im[expr], k]"
),
}
@@ -493,3 +721,63 @@ def eval(self, expr, k, evaluation: Evaluation):
n = round(n)
n = int(n)
return Expression(SymbolTimes, Integer(n), k)
+
+
+class Sign(SympyFunction):
+ """
+
+ :Sign:
+ https://en.wikipedia.org/wiki/Sign_function (
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Sign.html)
+
+
+
'Sign[$x$]'
+
return -1, 0, or 1 depending on whether $x$ is negative, zero, or positive.
+
+
+ >> Sign[19]
+ = 1
+ >> Sign[-6]
+ = -1
+ >> Sign[0]
+ = 0
+ >> Sign[{-5, -10, 15, 20, 0}]
+ = {-1, -1, 1, 1, 0}
+
+ For a complex number, 'Sign' returns the phase of the number:
+ >> Sign[3 - 4*I]
+ = 3 / 5 - 4 I / 5
+
+ """
+
+ summary_text = "complex sign of a number"
+ sympy_name = "sign"
+ # mpmath_name = 'sign'
+
+ attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
+
+ messages = {
+ "argx": "Sign called with `1` arguments; 1 argument is expected.",
+ }
+
+ rules = {
+ "Sign[Power[a_, b_]]": "Power[Sign[a], b]",
+ }
+
+ def eval(self, x, evaluation: Evaluation):
+ "Sign[x_]"
+ result = eval_Sign(x)
+ if result is not None:
+ return result
+ # return None
+
+ sympy_x = x.to_sympy()
+ if sympy_x is None:
+ return None
+ # Unhandled cases. Use sympy
+ return super(Sign, self).eval(x, evaluation)
+
+ def eval_error(self, x, seqs, evaluation: Evaluation):
+ "Sign[x_, seqs__]"
+ evaluation.message("Sign", "argx", Integer(len(seqs.get_sequence()) + 1))
diff --git a/mathics/builtin/optimization.py b/mathics/builtin/optimization.py
index 46d8694f6..c8ac562c4 100644
--- a/mathics/builtin/optimization.py
+++ b/mathics/builtin/optimization.py
@@ -18,9 +18,9 @@
import sympy
-from mathics.builtin.base import Builtin
from mathics.core.atoms import IntegerM1
from mathics.core.attributes import A_CONSTANT, A_PROTECTED, A_READ_PROTECTED
+from mathics.core.builtin import Builtin
from mathics.core.convert.python import from_python
from mathics.core.convert.sympy import from_sympy
from mathics.core.evaluation import Evaluation
@@ -46,12 +46,6 @@ class Maximize(Builtin):
>> Maximize[-2 x^2 - 3 x + 5, x]
= {{49 / 8, {x -> -3 / 4}}}
-
- #>> Maximize[1 - (x y - 3)^2, {x, y}]
- = {{1, {x -> 3, y -> 1}}}
-
- #>> Maximize[{x - 2 y, x^2 + y^2 <= 1}, {x, y}]
- = {{Sqrt[5], {x -> Sqrt[5] / 5, y -> -2 Sqrt[5] / 5}}}
"""
attributes = A_PROTECTED | A_READ_PROTECTED
@@ -79,11 +73,12 @@ def eval_constraints(self, f, vars, evaluation: Evaluation):
"Maximize[f_List, vars_]"
constraints = [function for function in f.elements]
- constraints[0] = from_sympy(constraints[0].to_sympy() * IntegerM1)
-
- dual_solutions = (
- Expression(SymbolMinimize, constraints, vars).evaluate(evaluation).elements
+ constraints[0] = from_sympy(-(constraints[0].to_sympy()))
+ constraints = ListExpression(*constraints)
+ minimize_expr = Expression(SymbolMinimize, constraints, vars).evaluate(
+ evaluation
)
+ dual_solutions = minimize_expr.evaluate(evaluation).elements
solutions = []
for dual_solution in dual_solutions:
@@ -107,12 +102,6 @@ class Minimize(Builtin):
>> Minimize[2 x^2 - 3 x + 5, x]
= {{31 / 8, {x -> 3 / 4}}}
-
- #>> Minimize[(x y - 3)^2 + 1, {x, y}]
- = {{1, {x -> 3, y -> 1}}}
-
- #>> Minimize[{x - 2 y, x^2 + y^2 <= 1}, {x, y}]
- = {{-Sqrt[5], {x -> -Sqrt[5] / 5, y -> 2 Sqrt[5] / 5}}}
"""
attributes = A_PROTECTED | A_READ_PROTECTED
@@ -120,7 +109,6 @@ class Minimize(Builtin):
def eval_onevariable(self, f, x, evaluation: Evaluation):
"Minimize[f_?NotListQ, x_?NotListQ]"
-
sympy_x = x.to_sympy()
sympy_f = f.to_sympy()
@@ -129,11 +117,9 @@ def eval_onevariable(self, f, x, evaluation: Evaluation):
candidates = sympy.solve(derivative, sympy_x, real=True, dict=True)
minimum_list = []
-
for candidate in candidates:
value = second_derivative.subs(candidate)
if value.is_real and value > 0:
-
if candidate is not list:
candidate = candidate
@@ -161,7 +147,6 @@ def eval_multiplevariable(self, f, vars, evaluation: Evaluation):
or head_name in ("System`Plus", "System`Times", "System`Power") # noqa
or A_CONSTANT & var.get_attributes(evaluation.definitions)
):
-
evaluation.message("Minimize", "ivar", vars_or)
return
@@ -190,7 +175,6 @@ def eval_multiplevariable(self, f, vars, evaluation: Evaluation):
candidates.append(candidate)
minimum_list = []
-
for candidate in candidates:
eigenvals = hessian.subs(candidate).eigenvals()
@@ -214,14 +198,16 @@ def eval_multiplevariable(self, f, vars, evaluation: Evaluation):
*(
ListExpression(
from_sympy(sympy_f.subs(minimum).simplify()),
- [
- Expression(
- SymbolRule,
- from_sympy(list(minimum.keys())[i]),
- from_sympy(list(minimum.values())[i]),
+ ListExpression(
+ *(
+ Expression(
+ SymbolRule,
+ from_sympy(list(minimum.keys())[i]),
+ from_sympy(list(minimum.values())[i]),
+ )
+ for i in range(len(vars_sympy))
)
- for i in range(len(vars_sympy))
- ],
+ ),
)
for minimum in minimum_list
)
@@ -238,7 +224,6 @@ def eval_constraints(self, f, vars, evaluation: Evaluation):
or head_name in ("System`Plus", "System`Times", "System`Power") # noqa
or A_CONSTANT & var.get_attributes(evaluation.definitions)
):
-
evaluation.message("Minimize", "ivar", vars_or)
return
@@ -407,14 +392,16 @@ def eval_constraints(self, f, vars, evaluation: Evaluation):
*(
ListExpression(
from_sympy(objective_function.subs(minimum).simplify()),
- [
- Expression(
- SymbolRule,
- from_sympy(list(minimum.keys())[i]),
- from_sympy(list(minimum.values())[i]),
+ ListExpression(
+ *(
+ Expression(
+ SymbolRule,
+ from_sympy(list(minimum.keys())[i]),
+ from_sympy(list(minimum.values())[i]),
+ )
+ for i in range(len(vars_sympy))
)
- for i in range(len(vars_sympy))
- ],
+ ),
)
for minimum in minimum_list
)
diff --git a/mathics/builtin/options.py b/mathics/builtin/options.py
index 3b6acb581..c1a4e874d 100644
--- a/mathics/builtin/options.py
+++ b/mathics/builtin/options.py
@@ -11,9 +11,9 @@
https://reference.wolfram.com/language/guide/OptionsManagement.html
"""
-from mathics.builtin.base import Builtin, Predefined, Test, get_option
from mathics.builtin.image.base import Image
from mathics.core.atoms import String
+from mathics.core.builtin import Builtin, Predefined, Test, get_option
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression, SymbolDefault, get_default_value
from mathics.core.list import ListExpression
@@ -306,11 +306,6 @@ class Options(Builtin):
>> f[x, n -> 3]
= x ^ 3
- #> f[x_, OptionsPattern[f]] := x ^ OptionValue["m"];
- #> Options[f] = {"m" -> 7};
- #> f[x]
- = x ^ 7
-
Delayed option rules are evaluated just when the corresponding 'OptionValue' is called:
>> f[a :> Print["value"]] /. f[OptionsPattern[{}]] :> (OptionValue[a]; Print["between"]; OptionValue[a]);
| value
@@ -334,18 +329,6 @@ class Options(Builtin):
>> Options[a + b] = {a -> b}
: Argument a + b at position 1 is expected to be a symbol.
= {a -> b}
-
- #> f /: Options[f] = {a -> b}
- = {a -> b}
- #> Options[f]
- = {a :> b}
- #> f /: Options[g] := {a -> b}
- : Rule for Options can only be attached to g.
- = $Failed
-
- #> Options[f] = a /; True
- : a /; True is not a valid list of option rules.
- = a /; True
"""
summary_text = "the list of optional arguments and their default values"
diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py
index a52e1f75a..46a8c433f 100644
--- a/mathics/builtin/patterns.py
+++ b/mathics/builtin/patterns.py
@@ -42,14 +42,6 @@
from typing import Callable, List, Optional as OptionalType, Tuple, Union
-from mathics.builtin.base import (
- AtomBuiltin,
- BinaryOperator,
- Builtin,
- PatternError,
- PatternObject,
- PostfixOperator,
-)
from mathics.core.atoms import Integer, Number, Rational, Real, String
from mathics.core.attributes import (
A_HOLD_ALL,
@@ -58,6 +50,14 @@
A_PROTECTED,
A_SEQUENCE_HOLD,
)
+from mathics.core.builtin import (
+ AtomBuiltin,
+ BinaryOperator,
+ Builtin,
+ PatternError,
+ PatternObject,
+ PostfixOperator,
+)
from mathics.core.element import BaseElement, EvalMixin
from mathics.core.evaluation import Evaluation
from mathics.core.exceptions import InvalidLevelspecError
@@ -327,9 +327,6 @@ class ReplaceAll(BinaryOperator):
>> ReplaceAll[{a -> 1}][{a, b}]
= {1, b}
- #> a + b /. x_ + y_ -> {x, y}
- = {a, b}
-
ReplaceAll replaces the shallowest levels first:
>> ReplaceAll[x[1], {x[1] -> y, 1 -> 2}]
= y
@@ -568,7 +565,7 @@ def init(
"System`StringQ": self.match_string,
"System`NumericQ": self.match_numericq,
"System`NumberQ": self.match_numberq,
- "System`RealNumberQ": self.match_real_numberq,
+ "System`RealValuedNumberQ": self.match_real_numberq,
"Internal`RealValuedNumberQ": self.match_real_numberq,
"System`Posive": self.match_positive,
"System`Negative": self.match_negative,
@@ -700,7 +697,7 @@ def quick_pattern_test(self, candidate, test, evaluation: Evaluation):
and candidate.elements[1].value < 0
)
else:
- from mathics.builtin.base import Test
+ from mathics.core.builtin import Test
builtin = None
builtin = evaluation.definitions.get_definition(test)
@@ -761,9 +758,6 @@ class Alternatives(BinaryOperator, PatternObject):
Alternatives can also be used for string expressions
>> StringReplace["0123 3210", "1" | "2" -> "X"]
= 0XX3 3XX0
-
- #> StringReplace["h1d9a f483", DigitCharacter | WhitespaceCharacter -> ""]
- = hdaf
"""
arg_counts = None
@@ -829,9 +823,6 @@ class Except(PatternObject):
Except can also be used for string expressions:
>> StringReplace["Hello world!", Except[LetterCharacter] -> ""]
= Helloworld
-
- #> StringReplace["abc DEF 123!", Except[LetterCharacter, WordCharacter] -> "0"]
- = abc DEF 000!
"""
arg_counts = [1, 2]
@@ -1039,7 +1030,7 @@ def match(self, yield_func, expression, vars, evaluation, **kwargs):
yield_func(vars, None)
def get_match_candidates(
- self, elements, expression, attributes, evaluation, vars={}
+ self, elements: tuple, expression, attributes, evaluation, vars={}
):
existing = vars.get(self.varname, None)
if existing is None:
@@ -1091,15 +1082,6 @@ class Optional(BinaryOperator, PatternObject):
>> Default[h, k_] := k
>> h[a] /. h[x_, y_.] -> {x, y}
= {a, 2}
-
- #> a:b:c
- = a : b : c
- #> FullForm[a:b:c]
- = Optional[Pattern[a, b], c]
- #> (a:b):c
- = a : b : c
- #> a:(b:c)
- = a : (b : c)
"""
arg_counts = [1, 2]
@@ -1137,7 +1119,7 @@ def match(
head=None,
element_index=None,
element_count=None,
- **kwargs
+ **kwargs,
):
if expression.has_form("Sequence", 0):
if self.default is None:
@@ -1235,9 +1217,6 @@ class Blank(_Blank):
'Blank' only matches a single expression:
>> MatchQ[f[1, 2], f[_]]
= False
-
- #> StringReplace["hello world!", _ -> "x"]
- = xxxxxxxxxxxx
"""
rules = {
@@ -1252,7 +1231,7 @@ def match(
expression: Expression,
vars: dict,
evaluation: Evaluation,
- **kwargs
+ **kwargs,
):
if not expression.has_form("Sequence", 0):
if self.head is not None:
@@ -1293,14 +1272,6 @@ class BlankSequence(_Blank):
'Sequence' object:
>> f[1, 2, 3] /. f[x__] -> x
= Sequence[1, 2, 3]
-
- #> f[a, b, c, d] /. f[x__, c, y__] -> {{x},{y}}
- = {{a, b}, {d}}
- #> a + b + c + d /. Plus[x__, c] -> {x}
- = {a, b, d}
-
- #> StringReplace[{"ab", "abc", "abcd"}, "b" ~~ __ -> "x"]
- = {ab, ax, ax}
"""
rules = {
@@ -1315,7 +1286,7 @@ def match(
expression: Expression,
vars: dict,
evaluation: Evaluation,
- **kwargs
+ **kwargs,
):
elements = expression.get_sequence()
if not elements:
@@ -1350,21 +1321,6 @@ class BlankNullSequence(_Blank):
empty sequence:
>> MatchQ[f[], f[___]]
= True
-
- ## This test hits infinite recursion
- ##
- ##The value captured by a named 'BlankNullSequence' pattern is a
- ##'Sequence' object, which can have no elements:
- ##>> f[] /. f[x___] -> x
- ## = Sequence[]
-
- #> ___symbol
- = ___symbol
- #> ___symbol //FullForm
- = BlankNullSequence[symbol]
-
- #> StringReplace[{"ab", "abc", "abcd"}, "b" ~~ ___ -> "x"]
- = {ax, ax, ax}
"""
rules = {
@@ -1379,7 +1335,7 @@ def match(
expression: Expression,
vars: dict,
evaluation: Evaluation,
- **kwargs
+ **kwargs,
):
elements = expression.get_sequence()
if self.head:
@@ -1414,16 +1370,6 @@ class Repeated(PostfixOperator, PatternObject):
= {{}, a, {a, b}, a, {a, a, a, a}}
>> f[x, 0, 0, 0] /. f[x, s:0..] -> s
= Sequence[0, 0, 0]
-
- #> 1.. // FullForm
- = Repeated[1]
- #> 8^^1.. // FullForm (* Mathematica gets this wrong *)
- = Repeated[1]
-
- #> StringReplace["010110110001010", "01".. -> "a"]
- = a1a100a0
- #> StringMatchQ[#, "a" ~~ ("b"..) ~~ "a"] &/@ {"aa", "aba", "abba"}
- = {False, True, True}
"""
arg_counts = [1, 2]
@@ -1502,14 +1448,6 @@ class RepeatedNull(Repeated):
= RepeatedNull[Pattern[a, BlankNullSequence[Integer]]]
>> f[x] /. f[x, 0...] -> t
= t
-
- #> 1... // FullForm
- = RepeatedNull[1]
- #> 8^^1... // FullForm (* Mathematica gets this wrong *)
- = RepeatedNull[1]
-
- #> StringMatchQ[#, "a" ~~ ("b"...) ~~ "a"] &/@ {"aa", "aba", "abba"}
- = {True, True, True}
"""
operator = "..."
@@ -1615,7 +1553,7 @@ def match(
expression: Expression,
vars: dict,
evaluation: Evaluation,
- **kwargs
+ **kwargs,
):
# for new_vars, rest in self.pattern.match(expression, vars,
# evaluation):
@@ -1666,26 +1604,6 @@ class OptionsPattern(PatternObject):
Options might be given in nested lists:
>> f[x, {{{n->4}}}]
= x ^ 4
-
- #> {opt -> b} /. OptionsPattern[{}] -> t
- = t
-
- #> Clear[f]
- #> Options[f] = {Power -> 2};
- #> f[x_, OptionsPattern[f]] := x ^ OptionValue[Power]
- #> f[10]
- = 100
- #> f[10, Power -> 3]
- = 1000
- #> Clear[f]
-
- #> Options[f] = {Power -> 2};
- #> f[x_, OptionsPattern[]] := x ^ OptionValue[Power]
- #> f[10]
- = 100
- #> f[10, Power -> 3]
- = 1000
- #> Clear[f]
"""
arg_counts = [0, 1]
@@ -1708,7 +1626,7 @@ def match(
expression: Expression,
vars: dict,
evaluation: Evaluation,
- **kwargs
+ **kwargs,
):
if self.defaults is None:
self.defaults = kwargs.get("head")
diff --git a/mathics/builtin/physchemdata.py b/mathics/builtin/physchemdata.py
index 9f0812e6c..51d016e65 100644
--- a/mathics/builtin/physchemdata.py
+++ b/mathics/builtin/physchemdata.py
@@ -8,8 +8,8 @@
import os
from csv import reader as csvreader
-from mathics.builtin.base import Builtin
from mathics.core.atoms import Integer, String
+from mathics.core.builtin import Builtin
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
@@ -87,9 +87,6 @@ class ElementData(Builtin):
>> ListPlot[Table[ElementData[z, "AtomicWeight"], {z, 118}]]
= -Graphics-
-
- ## Ensure all data parses #664
- #> Outer[ElementData, Range[118], ElementData["Properties"]];
"""
messages = {
diff --git a/mathics/builtin/procedural.py b/mathics/builtin/procedural.py
index 8a1307267..3ebb5f8d8 100644
--- a/mathics/builtin/procedural.py
+++ b/mathics/builtin/procedural.py
@@ -17,13 +17,13 @@
"""
-from mathics.builtin.base import BinaryOperator, Builtin, IterationFunction
from mathics.core.attributes import (
A_HOLD_ALL,
A_HOLD_REST,
A_PROTECTED,
A_READ_PROTECTED,
)
+from mathics.core.builtin import BinaryOperator, Builtin, IterationFunction
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.interrupt import (
@@ -165,39 +165,6 @@ class CompoundExpression(BinaryOperator):
= d
If the last argument is omitted, 'Null' is taken:
>> a;
-
- ## Parser Tests
- #> FullForm[Hold[; a]]
- : "FullForm[Hold[" cannot be followed by "; a]]" (line 1 of "").
- #> FullForm[Hold[; a ;]]
- : "FullForm[Hold[" cannot be followed by "; a ;]]" (line 1 of "").
-
- ## Issue331
- #> CompoundExpression[x, y, z]
- = z
- #> %
- = z
-
- #> CompoundExpression[x, y, Null]
- #> %
- = y
-
- #> CompoundExpression[CompoundExpression[x, y, Null], Null]
- #> %
- = y
-
- #> CompoundExpression[x, Null, Null]
- #> %
- = x
-
- #> CompoundExpression[]
- #> %
-
- ## Issue 531
- #> z = Max[1, 1 + x]; x = 2; z
- = 3
-
- #> Clear[x]; Clear[z]
"""
attributes = A_HOLD_ALL | A_PROTECTED | A_READ_PROTECTED
@@ -294,10 +261,6 @@ class Do(IterationFunction):
| 5
| 7
| 9
-
- #> Do[Print["hi"],{1+1}]
- | hi
- | hi
"""
allow_loopcontrol = True
@@ -330,12 +293,6 @@ class For(Builtin):
= 3628800
>> n == 10!
= True
-
- #> n := 1
- #> For[i=1, i<=10, i=i+1, If[i > 5, Return[i]]; n = n * i]
- = 6
- #> n
- = 120
"""
attributes = A_HOLD_REST | A_PROTECTED
@@ -473,17 +430,6 @@ class Return(Builtin):
>> g[x_] := (Do[If[x < 0, Return[0]], {i, {2, 1, 0, -1}}]; x)
>> g[-1]
= -1
-
- #> h[x_] := (If[x < 0, Return[]]; x)
- #> h[1]
- = 1
- #> h[-1]
-
- ## Issue 513
- #> f[x_] := Return[x];
- #> g[y_] := Module[{}, z = f[y]; 2]
- #> g[1]
- = 2
"""
rules = {
@@ -519,15 +465,14 @@ class Switch(Builtin):
: Switch called with 2 arguments. Switch must be called with an odd number of arguments.
= Switch[2, 1]
- #> a; Switch[b, b]
- : Switch called with 2 arguments. Switch must be called with an odd number of arguments.
- = Switch[b, b]
- ## Issue 531
- #> z = Switch[b, b];
- : Switch called with 2 arguments. Switch must be called with an odd number of arguments.
- #> z
- = Switch[b, b]
+ Notice that 'Switch' evaluates each pattern before it against \
+ $expr$, stopping after the first match:
+ >> a:=(Print["a->p"];p); b:=(Print["b->q"];q);
+ >> Switch[p,a,1,b,2]
+ | a->p
+ = 1
+ >> a=.; b=.;
"""
summary_text = "switch based on a value, with patterns allowed"
@@ -550,7 +495,10 @@ def eval(self, expr, rules, evaluation):
evaluation.message("Switch", "argct", "Switch", len(rules) + 1)
return
for pattern, value in zip(rules[::2], rules[1::2]):
- if match(expr, pattern, evaluation):
+ # The match is done against the result of the evaluation
+ # of `pattern`. HoldRest allows to evaluate the patterns
+ # just until a match is found.
+ if match(expr, pattern.evaluate(evaluation), evaluation):
return value.evaluate(evaluation)
# return unevaluated Switch when no pattern matches
@@ -636,9 +584,6 @@ class While(Builtin):
>> While[b != 0, {a, b} = {b, Mod[a, b]}];
>> a
= 3
-
- #> i = 1; While[True, If[i^2 > 100, Return[i + 1], i++]]
- = 12
"""
summary_text = "evaluate an expression while a criterion is true"
diff --git a/mathics/builtin/pympler/asizeof.py b/mathics/builtin/pympler/asizeof.py
index c80643320..7ff36aa62 100644
--- a/mathics/builtin/pympler/asizeof.py
+++ b/mathics/builtin/pympler/asizeof.py
@@ -2390,7 +2390,7 @@ def print_largest(self, w=0, cutoff=0, **print3options):
self._ranked,
s,
_SI(s),
- **print3options
+ **print3options,
)
id2x = dict((r.id, i) for i, r in enumerate(self._ranks))
for r in self._ranks[:n]:
@@ -2428,7 +2428,7 @@ def print_profiles(self, w=0, cutoff=0, **print3options):
_plural(len(t)),
s,
self._incl,
- **print3options
+ **print3options,
)
r = len(t)
for v, k in sorted(t, reverse=True):
@@ -2498,7 +2498,7 @@ def print_stats(
_SI(z),
self._incl,
self._repr(o),
- **print3options
+ **print3options,
)
else:
if objs:
@@ -2531,7 +2531,7 @@ def print_summary(self, w=0, objs=(), **print3options):
self._total,
_SI(self._total),
self._incl,
- **print3options
+ **print3options,
)
if self._mask:
self._printf("%*d byte aligned", w, self._mask + 1, **print3options)
@@ -2581,7 +2581,7 @@ def print_typedefs(self, w=0, **print3options):
len(t),
k,
_plural(len(t)),
- **print3options
+ **print3options,
)
for a, v in sorted(t):
self._printf("%*s %s: %s", w, "", a, v, **print3options)
@@ -2612,7 +2612,7 @@ def reset(
limit=100,
stats=0,
stream=None,
- **extra
+ **extra,
):
"""Reset sizing options, state, etc. to defaults.
@@ -3068,7 +3068,6 @@ def refs(obj, **opts):
if __name__ == "__main__":
-
if "-v" in sys.argv:
import platform
diff --git a/mathics/builtin/quantities.py b/mathics/builtin/quantities.py
index 20f24adbb..be77227ef 100644
--- a/mathics/builtin/quantities.py
+++ b/mathics/builtin/quantities.py
@@ -2,44 +2,38 @@
"""
Units and Quantities
"""
+from typing import Optional
-from pint import UnitRegistry
-
-from mathics.builtin.base import Builtin, Test
-from mathics.core.atoms import Integer, Integer1, Number, Real, String
+from mathics.core.atoms import Integer, Integer1, Number, String
from mathics.core.attributes import (
A_HOLD_REST,
A_N_HOLD_REST,
A_PROTECTED,
A_READ_PROTECTED,
)
-from mathics.core.convert.expression import to_mathics_list
-from mathics.core.convert.python import from_python
+from mathics.core.builtin import Builtin, Test
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.symbols import Symbol
-from mathics.core.systemsymbols import SymbolQuantity, SymbolRowBox
+from mathics.core.systemsymbols import (
+ SymbolPower,
+ SymbolQuantity,
+ SymbolRow,
+ SymbolTimes,
+)
+from mathics.eval.quantities import (
+ add_quantities,
+ convert_units,
+ normalize_unit_expression,
+ normalize_unit_expression_with_magnitude,
+ validate_pint_unit,
+ validate_unit_expression,
+)
# This tells documentation how to sort this module
sort_order = "mathics.builtin.units-and-quantites"
-ureg = UnitRegistry()
-Q_ = ureg.Quantity
-
-
-def get_converted_magnitude(magnitude_expr, evaluation: Evaluation) -> float:
- """
- The Python "pint" library mixes in a Python numeric value as a multiplier inside
- a Mathics Expression. here we pick out that multiplier and
- convert it from a Python numeric to a Mathics numeric.
- """
- magnitude_elements = list(magnitude_expr.elements)
- magnitude_elements[1] = from_python(magnitude_elements[1])
- magnitude_expr._elements = tuple(magnitude_elements)
- # FIXME: consider returning an int when that is possible
- return magnitude_expr.evaluate(evaluation).get_float_value()
-
class KnownUnitQ(Test):
"""
@@ -57,20 +51,15 @@ class KnownUnitQ(Test):
>> KnownUnitQ["Foo"]
= False
+
+ >> KnownUnitQ["meter"^2/"second"]
+ = True
"""
summary_text = "tests whether its argument is a canonical unit."
def test(self, expr) -> bool:
- def validate(unit):
- try:
- Q_(1, unit)
- except Exception:
- return False
- else:
- return True
-
- return validate(expr.get_string_value().lower())
+ return validate_unit_expression(expr)
class Quantity(Builtin):
@@ -93,74 +82,142 @@ class Quantity(Builtin):
>> Quantity[10, "Meters"]
= 10 meter
- >> Quantity[{10,20}, "Meters"]
+ If the first argument is an array, then the unit is distributed on each element
+ >> Quantity[{10, 20}, "Meters"]
= {10 meter, 20 meter}
- #> Quantity[10, Meters]
- = Quantity[10, Meters]
+ If the second argument is a number, then the expression is evaluated to
+ the product of the magnitude and that number
+ >> Quantity[2, 3/2]
+ = 3
+
+ Notice that units are specified as Strings. If the unit is not a Symbol or a Number,
+ the expression is not interpreted as a Quantity object:
+
+ >> QuantityQ[Quantity[2, Second]]
+ : Unable to interpret unit specification Second.
+ = False
+
+ Quantities can be multiplied and raised to integer powers:
+ >> Quantity[3, "centimeter"] / Quantity[2, "second"]^2
+ = 3 / 4 centimeter / second ^ 2
- #> Quantity[Meters]
- : Unable to interpret unit specification Meters.
- = Quantity[Meters]
+ ## TODO: Allow to simplify producs:
+ ## >> Quantity[3, "centimeter"] Quantity[2, "meter"]
+ ## = 600 centimeter ^ 2
- #> Quantity[1, "foot"]
- = 1 foot
+ Quantities of the same kind can be added:
+ >> Quantity[6, "meter"] + Quantity[3, "centimeter"]
+ = 603 centimeter
+
+
+ Quantities of different kind can not:
+ >> Quantity[6, "meter"] + Quantity[3, "second"]
+ : second and meter are incompatible units.
+ = 3 second + 6 meter
+
+ ## TODO: Implement quantities with composed units:
+ ## >> UnitConvert[Quantity[2, "Ampere" * "Second"], "Coulomb"]
+ ## = Quantity[2, Coulomb]
"""
attributes = A_HOLD_REST | A_N_HOLD_REST | A_PROTECTED | A_READ_PROTECTED
messages = {
"unkunit": "Unable to interpret unit specification `1`.",
+ "compat": "`1` and `2` are incompatible units.",
+ }
+ # TODO: Support fractional powers of units
+ rules = {
+ "Quantity[m1_, u1_]*Quantity[m2_, u2_]": "Quantity[m1*m2, u1*u2]",
+ "Quantity[m_, u_]*a_": "Quantity[a*m, u]",
+ "Power[Quantity[m_, u_], p_]": "Quantity[m^p, u^p]",
}
summary_text = "represents a quantity with units"
- def validate(self, unit, evaluation: Evaluation):
- if KnownUnitQ(unit).evaluate(evaluation) is Symbol("False"):
- return False
- return True
+ def eval_plus(self, q1, u1, q2, u2, evaluation):
+ """Plus[Quantity[q1_, u1_], Quantity[q2_,u2_]]"""
+ result = add_quantities(q1, u1, q2, u2, evaluation)
+ if result is None:
+ evaluation.message("Quantity", "compat", u1, u2)
+ return result
+
+ def format_quantity(self, mag, unit, evaluation: Evaluation):
+ "Quantity[mag_, unit_]"
+
+ def format_units(units):
+ if isinstance(units, String):
+ q_unit = units.value
+ if validate_pint_unit(q_unit):
+ result = String(q_unit.replace("_", " "))
+ return result
+ return None
+ if units.has_form("Power", 2):
+ base, exp = units.elements
+ if not isinstance(exp, Integer):
+ return None
+ result = Expression(SymbolPower, format_units(base), exp)
+ return result
+ if units.has_form("Times", None):
+ result = Expression(
+ SymbolTimes, *(format_units(factor) for factor in units.elements)
+ )
+ return result
+ return None
- def eval_makeboxes(self, mag, unit, f, evaluation: Evaluation):
- "MakeBoxes[Quantity[mag_, unit_String], f:StandardForm|TraditionalForm|OutputForm|InputForm]"
+ unit = format_units(unit)
+ if unit is None:
+ return None
- q_unit = unit.value.lower()
- if self.validate(unit, evaluation):
- return Expression(
- SymbolRowBox, ListExpression(mag, String(" "), String(q_unit))
- )
- else:
- return Expression(
- SymbolRowBox,
- to_mathics_list(SymbolQuantity, "[", mag, ",", q_unit, "]"),
- )
+ return Expression(SymbolRow, ListExpression(mag, String(" "), unit))
- def eval_n(self, mag, unit, evaluation: Evaluation):
- "Quantity[mag_, unit_String]"
-
- if self.validate(unit, evaluation):
- if mag.has_form("List", None):
- results = []
- for i in range(len(mag.elements)):
- quantity = Q_(mag.elements[i], unit.value.lower())
- results.append(
- Expression(
- SymbolQuantity, quantity.magnitude, String(quantity.units)
- )
- )
- return ListExpression(*results)
- else:
- quantity = Q_(mag, unit.value.lower())
- return Expression(
- SymbolQuantity, quantity.magnitude, String(quantity.units)
- )
- else:
+ def eval_list_of_magnitudes_unit(self, mag, unit, evaluation: Evaluation):
+ "Quantity[mag_List, unit_]"
+ head = Symbol(self.get_name())
+ return ListExpression(
+ *(Expression(head, m, unit).evaluate(evaluation) for m in mag.elements)
+ )
+
+ def eval_magnitude_and_unit(
+ self, mag, unit, evaluation: Evaluation
+ ) -> Optional[Expression]:
+ "Quantity[mag_, unit_]"
+
+ unit = unit.evaluate(evaluation)
+
+ if isinstance(unit, Number):
+ return Expression(SymbolTimes, mag, unit).evaluate(evaluation)
+
+ if unit.has_form("Quantity", 2):
+ if not validate_unit_expression(unit):
+ return None
+ unit = unit.elements[1]
+
+ try:
+ normalized_unit = normalize_unit_expression_with_magnitude(unit, mag)
+ except ValueError:
evaluation.message("Quantity", "unkunit", unit)
+ return None
+
+ if unit.sameQ(normalized_unit):
+ return None
- def eval(self, unit, evaluation: Evaluation):
+ return Expression(SymbolQuantity, mag, normalized_unit)
+
+ def eval_unit(self, unit, evaluation: Evaluation):
"Quantity[unit_]"
- if not isinstance(unit, String):
+ unit = unit.evaluate(evaluation)
+ if isinstance(unit, Number):
+ return unit
+ if unit.has_form("Quantity", 2):
+ return unit
+ try:
+ unit = normalize_unit_expression(unit)
+ except ValueError:
evaluation.message("Quantity", "unkunit", unit)
- else:
- return self.eval_n(Integer1, unit, evaluation)
+ return None
+ # TODO: add element property "fully_evaluated
+ return Expression(SymbolQuantity, Integer1, unit)
class QuantityMagnitude(Builtin):
@@ -185,88 +242,59 @@ class QuantityMagnitude(Builtin):
>> QuantityMagnitude[Quantity[{10,20}, "Meters"]]
= {10, 20}
-
- #> QuantityMagnitude[Quantity[1, "meter"], "centimeter"]
- = 100
-
- #> QuantityMagnitude[Quantity[{3,1}, "meter"], "centimeter"]
- = {300, 100}
-
- #> QuantityMagnitude[Quantity[{300,100}, "centimeter"], "meter"]
- = {3, 1}
-
- #> QuantityMagnitude[Quantity[{3, 1}, "meter"], "inch"]
- = {118.11, 39.3701}
-
- #> QuantityMagnitude[Quantity[{3, 1}, "meter"], Quantity[3, "centimeter"]]
- = {300, 100}
-
- #> QuantityMagnitude[Quantity[3,"mater"]]
- : Unable to interpret unit specification mater.
- = QuantityMagnitude[Quantity[3,mater]]
"""
summary_text = "get magnitude associated with a quantity."
- def eval(self, expr, evaluation: Evaluation):
- "QuantityMagnitude[expr_]"
+ def eval_list(self, expr, evaluation: Evaluation):
+ "QuantityMagnitude[expr_List]"
+ return ListExpression(
+ *(
+ Expression(Symbol(self.get_name()), e).evaluate(evaluation)
+ for e in expr.elements
+ )
+ )
+
+ def eval_list_with_unit(self, expr, unit, evaluation: Evaluation):
+ "QuantityMagnitude[expr_List, unit_]"
+ return ListExpression(
+ *(
+ Expression(Symbol(self.get_name()), e, unit).evaluate(evaluation)
+ for e in expr.elements
+ )
+ )
- def get_magnitude(elements):
- if len(elements) == 1:
- return 1
- else:
- return elements[0]
+ def eval_quantity(self, mag, unit, evaluation: Evaluation):
+ "QuantityMagnitude[Quantity[mag_, unit_]]"
+ return mag if validate_unit_expression(unit) else None
- if len(evaluation.out) > 0:
- return
- if expr.has_form("List", None):
- results = []
- for i in range(len(expr.elements)):
- results.append(get_magnitude(expr.elements[i].elements))
- return ListExpression(*results)
- else:
- return get_magnitude(expr.elements)
-
- def eval_unit(self, expr, unit, evaluation: Evaluation):
- "QuantityMagnitude[expr_, unit_]"
-
- def get_magnitude(elements, targetUnit, evaluation: Evaluation):
- quantity = Q_(elements[0], elements[1].get_string_value())
- converted_quantity = quantity.to(targetUnit)
- q_mag = get_converted_magnitude(converted_quantity.magnitude, evaluation)
-
- # Displaying the magnitude in Integer form if the convert rate is an Integer
- if q_mag - int(q_mag) > 0:
- return Real(q_mag)
- else:
- return Integer(q_mag)
-
- if len(evaluation.out) > 0:
- return
-
- # Getting the target unit
- if unit.has_form("Quantity", None):
- targetUnit = unit.elements[1].get_string_value().lower()
- elif unit.has_form("List", None):
- if not unit.elements[0].has_form("Quantity", None):
- return
- else:
- targetUnit = unit.elements[0].elements[1].get_string_value().lower()
- elif isinstance(unit, String):
- targetUnit = unit.get_string_value().lower()
- else:
- return
-
- # convert the quantity to the target unit and return the magnitude
- if expr.has_form("List", None):
- results = []
- for i in range(len(expr.elements)):
- results.append(
- get_magnitude(expr.elements[i].elements, targetUnit, evaluation)
+ def eval_quantity_unit(self, quantity, targetUnit, evaluation: Evaluation):
+ "QuantityMagnitude[quantity_Quantity, targetUnit_]"
+
+ if targetUnit.has_form("System`List", None):
+ return ListExpression(
+ *(
+ Expression(Symbol(self.get_name()), quantity, u)
+ for u in targetUnit.elements
)
- return ListExpression(*results)
- else:
- return get_magnitude(expr.elements, targetUnit, evaluation)
+ )
+ if targetUnit.has_form("Quantity", 2):
+ targetUnit = targetUnit.elements[1]
+
+ try:
+ magnitude, unit = quantity.elements
+ except ValueError:
+ return None
+ try:
+ converted_quantity = convert_units(
+ magnitude,
+ unit,
+ targetUnit,
+ evaluation,
+ )
+ return converted_quantity.elements[0]
+ except ValueError:
+ return None
class QuantityQ(Test):
@@ -285,40 +313,22 @@ class QuantityQ(Test):
>> QuantityQ[Quantity[3, "Maters"]]
: Unable to interpret unit specification Maters.
= False
-
- #> QuantityQ[3]
- = False
"""
summary_text = "tests whether its the argument is a quantity"
def test(self, expr) -> bool:
- def validate_unit(unit):
- try:
- Q_(1, unit)
- except Exception:
- return False
- else:
- return True
-
- def validate(elements):
- if len(elements) < 1 or len(elements) > 2:
- return False
- elif len(elements) == 1:
- if validate_unit(elements[0].get_string_value().lower()):
- return True
- else:
- return False
- else:
- if isinstance(elements[0], Number):
- if validate_unit(elements[1].get_string_value().lower()):
- return True
- else:
- return False
- else:
- return False
-
- return expr.get_head() == SymbolQuantity and validate(expr.elements)
+ if not expr.has_form("Quantity", 2):
+ return False
+ try:
+ magnitude, unit = expr.elements
+ except ValueError:
+ return False
+
+ if not isinstance(magnitude, Number):
+ return False
+
+ return validate_unit_expression(unit)
class QuantityUnit(Builtin):
@@ -340,36 +350,25 @@ class QuantityUnit(Builtin):
>> QuantityUnit[Quantity[{10,20}, "Meters"]]
= {meter, meter}
-
- #> QuantityUnit[Quantity[10, "aaa"]]
- : Unable to interpret unit specification aaa.
- = QuantityUnit[Quantity[10,aaa]]
"""
summary_text = "the unit associated to a quantity"
- def eval(self, expr, evaluation: Evaluation):
- "QuantityUnit[expr_]"
-
- def get_unit(elements):
- if len(elements) == 1:
- return elements[0]
- else:
- return elements[1]
+ def eval_quantity(self, mag, unit, evaluation: Evaluation):
+ "QuantityUnit[Quantity[mag_, unit_]]"
+ return unit if validate_unit_expression(unit) else None
- if len(evaluation.out) > 0:
- return
- if expr.has_form("List", None):
- results = []
- for i in range(len(expr.elements)):
- results.append(get_unit(expr.elements[i].elements))
- return ListExpression(*results)
- else:
- return get_unit(expr.elements)
+ def eval_list(self, expr, evaluation: Evaluation):
+ "QuantityUnit[expr_List]"
+ return ListExpression(
+ *(
+ Expression(Symbol(self.get_name()), e).evaluate(evaluation)
+ for e in expr.elements
+ )
+ )
class UnitConvert(Builtin):
-
"""
:WMA link:
@@ -390,19 +389,6 @@ class UnitConvert(Builtin):
Convert a Quantity object to the appropriate SI base units:
>> UnitConvert[Quantity[3.8, "Pounds"]]
= 1.72365 kilogram
-
- #> UnitConvert[Quantity[{3, 10}, "centimeter"]]
- = {0.03 meter, 0.1 meter}
-
- #> UnitConvert[Quantity[3, "aaa"]]
- : Unable to interpret unit specification aaa.
- = UnitConvert[Quantity[3,aaa]]
-
- #> UnitConvert[Quantity[{300, 152}, "centimeter"], Quantity[10, "meter"]]
- = {3 meter, 1.52 meter}
-
- #> UnitConvert[Quantity[{3, 1}, "meter"], "inch"]
- = {118.11 inch, 39.3701 inch}
"""
messages = {
@@ -410,71 +396,58 @@ class UnitConvert(Builtin):
}
summary_text = "convert between units."
- def eval(self, expr, toUnit, evaluation: Evaluation):
- "UnitConvert[expr_, toUnit_]"
-
- def convert_unit(elements, target):
-
- mag = elements[0]
- unit = elements[1].get_string_value()
- quantity = Q_(mag, unit)
- converted_quantity = quantity.to(target)
-
- q_mag = get_converted_magnitude(converted_quantity.magnitude, evaluation)
-
- # Displaying the magnitude in Integer form if the convert rate is an Integer
- if q_mag - int(q_mag) > 0:
- return Expression(SymbolQuantity, Real(q_mag), String(target))
- else:
- return Expression(SymbolQuantity, Integer(q_mag), String(target))
-
- if len(evaluation.out) > 0:
- return
-
- if toUnit.has_form("Quantity", None):
- targetUnit = toUnit.elements[1].get_string_value().lower()
- elif toUnit.has_form("List", None):
- if not toUnit.elements[0].has_form("Quantity", None):
- return
- else:
- targetUnit = toUnit.elements[0].elements[1].get_string_value().lower()
- elif isinstance(toUnit, String):
- targetUnit = toUnit.get_string_value().lower()
- else:
- return
- if expr.has_form("List", None):
- abc = []
- for i in range(len(expr.elements)):
- abc.append(convert_unit(expr.elements[i].elements, targetUnit))
- return ListExpression(*abc)
- else:
- return convert_unit(expr.elements, targetUnit)
-
- def eval_base_unit(self, expr, evaluation: Evaluation):
- "UnitConvert[expr_]"
-
- def convert_unit(elements):
+ def eval_expr_several_units(self, expr, toUnit, evaluation: Evaluation):
+ "UnitConvert[expr_, toUnit_List]"
+ return ListExpression(
+ *(
+ Expression(Symbol(self.get_name()), expr, u).evaluate(evaluation)
+ for u in toUnit.elements
+ )
+ )
- mag = elements[0]
- unit = elements[1].get_string_value()
+ def eval_quantity_to_unit_from_quantity(self, expr, toUnit, evaluation: Evaluation):
+ "UnitConvert[expr_, toUnit_Quantity]"
+ if not toUnit.has_form("Quantity", 2):
+ return None
+ toUnit = toUnit.elements[1]
+ return Expression(Symbol(self.get_name()), expr, toUnit).evaluate(evaluation)
- quantity = Q_(mag, unit)
- converted_quantity = quantity.to_base_units()
+ def eval_quantity_to_unit(self, expr, toUnit, evaluation: Evaluation):
+ "UnitConvert[expr_, toUnit_]"
+ if expr.has_form("List", None):
+ return ListExpression(
+ *(
+ Expression(Symbol(self.get_name()), elem, toUnit).evaluate(
+ evaluation
+ )
+ for elem in expr.elements
+ )
+ )
+ if not expr.has_form("Quantity", 2):
+ return None
- mag = get_converted_magnitude(converted_quantity.magnitude, evaluation)
+ mag, unit = expr.elements
- return Expression(
- SymbolQuantity,
- converted_quantity.magnitude,
- String(converted_quantity.units),
+ try:
+ return convert_units(
+ mag,
+ unit,
+ toUnit,
+ evaluation,
)
-
- if len(evaluation.out) > 0:
- return
- if expr.has_form("List", None):
- abc = []
- for i in range(len(expr.elements)):
- abc.append(convert_unit(expr.elements[i].elements))
- return ListExpression(*abc)
- else:
- return convert_unit(expr.elements)
+ except ValueError:
+ return None
+
+ def eval_list_to_base_unit(self, expr, evaluation: Evaluation):
+ "UnitConvert[expr_List]"
+ head = Symbol(self.get_name())
+ return ListExpression(
+ *(Expression(head, item).evaluate(evaluation) for item in expr.elements)
+ )
+
+ def eval_quantity_to_base_unit(self, mag, unit, evaluation: Evaluation):
+ "UnitConvert[Quantity[mag_, unit_]]"
+ try:
+ return convert_units(mag, unit, evaluation=evaluation)
+ except ValueError:
+ return None
diff --git a/mathics/builtin/quantum_mechanics/angular.py b/mathics/builtin/quantum_mechanics/angular.py
index b063c7081..bddc80dc3 100644
--- a/mathics/builtin/quantum_mechanics/angular.py
+++ b/mathics/builtin/quantum_mechanics/angular.py
@@ -15,12 +15,12 @@
from sympy.physics.quantum.cg import CG
from sympy.physics.wigner import wigner_3j, wigner_6j
-from mathics.builtin.base import SympyFunction
from mathics.core.atoms import Integer
from mathics.core.attributes import ( # A_LISTABLE,; A_NUMERIC_FUNCTION,
A_PROTECTED,
A_READ_PROTECTED,
)
+from mathics.core.builtin import SympyFunction
from mathics.core.convert.python import from_python
from mathics.core.convert.sympy import from_sympy
from mathics.core.evaluation import Evaluation
diff --git a/mathics/builtin/recurrence.py b/mathics/builtin/recurrence.py
index f6c215b26..a795d3d8a 100644
--- a/mathics/builtin/recurrence.py
+++ b/mathics/builtin/recurrence.py
@@ -12,9 +12,9 @@
import sympy
-from mathics.builtin.base import Builtin
from mathics.core.atoms import IntegerM1
from mathics.core.attributes import A_CONSTANT
+from mathics.core.builtin import Builtin
from mathics.core.convert.sympy import from_sympy, sympy_symbol_prefix
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
@@ -118,7 +118,6 @@ def is_relation(eqn):
and isinstance(le.elements[0].to_python(), int)
and ri.is_numeric(evaluation)
):
-
r_sympy = ri.to_sympy()
if r_sympy is None:
raise ValueError
diff --git a/mathics/builtin/scipy_utils/integrators.py b/mathics/builtin/scipy_utils/integrators.py
index 72dde20fe..82db83667 100644
--- a/mathics/builtin/scipy_utils/integrators.py
+++ b/mathics/builtin/scipy_utils/integrators.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from mathics.builtin.base import check_requires_list
+from mathics.core.builtin import check_requires_list
from mathics.core.util import IS_PYPY
if IS_PYPY or not check_requires_list(["scipy", "numpy"]):
diff --git a/mathics/builtin/scipy_utils/optimizers.py b/mathics/builtin/scipy_utils/optimizers.py
index 4ca04a67e..dcc52b47a 100644
--- a/mathics/builtin/scipy_utils/optimizers.py
+++ b/mathics/builtin/scipy_utils/optimizers.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from mathics.builtin.base import check_requires_list
from mathics.core.atoms import Number, Real
+from mathics.core.builtin import check_requires_list
from mathics.core.convert.function import expression_to_callable_and_args
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
diff --git a/mathics/builtin/scoping.py b/mathics/builtin/scoping.py
index f317f22a6..2333dcedd 100644
--- a/mathics/builtin/scoping.py
+++ b/mathics/builtin/scoping.py
@@ -5,10 +5,10 @@
from mathics_scanner import is_symbol_name
-from mathics.builtin.base import Builtin, Predefined
from mathics.core.assignment import get_symbol_list
from mathics.core.atoms import Integer, String
from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED, attribute_string_to_number
+from mathics.core.builtin import Builtin, Predefined
from mathics.core.evaluation import Evaluation
from mathics.core.list import ListExpression
from mathics.core.symbols import Symbol, fully_qualified_symbol_name
@@ -216,15 +216,6 @@ class Context_(Predefined):
>> $Context
= Global`
-
- #> InputForm[$Context]
- = "Global`"
-
- ## Test general context behaviour
- #> Plus === Global`Plus
- = False
- #> `Plus === Global`Plus
- = True
"""
messages = {"cxset": "`1` is not a valid context name ending in `."}
@@ -549,9 +540,6 @@ class Unique(Predefined):
>> Unique["x"]
= x...
- #> Unique[{}]
- = {}
-
## FIXME: include the rest of these in test/builtin/test-unique.py
## Each use of Unique[symbol] increments $ModuleNumber:
## >> {$ModuleNumber, Unique[x], $ModuleNumber}
diff --git a/mathics/builtin/sparse.py b/mathics/builtin/sparse.py
index d2511fda7..8d82edc21 100644
--- a/mathics/builtin/sparse.py
+++ b/mathics/builtin/sparse.py
@@ -5,8 +5,8 @@
"""
-from mathics.builtin.base import Builtin
from mathics.core.atoms import Integer, Integer0
+from mathics.core.builtin import Builtin
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
diff --git a/mathics/builtin/specialfns/bessel.py b/mathics/builtin/specialfns/bessel.py
index efd9de159..ebe20fea2 100644
--- a/mathics/builtin/specialfns/bessel.py
+++ b/mathics/builtin/specialfns/bessel.py
@@ -4,8 +4,6 @@
import mpmath
-from mathics.builtin.arithmetic import _MPMathFunction
-from mathics.builtin.base import Builtin
from mathics.core.atoms import Integer
from mathics.core.attributes import (
A_LISTABLE,
@@ -14,6 +12,7 @@
A_PROTECTED,
A_READ_PROTECTED,
)
+from mathics.core.builtin import Builtin, MPMathFunction
from mathics.core.convert.mpmath import from_mpmath
from mathics.core.evaluation import Evaluation
from mathics.core.number import (
@@ -24,14 +23,13 @@
)
-class _Bessel(_MPMathFunction):
-
+class _Bessel(MPMathFunction):
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED | A_READ_PROTECTED
nargs = {2}
-class AiryAi(_MPMathFunction):
+class AiryAi(MPMathFunction):
"""
:Airy function of the first kind:
https://en.wikipedia.org/wiki/Airy_function (
@@ -65,7 +63,7 @@ class AiryAi(_MPMathFunction):
sympy_name = "airyai"
-class AiryAiPrime(_MPMathFunction):
+class AiryAiPrime(MPMathFunction):
"""
Derivative of Airy function (
:Sympy:
@@ -110,18 +108,6 @@ class AiryAiZero(Builtin):
>> N[AiryAiZero[1]]
= -2.33811
-
- #> AiryAiZero[1]
- = AiryAiZero[1]
-
- #> AiryAiZero[1.]
- = AiryAiZero[1.]
-
- #> AiryAi[AiryAiZero[1]]
- = 0
-
- #> N[AiryAiZero[2], 100]
- = -4.087949444130970616636988701457391060224764699108529754984160876025121946836047394331169160758270562
"""
# TODO: 'AiryAiZero[$k$, $x0$]' - $k$th zero less than x0
@@ -160,7 +146,7 @@ def eval_N(self, k, precision, evaluation: Evaluation):
return from_mpmath(result, precision=p)
-class AiryBi(_MPMathFunction):
+class AiryBi(MPMathFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/AiryBi.html
@@ -193,7 +179,7 @@ class AiryBi(_MPMathFunction):
sympy_name = "airybi"
-class AiryBiPrime(_MPMathFunction):
+class AiryBiPrime(MPMathFunction):
"""
:WMA link:https://reference.wolfram.com/language/ref/AiryBiPrime.html
@@ -236,18 +222,6 @@ class AiryBiZero(Builtin):
>> N[AiryBiZero[1]]
= -1.17371
-
- #> AiryBiZero[1]
- = AiryBiZero[1]
-
- #> AiryBiZero[1.]
- = AiryBiZero[1.]
-
- #> AiryBi[AiryBiZero[1]]
- = 0
-
- #> N[AiryBiZero[2], 100]
- = -3.271093302836352715680228240166413806300935969100284801485032396261130864238742879252000673830055014
"""
# TODO: 'AiryBiZero[$k$, $x0$]' - $k$th zero less than x0
@@ -381,9 +355,6 @@ class BesselJ(_Bessel):
>> BesselJ[0, 5.2]
= -0.11029
- #> BesselJ[2.5, 1]
- = 0.0494968
-
>> D[BesselJ[n, z], z]
= -BesselJ[1 + n, z] / 2 + BesselJ[-1 + n, z] / 2
diff --git a/mathics/builtin/specialfns/elliptic.py b/mathics/builtin/specialfns/elliptic.py
index 928b4bd26..a0db45c02 100644
--- a/mathics/builtin/specialfns/elliptic.py
+++ b/mathics/builtin/specialfns/elliptic.py
@@ -12,9 +12,9 @@
import sympy
-from mathics.builtin.base import SympyFunction
from mathics.core.atoms import Integer
from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED
+from mathics.core.builtin import SympyFunction
from mathics.core.convert.sympy import from_sympy, to_numeric_sympy_args
from mathics.eval.numerify import numerify
diff --git a/mathics/builtin/specialfns/erf.py b/mathics/builtin/specialfns/erf.py
index a79aac241..08f33f860 100644
--- a/mathics/builtin/specialfns/erf.py
+++ b/mathics/builtin/specialfns/erf.py
@@ -5,17 +5,18 @@
"""
-from mathics.builtin.arithmetic import _MPMathFunction, _MPMathMultiFunction
from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED
+from mathics.core.builtin import MPMathFunction, MPMathMultiFunction
-class Erf(_MPMathMultiFunction):
+class Erf(MPMathMultiFunction):
"""
:Error function:
https://en.wikipedia.org/wiki/Error_function (
:SymPy:
- https://docs.sympy.org/latest/modules/functions/special.html#sympy.functions.special.error_functions.erf,
+ https://docs.sympy.org/latest/modules/functions
+ /special.html#sympy.functions.special.error_functions.erf,
:WMA: https://reference.wolfram.com/language/ref/Erf.html)
@@ -269,9 +269,6 @@ class SphericalHarmonicY(_MPMathFunction):
## Results depend on sympy version
>> SphericalHarmonicY[3, 1, theta, phi]
= ...
-
- #> SphericalHarmonicY[1,1,x,y]
- = -Sqrt[6] E ^ (I y) Sin[x] / (4 Sqrt[Pi])
"""
nargs = {4}
@@ -290,7 +287,7 @@ def prepare_mathics(self, sympy_expr):
# TODO: Zernike polynomials not yet implemented in mpmath nor sympy
#
-# class ZernikeR(_MPMathFunction):
+# class ZernikeR(MPMathFunction):
# """
# :Zermike polynomials: https://en.wikipedia.org/wiki/Zernike_polynomials
.
diff --git a/mathics/builtin/specialfns/zeta.py b/mathics/builtin/specialfns/zeta.py
index 85059404c..dd1e49e8f 100644
--- a/mathics/builtin/specialfns/zeta.py
+++ b/mathics/builtin/specialfns/zeta.py
@@ -6,11 +6,11 @@
import mpmath
-from mathics.builtin.arithmetic import _MPMathFunction
+from mathics.core.builtin import MPMathFunction
from mathics.core.convert.mpmath import from_mpmath
-class LerchPhi(_MPMathFunction):
+class LerchPhi(MPMathFunction):
"""
:WMA link:
@@ -45,7 +45,7 @@ def eval(self, z, s, a, evaluation):
# return sympy.expand_func(sympy.lerchphi(py_z, py_s, py_a))
-class Zeta(_MPMathFunction):
+class Zeta(MPMathFunction):
"""
:WMA link:
diff --git a/mathics/builtin/statistics/base.py b/mathics/builtin/statistics/base.py
index 8ec4fccf5..b181cf71d 100644
--- a/mathics/builtin/statistics/base.py
+++ b/mathics/builtin/statistics/base.py
@@ -1,7 +1,7 @@
"""
Base classes for Descriptive Statistics
"""
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.symbols import Symbol
diff --git a/mathics/builtin/statistics/dependency.py b/mathics/builtin/statistics/dependency.py
index 899e77e12..e2949f40e 100644
--- a/mathics/builtin/statistics/dependency.py
+++ b/mathics/builtin/statistics/dependency.py
@@ -9,9 +9,9 @@
# Here we are also hiding "moements" since this can erroneously appear at the top level.
sort_order = "mathics.builtin.special-moments"
-from mathics.builtin.base import Builtin
from mathics.builtin.statistics.base import NotRectangularException, Rectangular
from mathics.core.atoms import Integer
+from mathics.core.builtin import Builtin
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.symbols import Symbol, SymbolDivide
diff --git a/mathics/builtin/statistics/general.py b/mathics/builtin/statistics/general.py
index 1ad3726da..782c8147b 100644
--- a/mathics/builtin/statistics/general.py
+++ b/mathics/builtin/statistics/general.py
@@ -3,8 +3,8 @@
General Statistics
"""
-# from mathics.builtin.base import Builtin, SympyFunction
-from mathics.builtin.base import Builtin
+# from mathics.core.builtin import Builtin, SympyFunction
+from mathics.core.builtin import Builtin
# import sympy.stats
# from mathics.core.convert.sympy import from_sympy
diff --git a/mathics/builtin/statistics/location.py b/mathics/builtin/statistics/location.py
index 2999289a1..079e290ea 100644
--- a/mathics/builtin/statistics/location.py
+++ b/mathics/builtin/statistics/location.py
@@ -3,9 +3,9 @@
"""
from mathics.algorithm.introselect import introselect
-from mathics.builtin.base import Builtin
from mathics.builtin.statistics.base import NotRectangularException, Rectangular
from mathics.core.atoms import Integer2
+from mathics.core.builtin import Builtin
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.symbols import SymbolDivide, SymbolPlus
diff --git a/mathics/builtin/statistics/orderstats.py b/mathics/builtin/statistics/orderstats.py
index 5a8f63f0c..56257a784 100644
--- a/mathics/builtin/statistics/orderstats.py
+++ b/mathics/builtin/statistics/orderstats.py
@@ -16,9 +16,9 @@
from mpmath import ceil as mpceil, floor as mpfloor
from mathics.algorithm.introselect import introselect
-from mathics.builtin.base import Builtin
from mathics.builtin.list.math import _RankedTakeLargest, _RankedTakeSmallest
from mathics.core.atoms import Atom, Integer, Symbol, SymbolTrue
+from mathics.core.builtin import Builtin
from mathics.core.expression import Evaluation, Expression
from mathics.core.list import ListExpression
from mathics.core.symbols import SymbolFloor, SymbolPlus, SymbolTimes
@@ -272,9 +272,6 @@ class Sort(Builtin):
= {2 + c_, 1 + b__}
>> Sort[{x_ + n_*y_, x_ + y_}, PatternsOrderedQ]
= {x_ + n_ y_, x_ + y_}
-
- #> Sort[{x_, y_}, PatternsOrderedQ]
- = {x_, y_}
"""
summary_text = "sort lexicographically or with any comparison function"
diff --git a/mathics/builtin/statistics/shape.py b/mathics/builtin/statistics/shape.py
index b89d7b93b..f0b9e6218 100644
--- a/mathics/builtin/statistics/shape.py
+++ b/mathics/builtin/statistics/shape.py
@@ -4,7 +4,7 @@
Shape Statistics
"""
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
class Kurtosis(Builtin):
diff --git a/mathics/builtin/string/characters.py b/mathics/builtin/string/characters.py
index 37e0b9a70..2a8808403 100644
--- a/mathics/builtin/string/characters.py
+++ b/mathics/builtin/string/characters.py
@@ -4,9 +4,9 @@
"""
-from mathics.builtin.base import Builtin, Test
from mathics.core.atoms import String
from mathics.core.attributes import A_LISTABLE, A_PROTECTED, A_READ_PROTECTED
+from mathics.core.builtin import Builtin, Test
from mathics.core.convert.expression import to_mathics_list
from mathics.core.evaluation import Evaluation
from mathics.core.list import ListExpression
@@ -25,18 +25,6 @@ class Characters(Builtin):
>> Characters["abc"]
= {a, b, c}
-
- #> \\.78\\.79\\.7A
- = xyz
-
- #> \\:0078\\:0079\\:007A
- = xyz
-
- #> \\101\\102\\103\\061\\062\\063
- = ABC123
-
- #> \\[Alpha]\\[Beta]\\[Gamma]
- = \u03B1\u03B2\u03B3
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -142,12 +130,6 @@ class LetterQ(Builtin):
>> LetterQ["Welcome to Mathics"]
= False
-
- #> LetterQ[""]
- = True
-
- #> LetterQ["\\[Alpha]\\[Beta]\\[Gamma]\\[Delta]\\[Epsilon]\\[Zeta]\\[Eta]\\[Theta]"]
- = True
"""
rules = {
diff --git a/mathics/builtin/string/charcodes.py b/mathics/builtin/string/charcodes.py
index 54a022f8a..325b71129 100644
--- a/mathics/builtin/string/charcodes.py
+++ b/mathics/builtin/string/charcodes.py
@@ -6,8 +6,8 @@
import sys
from mathics.builtin.atomic.strings import to_python_encoding
-from mathics.builtin.base import Builtin
from mathics.core.atoms import Integer, Integer1, String
+from mathics.core.builtin import Builtin
from mathics.core.convert.expression import to_mathics_list
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
@@ -57,26 +57,12 @@ class ToCharacterCode(Builtin):
>> ToCharacterCode[{"ab", "c"}]
= {{97, 98}, {99}}
- #> ToCharacterCode[{"ab"}]
- = {{97, 98}}
-
- #> ToCharacterCode[{{"ab"}}]
- : String or list of strings expected at position 1 in ToCharacterCode[{{ab}}].
- = ToCharacterCode[{{ab}}]
-
>> ToCharacterCode[{"ab", x}]
: String or list of strings expected at position 1 in ToCharacterCode[{ab, x}].
= ToCharacterCode[{ab, x}]
>> ListPlot[ToCharacterCode["plot this string"], Filling -> Axis]
= -Graphics-
-
- #> ToCharacterCode[x]
- : String or list of strings expected at position 1 in ToCharacterCode[x].
- = ToCharacterCode[x]
-
- #> ToCharacterCode[""]
- = {}
"""
messages = {
@@ -167,42 +153,6 @@ class FromCharacterCode(Builtin):
>> ToCharacterCode["abc 123"] // FromCharacterCode
= abc 123
-
- #> #1 == ToCharacterCode[FromCharacterCode[#1]] & [RandomInteger[{0, 65535}, 100]]
- = True
-
- #> FromCharacterCode[{}] // InputForm
- = ""
-
- #> FromCharacterCode[65536]
- : A character code, which should be a non-negative integer less than 65536, is expected at position 1 in {65536}.
- = FromCharacterCode[65536]
- #> FromCharacterCode[-1]
- : Non-negative machine-sized integer expected at position 1 in FromCharacterCode[-1].
- = FromCharacterCode[-1]
- #> FromCharacterCode[444444444444444444444444444444444444]
- : Non-negative machine-sized integer expected at position 1 in FromCharacterCode[444444444444444444444444444444444444].
- = FromCharacterCode[444444444444444444444444444444444444]
-
- #> FromCharacterCode[{100, 101, -1}]
- : A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, -1}.
- = FromCharacterCode[{100, 101, -1}]
- #> FromCharacterCode[{100, 101, 65536}]
- : A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, 65536}.
- = FromCharacterCode[{100, 101, 65536}]
- #> FromCharacterCode[{100, 101, x}]
- : A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, x}.
- = FromCharacterCode[{100, 101, x}]
- #> FromCharacterCode[{100, {101}}]
- : A character code, which should be a non-negative integer less than 65536, is expected at position 2 in {100, {101}}.
- = FromCharacterCode[{100, {101}}]
-
- #> FromCharacterCode[{{97, 98, 99}, {100, 101, x}}]
- : A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, x}.
- = FromCharacterCode[{{97, 98, 99}, {100, 101, x}}]
- #> FromCharacterCode[{{97, 98, x}, {100, 101, x}}]
- : A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {97, 98, x}.
- = FromCharacterCode[{{97, 98, x}, {100, 101, x}}]
"""
messages = {
diff --git a/mathics/builtin/string/operations.py b/mathics/builtin/string/operations.py
index e06d44ce0..543084581 100644
--- a/mathics/builtin/string/operations.py
+++ b/mathics/builtin/string/operations.py
@@ -13,7 +13,6 @@
mathics_split,
to_regex,
)
-from mathics.builtin.base import BinaryOperator, Builtin
from mathics.core.atoms import Integer, Integer1, String
from mathics.core.attributes import (
A_FLAT,
@@ -22,6 +21,7 @@
A_PROTECTED,
A_READ_PROTECTED,
)
+from mathics.core.builtin import BinaryOperator, Builtin
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
from mathics.core.expression import BoxError, Expression, string_list
@@ -180,87 +180,19 @@ class StringInsert(Builtin):
>> StringInsert["noting", "h", 4]
= nothing
- #> StringInsert["abcdefghijklm", "X", 15]
- : Cannot insert at position 15 in abcdefghijklm.
- = StringInsert[abcdefghijklm, X, 15]
-
- #> StringInsert[abcdefghijklm, "X", 4]
- : String or list of strings expected at position 1 in StringInsert[abcdefghijklm, X, 4].
- = StringInsert[abcdefghijklm, X, 4]
-
- #> StringInsert["abcdefghijklm", X, 4]
- : String expected at position 2 in StringInsert[abcdefghijklm, X, 4].
- = StringInsert[abcdefghijklm, X, 4]
-
- #> StringInsert["abcdefghijklm", "X", a]
- : Position specification a in StringInsert[abcdefghijklm, X, a] is not a machine-sized integer or a list of machine-sized integers.
- = StringInsert[abcdefghijklm, X, a]
-
- #> StringInsert["abcdefghijklm", "X", 0]
- : Cannot insert at position 0 in abcdefghijklm.
- = StringInsert[abcdefghijklm, X, 0]
-
>> StringInsert["note", "d", -1]
= noted
>> StringInsert["here", "t", -5]
= there
- #> StringInsert["abcdefghijklm", "X", -15]
- : Cannot insert at position -15 in abcdefghijklm.
- = StringInsert[abcdefghijklm, X, -15]
-
>> StringInsert["adac", "he", {1, 5}]
= headache
- #> StringInsert["abcdefghijklm", "X", {1, -1, 14, -14}]
- = XXabcdefghijklmXX
-
- #> StringInsert["abcdefghijklm", "X", {1, 0}]
- : Cannot insert at position 0 in abcdefghijklm.
- = StringInsert[abcdefghijklm, X, {1, 0}]
-
- #> StringInsert["", "X", {1}]
- = X
-
- #> StringInsert["", "X", {1, -1}]
- = XX
-
- #> StringInsert["", "", {1}]
- = #<--#
-
- #> StringInsert["", "X", {1, 2}]
- : Cannot insert at position 2 in .
- = StringInsert[, X, {1, 2}]
-
- #> StringInsert["abcdefghijklm", "", {1, 2, 3, 4 ,5, -6}]
- = abcdefghijklm
-
- #> StringInsert["abcdefghijklm", "X", {}]
- = abcdefghijklm
-
>> StringInsert[{"something", "sometimes"}, " ", 5]
= {some thing, some times}
- #> StringInsert[{"abcdefghijklm", "Mathics"}, "X", 13]
- : Cannot insert at position 13 in Mathics.
- = {abcdefghijklXm, StringInsert[Mathics, X, 13]}
-
- #> StringInsert[{"", ""}, "", {1, 1, 1, 1}]
- = {, }
-
- #> StringInsert[{"abcdefghijklm", "Mathics"}, "X", {0, 2}]
- : Cannot insert at position 0 in abcdefghijklm.
- : Cannot insert at position 0 in Mathics.
- = {StringInsert[abcdefghijklm, X, {0, 2}], StringInsert[Mathics, X, {0, 2}]}
-
- #> StringInsert[{"abcdefghijklm", Mathics}, "X", {1, 2}]
- : String or list of strings expected at position 1 in StringInsert[{abcdefghijklm, Mathics}, X, {1, 2}].
- = StringInsert[{abcdefghijklm, Mathics}, X, {1, 2}]
-
- #> StringInsert[{"", "Mathics"}, "X", {1, 1, -1}]
- = {XXX, XXMathicsX}
-
+ Insert dot as millar separators
>> StringInsert["1234567890123456", ".", Range[-16, -4, 3]]
= 1.234.567.890.123.456"""
@@ -468,34 +400,6 @@ class StringPosition(Builtin):
>> StringPosition[data, "uranium"]
= {{299, 305}, {870, 876}, {1538, 1544}, {1671, 1677}, {2300, 2306}, {2784, 2790}, {3093, 3099}}
- #> StringPosition["123ABCxyABCzzzABCABC", "ABC", -1]
- : Non-negative integer or Infinity expected at position 3 in StringPosition[123ABCxyABCzzzABCABC, ABC, -1].
- = StringPosition[123ABCxyABCzzzABCABC, ABC, -1]
-
- ## Overlaps
- #> StringPosition["1231221312112332", RegularExpression["[12]+"]]
- = {{1, 2}, {2, 2}, {4, 7}, {5, 7}, {6, 7}, {7, 7}, {9, 13}, {10, 13}, {11, 13}, {12, 13}, {13, 13}, {16, 16}}
- #> StringPosition["1231221312112332", RegularExpression["[12]+"], Overlaps -> False]
- = {{1, 2}, {4, 7}, {9, 13}, {16, 16}}
- #> StringPosition["1231221312112332", RegularExpression["[12]+"], Overlaps -> x]
- = {{1, 2}, {4, 7}, {9, 13}, {16, 16}}
- #> StringPosition["1231221312112332", RegularExpression["[12]+"], Overlaps -> All]
- : Overlaps -> All option is not currently implemented in Mathics.
- = {{1, 2}, {2, 2}, {4, 7}, {5, 7}, {6, 7}, {7, 7}, {9, 13}, {10, 13}, {11, 13}, {12, 13}, {13, 13}, {16, 16}}
-
- #> StringPosition["21211121122", {"121", "11"}]
- = {{2, 4}, {4, 5}, {5, 6}, {6, 8}, {8, 9}}
- #> StringPosition["21211121122", {"121", "11"}, Overlaps -> False]
- = {{2, 4}, {5, 6}, {8, 9}}
-
- #> StringPosition[{"abc", "abcda"}, "a"]
- = {{{1, 1}}, {{1, 1}, {5, 5}}}
-
- #> StringPosition[{"abc"}, "a", Infinity]
- = {{{1, 1}}}
-
- #> StringPosition["abc"]["123AabcDEabc"]
- = {{5, 7}, {10, 12}}
"""
messages = {
@@ -639,51 +543,6 @@ class StringReplace(_StringFind):
>> StringReplace[{"xyxyxxy", "yxyxyxxxyyxy"}, "xy" -> "A"]
= {AAxA, yAAxxAyA}
- #> StringReplace["abcabc", "a" -> "b", Infinity]
- = bbcbbc
- #> StringReplace[x, "a" -> "b"]
- : String or list of strings expected at position 1 in StringReplace[x, a -> b].
- = StringReplace[x, a -> b]
- #> StringReplace["xyzwxyzwaxyzxyzw", x]
- : x is not a valid string replacement rule.
- = StringReplace[xyzwxyzwaxyzxyzw, x]
- #> StringReplace["xyzwxyzwaxyzxyzw", x -> y]
- : Element x is not a valid string or pattern element in x.
- = StringReplace[xyzwxyzwaxyzxyzw, x -> y]
- #> StringReplace["abcabc", "a" -> "b", -1]
- : Non-negative integer or Infinity expected at position 3 in StringReplace[abcabc, a -> b, -1].
- = StringReplace[abcabc, a -> b, -1]
- #> StringReplace["abc", "b" -> 4]
- : String expected.
- = a <> 4 <> c
-
- #> StringReplace["01101100010", "01" .. -> "x"]
- = x1x100x0
-
- #> StringReplace["abc abcb abdc", "ab" ~~ _ -> "X"]
- = X Xb Xc
-
- #> StringReplace["abc abcd abcd", WordBoundary ~~ "abc" ~~ WordBoundary -> "XX"]
- = XX abcd abcd
-
- #> StringReplace["abcd acbd", RegularExpression["[ab]"] -> "XX"]
- = XXXXcd XXcXXd
-
- #> StringReplace["abcd acbd", RegularExpression["[ab]"] ~~ _ -> "YY"]
- = YYcd YYYY
-
- #> StringReplace["abcdabcdaabcabcd", {"abc" -> "Y", "d" -> "XXX"}]
- = YXXXYXXXaYYXXX
-
-
- #> StringReplace[" Have a nice day. ", (StartOfString ~~ Whitespace) | (Whitespace ~~ EndOfString) -> ""] // FullForm
- = "Have a nice day."
-
- #> StringReplace["xyXY", "xy" -> "01"]
- = 01XY
- #> StringReplace["xyXY", "xy" -> "01", IgnoreCase -> True]
- = 0101
-
StringReplace also can be used as an operator:
>> StringReplace["y" -> "ies"]["city"]
= cities
@@ -764,45 +623,11 @@ class StringRiffle(Builtin):
>> StringRiffle[{"a", "b", "c", "d", "e"}]
= a b c d e
- #> StringRiffle[{a, b, c, "d", e, "f"}]
- = a b c d e f
-
- ## 1st is not a list
- #> StringRiffle["abcdef"]
- : List expected at position 1 in StringRiffle[abcdef].
- : StringRiffle called with 1 argument; 2 or more arguments are expected.
- = StringRiffle[abcdef]
-
- #> StringRiffle[{"", "", ""}] // FullForm
- = " "
-
- ## This form is not supported
- #> StringRiffle[{{"a", "b"}, {"c", "d"}}]
- : Sublist form in position 1 is is not implemented yet.
- = StringRiffle[{{a, b}, {c, d}}]
-
>> StringRiffle[{"a", "b", "c", "d", "e"}, ", "]
= a, b, c, d, e
- #> StringRiffle[{"a", "b", "c", "d", "e"}, sep]
- : String expected at position 2 in StringRiffle[{a, b, c, d, e}, sep].
- = StringRiffle[{a, b, c, d, e}, sep]
-
>> StringRiffle[{"a", "b", "c", "d", "e"}, {"(", " ", ")"}]
= (a b c d e)
-
- #> StringRiffle[{"a", "b", "c", "d", "e"}, {" ", ")"}]
- : String expected at position 2 in StringRiffle[{a, b, c, d, e}, { , )}].
- = StringRiffle[{a, b, c, d, e}, { , )}]
- #> StringRiffle[{"a", "b", "c", "d", "e"}, {left, " ", "."}]
- : String expected at position 2 in StringRiffle[{a, b, c, d, e}, {left, , .}].
- = StringRiffle[{a, b, c, d, e}, {left, , .}]
-
- ## This form is not supported
- #> StringRiffle[{"a", "b", "c"}, "+", "-"]
- ## Mathematica result: a+b+c, but we are not support multiple separators
- : Multiple separators form is not implemented yet.
- = StringRiffle[{a, b, c}, +, -]
"""
attributes = A_PROTECTED | A_READ_PROTECTED
@@ -919,14 +744,6 @@ class StringSplit(Builtin):
>> StringSplit["x", "x"]
= {}
- #> StringSplit[x]
- : String or list of strings expected at position 1 in StringSplit[x].
- = StringSplit[x, Whitespace]
-
- #> StringSplit["x", x]
- : Element x is not a valid string or pattern element in x.
- = StringSplit[x, x]
-
Split using a delmiter that has nonzero list of 12's
>> StringSplit["12312123", "12"..]
= {3, 3}
@@ -1043,25 +860,6 @@ class StringTake(Builtin):
StringTake also supports standard sequence specifications
>> StringTake["abcdef", All]
= abcdef
-
- #> StringTake["abcd", 0] // InputForm
- = ""
- #> StringTake["abcd", {3, 2}] // InputForm
- = ""
- #> StringTake["", {1, 0}] // InputForm
- = ""
-
- #> StringTake["abc", {0, 0}]
- : Cannot take positions 0 through 0 in "abc".
- = StringTake[abc, {0, 0}]
-
- #> StringTake[{2, 4},2]
- : String or list of strings expected at position 1.
- = StringTake[{2, 4}, 2]
-
- #> StringTake["kkkl",Graphics[{}]]
- : Integer or a list of sequence specifications expected at position 2.
- = StringTake[kkkl, -Graphics-]
"""
messages = {
diff --git a/mathics/builtin/string/patterns.py b/mathics/builtin/string/patterns.py
index c280db4a6..96e56096b 100644
--- a/mathics/builtin/string/patterns.py
+++ b/mathics/builtin/string/patterns.py
@@ -13,9 +13,9 @@
anchor_pattern,
to_regex,
)
-from mathics.builtin.base import BinaryOperator, Builtin
from mathics.core.atoms import Integer1, String
from mathics.core.attributes import A_FLAT, A_LISTABLE, A_ONE_IDENTITY, A_PROTECTED
+from mathics.core.builtin import BinaryOperator, Builtin
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
@@ -44,9 +44,6 @@ class DigitCharacter(Builtin):
>> StringMatchQ["123245", DigitCharacter..]
= True
-
- #> StringMatchQ["123245a6", DigitCharacter..]
- = False
"""
summary_text = "digit 0-9"
@@ -205,10 +202,6 @@ class StringCases(_StringFind):
>> StringCases["abc-abc xyz-uvw", Shortest[x : WordCharacter .. ~~ "-" ~~ x_] -> x]
= {abc}
- #> StringCases["abc-abc xyz-uvw", Shortest[x : WordCharacter .. ~~ "-" ~~ x : LetterCharacter] -> x]
- : Ignored restriction given for x in x : LetterCharacter as it does not match previous occurrences of x.
- = {abc}
-
>> StringCases["abba", {"a" -> 10, "b" -> 20}, 2]
= {10, 20}
@@ -251,12 +244,6 @@ class StringExpression(BinaryOperator):
>> "a" ~~ "b" // FullForm
= "ab"
-
- #> "a" ~~ "b" ~~ "c" // FullForm
- = "abc"
-
- #> a ~~ b
- = a ~~ b
"""
attributes = A_FLAT | A_ONE_IDENTITY | A_PROTECTED
@@ -305,62 +292,19 @@ class StringFreeQ(Builtin):
>> StringFreeQ["mathics", "a" ~~ __ ~~ "m"]
= True
- #> StringFreeQ["Hello", "o"]
- = False
-
- #> StringFreeQ["a"]["abcd"]
- = False
-
- #> StringFreeQ["Mathics", "ma", IgnoreCase -> False]
- = True
-
>> StringFreeQ["Mathics", "MA" , IgnoreCase -> True]
= False
- #> StringFreeQ["", "Empty String"]
- = True
-
- #> StringFreeQ["", ___]
- = False
-
- #> StringFreeQ["Empty Pattern", ""]
- = False
-
- #> StringFreeQ[notastring, "n"]
- : String or list of strings expected at position 1 in StringFreeQ[notastring, n].
- = StringFreeQ[notastring, n]
-
- #> StringFreeQ["Welcome", notapattern]
- : Element notapattern is not a valid string or pattern element in notapattern.
- = StringFreeQ[Welcome, notapattern]
-
>> StringFreeQ[{"g", "a", "laxy", "universe", "sun"}, "u"]
= {True, True, True, False, False}
- #> StringFreeQ[{}, "list of string is empty"]
- = {}
>> StringFreeQ["e" ~~ ___ ~~ "u"] /@ {"The Sun", "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"}
= {False, False, False, True, True, True, True, True, False}
- #> StringFreeQ[{"A", "Galaxy", "Far", "Far", "Away"}, {"F" ~~ __ ~~ "r", "aw" ~~ ___}]
- = {True, True, False, False, True}
-
>> StringFreeQ[{"A", "Galaxy", "Far", "Far", "Away"}, {"F" ~~ __ ~~ "r", "aw" ~~ ___}, IgnoreCase -> True]
= {True, True, False, False, False}
- #> StringFreeQ[{"A", "Galaxy", "Far", "Far", "Away"}, {}]
- = {True, True, True, True, True}
-
- #> StringFreeQ[{"A", Galaxy, "Far", "Far", Away}, {"F" ~~ __ ~~ "r", "aw" ~~ ___}]
- : String or list of strings expected at position 1 in StringFreeQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}].
- = StringFreeQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}]
-
- #> StringFreeQ[{"A", "Galaxy", "Far", "Far", "Away"}, {F ~~ __ ~~ "r", aw ~~ ___}]
- : Element F ~~ __ ~~ r is not a valid string or pattern element in {F ~~ __ ~~ r, aw ~~ ___}.
- = StringFreeQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}]
- ## Mathematica can detemine correct invalid element in the pattern, it reports error:
- ## Element F is not a valid string or pattern element in {F ~~ __ ~~ r, aw ~~ ___}.
"""
messages = {
@@ -403,47 +347,9 @@ class StringMatchQ(Builtin):
>> StringMatchQ["15a94xcZ6", (DigitCharacter | LetterCharacter)..]
= True
- #> StringMatchQ["abc1", LetterCharacter]
- = False
-
- #> StringMatchQ["abc", "ABC"]
- = False
- #> StringMatchQ["abc", "ABC", IgnoreCase -> True]
- = True
-
- ## Words containing nonword characters
- #> StringMatchQ[{"monkey", "don't", "AAA", "S&P"}, ___ ~~ Except[WordCharacter] ~~ ___]
- = {False, True, False, True}
-
- ## Try to match a literal number
- #> StringMatchQ[1.5, NumberString]
- : String or list of strings expected at position 1 in StringMatchQ[1.5, NumberString].
- = StringMatchQ[1.5, NumberString]
-
Use StringMatchQ as an operator
>> StringMatchQ[LetterCharacter]["a"]
= True
-
- ## Abbreviated string patterns Issue #517
- #> StringMatchQ["abcd", "abc*"]
- = True
- #> StringMatchQ["abc", "abc*"]
- = True
- #> StringMatchQ["abc\\", "abc\\"]
- = True
- #> StringMatchQ["abc*d", "abc\\*d"]
- = True
- #> StringMatchQ["abc*d", "abc\\**"]
- = True
- #> StringMatchQ["abcde", "a*f"]
- = False
-
- #> StringMatchQ["abcde", "a@e"]
- = True
- #> StringMatchQ["aBCDe", "a@e"]
- = False
- #> StringMatchQ["ae", "a@e"]
- = False
"""
attributes = A_LISTABLE | A_PROTECTED
diff --git a/mathics/builtin/string/regexp.py b/mathics/builtin/string/regexp.py
index 9776ffabb..da8d55ff7 100644
--- a/mathics/builtin/string/regexp.py
+++ b/mathics/builtin/string/regexp.py
@@ -4,7 +4,7 @@
"""
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
# eval.strings.to_regex seems to have the implementation.
diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py
index b2feca05d..30f4ab8fe 100644
--- a/mathics/builtin/system.py
+++ b/mathics/builtin/system.py
@@ -4,8 +4,6 @@
Global System Information
"""
-sort_order = "mathics.builtin.global-system-information"
-
import gc
import os
import platform
@@ -13,8 +11,9 @@
import sys
from mathics import version_string
-from mathics.builtin.base import Builtin, Predefined
from mathics.core.atoms import Integer, Integer0, IntegerM1, Real, String
+from mathics.core.attributes import A_CONSTANT
+from mathics.core.builtin import Builtin, Predefined
from mathics.core.convert.expression import to_mathics_list
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
@@ -28,19 +27,119 @@
else:
have_psutil = True
+sort_order = "mathics.builtin.global-system-information"
+
+
+class MaxLengthIntStringConversion(Predefined):
+ """
+ :Python 3.11 Integer string conversion length limitation:
+ https://docs.python.org/3.11/library/stdtypes.html#int-max-str-digits
+
+
'$MaxLengthIntStringConversion'
+
A positive system integer that fixes the largest size of the string that \
+ can appear when converting an 'Integer' value into a 'String'. When the \
+ string value is too large, then the middle of the integer contains \
+ an indication of the number of digits elided inside << >>.
+
+ If '$MaxLengthIntStringConversion' is set to 0, there is no \
+ bound. Aside from 0, 640 is the smallest value allowed.
+
+ The initial value can be set via environment variable \
+ 'DEFAULT_MAX_STR_DIGITS'. If that is not set, \
+ the default value is 7000.
+
+
+ Although Mathics3 can represent integers of arbitrary size, when it formats \
+ the value for display, there can be nonlinear behavior in printing the decimal string \
+ or converting it to a 'String'.
+
+ Python, in version 3.11 and up, puts a default limit on the size of \
+ the number of digits allows when converting a large integer into \
+ a string.
+
+ Show the default value of '$MaxLengthIntStringConversion':
+ >> $MaxLengthIntStringConversion
+ = ...
+
+ 500! is a 1135-digit number:
+ >> 500! //ToString//StringLength
+ = ...
+
+ We first set '$MaxLengthIntStringConversion' to the smallest value allowed, \
+ so that we can see the trunction of digits in the middle:
+ >> $MaxLengthIntStringConversion = 640
+ ## Pyston 2.3.5 returns 0 while CPython returns 640
+ ## Therefore output testing below is generic.
+ = ...
+
+ Note that setting '$MaxLengthIntStringConversion' has an effect only on Python 3.11 and later;
+ Pyston 2.x however ignores this.
+
+ Now when we print the string value of 500! and Pyston 2.x is not used, \
+ the middle digits are removed:
+ >> 500!
+ = ...
+
+ To see this easier, manipulate the result as 'String':
+
+ >> bigFactorial = ToString[500!]; StringTake[bigFactorial, {310, 330}]
+ = ...
+
+ The <<501>> indicates that 501 digits have been omitted in the string conversion.
+
+ Other than 0, an 'Integer' value less than 640 is not accepted:
+ >> $MaxLengthIntStringConversion = 10
+ : 10 is not 0 or an Integer value greater than 640.
+ = ...
+ """
+
+ attributes = A_CONSTANT
+ messages = {"inv": "`1` is not 0 or an Integer value greater than 640."}
+ name = "$MaxLengthIntStringConversion"
+ summary_text = "the maximum length for which an integer is converted to a String"
+
+ def evaluate(self, evaluation) -> Integer:
+ try:
+ return Integer(sys.get_int_max_str_digits())
+ except AttributeError:
+ return Integer0
+
+ def eval_set(self, expr, evaluation):
+ """Set[$MaxLengthIntStringConversion, expr_]"""
+ if isinstance(expr, Integer):
+ try:
+ sys.set_int_max_str_digits(expr.value)
+ return self.evaluate(evaluation)
+ except AttributeError:
+ if expr.value != 0 and expr.value < 640:
+ evaluation.message("$MaxLengthIntStringConversion", "inv", expr)
+ return Integer0
+ except ValueError:
+ pass
+
+ evaluation.message("$MaxLengthIntStringConversion", "inv", expr)
+ return self.evaluate(evaluation)
+
+ def eval_setdelayed(self, expr, evaluation):
+ """SetDelayed[$MaxLengthIntStringConversion, expr_]"""
+ return self.eval_set(expr)
+
class CommandLine(Predefined):
"""
:WMA link:https://reference.wolfram.com/language/ref/$CommandLine.html
'$CommandLine'
-
is a list of strings passed on the command line to launch the Mathics session.
+
is a list of strings passed on the command line to launch the Mathics3 session.
>> $CommandLine
= {...}
"""
- summary_text = "the command line arguments passed when the current Mathics session was launched"
+ summary_text = (
+ "the command line arguments passed when the current Mathics3 "
+ "session was launched"
+ )
name = "$CommandLine"
def evaluate(self, evaluation) -> Expression:
@@ -113,7 +212,8 @@ class Machine(Predefined):
'$Machine'
-
returns a string describing the type of computer system on which the Mathics is being run.
+
returns a string describing the type of computer system on which the \
+ Mathics3 is being run.
X> $Machine
= linux
@@ -132,7 +232,8 @@ class MachineName(Predefined):
'$MachineName'
-
is a string that gives the assigned name of the computer on which Mathics is being run, if such a name is defined.
+
is a string that gives the assigned name of the computer on which Mathics3 \
+ is being run, if such a name is defined.
X> $MachineName
= buster
@@ -169,13 +270,12 @@ class Packages(Predefined):
'$Packages'
-
returns a list of the contexts corresponding to all packages which have been loaded into Mathics.
+
returns a list of the contexts corresponding to all packages which have \
+ been loaded into Mathics.
X> $Packages
= {ImportExport`,XML`,Internal`,System`,Global`}
- #> MemberQ[$Packages, "System`"]
- = True
"""
summary_text = "list the packages loaded in the current session"
@@ -191,14 +291,13 @@ class ParentProcessID(Predefined):
'$ParentProcesID'
-
gives the ID assigned to the process which invokes the \Mathics by the operating system under which it is run.
+
gives the ID assigned to the process which invokes Mathics3 by the operating \
+ system under which it is run.
>> $ParentProcessID
= ...
- #> Head[$ParentProcessID] == Integer
- = True
"""
summary_text = "id of the process that invoked Mathics"
name = "$ParentProcessID"
@@ -213,14 +312,12 @@ class ProcessID(Predefined):
'$ProcessID'
-
gives the ID assigned to the \Mathics process by the operating system under which it is run.
+
gives the ID assigned to the Mathics3 process by the operating system under \
+ which it is run.
gives a string giving the architecture of the processor on which the \Mathics is being run.
+
gives a string giving the architecture of the processor on which \
+ Mathics3 is being run.
>> $ProcessorType
= ...
"""
+
name = "$ProcessorType"
summary_text = (
- "name of the architecture of the processor over which Mathics is running"
+ "name of the architecture of the processor over which Mathics3 is running"
)
def evaluate(self, evaluation):
@@ -259,14 +358,14 @@ class PythonImplementation(Predefined):
'$PythonImplementation'
-
gives a string indication the Python implementation used to run \Mathics.
+
gives a string indication the Python implementation used to run Mathics3.
>> $PythonImplementation
= ...
"""
name = "$PythonImplementation"
- summary_text = "name of the Python implementation running Mathics"
+ summary_text = "name of the Python implementation running Mathics3"
def evaluate(self, evaluation):
from mathics.system_info import python_implementation
@@ -306,7 +405,8 @@ class Run(Builtin):
'Run[$command$]'
-
runs command as an external operating system command, returning the exit code obtained.
+
runs command as an external operating system command, returning the exit \
+ code returned from running the system command.
X> Run["date"]
= ...
@@ -344,13 +444,11 @@ class SystemWordLength(Predefined):
'$SystemWordLength'
-
gives the effective number of bits in raw machine words on the computer system where \Mathics is running.
+
gives the effective number of bits in raw machine words on the computer \
+ system where Mathics3 is running.
X> $SystemWordLength
= 64
-
- #> Head[$SystemWordLength] == Integer
- = True
"""
summary_text = "word length of computer system"
name = "$SystemWordLength"
@@ -578,9 +676,14 @@ class Share(Builtin):
'Share[]'
-
release memory forcing Python to do garbage collection. If Python package is 'psutil' installed is the amount of released memoryis returned. Otherwise returns $0$. This function differs from WMA which tries to reduce the amount of memory required to store definitions, by reducing duplicated definitions.
+
release memory forcing Python to do garbage collection. If Python package \
+ 'psutil' installed is the amount of released memoryis returned. Otherwise \
+ returns $0$. This function differs from WMA which tries to reduce the amount \
+ of memory required to store definitions, by reducing duplicated definitions.
'Share[Symbol]'
-
Does the same thing as 'Share[]'; Note: this function differs from WMA which tries to reduce the amount of memory required to store definitions associated to $Symbol$.
+
Does the same thing as 'Share[]'; Note: this function differs from WMA which \
+ tries to reduce the amount of memory required to store definitions associated \
+ to $Symbol$.
diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py
index c76f74486..bad1a83f4 100644
--- a/mathics/builtin/tensors.py
+++ b/mathics/builtin/tensors.py
@@ -19,65 +19,17 @@
"""
-from mathics.builtin.base import BinaryOperator, Builtin
-from mathics.core.atoms import Integer, String
+from mathics.core.atoms import Integer
from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_PROTECTED
+from mathics.core.builtin import BinaryOperator, Builtin
from mathics.core.evaluation import Evaluation
-from mathics.core.expression import Expression
from mathics.core.list import ListExpression
-from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue
-from mathics.eval.parts import get_part
-
-
-def get_default_distance(p):
- if all(q.is_numeric() for q in p):
- return Symbol("SquaredEuclideanDistance")
- elif all(q.get_head_name() == "System`List" for q in p):
- dimensions = [get_dimensions(q) for q in p]
- if len(dimensions) < 1:
- return None
- d0 = dimensions[0]
- if not all(d == d0 for d in dimensions[1:]):
- return None
- if len(dimensions[0]) == 1: # vectors?
-
- def is_boolean(x):
- return x.get_head_name() == "System`Symbol" and x in (
- SymbolTrue,
- SymbolFalse,
- )
-
- if all(all(is_boolean(e) for e in q.elements) for q in p):
- return Symbol("JaccardDissimilarity")
- return Symbol("SquaredEuclideanDistance")
- elif all(isinstance(q, String) for q in p):
- return Symbol("EditDistance")
- else:
- from mathics.builtin.colors.color_directives import expression_to_color
-
- if all(expression_to_color(q) is not None for q in p):
- return Symbol("ColorDistance")
-
- return None
-
-
-def get_dimensions(expr, head=None):
- if isinstance(expr, Atom):
- return []
- else:
- if head is not None and not expr.head.sameQ(head):
- return []
- sub_dim = None
- sub = []
- for element in expr.elements:
- sub = get_dimensions(element, expr.head)
- if sub_dim is None:
- sub_dim = sub
- else:
- if sub_dim != sub:
- sub = []
- break
- return [len(expr.elements)] + sub
+from mathics.eval.tensors import (
+ eval_Inner,
+ eval_LeviCivitaTensor,
+ eval_Outer,
+ get_dimensions,
+)
class ArrayDepth(Builtin):
@@ -128,11 +80,6 @@ class Dimensions(Builtin):
The expression can have any head:
>> Dimensions[f[f[a, b, c]]]
= {1, 3}
-
- #> Dimensions[{}]
- = {0}
- #> Dimensions[{{}}]
- = {1, 0}
"""
summary_text = "the dimensions of a tensor"
@@ -202,15 +149,6 @@ class Inner(Builtin):
Inner works with tensors of any depth:
>> Inner[f, {{{a, b}}, {{c, d}}}, {{1}, {2}}, g]
= {{{g[f[a, 1], f[b, 2]]}}, {{g[f[c, 1], f[d, 2]]}}}
-
-
- ## Issue #670
- #> A = {{ b ^ ( -1 / 2), 0}, {a * b ^ ( -1 / 2 ), b ^ ( 1 / 2 )}}
- = {{1 / Sqrt[b], 0}, {a / Sqrt[b], Sqrt[b]}}
- #> A . Inverse[A]
- = {{1, 0}, {0, 1}}
- #> A
- = {{1 / Sqrt[b], 0}, {a / Sqrt[b], Sqrt[b]}}
"""
messages = {
@@ -229,46 +167,7 @@ class Inner(Builtin):
def eval(self, f, list1, list2, g, evaluation: Evaluation):
"Inner[f_, list1_, list2_, g_]"
- m = get_dimensions(list1)
- n = get_dimensions(list2)
-
- if not m or not n:
- evaluation.message("Inner", "normal")
- return
- if list1.get_head() != list2.get_head():
- evaluation.message("Inner", "heads", list1.get_head(), list2.get_head())
- return
- if m[-1] != n[0]:
- evaluation.message("Inner", "incom", m[-1], len(m), list1, n[0], list2)
- return
-
- head = list1.get_head()
- inner_dim = n[0]
-
- def rec(i_cur, j_cur, i_rest, j_rest):
- evaluation.check_stopped()
- if i_rest:
- elements = []
- for i in range(1, i_rest[0] + 1):
- elements.append(rec(i_cur + [i], j_cur, i_rest[1:], j_rest))
- return Expression(head, *elements)
- elif j_rest:
- elements = []
- for j in range(1, j_rest[0] + 1):
- elements.append(rec(i_cur, j_cur + [j], i_rest, j_rest[1:]))
- return Expression(head, *elements)
- else:
-
- def summand(i):
- part1 = get_part(list1, i_cur + [i])
- part2 = get_part(list2, [i] + j_cur)
- return Expression(f, part1, part2)
-
- part = Expression(g, *[summand(i) for i in range(1, inner_dim + 1)])
- # cur_expr.elements.append(part)
- return part
-
- return rec([], [], m[:-1], n[1:])
+ return eval_Inner(f, list1, list2, g, evaluation)
class Outer(Builtin):
@@ -287,10 +186,21 @@ class Outer(Builtin):
Outer product of two matrices:
>> Outer[Times, {{a, b}, {c, d}}, {{1, 2}, {3, 4}}]
= {{{{a, 2 a}, {3 a, 4 a}}, {{b, 2 b}, {3 b, 4 b}}}, {{{c, 2 c}, {3 c, 4 c}}, {{d, 2 d}, {3 d, 4 d}}}}
+
+ Outer product of two sparse arrays:
+ >> Outer[Times, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]]
+ = SparseArray[Automatic, {2, 2, 2, 2}, 0, {{1, 2, 1, 2} -> a c, {1, 2, 2, 1} -> a d, {2, 1, 1, 2} -> b c, {2, 1, 2, 1} -> b d}]
'Outer' of multiple lists:
>> Outer[f, {a, b}, {x, y, z}, {1, 2}]
= {{{f[a, x, 1], f[a, x, 2]}, {f[a, y, 1], f[a, y, 2]}, {f[a, z, 1], f[a, z, 2]}}, {{f[b, x, 1], f[b, x, 2]}, {f[b, y, 1], f[b, y, 2]}, {f[b, z, 1], f[b, z, 2]}}}
+
+ 'Outer' converts input sparse arrays to lists if f=!=Times, or if the input is a mixture of sparse arrays and lists:
+ >> Outer[f, SparseArray[{{1, 2} -> a, {2, 1} -> b}], SparseArray[{{1, 2} -> c, {2, 1} -> d}]]
+ = {{{{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}, {{f[a, 0], f[a, c]}, {f[a, d], f[a, 0]}}}, {{{f[b, 0], f[b, c]}, {f[b, d], f[b, 0]}}, {{f[0, 0], f[0, c]}, {f[0, d], f[0, 0]}}}}
+
+ >> Outer[Times, SparseArray[{{1, 2} -> a, {2, 1} -> b}], {c, d}]
+ = {{{0, 0}, {a c, a d}}, {{b c, b d}, {0, 0}}}
Arrays can be ragged:
>> Outer[Times, {{1, 2}}, {{a, b}, {c, d, e}}]
@@ -313,32 +223,7 @@ class Outer(Builtin):
def eval(self, f, lists, evaluation: Evaluation):
"Outer[f_, lists__]"
- lists = lists.get_sequence()
- head = None
- for list in lists:
- if isinstance(list, Atom):
- evaluation.message("Outer", "normal")
- return
- if head is None:
- head = list.head
- elif not list.head.sameQ(head):
- evaluation.message("Outer", "heads", head, list.head)
- return
-
- def rec(item, rest_lists, current):
- evaluation.check_stopped()
- if isinstance(item, Atom) or not item.head.sameQ(head):
- if rest_lists:
- return rec(rest_lists[0], rest_lists[1:], current + [item])
- else:
- return Expression(f, *(current + [item]))
- else:
- elements = []
- for element in item.elements:
- elements.append(rec(element, rest_lists, current))
- return Expression(head, *elements)
-
- return rec(lists[0], lists[1:], [])
+ return eval_Outer(f, lists, evaluation)
class RotationTransform(Builtin):
@@ -464,7 +349,7 @@ class Transpose(Builtin):
:WMA: https://reference.wolfram.com/language/ref/Transpose.html)
-
'Tranpose[$m$]'
+
'Transpose[$m$]'
transposes rows and columns in the matrix $m$.
@@ -489,8 +374,6 @@ class Transpose(Builtin):
= True
#> Clear[matrix, square]
- #> Transpose[x]
- = Transpose[x]
"""
summary_text = "transpose to rearrange indices in any way"
@@ -506,3 +389,57 @@ def eval(self, m, evaluation: Evaluation):
else:
result[col_index].append(item)
return ListExpression(*[ListExpression(*row) for row in result])
+
+
+class ConjugateTranspose(Builtin):
+ """
+
+ :Conjugate transpose: https://en.wikipedia.org/wiki/Conjugate_transpose (
+ :WMA: https://reference.wolfram.com/language/ref/ConjugateTranspose.html)
+
+
gives the $d$-dimensional Levi-Civita totally antisymmetric tensor.
+
+
+ >> LeviCivitaTensor[3]
+ = SparseArray[Automatic, {3, 3, 3}, 0, {{1, 2, 3} -> 1, {1, 3, 2} -> -1, {2, 1, 3} -> -1, {2, 3, 1} -> 1, {3, 1, 2} -> 1, {3, 2, 1} -> -1}]
+
+ >> LeviCivitaTensor[3, List]
+ = {{{0, 0, 0}, {0, 0, 1}, {0, -1, 0}}, {{0, 0, -1}, {0, 0, 0}, {1, 0, 0}}, {{0, 1, 0}, {-1, 0, 0}, {0, 0, 0}}}
+ """
+
+ rules = {
+ "LeviCivitaTensor[d_Integer]/; Greater[d, 0]": "LeviCivitaTensor[d, SparseArray]",
+ "LeviCivitaTensor[d_Integer, List] /; Greater[d, 0]": "LeviCivitaTensor[d, SparseArray] // Normal",
+ }
+
+ summary_text = "give the Levi-Civita tensor with a given dimension"
+
+ def eval(self, d, type, evaluation: Evaluation):
+ "LeviCivitaTensor[d_Integer, type_]"
+
+ return eval_LeviCivitaTensor(d, type)
diff --git a/mathics/builtin/testing_expressions/equality_inequality.py b/mathics/builtin/testing_expressions/equality_inequality.py
index 59152e31e..9b193f7f1 100644
--- a/mathics/builtin/testing_expressions/equality_inequality.py
+++ b/mathics/builtin/testing_expressions/equality_inequality.py
@@ -7,7 +7,6 @@
import sympy
-from mathics.builtin.base import BinaryOperator, Builtin, SympyFunction
from mathics.builtin.numbers.constants import mp_convert_constant
from mathics.core.atoms import COMPARE_PREC, Integer, Integer1, Number, String
from mathics.core.attributes import (
@@ -17,6 +16,7 @@
A_ORDERLESS,
A_PROTECTED,
)
+from mathics.core.builtin import BinaryOperator, Builtin, SympyFunction
from mathics.core.convert.expression import to_expression, to_numeric_args
from mathics.core.expression import Expression
from mathics.core.expression_predefined import (
@@ -253,7 +253,6 @@ def eval_other(self, args, evaluation):
class _MinMax(Builtin):
-
attributes = (
A_FLAT | A_NUMERIC_FUNCTION | A_ONE_IDENTITY | A_ORDERLESS | A_PROTECTED
)
@@ -337,12 +336,6 @@ class BooleanQ(Builtin):
>> BooleanQ[1 < 2]
= True
-
- #> BooleanQ["string"]
- = False
-
- #> BooleanQ[Together[x/y + y/x]]
- = False
"""
rules = {
@@ -668,8 +661,6 @@ class Max(_MinMax):
'Max' does not compare strings or symbols:
>> Max[-1.37, 2, "a", b]
= Max[2, a, b]
- #> Max[x]
- = x
"""
sense = 1
@@ -704,9 +695,6 @@ class Min(_MinMax):
With no arguments, 'Min' gives 'Infinity':
>> Min[]
= Infinity
-
- #> Min[x]
- = x
"""
sense = -1
@@ -850,22 +838,6 @@ class Unequal(_EqualityOperator, _SympyComparison):
>> "a" != "a"
= False
- #> Pi != N[Pi]
- = False
-
- #> a_ != b_
- = a_ != b_
-
- #> Clear[a, b];
- #> a != a != a
- = False
- #> "abc" != "def" != "abc"
- = False
-
- ## Reproduce strange MMA behaviour
- #> a != b != a
- = a != b != a
-
'Unequal' using an empty parameter or list, or a list with one element is True. This is the same as 'Equal".
>> {Unequal[], Unequal[x], Unequal[1]}
diff --git a/mathics/builtin/testing_expressions/expression_tests.py b/mathics/builtin/testing_expressions/expression_tests.py
index 258bfd1e1..59ce5547b 100644
--- a/mathics/builtin/testing_expressions/expression_tests.py
+++ b/mathics/builtin/testing_expressions/expression_tests.py
@@ -1,7 +1,7 @@
"""
Expression Tests
"""
-from mathics.builtin.base import Builtin, PatternError, Test
+from mathics.core.builtin import Builtin, PatternError, Test
from mathics.core.evaluation import Evaluation
from mathics.core.symbols import SymbolFalse, SymbolTrue
from mathics.eval.patterns import match
diff --git a/mathics/builtin/testing_expressions/list_oriented.py b/mathics/builtin/testing_expressions/list_oriented.py
index 74b92c511..b2f819331 100644
--- a/mathics/builtin/testing_expressions/list_oriented.py
+++ b/mathics/builtin/testing_expressions/list_oriented.py
@@ -2,15 +2,15 @@
List-Oriented Tests
"""
-from mathics.builtin.base import Builtin, Test
from mathics.core.atoms import Integer, Integer1, Integer2
+from mathics.core.builtin import Builtin, Test
from mathics.core.evaluation import Evaluation
from mathics.core.exceptions import InvalidLevelspecError
from mathics.core.expression import Expression
-from mathics.core.rules import Pattern
from mathics.core.symbols import Atom, SymbolFalse, SymbolTrue
-from mathics.core.systemsymbols import SymbolSubsetQ
+from mathics.core.systemsymbols import SymbolSubsetQ # , SymbolSparseArray
from mathics.eval.parts import python_levelspec
+from mathics.eval.testing_expressions import check_ArrayQ # , check_SparseArrayQ
class ArrayQ(Builtin):
@@ -51,37 +51,10 @@ class ArrayQ(Builtin):
def eval(self, expr, pattern, test, evaluation: Evaluation):
"ArrayQ[expr_, pattern_, test_]"
- pattern = Pattern.create(pattern)
-
- dims = [len(expr.get_elements())] # to ensure an atom is not an array
-
- def check(level, expr):
- if not expr.has_form("List", None):
- test_expr = Expression(test, expr)
- if test_expr.evaluate(evaluation) != SymbolTrue:
- return False
- level_dim = None
- else:
- level_dim = len(expr.elements)
-
- if len(dims) > level:
- if dims[level] != level_dim:
- return False
- else:
- dims.append(level_dim)
- if level_dim is not None:
- for element in expr.elements:
- if not check(level + 1, element):
- return False
- return True
-
- if not check(0, expr):
- return SymbolFalse
+ # if not isinstance(expr, Atom) and expr.head.sameQ(SymbolSparseArray):
+ # return check_SparseArrayQ(expr, pattern, test, evaluation)
- depth = len(dims) - 1 # None doesn't count
- if not pattern.does_match(Integer(depth), evaluation):
- return SymbolFalse
- return SymbolTrue
+ return check_ArrayQ(expr, pattern, test, evaluation)
class DisjointQ(Test):
@@ -298,31 +271,6 @@ class SubsetQ(Builtin):
Every list is a subset of itself:
>> SubsetQ[{1, 2, 3}, {1, 2, 3}]
= True
-
- #> SubsetQ[{1, 2, 3}, {0, 1}]
- = False
-
- #> SubsetQ[{1, 2, 3}, {1, 2, 3, 4}]
- = False
-
- #> SubsetQ[{1, 2, 3}]
- : SubsetQ called with 1 argument; 2 arguments are expected.
- = SubsetQ[{1, 2, 3}]
-
- #> SubsetQ[{1, 2, 3}, {1, 2}, {3}]
- : SubsetQ called with 3 arguments; 2 arguments are expected.
- = SubsetQ[{1, 2, 3}, {1, 2}, {3}]
-
- #> SubsetQ[a + b + c, {1}]
- : Heads Plus and List at positions 1 and 2 are expected to be the same.
- = SubsetQ[a + b + c, {1}]
-
- #> SubsetQ[{1, 2, 3}, n]
- : Nonatomic expression expected at position 2 in SubsetQ[{1, 2, 3}, n].
- = SubsetQ[{1, 2, 3}, n]
-
- #> SubsetQ[f[a, b, c], f[a]]
- = True
"""
messages = {
diff --git a/mathics/builtin/testing_expressions/logic.py b/mathics/builtin/testing_expressions/logic.py
index 1501b3c11..9ffc80118 100644
--- a/mathics/builtin/testing_expressions/logic.py
+++ b/mathics/builtin/testing_expressions/logic.py
@@ -2,7 +2,6 @@
"""
Logical Combinations
"""
-from mathics.builtin.base import BinaryOperator, Builtin, Predefined, PrefixOperator
from mathics.core.attributes import (
A_FLAT,
A_HOLD_ALL,
@@ -11,6 +10,7 @@
A_ORDERLESS,
A_PROTECTED,
)
+from mathics.core.builtin import BinaryOperator, Builtin, Predefined, PrefixOperator
from mathics.core.evaluation import Evaluation
from mathics.core.exceptions import InvalidLevelspecError
from mathics.core.expression import Expression
@@ -142,9 +142,6 @@ class AnyTrue(_ManyTrue):
>> AnyTrue[{1, 4, 5}, EvenQ]
= True
-
- #> AnyTrue[{}, EvenQ]
- = False
"""
summary_text = "some of the elements are True"
@@ -175,9 +172,6 @@ class AllTrue(_ManyTrue):
>> AllTrue[{2, 4, 7}, EvenQ]
= False
-
- #> AllTrue[{}, EvenQ]
- = True
"""
summary_text = "all the elements are True"
@@ -214,10 +208,6 @@ class Equivalent(BinaryOperator):
Otherwise, 'Equivalent' returns a result in DNF
>> Equivalent[a, b, True, c]
= a && b && c
- #> Equivalent[]
- = True
- #> Equivalent[a]
- = True
"""
attributes = A_ORDERLESS | A_PROTECTED
@@ -330,9 +320,6 @@ class NoneTrue(_ManyTrue):
>> NoneTrue[{1, 4, 5}, EvenQ]
= False
-
- #> NoneTrue[{}, EvenQ]
- = True
"""
summary_text = "all the elements are False"
@@ -505,16 +492,6 @@ class Xor(BinaryOperator):
returns a result in symbolic form:
>> Xor[a, False, b]
= a \\[Xor] b
- #> Xor[]
- = False
- #> Xor[a]
- = a
- #> Xor[False]
- = False
- #> Xor[True]
- = True
- #> Xor[a, b]
- = a \\[Xor] b
"""
attributes = A_FLAT | A_ONE_IDENTITY | A_ORDERLESS | A_PROTECTED
diff --git a/mathics/builtin/testing_expressions/numerical_properties.py b/mathics/builtin/testing_expressions/numerical_properties.py
index 5f5822f9e..02205eaa3 100644
--- a/mathics/builtin/testing_expressions/numerical_properties.py
+++ b/mathics/builtin/testing_expressions/numerical_properties.py
@@ -5,9 +5,9 @@
import sympy
-from mathics.builtin.base import Builtin, SympyFunction, Test
from mathics.core.atoms import Integer, Integer0, Number
from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED
+from mathics.core.builtin import Builtin, SympyFunction, Test
from mathics.core.convert.python import from_bool, from_python
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
@@ -38,16 +38,22 @@ class CoprimeQ(Builtin):
>> CoprimeQ[12, 15]
= False
- CoprimeQ also works for complex numbers
- >> CoprimeQ[1+2I, 1-I]
- = True
+ ##
+ ## CoprimeQ also works for complex numbers
+ ## >> CoprimeQ[1+2I, 1-I]
+ ## = True
+
+ ## This test case is commenteted out because the result produced by sympy is wrong:
+ ## In this case, both numbers can be factorized as 2 (2 + I) and 3 (2 + I):
+ ## >> CoprimeQ[4+2I, 6+3I]
+ ## = False
- >> CoprimeQ[4+2I, 6+3I]
- = True
+ For more than two arguments, CoprimeQ checks if any pair or arguments are coprime:
>> CoprimeQ[2, 3, 5]
= True
+ In this case, since 2 divides 4, the result is False:
>> CoprimeQ[2, 4, 5]
= False
"""
@@ -88,7 +94,7 @@ class EvenQ(Test):
"""
attributes = A_LISTABLE | A_PROTECTED
- summary_text = "test whether one number is divisible by the other"
+ summary_text = "test whether elements are even numbers"
def test(self, n) -> bool:
value = n.get_int_value()
@@ -223,10 +229,6 @@ class MachineNumberQ(Test):
= True
>> MachineNumberQ[2.71828182845904524 + 3.14159265358979324 I]
= False
- #> MachineNumberQ[1.5 + 3.14159265358979324 I]
- = True
- #> MachineNumberQ[1.5 + 5 I]
- = True
"""
summary_text = "test if expression is a machine precision real or complex number"
@@ -253,10 +255,6 @@ class Negative(Builtin):
= False
>> Negative[a + b]
= Negative[a + b]
- #> Negative[-E]
- = True
- #> Negative[Sin[{11, 14}]]
- = {True, False}
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -506,13 +504,6 @@ class Positive(Builtin):
= False
>> Positive[1 + 2 I]
= False
-
- #> Positive[Pi]
- = True
- #> Positive[x]
- = Positive[x]
- #> Positive[Sin[{11, 14}]]
- = {False, True}
"""
attributes = A_LISTABLE | A_PROTECTED
@@ -547,11 +538,6 @@ class PrimeQ(SympyFunction):
>> PrimeQ[2 ^ 127 - 1]
= True
- #> PrimeQ[1]
- = False
- #> PrimeQ[2 ^ 255 - 1]
- = False
-
All prime numbers between 1 and 100:
>> Select[Range[100], PrimeQ]
= {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97}
diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py
index 759c4df31..3e088d43c 100644
--- a/mathics/builtin/trace.py
+++ b/mathics/builtin/trace.py
@@ -22,8 +22,8 @@
from time import time
from typing import Callable
-from mathics.builtin.base import Builtin
from mathics.core.attributes import A_HOLD_ALL, A_HOLD_ALL_COMPLETE, A_PROTECTED
+from mathics.core.builtin import Builtin
from mathics.core.convert.python import from_bool, from_python
from mathics.core.definitions import Definitions
from mathics.core.evaluation import Evaluation
@@ -446,8 +446,9 @@ class PythonCProfileEvaluation(Builtin):
profile $expr$ with the Python's cProfiler.
- >> PythonCProfileEvaluation[a + b + 1]
- = ...
+ ## This produces an error in the LaTeX documentation.
+ ## >> PythonCProfileEvaluation[a + b + 1]
+ ## = ...
"""
attributes = A_HOLD_ALL_COMPLETE | A_PROTECTED
diff --git a/mathics/builtin/vectors/__init__.py b/mathics/builtin/vectors/__init__.py
index 4e1ad3afe..96fe8eef5 100644
--- a/mathics/builtin/vectors/__init__.py
+++ b/mathics/builtin/vectors/__init__.py
@@ -8,7 +8,7 @@
In computer science, it is an array data structure consisting of collection \
of elements identified by at least on array index or key.
-In \Mathics vectors as are Lists. One never needs to distinguish between row \
+In \\Mathics vectors as are Lists. One never needs to distinguish between row \
and column vectors. As with other objects vectors can mix number and symbolic elements.
Vectors can be long, dense, or sparse.
diff --git a/mathics/builtin/vectors/constructing.py b/mathics/builtin/vectors/constructing.py
index 267aa379e..551617bbf 100644
--- a/mathics/builtin/vectors/constructing.py
+++ b/mathics/builtin/vectors/constructing.py
@@ -8,7 +8,7 @@
See also Constructing Lists.
"""
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
class AngleVector(Builtin):
diff --git a/mathics/builtin/vectors/math_ops.py b/mathics/builtin/vectors/math_ops.py
index c57d86721..19f373740 100644
--- a/mathics/builtin/vectors/math_ops.py
+++ b/mathics/builtin/vectors/math_ops.py
@@ -6,8 +6,8 @@
import sympy
-from mathics.builtin.base import Builtin, SympyFunction
from mathics.core.attributes import A_PROTECTED
+from mathics.core.builtin import Builtin, SympyFunction
from mathics.core.convert.sympy import from_sympy, to_sympy_matrix
from mathics.eval.math_ops import eval_Norm, eval_Norm_p
diff --git a/mathics/builtin/vectors/vector_space_operations.py b/mathics/builtin/vectors/vector_space_operations.py
index b6175df0b..776ad5f08 100644
--- a/mathics/builtin/vectors/vector_space_operations.py
+++ b/mathics/builtin/vectors/vector_space_operations.py
@@ -6,12 +6,12 @@
from sympy.physics.quantum import TensorProduct
-from mathics.builtin.base import Builtin, SympyFunction
from mathics.core.atoms import Complex, Integer, Integer0, Integer1, Real
from mathics.core.attributes import ( # A_LISTABLE,; A_NUMERIC_FUNCTION,
A_PROTECTED,
A_READ_PROTECTED,
)
+from mathics.core.builtin import Builtin, SympyFunction
from mathics.core.convert.sympy import from_sympy, to_sympy_matrix
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
@@ -85,14 +85,6 @@ class Normalize(Builtin):
>> Normalize[1 + I]
= (1 / 2 + I / 2) Sqrt[2]
- #> Normalize[0]
- = 0
-
- #> Normalize[{0}]
- = {0}
-
- #> Normalize[{}]
- = {}
"""
rules = {"Normalize[v_]": "Module[{norm = Norm[v]}, If[norm == 0, v, v / norm, v]]"}
@@ -228,9 +220,6 @@ class VectorAngle(Builtin):
>> VectorAngle[{1, 1, 0}, {1, 0, 1}]
= Pi / 3
-
- #> VectorAngle[{0, 1}, {0, 1}]
- = 0
"""
rules = {"VectorAngle[u_, v_]": "ArcCos[u.v / (Norm[u] Norm[v])]"}
diff --git a/mathics/core/assignment.py b/mathics/core/assignment.py
index ab10eacee..fb04e7615 100644
--- a/mathics/core/assignment.py
+++ b/mathics/core/assignment.py
@@ -44,7 +44,7 @@ def __init__(self, lhs, rhs) -> None:
self.rhs = rhs
-def assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset=None):
+def assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset=False):
"""
This is the default assignment. Stores a rule of the form lhs->rhs
as a value associated to each symbol listed in tags.
@@ -159,9 +159,9 @@ def repl_pattern_by_symbol(expr):
changed = False
new_elements = []
- for element in elements:
- element = repl_pattern_by_symbol(element)
- if not (element is element):
+ for _element in elements:
+ element = repl_pattern_by_symbol(_element)
+ if element is not _element:
changed = True
new_elements.append(element)
if changed:
@@ -220,7 +220,7 @@ def unroll_patterns(lhs, rhs, evaluation) -> Tuple[BaseElement, BaseElement]:
# like
# rhs = Expression(Symbol("System`Replace"), Rule(*rulerepl))
# TODO: check if this is the correct behavior.
- rhs, status = rhs.do_apply_rules([Rule(*rulerepl)], evaluation)
+ rhs, _ = rhs.do_apply_rules([Rule(*rulerepl)], evaluation)
name = lhs.get_head_name()
elif name == "System`HoldPattern":
lhs = lhs_elements[0]
@@ -468,7 +468,7 @@ def eval_assign_list(self, lhs, rhs, evaluation, tags, upset):
def eval_assign_makeboxes(self, lhs, rhs, evaluation, tags, upset):
# FIXME: the below is a big hack.
# Currently MakeBoxes boxing is implemented as a bunch of rules.
- # See mathics.builtin.base contribute().
+ # See mathics.core.builtin contribute().
# I think we want to change this so it works like normal SetDelayed
# That is:
# MakeBoxes[CubeRoot, StandardForm] := RadicalBox[3, StandardForm]
@@ -703,7 +703,6 @@ def eval_assign_recursion_limit(lhs, rhs, evaluation):
if (
not rhs_int_value or rhs_int_value < 20 or rhs_int_value > MAX_RECURSION_DEPTH
): # nopep8
-
evaluation.message("$RecursionLimit", "limset", rhs)
raise AssignmentException(lhs, None)
try:
diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py
index eadc66ad0..a265b52c1 100644
--- a/mathics/core/atoms.py
+++ b/mathics/core/atoms.py
@@ -29,8 +29,9 @@
)
from mathics.core.systemsymbols import SymbolFullForm, SymbolInfinity, SymbolInputForm
-# Imperical number that seems to work.
-# We have to be able to match mpmath values with sympy values
+# The below value is an empirical number for comparison precedence
+# that seems to work. We have to be able to match mpmath values with
+# sympy values
COMPARE_PREC = 50
SymbolI = Symbol("I")
@@ -85,17 +86,22 @@ def is_literal(self) -> bool:
return True
def is_numeric(self, evaluation=None) -> bool:
+ # Anything that is in a number class is Numeric, so return True.
return True
- def to_mpmath(self):
+ def to_mpmath(self, precision: Optional[int] = None) -> mpmath.ctx_mp_python.mpf:
"""
- Convert self._value to an mnpath number.
+ Convert self._value to an mpmath number with precision ``precision``
+ If ``precision`` is None, use mpmath's default precision.
- This is the default implementation for Number.
+ A mpmath number is the default implementation for Number.
There are kinds of numbers, like Rational, or Complex, that
need to work differently than this default, and they will
change the implementation accordingly.
"""
+ if precision is not None:
+ with mpmath.workprec(precision):
+ return mpmath.mpf(self._value)
return mpmath.mpf(self._value)
@property
@@ -240,9 +246,21 @@ def default_format(self, evaluation, form) -> str:
def make_boxes(self, form) -> "String":
from mathics.eval.makeboxes import _boxed_string
- if form in ("System`InputForm", "System`FullForm"):
- return _boxed_string(str(self.value), number_as_text=True)
- return String(str(self._value))
+ try:
+ if form in ("System`InputForm", "System`FullForm"):
+ return _boxed_string(str(self.value), number_as_text=True)
+
+ return String(str(self._value))
+ except ValueError:
+ # In Python 3.11, the size of the string
+ # obtained from an integer is limited, and for longer
+ # numbers, this exception is raised.
+ # The idea is to represent the number by its
+ # more significant digits, the lowest significant digits,
+ # and a placeholder saying the number of omitted digits.
+ from mathics.eval.makeboxes import int_to_string_shorter_repr
+
+ return int_to_string_shorter_repr(self._value, form)
def to_sympy(self, **kwargs):
return sympy.Integer(self._value)
@@ -416,7 +434,7 @@ def __neg__(self) -> "MachineReal":
def do_copy(self) -> "MachineReal":
return MachineReal(self._value)
- def get_precision(self) -> float:
+ def get_precision(self) -> int:
"""Returns the default specification for precision in N and other numerical functions."""
return FP_MANTISA_BINARY_DIGITS
@@ -434,14 +452,14 @@ def is_machine_precision(self) -> bool:
return True
def make_boxes(self, form):
- from mathics.builtin.makeboxes import number_form
+ from mathics.builtin.makeboxes import NumberForm_to_String
_number_form_options["_Form"] = form # passed to _NumberFormat
if form in ("System`InputForm", "System`FullForm"):
n = None
else:
n = 6
- return number_form(self, n, None, None, _number_form_options)
+ return NumberForm_to_String(self, n, None, None, _number_form_options)
@property
def is_zero(self) -> bool:
@@ -455,7 +473,7 @@ def round(self, d: Optional[int] = None) -> "MachineReal":
def sameQ(self, other) -> bool:
"""Mathics SameQ for MachineReal.
- If the other comparision value is a MachineReal, the values
+ If the other comparison value is a MachineReal, the values
have to be equal. If the other value is a PrecisionReal though, then
the two values have to be within 1/2 ** (precision) of
other-value's precision. For any other type, sameQ is False.
@@ -467,7 +485,7 @@ def sameQ(self, other) -> bool:
value = self.to_sympy()
# If sympy fixes the issue, this comparison would be
# enough
- if value == other_value:
+ if (value - other_value).is_zero:
return True
# this handles the issue...
diff = abs(value - other_value)
@@ -533,19 +551,20 @@ def __neg__(self) -> "PrecisionReal":
def do_copy(self) -> "PrecisionReal":
return PrecisionReal(self.value)
- def get_precision(self) -> float:
+ def get_precision(self) -> int:
"""Returns the default specification for precision (in binary digits) in N and other numerical functions."""
- return self.value._prec + 1.0
+ return self.value._prec + 1
@property
def is_zero(self) -> bool:
- return self.value == 0.0
+ # self.value == 0 does not work for sympy >=1.13
+ return self.value.is_zero
def make_boxes(self, form):
- from mathics.builtin.makeboxes import number_form
+ from mathics.builtin.makeboxes import NumberForm_to_String
_number_form_options["_Form"] = form # passed to _NumberFormat
- return number_form(
+ return NumberForm_to_String(
self, dps(self.get_precision()), None, None, _number_form_options
)
@@ -566,7 +585,7 @@ def sameQ(self, other) -> bool:
value = self.value
# If sympy would handle properly
# the precision, this wold be enough
- if value == other_value:
+ if (value - other_value).is_zero:
return True
# in the meantime, let's use this comparison.
value = self.value
@@ -706,10 +725,17 @@ def __new__(cls, real, imag):
if isinstance(real, MachineReal) and not isinstance(imag, MachineReal):
imag = imag.round()
- if isinstance(imag, MachineReal) and not isinstance(real, MachineReal):
+ prec = FP_MANTISA_BINARY_DIGITS
+ elif isinstance(imag, MachineReal) and not isinstance(real, MachineReal):
real = real.round()
+ prec = FP_MANTISA_BINARY_DIGITS
+ else:
+ prec = min(
+ (u for u in (x.get_precision() for x in (real, imag)) if u is not None),
+ default=None,
+ )
- value = (real, imag)
+ value = (real, imag, prec)
self = cls._complex_numbers.get(value)
if self is None:
self = super().__new__(cls)
@@ -789,6 +815,7 @@ def is_machine_precision(self) -> bool:
return True
return False
+ # FIXME: funny name get_float_value returns complex?
def get_float_value(self, permit_complex=False) -> Optional[complex]:
if permit_complex:
real = self.real.get_float_value()
@@ -798,7 +825,7 @@ def get_float_value(self, permit_complex=False) -> Optional[complex]:
else:
return None
- def get_precision(self) -> Optional[float]:
+ def get_precision(self) -> Optional[int]:
"""Returns the default specification for precision in N and other numerical functions.
When `None` is be returned no precision is has been defined and this object's value is
exact.
diff --git a/mathics/builtin/base.py b/mathics/core/builtin.py
similarity index 91%
rename from mathics/builtin/base.py
rename to mathics/core/builtin.py
index 4a21365a2..bc0dfa928 100644
--- a/mathics/builtin/base.py
+++ b/mathics/core/builtin.py
@@ -1,12 +1,18 @@
# -*- coding: utf-8 -*-
-# cython: language_level=3
+"""
+Class definitions used in mathics.builtin modules that define the
+base Mathics3's classes: Predefined, Builtin, Test, Operator (and from that
+UnaryOperator, BinaryOperator, PrefixOperator, PostfixOperator, etc.),
+SympyFunction, MPMathFunction, etc.
+"""
import importlib
import re
from functools import lru_cache, total_ordering
from itertools import chain
-from typing import Any, Callable, Dict, Iterable, List, Optional, Union, cast
+from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union, cast
+import mpmath
import sympy
from mathics.core.atoms import (
@@ -18,7 +24,13 @@
PrecisionReal,
String,
)
-from mathics.core.attributes import A_HOLD_ALL, A_NO_ATTRIBUTES, A_PROTECTED
+from mathics.core.attributes import (
+ A_HOLD_ALL,
+ A_LISTABLE,
+ A_NO_ATTRIBUTES,
+ A_NUMERIC_FUNCTION,
+ A_PROTECTED,
+)
from mathics.core.convert.expression import to_expression
from mathics.core.convert.op import ascii_operator_to_symbol
from mathics.core.convert.python import from_bool
@@ -29,7 +41,7 @@
from mathics.core.expression import Expression, SymbolDefault
from mathics.core.interrupt import BreakInterrupt, ContinueInterrupt, ReturnInterrupt
from mathics.core.list import ListExpression
-from mathics.core.number import PrecisionValueError, get_precision
+from mathics.core.number import PrecisionValueError, dps, get_precision, min_prec
from mathics.core.parser.util import PyMathicsDefinitions, SystemDefinitions
from mathics.core.rules import BuiltinRule, Pattern, Rule
from mathics.core.symbols import (
@@ -50,110 +62,11 @@
SymbolRule,
SymbolSequence,
)
-from mathics.eval.numbers import cancel
+from mathics.eval.arithmetic import eval_mpmath_function
+from mathics.eval.numbers.numbers import cancel
from mathics.eval.numerify import numerify
from mathics.eval.scoping import dynamic_scoping
-# Signals to Mathics doc processing not to include this module in its documentation.
-no_doc = True
-
-
-class DefaultOptionChecker:
- """
- Callable class that is used in checking that options are valid.
-
- If initialized with ``strict`` set to True,
- then a instantance calls will return True only if all
- options listed in ``options_to_check`` are in the constructor's
- list of options. In either case, when an option is not in the
- constructor list, give an "optx" message.
- """
-
- def __init__(self, builtin, options, strict: bool):
- self.name = builtin.get_name()
- self.strict = strict
- self.options = options
-
- def __call__(self, options_to_check, evaluation):
- option_name = self.name
- options = self.options
- strict = self.strict
-
- for key, value in options_to_check.items():
- short_key = strip_context(key)
- if not has_option(options, short_key, evaluation):
- evaluation.message(
- option_name,
- "optx",
- Expression(SymbolRule, String(short_key), value),
- strip_context(option_name),
- )
- if strict:
- return False
- return True
-
-
-class UnavailableFunction:
- """
- Callable class used when the evaluation function is not available.
- """
-
- def __init__(self, builtin):
- self.name = builtin.get_name()
-
- def __call__(self, **kwargs):
- kwargs["evaluation"].message(
- "General",
- "pyimport", # see messages.py for error message definition
- strip_context(self.name),
- )
-
-
-def check_requires_list(requires: list) -> bool:
- """
- Check if module names in ``requires`` can be imported and return
- True if they can, or False if not.
-
- """
- for package in requires:
- lib_is_installed = True
- try:
- lib_is_installed = importlib.util.find_spec(package) is not None
- except ImportError:
- # print("XXX requires import error", requires)
- lib_is_installed = False
- if not lib_is_installed:
- # print("XXX requires not found error", requires)
- return False
- return True
-
-
-def get_option(options, name, evaluation, pop=False, evaluate=True):
- # we do not care whether an option X is given as System`X,
- # Global`X, or with any prefix from $ContextPath for that
- # matter. Also, the quoted string form "X" is ok. all these
- # variants name the same option. this matches Wolfram Language
- # behaviour.
- name = strip_context(name)
- contexts = (s + "%s" for s in evaluation.definitions.get_context_path())
-
- for variant in chain(contexts, ('"%s"',)):
- resolved_name = variant % name
- if pop:
- value = options.pop(resolved_name, None)
- else:
- value = options.get(resolved_name)
- if value is not None:
- return value.evaluate(evaluation) if evaluate else value
- return None
-
-
-def has_option(options, name, evaluation):
- return get_option(options, name, evaluation, evaluate=False) is not None
-
-
-mathics_to_python = {} # here we have: name -> string
-
class Builtin:
"""
@@ -565,6 +478,260 @@ def __hash__(self):
return hash((self.get_name(), id(self)))
+# This has to come before SympyFunction
+class SympyObject(Builtin):
+ sympy_name: Optional[str] = None
+
+ mathics_to_sympy = {}
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if self.sympy_name is None:
+ self.sympy_name = strip_context(self.get_name()).lower()
+ self.mathics_to_sympy[self.__class__.__name__] = self.sympy_name
+
+ def is_constant(self) -> bool:
+ return False
+
+ def get_sympy_names(self) -> List[str]:
+ if self.sympy_name:
+ return [self.sympy_name]
+ return []
+
+
+# This has to come before MPMathFunction
+class SympyFunction(SympyObject):
+ def eval(self, z, evaluation):
+ # Note: we omit a docstring here, so as not to confuse
+ # function signature collector ``contribute``.
+
+ # Generic eval method that uses the class sympy_name.
+ # to call the corresponding sympy function. Arguments are
+ # converted to python and the result is converted from sympy
+ #
+ # "%(name)s[z__]"
+ sympy_args = to_numeric_sympy_args(z, evaluation)
+ sympy_fn = getattr(sympy, self.sympy_name)
+ try:
+ return from_sympy(run_sympy(sympy_fn, *sympy_args))
+ except Exception:
+ return
+
+ def get_constant(self, precision, evaluation, have_mpmath=False):
+ try:
+ d = get_precision(precision, evaluation)
+ except PrecisionValueError:
+ return
+
+ sympy_fn = self.to_sympy()
+ if d is None:
+ result = self.get_mpmath_function() if have_mpmath else sympy_fn()
+ return MachineReal(result)
+ else:
+ return PrecisionReal(sympy_fn.n(d))
+
+ def get_sympy_function(self, elements=None):
+ if self.sympy_name:
+ return getattr(sympy, self.sympy_name)
+ return None
+
+ def prepare_sympy(self, elements: Iterable) -> Iterable:
+ return elements
+
+ def to_sympy(self, expr, **kwargs):
+ try:
+ if self.sympy_name:
+ elements = self.prepare_sympy(expr.elements)
+ sympy_args = [element.to_sympy(**kwargs) for element in elements]
+ if None in sympy_args:
+ return None
+ sympy_function = self.get_sympy_function(elements)
+ return sympy_function(*sympy_args)
+ except TypeError:
+ pass
+
+ def from_sympy(self, sympy_name, elements):
+ return to_expression(self.get_name(), *elements)
+
+ def prepare_mathics(self, sympy_expr):
+ return sympy_expr
+
+
+class MPMathFunction(SympyFunction):
+ # These below attributes are the default attributes:
+ #
+ # * functions take lists as an argument
+ # * functions take numeric values only
+ # * functions can't be changed
+ #
+ # However hey are not correct for some derived classes, like
+ # InverseErf or InverseErfc.
+ # So those classes should expclicitly set/override this.
+ attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
+
+ mpmath_name = None
+ nargs = {1}
+
+ @lru_cache(maxsize=1024)
+ def get_mpmath_function(self, args):
+ if self.mpmath_name is None or len(args) not in self.nargs:
+ return None
+ return getattr(mpmath, self.mpmath_name)
+
+ def eval(self, z, evaluation: Evaluation):
+ "%(name)s[z__]"
+
+ args = numerify(z, evaluation).get_sequence()
+
+ # if no arguments are inexact attempt to use sympy
+ if all(not x.is_inexact() for x in args):
+ result = to_expression(self.get_name(), *args).to_sympy()
+ result = self.prepare_mathics(result)
+ result = from_sympy(result)
+ # evaluate elements to convert e.g. Plus[2, I] -> Complex[2, 1]
+ return result.evaluate_elements(evaluation)
+
+ if not all(isinstance(arg, Number) for arg in args):
+ return
+
+ mpmath_function = self.get_mpmath_function(tuple(args))
+ if mpmath_function is None:
+ return
+
+ if any(arg.is_machine_precision() for arg in args):
+ prec = None
+ else:
+ prec = min_prec(*args)
+ d = dps(prec)
+ args = [arg.round(d) for arg in args]
+
+ return eval_mpmath_function(mpmath_function, *args, prec=prec)
+
+
+class MPMathMultiFunction(MPMathFunction):
+ sympy_names = None
+ mpmath_names = None
+
+ def get_sympy_names(self):
+ if self.sympy_names is None:
+ return [self.sympy_name]
+ return self.sympy_names.values()
+
+ def get_function(self, module, names, fallback_name, elements):
+ try:
+ name = fallback_name
+ if names is not None:
+ name = names[len(elements)]
+ if name is None:
+ return None
+ return getattr(module, name)
+ except KeyError:
+ return None
+
+ def get_sympy_function(self, elements):
+ return self.get_function(sympy, self.sympy_names, self.sympy_name, elements)
+
+ def get_mpmath_function(self, elements):
+ return self.get_function(mpmath, self.mpmath_names, self.mpmath_name, elements)
+
+
+class DefaultOptionChecker:
+ """
+ Callable class that is used in checking that options are valid.
+
+ If initialized with ``strict`` set to True,
+ then a instantance calls will return True only if all
+ options listed in ``options_to_check`` are in the constructor's
+ list of options. In either case, when an option is not in the
+ constructor list, give an "optx" message.
+ """
+
+ def __init__(self, builtin, options, strict: bool):
+ self.name = builtin.get_name()
+ self.strict = strict
+ self.options = options
+
+ def __call__(self, options_to_check, evaluation):
+ option_name = self.name
+ options = self.options
+ strict = self.strict
+
+ for key, value in options_to_check.items():
+ short_key = strip_context(key)
+ if not has_option(options, short_key, evaluation):
+ evaluation.message(
+ option_name,
+ "optx",
+ Expression(SymbolRule, String(short_key), value),
+ strip_context(option_name),
+ )
+ if strict:
+ return False
+ return True
+
+
+class UnavailableFunction:
+ """
+ Callable class used when the evaluation function is not available.
+ """
+
+ def __init__(self, builtin):
+ self.name = builtin.get_name()
+
+ def __call__(self, **kwargs):
+ kwargs["evaluation"].message(
+ "General",
+ "pyimport", # see messages.py for error message definition
+ strip_context(self.name),
+ )
+
+
+def check_requires_list(requires: list) -> bool:
+ """
+ Check if module names in ``requires`` can be imported and return
+ True if they can, or False if not.
+
+ """
+ for package in requires:
+ lib_is_installed = True
+ try:
+ lib_is_installed = importlib.util.find_spec(package) is not None
+ except ImportError:
+ # print("XXX requires import error", requires)
+ lib_is_installed = False
+ if not lib_is_installed:
+ # print("XXX requires not found error", requires)
+ return False
+ return True
+
+
+def get_option(options, name, evaluation, pop=False, evaluate=True):
+ # we do not care whether an option X is given as System`X,
+ # Global`X, or with any prefix from $ContextPath for that
+ # matter. Also, the quoted string form "X" is ok. all these
+ # variants name the same option. this matches Wolfram Language
+ # behaviour.
+ name = strip_context(name)
+ contexts = (s + "%s" for s in evaluation.definitions.get_context_path())
+
+ for variant in chain(contexts, ('"%s"',)):
+ resolved_name = variant % name
+ if pop:
+ value = options.pop(resolved_name, None)
+ else:
+ value = options.get(resolved_name)
+ if value is not None:
+ return value.evaluate(evaluation) if evaluate else value
+ return None
+
+
+def has_option(options, name, evaluation):
+ return get_option(options, name, evaluation, evaluate=False) is not None
+
+
+mathics_to_python = {} # here we have: name -> string
+
+
class AtomBuiltin(Builtin):
"""
This class is used to define Atoms other than those ones in core, but also
@@ -627,7 +794,7 @@ def eval_range(self, expr, i, imax, evaluation):
def eval_max(self, expr, imax, evaluation):
"%(name)s[expr_, {imax_}]"
- # Even though `imax` should be an integeral value, its type does not
+ # Even though `imax` should be an integral value, its type does not
# have to be an Integer.
result = []
@@ -809,26 +976,6 @@ def get_functions(self, prefix="eval", is_pymodule=False) -> List[Callable]:
return functions
-class SympyObject(Builtin):
- sympy_name: Optional[str] = None
-
- mathics_to_sympy = {}
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- if self.sympy_name is None:
- self.sympy_name = strip_context(self.get_name()).lower()
- self.mathics_to_sympy[self.__class__.__name__] = self.sympy_name
-
- def is_constant(self) -> bool:
- return False
-
- def get_sympy_names(self) -> List[str]:
- if self.sympy_name:
- return [self.sympy_name]
- return []
-
-
class UnaryOperator(Operator):
def __init__(self, format_function, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -922,63 +1069,6 @@ def run_sympy(sympy_fn: Callable, *sympy_args) -> Any:
return sympy_fn(*sympy_args)
-class SympyFunction(SympyObject):
- def eval(self, z, evaluation):
- # Note: we omit a docstring here, so as not to confuse
- # function signature collector ``contribute``.
-
- # Generic eval method that uses the class sympy_name.
- # to call the corresponding sympy function. Arguments are
- # converted to python and the result is converted from sympy
- #
- # "%(name)s[z__]"
- sympy_args = to_numeric_sympy_args(z, evaluation)
- sympy_fn = getattr(sympy, self.sympy_name)
- try:
- return from_sympy(run_sympy(sympy_fn, *sympy_args))
- except Exception:
- return
-
- def get_constant(self, precision, evaluation, have_mpmath=False):
- try:
- d = get_precision(precision, evaluation)
- except PrecisionValueError:
- return
-
- sympy_fn = self.to_sympy()
- if d is None:
- result = self.get_mpmath_function() if have_mpmath else sympy_fn()
- return MachineReal(result)
- else:
- return PrecisionReal(sympy_fn.n(d))
-
- def get_sympy_function(self, elements=None):
- if self.sympy_name:
- return getattr(sympy, self.sympy_name)
- return None
-
- def prepare_sympy(self, elements: Iterable) -> Iterable:
- return elements
-
- def to_sympy(self, expr, **kwargs):
- try:
- if self.sympy_name:
- elements = self.prepare_sympy(expr.elements)
- sympy_args = [element.to_sympy(**kwargs) for element in elements]
- if None in sympy_args:
- return None
- sympy_function = self.get_sympy_function(elements)
- return sympy_function(*sympy_args)
- except TypeError:
- pass
-
- def from_sympy(self, sympy_name, elements):
- return to_expression(self.get_name(), *elements)
-
- def prepare_mathics(self, sympy_expr):
- return sympy_expr
-
-
class PatternError(Exception):
def __init__(self, name, tag, *args):
super().__init__()
@@ -1032,8 +1122,8 @@ def get_lookup_name(self) -> str:
return self.get_name()
def get_match_candidates(
- self, elements, expression, attributes, evaluation, vars={}
- ):
+ self, elements: Tuple[BaseElement], expression, attributes, evaluation, vars={}
+ ) -> Tuple[BaseElement]:
return elements
def get_match_count(self, vars={}):
diff --git a/mathics/core/convert/__init__.py b/mathics/core/convert/__init__.py
index 13cc331bb..dba17738a 100644
--- a/mathics/core/convert/__init__.py
+++ b/mathics/core/convert/__init__.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
Routines here convert between various internal representations such as
-between ``Expressions``, LLVM functions, SymPy Arguments, MPMath datattypes and
-so on. However this does not include the inital conversion a parsed string into
+between ``Expressions``, LLVM functions, SymPy Arguments, MPMath datatypes and
+so on. However this does not include the initial conversion a parsed string into
one of the internal representations. That is done in the parser.
"""
diff --git a/mathics/core/convert/expression.py b/mathics/core/convert/expression.py
index d4360a3f2..54f806af1 100644
--- a/mathics/core/convert/expression.py
+++ b/mathics/core/convert/expression.py
@@ -21,7 +21,7 @@ def make_expression(head, *elements, **kwargs) -> Expression:
def to_expression(
head: Union[str, Symbol],
*elements: Any,
- elements_conversion_fn: Callable = from_python
+ elements_conversion_fn: Callable = from_python,
) -> Expression:
"""
This is an expression constructor that can be used when the Head and elements are not Mathics
@@ -45,14 +45,14 @@ def to_expression(
head,
*elements_tuple,
elements_properties=elements_properties,
- literal_values=literal_values
+ literal_values=literal_values,
)
def to_expression_with_specialization(
head: Union[str, Symbol],
*elements: Any,
- elements_conversion_fn: Callable = from_python
+ elements_conversion_fn: Callable = from_python,
) -> Union[ListExpression, Expression]:
"""
This expression constructor will figure out what the right kind of
diff --git a/mathics/core/convert/mpmath.py b/mathics/core/convert/mpmath.py
index 485fd2740..cf4895667 100644
--- a/mathics/core/convert/mpmath.py
+++ b/mathics/core/convert/mpmath.py
@@ -18,7 +18,7 @@
from mathics.core.systemsymbols import SymbolIndeterminate
-@lru_cache(maxsize=1024)
+@lru_cache(maxsize=1024, typed=True)
def from_mpmath(
value: Union[mpmath.mpf, mpmath.mpc],
precision: Optional[int] = None,
@@ -42,8 +42,15 @@ def from_mpmath(
# HACK: use str here to prevent loss of precision
return PrecisionReal(sympy.Float(str(value), precision=precision - 1))
elif isinstance(value, mpmath.mpc):
- if value.imag == 0.0:
- return from_mpmath(value.real, precision=precision)
+ # Comment mmatera:
+ # In Python, and mpmath, `0.j` and `0.` are equivalent, in the sense
+ # that are considered equal numbers, and have the same associated
+ # hash.
+ # In WMA, this is not the case. To produce the
+ # Python's behavior, uncomment the following lines:
+ #
+ # if value.imag == 0.0:
+ # return from_mpmath(value.real, precision=precision)
val_re, val_im = value.real, value.imag
if mpmath.isinf(val_re):
if mpmath.isinf(val_im):
diff --git a/mathics/core/convert/python.py b/mathics/core/convert/python.py
index 5fc1061f5..35863424f 100644
--- a/mathics/core/convert/python.py
+++ b/mathics/core/convert/python.py
@@ -31,7 +31,7 @@ def from_bool(arg: bool) -> BooleanType:
# Expression class which tried to handle anything given it using
# conversions.
# Also, through vague or lazy coding this cause a lot of
-# unecessary conversions.
+# unnecessary conversions.
# We may be out of those days, but we should still
# be mindful that this routine can be the source
@@ -43,7 +43,7 @@ def from_python(arg: Any) -> BaseElement:
"""Converts a Python expression into a Mathics expression.
TODO: I think there are number of subtleties to be explained here.
- In particular, the expression might beeen the result of evaluation
+ In particular, the expression might been the result of evaluation
a sympy expression which contains sympy symbols.
If the end result is to go back into Mathics for further
@@ -62,7 +62,7 @@ def from_python(arg: Any) -> BaseElement:
number_type = get_type(arg)
# We should investigate whether this could be sped up
- # using a disctionary lookup on type.
+ # using a dictionary lookup on type.
if arg is None:
return SymbolNull
if isinstance(arg, bool):
diff --git a/mathics/core/convert/regex.py b/mathics/core/convert/regex.py
index 7c0a3262f..45e302d11 100644
--- a/mathics/core/convert/regex.py
+++ b/mathics/core/convert/regex.py
@@ -98,7 +98,7 @@ def to_regex_internal(
def recurse(x: Expression, quantifiers=q) -> Tuple[Optional[str], str]:
"""
- Shortend way to call to_regexp_internal -
+ Shortened way to call to_regexp_internal -
only the expr and quantifiers change here.
"""
return to_regex_internal(
diff --git a/mathics/core/convert/sympy.py b/mathics/core/convert/sympy.py
index 843d679f3..75b8b2191 100644
--- a/mathics/core/convert/sympy.py
+++ b/mathics/core/convert/sympy.py
@@ -4,7 +4,6 @@
Converts expressions from SymPy to Mathics expressions.
Conversion to SymPy is handled directly in BaseElement descendants.
"""
-
from typing import Optional, Type, Union
import sympy
@@ -13,9 +12,6 @@
# Import the singleton class
from sympy.core.numbers import S
-BasicSympy = sympy.Expr
-
-
from mathics.core.atoms import (
MATHICS3_COMPLEX_I,
Complex,
@@ -72,6 +68,9 @@
SymbolUnequal,
)
+BasicSympy = sympy.Expr
+
+
SymbolPrime = Symbol("Prime")
SymbolRoot = Symbol("Root")
SymbolRootSum = Symbol("RootSum")
@@ -108,14 +107,13 @@
def is_Cn_expr(name) -> bool:
+ """Check if name is of the form {prefix}Cnnn"""
if name.startswith(sympy_symbol_prefix) or name.startswith(sympy_slot_prefix):
return False
if not name.startswith("C"):
return False
- n = name[1:]
- if n and n.isdigit():
- return True
- return False
+ number = name[1:]
+ return number and number.isdigit()
def to_sympy_matrix(data, **kwargs) -> Optional[sympy.MutableDenseMatrix]:
@@ -131,6 +129,8 @@ def to_sympy_matrix(data, **kwargs) -> Optional[sympy.MutableDenseMatrix]:
class SympyExpression(BasicSympy):
+ """A Sympy expression with an associated Mathics expression"""
+
is_Function = True
nargs = None
@@ -140,7 +140,7 @@ def __new__(cls, *exprs):
if all(isinstance(expr, BasicSympy) for expr in exprs):
# called with SymPy arguments
- obj = BasicSympy.__new__(cls, *exprs)
+ obj = super().__new__(cls, *exprs)
elif len(exprs) == 1 and isinstance(exprs[0], Expression):
# called with Mathics argument
expr = exprs[0]
@@ -148,22 +148,17 @@ def __new__(cls, *exprs):
sympy_elements = [element.to_sympy() for element in expr.elements]
if sympy_head is None or None in sympy_elements:
return None
- obj = BasicSympy.__new__(cls, sympy_head, *sympy_elements)
+ obj = super().__new__(cls, sympy_head, *sympy_elements)
obj.expr = expr
else:
raise TypeError
return obj
- """def new(self, *args):
- from mathics.core import expression
-
- expr = expression.Expression(from_sympy(args[0]),
- *(from_sympy(arg) for arg in args[1:]))
- return SympyExpression(expr)"""
-
@property
def func(self):
class SympyExpressionFunc:
+ """A class to mimic the behavior of sympy.Function"""
+
def __new__(cls, *args):
return SympyExpression(self.expr)
# return SympyExpression(expression.Expression(self.expr.head,
@@ -172,10 +167,12 @@ def __new__(cls, *args):
return SympyExpressionFunc
def has_any_symbols(self, *syms) -> bool:
+ """Check if any of the symbols in syms appears in the expression."""
result = any(arg.has_any_symbols(*syms) for arg in self.args)
return result
def _eval_subs(self, old, new):
+ """Replace occurencies of old by new in self."""
if self == old:
return new
old, new = from_sympy(old), from_sympy(new)
@@ -185,18 +182,16 @@ def _eval_subs(self, old, new):
return SympyExpression(new_expr)
return self
- def _eval_rewrite(self, pattern, rule, **hints):
+ def _eval_rewrite(self, rule, args, **hints):
return self
@property
def is_commutative(self) -> bool:
- if all(getattr(t, "is_commutative", False) for t in self.args):
- return True
- else:
- return False
+ """Check if the arguments are commutative."""
+ return all(getattr(t, "is_commutative", False) for t in self.args)
def __str__(self) -> str:
- return "%s[%s]" % (super(SympyExpression, self).__str__(), self.expr)
+ return f"{super().__str__()}[{self.expr}])"
class SympyPrime(sympy.Function):
@@ -212,6 +207,7 @@ def eval(cls, n):
except Exception:
# n is too big, SymPy doesn't know the n-th prime
pass
+ return None
def expression_to_sympy(expr: Expression, **kwargs):
@@ -292,8 +288,8 @@ def from_sympy_matrix(
# This is a vector (only one column)
# Transpose and select first row to get result equivalent to Mathematica
return to_mathics_list(*expr.T.tolist()[0], elements_conversion_fn=from_sympy)
- else:
- return to_mathics_list(*expr.tolist(), elements_conversion_fn=from_sympy)
+
+ return to_mathics_list(*expr.tolist(), elements_conversion_fn=from_sympy)
"""
@@ -357,7 +353,7 @@ def old_from_sympy(expr) -> BaseElement:
if expr.is_Symbol:
name = str(expr)
if isinstance(expr, sympy.Dummy):
- name = name + ("__Dummy_%d" % expr.dummy_index)
+ name = name + (f"__Dummy_{expr.dummy_index}")
# Probably, this should be the value attribute
return Symbol(name, sympy_dummy=expr)
if is_Cn_expr(name):
@@ -374,17 +370,17 @@ def old_from_sympy(expr) -> BaseElement:
if builtin is not None:
name = builtin.get_name()
return Symbol(name)
- elif isinstance(expr, sympy.core.numbers.Infinity):
+ if isinstance(expr, sympy.core.numbers.Infinity):
return MATHICS3_INFINITY
- elif isinstance(expr, sympy.core.numbers.ComplexInfinity):
+ if isinstance(expr, sympy.core.numbers.ComplexInfinity):
return MATHICS3_COMPLEX_INFINITY
- elif isinstance(expr, sympy.core.numbers.NegativeInfinity):
+ if isinstance(expr, sympy.core.numbers.NegativeInfinity):
return MATHICS3_NEG_INFINITY
- elif isinstance(expr, sympy.core.numbers.ImaginaryUnit):
+ if isinstance(expr, sympy.core.numbers.ImaginaryUnit):
return MATHICS3_COMPLEX_I
- elif isinstance(expr, sympy.Integer):
+ if isinstance(expr, sympy.Integer):
return Integer(int(expr))
- elif isinstance(expr, sympy.Rational):
+ if isinstance(expr, sympy.Rational):
numerator, denominator = map(int, expr.as_numer_denom())
if denominator == 0:
if numerator > 0:
@@ -395,17 +391,17 @@ def old_from_sympy(expr) -> BaseElement:
assert numerator == 0
return SymbolIndeterminate
return Rational(numerator, denominator)
- elif isinstance(expr, sympy.Float):
+ if isinstance(expr, sympy.Float):
if expr._prec == FP_MANTISA_BINARY_DIGITS:
return MachineReal(float(expr))
return Real(expr)
- elif isinstance(expr, sympy.core.numbers.NaN):
+ if isinstance(expr, sympy.core.numbers.NaN):
return SymbolIndeterminate
- elif isinstance(expr, sympy.core.function.FunctionClass):
+ if isinstance(expr, sympy.core.function.FunctionClass):
return Symbol(str(expr))
- elif expr is sympy.true:
+ if expr is sympy.true:
return SymbolTrue
- elif expr is sympy.false:
+ if expr is sympy.false:
return SymbolFalse
if expr.is_number and all([x.is_Number for x in expr.as_real_imag()]):
@@ -419,19 +415,19 @@ def old_from_sympy(expr) -> BaseElement:
return to_expression(
SymbolPlus, *sorted([from_sympy(arg) for arg in expr.args])
)
- elif expr.is_Mul:
+ if expr.is_Mul:
return to_expression(
SymbolTimes, *sorted([from_sympy(arg) for arg in expr.args])
)
- elif expr.is_Pow:
+ if expr.is_Pow:
return to_expression(SymbolPower, *[from_sympy(arg) for arg in expr.args])
- elif expr.is_Equality:
+ if expr.is_Equality:
return to_expression(SymbolEqual, *[from_sympy(arg) for arg in expr.args])
- elif isinstance(expr, SympyExpression):
+ if isinstance(expr, SympyExpression):
return expr.expr
- elif isinstance(expr, sympy.Piecewise):
+ if isinstance(expr, sympy.Piecewise):
args = expr.args
return Expression(
SymbolPiecewise,
@@ -443,11 +439,11 @@ def old_from_sympy(expr) -> BaseElement:
),
)
- elif isinstance(expr, SympyPrime):
+ if isinstance(expr, SympyPrime):
return Expression(SymbolPrime, from_sympy(expr.args[0]))
- elif isinstance(expr, sympy.RootSum):
+ if isinstance(expr, sympy.RootSum):
return Expression(SymbolRootSum, from_sympy(expr.poly), from_sympy(expr.fun))
- elif isinstance(expr, sympy.PurePoly):
+ if isinstance(expr, sympy.PurePoly):
coeffs = expr.coeffs()
monoms = expr.monoms()
result = []
@@ -467,26 +463,26 @@ def old_from_sympy(expr) -> BaseElement:
else:
result.append(Integer1)
return Expression(SymbolFunction, Expression(SymbolPlus, *result))
- elif isinstance(expr, sympy.CRootOf):
+ if isinstance(expr, sympy.CRootOf):
try:
- e, i = expr.args
+ e_root, indx = expr.args
except ValueError:
return SymbolNull
try:
- e = sympy.PurePoly(e)
+ e_root = sympy.PurePoly(e_root)
except Exception:
pass
- return Expression(SymbolRoot, from_sympy(e), Integer(i + 1))
- elif isinstance(expr, sympy.Lambda):
- vars = [
- sympy.Symbol("%s%d" % (sympy_slot_prefix, index + 1))
+ return Expression(SymbolRoot, from_sympy(e_root), Integer(indx + 1))
+ if isinstance(expr, sympy.Lambda):
+ variables = [
+ sympy.Symbol(f"{sympy_slot_prefix}{index + 1}")
for index in range(len(expr.variables))
]
- return Expression(SymbolFunction, from_sympy(expr(*vars)))
+ return Expression(SymbolFunction, from_sympy(expr(*variables)))
- elif expr.is_Function or isinstance(
+ if expr.is_Function or isinstance(
expr, (sympy.Integral, sympy.Derivative, sympy.Sum, sympy.Product)
):
if isinstance(expr, sympy.Integral):
@@ -514,7 +510,7 @@ def old_from_sympy(expr) -> BaseElement:
if is_Cn_expr(name):
return Expression(
Expression(Symbol("C"), Integer(int(name[1:]))),
- *[from_sympy(arg) for arg in expr.args]
+ *[from_sympy(arg) for arg in expr.args],
)
if name.startswith(sympy_symbol_prefix):
name = name[len(sympy_symbol_prefix) :]
@@ -524,46 +520,46 @@ def old_from_sympy(expr) -> BaseElement:
return builtin.from_sympy(name, args)
return Expression(Symbol(name), *args)
- elif isinstance(expr, sympy.Tuple):
+ if isinstance(expr, sympy.Tuple):
return to_mathics_list(*expr.args, elements_conversion_fn=from_sympy)
# elif isinstance(expr, sympy.Sum):
# return Expression('Sum', )
- elif isinstance(expr, sympy.LessThan):
+ if isinstance(expr, sympy.LessThan):
return to_expression(
SymbolLessEqual, *expr.args, elements_conversion_fn=from_sympy
)
- elif isinstance(expr, sympy.StrictLessThan):
+ if isinstance(expr, sympy.StrictLessThan):
return to_expression(SymbolLess, *expr.args, elements_conversion_fn=from_sympy)
- elif isinstance(expr, sympy.GreaterThan):
+ if isinstance(expr, sympy.GreaterThan):
return to_expression(
SymbolGreaterEqual, *expr.args, elements_conversion_fn=from_sympy
)
- elif isinstance(expr, sympy.StrictGreaterThan):
+ if isinstance(expr, sympy.StrictGreaterThan):
return to_expression(
SymbolGreater, *expr.args, elements_conversion_fn=from_sympy
)
- elif isinstance(expr, sympy.Unequality):
+ if isinstance(expr, sympy.Unequality):
return to_expression(
SymbolUnequal, *expr.args, elements_conversion_fn=from_sympy
)
- elif isinstance(expr, sympy.Equality):
+ if isinstance(expr, sympy.Equality):
return to_expression(SymbolEqual, *expr.args, elements_conversion_fn=from_sympy)
- elif isinstance(expr, sympy.O):
+ if isinstance(expr, sympy.O):
if expr.args[0].func == sympy.core.power.Pow:
[var, power] = [from_sympy(arg) for arg in expr.args[0].args]
- o = Expression(SymbolO, var)
- return Expression(SymbolPower, o, power)
+ o_expr = Expression(SymbolO, var)
+ return Expression(SymbolPower, o_expr, power)
else:
return Expression(SymbolO, from_sympy(expr.args[0]))
- else:
- raise ValueError(
- "Unknown SymPy expression: {} (instance of {})".format(
- expr, str(expr.__class__)
- )
+
+ raise ValueError(
+ "Unknown SymPy expression: {} (instance of {})".format(
+ expr, str(expr.__class__)
)
+ )
from_sympy = old_from_sympy
diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py
index bd2d8d3bf..0308f5312 100644
--- a/mathics/core/definitions.py
+++ b/mathics/core/definitions.py
@@ -2,6 +2,7 @@
import base64
import bisect
import os
+import os.path as osp
import pickle
import re
from collections import defaultdict
@@ -18,6 +19,7 @@
from mathics.core.load_builtin import definition_contribute, mathics3_builtins_modules
from mathics.core.symbols import Atom, Symbol, strip_context
from mathics.core.systemsymbols import SymbolGet
+from mathics.core.util import canonic_filename
from mathics.settings import ROOT_DIR
type_compiled_pattern = type(re.compile("a.a"))
@@ -121,6 +123,7 @@ def __init__(
"System`",
"Global`",
)
+ self.inputfile = ""
# Importing "mathics.format" populates the Symbol of the
# PrintForms and OutputForms sets.
@@ -243,6 +246,9 @@ def get_current_context(self):
def get_context_path(self):
return self.context_path
+ def get_inputfile(self) -> str:
+ return self.inputfile if hasattr(self, "inputfile") else ""
+
def set_current_context(self, context) -> None:
assert isinstance(context, str)
self.set_ownvalue("System`$Context", String(context))
@@ -259,6 +265,10 @@ def set_context_path(self, context_path) -> None:
self.context_path = context_path
self.clear_cache()
+ def set_inputfile(self, dir: str) -> None:
+ self.inputfile = osp.normpath(osp.abspath(dir))
+ self.inputfile = canonic_filename(self.inputfile)
+
def get_builtin_names(self):
return set(self.builtin)
diff --git a/mathics/core/element.py b/mathics/core/element.py
index 23b6ba8c7..4bac5dd7c 100644
--- a/mathics/core/element.py
+++ b/mathics/core/element.py
@@ -307,7 +307,7 @@ def get_name(self):
def get_option_values(self, evaluation, allow_symbols=False, stop_on_error=True):
pass
- def get_precision(self) -> Optional[float]:
+ def get_precision(self) -> Optional[int]:
"""Returns the default specification for precision in N and other
numerical functions. It is expected to be redefined in those
classes that provide inexact arithmetic like PrecisionReal.
diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py
index a45b172f3..3d132a5dc 100644
--- a/mathics/core/evaluation.py
+++ b/mathics/core/evaluation.py
@@ -3,6 +3,7 @@
import os
import sys
import time
+from abc import ABC
from queue import Queue
from threading import Thread, stack_size as set_thread_stack_size
from typing import List, Optional, Tuple, Union
@@ -183,11 +184,11 @@ def __init__(
# ``mathics.builtin.numeric.N``.
self._preferred_n_method = []
- def parse(self, query):
+ def parse(self, query, src_name: str = ""):
"Parse a single expression and print the messages."
from mathics.core.parser import MathicsSingleLineFeeder
- return self.parse_feeder(MathicsSingleLineFeeder(query))
+ return self.parse_feeder(MathicsSingleLineFeeder(query, src_name))
def parse_evaluate(self, query, timeout=None):
expr = self.parse(query)
@@ -471,7 +472,7 @@ def message(self, symbol_name: str, tag, *msgs) -> "Message":
symbol_shortname = self.definitions.shorten_name(symbol)
if settings.DEBUG_PRINT:
- print("MESSAGE: %s::%s (%s)" % (symbol_shortname, tag, msgs))
+ print(f"MESSAGE: {symbol_shortname}::{tag} ({msgs})")
text = self.definitions.get_value(symbol, "System`Messages", pattern, self)
if text is None:
@@ -481,7 +482,7 @@ def message(self, symbol_name: str, tag, *msgs) -> "Message":
)
if text is None:
- text = String("Message %s::%s not found." % (symbol_shortname, tag))
+ text = String(f"Message {symbol_shortname}::{tag} not found.")
text = self.format_output(
Expression(SymbolStringForm, text, *(from_python(arg) for arg in msgs)),
@@ -587,8 +588,9 @@ def __init__(self, symbol: Union[Symbol, str], tag: str, text: str) -> None:
use a string.
tag: a short slug string that indicates the kind of message
- In Django we need to use a string for symbol, since we need something that is JSON serializable
- and a Mathics3 Symbol is not like this.
+ In Django we need to use a string for symbol, since we need
+ something that is JSON serializable and a Mathics3 Symbol is not
+ like this.
"""
super(Message, self).__init__()
self.is_message = True # Why do we need this?
@@ -607,7 +609,7 @@ def get_data(self):
"message": True,
"symbol": self.symbol,
"tag": self.tag,
- "prefix": "%s::%s" % (self.symbol, self.tag),
+ "prefix": f"{self.symbol}::{self.tag}",
"text": self.text,
}
@@ -631,9 +633,17 @@ def get_data(self):
}
-class Output:
- def max_stored_size(self, settings) -> int:
- return settings.MAX_STORED_SIZE
+class Output(ABC):
+ """
+ Base class for Mathics output history.
+ This needs to be subclassed.
+ """
+
+ def max_stored_size(self, output_settings) -> int:
+ """
+ Return the largeet number of history items allowed.
+ """
+ return output_settings.MAX_STORED_SIZE
def out(self, out):
pass
diff --git a/mathics/core/exceptions.py b/mathics/core/exceptions.py
index 06a0e2ff3..e1c7cb179 100644
--- a/mathics/core/exceptions.py
+++ b/mathics/core/exceptions.py
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
+"""
+Various Exception objects used in Mathics3.
+"""
class BoxExpressionError(Exception):
@@ -35,4 +38,7 @@ def __init__(self, *message):
self._message = message
def message(self, evaluation):
+ """
+ Transfer this exception to evaluation's ``message`` method.
+ """
evaluation.message(*self._message)
diff --git a/mathics/core/expression.py b/mathics/core/expression.py
index 098e385a0..f407f9957 100644
--- a/mathics/core/expression.py
+++ b/mathics/core/expression.py
@@ -5,7 +5,7 @@
import time
from bisect import bisect_left
from itertools import chain
-from typing import Any, Callable, Iterable, List, Optional, Tuple, Type
+from typing import Any, Callable, Iterable, List, Optional, Tuple, Type, Union
import sympy
@@ -281,7 +281,6 @@ def _build_elements_properties(self):
self.elements_properties.elements_fully_evaluated = False
if isinstance(element, Expression):
-
# "self" can't be flat.
self.elements_properties.is_flat = False
@@ -772,7 +771,6 @@ def get_rules_list(self) -> Optional[list]:
# FIXME: return type should be a specific kind of Tuple, not a tuple.
def get_sort_key(self, pattern_sort=False) -> tuple:
-
if pattern_sort:
"""
Pattern sort key structure:
@@ -1358,7 +1356,9 @@ def rules():
# Expr8: to_expression("Plus", n1,..., n1) (nontrivial evaluation to a long expression, with just undefined symbols)
#
- def round_to_float(self, evaluation=None, permit_complex=False) -> Optional[float]:
+ def round_to_float(
+ self, evaluation=None, permit_complex=False
+ ) -> Optional[Union[float, complex]]:
"""
Round to a Python float. Return None if rounding is not possible.
This can happen if self or evaluation is NaN.
@@ -1454,14 +1454,13 @@ def to_python(self, *args, **kwargs):
numbers -> Python number
If kwarg n_evaluation is given, apply N first to the expression.
"""
- from mathics.builtin.base import mathics_to_python
+ from mathics.core.builtin import mathics_to_python
n_evaluation = kwargs.get("n_evaluation", None)
assert n_evaluation is None
head = self._head
if head is SymbolFunction:
-
from mathics.core.convert.function import expression_to_callable_and_args
vars, expr_fn = self.elements
@@ -1618,7 +1617,6 @@ def replace_vars(
in ("System`Module", "System`Block", "System`With")
and len(self._elements) > 0
): # nopep8
-
scoping_vars = set(
name for name, new_def in get_scoping_vars(self._elements[0])
)
diff --git a/mathics/core/load_builtin.py b/mathics/core/load_builtin.py
old mode 100755
new mode 100644
index a2a3453e2..a77b0ccab
--- a/mathics/core/load_builtin.py
+++ b/mathics/core/load_builtin.py
@@ -8,13 +8,15 @@
import importlib
import inspect
+import logging
import os
import os.path as osp
import pkgutil
from glob import glob
from types import ModuleType
-from typing import List, Optional
+from typing import Dict, List, Optional, Set
+from mathics.core.convert.sympy import mathics_to_sympy, sympy_to_mathics
from mathics.core.pattern import pattern_objects
from mathics.core.symbols import Symbol
from mathics.eval.makeboxes import builtins_precedence
@@ -27,20 +29,63 @@
mathics3_builtins_modules: List[ModuleType] = []
_builtins = {}
-builtins_by_module = {}
-display_operators_set = set()
+# builtins_by_module gives a way of mapping a Python module name
+# e.g. 'mathics.builtin.arithmetic' to the list of Builtin class instances
+# that appear inside that module, e.g. for key 'mathics.builtin.arithmetic' we
+# have:
+# [, =", "===", "<<", etc.
+display_operators_set: Set[str] = set()
+
+
+def add_builtins_from_builtin_module(module: ModuleType, builtins_list: list):
+ """
+ Process a modules which contains Builtin classes so that the
+ class is imported in the Python sense but also that we
+ have information added to module variable ``builtins_by_module``.
+
+ """
+ from mathics.core.builtin import Builtin
+
+ builtins_by_module[module.__name__] = []
+ module_vars = dir(module)
+
+ for name in module_vars:
+ builtin_class = name_is_builtin_symbol(module, name)
+ if builtin_class is not None:
+ instance = builtin_class(expression=False)
+
+ if isinstance(instance, Builtin):
+ # This set the default context for symbols in mathics.builtins
+ if not type(instance).context:
+ type(instance).context = "System`"
+ builtins_list.append((instance.get_name(), instance))
+ builtins_by_module[module.__name__].append(instance)
+ update_display_operators_set(instance)
+
+
+def add_builtins_from_builtin_modules(modules: List[ModuleType]):
+ builtins_list = []
+ for module in modules:
+ add_builtins_from_builtin_module(module, builtins_list)
+ add_builtins(builtins_list)
+ return builtins_by_module
+
+
+# The fact that we are importing inside here, suggests add_builtins
# should get moved elsewhere.
def add_builtins(new_builtins):
- from mathics.builtin.base import (
+ from mathics.core.builtin import (
Operator,
PatternObject,
SympyObject,
mathics_to_python,
)
- from mathics.core.convert.sympy import mathics_to_sympy, sympy_to_mathics
for _, builtin in new_builtins:
name = builtin.get_name()
@@ -54,37 +99,13 @@ def add_builtins(new_builtins):
# print("XXX1", sympy_name)
sympy_to_mathics[sympy_name] = builtin
if isinstance(builtin, Operator):
+ assert builtin.precedence is not None
builtins_precedence[Symbol(name)] = builtin.precedence
if isinstance(builtin, PatternObject):
pattern_objects[name] = builtin.__class__
_builtins.update(dict(new_builtins))
-def add_builtins_from_builtin_modules(modules: List[ModuleType]):
- # This can be put at the top after mathics.builtin.__init__
- # cleanup is done.
- from mathics.builtin.base import Builtin
-
- builtins_list = []
- for module in modules:
- builtins_by_module[module.__name__] = []
- module_vars = dir(module)
-
- for name in module_vars:
- builtin_class = name_is_builtin_symbol(module, name)
- if builtin_class is not None:
- instance = builtin_class(expression=False)
-
- if isinstance(instance, Builtin):
- # This set the default context for symbols in mathics.builtins
- if not type(instance).context:
- type(instance).context = "System`"
- builtins_list.append((instance.get_name(), instance))
- builtins_by_module[module.__name__].append(instance)
- add_builtins(builtins_list)
- return builtins_by_module
-
-
def builtins_dict(builtins_by_module):
return {
builtin.get_name(): builtin
@@ -124,6 +145,12 @@ def import_and_load_builtins():
"""
Imports Builtin modules in mathics.builtin and add rules, and definitions from that.
"""
+ # TODO: Check if this is the expected behavior, or it the structures
+ # must be cleaned.
+ if len(mathics3_builtins_modules) > 0:
+ logging.warning("``import_and_load_builtins`` should be called just once...")
+ return
+
builtin_path = osp.join(
osp.dirname(
__file__,
@@ -143,13 +170,32 @@ def import_and_load_builtins():
# server, we disallow local file access.
disable_file_module_names = set() if ENABLE_FILES_MODULE else {"files_io"}
- subdirectories = next(os.walk(builtin_path))[1]
+ subdirectory_list = next(os.walk(builtin_path))[1]
+ subdirectories = set(subdirectory_list) - set("__pycache__")
import_builtin_subdirectories(
subdirectories, disable_file_module_names, mathics3_builtins_modules
)
add_builtins_from_builtin_modules(mathics3_builtins_modules)
- initialize_display_operators_set()
+
+
+def import_builtin_module(import_name: str, modules: List[ModuleType]):
+ """
+ Imports ``the list of Mathics3 Built-in modules so that inside
+ Mathics3 Builtin Functions, like Plus[], List[] are defined.
+
+ List ``module_names`` is updated.
+ """
+ try:
+ module = importlib.import_module(import_name)
+ except Exception as e:
+ print(e)
+ print(f" Not able to load {import_name}. Check your installation.")
+ print(f" mathics.builtin loads from {__file__[:-11]}")
+ return None
+
+ if module:
+ modules.append(module)
# TODO: When we drop Python 3.7,
@@ -162,22 +208,12 @@ def import_builtins(
"""
Imports the list of Mathics3 Built-in modules so that inside
Mathics3 Builtin Functions, like Plus[], List[] are defined.
- """
-
- def import_module(module_name: str, import_name: str):
- try:
- module = importlib.import_module(import_name)
- except Exception as e:
- print(e)
- print(f" Not able to load {module_name}. Check your installation.")
- print(f" mathics.builtin loads from {__file__[:-11]}")
- return None
- if module:
- modules.append(module)
+ List ``module_names`` is updated.
+ """
if submodule_name:
- import_module(submodule_name, f"mathics.builtin.{submodule_name}")
+ import_builtin_module(f"mathics.builtin.{submodule_name}", modules)
for module_name in module_names:
import_name = (
@@ -185,11 +221,11 @@ def import_module(module_name: str, import_name: str):
if submodule_name
else f"mathics.builtin.{module_name}"
)
- import_module(module_name, import_name)
+ import_builtin_module(import_name, modules)
def import_builtin_subdirectories(
- subdirectories: List[str], disable_file_module_names: set, modules
+ subdirectories: Set[str], disable_file_module_names: set, modules
):
"""
Runs import_builtisn on the each subdirectory in ``subdirectories`` that inside
@@ -209,15 +245,6 @@ def import_builtin_subdirectories(
import_builtins(submodule_names, modules, subdir)
-def initialize_display_operators_set():
- for _, builtins in builtins_by_module.items():
- for builtin in builtins:
- # name = builtin.get_name()
- operator = builtin.get_operator_display()
- if operator is not None:
- display_operators_set.add(operator)
-
-
def name_is_builtin_symbol(module: ModuleType, name: str) -> Optional[type]:
"""
Checks if ``name`` should be added to definitions, and return
@@ -256,8 +283,8 @@ def name_is_builtin_symbol(module: ModuleType, name: str) -> Optional[type]:
) is not module and not module.__name__.startswith("pymathics."):
return None
- # Skip objects in module mathics.builtin.base.
- if module_object.__module__ == "mathics.builtin.base":
+ # Skip objects in module mathics.core.builtin.
+ if module_object.__module__ == "mathics.core.builtin":
return None
# Skip those builtins that are not submodules of mathics.builtin.
@@ -267,7 +294,7 @@ def name_is_builtin_symbol(module: ModuleType, name: str) -> Optional[type]:
):
return None
- from mathics.builtin.base import Builtin
+ from mathics.core.builtin import Builtin
# If it is not a subclass of Builtin, skip it.
if not issubclass(module_object, Builtin):
@@ -277,3 +304,13 @@ def name_is_builtin_symbol(module: ModuleType, name: str) -> Optional[type]:
if module_object in getattr(module, "DOES_NOT_ADD_BUILTIN_DEFINITION", []):
return None
return module_object
+
+
+def update_display_operators_set(builtin_instance):
+ """
+ If builtin_instance is an operator of some kind, add that
+ to the set of opererator strings ``display_operators_set``.
+ """
+ operator = builtin_instance.get_operator_display()
+ if operator is not None:
+ display_operators_set.add(operator)
diff --git a/mathics/core/number.py b/mathics/core/number.py
index 4103a5d8b..0a075b2a6 100644
--- a/mathics/core/number.py
+++ b/mathics/core/number.py
@@ -4,7 +4,7 @@
import string
from math import ceil, log
from sys import float_info
-from typing import List, Optional
+from typing import List, Optional, Union
import mpmath
import sympy
@@ -70,7 +70,7 @@ def _get_float_inf(value, evaluation) -> Optional[float]:
def get_precision(
value: BaseElement, evaluation, show_messages: bool = True
-) -> Optional[float]:
+) -> Optional[Union[int, float]]:
"""
Returns the ``float`` in the interval [``$MinPrecision``, ``$MaxPrecision``] closest
to ``value``.
@@ -136,7 +136,7 @@ def prec(dps) -> int:
return max(1, int(round((int(dps) + 1) * LOG2_10)))
-def min_prec(*args: BaseElement) -> Optional[float]:
+def min_prec(*args: BaseElement) -> Optional[int]:
"""
Returns the precision of the expression with the minimum precision.
If all the expressions are exact or non numeric, return None.
diff --git a/mathics/core/parser/__init__.py b/mathics/core/parser/__init__.py
index 39e5238c1..8ad4654db 100644
--- a/mathics/core/parser/__init__.py
+++ b/mathics/core/parser/__init__.py
@@ -6,7 +6,7 @@
There is a separate `README
`_
-for decribing how this works.
+for describing how this works.
"""
diff --git a/mathics/core/parser/convert.py b/mathics/core/parser/convert.py
index c68c872c8..b53caa512 100644
--- a/mathics/core/parser/convert.py
+++ b/mathics/core/parser/convert.py
@@ -18,6 +18,7 @@
Symbol as AST_Symbol,
)
from mathics.core.symbols import Symbol, SymbolList
+from mathics.core.util import canonic_filename
class GenericConverter:
@@ -36,7 +37,7 @@ def do_convert(self, node):
return "Expression", head, children
@staticmethod
- def string_escape(s):
+ def string_escape(s: str) -> str:
return s.encode("raw_unicode_escape").decode("unicode_escape")
def convert_Symbol(self, node: AST_Symbol) -> Tuple[str, str]:
@@ -54,8 +55,14 @@ def convert_Filename(self, node: AST_Filename):
if s.startswith('"'):
assert s.endswith('"')
s = s[1:-1]
+
+ s = self.string_escape(canonic_filename(s))
s = self.string_escape(s)
- s = s.replace("\\", "\\\\")
+
+ # Do we need this? If we do this before non-escaped characters,
+ # like \-, then Python gives a warning.
+ # s = s.replace("\\", "\\\\")
+
return "String", s
def convert_Number(self, node: AST_Number) -> tuple:
diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py
index f287d9815..8818da1d0 100644
--- a/mathics/core/pattern.py
+++ b/mathics/core/pattern.py
@@ -200,7 +200,6 @@ def does_match(
vars: Optional[dict] = None,
fully: bool = True,
) -> bool:
-
"""
returns True if `expression` matches self.
"""
@@ -262,7 +261,7 @@ def get_match_candidates(
evaluation: Evaluation,
vars: dict = {},
):
- return []
+ return tuple()
def get_match_candidates_count(
self,
@@ -435,7 +434,7 @@ def yield_choice(pre_vars):
self.match_element(
yield_func,
next_element,
- next_elements,
+ tuple(next_elements),
([], expression.elements),
pre_vars,
expression,
@@ -643,7 +642,7 @@ def yield_next(next):
# for setting in per_name(groups.items(), vars):
# def yield_name(setting):
# yield_func(setting)
- per_name(yield_choice, list(groups.items()), vars)
+ per_name(yield_choice, tuple(groups.items()), vars)
else:
yield_choice(vars)
@@ -709,7 +708,6 @@ def match_element(
fully: bool = True,
depth: int = 1,
):
-
if rest_expression is None:
rest_expression = ([], [])
@@ -717,7 +715,7 @@ def match_element(
match_count = element.get_match_count(vars)
element_candidates = element.get_match_candidates(
- rest_expression[1], # element.candidates,
+ tuple(rest_expression[1]), # element.candidates,
expression,
attributes,
evaluation,
@@ -785,7 +783,7 @@ def match_element(
candidates,
included=element_candidates,
less_first=less_first,
- *set_lengths
+ *set_lengths,
)
else:
# a generator that yields partitions of
@@ -796,7 +794,7 @@ def match_element(
flexible_start=first and not fully,
included=element_candidates,
less_first=less_first,
- *set_lengths
+ *set_lengths,
)
if rest_elements:
next_element = rest_elements[0]
@@ -863,7 +861,7 @@ def yield_wrapping(item):
self.get_wrappings(
yield_wrapping,
- items,
+ tuple(items),
match_count[1],
expression,
attributes,
diff --git a/mathics/core/read.py b/mathics/core/read.py
index 5531cf6d4..3c571bb16 100644
--- a/mathics/core/read.py
+++ b/mathics/core/read.py
@@ -3,7 +3,6 @@
"""
import io
-import os.path as osp
from mathics.builtin.atomic.strings import to_python_encoding
from mathics.core.atoms import Integer, String
@@ -12,13 +11,11 @@
from mathics.core.list import ListExpression
from mathics.core.streams import Stream, path_search, stream_manager
from mathics.core.symbols import Symbol
-
-# FIXME: don't use a module-level path
-INPUTFILE_VAR = ""
-
-SymbolInputStream = Symbol("InputStream")
-SymbolOutputStream = Symbol("OutputStream")
-SymbolEndOfFile = Symbol("EndOfFile")
+from mathics.core.systemsymbols import (
+ SymbolEndOfFile,
+ SymbolInputStream,
+ SymbolOutputStream,
+)
READ_TYPES = [
Symbol(k)
@@ -83,8 +80,6 @@ def __enter__(self, is_temporary_file=False):
# Open the file
self.fp = io.open(path, self.mode, encoding=self.encoding)
- global INPUTFILE_VAR
- INPUTFILE_VAR = osp.abspath(path)
# Add to our internal list of streams
self.stream = stream_manager.add(
@@ -100,8 +95,6 @@ def __enter__(self, is_temporary_file=False):
return self.fp
def __exit__(self, type, value, traceback):
- global INPUTFILE_VAR
- INPUTFILE_VAR = self.old_inputfile_var or ""
self.fp.close()
stream_manager.delete_stream(self.stream)
super().__exit__(type, value, traceback)
diff --git a/mathics/core/rules.py b/mathics/core/rules.py
index a1bb2385f..861b017e7 100644
--- a/mathics/core/rules.py
+++ b/mathics/core/rules.py
@@ -81,7 +81,7 @@ def yield_match(vars, rest):
if rest[0] or rest[1]:
result = Expression(
expression.get_head(),
- *list(chain(rest[0], [new_expression], rest[1]))
+ *list(chain(rest[0], [new_expression], rest[1])),
)
else:
result = new_expression
diff --git a/mathics/core/streams.py b/mathics/core/streams.py
index 2cf5aa988..94b4b3f4b 100644
--- a/mathics/core/streams.py
+++ b/mathics/core/streams.py
@@ -12,6 +12,7 @@
import requests
+from mathics.core.util import canonic_filename
from mathics.settings import ROOT_DIR
HOME_DIR = osp.expanduser("~")
@@ -80,7 +81,7 @@ def path_search(filename: str) -> Tuple[str, bool]:
is_temporary_file = True
else:
for p in PATH_VAR + [""]:
- path = osp.join(p, filename)
+ path = canonic_filename(osp.join(p, filename))
if osp.exists(path):
result = path
break
diff --git a/mathics/core/subexpression.py b/mathics/core/subexpression.py
index 9f0fd7f21..4fb1846ef 100644
--- a/mathics/core/subexpression.py
+++ b/mathics/core/subexpression.py
@@ -289,7 +289,7 @@ def elements(self, value):
def to_expression(self):
return Expression(
self._headp.to_expression(),
- *(element.to_expression() for element in self._elementsp)
+ *(element.to_expression() for element in self._elementsp),
)
def replace(self, new):
diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py
index adfe063e9..1be4ce772 100644
--- a/mathics/core/symbols.py
+++ b/mathics/core/symbols.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
import time
-from typing import Any, FrozenSet, List, Optional, Tuple
+from typing import Any, FrozenSet, List, Optional, Union
from mathics.core.element import (
BaseElement,
@@ -17,6 +17,7 @@
sympy_symbol_prefix = "_Mathics_User_"
sympy_slot_prefix = "_Mathics_Slot_"
+
# FIXME: This is repeated below
class NumericOperators:
"""
@@ -199,7 +200,8 @@ class Atom(BaseElement):
Atom is not a directly-mentioned WL entity, although conceptually
it very much seems to exist.
- The other kinds expression element is a Builtin, e.g. `ByteArray``, `CompiledCode`` or ``Image``.
+ The other kinds expression element is a Builtin, e.g. `ByteArray``, `CompiledCode``
+ or ``Image``.
"""
_head_name = ""
@@ -251,8 +253,8 @@ def get_atom_name(self) -> str:
def get_atoms(self, include_heads=True) -> List["Atom"]:
return [self]
- # We seem to need this because the caller doesn't distinguish something with elements
- # from a single atom.
+ # We seem to need this because the caller doesn't distinguish
+ # something with elements from a single atom.
def get_elements(self):
return []
@@ -262,14 +264,18 @@ def get_head(self) -> "Symbol":
def get_head_name(self) -> "str":
return self.class_head_name # System`" + self.__class__.__name__
- # def get_option_values(self, evaluation, allow_symbols=False, stop_on_error=True):
+ # def get_option_values(self, evaluation, allow_symbols=False,
+ # stop_on_error=True):
# """
# Build a dictionary of options from an expression.
- # For example Symbol("Integrate").get_option_values(evaluation, allow_symbols=True)
- # will return a list of options associated to the definition of the symbol "Integrate".
+ # For example Symbol("Integrate").get_option_values(evaluation,
+ # allow_symbols=True)
+ # will return a list of options associated to the definition of the symbol
+ # "Integrate".
# If self is not an expression,
# """
- # print("get_option_values is trivial for ", (self, stop_on_error, allow_symbols ))
+ # print("get_option_values is trivial for ", (self, stop_on_error,
+ # allow_symbols ))
# 1/0
# return None if stop_on_error else {}
@@ -660,8 +666,10 @@ class SymbolConstant(Symbol):
# We use __new__ here to unsure that two Integer's that have the same value
# return the same object.
- def __new__(cls, name, value):
+ _value = None
+
+ def __new__(cls, name, value):
name = ensure_context(name)
self = cls._symbol_constants.get(name)
if self is None:
@@ -825,7 +833,11 @@ def __floordiv__(self, other) -> BaseElement:
def __pow__(self, other) -> BaseElement:
return self.create_expression(SymbolPower, self, other)
- def round_to_float(self, evaluation=None, permit_complex=False) -> Optional[float]:
+ # FIXME: The name "round_to_float" is misleading when
+ # permit_complex is True.
+ def round_to_float(
+ self, evaluation=None, permit_complex=False
+ ) -> Optional[Union[complex, float]]:
"""
Round to a Python float. Return None if rounding is not possible.
This can happen if self or evaluation is NaN.
diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py
index fa388a92d..8ce706595 100644
--- a/mathics/core/systemsymbols.py
+++ b/mathics/core/systemsymbols.py
@@ -39,6 +39,7 @@
SymbolAssumptions = Symbol("System`$Assumptions")
SymbolAttributes = Symbol("System`Attributes")
SymbolAutomatic = Symbol("System`Automatic")
+SymbolBaseForm = Symbol("System`BaseForm")
SymbolBlank = Symbol("System`Blank")
SymbolBlankNullSequence = Symbol("System`BlankNullSequence")
SymbolBlankSequence = Symbol("System`BlankSequence")
@@ -80,6 +81,7 @@
SymbolDownValues = Symbol("System`DownValues")
SymbolE = Symbol("System`E")
SymbolEdgeForm = Symbol("System`EdgeForm")
+SymbolEndOfFile = Symbol("System`EndOfFile")
SymbolEndOfLine = Symbol("System`EndOfLine")
SymbolEndOfString = Symbol("System`EndOfString")
SymbolEqual = Symbol("System`Equal")
@@ -123,6 +125,7 @@
SymbolInfinity = Symbol("System`Infinity")
SymbolInfix = Symbol("System`Infix")
SymbolInputForm = Symbol("System`InputForm")
+SymbolInputStream = Symbol("System`InputStream")
SymbolInteger = Symbol("System`Integer")
SymbolIntegrate = Symbol("System`Integrate")
SymbolLeft = Symbol("System`Left")
@@ -168,6 +171,7 @@
SymbolNumberQ = Symbol("System`NumberQ")
SymbolNumericQ = Symbol("System`NumericQ")
SymbolO = Symbol("System`O")
+SymbolOpacity = Symbol("System`Opacity")
SymbolOptionValue = Symbol("System`OptionValue")
SymbolOptional = Symbol("System`Optional")
SymbolOptions = Symbol("System`Options")
@@ -175,10 +179,12 @@
SymbolOr = Symbol("System`Or")
SymbolOut = Symbol("System`Out")
SymbolOutputForm = Symbol("System`OutputForm")
+SymbolOutputStream = Symbol("System`OutputStream")
SymbolOverflow = Symbol("System`Overflow")
SymbolOwnValues = Symbol("System`OwnValues")
SymbolPackages = Symbol("System`$Packages")
SymbolPart = Symbol("System`Part")
+SymbolPath = Symbol("System`$Path")
SymbolPattern = Symbol("System`Pattern")
SymbolPatternTest = Symbol("System`PatternTest")
SymbolPi = Symbol("System`Pi")
diff --git a/mathics/core/util.py b/mathics/core/util.py
index b1bfef55e..4a1be908f 100644
--- a/mathics/core/util.py
+++ b/mathics/core/util.py
@@ -1,22 +1,36 @@
# -*- coding: utf-8 -*-
+"""
+Miscellaneous mathics.core utility functions.
+"""
-import re
import sys
from itertools import chain
+from pathlib import PureWindowsPath
+from platform import python_implementation
-# Remove "try" below and adjust return type after Python 3.6 support is dropped.
-try:
- from re import Pattern
-except ImportError:
- Pattern = re._pattern_type
+IS_PYPY = python_implementation() == "PyPy"
-IS_PYPY = "__pypy__" in sys.builtin_module_names
+def canonic_filename(path: str) -> str:
+ """
+ Canonicalize path. On Microsoft Windows, use PureWidnowsPath() to
+ turn backslash "\" to "/". On other platforms we currently, do
+ nothing, but we might in the future canonicalize the filename
+ further, e.g. via os.path.normpath().
+ """
+ if sys.platform.startswith("win"):
+ # win32 or win64..
+ # PureWindowsPath.as_posix() strips trailing "/" .
+ dir_suffix = "/" if path.endswith("/") else ""
+ path = PureWindowsPath(path).as_posix() + dir_suffix
+ # Should we use "os.path.normpath() here?
+ return path
+
# FIXME: These functions are used pattern.py
-def permutations(items, without_duplicates=True):
+def permutations(items):
if not items:
yield []
# already_taken = set()
@@ -26,7 +40,7 @@ def permutations(items, without_duplicates=True):
item = items[index]
# if item not in already_taken:
for sub in permutations(items[:index] + items[index + 1 :]):
- yield [item] + sub
+ yield [item] + list(sub)
# already_taken.add(item)
diff --git a/mathics/data/.gitignore b/mathics/data/.gitignore
index 850313a22..176966229 100644
--- a/mathics/data/.gitignore
+++ b/mathics/data/.gitignore
@@ -1,2 +1,3 @@
/doc_latex_data.pcl
+/doctest_latex_data.pcl
/op-tables.json
diff --git a/mathics/data/ExampleData/EinsteinSzilLetter.txt b/mathics/data/ExampleData/EinsteinSzilLetter.txt
index b8957e91f..b21183e4e 100644
--- a/mathics/data/ExampleData/EinsteinSzilLetter.txt
+++ b/mathics/data/ExampleData/EinsteinSzilLetter.txt
@@ -67,4 +67,3 @@ is now being repeated.
Yours very truly,
A. Einstein
(Albert Einstein)
-
diff --git a/mathics/data/ExampleData/Middlemarch.txt b/mathics/data/ExampleData/Middlemarch.txt
index dedf7acfc..87122e47f 100644
--- a/mathics/data/ExampleData/Middlemarch.txt
+++ b/mathics/data/ExampleData/Middlemarch.txt
@@ -293,4 +293,4 @@ going about what work he had in a mood of despair, and Rosamond
feeling, with some justification, that he was behaving cruelly. It was
of no use to say anything to Tertius; but when Will Ladislaw came, she
was determined to tell him everything. In spite of her general
-reticence, she needed some one who would recognize her wrongs.
\ No newline at end of file
+reticence, she needed some one who would recognize her wrongs.
diff --git a/mathics/data/ExampleData/Testosterone.svg b/mathics/data/ExampleData/Testosterone.svg
index 6bcb095d8..a29abac6e 100644
--- a/mathics/data/ExampleData/Testosterone.svg
+++ b/mathics/data/ExampleData/Testosterone.svg
@@ -196,4 +196,4 @@
-
\ No newline at end of file
+
diff --git a/mathics/doc/__init__.py b/mathics/doc/__init__.py
index 26efa89b8..1be93c620 100644
--- a/mathics/doc/__init__.py
+++ b/mathics/doc/__init__.py
@@ -1,8 +1,29 @@
# -*- coding: utf-8 -*-
"""
-Module for handling Mathics-style documentation.
+A module and library that assists in organizing document data
+located in static files and docstrings from
+Mathics3 Builtin Modules. Builtin Modules are written in Python and
+reside either in the Mathics3 core (mathics.builtin) or are packaged outside,
+in Mathics3 Modules e.g. pymathics.natlang.
-Right now this covers common LaTeX/PDF and routines common to
-Mathics Django. When this code is moved out, perhaps it will
-include the Mathics Django-specific piece.
+This data is stored in a way that facilitates:
+* organizing information to produce a LaTeX file
+* running documentation tests
+* producing HTML-based documentation
+
+The command-line utility ``docpipeline.py``, loads the data from
+Python modules and static files, accesses the functions here.
+
+Mathics Django also uses this library for its HTML-based documentation.
+
+The Mathics3 builtin function ``Information[]`` also uses to provide the
+information it reports.
+As with reading in data, final assembly to a LaTeX file or running
+documentation tests is done elsewhere.
+
+FIXME: This code should be replaced by Sphinx and autodoc.
+Things are such a mess, that it is too difficult to contemplate this right now.
+Also there higher-priority flaws that are more more pressing.
+In the shorter, we might we move code for extracting printing to a
+separate package.
"""
diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py
index 617c43e27..7b845b934 100644
--- a/mathics/doc/common_doc.py
+++ b/mathics/doc/common_doc.py
@@ -1,1156 +1,60 @@
# -*- coding: utf-8 -*-
-"""A module and library that assists in organizing document data
-previously obtained from static files and Python module/class doc
-strings. This data is stored in a way that facilitates:
-
-* organizing information to produce a LaTeX file
-* running documentation tests
-* producing HTML-based documentation
-
-The command-line utility `docpipeline.py`, which loads the data from
-Python modules and static files, accesses the functions here.
-
-Mathics-core routines also use this to get usage strings of Mathics
-Built-in functions.
-
-Mathics Django also uses this library for its HTML-based documentation.
-
-As with reading in data, final assembly to a LateX file or running
-documentation tests is done elsewhere.
-
-FIXME: Code should be moved for both to a separate package.
-
-More importantly, this code should be replaced by Sphinx and autodoc.
-Things are such a mess, that it is too difficult to contemplate this right now.
"""
-import importlib
-import os.path as osp
-import pkgutil
-import re
-from os import getenv, listdir
-from types import ModuleType
-from typing import Callable
-
-from mathics import settings
-from mathics.builtin.base import check_requires_list
-from mathics.core.evaluation import Message, Print
-from mathics.core.load_builtin import (
- builtins_by_module as global_builtins_by_module,
- mathics3_builtins_modules,
-)
-from mathics.core.util import IS_PYPY
-from mathics.doc.utils import slugify
-from mathics.eval.pymathics import pymathics_builtins_by_module, pymathics_modules
-# These are all the XML/HTML-like tags that documentation supports.
-ALLOWED_TAGS = (
- "dl",
- "dd",
- "dt",
- "em",
- "url",
- "ul",
- "i",
- "ol",
- "li",
- "con",
- "console",
- "img",
- "imgpng",
- "ref",
- "subsection",
-)
-ALLOWED_TAGS_RE = dict(
- (allowed, re.compile("<(%s.*?)>" % allowed)) for allowed in ALLOWED_TAGS
-)
+common_doc
-# This string is used, so we can indicate a trailing blank at the end of a line by
-# adding this string to the end of the line which gets stripped off.
-# Some editors and formatters like to strip off trailing blanks at the ends of lines.
-END_LINE_SENTINAL = "#<--#"
+This module is kept for backward compatibility.
-# The regular expressions below (strings ending with _RE
-# pull out information from docstring or text in a file. Ghetto parsing.
+The module was split into
+* mathics.doc.doc_entries: classes containing the documentation entries and doctests.
+* mathics.doc.structure: the classes describing the elements in the documentation organization
+* mathics.doc.gather: functions to gather information from modules to build the
+ documentation reference.
-CHAPTER_RE = re.compile('(?s)(.*?)')
-CONSOLE_RE = re.compile(r"(?s)<(?Pcon|console)>(?P.*?)(?P=tag)>")
-DL_ITEM_RE = re.compile(
- r"(?s)<(?Pd[td])>(?P.*?)(?:(?P=tag)>|)\s*(?:(?=)|$)"
-)
-DL_RE = re.compile(r"(?s)
(.*?)
")
-HYPERTEXT_RE = re.compile(
- r"(?s)<(?Pem|url)>(\s*:(?P.*?):\s*)?(?P.*?)(?P=tag)>"
-)
-IMG_PNG_RE = re.compile(
- r''
-)
-IMG_RE = re.compile(
- r''
-)
-# Preserve space before and after in-line code variables.
-LATEX_RE = re.compile(r"(\s?)\$(\w+?)\$(\s?)")
-
-LIST_ITEM_RE = re.compile(r"(?s)
", r"\bigskip"),
-}
-SUBSECTION_END_RE = re.compile("")
-SUBSECTION_RE = re.compile('(?s)')
-TESTCASE_RE = re.compile(
- r"""(?mx)^ # re.MULTILINE (multi-line match)
- # and re.VERBOSE (readable regular expressions
- ((?:.|\n)*?)
- ^\s+([>#SX])>[ ](.*) # test-code indicator
- ((?:\n\s*(?:[:|=.][ ]|\.).*)*) # test-code results"""
+from mathics.doc.doc_entries import (
+ ALLOWED_TAGS,
+ ALLOWED_TAGS_RE,
+ CONSOLE_RE,
+ DL_ITEM_RE,
+ DL_RE,
+ HYPERTEXT_RE,
+ IMG_PNG_RE,
+ IMG_RE,
+ LATEX_RE,
+ LIST_ITEM_RE,
+ LIST_RE,
+ MATHICS_RE,
+ PYTHON_RE,
+ QUOTATIONS_RE,
+ REF_RE,
+ SPECIAL_COMMANDS,
+ DocTest,
+ DocTests,
+ DocText,
+ DocumentationEntry,
+ Tests,
+ get_results_by_test,
+ parse_docstring_to_DocumentationEntry_items,
+ post_sub,
+ pre_sub,
+)
+
+gather_tests = parse_docstring_to_DocumentationEntry_items
+XMLDOC = DocumentationEntry
+
+from mathics.doc.structure import (
+ MATHICS3_MODULES_TITLE,
+ SUBSECTION_END_RE,
+ SUBSECTION_RE,
+ DocChapter,
+ DocGuideSection,
+ DocPart,
+ DocSection,
+ DocSubsection,
+ Documentation,
+ MathicsMainDocumentation,
+ sorted_chapters,
)
-TESTCASE_OUT_RE = re.compile(r"^\s*([:|=])(.*)$")
-
-# Used for getting test results by test expresson and chapter/section information.
-test_result_map = {}
-
-
-def get_module_doc(module: ModuleType) -> tuple:
- doc = module.__doc__
- if doc is not None:
- doc = doc.strip()
- if doc:
- title = doc.splitlines()[0]
- text = "\n".join(doc.splitlines()[1:])
- else:
- title = module.__name__
- for prefix in ("mathics.builtin.", "mathics.optional."):
- if title.startswith(prefix):
- title = title[len(prefix) :]
- title = title.capitalize()
- text = ""
- return title, text
-
-
-def get_results_by_test(test_expr: str, full_test_key: list, doc_data: dict) -> dict:
- """
- Sometimes test numbering is off, either due to bugs or changes since the
- data was read.
-
- Here, we compensate for this by looking up the test by its chapter and section name
- portion stored in `full_test_key` along with the and the test expresion data
- stored in `test_expr`.
-
- This new key is looked up in `test_result_map` its value is returned.
-
- `doc_data` is only first time this is called to populate `test_result_map`.
- """
-
- # Strip off the test index form new key with this and the test string.
- # Add to any existing value for that "result". This is now what we want to
- # use as a tee in test_result_map to look for.
- test_section = list(full_test_key)[:-1]
- search_key = tuple(test_section)
-
- if not test_result_map:
- # Populate test_result_map from doc_data
- for key, result in doc_data.items():
- test_section = list(key)[:-1]
- new_test_key = tuple(test_section)
- next_result = test_result_map.get(new_test_key, None)
- if next_result:
- next_result.append(result)
- else:
- next_result = [result]
- test_result_map[new_test_key] = next_result
-
- results = test_result_map.get(search_key, None)
- result = {}
- if results:
- for result_candidate in results:
- if result_candidate["query"] == test_expr:
- if result:
- # Already found something
- print(f"Warning, multiple results appear under {search_key}.")
- return {}
- else:
- result = result_candidate
-
- return result
-
-
-def get_submodule_names(object) -> list:
- """Many builtins are organized into modules which, from a documentation
- standpoint, are like Mathematica Online Guide Docs.
-
- "List Functions", "Colors", or "Distance and Similarity Measures"
- are some examples Guide Documents group group various Bultin Functions,
- under submodules relate to that general classification.
-
- Here, we want to return a list of the Python modules under a "Guide Doc"
- module.
-
- As an example of a "Guide Doc" and its submodules, consider the
- module named mathics.builtin.colors. It collects code and documentation pertaining
- to the builtin functions that would be found in the Guide documenation for "Colors".
-
- The `mathics.builtin.colors` module has a submodule
- `mathics.builtin.colors.named_colors`.
-
- The builtin functions defined in `named_colors` then are those found in the
- "Named Colors" group of the "Colors" Guide Doc.
-
- So in this example then, in the list the modules returned for
- Python module `mathics.builtin.colors` would be the
- `mathics.builtin.colors.named_colors` module which contains the
- definition and docs for the "Named Colors" Mathics Bultin
- Functions.
- """
- modpkgs = []
- if hasattr(object, "__path__"):
- for importer, modname, ispkg in pkgutil.iter_modules(object.__path__):
- modpkgs.append(modname)
- modpkgs.sort()
- return modpkgs
-
-
-def filter_comments(doc: str) -> str:
- """Remove docstring documentation comments. These are lines
- that start with ##"""
- return "\n".join(
- line for line in doc.splitlines() if not line.lstrip().startswith("##")
- )
-
-
-def get_doc_name_from_module(module):
- name = "???"
- if module.__doc__:
- lines = module.__doc__.strip()
- if not lines:
- name = module.__name__
- else:
- name = lines.split("\n")[0]
- return name
-
-
-POST_SUBSTITUTION_TAG = "_POST_SUBSTITUTION%d_"
-
-
-def pre_sub(regexp, text: str, repl_func):
- post_substitutions = []
-
- def repl_pre(match):
- repl = repl_func(match)
- index = len(post_substitutions)
- post_substitutions.append(repl)
- return POST_SUBSTITUTION_TAG % index
-
- text = regexp.sub(repl_pre, text)
-
- return text, post_substitutions
-
-
-def post_sub(text: str, post_substitutions) -> str:
- for index, sub in enumerate(post_substitutions):
- text = text.replace(POST_SUBSTITUTION_TAG % index, sub)
- return text
-
-
-def skip_doc(cls) -> bool:
- """Returns True if we should skip cls in docstring extraction."""
- return cls.__name__.endswith("Box") or (hasattr(cls, "no_doc") and cls.no_doc)
-
-
-class Tests:
- # FIXME: add optional guide section
- def __init__(self, part: str, chapter: str, section: str, doctests):
- self.part, self.chapter = part, chapter
- self.section, self.tests = section, doctests
-
-
-def skip_module_doc(module, modules_seen) -> bool:
- return (
- module.__doc__ is None
- or module in modules_seen
- or module.__name__.split(".")[0] not in ("mathics", "pymathics")
- or hasattr(module, "no_doc")
- and module.no_doc
- )
-
-
-def sorted_chapters(chapters: list) -> list:
- """Return chapters sorted by title"""
- return sorted(chapters, key=lambda chapter: chapter.title)
-
-
-def gather_tests(
- doc: str,
- test_collection_constructor: Callable,
- test_case_constructor: Callable,
- text_constructor: Callable,
- key_part=None,
-) -> list:
- """
- This parses string `doc` (using regular expresssions) into Python objects.
- test_collection_fn() is the class construtorto call to create an object for the
- test collection. Each test is created via test_case_fn().
- Text within the test is stored via text_constructor.
- """
- # Remove commented lines.
- doc = filter_comments(doc).strip(r"\s")
-
- # Remove leading
...
- # doc = DL_RE.sub("", doc)
-
- # pre-substitute Python code because it might contain tests
- doc, post_substitutions = pre_sub(
- PYTHON_RE, doc, lambda m: "%s" % m.group(1)
- )
-
- # HACK: Artificially construct a last testcase to get the "intertext"
- # after the last (real) testcase. Ignore the test, of course.
- doc += "\n >> test\n = test"
- testcases = TESTCASE_RE.findall(doc)
-
- tests = None
- items = []
- for index in range(len(testcases)):
- testcase = list(testcases[index])
- text = testcase.pop(0).strip()
- if text:
- if tests is not None:
- items.append(tests)
- tests = None
- text = post_sub(text, post_substitutions)
- items.append(text_constructor(text))
- tests = None
- if index < len(testcases) - 1:
- test = test_case_constructor(index, testcase, key_part)
- if tests is None:
- tests = test_collection_constructor()
- tests.tests.append(test)
- if tests is not None:
- items.append(tests)
- tests = None
- return items
-
-
-class Documentation:
- def __init__(self, part, title: str, doc=None):
- self.doc = doc
- self.guide_sections = []
- self.part = part
- self.sections = []
- self.sections_by_slug = {}
- self.slug = slugify(title)
- self.title = title
- part.chapters_by_slug[self.slug] = self
-
- def add_section(
- self,
- chapter,
- section_name: str,
- section_object,
- operator,
- is_guide: bool = False,
- in_guide: bool = False,
- summary_text="",
- ):
- """
- Adds a DocSection or DocGuideSection
- object to the chapter, a DocChapter object.
- "section_object" is either a Python module or a Class object instance.
- """
- installed = check_requires_list(getattr(section_object, "requires", []))
-
- # FIXME add an additional mechanism in the module
- # to allow a docstring and indicate it is not to go in the
- # user manual
- if not section_object.__doc__:
- return
- if is_guide:
- section = self.doc_guide_section_fn(
- chapter,
- section_name,
- section_object.__doc__,
- section_object,
- installed=installed,
- )
- chapter.guide_sections.append(section)
- else:
- section = self.doc_section_fn(
- chapter,
- section_name,
- section_object.__doc__,
- operator=operator,
- installed=installed,
- in_guide=in_guide,
- summary_text=summary_text,
- )
- chapter.sections.append(section)
-
- return section
-
- def add_subsection(
- self,
- chapter,
- section,
- subsection_name: str,
- instance,
- operator=None,
- in_guide=False,
- ):
- installed = check_requires_list(getattr(instance, "requires", []))
-
- # FIXME add an additional mechanism in the module
- # to allow a docstring and indicate it is not to go in the
- # user manual
-
- """
- Append a subsection for ``instance`` into ``section.subsections``
- """
- installed = True
- for package in getattr(instance, "requires", []):
- try:
- importlib.import_module(package)
- except ImportError:
- installed = False
- break
-
- # FIXME add an additional mechanism in the module
- # to allow a docstring and indicate it is not to go in the
- # user manual
- if not instance.__doc__:
- return
- summary_text = (
- instance.summary_text if hasattr(instance, "summary_text") else ""
- )
- subsection = self.doc_subsection_fn(
- chapter,
- section,
- subsection_name,
- instance.__doc__,
- operator=operator,
- installed=installed,
- in_guide=in_guide,
- summary_text=summary_text,
- )
- section.subsections.append(subsection)
-
- def doc_part(self, title, modules, builtins_by_module, start):
- """
- Produce documentation for a "Part" - reference section or
- possibly Pymathics modules
- """
-
- builtin_part = self.doc_part_fn(self, title, is_reference=start)
- modules_seen = set([])
- submodule_names_seen = set([])
-
- want_sorting = True
- if want_sorting:
- module_collection_fn = lambda x: sorted(
- modules,
- key=lambda module: module.sort_order
- if hasattr(module, "sort_order")
- else module.__name__,
- )
- else:
- module_collection_fn = lambda x: x
- for module in module_collection_fn(modules):
- if skip_module_doc(module, modules_seen):
- continue
- title, text = get_module_doc(module)
- chapter = self.doc_chapter_fn(
- builtin_part, title, self.doc_fn(text, title, None)
- )
- builtins = builtins_by_module.get(module.__name__)
- if module.__file__.endswith("__init__.py"):
- # We have a Guide Section.
- name = get_doc_name_from_module(module)
- guide_section = self.add_section(
- chapter, name, module, operator=None, is_guide=True
- )
- submodules = [
- value
- for value in module.__dict__.values()
- if isinstance(value, ModuleType)
- ]
-
- sorted_submodule = lambda x: sorted(
- submodules,
- key=lambda submodule: submodule.sort_order
- if hasattr(submodule, "sort_order")
- else submodule.__name__,
- )
-
- # Add sections in the guide section...
- for submodule in sorted_submodule(submodules):
- if skip_module_doc(submodule, modules_seen):
- continue
- elif IS_PYPY and submodule.__name__ == "builtins":
- # PyPy seems to add this module on its own,
- # but it is not something that can be importable
- continue
-
- submodule_name = get_doc_name_from_module(submodule)
- if submodule_name in submodule_names_seen:
- continue
- section = self.add_section(
- chapter,
- submodule_name,
- submodule,
- operator=None,
- is_guide=False,
- in_guide=True,
- )
- modules_seen.add(submodule)
- submodule_names_seen.add(submodule_name)
- guide_section.subsections.append(section)
-
- builtins = builtins_by_module.get(submodule.__name__, [])
- subsections = [builtin for builtin in builtins]
- for instance in subsections:
- if hasattr(instance, "no_doc") and instance.no_doc:
- continue
-
- name = instance.get_name(short=True)
- if name in submodule_names_seen:
- continue
-
- submodule_names_seen.add(name)
- modules_seen.add(instance)
-
- self.add_subsection(
- chapter,
- section,
- name,
- instance,
- instance.get_operator(),
- in_guide=True,
- )
- else:
- if not builtins:
- continue
- sections = [
- builtin for builtin in builtins if not skip_doc(builtin.__class__)
- ]
- self.doc_sections(sections, modules_seen, chapter)
- builtin_part.chapters.append(chapter)
- self.parts.append(builtin_part)
-
- def doc_sections(self, sections, modules_seen, chapter):
- for instance in sections:
- if instance not in modules_seen and (
- not hasattr(instance, "no_doc") or not instance.no_doc
- ):
- name = instance.get_name(short=True)
- summary_text = (
- instance.summary_text if hasattr(instance, "summary_text") else ""
- )
- self.add_section(
- chapter,
- name,
- instance,
- instance.get_operator(),
- is_guide=False,
- in_guide=False,
- summary_text=summary_text,
- )
- modules_seen.add(instance)
-
- def gather_doctest_data(self):
- """
- Extract doctest data from various static XML-like doc files, Mathics3 Built-in functions
- (inside mathics.builtin), and external Mathics3 Modules.
-
- The extracted structure is stored in ``self``.
- """
-
- # First gather data from static XML-like files. This constitutes "Part 1" of the
- # documentation.
- files = listdir(self.doc_dir)
- files.sort()
- appendix = []
-
- for file in files:
- part_title = file[2:]
- if part_title.endswith(".mdoc"):
- part_title = part_title[: -len(".mdoc")]
- part = self.doc_part_fn(self, part_title)
- text = open(osp.join(self.doc_dir, file), "rb").read().decode("utf8")
- text = filter_comments(text)
- chapters = CHAPTER_RE.findall(text)
- for title, text in chapters:
- chapter = self.doc_chapter_fn(part, title)
- text += ''
- sections = SECTION_RE.findall(text)
- for pre_text, title, text in sections:
- if title:
- section = self.doc_section_fn(
- chapter, title, text, operator=None, installed=True
- )
- chapter.sections.append(section)
- subsections = SUBSECTION_RE.findall(text)
- for subsection_title in subsections:
- subsection = self.doc_subsection_fn(
- chapter,
- section,
- subsection_title,
- text,
- )
- section.subsections.append(subsection)
- pass
- pass
- else:
- section = None
- if not chapter.doc:
- chapter.doc = self.doc_fn(pre_text, title, section)
- pass
-
- part.chapters.append(chapter)
- if file[0].isdigit():
- self.parts.append(part)
- else:
- part.is_appendix = True
- appendix.append(part)
-
- # Next extract data that has been loaded into Mathics3 when it runs.
- # This is information from `mathics.builtin`.
- # This is Part 2 of the documentation.
-
- for title, modules, builtins_by_module, start in [
- (
- "Reference of Built-in Symbols",
- mathics3_builtins_modules,
- global_builtins_by_module,
- True,
- )
- ]:
- self.doc_part(title, modules, builtins_by_module, start)
-
- # Now extract external Mathics3 Modules that have been loaded via
- # LoadModule, or eval_LoadModule.
-
- # This is Part 3 of the documentation.
-
- for title, modules, builtins_by_module, start in [
- (
- "Mathics3 Modules",
- pymathics_modules,
- pymathics_builtins_by_module,
- True,
- )
- ]:
- self.doc_part(title, modules, builtins_by_module, start)
-
- # Now extract Appendix information. This include License text
-
- # This is the final Part of the documentation.
-
- for part in appendix:
- self.parts.append(part)
-
- # Via the wanderings above, collect all tests that have been
- # seen.
- #
- # Each test is accessble by its part + chapter + section and test number
- # in that section.
- for tests in self.get_tests():
- for test in tests.tests:
- test.key = (tests.part, tests.chapter, tests.section, test.index)
- return
-
- def get_part(self, part_slug):
- return self.parts_by_slug.get(part_slug)
-
- def get_chapter(self, part_slug, chapter_slug):
- part = self.parts_by_slug.get(part_slug)
- if part:
- return part.chapters_by_slug.get(chapter_slug)
- return None
-
- def get_section(self, part_slug, chapter_slug, section_slug):
- part = self.parts_by_slug.get(part_slug)
- if part:
- chapter = part.chapters_by_slug.get(chapter_slug)
- if chapter:
- return chapter.sections_by_slug.get(section_slug)
- return None
-
- def get_subsection(self, part_slug, chapter_slug, section_slug, subsection_slug):
- part = self.parts_by_slug.get(part_slug)
- if part:
- chapter = part.chapters_by_slug.get(chapter_slug)
- if chapter:
- section = chapter.sections_by_slug.get(section_slug)
- if section:
- return section.subsections_by_slug.get(subsection_slug)
-
- return None
-
- def get_tests(self, want_sorting=False):
- for part in self.parts:
- if want_sorting:
- chapter_collection_fn = lambda x: sorted_chapters(x)
- else:
- chapter_collection_fn = lambda x: x
- for chapter in chapter_collection_fn(part.chapters):
- tests = chapter.doc.get_tests()
- if tests:
- yield Tests(part.title, chapter.title, "", tests)
- for section in chapter.all_sections:
- if section.installed:
- if isinstance(section, DocGuideSection):
- for docsection in section.subsections:
- for docsubsection in docsection.subsections:
- # FIXME: Something is weird here where tests for subsection items
- # appear not as a collection but individually and need to be
- # iterated below. Probably some other code is faulty and
- # when fixed the below loop and collection into doctest_list[]
- # will be removed.
- if not docsubsection.installed:
- continue
- doctest_list = []
- index = 1
- for doctests in docsubsection.items:
- doctest_list += list(doctests.get_tests())
- for test in doctest_list:
- test.index = index
- index += 1
-
- if doctest_list:
- yield Tests(
- section.chapter.part.title,
- section.chapter.title,
- docsubsection.title,
- doctest_list,
- )
- else:
- tests = section.doc.get_tests()
- if tests:
- yield Tests(
- part.title, chapter.title, section.title, tests
- )
- pass
- pass
- pass
- pass
- pass
- pass
- return
-
-
-class DocChapter:
- def __init__(self, part, title, doc=None):
- self.doc = doc
- self.guide_sections = []
- self.part = part
- self.title = title
- self.slug = slugify(title)
- self.sections = []
- self.sections_by_slug = {}
- part.chapters_by_slug[self.slug] = self
-
- def __str__(self):
- sections = "\n".join(str(section) for section in self.sections)
- return f"= {self.title} =\n\n{sections}"
-
- @property
- def all_sections(self):
- return sorted(self.sections + self.guide_sections)
-
-
-class DocSection:
- def __init__(
- self,
- chapter,
- title: str,
- text: str,
- operator,
- installed=True,
- in_guide=False,
- summary_text="",
- ):
- self.chapter = chapter
- self.in_guide = in_guide
- self.installed = installed
- self.items = [] # tests in section when this is under a guide section
- self.operator = operator
- self.slug = slugify(title)
- self.subsections = []
- self.subsections_by_slug = {}
- self.summary_text = summary_text
- self.title = title
-
- if text.count("
") != text.count("
"):
- raise ValueError(
- "Missing opening or closing
tag in "
- "{} documentation".format(title)
- )
-
- # Needs to come after self.chapter is initialized since
- # XMLDoc uses self.chapter.
- self.doc = XMLDoc(text, title, self)
-
- chapter.sections_by_slug[self.slug] = self
-
- # Add __eq__ and __lt__ so we can sort Sections.
- def __eq__(self, other):
- return self.title == other.title
-
- def __lt__(self, other):
- return self.title < other.title
-
- def __str__(self):
- return f"== {self.title} ==\n{self.doc}"
-
-
-class DocGuideSection(DocSection):
- """An object for a Documented Guide Section.
- A Guide Section is part of a Chapter. "Colors" or "Special Functions"
- are examples of Guide Sections, and each contains a number of Sections.
- like NamedColors or Orthogonal Polynomials.
- """
-
- def __init__(
- self, chapter: str, title: str, text: str, submodule, installed: bool = True
- ):
- self.chapter = chapter
- self.doc = XMLDoc(text, title, None)
- self.in_guide = False
- self.installed = installed
- self.section = submodule
- self.slug = slugify(title)
- self.subsections = []
- self.subsections_by_slug = {}
- self.title = title
-
- # FIXME: Sections never are operators. Subsections can have
- # operators though. Fix up the view and searching code not to
- # look for the operator field of a section.
- self.operator = False
-
- if text.count("
") != text.count("
"):
- raise ValueError(
- "Missing opening or closing
tag in "
- "{} documentation".format(title)
- )
- # print("YYY Adding section", title)
- chapter.sections_by_slug[self.slug] = self
-
- def get_tests(self):
- # FIXME: The below is a little weird for Guide Sections.
- # Figure out how to make this clearer.
- # A guide section's subsection are Sections without the Guide.
- # it is *their* subsections where we generally find tests.
- for section in self.subsections:
- if not section.installed:
- continue
- for subsection in section.subsections:
- # FIXME we are omitting the section title here...
- if not subsection.installed:
- continue
- for doctests in subsection.items:
- yield doctests.get_tests()
-
-
-class DocSubsection:
- """An object for a Documented Subsection.
- A Subsection is part of a Section.
- """
-
- def __init__(
- self,
- chapter,
- section,
- title,
- text,
- operator=None,
- installed=True,
- in_guide=False,
- summary_text="",
- ):
- """
- Information that goes into a subsection object. This can be a written text, or
- text extracted from the docstring of a builtin module or class.
-
- About some of the parameters...
-
- Some subsections are contained in a grouping module and need special work to
- get the grouping module name correct.
-
- For example the Chapter "Colors" is a module so the docstring text for it is in
- mathics/builtin/colors/__init__.py . In mathics/builtin/colors/named-colors.py we have
- the "section" name for the class Read (the subsection) inside it.
- """
-
- title_summary_text = re.split(" -- ", title)
- n = len(title_summary_text)
- self.title = title_summary_text[0] if n > 0 else ""
- self.summary_text = title_summary_text[1] if n > 1 else summary_text
-
- self.doc = XMLDoc(text, title, section)
- self.chapter = chapter
- self.in_guide = in_guide
- self.installed = installed
- self.operator = operator
-
- self.section = section
- self.slug = slugify(title)
- self.subsections = []
- self.title = title
-
- if section:
- chapter = section.chapter
- part = chapter.part
- # Note: we elide section.title
- key_prefix = (part.title, chapter.title, title)
- else:
- key_prefix = None
-
- if in_guide:
- # Tests haven't been picked out yet from the doc string yet.
- # Gather them here.
- self.items = gather_tests(text, DocTests, DocTest, DocText, key_prefix)
- else:
- self.items = []
-
- if text.count("
") != text.count("
"):
- raise ValueError(
- "Missing opening or closing
tag in "
- "{} documentation".format(title)
- )
- self.section.subsections_by_slug[self.slug] = self
-
- def __str__(self):
- return f"=== {self.title} ===\n{self.doc}"
-
-
-class DocTest:
- """
- DocTest formatting rules:
-
- * `>>` Marks test case; it will also appear as part of
- the documentation.
- * `#>` Marks test private or one that does not appear as part of
- the documentation.
- * `X>` Shows the example in the docs, but disables testing the example.
- * `S>` Shows the example in the docs, but disables testing if environment
- variable SANDBOX is set.
- * `=` Compares the result text.
- * `:` Compares an (error) message.
- `|` Prints output.
- """
-
- def __init__(self, index, testcase, key_prefix=None):
- def strip_sentinal(line):
- """Remove END_LINE_SENTINAL from the end of a line if it appears.
-
- Some editors like to strip blanks at the end of a line.
- Since the line ends in END_LINE_SENTINAL which isn't blank,
- any blanks that appear before will be preserved.
-
- Some tests require some lines to be blank or entry because
- Mathics3 output can be that way
- """
- if line.endswith(END_LINE_SENTINAL):
- line = line[: -len(END_LINE_SENTINAL)]
-
- # Also remove any remaining trailing blanks since that
- # seems *also* what we want to do.
- return line.strip()
-
- self.index = index
- self.result = None
- self.outs = []
-
- # Private test cases are executed, but NOT shown as part of the docs
- self.private = testcase[0] == "#"
-
- # Ignored test cases are NOT executed, but shown as part of the docs
- # Sandboxed test cases are NOT executed if environment SANDBOX is set
- if testcase[0] == "X" or (testcase[0] == "S" and getenv("SANDBOX", False)):
- self.ignore = True
- # substitute '>' again so we get the correct formatting
- testcase[0] = ">"
- else:
- self.ignore = False
-
- self.test = strip_sentinal(testcase[1])
-
- self.key = None
- if key_prefix:
- self.key = tuple(key_prefix + (index,))
- outs = testcase[2].splitlines()
- for line in outs:
- line = strip_sentinal(line)
- if line:
- if line.startswith("."):
- text = line[1:]
- if text.startswith(" "):
- text = text[1:]
- text = "\n" + text
- if self.result is not None:
- self.result += text
- elif self.outs:
- self.outs[-1].text += text
- continue
-
- match = TESTCASE_OUT_RE.match(line)
- if not match:
- continue
- symbol, text = match.group(1), match.group(2)
- text = text.strip()
- if symbol == "=":
- self.result = text
- elif symbol == ":":
- out = Message("", "", text)
- self.outs.append(out)
- elif symbol == "|":
- out = Print(text)
- self.outs.append(out)
-
- def __str__(self):
- return self.test
-
-
-# FIXME: think about - do we need this? Or can we use DjangoMathicsDocumentation and
-# LatTeXMathicsDocumentation only?
-class MathicsMainDocumentation(Documentation):
- """
- This module is used for creating test data and saving it to a Python Pickle file
- and running tests that appear in the documentation (doctests).
-
- There are other classes DjangoMathicsDocumentation and LaTeXMathicsDocumentation
- that format the data accumulated here. In fact I think those can sort of serve
- instead of this.
- """
-
- def __init__(self, want_sorting=False):
- self.doc_chapter_fn = DocChapter
- self.doc_dir = settings.DOC_DIR
- self.doc_fn = XMLDoc
- self.doc_guide_section_fn = DocGuideSection
- self.doc_part_fn = DocPart
- self.doc_section_fn = DocSection
- self.doc_subsection_fn = DocSubsection
- self.doctest_latex_pcl_path = settings.DOCTEST_LATEX_DATA_PCL
- self.parts = []
- self.parts_by_slug = {}
- self.pymathics_doc_loaded = False
- self.doc_data_file = settings.get_doctest_latex_data_path(
- should_be_readable=True
- )
- self.title = "Overview"
-
-
-class XMLDoc:
- """A class to hold our internal XML-like format data.
- Specialized classes like LaTeXDoc or and DjangoDoc provide methods for
- getting formatted output. For LaTeXDoc ``latex()`` is added while for
- DjangoDoc ``html()`` is added
-
- Mathics core also uses this in getting usage strings (`??`).
- """
-
- def __init__(self, doc, title, section=None):
- self.title = title
- if section:
- chapter = section.chapter
- part = chapter.part
- # Note: we elide section.title
- key_prefix = (part.title, chapter.title, title)
- else:
- key_prefix = None
-
- self.rawdoc = doc
- self.items = gather_tests(self.rawdoc, DocTests, DocTest, DocText, key_prefix)
-
- def __str__(self):
- return "\n".join(str(item) for item in self.items)
-
- def text(self, detail_level):
- # used for introspection
- # TODO parse XML and pretty print
- # HACK
- item = str(self.items[0])
- item = "\n".join(line.strip() for line in item.split("\n"))
- item = item.replace("
", "")
- item = item.replace("
", "")
- item = item.replace("
", " ")
- item = item.replace("
", "")
- item = item.replace("
", " ")
- item = item.replace("
", "")
- item = "\n".join(line for line in item.split("\n") if not line.isspace())
- return item
-
- def get_tests(self):
- tests = []
- for item in self.items:
- tests.extend(item.get_tests())
- return tests
-
-
-class DocPart:
- def __init__(self, doc, title, is_reference=False):
- self.doc = doc
- self.title = title
- self.slug = slugify(title)
- self.chapters = []
- self.chapters_by_slug = {}
- self.is_reference = is_reference
- self.is_appendix = False
- doc.parts_by_slug[self.slug] = self
-
- def __str__(self):
- return "%s\n\n%s" % (
- self.title,
- "\n".join(str(chapter) for chapter in sorted_chapters(self.chapters)),
- )
-
-
-class DocText:
- def __init__(self, text):
- self.text = text
-
- def __str__(self):
- return self.text
-
- def get_tests(self):
- return []
-
- def is_private(self):
- return False
-
- def test_indices(self):
- return []
-
-
-class DocTests:
- def __init__(self):
- self.tests = []
- self.text = ""
-
- def get_tests(self):
- return self.tests
-
- def is_private(self):
- return all(test.private for test in self.tests)
-
- def __str__(self):
- return "\n".join(str(test) for test in self.tests)
-
- def test_indices(self):
- return [test.index for test in self.tests]
diff --git a/mathics/doc/doc_entries.py b/mathics/doc/doc_entries.py
new file mode 100644
index 000000000..114343180
--- /dev/null
+++ b/mathics/doc/doc_entries.py
@@ -0,0 +1,612 @@
+"""
+Documentation entries and doctests
+
+This module contains the objects representing the entries in the documentation
+system, and the functions used to parse docstrings into these objects.
+
+
+"""
+
+import logging
+import re
+from os import getenv
+from typing import Callable, List, Optional
+
+from mathics.core.evaluation import Message, Print
+
+# Used for getting test results by test expression and chapter/section information.
+test_result_map = {}
+
+
+# These are all the XML/HTML-like tags that documentation supports.
+ALLOWED_TAGS = (
+ "dl",
+ "dd",
+ "dt",
+ "em",
+ "url",
+ "ul",
+ "i",
+ "ol",
+ "li",
+ "con",
+ "console",
+ "img",
+ "imgpng",
+ "ref",
+ "subsection",
+)
+ALLOWED_TAGS_RE = dict(
+ (allowed, re.compile("<(%s.*?)>" % allowed)) for allowed in ALLOWED_TAGS
+)
+
+# This string is used, so we can indicate a trailing blank at the end of a line by
+# adding this string to the end of the line which gets stripped off.
+# Some editors and formatters like to strip off trailing blanks at the ends of lines.
+END_LINE_SENTINAL = "#<--#"
+
+# The regular expressions below (strings ending with _RE
+# pull out information from docstring or text in a file. Ghetto parsing.
+
+CONSOLE_RE = re.compile(r"(?s)<(?Pcon|console)>(?P.*?)(?P=tag)>")
+DL_ITEM_RE = re.compile(
+ r"(?s)<(?Pd[td])>(?P.*?)(?:(?P=tag)>|)\s*(?:(?=)|$)"
+)
+DL_RE = re.compile(r"(?s)
(.*?)
")
+HYPERTEXT_RE = re.compile(
+ r"(?s)<(?Pem|url)>(\s*:(?P.*?):\s*)?(?P.*?)(?P=tag)>"
+)
+IMG_PNG_RE = re.compile(
+ r''
+)
+IMG_RE = re.compile(
+ r''
+)
+# Preserve space before and after in-line code variables.
+LATEX_RE = re.compile(r"(\s?)\$(\w+?)\$(\s?)")
+
+LIST_ITEM_RE = re.compile(r"(?s)
", r"\bigskip"),
+}
+
+
+TESTCASE_RE = re.compile(
+ r"""(?mx)^ # re.MULTILINE (multi-line match)
+ # and re.VERBOSE (readable regular expressions
+ ((?:.|\n)*?)
+ ^\s+([>#SX])>[ ](.*) # test-code indicator
+ ((?:\n\s*(?:[:|=.][ ]|\.).*)*) # test-code results"""
+)
+TESTCASE_OUT_RE = re.compile(r"^\s*([:|=])(.*)$")
+
+
+def get_results_by_test(test_expr: str, full_test_key: list, doc_data: dict) -> dict:
+ """
+ Sometimes test numbering is off, either due to bugs or changes since the
+ data was read.
+
+ Here, we compensate for this by looking up the test by its chapter and section name
+ portion stored in `full_test_key` along with the and the test expression data
+ stored in `test_expr`.
+
+ This new key is looked up in `test_result_map` its value is returned.
+
+ `doc_data` is only first time this is called to populate `test_result_map`.
+ """
+
+ # Strip off the test index form new key with this and the test string.
+ # Add to any existing value for that "result". This is now what we want to
+ # use as a tee in test_result_map to look for.
+ test_section = list(full_test_key)[:-1]
+ search_key = tuple(test_section)
+
+ if not test_result_map:
+ # Populate test_result_map from doc_data
+ for key, result in doc_data.items():
+ test_section = list(key)[:-1]
+ new_test_key = tuple(test_section)
+ next_result = test_result_map.get(new_test_key, None)
+ if next_result is None:
+ next_result = [result]
+ else:
+ next_result.append(result)
+
+ test_result_map[new_test_key] = next_result
+
+ results = test_result_map.get(search_key, None)
+ result = {}
+ if results:
+ for result_candidate in results:
+ if result_candidate["query"] == test_expr:
+ if result:
+ # Already found something
+ logging.warning(
+ f"Warning, multiple results appear under {search_key}."
+ )
+ return {}
+
+ result = result_candidate
+
+ return result
+
+
+def filter_comments(doc: str) -> str:
+ """Remove docstring documentation comments. These are lines
+ that start with ##"""
+ return "\n".join(
+ line for line in doc.splitlines() if not line.lstrip().startswith("##")
+ )
+
+
+POST_SUBSTITUTION_TAG = "_POST_SUBSTITUTION%d_"
+
+
+def pre_sub(regexp, text: str, repl_func):
+ """apply substitutions previous to parse the text"""
+ post_substitutions = []
+
+ def repl_pre(match):
+ repl = repl_func(match)
+ index = len(post_substitutions)
+ post_substitutions.append(repl)
+ return POST_SUBSTITUTION_TAG % index
+
+ text = regexp.sub(repl_pre, text)
+
+ return text, post_substitutions
+
+
+def post_sub(text: str, post_substitutions) -> str:
+ """apply substitutions after parsing the doctests."""
+ for index, sub in enumerate(post_substitutions):
+ text = text.replace(POST_SUBSTITUTION_TAG % index, sub)
+ return text
+
+
+def parse_docstring_to_DocumentationEntry_items(
+ doc: str,
+ test_collection_constructor: Callable,
+ test_case_constructor: Callable,
+ text_constructor: Callable,
+ key_part=None,
+) -> list:
+ """
+ This parses string `doc` (using regular expressions) into Python objects.
+ The function returns a list of ``DocText`` and ``DocTests`` objects which
+ are contained in a ``DocumentationElement``.
+
+ test_collection_constructor() is the class constructor call to create an
+ object for the test collection.
+ Each test is created via test_case_constructor().
+ Text within the test is stored via text_constructor.
+ """
+ # This function is used to populate a ``DocumentEntry`` element, that
+ # in principle is not associated to any container
+ # (``DocChapter``/``DocSection``/``DocSubsection``)
+ # of the documentation system.
+ #
+ # The ``key_part`` parameter was used to set the ``key`` of the
+ # ``DocTest`` elements. This attribute
+ # should be set just after the ``DocumentationEntry`` (
+ # to which the tests belongs) is associated
+ # to a container, by calling ``container.set_parent_path``.
+ # However, the parameter is still used in MathicsDjango, so let's
+ # keep it and discard its value.
+ #
+ if key_part:
+ logging.warning("``key_part`` is deprecated. Its value is discarded.")
+
+ # Remove commented lines.
+ doc = filter_comments(doc).strip(r"\s")
+
+ # Remove leading
...
+ # doc = DL_RE.sub("", doc)
+
+ # pre-substitute Python code because it might contain tests
+ doc, post_substitutions = pre_sub(
+ PYTHON_RE, doc, lambda m: "%s" % m.group(1)
+ )
+
+ # HACK: Artificially construct a last testcase to get the "intertext"
+ # after the last (real) testcase. Ignore the test, of course.
+ doc += "\n >> test\n = test"
+ testcases = TESTCASE_RE.findall(doc)
+
+ tests = None
+ items = []
+ for index, test_case in enumerate(testcases):
+ testcase = list(test_case)
+ text = testcase.pop(0).strip()
+ if text:
+ if tests is not None:
+ items.append(tests)
+ tests = None
+ text = post_sub(text, post_substitutions)
+ items.append(text_constructor(text))
+ tests = None
+ if index < len(testcases) - 1:
+ test = test_case_constructor(index, testcase, None)
+ if tests is None:
+ tests = test_collection_constructor()
+ tests.tests.append(test)
+
+ # If the last block in the loop was not a Text block, append the
+ # last set of tests.
+ if tests is not None:
+ items.append(tests)
+ tests = None
+ return items
+
+
+class DocTest:
+ """
+ Class to hold a single doctest.
+
+ DocTest formatting rules:
+
+ * `>>` Marks test case; it will also appear as part of
+ the documentation.
+ * `#>` Marks test private or one that does not appear as part of
+ the documentation.
+ * `X>` Shows the example in the docs, but disables testing the example.
+ * `S>` Shows the example in the docs, but disables testing if environment
+ variable SANDBOX is set.
+ * `=` Compares the result text.
+ * `:` Compares an (error) message.
+ `|` Prints output.
+ """
+
+ def __init__(
+ self,
+ index: int,
+ testcase: List[str],
+ key_prefix: Optional[tuple] = None,
+ ):
+ def strip_sentinal(line: str):
+ """Remove END_LINE_SENTINAL from the end of a line if it appears.
+
+ Some editors like to strip blanks at the end of a line.
+ Since the line ends in END_LINE_SENTINAL which isn't blank,
+ any blanks that appear before will be preserved.
+
+ Some tests require some lines to be blank or entry because
+ Mathics3 output can be that way
+ """
+ if line.endswith(END_LINE_SENTINAL):
+ line = line[: -len(END_LINE_SENTINAL)]
+
+ # Also remove any remaining trailing blanks since that
+ # seems *also* what we want to do.
+ return line.strip()
+
+ self.index = index
+ self.outs = []
+ self.result = None
+
+ # Private test cases are executed, but NOT shown as part of the docs
+ self.private = testcase[0] == "#"
+
+ # Ignored test cases are NOT executed, but shown as part of the docs
+ # Sandboxed test cases are NOT executed if environment SANDBOX is set
+ if testcase[0] == "X" or (testcase[0] == "S" and getenv("SANDBOX", False)):
+ self.ignore = True
+ # substitute '>' again so we get the correct formatting
+ testcase[0] = ">"
+ else:
+ self.ignore = False
+
+ self.test = strip_sentinal(testcase[1])
+ self._key = key_prefix + (index,) if key_prefix else None
+
+ outs = testcase[2].splitlines()
+ for line in outs:
+ line = strip_sentinal(line)
+ if line:
+ if line.startswith("."):
+ text = line[1:]
+ if text.startswith(" "):
+ text = text[1:]
+ text = "\n" + text
+ if self.result is not None:
+ self.result += text
+ elif self.outs:
+ self.outs[-1].text += text
+ continue
+
+ match = TESTCASE_OUT_RE.match(line)
+ if not match:
+ continue
+ symbol, text = match.group(1), match.group(2)
+ text = text.strip()
+ if symbol == "=":
+ self.result = text
+ elif symbol == ":":
+ out = Message("", "", text)
+ self.outs.append(out)
+ elif symbol == "|":
+ out = Print(text)
+ self.outs.append(out)
+
+ def __str__(self) -> str:
+ return self.test
+
+ def compare(self, result: Optional[str], out: Optional[tuple] = tuple()) -> bool:
+ """
+ Performs a doctest comparison between ``result`` and ``wanted`` and returns
+ True if the test should be considered a success.
+ """
+ return self.compare_result(result) and self.compare_out(out)
+
+ def compare_result(self, result: Optional[str]):
+ """Compare a result with the expected result"""
+ wanted = self.result
+ # Check result
+ if wanted in ("...", result):
+ return True
+
+ if result is None or wanted is None:
+ return False
+ result_list = result.splitlines()
+ wanted_list = wanted.splitlines()
+ if result_list == [] and wanted_list == ["#<--#"]:
+ return True
+
+ if len(result_list) != len(wanted_list):
+ return False
+
+ for res, want in zip(result_list, wanted_list):
+ wanted_re = re.escape(want.strip())
+ wanted_re = wanted_re.replace("\\.\\.\\.", ".*?")
+ wanted_re = f"^{wanted_re}$"
+ if not re.match(wanted_re, res.strip()):
+ return False
+ return True
+
+ def compare_out(self, outs: tuple = tuple()) -> bool:
+ """Compare messages and warnings produced during the evaluation of
+ the test with the expected messages and warnings."""
+ # Check out
+ wanted_outs = self.outs
+ if len(wanted_outs) == 1 and wanted_outs[0].text == "...":
+ # If we have ... don't check
+ return True
+ if len(outs) != len(wanted_outs):
+ # Mismatched number of output lines, and we don't have "..."
+ return False
+
+ # Need to check all output line by line
+ for got, wanted in zip(outs, wanted_outs):
+ if wanted.text == "...":
+ return True
+ if not got == wanted:
+ return False
+
+ return True
+
+ @property
+ def key(self):
+ """key identifier of the test"""
+ return self._key if hasattr(self, "_key") else None
+
+ @key.setter
+ def key(self, value):
+ """setter for the key identifier of the test"""
+ assert self.key is None
+ self._key = value
+ return self._key
+
+
+class DocTests:
+ """
+ A bunch of consecutive ``DocTest`` objects extracted from a Builtin docstring.
+ """
+
+ def __init__(self):
+ self.tests = []
+ self.text = ""
+
+ def get_tests(self) -> list:
+ """
+ Returns lists test objects.
+ """
+ return self.tests
+
+ def is_private(self) -> bool:
+ """Returns True if this test is "private" not supposed to be visible as example documentation."""
+ return all(test.private for test in self.tests)
+
+ def __str__(self) -> str:
+ return "\n".join(str(test) for test in self.tests)
+
+ def test_indices(self) -> List[int]:
+ """indices of the tests"""
+ return [test.index for test in self.tests]
+
+
+class DocText:
+ """
+ Class to hold some (non-test) text.
+
+ Some of the kinds of tags you may find here are showing in global ALLOWED_TAGS.
+ Some text may be marked with surrounding "$" or "'".
+
+ The code here however does not make use of any of the tagging.
+
+ """
+
+ def __init__(self, text):
+ self.text = text
+
+ def __str__(self) -> str:
+ return self.text
+
+ def get_tests(self) -> list:
+ """
+ Return tests in a DocText item - there never are any.
+ """
+ return []
+
+ def is_private(self) -> bool:
+ """the test is private, meaning that it will not be included in the
+ documentation, but tested in the doctest cycle."""
+ return False
+
+ def test_indices(self) -> List[int]:
+ """indices of the tests"""
+ return []
+
+
+# Former XMLDoc
+class DocumentationEntry:
+ """
+ A class to hold the content of a documentation entry,
+ in our custom XML-like format.
+
+ Describes the contain of an entry in the documentation system, as a
+ sequence (list) of items of the clase `DocText` and `DocTests`.
+ ``DocText`` items contains an internal XML-like formatted text. ``DocTests`` entries
+ contain one or more `DocTest` element.
+ Each level of the Documentation hierarchy contains an XMLDoc, describing the
+ content after the title and before the elements of the next level. For example,
+ in ``DocChapter``, ``DocChapter.doc`` contains the text coming after the title
+ of the chapter, and before the sections in `DocChapter.sections`.
+ Specialized classes like LaTeXDoc or and DjangoDoc provide methods for
+ getting formatted output. For LaTeXDoc ``latex()`` is added while for
+ DjangoDoc ``html()`` is added
+ Mathics core also uses this in getting usage strings (`??`).
+
+ """
+
+ def __init__(
+ self, doc_str: str, title: str, section: Optional["DocSection"] = None
+ ):
+ self._set_classes()
+ self.title = title
+ self.path = None
+ if section:
+ chapter = section.chapter
+ part = chapter.part
+ # Note: we elide section.title
+ key_prefix = (part.title, chapter.title, title)
+ else:
+ key_prefix = None
+
+ self.key_prefix = key_prefix
+ self.rawdoc = doc_str
+ self.items = parse_docstring_to_DocumentationEntry_items(
+ self.rawdoc,
+ self.docTest_collection_class,
+ self.docTest_class,
+ self.docText_class,
+ None,
+ )
+
+ def _set_classes(self):
+ """
+ Tells to the initializator the classes to be used to build the items.
+ This must be overloaded by the daughter classes.
+ """
+ if not hasattr(self, "docTest_collection_class"):
+ self.docTest_collection_class = DocTests
+ self.docTest_class = DocTest
+ self.docText_class = DocText
+
+ def __str__(self) -> str:
+ return "\n\n".join(str(item) for item in self.items)
+
+ def text(self) -> str:
+ """text version of the documentation entry"""
+ # used for introspection
+ # TODO parse XML and pretty print
+ # HACK
+ item = str(self.items[0])
+ item = "\n".join(line.strip() for line in item.split("\n"))
+ item = item.replace("
", "")
+ item = item.replace("
", "")
+ item = item.replace("
", " ")
+ item = item.replace("
", "")
+ item = item.replace("
", " ")
+ item = item.replace("
", "")
+ item = "\n".join(line for line in item.split("\n") if not line.isspace())
+ return item
+
+ def get_tests(self) -> list:
+ """retrieve a list of tests in the documentation entry"""
+ tests = []
+ for item in self.items:
+ tests.extend(item.get_tests())
+ return tests
+
+ def set_parent_path(self, parent):
+ """Set the parent path"""
+ self.path = None
+ path = []
+ while hasattr(parent, "parent"):
+ path = [parent.title] + path
+ parent = parent.parent
+
+ if hasattr(parent, "title"):
+ path = [parent.title] + path
+
+ if path:
+ self.path = path
+ # Set the key on each test
+ for test in self.get_tests():
+ assert test.key is None
+ # For backward compatibility, we need
+ # to reduce this to three fields.
+ # TODO: remove me and ensure that this
+ # works here and in Mathics Django
+ if len(path) > 3:
+ path = path[:2] + [path[-1]]
+ test.key = tuple(path) + (test.index,)
+
+ return self
+
+
+class Tests:
+ """
+ A group of tests in the same section or subsection.
+ """
+
+ def __init__(
+ self,
+ part_name: str,
+ chapter_name: str,
+ section_name: str,
+ doctests: List[DocTest],
+ subsection_name: Optional[str] = None,
+ ):
+ self.part = part_name
+ self.chapter = chapter_name
+ self.section = section_name
+ self.subsection = subsection_name
+ self.tests = doctests
+ self._key = None
+
+ @property
+ def key(self):
+ """key of the tests"""
+ return self._key
+
+ @key.setter
+ def key(self, value):
+ assert self._key is None
+ self._key = value
+ return self._key
diff --git a/mathics/doc/documentation/1-Manual.mdoc b/mathics/doc/documentation/1-Manual.mdoc
index 9b3477920..f878f2660 100644
--- a/mathics/doc/documentation/1-Manual.mdoc
+++ b/mathics/doc/documentation/1-Manual.mdoc
@@ -1,14 +1,25 @@
+
-\Mathics---to be pronounced like "Mathematics" without the "emat"---is a general-purpose computer algebra system (CAS). It is meant to be a free, open-source alternative to \Mathematica. It is free both as in "free beer" and as in "freedom". Mathics can be run \Mathics locally, and to facilitate installation of the vast amount of software need to run this, there is a :docker image available on dockerhub: https://hub.docker.com/r/mathicsorg/mathics.
+\Mathics---to be pronounced like "Mathematics" without the "emat"---is
+a :computer algebra
+system:https://en.wikipedia.org/wiki/Computer_algebra_system. It
+is a free, open-source alternative to \Mathematica or the \Wolfram
+Language. However, \Mathics builds around and on top of the Python
+ecosystem of libraries and tools. So in a sense, you can think of it
+as a WMA front-end to the Python ecosystem of tools.
+
+\Mathics is free both as in "free beer" but also, more importantly, as in "freedom". \Mathics can be run locally. But to facilitate installation of the vast amount of software need to run this, there is a :docker image available on dockerhub: https://hub.docker.com/r/mathicsorg/mathics.
+
+The programming language and built-in functions of \Mathics tries to match the \Wolfram Language, which is continually evolving changing.
-The programming language of \Mathics is meant to resemble the \Wolfram Language as much as possible. However, \Mathics is in no way affiliated or supported by \Wolfram. \Mathics will probably never have the power to compete with \Mathematica in industrial applications; it is an alternative though. It also invites community development at all levels.
+\Mathics is in no way affiliated or supported by \Wolfram. \Mathics will probably never have the power to compete with \Mathematica in industrial applications; it is a free alternative though. It also invites community development at all levels.
See the :installation instructions: https://mathics-development-guide.readthedocs.io/en/latest/installing/index.html for the most recent instructions for installing from PyPI, or the source.
-For implementation details see https://mathics-development-guide.readthedocs.io/en/latest/.
+For implementation details, plrease refer to the :Developers Guide:https://mathics-development-guide.readthedocs.io/en/latest/.
-
+
\Mathematica is great, but it a couple of disadvantages.
@@ -21,13 +32,13 @@ The second point some may find and advantage.
However, even if you are willing to pay hundreds of dollars for the software, you would will not be able to see what\'s going on "inside" the program if that is your interest. That\'s what free, open-source, and community-supported software is for!
-\Mathics aims at combining the best of both worlds: the beauty of \Mathematica backed by a free, extensible Python core which includes a rich set of Python tools including:
+\Mathics combines the beauty of \Mathematica implemented in an open-source environment written in Python. The Python ecosystem includes libraries and toos like:
:mpmath: https://mpmath.org/ for floating-point arithmetic with arbitrary precision,
-
:numpy: https://numpy.org/numpy for numeric computation,
+
:NumPy: https://numpy.org for numeric computation,
:SymPy: https://sympy.org for symbolic mathematics, and
-
optionally :SciPy: https://www.scipy.org/ for Scientific calculations.
+
:SciPy: https://www.scipy.org/ for Scientific calculations.
Performance of \Mathics is not, right now, practical in large-scale projects and calculations. However can be used as a tool for exploration and education.
@@ -36,16 +47,16 @@ Performance of \Mathics is not, right now, practical in large-scale projects and
-Some of the features of \Mathics tries to be compatible with Wolfram-Language kernel within the confines of the Python ecosystem.
-
-Given this, it is a powerful functional programming language, driven by pattern matching and rule application.
+Because \Mathics is compatible with Wolfram-Language kernel within the
+confines of the Python ecosystem, it is a powerful functional
+programming language, driven by pattern matching and rule application.
Primitive types include rationals, complex numbers, and arbitrary-precision numbers. Other primitive types such as images or graphs, or NLP come from the various Python libraries that \Mathics uses.
Outside of the "core" \Mathics kernel (which has a only primitive command-line interface), in separate github projects, as add-ons, there is:
-
a Django-based web server
+
a :Django-based web server:https://pypi.org/project/Mathics-Django/
a command-line interface using either prompt-toolkit, or GNU Readline
a :Mathics3 module for Graphs:https://pypi.org/project/pymathics-graph/ (via :NetworkX:https://networkx.org/),
a :Mathics3 module for NLP:https://pypi.org/project/pymathics-natlang/ (via :nltk:https://www.nltk.org/, :spacy:https://spacy.io/, and others)
@@ -61,7 +72,7 @@ After that, Angus Griffith took over primary leadership and rewrote the parser t
A :docker image of the v.9 release: https://hub.docker.com/r/arkadi/mathics can be found on dockerhub.
-Around 2017, the project was largely abandoned in its largely Python 2.7 state, with support for Python 3.2-3.5 via six.
+Around 2017, the project was largely abandoned in its largely Python 2.7 state, with some support for Python 3.2-3.5 via six.
Subsequently, around mid 2020, it was picked up by the current developers. A list of authors and contributors can be found in the
:AUTHORS.txt:
@@ -93,7 +104,9 @@ See :The Mathics3 Developer Guide:https://mathics-development-guide.readthe
The following sections are introductions to the basic principles of the language of \Mathics. A few examples and functions are presented. Only their most common usages are listed; for a full description of a Symbols possible arguments, options, etc., see its entry in the Reference of Built-in Symbols.
-However if you google for "Mathematica Tutorials" you will find easily dozens of other tutorials which are applicable. Be warned though that \Mathics does not yet offer the full range and features and capabilities of \Mathematica.
+However if you google for "Mathematica Tutorials" you will find easily dozens of other tutorials which are applicable. For example, see :An Elementary Introduction to the Wolfram Language:https://www.wolfram.com/language/elementary-introduction/. In the :docker image that we supply:https://hub.docker.com/r/mathicsorg/mathics, you can load "workspaces" containing the examples described in the chapters of this introduction.
+
+Be warned though that \Mathics does not yet offer the full range and features and capabilities of \Mathematica.
\Mathics can be used to calculate basic stuff:
@@ -168,29 +181,41 @@ Of course, \Mathics has complex numbers:
= 5
\Mathics can operate with pretty huge numbers:
- >> 100!
- = 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
+ >> 55! (* Also known as Factorial[55] *)
+ = 12696403353658275925965100847566516959580321051449436762275840000000000000
+
+We could easily increase use a number larger than 55, but the digits will just run off the page.
-('!' denotes the factorial function.)
The precision of numerical evaluation can be set:
>> N[Pi, 30]
= 3.14159265358979323846264338328
-Division by zero is forbidden:
+Division by zero gives an error:
>> 1 / 0
: Infinite expression 1 / 0 encountered.
= ComplexInfinity
-Other expressions involving 'Infinity' are evaluated:
+But zero division returns value :'ComplexInfinity':/doc/reference-of-built-in-symbols/integer-and-number-theoretical-functions/mathematical-constants/complexinfinity and that can be used as a value:
+
+ >> Cos[ComplexInfinity]
+ = Indeterminate
+
+'ComplexInfinity' is a shorthand though for 'DirectedInfinty[]'.
+
+Similarly, expressions using :'Infinity':/doc/reference-of-built-in-symbols/integer-and-number-theoretical-functions/mathematical-constants/complexinfinity as a value are allowed and are evaluated:
>> Infinity + 2 Infinity
= Infinity
-In contrast to combinatorial belief, '0^0' is undefined:
+There is also the value, :'Indeterminate':/doc/reference-of-built-in-symbols/integer-and-number-theoretical-functions/mathematical-constants/indeterminate:
+
>> 0 ^ 0
: Indeterminate expression 0 ^ 0 encountered.
= Indeterminate
+
+
@@ -216,7 +243,8 @@ The relative uncertainty of '3.1416`3' is 10^-3. It is numerically equivalent, i
>> 3.1416`3 == 3.1413`4
= True
-We can get the precision of the number by using the \Mathics Built-in function :'Precision': /doc/reference-of-built-in-symbols/atomic-elements-of-expressions/representation-of-numbers/precision/:
+
+We can get the precision of the number by using the \Mathics Built-in function :'Precision': /doc/reference-of-built-in-symbols/atomic-elements-of-expressions/representation-of-numbers/precision:
>> Precision[3.1413`4]
= 4.
@@ -226,14 +254,16 @@ While 3.1419 not the closest approximation to Pi in 4 digits after the decimal p
>> Pi == 3.141987654321`3
= True
-The absolute accuracy of a number, is set by adding a two RawBackquotes '``' and the number digits.
+The absolute accuracy of a number, is set by adding a two RawBackquotes '``' and the number digits.
For example:
>> 13.1416``4
= 13.142
-is a number having a absolute uncertainty of 10^-4. This number is numerically equivalent to '13.1413``4':
+is a number having an absolute uncertainty of $10^-4$.
+
+This number is numerically equivalent to '13.1413``4':
>> 13.1416``4 == 13.1413``4
= True
@@ -664,7 +694,7 @@ Pure functions are very handy when functions are used only locally, e.g., when c
>> # ^ 2 & /@ Range[5]
= {1, 4, 9, 16, 25}
-Sort according to the second part of a list:
+Sort using the second element of a list as a key:
>> Sort[{{x, 10}, {y, 2}, {z, 5}}, #1[[2]] < #2[[2]] &]
= {{y, 2}, {z, 5}, {x, 10}}
@@ -983,6 +1013,7 @@ Colors can be added in the list of graphics primitives to change the drawing col
'GrayLevel[$l$]'
specifies a color using a gray level.
+
All components range from 0 to 1. Each color function can be supplied with an additional argument specifying the desired opacity ("alpha") of the color. There are many predefined colors, such as 'Black', 'White', 'Red', 'Green', 'Blue', etc.
>> Graphics[{Red, Disk[]}]
@@ -1068,7 +1099,7 @@ Three-dimensional plots are supported as well:
-
+
Let\'s sketch the function
>> f[x_] := 4 x / (x ^ 2 + 3 x + 5)
@@ -1216,6 +1247,7 @@ We want to combine 'Dice' objects using the '+' operator:
>> Dice[a___] + Dice[b___] ^:= Dice[Sequence @@ {a, b}]
The '^:=' ('UpSetDelayed') tells \Mathics to associate this rule with 'Dice' instead of 'Plus'.
+
'Plus' is protected---we would have to unprotect it first:
>> Dice[a___] + Dice[b___] := Dice[Sequence @@ {a, b}]
: Tag Plus in Dice[a___] + Dice[b___] is Protected.
diff --git a/mathics/doc/gather.py b/mathics/doc/gather.py
new file mode 100644
index 000000000..88d01e2b9
--- /dev/null
+++ b/mathics/doc/gather.py
@@ -0,0 +1,374 @@
+# -*- coding: utf-8 -*-
+"""
+Gather module information
+
+Functions used to build the reference sections from module information.
+
+"""
+
+import importlib
+import os.path as osp
+import pkgutil
+from os import listdir
+from types import ModuleType
+from typing import Tuple, Union
+
+from mathics.core.builtin import Builtin, check_requires_list
+from mathics.core.util import IS_PYPY
+from mathics.doc.doc_entries import DocumentationEntry
+from mathics.doc.structure import DocChapter, DocGuideSection, DocSection, DocSubsection
+
+
+def check_installed(src: Union[ModuleType, Builtin]) -> bool:
+ """Check if the required libraries"""
+ required_libs = getattr(src, "requires", [])
+ return check_requires_list(required_libs) if required_libs else True
+
+
+def filter_toplevel_modules(module_list):
+ """
+ Keep just the modules at the top level.
+ """
+ if len(module_list) == 0:
+ return module_list
+
+ modules_and_levels = sorted(
+ ((module.__name__.count("."), module) for module in module_list),
+ key=lambda x: x[0],
+ )
+ top_level = modules_and_levels[0][0]
+ return (entry[1] for entry in modules_and_levels if entry[0] == top_level)
+
+
+def gather_docs_from_files(documentation, path):
+ """
+ Load documentation from files in path
+ """
+ # First gather data from static XML-like files. This constitutes "Part 1" of the
+ # documentation.
+ files = listdir(path)
+ files.sort()
+
+ chapter_order = 0
+ for file in files:
+ part_title = file[2:]
+ if part_title.endswith(".mdoc"):
+ part_title = part_title[: -len(".mdoc")]
+ # If the filename start with a number, then is a main part. Otherwise
+ # is an appendix.
+ is_appendix = not file[0].isdigit()
+ chapter_order = documentation.load_part_from_file(
+ osp.join(path, file),
+ part_title,
+ chapter_order,
+ is_appendix,
+ )
+
+
+def gather_reference_part(documentation, title, modules, builtins_by_module):
+ """
+ Build a part from a title, a list of modules and information
+ of builtins by modules.
+ """
+ part_class = documentation.part_class
+ reference_part = part_class(documentation, title, True)
+ modules = filter_toplevel_modules(modules)
+ for module in sorted_modules(modules):
+ if skip_module_doc(module):
+ continue
+ chapter = doc_chapter(module, reference_part, builtins_by_module)
+ if chapter is None:
+ continue
+ # reference_part.chapters.append(chapter)
+ return reference_part
+
+
+def doc_chapter(module, part, builtins_by_module):
+ """
+ Build documentation structure for a "Chapter" - reference section which
+ might be a Mathics Module.
+ """
+ # TODO: reformulate me in a way that symbols are always translated to
+ # sections, and guide sections do not contain subsections.
+ documentation = part.documentation if part else None
+ chapter_class = documentation.chapter_class if documentation else DocChapter
+ doc_class = documentation.doc_class if documentation else DocumentationEntry
+ title, text = get_module_doc(module)
+ chapter = chapter_class(part, title, doc_class(text, title, None))
+ part.chapters.append(chapter)
+
+ assert len(chapter.sections) == 0
+
+ # visited = set(sec.title for sec in symbol_sections)
+ # If the module is a package, add the guides and symbols from the submodules
+ if module.__file__.endswith("__init__.py"):
+ guide_sections, symbol_sections = gather_guides_and_sections(
+ chapter, module, builtins_by_module
+ )
+ chapter.guide_sections.extend(guide_sections)
+
+ for sec in symbol_sections:
+ if sec.title in visited:
+ print(sec.title, "already visited. Skipped.")
+ else:
+ visited.add(sec.title)
+ chapter.sections.append(sec)
+ else:
+ symbol_sections = gather_sections(chapter, module, builtins_by_module)
+ chapter.sections.extend(symbol_sections)
+
+ return chapter
+
+
+def gather_sections(chapter, module, builtins_by_module, section_class=None) -> list:
+ """Build a list of DocSections from a "top-level" module"""
+ symbol_sections = []
+ if skip_module_doc(module):
+ return []
+
+ part = chapter.part if chapter else None
+ documentation = part.documentation if part else None
+ if section_class is None:
+ section_class = documentation.section_class if documentation else DocSection
+
+ # TODO: Check the reason why for the module
+ # `mathics.builtin.numbers.constants`
+ # `builtins_by_module` has two copies of `Khinchin`.
+ # By now, we avoid the repetition by
+ # converting the entries into `set`s.
+ #
+ visited = set()
+ for symbol_instance in builtins_by_module[module.__name__]:
+ if skip_doc(symbol_instance, module):
+ continue
+ default_contexts = ("System`", "Pymathics`")
+ title = symbol_instance.get_name(
+ short=(symbol_instance.context in default_contexts)
+ )
+ if title in visited:
+ continue
+ visited.add(title)
+ text = symbol_instance.__doc__
+ operator = symbol_instance.get_operator()
+ installed = check_installed(symbol_instance)
+ summary_text = symbol_instance.summary_text
+ section = section_class(
+ chapter,
+ title,
+ text,
+ operator,
+ installed,
+ summary_text=summary_text,
+ )
+ assert (
+ section not in symbol_sections
+ ), f"{section.title} already in {module.__name__}"
+ symbol_sections.append(section)
+
+ return symbol_sections
+
+
+def gather_subsections(chapter, section, module, builtins_by_module) -> list:
+ """Build a list of DocSubsections from a "top-level" module"""
+
+ part = chapter.part if chapter else None
+ documentation = part.documentation if part else None
+ section_class = documentation.subsection_class if documentation else DocSubsection
+
+ def section_function(
+ chapter,
+ title,
+ text,
+ operator=None,
+ installed=True,
+ in_guide=False,
+ summary_text="",
+ ):
+ return section_class(
+ chapter, section, title, text, operator, installed, in_guide, summary_text
+ )
+
+ return gather_sections(chapter, module, builtins_by_module, section_function)
+
+
+def gather_guides_and_sections(chapter, module, builtins_by_module):
+ """
+ Look at the submodules in module, and produce the guide sections
+ and sections.
+ """
+ guide_sections = []
+ symbol_sections = []
+ if skip_module_doc(module):
+ return guide_sections, symbol_sections
+
+ if not module.__file__.endswith("__init__.py"):
+ return guide_sections, symbol_sections
+
+ # Determine the class for sections and guide sections
+ part = chapter.part if chapter else None
+ documentation = part.documentation if part else None
+ guide_class = (
+ documentation.guide_section_class if documentation else DocGuideSection
+ )
+
+ # Loop over submodules
+ docpath = f"/doc/{chapter.part.slug}/{chapter.slug}/"
+
+ for sub_module in submodules(module):
+ if skip_module_doc(sub_module):
+ continue
+
+ title, text = get_module_doc(sub_module)
+ installed = check_installed(sub_module)
+
+ guide_section = guide_class(
+ chapter=chapter,
+ title=title,
+ text=text,
+ submodule=sub_module,
+ installed=installed,
+ )
+
+ submodule_symbol_sections = gather_subsections(
+ chapter, guide_section, sub_module, builtins_by_module
+ )
+
+ guide_section.subsections.extend(submodule_symbol_sections)
+ guide_sections.append(guide_section)
+
+ # TODO, handle recursively the submodules.
+ # Here there I see two options:
+ # if sub_module.__file__.endswith("__init__.py"):
+ # (deeper_guide_sections,
+ # deeper_symbol_sections) = gather_guides_and_sections(chapter,
+ # sub_module, builtins_by_module)
+ # symbol_sections.extend(deeper_symbol_sections)
+ # guide_sections.extend(deeper_guide_sections)
+ return guide_sections, []
+
+
+def get_module_doc(module: ModuleType) -> Tuple[str, str]:
+ """
+ Determine the title and text associated to the documentation
+ of a module.
+ If the module has a module docstring, extract the information
+ from it. If not, pick the title from the name of the module.
+ """
+ doc = module.__doc__
+ if doc is not None:
+ doc = doc.strip()
+ if doc:
+ title = doc.splitlines()[0]
+ text = "\n".join(doc.splitlines()[1:])
+ else:
+ title = module.__name__
+ for prefix in ("mathics.builtin.", "mathics.optional.", "pymathics."):
+ if title.startswith(prefix):
+ title = title[len(prefix) :]
+ title = title.capitalize()
+ text = ""
+ return title, text
+
+
+def get_submodule_names(obj) -> list:
+ """Many builtins are organized into modules which, from a documentation
+ standpoint, are like Mathematica Online Guide Docs.
+
+ "List Functions", "Colors", or "Distance and Similarity Measures"
+ are some examples Guide Documents group group various Builtin Functions,
+ under submodules relate to that general classification.
+
+ Here, we want to return a list of the Python modules under a "Guide Doc"
+ module.
+
+ As an example of a "Guide Doc" and its submodules, consider the
+ module named mathics.builtin.colors. It collects code and documentation pertaining
+ to the builtin functions that would be found in the Guide documentation for "Colors".
+
+ The `mathics.builtin.colors` module has a submodule
+ `mathics.builtin.colors.named_colors`.
+
+ The builtin functions defined in `named_colors` then are those found in the
+ "Named Colors" group of the "Colors" Guide Doc.
+
+ So in this example then, in the list the modules returned for
+ Python module `mathics.builtin.colors` would be the
+ `mathics.builtin.colors.named_colors` module which contains the
+ definition and docs for the "Named Colors" Mathics Builtin
+ Functions.
+ """
+ modpkgs = []
+ if hasattr(obj, "__path__"):
+ for _, modname, __ in pkgutil.iter_modules(obj.__path__):
+ modpkgs.append(modname)
+ modpkgs.sort()
+ return modpkgs
+
+
+def get_doc_name_from_module(module) -> str:
+ """
+ Get the title associated to the module.
+ If the module has a docstring, pick the name from
+ its first line (the title). Otherwise, use the
+ name of the module.
+ """
+ name = "???"
+ if module.__doc__:
+ lines = module.__doc__.strip()
+ if not lines:
+ name = module.__name__
+ else:
+ name = lines.split("\n")[0]
+ return name
+
+
+def skip_doc(instance, module="") -> bool:
+ """Returns True if we should skip the docstring extraction."""
+ if not isinstance(module, str):
+ module = module.__name__ if module else ""
+
+ if type(instance).__name__.endswith("Box"):
+ return True
+ if hasattr(instance, "no_doc") and instance.no_doc:
+ return True
+
+ # Just include the builtins defined in the module.
+ if module:
+ if module != instance.__class__.__module__:
+ return True
+ return False
+
+
+def skip_module_doc(module, must_be_skipped=frozenset()) -> bool:
+ """True if the module should not be included in the documentation"""
+ if IS_PYPY and module.__name__ == "builtins":
+ return True
+ return (
+ module.__doc__ is None
+ or module in must_be_skipped
+ or module.__name__.split(".")[0] not in ("mathics", "pymathics")
+ or hasattr(module, "no_doc")
+ and module.no_doc
+ )
+
+
+def sorted_modules(modules) -> list:
+ """Return modules sorted by the ``sort_order`` attribute if that
+ exists, or the module's name if not."""
+ return sorted(
+ modules,
+ key=lambda module: module.sort_order
+ if hasattr(module, "sort_order")
+ else module.__name__,
+ )
+
+
+def submodules(package):
+ """Generator of the submodules in a package"""
+ package_folder = package.__file__[: -len("__init__.py")]
+ for _, module_name, __ in pkgutil.iter_modules([package_folder]):
+ try:
+ module = importlib.import_module(package.__name__ + "." + module_name)
+ except Exception:
+ continue
+ yield module
diff --git a/mathics/doc/latex/.gitignore b/mathics/doc/latex/.gitignore
index 60872e954..2a22fb898 100644
--- a/mathics/doc/latex/.gitignore
+++ b/mathics/doc/latex/.gitignore
@@ -1,5 +1,6 @@
/core-version.tex
/doc_latex_data.pcl
+/documentation.log
/documentation.tex
/documentation.tex-before-sed
/images/
diff --git a/mathics/doc/latex/1-Manual.mdoc b/mathics/doc/latex/1-Manual.mdoc
new file mode 120000
index 000000000..f23c9aa64
--- /dev/null
+++ b/mathics/doc/latex/1-Manual.mdoc
@@ -0,0 +1 @@
+../documentation/1-Manual.mdoc
\ No newline at end of file
diff --git a/mathics/doc/latex/Makefile b/mathics/doc/latex/Makefile
index 0b0fdac95..26e5ac140 100644
--- a/mathics/doc/latex/Makefile
+++ b/mathics/doc/latex/Makefile
@@ -17,7 +17,7 @@ all doc texdoc: mathics.pdf
#: Create internal Document Data from .mdoc and Python builtin module docstrings
doc-data $(DOCTEST_LATEX_DATA_PCL):
- (cd ../.. && MATHICS_CHARACTER_ENCODING="UTF-8" $(PYTHON) docpipeline.py --output --keep-going --want-sorting $(MATHICS3_MODULE_OPTION))
+ (cd ../.. && MATHICS_CHARACTER_ENCODING="UTF-8" $(PYTHON) docpipeline.py --output --keep-going $(MATHICS3_MODULE_OPTION))
#: Build mathics PDF
mathics.pdf: mathics.tex documentation.tex logo-text-nodrop.pdf logo-heptatom.pdf version-info.tex $(DOCTEST_LATEX_DATA_PCL)
@@ -37,7 +37,7 @@ logo-heptatom.pdf logo-text-nodrop.pdf:
(cd .. && $(BASH) ./images.sh)
#: The build of the documentation which is derived from docstrings in the Python code and doctest data
-documentation.tex: $(DOCTEST_LATEX_DATA_PCL)
+documentation.tex: $(DOCTEST_LATEX_DATA_PCL) 1-Manual.mdoc
$(PYTHON) ./doc2latex.py $(MATHICS3_MODULE_OPTION) && $(BASH) ./sed-hack.sh
#: Same as mathics.pdf
@@ -48,7 +48,7 @@ clean:
rm -f mathics.asy mathics.aux mathics.idx mathics.log mathics.mtc mathics.mtc* mathics.out mathics.toc || true
rm -f test-mathics.aux test-mathics.idx test-mathics.log test-mathics.mtc test-mathics.mtc* test-mathics.out test-mathics.toc || true
rm -f mathics.fdb_latexmk mathics.ilg mathics.ind mathics.maf mathics.pre || true
- rm -f mathics_*.* || true
+ rm -f mathics-*.* || true
rm -f mathics-test.asy mathics-test.aux mathics-test.idx mathics-test.log mathics-test.mtc mathicsest.mtc* mathics-test.out mathics-test.toc || true
rm -f documentation.tex $(DOCTEST_LATEX_DATA_PCL) || true
rm -f mathics.pdf mathics.dvi test-mathics.pdf test-mathics.dvi || true
diff --git a/mathics/doc/latex/doc2latex.py b/mathics/doc/latex/doc2latex.py
index 43371863a..85ad0b100 100755
--- a/mathics/doc/latex/doc2latex.py
+++ b/mathics/doc/latex/doc2latex.py
@@ -17,7 +17,6 @@
import os
import os.path as osp
-import pickle
import subprocess
import sys
from argparse import ArgumentParser
@@ -32,6 +31,7 @@
from mathics.core.definitions import Definitions
from mathics.core.load_builtin import import_and_load_builtins
from mathics.doc.latex_doc import LaTeXMathicsDocumentation
+from mathics.doc.utils import load_doctest_data, open_ensure_dir
from mathics.eval.pymathics import PyMathicsLoadException, eval_LoadModule
# Global variables
@@ -66,45 +66,6 @@ def read_doctest_data(quiet=False) -> Optional[Dict[tuple, dict]]:
return
-def load_doctest_data(data_path, quiet=False) -> Dict[tuple, dict]:
- """
- Read doctest information from PCL file and return this.
-
- The return value is a dictionary of test results. The key is a tuple
- of:
- * Part name,
- * Chapter name,
- * [Guide Section name],
- * Section name,
- * Subsection name,
- * test number
- and the value is a dictionary of a Result.getdata() dictionary.
- """
- if not quiet:
- print(f"Loading LaTeX internal data from {data_path}")
- with open_ensure_dir(data_path, "rb") as doc_data_fp:
- return pickle.load(doc_data_fp)
-
-
-def open_ensure_dir(f, *args, **kwargs):
- try:
- return open(f, *args, **kwargs)
- except (IOError, OSError):
- d = osp.dirname(f)
- if d and not osp.exists(d):
- os.makedirs(d)
- return open(f, *args, **kwargs)
-
-
-def print_and_log(*args):
- global logfile
- a = [a.decode("utf-8") if isinstance(a, bytes) else a for a in args]
- string = "".join(a)
- print(string)
- if logfile:
- logfile.write(string)
-
-
def get_versions():
def try_cmd(cmd_list: tuple, stdout_or_stderr: str) -> str:
status = subprocess.run(cmd_list, capture_output=True)
@@ -157,7 +118,6 @@ def write_latex(
def main():
-
global logfile
parser = ArgumentParser(description="Mathics test suite.", add_help=False)
diff --git a/mathics/doc/latex/mathics.tex b/mathics/doc/latex/mathics.tex
index 74d325e45..0462a6bc1 100644
--- a/mathics/doc/latex/mathics.tex
+++ b/mathics/doc/latex/mathics.tex
@@ -47,7 +47,7 @@
\usepackage[k-tight]{minitoc}
\setlength{\mtcindent}{0pt}
\mtcsetformat{minitoc}{tocrightmargin}{2.55em plus 1fil}
-\newcommand{\multicolumnmtc}{3}
+\newcommand{\multicolumnmtc}{2}
\makeatletter
\let\SV@mtc@verse\mtc@verse
\let\SV@endmtc@verse\endmtc@verse
@@ -69,10 +69,13 @@
\includegraphics[height=0.08125\linewidth]{logo-text-nodrop.pdf}
\\[.5em]
{\LARGE\color{subtitle}\textit{\textmd{A free, open-source alternative to Mathematica}}}
- \par\textmd{\Large Mathics Core Version \MathicsCoreVersion}
+ \par\textmd{\Large Mathics3 Core Version \MathicsCoreVersion}
}
\author{The Mathics3 Team}
+% Since we are using a XML input we have need to specify missed hyphenation
+% in LaTeX sich as here:
+\hyphenation{eco-system}
% Fix unicode mappings for listings
% http://tex.stackexchange.com/questions/39640/typesetting-utf8-listings-with-german-umlaute
@@ -133,19 +136,21 @@
\newcommand{\chapterstart}{
}
\newcommand{\chaptersections}{
- \minitoc
- %\begin{multicols}{2}
+ \begin{sloppypar}
+ \minitoc
+ \end{sloppypar}
}
\newcommand{\chapterend}{
%\end{multicols}
}
\newcommand{\referencestart}{
-\setcounter{chapter}{0}
%\def\thechapter{\Roman{chapter}}
\renewcommand{\chaptersections}{
- \minitoc
+ \begin{sloppypar}
+ \minitoc
%\begin{multicols*}{2}
+ \end{sloppypar}
}
\renewcommand{\chapterend}{
%\end{multicols*}
@@ -247,7 +252,7 @@
\newcommand{\console}[1]{\hbadness=10000{\ttfamily #1}}
\setlength{\parindent}{0mm}
-\setlength{\parskip}{1pt}
+\setlength{\parskip}{10pt}
\setlength{\mathindent}{0em}
@@ -269,6 +274,7 @@
\setcounter{tocdepth}{0}
\tableofcontents
+
\lstset{
% inputencoding=utf8,
extendedchars=true,
diff --git a/mathics/doc/latex/sed-hack.sh b/mathics/doc/latex/sed-hack.sh
index a8e213653..b72462d5d 100755
--- a/mathics/doc/latex/sed-hack.sh
+++ b/mathics/doc/latex/sed-hack.sh
@@ -48,3 +48,11 @@ sed -i -e "s/°/\\\\degree{}/g" documentation.tex
# from Properties in a Section heading.
# TODO: figure out how to fix that bug.
sed -i -e "s/Propertie\\\\/Properties\\\\/g" documentation.tex
+
+# TODO: find the right LaTeX representation for these characters
+sed -i -e 's/ç/\\c{c}/g' documentation.tex
+sed -i -e 's/ñ/\\~n/g' documentation.tex
+sed -i -e 's/ê/\\^e/g' documentation.tex
+sed -i -e 's/≖/=||=/g' documentation.tex
+sed -i -e 's/⇒/==>/g' documentation.tex
+sed -i -e "s/é/\\\'e/g" documentation.tex
diff --git a/mathics/doc/latex_doc.py b/mathics/doc/latex_doc.py
index 8dc6a53e0..61eceb818 100644
--- a/mathics/doc/latex_doc.py
+++ b/mathics/doc/latex_doc.py
@@ -1,18 +1,15 @@
"""
This code is the LaTeX-specific part of the homegrown sphinx documentation.
-FIXME: Ditch this and hook into sphinx.
+FIXME: Ditch home-grown and lame parsing and hook into sphinx.
"""
import re
-from os import getenv
+from typing import Optional
-from mathics import settings
-from mathics.core.evaluation import Message, Print
-from mathics.doc.common_doc import (
+from mathics.doc.doc_entries import (
CONSOLE_RE,
DL_ITEM_RE,
DL_RE,
- END_LINE_SENTINAL,
HYPERTEXT_RE,
IMG_PNG_RE,
IMG_RE,
@@ -24,27 +21,29 @@
QUOTATIONS_RE,
REF_RE,
SPECIAL_COMMANDS,
- SUBSECTION_END_RE,
- SUBSECTION_RE,
- TESTCASE_OUT_RE,
- DocChapter,
- DocPart,
- DocSection,
DocTest,
DocTests,
DocText,
- Documentation,
- XMLDoc,
- gather_tests,
+ DocumentationEntry,
get_results_by_test,
post_sub,
pre_sub,
+)
+from mathics.doc.structure import (
+ SUBSECTION_END_RE,
+ SUBSECTION_RE,
+ DocChapter,
+ DocGuideSection,
+ DocPart,
+ DocSection,
+ DocSubsection,
+ Documentation,
+ MathicsMainDocumentation,
sorted_chapters,
)
-from mathics.doc.utils import slugify
# We keep track of the number of \begin{asy}'s we see so that
-# we can assocation asymptote file numbers with where they are
+# we can association asymptote file numbers with where they are
# in the document
next_asy_number = 1
@@ -132,7 +131,7 @@ def repl(match):
text = text[:-1] + r"\ "
return "\\code{\\lstinline%s%s%s}" % (escape_char, text, escape_char)
else:
- # treat double '' literaly
+ # treat double '' literally
return "''"
text = MATHICS_RE.sub(repl, text)
@@ -256,6 +255,8 @@ def repl_hypertext(match) -> str:
# then is is a link to a section
# in this manual, so use "\ref" rather than "\href'.
if content.find("/doc/") == 0:
+ slug = "/".join(content.split("/")[2:]).rstrip("/")
+ return "%s \\ref{%s}" % (text, latex_label_safe(slug))
slug = "/".join(content.split("/")[2:]).rstrip("/")
return "%s of section~\\ref{%s}" % (text, latex_label_safe(slug))
else:
@@ -272,8 +273,7 @@ def repl_console(match):
content = content.replace(r"\$", "$")
if tag == "con":
return "\\console{%s}" % content
- else:
- return "\\begin{lstlisting}\n%s\n\\end{lstlisting}" % content
+ return "\\begin{lstlisting}\n%s\n\\end{lstlisting}" % content
text = CONSOLE_RE.sub(repl_console, text)
@@ -295,7 +295,7 @@ def repl_italic(match):
# text = LATEX_BETWEEN_ASY_RE.sub(repl_asy, text)
def repl_subsection(match):
- return "\n\\subsection*{%s}\n" % match.group(1)
+ return "\n\\subsection{%s}\n" % match.group(1)
text = SUBSECTION_RE.sub(repl_subsection, text)
text = SUBSECTION_END_RE.sub("", text)
@@ -423,7 +423,7 @@ def repl_out(match):
return "\\begin{%s}%s\\end{%s}" % (tag, content, tag)
def repl_inline_end(match):
- """Prevent linebreaks between inline code and sentence delimeters"""
+ """Prevent linebreaks between inline code and sentence delimiters"""
code = match.group("all")
if code[-2] == "}":
@@ -479,72 +479,7 @@ class LaTeXDocTest(DocTest):
"""
def __init__(self, index, testcase, key_prefix=None):
- def strip_sentinal(line):
- """Remove END_LINE_SENTINAL from the end of a line if it appears.
-
- Some editors like to strip blanks at the end of a line.
- Since the line ends in END_LINE_SENTINAL which isn't blank,
- any blanks that appear before will be preserved.
-
- Some tests require some lines to be blank or entry because
- Mathics output can be that way
- """
- if line.endswith(END_LINE_SENTINAL):
- line = line[: -len(END_LINE_SENTINAL)]
-
- # Also remove any remaining trailing blanks since that
- # seems *also* what we want to do.
- return line.strip()
-
- self.index = index
- self.result = None
- self.outs = []
-
- # Private test cases are executed, but NOT shown as part of the docs
- self.private = testcase[0] == "#"
-
- # Ignored test cases are NOT executed, but shown as part of the docs
- # Sandboxed test cases are NOT executed if environment SANDBOX is set
- if testcase[0] == "X" or (testcase[0] == "S" and getenv("SANDBOX", False)):
- self.ignore = True
- # substitute '>' again so we get the correct formatting
- testcase[0] = ">"
- else:
- self.ignore = False
-
- self.test = strip_sentinal(testcase[1])
-
- self.key = None
- if key_prefix:
- self.key = tuple(key_prefix + (index,))
- outs = testcase[2].splitlines()
- for line in outs:
- line = strip_sentinal(line)
- if line:
- if line.startswith("."):
- text = line[1:]
- if text.startswith(" "):
- text = text[1:]
- text = "\n" + text
- if self.result is not None:
- self.result += text
- elif self.outs:
- self.outs[-1].text += text
- continue
-
- match = TESTCASE_OUT_RE.match(line)
- if not match:
- continue
- symbol, text = match.group(1), match.group(2)
- text = text.strip()
- if symbol == "=":
- self.result = text
- elif symbol == ":":
- out = Message("", "", text)
- self.outs.append(out)
- elif symbol == "|":
- out = Print(text)
- self.outs.append(out)
+ super().__init__(index, testcase, key_prefix)
def __str__(self):
return self.test
@@ -593,76 +528,20 @@ def latex(self, doc_data: dict) -> str:
return text
-class LaTeXDocumentation(Documentation):
- def __str__(self):
- return "\n\n\n".join(str(part) for part in self.parts)
-
- def get_section(self, part_slug, chapter_slug, section_slug):
- part = self.parts_by_slug.get(part_slug)
- if part:
- chapter = part.chapters_by_slug.get(chapter_slug)
- if chapter:
- return chapter.sections_by_slug.get(section_slug)
- return None
-
- def latex(
- self,
- doc_data: dict,
- quiet=False,
- filter_parts=None,
- filter_chapters=None,
- filter_sections=None,
- ) -> str:
- """Render self as a LaTeX string and return that.
-
- `output` is not used here but passed along to the bottom-most
- level in getting expected test results.
- """
- parts = []
- appendix = False
- for part in self.parts:
- if filter_parts:
- if part.title not in filter_parts:
- continue
- text = part.latex(
- doc_data,
- quiet,
- filter_chapters=filter_chapters,
- filter_sections=filter_sections,
- )
- if part.is_appendix and not appendix:
- appendix = True
- text = "\n\\appendix\n" + text
- parts.append(text)
- result = "\n\n".join(parts)
- result = post_process_latex(result)
- return result
-
-
-class LaTeXDoc(XMLDoc):
- """A class to hold our internal XML-like format data.
+class LaTeXDocumentationEntry(DocumentationEntry):
+ """A class to hold our custom XML-like format.
The `latex()` method can turn this into LaTeX.
Mathics core also uses this in getting usage strings (`??`).
"""
- def __init__(self, doc, title, section):
- self.title = title
- if section:
- chapter = section.chapter
- part = chapter.part
- # Note: we elide section.title
- key_prefix = (part.title, chapter.title, title)
- else:
- key_prefix = None
-
- self.rawdoc = doc
- self.items = gather_tests(
- self.rawdoc, LaTeXDocTests, LaTeXDocTest, LaTeXDocText, key_prefix
- )
- return
+ def __init__(self, doc_str: str, title: str, section: Optional[DocSection]):
+ super().__init__(doc_str, title, section)
- def latex(self, doc_data: dict):
+ def latex(self, doc_data: dict) -> str:
+ """
+ Return a LaTeX string representation for this object.
+ """
if len(self.items) == 0:
if hasattr(self, "rawdoc") and len(self.rawdoc) != 0:
# We have text but no tests
@@ -672,45 +551,63 @@ def latex(self, doc_data: dict):
item.latex(doc_data) for item in self.items if not item.is_private()
)
+ def _set_classes(self):
+ """
+ Tells to the initializator of DocumentationEntry
+ the classes to be used to build the items.
+ """
+ self.docTest_collection_class = LaTeXDocTests
+ self.docTest_class = LaTeXDocTest
+ self.docText_class = LaTeXDocText
-class LaTeXMathicsDocumentation(Documentation):
- def __init__(self, want_sorting=False):
- self.doc_chapter_fn = LaTeXDocChapter
- self.doc_dir = settings.DOC_DIR
- self.doc_fn = LaTeXDoc
- self.doc_data_file = settings.get_doctest_latex_data_path(
- should_be_readable=True
- )
- self.doc_guide_section_fn = LaTeXDocGuideSection
- self.doc_part_fn = LaTeXDocPart
- self.doc_section_fn = LaTeXDocSection
- self.doc_subsection_fn = LaTeXDocSubsection
- self.doctest_latex_pcl_path = settings.DOCTEST_LATEX_DATA_PCL
- self.parts = []
- self.parts_by_slug = {}
- self.title = "Overview"
- self.gather_doctest_data()
+class LaTeXMathicsDocumentation(MathicsMainDocumentation):
+ """
+ Subclass of MathicsMainDocumentation which is able to
+ produce a the documentation in LaTeX format.
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.load_documentation_sources()
+
+ def _set_classes(self):
+ """
+ This function tells to the initializator of
+ MathicsMainDocumentation which classes must be used to
+ create the different elements in the hierarchy.
+ """
+ self.chapter_class = LaTeXDocChapter
+ self.doc_class = LaTeXDocumentationEntry
+ self.guide_section_class = LaTeXDocGuideSection
+ self.part_class = LaTeXDocPart
+ self.section_class = LaTeXDocSection
+ self.subsection_class = LaTeXDocSubsection
def latex(
self,
doc_data: dict,
quiet=False,
- filter_parts=None,
- filter_chapters=None,
- filter_sections=None,
+ filter_parts: Optional[str] = None,
+ filter_chapters: Optional[str] = None,
+ filter_sections: Optional[str] = None,
) -> str:
"""Render self as a LaTeX string and return that.
`output` is not used here but passed along to the bottom-most
level in getting expected test results.
"""
+ seen_parts = set()
+ parts_set = None
+ if filter_parts is not None:
+ parts_set = set(filter_parts.split(","))
parts = []
appendix = False
for part in self.parts:
if filter_parts:
if part.title not in filter_parts:
continue
+ seen_parts.add(part.title)
text = part.latex(
doc_data,
quiet,
@@ -721,41 +618,21 @@ def latex(
appendix = True
text = "\n\\appendix\n" + text
parts.append(text)
+ if parts_set == seen_parts:
+ break
+
result = "\n\n".join(parts)
result = post_process_latex(result)
return result
-class LaTeXDocPart(DocPart):
+class LaTeXDocChapter(DocChapter):
def latex(
- self, doc_data: dict, quiet=False, filter_chapters=None, filter_sections=None
+ self, doc_data: dict, quiet=False, filter_sections: Optional[str] = None
) -> str:
- """Render this Part object as LaTeX string and return that.
-
- `output` is not used here but passed along to the bottom-most
- level in getting expected test results.
- """
- if self.is_reference:
- chapter_fn = sorted_chapters
- else:
- chapter_fn = lambda x: x
- result = "\n\n\\part{%s}\n\n" % escape_latex(self.title) + (
- "\n\n".join(
- chapter.latex(doc_data, quiet, filter_sections=filter_sections)
- for chapter in chapter_fn(self.chapters)
- if not filter_chapters or chapter.title in filter_chapters
- )
- )
- if self.is_reference:
- result = "\n\n\\referencestart" + result
- return result
-
-
-class LaTeXDocChapter(DocChapter):
- def latex(self, doc_data: dict, quiet=False, filter_sections=None) -> str:
"""Render this Chapter object as LaTeX string and return that.
- `output` is not used here but passed along to the bottom-most
+ ``output`` is not used here but passed along to the bottom-most
level in getting expected test results.
"""
if not quiet:
@@ -768,13 +645,32 @@ def latex(self, doc_data: dict, quiet=False, filter_sections=None) -> str:
intro,
short,
)
+
+ if self.part.is_reference:
+ sort_section_function = sorted
+ else:
+ sort_section_function = lambda x: x
+
chapter_sections = [
("\n\n\\chapter{%(title)s}\n\\chapterstart\n\n%(intro)s")
% {"title": escape_latex(self.title), "intro": intro},
"\\chaptersections\n",
+ # ####################
"\n\n".join(
section.latex(doc_data, quiet)
- for section in sorted(self.sections)
+ # Here we should use self.all_sections, but for some reason
+ # guidesections are not properly loaded, duplicating
+ # the load of subsections.
+ for section in sorted(self.guide_sections)
+ if not filter_sections or section.title in filter_sections
+ ),
+ # ###################
+ "\n\n".join(
+ section.latex(doc_data, quiet)
+ # Here we should use self.all_sections, but for some reason
+ # guidesections are not properly loaded, duplicating
+ # the load of subsections.
+ for section in sort_section_function(self.sections)
if not filter_sections or section.title in filter_sections
),
"\n\\chapterend\n",
@@ -782,6 +678,35 @@ def latex(self, doc_data: dict, quiet=False, filter_sections=None) -> str:
return "".join(chapter_sections)
+class LaTeXDocPart(DocPart):
+ def __init__(self, doc: "Documentation", title: str, is_reference: bool = False):
+ self.chapter_class = LaTeXDocChapter
+ super().__init__(doc, title, is_reference)
+
+ def latex(
+ self, doc_data: dict, quiet=False, filter_chapters=None, filter_sections=None
+ ) -> str:
+ """Render this Part object as LaTeX string and return that.
+
+ `output` is not used here but passed along to the bottom-most
+ level in getting expected test results.
+ """
+ if self.is_reference:
+ chapter_fn = sorted_chapters
+ else:
+ chapter_fn = lambda x: x
+ result = "\n\n\\part{%s}\n\n" % escape_latex(self.title) + (
+ "\n\n".join(
+ chapter.latex(doc_data, quiet, filter_sections=filter_sections)
+ for chapter in chapter_fn(self.chapters)
+ if not filter_chapters or chapter.title in filter_chapters
+ )
+ )
+ if self.is_reference:
+ result = "\n\n\\referencestart" + result
+ return result
+
+
class LaTeXDocSection(DocSection):
def __init__(
self,
@@ -793,27 +718,9 @@ def __init__(
in_guide=False,
summary_text="",
):
- self.chapter = chapter
- self.in_guide = in_guide
- self.installed = installed
- self.operator = operator
- self.slug = slugify(title)
- self.subsections = []
- self.subsections_by_slug = {}
- self.summary_text = summary_text
- self.title = title
-
- if text.count("
") != text.count("
"):
- raise ValueError(
- "Missing opening or closing
tag in "
- "{} documentation".format(title)
- )
-
- # Needs to come after self.chapter is initialized since
- # XMLDoc uses self.chapter.
- self.doc = LaTeXDoc(text, title, self)
-
- chapter.sections_by_slug[self.slug] = self
+ super().__init__(
+ chapter, title, text, operator, installed, in_guide, summary_text
+ )
def latex(self, doc_data: dict, quiet=False) -> str:
"""Render this Section object as LaTeX string and return that.
@@ -836,18 +743,18 @@ def latex(self, doc_data: dict, quiet=False) -> str:
sections = "\n\n".join(section.latex(doc_data) for section in self.subsections)
slug = f"{self.chapter.part.slug}/{self.chapter.slug}/{self.slug}"
section_string = (
- "\n\n\\section*{%s}{%s}\n" % (title, index)
+ "\n\n\\section{%s}{%s}\n" % (title, index)
+ "\n\\label{%s}" % latex_label_safe(slug)
+ "\n\\sectionstart\n\n"
+ f"{content}"
- + ("\\addcontentsline{toc}{section}{%s}" % title)
+ # + ("\\addcontentsline{toc}{section}{%s}" % title)
+ sections
+ "\\sectionend"
)
return section_string
-class LaTeXDocGuideSection(DocSection):
+class LaTeXDocGuideSection(DocGuideSection):
"""An object for a Documented Guide Section.
A Guide Section is part of a Chapter. "Colors" or "Special Functions"
are examples of Guide Sections, and each contains a number of Sections.
@@ -855,30 +762,14 @@ class LaTeXDocGuideSection(DocSection):
"""
def __init__(
- self, chapter: str, title: str, text: str, submodule, installed: bool = True
+ self,
+ chapter: LaTeXDocChapter,
+ title: str,
+ text: str,
+ submodule,
+ installed: bool = True,
):
- self.chapter = chapter
- self.doc = LaTeXDoc(text, title, None)
- self.in_guide = False
- self.installed = installed
- self.section = submodule
- self.slug = slugify(title)
- self.subsections = []
- self.subsections_by_slug = {}
- self.title = title
-
- # FIXME: Sections never are operators. Subsections can have
- # operators though. Fix up the view and searching code not to
- # look for the operator field of a section.
- self.operator = False
-
- if text.count("
") != text.count("
"):
- raise ValueError(
- "Missing opening or closing
tag in "
- "{} documentation".format(title)
- )
- # print("YYY Adding section", title)
- chapter.sections_by_slug[self.slug] = self
+ super().__init__(chapter, title, text, submodule, installed)
def get_tests(self):
# FIXME: The below is a little weird for Guide Sections.
@@ -895,7 +786,7 @@ def get_tests(self):
for doctests in subsection.items:
yield doctests.get_tests()
- def latex(self, doc_data: dict, quiet=False):
+ def latex(self, doc_data: dict, quiet=False) -> str:
"""Render this Guide Section object as LaTeX string and return that.
`output` is not used here but passed along to the bottom-most
@@ -905,6 +796,7 @@ def latex(self, doc_data: dict, quiet=False):
# The leading spaces help show chapter level.
print(f" Formatting Guide Section {self.title}")
intro = self.doc.latex(doc_data).strip()
+ slug = f"{self.chapter.part.slug}/{self.chapter.slug}/{self.slug}"
if intro:
short = "short" if len(intro) < 300 else ""
intro = "\\begin{guidesectionintro%s}\n%s\n\n\\end{guidesectionintro%s}" % (
@@ -914,16 +806,20 @@ def latex(self, doc_data: dict, quiet=False):
)
guide_sections = [
(
- "\n\n\\section{%(title)s}\n\\sectionstart\n\n%(intro)s"
- "\\addcontentsline{toc}{section}{%(title)s}"
+ "\n\n\\section{%(title)s}\n\\label{%(label)s}\n\\sectionstart\n\n%(intro)s"
+ # "\\addcontentsline{toc}{section}{%(title)s}"
)
- % {"title": escape_latex(self.title), "intro": intro},
+ % {
+ "title": escape_latex(self.title),
+ "label": latex_label_safe(slug),
+ "intro": intro,
+ },
"\n\n".join(section.latex(doc_data) for section in self.subsections),
]
return "".join(guide_sections)
-class LaTeXDocSubsection:
+class LaTeXDocSubsection(DocSubsection):
"""An object for a Documented Subsection.
A Subsection is part of a Section.
"""
@@ -952,33 +848,11 @@ def __init__(
mathics/builtin/colors/__init__.py . In mathics/builtin/colors/named-colors.py we have
the "section" name for the class Read (the subsection) inside it.
"""
+ super().__init__(
+ chapter, section, title, text, operator, installed, in_guide, summary_text
+ )
- self.doc = LaTeXDoc(text, title, section)
- self.chapter = chapter
- self.in_guide = in_guide
- self.installed = installed
- self.operator = operator
-
- self.section = section
- self.slug = slugify(title)
- self.subsections = []
- self.title = title
-
- if in_guide:
- # Tests haven't been picked out yet from the doc string yet.
- # Gather them here.
- self.items = gather_tests(text, LaTeXDocTests, LaTeXDocTest, LaTeXDocText)
- else:
- self.items = []
-
- if text.count("
") != text.count("
"):
- raise ValueError(
- "Missing opening or closing
tag in "
- "{} documentation".format(title)
- )
- self.section.subsections_by_slug[self.slug] = self
-
- def latex(self, doc_data: dict, quiet=False, chapters=None):
+ def latex(self, doc_data: dict, quiet=False, chapters=None) -> str:
"""Render this Subsection object as LaTeX string and return that.
`output` is not used here but passed along to the bottom-most
@@ -1000,10 +874,10 @@ def latex(self, doc_data: dict, quiet=False, chapters=None):
slug = f"{self.chapter.part.slug}/{self.chapter.slug}/{self.section.slug}/{self.slug}"
section_string = (
- "\n\n\\subsection*{%(title)s}%(index)s\n"
+ "\n\n\\subsection{%(title)s}%(index)s\n"
+ "\n\\label{%s}" % latex_label_safe(slug)
+ "\n\\subsectionstart\n\n%(content)s"
- "\\addcontentsline{toc}{subsection}{%(title)s}"
+ # "\\addcontentsline{toc}{subsection}{%(title)s}"
"%(sections)s"
"\\subsectionend"
) % {
@@ -1018,7 +892,7 @@ def latex(self, doc_data: dict, quiet=False, chapters=None):
class LaTeXDocTests(DocTests):
- def latex(self, doc_data: dict):
+ def latex(self, doc_data: dict) -> str:
if len(self.tests) == 0:
return "\n"
@@ -1033,5 +907,10 @@ def latex(self, doc_data: dict):
class LaTeXDocText(DocText):
- def latex(self, doc_data):
+ """
+ Class to hold some (non-test) LaTeX text.
+ """
+
+ def latex(self, doc_data: dict) -> str:
+ """Escape the text as LaTeX and return that string."""
return escape_latex(self.text)
diff --git a/mathics/doc/structure.py b/mathics/doc/structure.py
new file mode 100644
index 000000000..9135c5702
--- /dev/null
+++ b/mathics/doc/structure.py
@@ -0,0 +1,707 @@
+# -*- coding: utf-8 -*-
+"""
+Structural elements of Mathics Documentation
+
+This module contains the classes representing the Mathics documentation structure,
+and extended regular expressions used to parse it.
+
+"""
+import logging
+import re
+from os import environ
+from typing import Iterator, List, Optional
+
+from mathics import settings
+from mathics.core.builtin import check_requires_list
+from mathics.core.load_builtin import (
+ builtins_by_module as global_builtins_by_module,
+ mathics3_builtins_modules,
+)
+from mathics.doc.doc_entries import DocumentationEntry, Tests, filter_comments
+from mathics.doc.utils import slugify
+from mathics.eval.pymathics import pymathics_builtins_by_module, pymathics_modules
+
+CHAPTER_RE = re.compile('(?s)(.*?)')
+SECTION_RE = re.compile('(?s)(.*?)(.*?)')
+SUBSECTION_RE = re.compile('(?s)')
+SUBSECTION_END_RE = re.compile("")
+
+# Debug flags.
+
+# Set to True if want to follow the process
+# The first phase is building the documentation data structure
+# based on docstrings:
+
+MATHICS_DEBUG_DOC_BUILD: bool = "MATHICS_DEBUG_DOC_BUILD" in environ
+
+# After building the doc structure, we extract test cases.
+MATHICS_DEBUG_TEST_CREATE: bool = "MATHICS_DEBUG_TEST_CREATE" in environ
+
+# Name of the Mathics3 Module part of the document.
+MATHICS3_MODULES_TITLE = "Mathics3 Modules"
+
+
+# DocSection has to appear before DocGuideSection which uses it.
+class DocSection:
+ """An object for a Documented Section.
+ A Section is part of a Chapter. It can contain subsections.
+ """
+
+ def __init__(
+ self,
+ chapter,
+ title: str,
+ text: str,
+ operator,
+ installed: bool = True,
+ in_guide: bool = False,
+ summary_text: str = "",
+ ):
+ self.chapter = chapter
+ self.in_guide = in_guide
+ self.installed = installed
+ self.items = [] # tests in section when this is under a guide section
+ self.operator = operator
+ self.slug = slugify(title)
+ self.subsections = []
+ self.subsections_by_slug = {}
+ self.summary_text = summary_text
+ self.tests = None # tests in section when not under a guide section
+ self.title = title
+
+ if text.count("
") != text.count("
"):
+ raise ValueError(
+ "Missing opening or closing
tag in "
+ "{} documentation".format(title)
+ )
+
+ # Needs to come after self.chapter is initialized since
+ # DocumentationEntry uses self.chapter.
+ # Notice that we need the documentation object, to have access
+ # to the suitable subclass of DocumentationElement.
+ documentation = self.chapter.part.documentation
+ self.doc = documentation.doc_class(text, title, None).set_parent_path(self)
+
+ chapter.sections_by_slug[self.slug] = self
+ if MATHICS_DEBUG_DOC_BUILD:
+ print(" DEBUG Creating Section", title)
+
+ # Add __eq__ and __lt__ so we can sort Sections.
+ def __eq__(self, other) -> bool:
+ return self.title == other.title
+
+ def __lt__(self, other) -> bool:
+ return self.title < other.title
+
+ def __str__(self) -> str:
+ return f" == {self.title} ==\n{self.doc}"
+
+ @property
+ def parent(self):
+ "the container where the section is"
+ return self.chapter
+
+ @parent.setter
+ def parent(self, value):
+ "the container where the section is"
+ raise TypeError("parent is a read-only property")
+
+ def get_tests(self):
+ """yield tests"""
+ if self.installed:
+ for test in self.doc.get_tests():
+ yield test
+
+
+# DocChapter has to appear before DocGuideSection which uses it.
+class DocChapter:
+ """An object for a Documented Chapter.
+ A Chapter is part of a Part[dChapter. It can contain (Guide or plain) Sections.
+ """
+
+ def __init__(self, part, title, doc=None, chapter_order: Optional[int] = None):
+ self.chapter_order = chapter_order
+ self.doc = doc
+ self.guide_sections = []
+ self.part = part
+ self.title = title
+ self.slug = slugify(title)
+ self.sections = []
+ self.sections_by_slug = {}
+ self.sort_order = None
+ if doc:
+ self.doc.set_parent_path(self)
+
+ part.chapters_by_slug[self.slug] = self
+
+ if MATHICS_DEBUG_DOC_BUILD:
+ print(" DEBUG Creating Chapter", title)
+
+ def __str__(self) -> str:
+ """
+ A DocChapter is represented as the index of its sections
+ and subsections.
+ """
+ sections_descr = ""
+ for section in self.all_sections:
+ sec_class = "@>" if isinstance(section, DocGuideSection) else "@ "
+ sections_descr += f" {sec_class} " + section.title + "\n"
+ for subsection in section.subsections:
+ sections_descr += " * " + subsection.title + "\n"
+
+ return f" = {self.part.title}: {self.title} =\n\n{sections_descr}"
+
+ @property
+ def all_sections(self):
+ "guides and normal sections"
+ return sorted(self.guide_sections) + sorted(self.sections)
+
+ @property
+ def parent(self):
+ "the container where the chapter is"
+ return self.part
+
+ @parent.setter
+ def parent(self, value):
+ "the container where the chapter is"
+ raise TypeError("parent is a read-only property")
+
+
+class DocGuideSection(DocSection):
+ """An object for a Documented Guide Section.
+ A Guide Section is part of a Chapter. "Colors" or "Special Functions"
+ are examples of Guide Sections, and each contains a number of Sections.
+ like NamedColors or Orthogonal Polynomials.
+ """
+
+ def __init__(
+ self,
+ chapter: DocChapter,
+ title: str,
+ text: str,
+ submodule,
+ installed: bool = True,
+ ):
+ super().__init__(chapter, title, text, None, installed, False)
+ self.section = submodule
+
+ if MATHICS_DEBUG_DOC_BUILD:
+ print(" DEBUG Creating Guide Section", title)
+
+
+class DocPart:
+ """
+ Represents one of the main parts of the document. Parts
+ can be loaded from a mdoc file, generated automatically from
+ the docstrings of Builtin objects under `mathics.builtin`.
+ """
+
+ chapter_class = DocChapter
+
+ def __init__(self, documentation, title, is_reference=False):
+ self.documentation = documentation
+ self.title = title
+ self.chapters = []
+ self.chapters_by_slug = {}
+ self.is_reference = is_reference
+ self.is_appendix = False
+ self.slug = slugify(title)
+ documentation.parts_by_slug[self.slug] = self
+ if MATHICS_DEBUG_DOC_BUILD:
+ print("DEBUG Creating Part", title)
+
+ def __str__(self) -> str:
+ return f" Part {self.title}\n\n" + "\n\n".join(
+ str(chapter) for chapter in sorted_chapters(self.chapters)
+ )
+
+
+class Documentation:
+ """
+ `Documentation` describes an object containing the whole documentation system.
+ Documentation
+ |
+ +--------0> Parts
+ |
+ +-----0> Chapters
+ |
+ +-----0>Sections
+ | |
+ | +------0> SubSections
+ |
+ +---->0>GuideSections
+ |
+ +------0> SubSections
+
+ (with 0>) meaning "aggregation".
+
+ Each element contains a title, a collection of elements of the following class
+ in the hierarchy. Parts, Chapters, Guide Sections, Sections and SubSections contains a doc
+ attribute describing the content to be shown after the title, and before
+ the elements of the subsequent terms in the hierarchy.
+ """
+
+ def __init__(self, title: str = "Title", doc_dir: str = ""):
+ """
+ Parameters
+ ----------
+ title : str, optional
+ The title of the Documentation. The default is "Title".
+ doc_dir : str, optional
+ The path where the sources can be loaded. The default is "",
+ meaning that no sources must be loaded.
+ """
+ # This is a way to load the default classes
+ # without defining these attributes as class
+ # attributes.
+ self._set_classes()
+ self.appendix = []
+ self.doc_dir = doc_dir
+ self.parts = []
+ self.parts_by_slug = {}
+ self.title = title
+
+ def _set_classes(self):
+ """
+ Set the classes of the subelements. Must be overloaded
+ by the subclasses.
+ """
+ if not hasattr(self, "part_class"):
+ self.chapter_class = DocChapter
+ self.doc_class = DocumentationEntry
+ self.guide_section_class = DocGuideSection
+ self.part_class = DocPart
+ self.section_class = DocSection
+ self.subsection_class = DocSubsection
+
+ def __str__(self):
+ result = self.title + "\n" + len(self.title) * "~" + "\n"
+ return (
+ result + "\n\n".join([str(part) for part in self.parts]) + "\n" + 60 * "-"
+ )
+
+ def add_section(
+ self,
+ chapter,
+ section_name: str,
+ section_object,
+ operator,
+ is_guide: bool = False,
+ in_guide: bool = False,
+ summary_text="",
+ ):
+ """
+ Adds a DocSection or DocGuideSection
+ object to the chapter, a DocChapter object.
+ "section_object" is either a Python module or a Class object instance.
+ """
+ if section_object is not None:
+ required_libs = getattr(section_object, "requires", [])
+ installed = check_requires_list(required_libs) if required_libs else True
+ # FIXME add an additional mechanism in the module
+ # to allow a docstring and indicate it is not to go in the
+ # user manual
+ if not section_object.__doc__:
+ return None
+
+ installed = True
+
+ if is_guide:
+ section = self.guide_section_class(
+ chapter,
+ section_name,
+ section_object.__doc__,
+ section_object,
+ installed=installed,
+ )
+ chapter.guide_sections.append(section)
+ else:
+ section = self.section_class(
+ chapter,
+ section_name,
+ section_object.__doc__,
+ operator=operator,
+ installed=installed,
+ in_guide=in_guide,
+ summary_text=summary_text,
+ )
+ chapter.sections.append(section)
+
+ return section
+
+ def add_subsection(
+ self,
+ chapter,
+ section,
+ subsection_name: str,
+ instance,
+ operator=None,
+ in_guide=False,
+ ):
+ """
+ Append a subsection for ``instance`` into ``section.subsections``
+ """
+
+ required_libs = getattr(instance, "requires", [])
+ installed = check_requires_list(required_libs) if required_libs else True
+
+ # FIXME add an additional mechanism in the module
+ # to allow a docstring and indicate it is not to go in the
+ # user manual
+ if not instance.__doc__:
+ return
+ summary_text = (
+ instance.summary_text if hasattr(instance, "summary_text") else ""
+ )
+ subsection = self.subsection_class(
+ chapter,
+ section,
+ subsection_name,
+ instance.__doc__,
+ operator=operator,
+ installed=installed,
+ in_guide=in_guide,
+ summary_text=summary_text,
+ )
+ section.subsections.append(subsection)
+
+ def doc_part(self, title, start):
+ """
+ Build documentation structure for a "Part" - Reference
+ section or collection of Mathics3 Modules.
+ """
+
+ builtin_part = self.part_class(self, title, is_reference=start)
+ self.parts.append(builtin_part)
+
+ def get_part(self, part_slug):
+ """return a section from part key"""
+ return self.parts_by_slug.get(part_slug)
+
+ def get_chapter(self, part_slug, chapter_slug):
+ """return a section from part and chapter keys"""
+ part = self.parts_by_slug.get(part_slug)
+ if part:
+ return part.chapters_by_slug.get(chapter_slug)
+ return None
+
+ def get_section(self, part_slug, chapter_slug, section_slug):
+ """return a section from part, chapter and section keys"""
+ part = self.parts_by_slug.get(part_slug)
+ if part:
+ chapter = part.chapters_by_slug.get(chapter_slug)
+ if chapter:
+ return chapter.sections_by_slug.get(section_slug)
+ return None
+
+ def get_subsection(self, part_slug, chapter_slug, section_slug, subsection_slug):
+ """
+ return a section from part, chapter, section and subsection
+ keys
+ """
+ part = self.parts_by_slug.get(part_slug)
+ if part:
+ chapter = part.chapters_by_slug.get(chapter_slug)
+ if chapter:
+ section = chapter.sections_by_slug.get(section_slug)
+ if section:
+ return section.subsections_by_slug.get(subsection_slug)
+
+ return None
+
+ # FIXME: turn into a @property tests?
+ def get_tests(self) -> Iterator:
+ """
+ Returns a generator to extracts lists test objects.
+ """
+ for part in self.parts:
+ for chapter in sorted_chapters(part.chapters):
+ if MATHICS_DEBUG_TEST_CREATE:
+ print(f"DEBUG Gathering tests for Chapter {chapter.title}")
+
+ tests = chapter.doc.get_tests()
+ if tests:
+ yield Tests(part.title, chapter.title, "", tests)
+
+ for section in chapter.all_sections:
+ if section.installed:
+ if MATHICS_DEBUG_TEST_CREATE:
+ if isinstance(section, DocGuideSection):
+ print(
+ f"DEBUG Gathering tests for Guide Section {section.title}"
+ )
+ else:
+ print(
+ f"DEBUG Gathering tests for Section {section.title}"
+ )
+
+ tests = section.doc.get_tests()
+ if tests:
+ yield Tests(
+ part.title,
+ chapter.title,
+ section.title,
+ tests,
+ )
+
+ def load_documentation_sources(self):
+ """
+ Extract doctest data from various static XML-like doc files, Mathics3 Built-in functions
+ (inside mathics.builtin), and external Mathics3 Modules.
+
+ The extracted structure is stored in ``self``.
+ """
+ from mathics.doc.gather import gather_docs_from_files, gather_reference_part
+
+ assert (
+ len(self.parts) == 0
+ ), "The documentation must be empty to call this function."
+
+ gather_docs_from_files(self, self.doc_dir)
+ # Next extract data that has been loaded into Mathics3 when it runs.
+ # This is information from `mathics.builtin`.
+ # This is Part 2 of the documentation.
+
+ # Notice that in order to generate the documentation
+ # from the builtin classes, it is needed to call first to
+ # import_and_load_builtins()
+
+ for title, modules, builtins_by_module in [
+ (
+ "Reference of Built-in Symbols",
+ mathics3_builtins_modules,
+ global_builtins_by_module,
+ ),
+ (
+ MATHICS3_MODULES_TITLE,
+ pymathics_modules,
+ pymathics_builtins_by_module,
+ ),
+ ]:
+ self.parts.append(
+ gather_reference_part(self, title, modules, builtins_by_module)
+ )
+
+ # Finally, extract Appendix information. This include License text
+ # This is the final Part of the documentation.
+
+ for part in self.appendix:
+ self.parts.append(part)
+
+ def load_part_from_file(
+ self,
+ filename: str,
+ part_title: str,
+ chapter_order: int,
+ is_appendix: bool = False,
+ ) -> int:
+ """Load a document file (tagged XML-like in custom format) as
+ a part of the documentation"""
+ part = self.part_class(self, part_title)
+ with open(filename, "rb") as src_file:
+ text = src_file.read().decode("utf8")
+
+ text = filter_comments(text)
+ chapters = CHAPTER_RE.findall(text)
+ for chapter_title, text in chapters:
+ chapter = self.chapter_class(
+ part, chapter_title, chapter_order=chapter_order
+ )
+ chapter_order += 1
+ text += ''
+ section_texts = SECTION_RE.findall(text)
+ for pre_text, title, text in section_texts:
+ if title:
+ section = self.section_class(
+ chapter, title, text, operator=None, installed=True
+ )
+ chapter.sections.append(section)
+ subsections = SUBSECTION_RE.findall(text)
+ for subsection_title in subsections:
+ subsection = self.subsection_class(
+ chapter,
+ section,
+ subsection_title,
+ text,
+ )
+ section.subsections.append(subsection)
+ else:
+ section = None
+ if not chapter.doc:
+ chapter.doc = self.doc_class(pre_text, title, section)
+ pass
+
+ part.chapters.append(chapter)
+ if is_appendix:
+ part.is_appendix = True
+ self.appendix.append(part)
+ else:
+ self.parts.append(part)
+ return chapter_order
+
+
+class DocSubsection:
+ """An object for a Documented Subsection.
+ A Subsection is part of a Section.
+ """
+
+ def __init__(
+ self,
+ chapter,
+ section,
+ title,
+ text,
+ operator=None,
+ installed=True,
+ in_guide=False,
+ summary_text="",
+ ):
+ """
+ Information that goes into a subsection object. This can be a written text, or
+ text extracted from the docstring of a builtin module or class.
+
+ About some of the parameters...
+
+ Some subsections are contained in a grouping module and need special work to
+ get the grouping module name correct.
+
+ For example the Chapter "Colors" is a module so the docstring text for it is in
+ mathics/builtin/colors/__init__.py . In mathics/builtin/colors/named-colors.py we have
+ the "section" name for the class Red (the subsection) inside it.
+ """
+ title_summary_text = re.split(" -- ", title)
+ len_title = len(title_summary_text)
+ # We need the documentation object, to have access
+ # to the suitable subclass of DocumentationElement.
+ documentation = chapter.part.documentation
+
+ self.title = title_summary_text[0] if len_title > 0 else ""
+ self.summary_text = title_summary_text[1] if len_title > 1 else summary_text
+ self.doc = documentation.doc_class(text, title, None)
+ self.chapter = chapter
+ self.in_guide = in_guide
+ self.installed = installed
+ self.operator = operator
+
+ self.section = section
+ self.slug = slugify(title)
+ self.subsections = []
+ self.title = title
+ self.doc.set_parent_path(self)
+
+ # This smells wrong: Here a DocSection (a level in the documentation system)
+ # is mixed with a DocumentationEntry. `items` is an attribute of the
+ # `DocumentationEntry`, not of a Part / Chapter/ Section.
+ # The content of a subsection should be stored in self.doc,
+ # and the tests should set the route (key) through self.doc.set_parent_doc
+ if in_guide:
+ # Tests haven't been picked out yet from the doc string yet.
+ # Gather them here.
+ self.items = self.doc.items
+
+ for item in self.items:
+ for test in item.get_tests():
+ assert test.key is not None
+ else:
+ self.items = []
+
+ if text.count("
") != text.count("
"):
+ raise ValueError(
+ "Missing opening or closing
tag in "
+ "{} documentation".format(title)
+ )
+ self.section.subsections_by_slug[self.slug] = self
+
+ if MATHICS_DEBUG_DOC_BUILD:
+ print(" DEBUG Creating Subsection", title)
+
+ def __str__(self) -> str:
+ return f"=== {self.title} ===\n{self.doc}"
+
+ @property
+ def parent(self):
+ """the chapter where the section is"""
+ return self.section
+
+ @parent.setter
+ def parent(self, value):
+ raise TypeError("parent is a read-only property")
+
+ def get_tests(self):
+ """yield tests"""
+ if self.installed:
+ for test in self.doc.get_tests():
+ yield test
+
+
+class MathicsMainDocumentation(Documentation):
+ """MathicsMainDocumentation specializes ``Documentation`` by providing the attributes
+ and methods needed to generate the documentation from the Mathics library.
+
+ The parts of the documentation are loaded from the Markdown files contained
+ in the path specified by ``self.doc_dir``. Files with names starting in numbers
+ are considered parts of the main text, while those that starts with other characters
+ are considered as appendix parts.
+
+ In addition to the parts loaded from our custom-marked XML
+ document file, a ``Reference of Builtin-Symbols`` part and a part
+ for the loaded Pymathics modules are automatically generated.
+
+ In the ``Reference of Built-in Symbols`` tom-level modules and files in ``mathics.builtin``
+ are associated to Chapters. For single file submodules (like ``mathics.builtin.procedure``)
+ The chapter contains a Section for each Symbol in the module. For sub-packages
+ (like ``mathics.builtin.arithmetic``) sections are given by the sub-module files,
+ and the symbols in these sub-packages defines the Subsections. ``__init__.py`` in
+ subpackages are associated to GuideSections.
+
+ In a similar way, in the ``Mathics3 Modules`` part, each ``Mathics3`` module defines a Chapter,
+ files in the module defines Sections, and Symbols defines Subsections.
+
+
+ ``MathicsMainDocumentation`` is also used for creating test data and saving it to a
+ Python Pickle file and running tests that appear in the documentation (doctests).
+
+ There are other classes DjangoMathicsDocumentation and LaTeXMathicsDocumentation
+ that format the data accumulated here. In fact I think those can sort of serve
+ instead of this.
+
+ """
+
+ def __init__(self):
+ super().__init__(title="Mathics Main Documentation", doc_dir=settings.DOC_DIR)
+ self.doctest_latex_pcl_path = settings.DOCTEST_LATEX_DATA_PCL
+ self.pymathics_doc_loaded = False
+ self.doc_data_file = settings.get_doctest_latex_data_path(
+ should_be_readable=True
+ )
+
+ def gather_doctest_data(self):
+ """
+ Populates the documentatation.
+ (deprecated)
+ """
+ logging.warning(
+ "gather_doctest_data is deprecated. Use load_documentation_sources"
+ )
+ return self.load_documentation_sources()
+
+
+def sorted_chapters(chapters: List[DocChapter]) -> List[DocChapter]:
+ """Return chapters sorted by title"""
+ return sorted(
+ chapters,
+ key=lambda chapter: str(chapter.sort_order)
+ if chapter.sort_order is not None
+ else chapter.title,
+ )
+
+
+def sorted_modules(modules) -> list:
+ """Return modules sorted by the ``sort_order`` attribute if that
+ exists, or the module's name if not."""
+ return sorted(
+ modules,
+ key=lambda module: module.sort_order
+ if hasattr(module, "sort_order")
+ else module.__name__,
+ )
diff --git a/mathics/doc/utils.py b/mathics/doc/utils.py
index db37d9c12..983c336b2 100644
--- a/mathics/doc/utils.py
+++ b/mathics/doc/utils.py
@@ -1,11 +1,55 @@
-#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+import os.path as osp
+import pickle
import re
import unicodedata
+from os import makedirs
+from typing import Dict
-def slugify(value):
+def load_doctest_data(data_path, quiet=False) -> Dict[tuple, dict]:
+ """
+ Read doctest information from PCL file and return this.
+
+ The return value is a dictionary of test results. The key is a tuple
+ of:
+ * Part name,
+ * Chapter name,
+ * [Guide Section name],
+ * Section name,
+ * Subsection name,
+ * test number
+ and the value is a dictionary of a Result.getdata() dictionary.
+ """
+ if not quiet:
+ print(f"Loading LaTeX internal data from {data_path}")
+ with open_ensure_dir(data_path, "rb") as doc_data_fp:
+ return pickle.load(doc_data_fp)
+
+
+def open_ensure_dir(f, *args, **kwargs):
+ try:
+ return open(f, *args, **kwargs)
+ except (IOError, OSError):
+ d = osp.dirname(f)
+ if d and not osp.exists(d):
+ makedirs(d)
+ return open(f, *args, **kwargs)
+
+
+def print_and_log(logfile, *args):
+ """
+ Print a message and also log it to global LOGFILE.
+ """
+ msg_lines = [a.decode("utf-8") if isinstance(a, bytes) else a for a in args]
+ string = "".join(msg_lines)
+ print(string)
+ if logfile is not None:
+ logfile.write(string)
+
+
+def slugify(value: str) -> str:
"""
Converts to lowercase, removes non-word characters apart from '$',
and converts spaces to hyphens. Also strips leading and trailing
diff --git a/mathics/docpipeline.py b/mathics/docpipeline.py
index b8d6f4cd4..58c9b776b 100644
--- a/mathics/docpipeline.py
+++ b/mathics/docpipeline.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# FIXME: combine with same thing in Mathics core
+# FIXME: combine with same thing in Mathics Django
"""
Does 2 things which can either be done independently or
as a pipeline:
@@ -13,226 +13,269 @@
import os
import os.path as osp
import pickle
-import re
import sys
from argparse import ArgumentParser
+from collections import namedtuple
from datetime import datetime
-from typing import Dict
+from typing import Callable, Dict, Optional, Set, Union
import mathics
-import mathics.settings
from mathics import settings, version_string
-from mathics.core.definitions import Definitions
-from mathics.core.evaluation import Evaluation, Output
-from mathics.core.load_builtin import (
- builtins_by_module,
- builtins_dict,
- import_and_load_builtins,
-)
-from mathics.core.parser import MathicsSingleLineFeeder
-from mathics.doc.common_doc import MathicsMainDocumentation
+from mathics.core.evaluation import Output
+from mathics.core.load_builtin import _builtins, import_and_load_builtins
+from mathics.doc.common_doc import DocGuideSection, DocSection, MathicsMainDocumentation
+from mathics.doc.doc_entries import DocTest, DocTests
+from mathics.doc.utils import load_doctest_data, print_and_log, slugify
from mathics.eval.pymathics import PyMathicsLoadException, eval_LoadModule
+from mathics.session import MathicsSession
+from mathics.settings import get_doctest_latex_data_path
from mathics.timing import show_lru_cache_statistics
-builtins = builtins_dict(builtins_by_module)
+# Global variables
+
+# FIXME: After 3.8 is the minimum Python we can turn "str" into a Literal
+SEP: str = "-" * 70 + "\n"
+STARS: str = "*" * 10
+MAX_TESTS = 100000 # A number greater than the total number of tests.
+# When 3.8 is base, the below can be a Literal type.
+INVALID_TEST_GROUP_SETUP = (None, None)
+
+TestParameters = namedtuple(
+ "TestParameters",
+ [
+ "check_partial_elapsed_time",
+ "data_path",
+ "keep_going",
+ "max_tests",
+ "quiet",
+ "output_format",
+ "reload",
+ "start_at",
+ ],
+)
class TestOutput(Output):
+ """Output class for tests"""
+
def max_stored_size(self, _):
return None
-sep = "-" * 70 + "\n"
-
-# Global variables
-definitions = None
-documentation = None
-check_partial_elapsed_time = False
-logfile = None
-
-
-MAX_TESTS = 100000 # Number than the total number of tests
+class DocTestPipeline:
+ """
+ This class gathers all the information required to process
+ the doctests and generate the data for the documentation.
+ """
+ def __init__(self, args, output_format="latex", data_path: Optional[str] = None):
+ self.session = MathicsSession()
+ self.output_data = {}
+
+ # LoadModule Mathics3 modules
+ if args.pymathics:
+ required_modules = set(args.pymathics.split(","))
+ load_pymathics_modules(required_modules, self.session.definitions)
+
+ self.builtin_total = len(_builtins)
+ self.documentation = MathicsMainDocumentation()
+ self.documentation.load_documentation_sources()
+ self.logfile = open(args.logfilename, "wt") if args.logfilename else None
+
+ self.parameters = TestParameters(
+ check_partial_elapsed_time=args.elapsed_times,
+ data_path=data_path,
+ keep_going=args.keep_going and not args.stop_on_failure,
+ max_tests=args.count + args.skip,
+ quiet=args.quiet,
+ output_format=output_format,
+ reload=args.reload and not (args.chapters or args.sections),
+ start_at=args.skip + 1,
+ )
+ self.status = TestStatus(data_path, self.parameters.quiet)
+
+ def reset_user_definitions(self):
+ """Reset the user definitions"""
+ return self.session.definitions.reset_user_definitions()
+
+ def print_and_log(self, message):
+ """Print and log a message in the logfile"""
+ if not self.parameters.quiet:
+ print(message)
+ if self.logfile:
+ print_and_log(self.logfile, message.encode("utf-8"))
+
+ def validate_group_setup(
+ self,
+ include_set: set,
+ entity_name: Optional[str],
+ ):
+ """
+ Common things that need to be done before running a group of doctests.
+ """
+ test_parameters = self.parameters
+
+ if self.documentation is None:
+ self.print_and_log("Documentation is not initialized.")
+ return INVALID_TEST_GROUP_SETUP
+
+ if entity_name is not None:
+ include_names = ", ".join(include_set)
+ print(f"Testing {entity_name}(s): {include_names}")
+ else:
+ include_names = None
-def print_and_log(*args):
- a = [a.decode("utf-8") if isinstance(a, bytes) else a for a in args]
- string = "".join(a)
- print(string)
- if logfile:
- logfile.write(string)
+ if test_parameters.reload:
+ doctest_latex_data_path = get_doctest_latex_data_path(
+ should_be_readable=True
+ )
+ self.output_data = load_doctest_data(doctest_latex_data_path)
+ else:
+ self.output_data = {}
+ # For consistency set the character encoding ASCII which is
+ # the lowest common denominator available on all systems.
+ settings.SYSTEM_CHARACTER_ENCODING = "ASCII"
-def compare(result, wanted) -> bool:
- if wanted == "..." or result == wanted:
- return True
+ if self.session.definitions is None:
+ self.print_and_log("Definitions are not initialized.")
+ return INVALID_TEST_GROUP_SETUP
- if result is None or wanted is None:
- return False
- result = result.splitlines()
- wanted = wanted.splitlines()
- if result == [] and wanted == ["#<--#"]:
- return True
+ # Start with a clean variables state from whatever came before.
+ # In the test suite however, we may set new variables.
+ self.reset_user_definitions()
+ return self.output_data, include_names
- if len(result) != len(wanted):
- return False
- for r, w in zip(result, wanted):
- wanted_re = re.escape(w.strip())
- wanted_re = wanted_re.replace("\\.\\.\\.", ".*?")
- wanted_re = "^%s$" % wanted_re
- if not re.match(wanted_re, r.strip()):
- return False
- return True
+class TestStatus:
+ """
+ Status parameters of the tests
+ """
+ def __init__(self, data_path: Optional[str] = None, quiet=False):
+ self.texdatafolder = osp.dirname(data_path) if data_path is not None else None
+ self.total = 0
+ self.failed = 0
+ self.skipped = 0
+ self.failed_sections = set()
+ self.prev_key = []
+ self.quiet = quiet
+
+ def find_texdata_folder(self):
+ """Generate a folder for texdata"""
+ return self.textdatafolder
+
+ def mark_as_failed(self, key):
+ """Mark a key as failed"""
+ self.failed_sections.add(key)
+ self.failed += 1
+
+ def section_name_for_print(self, test) -> str:
+ """
+ If the test has a different key,
+ returns a printable version of the section name.
+ Otherwise, return the empty string.
+ """
+ key = list(test.key)[1:-1]
+ if key != self.prev_key:
+ return " / ".join(key)
+ return ""
+
+ def show_section(self, test):
+ """Show information about the current test case"""
+ section_name_for_print = self.section_name_for_print(test)
+ if section_name_for_print:
+ if self.quiet:
+ print(f"Testing section: {section_name_for_print}")
+ else:
+ print(f"{STARS} {section_name_for_print} {STARS}")
-stars = "*" * 10
+ def show_test(self, test, index, subindex):
+ """Show the current test"""
+ test_str = test.test
+ if not self.quiet:
+ print(f"{index:4d} ({subindex:2d}): TEST {test_str}")
def test_case(
- test, tests, index=0, subindex=0, quiet=False, section=None, format="text"
+ test: DocTest,
+ test_pipeline: DocTestPipeline,
+ fail: Optional[Callable] = lambda x: False,
) -> bool:
- global check_partial_elapsed_time
- test, wanted_out, wanted = test.test, test.outs, test.result
-
- def fail(why):
- part, chapter, section = tests.part, tests.chapter, tests.section
- print_and_log(
- f"""{sep}Test failed: {section} in {part} / {chapter}
-{part}
-{why}
-""".encode(
- "utf-8"
- )
- )
- return False
-
- if not quiet:
- if section:
- print(f"{stars} {tests.chapter} / {section} {stars}".encode("utf-8"))
- print(f"{index:4d} ({subindex:2d}): TEST {test}".encode("utf-8"))
+ """
+ Run a single test cases ``test``. Return True if test succeeds and False if it
+ fails. ``index``gives the global test number count, while ``subindex`` counts
+ from the beginning of the section or subsection.
- feeder = MathicsSingleLineFeeder(test, "")
- evaluation = Evaluation(
- definitions, catch_interrupt=False, output=TestOutput(), format=format
- )
+ The test results are assumed to be formatted to ASCII text.
+ """
+ test_parameters = test_pipeline.parameters
try:
- time_parsing = datetime.now()
- query = evaluation.parse_feeder(feeder)
- if check_partial_elapsed_time:
- print(" parsing took", datetime.now() - time_parsing)
- if query is None:
- # parsed expression is None
- result = None
- out = evaluation.out
- else:
- result = evaluation.evaluate(query)
- if check_partial_elapsed_time:
- print(" evaluation took", datetime.now() - time_parsing)
- out = result.out
- result = result.result
+ time_start = datetime.now()
+ result = test_pipeline.session.evaluate_as_in_cli(test.test, src_name="")
+ out = result.out
+ result = result.result
except Exception as exc:
- fail("Exception %s" % exc)
+ fail(f"Exception {exc}")
info = sys.exc_info()
sys.excepthook(*info)
return False
- time_comparing = datetime.now()
- comparison_result = compare(result, wanted)
+ time_start = datetime.now()
+ comparison_result = test.compare_result(result)
- if check_partial_elapsed_time:
- print(" comparison took ", datetime.now() - time_comparing)
+ if test_parameters.check_partial_elapsed_time:
+ print(" comparison took ", datetime.now() - time_start)
if not comparison_result:
- print("result =!=wanted")
- fail_msg = "Result: %s\nWanted: %s" % (result, wanted)
+ print("result != wanted")
+ fail_msg = f"Result: {result}\nWanted: {test.result}"
if out:
fail_msg += "\nAdditional output:\n"
fail_msg += "\n".join(str(o) for o in out)
return fail(fail_msg)
- output_ok = True
- time_comparing = datetime.now()
- if len(wanted_out) == 1 and wanted_out[0].text == "...":
- # If we have ... don't check
- pass
- elif len(out) != len(wanted_out):
- # Mismatched number of output lines and we don't have "..."
- output_ok = False
- else:
- # Need to check all output line by line
- for got, wanted in zip(out, wanted_out):
- if not got == wanted and wanted.text != "...":
- output_ok = False
- break
- if check_partial_elapsed_time:
- print(" comparing messages took ", datetime.now() - time_comparing)
+
+ time_start = datetime.now()
+ output_ok = test.compare_out(out)
+ if test_parameters.check_partial_elapsed_time:
+ print(" comparing messages took ", datetime.now() - time_start)
if not output_ok:
return fail(
"Output:\n%s\nWanted:\n%s"
- % ("\n".join(str(o) for o in out), "\n".join(str(o) for o in wanted_out))
+ % (
+ "\n".join(str(o) for o in out),
+ "\n".join(str(o) for o in test.outs),
+ )
)
return True
-def test_tests(
- tests,
- index,
- quiet=False,
- stop_on_failure=False,
- start_at=0,
- max_tests=MAX_TESTS,
- excludes=[],
-):
- # For consistency set the character encoding ASCII which is
- # the lowest common denominator available on all systems.
- mathics.settings.SYSTEM_CHARACTER_ENCODING = "ASCII"
-
- definitions.reset_user_definitions()
- total = failed = skipped = 0
- failed_symbols = set()
- section = tests.section
- if section in excludes:
- return total, failed, len(tests.tests), failed_symbols, index
- count = 0
- for subindex, test in enumerate(tests.tests):
- index += 1
- if test.ignore:
- continue
- if index < start_at:
- skipped += 1
- continue
- elif count >= max_tests:
- break
-
- total += 1
- count += 1
- if not test_case(test, tests, index, subindex + 1, quiet, section):
- failed += 1
- failed_symbols.add((tests.part, tests.chapter, tests.section))
- if stop_on_failure:
- break
-
- section = None
- return total, failed, skipped, failed_symbols, index
+def create_output(test_pipeline, tests):
+ """
+ Populate ``doctest_data`` with the results of the
+ ``tests`` in the format ``output_format``
+ """
+ output_format = test_pipeline.parameters.output_format
+ if test_pipeline.session.definitions is None:
+ test_pipeline.print_and_log("Definitions are not initialized.")
+ return
+ doctest_data = test_pipeline.output_data
+ test_pipeline.reset_user_definitions()
+ session = test_pipeline.session
-# FIXME: move this to common routine
-def create_output(tests, doctest_data, format="latex"):
- definitions.reset_user_definitions()
- for test in tests.tests:
+ for test in tests:
if test.private:
continue
key = test.key
- evaluation = Evaluation(
- definitions, format=format, catch_interrupt=True, output=TestOutput()
- )
try:
- result = evaluation.parse_evaluate(test.test)
- except: # noqa
+ result = session.evaluate_as_in_cli(test.test, form=output_format)
+ except Exception: # noqa
result = None
if result is None:
result = []
else:
result_data = result.get_data()
- result_data["form"] = format
+ result_data["form"] = output_format
result = [result_data]
doctest_data[key] = {
@@ -241,203 +284,424 @@ def create_output(tests, doctest_data, format="latex"):
}
-def test_chapters(
- chapters: set,
- quiet=False,
- stop_on_failure=False,
- generate_output=False,
- reload=False,
- want_sorting=False,
- keep_going=False,
+def load_pymathics_modules(module_names: set, definitions):
+ """
+ Load pymathics modules
+
+ PARAMETERS
+ ==========
+
+ module_names: set
+ a set of modules to be loaded.
+
+ Return
+ ======
+ loaded_modules : set
+ the set of successfully loaded modules.
+ """
+ loaded_modules = []
+ for module_name in module_names:
+ try:
+ eval_LoadModule(module_name, definitions)
+ except PyMathicsLoadException:
+ print(f"Python module {module_name} is not a Mathics3 module.")
+
+ except Exception as exc:
+ print(f"Python import errors with: {exc}.")
+ else:
+ print(f"Mathics3 Module {module_name} loaded")
+ loaded_modules.append(module_name)
+
+ return set(loaded_modules)
+
+
+def show_test_summary(
+ test_pipeline: DocTestPipeline,
+ entity_name: str,
+ entities_searched: str,
):
- failed = 0
- index = 0
- chapter_names = ", ".join(chapters)
- print(f"Testing chapter(s): {chapter_names}")
- output_data = load_doctest_data() if reload else {}
- prev_key = []
- for tests in documentation.get_tests():
- if tests.chapter in chapters:
- for test in tests.tests:
- key = list(test.key)[1:-1]
- if prev_key != key:
- prev_key = key
- print(f'Testing section: {" / ".join(key)}')
- index = 0
- if test.ignore:
- continue
- index += 1
- if not test_case(test, tests, index, quiet=quiet):
- failed += 1
- if stop_on_failure:
- break
- if generate_output and failed == 0:
- create_output(tests, output_data)
+ """
+ Print and log test summary results.
+
+ If ``data_path`` is not ``None``, we will also generate output data
+ to ``output_data``.
+ """
+ test_parameters: TestParameters = test_pipeline.parameters
+ test_status: TestStatus = test_pipeline.status
+ failed = test_status.failed
print()
- if index == 0:
- print_and_log(f"No chapters found named {chapter_names}.")
+ if test_status.total == 0:
+ test_parameters.print_and_log(
+ f"No {entity_name} found with a name in: {entities_searched}.",
+ )
+ if "MATHICS_DEBUG_TEST_CREATE" not in os.environ:
+ print(f"Set environment MATHICS_DEBUG_TEST_CREATE to see {entity_name}.")
elif failed > 0:
- if not (keep_going and format == "latex"):
- print_and_log("%d test%s failed." % (failed, "s" if failed != 1 else ""))
+ print(SEP)
+ if test_parameters.data_path is None:
+ test_pipeline.print_and_log(
+ f"""{failed} test{'s' if failed != 1 else ''} failed.""",
+ )
else:
- print_and_log("All tests passed.")
+ test_pipeline.print_and_log("All tests passed.")
+ if test_parameters.data_path and (failed == 0 or test_parameters.keep_going):
+ save_doctest_data(test_pipeline)
-def test_sections(
- sections: set,
- quiet=False,
- stop_on_failure=False,
- generate_output=False,
- reload=False,
- want_sorting=False,
- keep_going=False,
+
+def section_tests_iterator(
+ section, test_pipeline, include_subsections=None, exclude_sections=None
+):
+ """
+ Iterator over tests in a section.
+ A section contains tests in its documentation entry,
+ in the head of the chapter and in its subsections.
+ This function is a generator of all these tests.
+
+ Before yielding a test from a documentation entry,
+ the user definitions are reset.
+ """
+ chapter = section.chapter
+ subsections = [section]
+ if chapter.doc:
+ subsections = [chapter.doc] + subsections
+ if section.subsections:
+ subsections = subsections + section.subsections
+
+ for subsection in subsections:
+ if (
+ include_subsections is not None
+ and subsection.title not in include_subsections
+ ):
+ continue
+ if exclude_sections and subsection.title in exclude_sections:
+ continue
+ test_pipeline.reset_user_definitions()
+
+ for tests in subsection.get_tests():
+ if isinstance(tests, DocTests):
+ for test in tests:
+ yield test
+ else:
+ yield tests
+
+
+def test_section_in_chapter(
+ test_pipeline: DocTestPipeline,
+ section: Union[DocSection, DocGuideSection],
+ include_sections: Optional[Set[str]] = None,
+ exclude_sections: Optional[Set[str]] = None,
):
- failed = 0
+ """
+ Runs a tests for section ``section`` under a chapter or guide section.
+ Note that both of these contain a collection of section tests underneath.
+ """
+ test_parameters: TestParameters = test_pipeline.parameters
+ test_status: TestStatus = test_pipeline.status
+
+ # Start out assuming all subsections will be tested
+ include_subsections = None
+ if include_sections is not None and section.title not in include_sections:
+ # use include_section to filter subsections
+ include_subsections = include_sections
+
+ chapter = section.chapter
index = 0
- section_names = ", ".join(sections)
- print(f"Testing section(s): {section_names}")
- sections |= {"$" + s for s in sections}
- output_data = load_doctest_data() if reload else {}
- prev_key = []
- format = "latex" if generate_output else "text"
- for tests in documentation.get_tests():
- if tests.section in sections:
- for test in tests.tests:
- key = list(test.key)[1:-1]
- if prev_key != key:
- prev_key = key
- print(f'Testing section: {" / ".join(key)}')
- index = 0
- if test.ignore:
- continue
- index += 1
- if not test_case(test, tests, index, quiet=quiet, format=format):
- failed += 1
- if stop_on_failure:
- break
- if generate_output and (failed == 0 or keep_going):
- create_output(tests, output_data, format=format)
+ subsections = [section]
+ if chapter.doc:
+ subsections = [chapter.doc] + subsections
+ if section.subsections:
+ subsections = subsections + section.subsections
+
+ section_name_for_print = ""
+ for doctest in section_tests_iterator(
+ section, test_pipeline, include_subsections, exclude_sections
+ ):
+ if doctest.ignore:
+ continue
+ section_name_for_print = test_status.section_name_for_print(doctest)
+ test_status.show_section(doctest)
+ key = list(doctest.key)[1:-1]
+ if key != test_status.prev_key:
+ index = 1
+ else:
+ index += 1
+ test_status.prev_key = key
+ test_status.total += 1
+ if test_status.total > test_parameters.max_tests:
+ return
+ if test_status.total < test_parameters.start_at:
+ test_status.skipped += 1
+ continue
- print()
- if index == 0:
- print_and_log(f"No sections found named {section_names}.")
- elif failed > 0:
- if not (keep_going and format == "latex"):
- print_and_log("%d test%s failed." % (failed, "s" if failed != 1 else ""))
- else:
- print_and_log("All tests passed.")
- if generate_output and (failed == 0 or keep_going):
- save_doctest_data(output_data)
+ def fail_message(why):
+ test_pipeline.print_and_log(
+ (f"""{SEP}Test failed: in {section_name_for_print}\n""" f"""{why}"""),
+ )
+ return False
+ test_status.show_test(doctest, test_status.total, index)
-def open_ensure_dir(f, *args, **kwargs):
- try:
- return open(f, *args, **kwargs)
- except (IOError, OSError):
- d = osp.dirname(f)
- if d and not osp.exists(d):
- os.makedirs(d)
- return open(f, *args, **kwargs)
+ success = test_case(
+ doctest,
+ test_pipeline,
+ fail=fail_message,
+ )
+ if not success:
+ test_status.mark_as_failed(doctest.key[:-1])
+ if not test_pipeline.parameters.keep_going:
+ return
+ return
-def test_all(
- quiet=False,
- generate_output=True,
- stop_on_failure=False,
- start_at=0,
- count=MAX_TESTS,
- texdatafolder=None,
- doc_even_if_error=False,
- excludes=[],
- want_sorting=False,
+
+def test_tests(
+ test_pipeline: DocTestPipeline,
+ excludes: Optional[Set[str]] = None,
):
- if not quiet:
- print(f"Testing {version_string}")
+ """
+ Runs a group of related tests, ``Tests`` provided that the section is not
+ listed in ``excludes`` and the global test count given in ``index`` is not
+ before ``start_at``.
+
+ Tests are from a section or subsection (when the section is a guide
+ section). If ``quiet`` is True, the progress and results of the tests
+ are shown.
+
+ ``index`` has the current count. We will stop on the first failure
+ if ``keep_going`` is false.
+
+ """
+ test_status: TestStatus = test_pipeline.status
+ test_parameters: TestParameters = test_pipeline.parameters
+ # For consistency set the character encoding ASCII which is
+ # the lowest common denominator available on all systems.
+
+ settings.SYSTEM_CHARACTER_ENCODING = "ASCII"
+ test_pipeline.reset_user_definitions()
+
+ output_data, names = test_pipeline.validate_group_setup(
+ set(),
+ None,
+ )
+ if (output_data, names) == INVALID_TEST_GROUP_SETUP:
+ return
- if generate_output:
- if texdatafolder is None:
- texdatafolder = osp.dirname(
- settings.get_doctest_latex_data_path(
- should_be_readable=False, create_parent=True
+ # Loop over the whole documentation.
+ for part in test_pipeline.documentation.parts:
+ for chapter in part.chapters:
+ for section in chapter.all_sections:
+ section_name = section.title
+ if excludes and section_name in excludes:
+ continue
+
+ if test_status.total >= test_parameters.max_tests:
+ show_test_summary(
+ test_pipeline,
+ "chapters",
+ "",
+ )
+ return
+ test_section_in_chapter(
+ test_pipeline,
+ section,
+ exclude_sections=excludes,
)
- )
- try:
- index = 0
- total = failed = skipped = 0
- failed_symbols = set()
- output_data = {}
- for tests in documentation.get_tests(want_sorting=want_sorting):
- sub_total, sub_failed, sub_skipped, symbols, index = test_tests(
- tests,
- index,
- quiet=quiet,
- stop_on_failure=stop_on_failure,
- start_at=start_at,
- max_tests=count,
- excludes=excludes,
- )
- if generate_output:
- create_output(tests, output_data)
- total += sub_total
- failed += sub_failed
- skipped += sub_skipped
- failed_symbols.update(symbols)
- if sub_failed and stop_on_failure:
- break
- if total >= count:
- break
- builtin_total = len(builtins)
- except KeyboardInterrupt:
- print("\nAborted.\n")
+ if test_status.failed_sections:
+ if not test_parameters.keep_going:
+ show_test_summary(
+ test_pipeline,
+ "chapters",
+ "",
+ )
+ return
+ else:
+ if test_parameters.data_path:
+ create_output(
+ test_pipeline,
+ section_tests_iterator(
+ section,
+ test_pipeline,
+ exclude_sections=excludes,
+ ),
+ )
+ show_test_summary(
+ test_pipeline,
+ "chapters",
+ "",
+ )
+
+ return
+
+
+def test_chapters(
+ test_pipeline: DocTestPipeline,
+ include_chapters: set,
+ exclude_sections: set,
+):
+ """
+ Runs a group of related tests for the set specified in ``chapters``.
+
+ If ``quiet`` is True, the progress and results of the tests are shown.
+ """
+ test_status = test_pipeline.status
+ test_parameters = test_pipeline.parameters
+
+ output_data, chapter_names = test_pipeline.validate_group_setup(
+ include_chapters, "chapters"
+ )
+ if (output_data, chapter_names) == INVALID_TEST_GROUP_SETUP:
+ return
+
+ for chapter_name in include_chapters:
+ chapter_slug = slugify(chapter_name)
+ for part in test_pipeline.documentation.parts:
+ chapter = part.chapters_by_slug.get(chapter_slug, None)
+ if chapter is None:
+ continue
+ for section in chapter.all_sections:
+ test_section_in_chapter(
+ test_pipeline,
+ section,
+ exclude_sections=exclude_sections,
+ )
+ if test_parameters.data_path is not None and test_status.failed == 0:
+ create_output(
+ test_pipeline,
+ section.doc.get_tests(),
+ )
+
+ show_test_summary(
+ test_pipeline,
+ "chapters",
+ chapter_names,
+ )
+
+ return
+
+
+def test_sections(
+ test_pipeline: DocTestPipeline,
+ include_sections: set,
+ exclude_subsections: set,
+):
+ """Runs a group of related tests for the set specified in ``sections``.
+
+ If ``quiet`` is True, the progress and results of the tests are shown.
+
+ ``index`` has the current count. If ``keep_going`` is false
+ then the remaining tests in a section are skipped when a test
+ fails. If ``keep_going`` is True and there is a failure, the next
+ section is continued after failure occurs.
+ """
+ test_status = test_pipeline.status
+ test_parameters = test_pipeline.parameters
+
+ output_data, section_names = test_pipeline.validate_group_setup(
+ include_sections, "section"
+ )
+ if (output_data, section_names) == INVALID_TEST_GROUP_SETUP:
return
- if failed > 0:
- print(sep)
- if count == MAX_TESTS:
- print_and_log(
- "%d Tests for %d built-in symbols, %d passed, %d failed, %d skipped."
- % (total, builtin_total, total - failed - skipped, failed, skipped)
+ seen_sections = set()
+ seen_last_section = False
+ last_section_name = None
+ section_name_for_finish = None
+
+ for part in test_pipeline.documentation.parts:
+ for chapter in part.chapters:
+ for section in chapter.all_sections:
+ test_section_in_chapter(
+ test_pipeline,
+ section=section,
+ include_sections=include_sections,
+ exclude_sections=exclude_subsections,
+ )
+
+ if test_parameters.data_path is not None and test_status.failed == 0:
+ create_output(
+ test_pipeline,
+ section.doc.get_tests(),
+ )
+
+ if last_section_name != section_name_for_finish:
+ if seen_sections == include_sections:
+ seen_last_section = True
+ break
+ if section_name_for_finish in include_sections:
+ seen_sections.add(section_name_for_finish)
+ last_section_name = section_name_for_finish
+
+ if seen_last_section:
+ show_test_summary(test_pipeline, "sections", section_names)
+ return
+
+ show_test_summary(test_pipeline, "sections", section_names)
+ return
+
+
+def show_report(test_pipeline):
+ """Print a report with the results of the tests"""
+ test_status = test_pipeline.status
+ test_parameters = test_pipeline.parameters
+ total, failed = test_status.total, test_status.failed
+ builtin_total = test_pipeline.builtin_total
+ skipped = test_status.skipped
+ if test_parameters.max_tests == MAX_TESTS:
+ test_pipeline.print_and_log(
+ f"{total} Tests for {builtin_total} built-in symbols, {total-failed} "
+ f"passed, {failed} failed, {skipped} skipped.",
)
else:
- print_and_log(
- "%d Tests, %d passed, %d failed, %d skipped."
- % (total, total - failed, failed, skipped)
+ test_pipeline.print_and_log(
+ f"{total} Tests, {total - failed} passed, {failed} failed, {skipped} "
+ "skipped.",
)
- if failed_symbols:
- if stop_on_failure:
- print_and_log("(not all tests are accounted for due to --stop-on-failure)")
- print_and_log("Failed:")
- for part, chapter, section in sorted(failed_symbols):
- print_and_log(" - %s in %s / %s" % (section, part, chapter))
-
- if generate_output and (failed == 0 or doc_even_if_error):
- save_doctest_data(output_data)
- return True
-
- if failed == 0:
- print("\nOK")
- else:
- print("\nFAILED")
- return sys.exit(1) # Travis-CI knows the tests have failed
+ if test_status.failed_sections:
+ if not test_pipeline.parameters.keep_going:
+ test_pipeline.print_and_log(
+ "(not all tests are accounted for due to --)",
+ )
+ test_pipeline.print_and_log("Failed:")
+ for part, chapter, section in sorted(test_status.failed_sections):
+ test_pipeline.print_and_log(f" - {section} in {part} / {chapter}")
+
+ if test_parameters.data_path is not None and (
+ test_status.failed == 0 or test_parameters.doc_even_if_error
+ ):
+ save_doctest_data(test_pipeline)
+ return
-def load_doctest_data() -> Dict[tuple, dict]:
+def test_all(
+ test_pipeline: DocTestPipeline,
+ excludes: Optional[Set[str]] = None,
+):
"""
- Load doctest tests and test results from Python PCL file.
-
- See ``save_doctest_data()`` for the format of the loaded PCL data
- (a dict).
+ Run all the tests in the documentation.
"""
- doctest_latex_data_path = settings.get_doctest_latex_data_path(
- should_be_readable=True
- )
- print(f"Loading internal doctest data from {doctest_latex_data_path}")
- with open_ensure_dir(doctest_latex_data_path, "rb") as doctest_data_file:
- return pickle.load(doctest_data_file)
+ test_parameters = test_pipeline.parameters
+ test_status = test_pipeline.status
+ if not test_parameters.quiet:
+ print(f"Testing {version_string}")
+
+ try:
+ test_tests(
+ test_pipeline,
+ excludes=excludes,
+ )
+ except KeyboardInterrupt:
+ print("\nAborted.\n")
+ return
+
+ if test_status.failed > 0:
+ print(SEP)
+ show_report(test_pipeline)
-def save_doctest_data(output_data: Dict[tuple, dict]):
+
+def save_doctest_data(doctest_pipeline: DocTestPipeline):
"""
Save doctest tests and test results to a Python PCL file.
@@ -451,9 +715,14 @@ def save_doctest_data(output_data: Dict[tuple, dict]):
* test number
and the value is a dictionary of a Result.getdata() dictionary.
"""
- doctest_latex_data_path = settings.get_doctest_latex_data_path(
- should_be_readable=False, create_parent=True
- )
+ output_data: Dict[tuple, dict] = doctest_pipeline.output_data
+
+ if len(output_data) == 0:
+ print("output data is empty")
+ return
+ print("saving", len(output_data), "entries")
+ print(output_data.keys())
+ doctest_latex_data_path = doctest_pipeline.parameters.data_path
print(f"Writing internal document data to {doctest_latex_data_path}")
i = 0
for key in output_data:
@@ -465,41 +734,47 @@ def save_doctest_data(output_data: Dict[tuple, dict]):
pickle.dump(output_data, output_file, 4)
-def write_doctest_data(quiet=False, reload=False):
+def write_doctest_data(doctest_pipeline: DocTestPipeline):
"""
Get doctest information, which involves running the tests to obtain
test results and write out both the tests and the test results.
"""
- if not quiet:
+ test_parameters = doctest_pipeline.parameters
+ if not test_parameters.quiet:
print(f"Extracting internal doc data for {version_string}")
print("This may take a while...")
try:
- output_data = load_doctest_data() if reload else {}
- for tests in documentation.get_tests():
- create_output(tests, output_data)
+ doctest_pipeline.output_data = (
+ load_doctest_data(test_parameters.data_path)
+ if test_parameters.reload
+ else {}
+ )
+ for tests in doctest_pipeline.documentation.get_tests():
+ create_output(
+ doctest_pipeline,
+ tests,
+ )
except KeyboardInterrupt:
print("\nAborted.\n")
return
print("done.\n")
- save_doctest_data(output_data)
-
-def main():
- global definitions
- global logfile
- global check_partial_elapsed_time
+ save_doctest_data(doctest_pipeline)
- import_and_load_builtins()
- definitions = Definitions(add_builtin=True)
+def build_arg_parser():
+ """Build the argument parser"""
parser = ArgumentParser(description="Mathics test suite.", add_help=False)
parser.add_argument(
"--help", "-h", help="show this help message and exit", action="help"
)
parser.add_argument(
- "--version", "-v", action="version", version="%(prog)s " + mathics.__version__
+ "--version",
+ "-v",
+ action="version",
+ version="%(prog)s " + mathics.__version__,
)
parser.add_argument(
"--chapters",
@@ -523,7 +798,7 @@ def main():
default="",
dest="exclude",
metavar="SECTION",
- help="excude SECTION(s). "
+ help="exclude SECTION(s). "
"You can list multiple sections by adding a comma (and no space) in between section names.",
)
parser.add_argument(
@@ -561,7 +836,10 @@ def main():
"--doc-only",
dest="doc_only",
action="store_true",
- help="generate pickled internal document data without running tests; Can't be used with --section or --reload.",
+ help=(
+ "generate pickled internal document data without running tests; "
+ "Can't be used with --section or --reload."
+ ),
)
parser.add_argument(
"--reload",
@@ -571,7 +849,11 @@ def main():
help="reload pickled internal document data, before possibly adding to it",
)
parser.add_argument(
- "--quiet", "-q", dest="quiet", action="store_true", help="hide passed tests"
+ "--quiet",
+ "-q",
+ dest="quiet",
+ action="store_true",
+ help="hide passed tests",
)
parser.add_argument(
"--keep-going",
@@ -581,7 +863,11 @@ def main():
help="create documentation even if there is a test failure",
)
parser.add_argument(
- "--stop-on-failure", "-x", action="store_true", help="stop on failure"
+ "--stop-on-failure",
+ "-x",
+ dest="stop_on_failure",
+ action="store_true",
+ help="stop on failure",
)
parser.add_argument(
"--skip",
@@ -604,93 +890,54 @@ def main():
action="store_true",
help="print cache statistics",
)
- # FIXME: historically was weird interacting going on with
- # mathics when tests in sorted order. Possibly a
- # mpmath precsion reset bug.
- # We see a noticeable 2 minute delay in processing.
- # WHile the problem is in Mathics itself rather than
- # sorting, until we get this fixed, use
- # sort as an option only. For normal testing we don't
- # want it for speed. But for document building which is
- # rarely done, we do want sorting of the sections and chapters.
- parser.add_argument(
- "--want-sorting",
- dest="want_sorting",
- action="store_true",
- help="Sort chapters and sections",
+ return parser.parse_args()
+
+
+def main():
+ """main"""
+ args = build_arg_parser()
+ data_path = (
+ get_doctest_latex_data_path(should_be_readable=False, create_parent=True)
+ if args.output
+ else None
)
- global logfile
-
- args = parser.parse_args()
-
- if args.elapsed_times:
- check_partial_elapsed_time = True
- # If a test for a specific section is called
- # just test it
- if args.logfilename:
- logfile = open(args.logfilename, "wt")
-
- global documentation
- documentation = MathicsMainDocumentation(want_sorting=args.want_sorting)
-
- # LoadModule Mathics3 modules
- if args.pymathics:
- for module_name in args.pymathics.split(","):
- try:
- eval_LoadModule(module_name, definitions)
- except PyMathicsLoadException:
- print(f"Python module {module_name} is not a Mathics3 module.")
-
- except Exception as e:
- print(f"Python import errors with: {e}.")
- else:
- print(f"Mathics3 Module {module_name} loaded")
- documentation.gather_doctest_data()
+ test_pipeline = DocTestPipeline(args, output_format="latex", data_path=data_path)
+ test_status = test_pipeline.status
if args.sections:
- sections = set(args.sections.split(","))
-
- test_sections(
- sections,
- stop_on_failure=args.stop_on_failure,
- generate_output=args.output,
- reload=args.reload,
- keep_going=args.keep_going,
- )
+ include_sections = set(args.sections.split(","))
+ exclude_subsections = set(args.exclude.split(","))
+ start_time = datetime.now()
+ test_sections(test_pipeline, include_sections, exclude_subsections)
elif args.chapters:
- chapters = set(args.chapters.split(","))
-
- test_chapters(
- chapters, stop_on_failure=args.stop_on_failure, reload=args.reload
- )
+ start_time = datetime.now()
+ include_chapters = set(args.chapters.split(","))
+ exclude_sections = set(args.exclude.split(","))
+ test_chapters(test_pipeline, include_chapters, exclude_sections)
else:
if args.doc_only:
- write_doctest_data(
- quiet=args.quiet,
- reload=args.reload,
- )
+ write_doctest_data(test_pipeline)
else:
excludes = set(args.exclude.split(","))
- start_at = args.skip + 1
start_time = datetime.now()
- test_all(
- quiet=args.quiet,
- generate_output=args.output,
- stop_on_failure=args.stop_on_failure,
- start_at=start_at,
- count=args.count,
- doc_even_if_error=args.keep_going,
- excludes=excludes,
- want_sorting=args.want_sorting,
- )
- end_time = datetime.now()
- print("Tests took ", end_time - start_time)
- if logfile:
- logfile.close()
+ test_all(test_pipeline, excludes=excludes)
+
+ if test_status.total > 0 and start_time is not None:
+ print("Test evaluation took ", datetime.now() - start_time)
+
+ if test_pipeline.logfile:
+ test_pipeline.logfile.close()
if args.show_statistics:
show_lru_cache_statistics()
+ if test_status.failed == 0:
+ print("\nOK")
+ else:
+ print("\nFAILED")
+ sys.exit(1) # Travis-CI knows the tests have failed
+
if __name__ == "__main__":
+ import_and_load_builtins()
main()
diff --git a/mathics/eval/__init__.py b/mathics/eval/__init__.py
index e66f87c70..a883eda56 100644
--- a/mathics/eval/__init__.py
+++ b/mathics/eval/__init__.py
@@ -5,7 +5,7 @@
evaluation. If there were an instruction interpreter, these functions
that start "eval_" would be the interpreter instructions.
-These operatations then should include the most commonly-used Builtin-functions like
+These operations then should include the most commonly-used Builtin-functions like
``N[]`` and routines in support of performing those evaluation operations/instructions.
Performance of the operations here can be important for overall interpreter performance.
diff --git a/mathics/eval/arithmetic.py b/mathics/eval/arithmetic.py
index 8c25a9dc7..16f7fe520 100644
--- a/mathics/eval/arithmetic.py
+++ b/mathics/eval/arithmetic.py
@@ -363,12 +363,10 @@ def eval_mpmath_function(
return call_mpmath(mpmath_function, tuple(float_args), FP_MANTISA_BINARY_DIGITS)
else:
- with mpmath.workprec(prec):
- # to_mpmath seems to require that the precision is set from outside
- mpmath_args = [x.to_mpmath() for x in args]
- if None in mpmath_args:
- return
- return call_mpmath(mpmath_function, tuple(mpmath_args), prec)
+ mpmath_args = [x.to_mpmath(prec) for x in args]
+ if None in mpmath_args:
+ return
+ return call_mpmath(mpmath_function, tuple(mpmath_args), prec)
def eval_Exponential(exp: BaseElement) -> BaseElement:
diff --git a/mathics/eval/files_io/__init__.py b/mathics/eval/files_io/__init__.py
new file mode 100644
index 000000000..5f73ccc85
--- /dev/null
+++ b/mathics/eval/files_io/__init__.py
@@ -0,0 +1,3 @@
+"""
+Evaluation methods in support of Input/Output, Files, and the Filesystem.
+"""
diff --git a/mathics/eval/files_io/files.py b/mathics/eval/files_io/files.py
new file mode 100644
index 000000000..3678f6de1
--- /dev/null
+++ b/mathics/eval/files_io/files.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+"""
+File related evaluation functions.
+"""
+
+from typing import Callable, Optional
+
+from mathics_scanner import TranslateError
+
+import mathics
+from mathics.core.builtin import MessageException
+from mathics.core.evaluation import Evaluation
+from mathics.core.parser import MathicsFileLineFeeder, parse
+from mathics.core.read import MathicsOpen
+from mathics.core.symbols import SymbolNull
+from mathics.core.systemsymbols import SymbolFailed, SymbolPath
+from mathics.core.util import canonic_filename
+
+# Python representation of $InputFileName. On Windows platforms, we
+# canonicalize this to its Posix equivalent name.
+# FIXME: Remove this as a module-level variable and instead
+# define it in a session definitions object.
+# With this, multiple sessions will have separate
+# $InputFilename
+INPUT_VAR: str = ""
+
+
+def set_input_var(input_string: str):
+ """
+ Allow INPUT_VAR to get set, e.g. from main program.
+ """
+ global INPUT_VAR
+ INPUT_VAR = canonic_filename(input_string)
+
+
+def eval_Get(path: str, evaluation: Evaluation, trace_fn: Optional[Callable]):
+ """
+ Reads a file and evaluates each expression, returning only the last one.
+ """
+
+ path = canonic_filename(path)
+ result = None
+ definitions = evaluation.definitions
+
+ # Wrap actual evaluation to handle setting $Input
+ # and $InputFileName
+ # store input paths of calling context
+
+ global INPUT_VAR
+ outer_input_var = INPUT_VAR
+ outer_inputfile = definitions.get_inputfile()
+
+ # set new input paths
+ INPUT_VAR = path
+ definitions.set_inputfile(INPUT_VAR)
+
+ mathics.core.streams.PATH_VAR = SymbolPath.evaluate(evaluation).to_python(
+ string_quotes=False
+ )
+ if trace_fn is not None:
+ trace_fn(path)
+ try:
+ with MathicsOpen(path, "r") as f:
+ feeder = MathicsFileLineFeeder(f, trace_fn)
+ while not feeder.empty():
+ try:
+ query = parse(definitions, feeder)
+ except TranslateError:
+ return SymbolNull
+ finally:
+ feeder.send_messages(evaluation)
+ if query is None: # blank line / comment
+ continue
+ result = query.evaluate(evaluation)
+ except IOError:
+ evaluation.message("General", "noopen", path)
+ return SymbolFailed
+ except MessageException as e:
+ e.message(evaluation)
+ return SymbolFailed
+ finally:
+ # Always restore input paths of calling context.
+ INPUT_VAR = outer_input_var
+ definitions.set_inputfile(outer_inputfile)
+ return result
diff --git a/mathics/eval/image.py b/mathics/eval/image.py
index 18bb6dee6..acf4874ae 100644
--- a/mathics/eval/image.py
+++ b/mathics/eval/image.py
@@ -12,8 +12,8 @@
import PIL
import PIL.Image
-from mathics.builtin.base import String
from mathics.core.atoms import Rational
+from mathics.core.builtin import String
from mathics.core.convert.python import from_python
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
@@ -92,7 +92,6 @@ def extract_exif(image, evaluation: Evaluation) -> Optional[Expression]:
Return None if there is no Exif information.
"""
if hasattr(image, "getexif"):
-
# PIL seems to have a bug in getting v2_tags,
# specifically tag offsets because
# it expects image.fp to exist and for us it
@@ -115,7 +114,7 @@ def extract_exif(image, evaluation: Evaluation) -> Optional[Expression]:
# EXIF has the following types: Short, Long, Rational, Ascii, Byte
# (see http://www.exiv2.org/tags.html). we detect the type from the
- # Python type Pillow gives us and do the appropiate MMA handling.
+ # Python type Pillow gives us and do the appropriate MMA handling.
if isinstance(v, tuple) and len(v) == 2: # Rational
value = Rational(v[0], v[1])
@@ -299,7 +298,7 @@ def resize_width_height(
return image.filter(lambda im: im.resize((width, height), resample=resample))
- # The Below code is hand-crapted Guassian resampling code, which is what
+ # The Below code is hand-crapted Gaussian resampling code, which is what
# WMA does. For now, are going to punt on this, and we use PIL methods only.
# Gaussian need sto unrounded values to compute scaling ratios.
@@ -331,7 +330,7 @@ def resize_width_height(
# kwargs = {"downscale": (1.0 / s)}
# # scikit_image in version 0.19 changes the resize parameter deprecating
# # "multichannel". scikit_image also doesn't support older Pythons like 3.6.15.
- # # If we drop suport for 3.6 we can probably remove
+ # # If we drop support for 3.6 we can probably remove
# if skimage_version >= "0.19":
# # Not totally sure that we want channel_axis=1, but it makes the
# # test work. multichannel is deprecated in scikit-image-19.2
diff --git a/mathics/eval/makeboxes.py b/mathics/eval/makeboxes.py
index 5ecd4e65e..770d53d7b 100644
--- a/mathics/eval/makeboxes.py
+++ b/mathics/eval/makeboxes.py
@@ -7,7 +7,7 @@
import typing
-from typing import Any, Dict, Type
+from typing import Any, Dict, Optional, Type
from mathics.core.atoms import Complex, Integer, Rational, Real, String, SymbolI
from mathics.core.convert.expression import to_expression_with_specialization
@@ -67,6 +67,84 @@ def _boxed_string(string: str, **options):
return StyleBox(String(string), **options)
+# 640 = sys.int_info.str_digits_check_threshold.
+# Someday when 3.11 is the minimum version of Python supported,
+# we can replace the magic value 640 below with sys.int.str_digits_check_threshold.
+def int_to_string_shorter_repr(value: Integer, form: Symbol, max_digits=640):
+ """Convert value to a String, restricted to max_digits characters.
+
+ if value has an n-digit decimal representation,
+ value = d_1 *10^{n-1} d_2 * 10^{n-2} + d_3 10^{n-3} + ..... +
+ d_{n-2}*100 +d_{n-1}*10 + d_{n}
+ is represented as the string
+
+ "d_1d_2d_3...d_{k}<>d_{n-k-1}...d_{n-2}d_{n-1}d_{n}"
+
+ where n-2k digits are replaced by a placeholder.
+ """
+ if max_digits == 0:
+ return String(str(value))
+
+ # Normalize to positive quantities
+ is_negative = value < 0
+ if is_negative:
+ value = -value
+ max_digits = max_digits - 1
+
+ # Estimate the number of decimal digits
+ num_digits = int(value.bit_length() * 0.3)
+
+ # If the estimated number is below the threshold,
+ # return it as it is.
+ if num_digits <= max_digits:
+ if is_negative:
+ return String("-" + str(value))
+ return String(str(value))
+
+ # estimate the size of the placeholder
+ size_placeholder = len(str(num_digits)) + 6
+ # Estimate the number of available decimal places
+ avaliable_digits = max(max_digits - size_placeholder, 0)
+ # how many most significative digits include
+ len_msd = (avaliable_digits + 1) // 2
+ # how many least significative digits to include:
+ len_lsd = avaliable_digits - len_msd
+ # Compute the msd.
+ msd = str(value // 10 ** (num_digits - len_msd))
+ if msd == "0":
+ msd = ""
+
+ # If msd has more digits than the expected, it means that
+ # num_digits was wrong.
+ extra_msd_digits = len(msd) - len_msd
+ if extra_msd_digits > 0:
+ # Remove the extra digit and fix the real
+ # number of digits.
+ msd = msd[:len_msd]
+ num_digits = num_digits + 1
+
+ lsd = ""
+ if len_lsd > 0:
+ lsd = str(value % 10 ** (len_lsd))
+ # complete decimal positions in the lsd:
+ lsd = (len_lsd - len(lsd)) * "0" + lsd
+
+ # Now, compute the true number of hiding
+ # decimal places, and built the placeholder
+ remaining = num_digits - len_lsd - len_msd
+ placeholder = f" <<{remaining}>> "
+ # Check if the shorten string is actually
+ # shorter than the full string representation:
+ if len(placeholder) < remaining:
+ value_str = f"{msd}{placeholder}{lsd}"
+ else:
+ value_str = str(value)
+
+ if is_negative:
+ value_str = "-" + value_str
+ return String(value_str)
+
+
def eval_fullform_makeboxes(
self, expr, evaluation: Evaluation, form=SymbolStandardForm
) -> Expression:
@@ -81,9 +159,7 @@ def eval_fullform_makeboxes(
return Expression(SymbolMakeBoxes, expr, form).evaluate(evaluation)
-def eval_makeboxes(
- self, expr, evaluation: Evaluation, form=SymbolStandardForm
-) -> Expression:
+def eval_makeboxes(expr, evaluation: Evaluation, form=SymbolStandardForm) -> Expression:
"""
This function takes the definitions provided by the evaluation
object, and produces a boxed fullform for expr.
@@ -316,7 +392,10 @@ def do_format_expression(
def parenthesize(
- precedence: int, element: Type[BaseElement], element_boxes, when_equal: bool
+ precedence: Optional[int],
+ element: Type[BaseElement],
+ element_boxes,
+ when_equal: bool,
) -> Type[Expression]:
"""
"Determines if ``element_boxes`` needs to be surrounded with parenthesis.
diff --git a/mathics/eval/nevaluator.py b/mathics/eval/nevaluator.py
index 79d872507..c662f8b30 100644
--- a/mathics/eval/nevaluator.py
+++ b/mathics/eval/nevaluator.py
@@ -90,7 +90,7 @@ def eval_NValues(
# Here we look for the NValues associated to the
# lookup_name of the expression.
- # If a rule is found and successfuly applied,
+ # If a rule is found and successfully applied,
# reevaluate the result and apply `eval_NValues` again.
# This should be implemented as a loop instead of
# recursively.
diff --git a/mathics/eval/numbers/__init__.py b/mathics/eval/numbers/__init__.py
new file mode 100644
index 000000000..6166b84b7
--- /dev/null
+++ b/mathics/eval/numbers/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+"""
+Implementation of mathics.builtin.numbers
+"""
diff --git a/mathics/eval/numbers/algebra/__init__.py b/mathics/eval/numbers/algebra/__init__.py
new file mode 100644
index 000000000..20769ed33
--- /dev/null
+++ b/mathics/eval/numbers/algebra/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+"""
+Implementation of mathics.builtin.numbers.algebra
+"""
diff --git a/mathics/algorithm/simplify.py b/mathics/eval/numbers/algebra/simplify.py
similarity index 100%
rename from mathics/algorithm/simplify.py
rename to mathics/eval/numbers/algebra/simplify.py
diff --git a/mathics/eval/numbers/calculus/__init__.py b/mathics/eval/numbers/calculus/__init__.py
new file mode 100644
index 000000000..5f2e067a0
--- /dev/null
+++ b/mathics/eval/numbers/calculus/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+"""
+Implementation of mathics.builtin.numbers.calculus
+"""
diff --git a/mathics/algorithm/integrators.py b/mathics/eval/numbers/calculus/integrators.py
similarity index 99%
rename from mathics/algorithm/integrators.py
rename to mathics/eval/numbers/calculus/integrators.py
index e10ef8999..31e9d4884 100644
--- a/mathics/algorithm/integrators.py
+++ b/mathics/eval/numbers/calculus/integrators.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
-
+"""
+Implementation of builtin function integrators.
+"""
import numpy as np
from mathics.core.atoms import Integer, Integer0, Number
diff --git a/mathics/algorithm/optimizers.py b/mathics/eval/numbers/calculus/optimizers.py
similarity index 98%
rename from mathics/algorithm/optimizers.py
rename to mathics/eval/numbers/calculus/optimizers.py
index 6fd40270b..4798f1362 100644
--- a/mathics/algorithm/optimizers.py
+++ b/mathics/eval/numbers/calculus/optimizers.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
-
+"""
+Implementation of builtin optimizers.
+"""
from typing import Optional
from mathics.builtin.scoping import dynamic_scoping
@@ -389,9 +391,9 @@ def is_zero(
eps_expr: BaseElement = Integer10 ** (-prec_goal) if prec_goal else Integer0
if acc_goal:
eps_expr = eps_expr + Integer10 ** (-acc_goal) / abs(val)
- threeshold_expr = Expression(SymbolLog, eps_expr)
- threeshold: Real = eval_N(threeshold_expr, evaluation)
- return threeshold.to_python() > 0
+ threshold_expr = Expression(SymbolLog, eps_expr)
+ threshold: Real = eval_N(threshold_expr, evaluation)
+ return threshold.to_python() > 0
def determine_epsilon(x0: Real, options: dict, evaluation: Evaluation) -> Real:
diff --git a/mathics/algorithm/series.py b/mathics/eval/numbers/calculus/series.py
similarity index 99%
rename from mathics/algorithm/series.py
rename to mathics/eval/numbers/calculus/series.py
index c627fd7ec..a998a0c3d 100644
--- a/mathics/algorithm/series.py
+++ b/mathics/eval/numbers/calculus/series.py
@@ -1,3 +1,7 @@
+# -*- coding: utf-8 -*-
+"""
+Implementation of Series handling functions.
+"""
from mathics.core.atoms import Integer, Integer0, Rational
from mathics.core.convert.expression import to_mathics_list
from mathics.core.expression import Expression
@@ -77,7 +81,7 @@ def same_monomial(expr, x, x0):
# coeffs_powers = []
# coeffs_x = []
# for element in elements:
-# if x.sameQ(elemnt):
+# if x.sameQ(element):
# coeffs_x.append(x)
# elif isinstance(element, Atom):
# coeffs_free.append(element)
@@ -396,7 +400,7 @@ def build_series(f, x, x0, n, evaluation):
*[
build_series(element, x, x0, Integer(n), evaluation)
for element in f.elements
- ]
+ ],
)
data.append(newcoeff)
data = ListExpression(*data).evaluate(evaluation)
diff --git a/mathics/eval/numbers.py b/mathics/eval/numbers/numbers.py
similarity index 98%
rename from mathics/eval/numbers.py
rename to mathics/eval/numbers/numbers.py
index 7389ac8d3..628043d4e 100644
--- a/mathics/eval/numbers.py
+++ b/mathics/eval/numbers/numbers.py
@@ -1,3 +1,8 @@
+# -*- coding: utf-8 -*-
+"""
+Implementation of numbers handling functions.
+"""
+
from typing import Optional
import mpmath
diff --git a/mathics/eval/parts.py b/mathics/eval/parts.py
index 61a1adf33..910f96ed5 100644
--- a/mathics/eval/parts.py
+++ b/mathics/eval/parts.py
@@ -59,7 +59,7 @@ def get_subpart(sub_expression: BaseElement, sub_indices: List[int]) -> BaseElem
def set_part(expression, indices: List[int], new_atom: Atom) -> BaseElement:
- """Replace all parts of ``expression`` specified by ``indicies`` with
+ """Replace all parts of ``expression`` specified by ``indices`` with
``new_atom`. Return the modified compound expression.
"""
@@ -435,7 +435,7 @@ def python_seq(start, stop, step, length):
if start == 0 or stop == 0:
return None
- # wrap negative values to postive and convert from 1-based to 0-based
+ # wrap negative values to positive and convert from 1-based to 0-based
if start < 0:
start += length
else:
@@ -547,7 +547,7 @@ def sliced(x, s):
def deletecases_with_levelspec(expr, pattern, evaluation, levelspec=1, n=-1):
"""
- This function walks the expression `expr` and deleting occurrencies of `pattern`
+ This function walks the expression `expr` and deleting occurrences of `pattern`
If levelspec specifies a number, only those positions with
`levelspec` "coordinates" are return. By default, it just return
diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py
index 691616bbc..a7edb5a0d 100644
--- a/mathics/eval/plot.py
+++ b/mathics/eval/plot.py
@@ -248,7 +248,7 @@ def eval_ListPlot(
is_axis_filling = is_discrete_plot
if filling == "System`Axis":
- # TODO: Handle arbitary axis intercepts
+ # TODO: Handle arbitrary axis intercepts
filling = 0.0
is_axis_filling = True
elif filling == "System`Bottom":
diff --git a/mathics/eval/pymathics.py b/mathics/eval/pymathics.py
index d5ef66dfb..f61b41570 100644
--- a/mathics/eval/pymathics.py
+++ b/mathics/eval/pymathics.py
@@ -6,7 +6,7 @@
import inspect
import sys
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
from mathics.core.definitions import Definitions
from mathics.core.load_builtin import builtins_by_module, name_is_builtin_symbol
diff --git a/mathics/eval/quantities.py b/mathics/eval/quantities.py
new file mode 100644
index 000000000..abeaffba8
--- /dev/null
+++ b/mathics/eval/quantities.py
@@ -0,0 +1,296 @@
+# -*- coding: utf-8 -*-
+"""
+Implementation of mathics.builtin.quantities
+"""
+from typing import Optional
+
+from pint import UnitRegistry
+from pint.errors import DimensionalityError, UndefinedUnitError
+
+from mathics.core.atoms import (
+ Integer,
+ Integer0,
+ Integer1,
+ IntegerM1,
+ Number,
+ Rational,
+ Real,
+ String,
+)
+from mathics.core.element import BaseElement
+from mathics.core.evaluation import Evaluation
+from mathics.core.expression import Expression
+from mathics.core.systemsymbols import SymbolPower, SymbolQuantity, SymbolTimes
+
+ureg = UnitRegistry()
+Q_ = ureg.Quantity
+
+
+def add_quantities(
+ mag_1: float, u_1: BaseElement, mag_2: float, u_2: BaseElement, evaluation=None
+) -> Expression:
+ """Try to add two quantities"""
+ cmp = compare_units(u_1, u_2)
+ if cmp is None:
+ return None
+ if cmp == 1:
+ conv = convert_units(Integer1, u_1, u_2, evaluation).elements[0]
+ if conv is not Integer1:
+ mag_1 = conv * mag_1
+ u_1 = u_2
+ elif cmp == -1:
+ conv = convert_units(Integer1, u_2, u_1, evaluation).elements[0]
+ if conv is not Integer1:
+ mag_2 = conv * mag_2
+ mag = mag_1 + mag_2
+ if evaluation:
+ mag = mag.evaluate(evaluation)
+ return Expression(SymbolQuantity, mag, u_1)
+
+
+def compare_units(u_1: BaseElement, u_2: BaseElement) -> Optional[int]:
+ """
+ Compare two units.
+ if both units are equal, return 0.
+ If u1>u2 returns 1
+ If u1 1 else -1
+
+
+def convert_units(
+ magnitude: BaseElement,
+ src: BaseElement,
+ tgt: Optional[BaseElement] = None,
+ evaluation: Optional[Evaluation] = None,
+) -> Expression:
+ """
+ Implement the unit conversion
+
+ The Python "pint" library mixes in a Python numeric value as a multiplier inside
+ a Mathics Expression. Here we pick out that multiplier and
+ convert it from a Python numeric to a Mathics numeric.
+ """
+ assert isinstance(magnitude, Number)
+ assert isinstance(src, BaseElement)
+ assert tgt is None or isinstance(tgt, BaseElement)
+ src_unit: str = expression_to_pint_string(src)
+
+ if tgt is not None:
+ tgt_unit: Optional[str] = expression_to_pint_string(tgt)
+ try:
+ converted_quantity = Q_(1, src_unit).to(tgt_unit)
+ except (UndefinedUnitError, DimensionalityError) as exc:
+ raise ValueError("incompatible or undefined units") from exc
+ else:
+ converted_quantity = Q_(1, src_unit).to_base_units()
+
+ tgt_unit = str(converted_quantity.units)
+ scale = round_if_possible(converted_quantity.magnitude)
+
+ if is_multiplicative(src_unit) and is_multiplicative(tgt_unit):
+ if scale is not Integer1:
+ magnitude = scale * magnitude
+ else:
+ offset = round_if_possible(Q_(0, src_unit).to(tgt_unit).magnitude)
+ if offset is not Integer0:
+ scale = round_if_possible(scale.value - offset.value)
+ if scale.value != 1:
+ magnitude = magnitude * scale
+ magnitude = magnitude + offset
+ else:
+ magnitude = scale * magnitude
+
+ # If evaluation is provided, try to simplify
+ if evaluation is not None:
+ magnitude = magnitude.evaluate(evaluation)
+ return Expression(SymbolQuantity, magnitude, pint_str_to_expression(tgt_unit))
+
+
+def expression_to_pint_string(expr: BaseElement) -> str:
+ """
+ Convert a unit expression to a string
+ compatible with pint
+ """
+ if isinstance(expr, String):
+ result = expr.value
+ elif expr.has_form("Times", None):
+ result = "*".join(expression_to_pint_string(factor) for factor in expr.elements)
+ elif expr.has_form("Power", 2):
+ base, power = expr.elements
+ if not isinstance(power, Integer):
+ raise ValueError("invalid unit expression")
+ result = f" (({expression_to_pint_string(base)})**{power.value}) "
+ else:
+ raise ValueError("invalid unit expression")
+ return normalize_unit_name(result)
+
+
+def is_multiplicative(unit: str) -> bool:
+ """
+ Check if a quantity is multiplicative. For example,
+ centimeters are "multiplicative" because is a multiple
+ of its basis unit "meter"
+ On the other hand, "celsius" is not: the magnitude in Celsius
+ is the magnitude in Kelvin plus an offset.
+ """
+ # unit = normalize_unit_name(unit)
+ try:
+ return ureg._units[unit].converter.is_multiplicative
+ except (UndefinedUnitError, KeyError):
+ try:
+ unit = ureg.get_name(unit)
+ except UndefinedUnitError:
+ # if not found, assume it is
+ return True
+ try:
+ return ureg._units[unit].converter.is_multiplicative
+ except (UndefinedUnitError, KeyError):
+ # if not found, assume it is
+ return True
+
+
+def normalize_unit_expression(unit: BaseElement) -> str:
+ """Normalize the expression representing a unit"""
+ unit_str = expression_to_pint_string(unit)
+ return pint_str_to_expression(unit_str)
+
+
+def normalize_unit_expression_with_magnitude(
+ unit: BaseElement, magnitude: BaseElement
+) -> str:
+ """
+ Normalize the expression representing a unit,
+ taking into account the numeric value
+ """
+ unit_str = expression_to_pint_string(unit)
+
+ m = magnitude.value if isinstance(magnitude, Number) else 2.0
+ unit_str = normalize_unit_name_with_magnitude(unit_str, m)
+ return pint_str_to_expression(unit_str)
+
+
+def normalize_unit_name(unit: str) -> str:
+ """The normalized name of a unit"""
+ return normalize_unit_name_with_magnitude(unit, 1)
+
+
+def normalize_unit_name_with_magnitude(unit: str, magnitude) -> str:
+ """The normalized name of a unit"""
+ unit = unit.strip()
+ try:
+ return str(Q_(magnitude, unit).units)
+ except UndefinedUnitError:
+ unit = unit.replace(" ", "_")
+ unit.replace("_*", " *")
+ unit.replace("*_", "* ")
+ unit.replace("/_", "/ ")
+ unit.replace("_/", " /")
+ unit.replace("_(", " (")
+ unit.replace(")_", ") ")
+
+ try:
+ return str(Q_(magnitude, unit).units)
+ except UndefinedUnitError:
+ unit = unit.lower()
+
+ try:
+ return str(Q_(magnitude, unit).units)
+ except UndefinedUnitError as exc:
+ raise ValueError("undefined units") from exc
+
+
+def pint_str_to_expression(unit: str) -> BaseElement:
+ """
+ Produce a Mathics Expression from a pint unit expression
+ """
+ assert isinstance(unit, str)
+ unit = normalize_unit_name(unit)
+
+ factors = unit.split(" / ")
+ factor = factors[0]
+ divisors = factors[1:]
+ factors = factor.split(" * ")
+
+ def process_factor(factor):
+ base_and_power = factor.split(" ** ")
+ if len(base_and_power) == 1:
+ return String(normalize_unit_name(factor))
+ base, power = base_and_power
+ power_mathics = Integer(int(power))
+ base_mathics = String(normalize_unit_name(base))
+ return Expression(SymbolPower, base_mathics, power_mathics)
+
+ factors_mathics = [process_factor(factor) for factor in factors] + [
+ Expression(SymbolPower, process_factor(factor), IntegerM1)
+ for factor in divisors
+ ]
+ if len(factors_mathics) == 1:
+ return factors_mathics[0]
+ return Expression(SymbolTimes, *factors_mathics)
+
+
+def round_if_possible(x_float: float) -> Number:
+ """
+ Produce an exact Mathics number from x
+ when it is possible.
+ If x is integer, return Integer(x)
+ If 1/x is integer, return Rational(1,1/x)
+ Otherwise, return Real(x)
+ """
+ if x_float - int(x_float) == 0:
+ return Integer(x_float)
+
+ inv_x = 1 / x_float
+ if inv_x == int(inv_x):
+ return Rational(1, int(inv_x))
+ return Real(x_float)
+
+
+def validate_pint_unit(unit: str) -> bool:
+ """Test if `unit` is a valid unit"""
+ try:
+ ureg.get_name(unit)
+ except UndefinedUnitError:
+ unit = unit.lower().replace(" ", "_")
+ else:
+ return True
+
+ try:
+ ureg.get_name(unit)
+ except UndefinedUnitError:
+ return False
+ return True
+
+
+def validate_unit_expression(unit: BaseElement) -> bool:
+ """Test if `unit` is a valid unit"""
+ if isinstance(unit, String):
+ return validate_pint_unit(unit.value)
+ if unit.has_form("Power", 2):
+ base, exp = unit.elements
+ if not isinstance(exp, Integer):
+ return False
+ return validate_unit_expression(base)
+ if unit.has_form("Times", None):
+ return all(validate_unit_expression(factor) for factor in unit.elements)
+ return False
diff --git a/mathics/eval/tensors.py b/mathics/eval/tensors.py
new file mode 100644
index 000000000..35e11208d
--- /dev/null
+++ b/mathics/eval/tensors.py
@@ -0,0 +1,318 @@
+from typing import Union
+
+from sympy.combinatorics import Permutation
+from sympy.utilities.iterables import permutations
+
+from mathics.core.atoms import Integer, Integer0, Integer1, String
+from mathics.core.convert.python import from_python
+from mathics.core.evaluation import Evaluation
+from mathics.core.expression import BaseElement, Expression
+from mathics.core.list import ListExpression
+from mathics.core.symbols import (
+ Atom,
+ Symbol,
+ SymbolFalse,
+ SymbolList,
+ SymbolTimes,
+ SymbolTrue,
+)
+from mathics.core.systemsymbols import (
+ SymbolAutomatic,
+ SymbolNormal,
+ SymbolRule,
+ SymbolSparseArray,
+)
+from mathics.eval.parts import get_part
+
+
+def get_default_distance(p):
+ if all(q.is_numeric() for q in p):
+ return Symbol("SquaredEuclideanDistance")
+ elif all(q.get_head_name() == "System`List" for q in p):
+ dimensions = [get_dimensions(q) for q in p]
+ if len(dimensions) < 1:
+ return None
+ d0 = dimensions[0]
+ if not all(d == d0 for d in dimensions[1:]):
+ return None
+ if len(dimensions[0]) == 1: # vectors?
+
+ def is_boolean(x):
+ return x.get_head_name() == "System`Symbol" and x in (
+ SymbolTrue,
+ SymbolFalse,
+ )
+
+ if all(all(is_boolean(e) for e in q.elements) for q in p):
+ return Symbol("JaccardDissimilarity")
+ return Symbol("SquaredEuclideanDistance")
+ elif all(isinstance(q, String) for q in p):
+ return Symbol("EditDistance")
+ else:
+ from mathics.builtin.colors.color_directives import expression_to_color
+
+ if all(expression_to_color(q) is not None for q in p):
+ return Symbol("ColorDistance")
+
+ return None
+
+
+def get_dimensions(expr, head=None):
+ if isinstance(expr, Atom):
+ return []
+ else:
+ if head is not None and not expr.head.sameQ(head):
+ return []
+ sub_dim = None
+ sub = []
+ for element in expr.elements:
+ sub = get_dimensions(element, expr.head)
+ if sub_dim is None:
+ sub_dim = sub
+ else:
+ if sub_dim != sub:
+ sub = []
+ break
+ return [len(expr.elements)] + sub
+
+
+def to_std_sparse_array(sparse_array, evaluation: Evaluation):
+ "Get a SparseArray equivalent to input with default value 0."
+
+ if sparse_array.elements[2] == Integer0:
+ return sparse_array
+ else:
+ return Expression(
+ SymbolSparseArray, Expression(SymbolNormal, sparse_array)
+ ).evaluate(evaluation)
+
+
+def construct_outer(lists, current, const_etc: tuple) -> Union[list, BaseElement]:
+ """
+ Recursively unpacks lists to construct outer product.
+ ------------------------------------
+
+ Unlike direct products, outer (tensor) products require traversing the
+ lowest level of each list, hence we recursively unpacking lists until
+ the lowest level is reached.
+
+ Parameters:
+
+ ``item``: the current item to be unpacked (if not at lowest level),
+ or joined to current (if at lowest level)
+
+ ``rest_lists``: the rest of lists to be unpacked
+
+ ``current``: the current lowest level elements
+
+ ``level``: the current level (unused yet, will be used in
+ ``Outer[f_, lists__, n_]`` in the future)
+
+ ``const_etc``: a tuple of functions used in unpacking, remains constant
+ throughout the recursion.
+
+ Format of ``const_etc``:
+
+ ```
+ (
+ cond_next_list, # return True/False to unpack the next list/this list at next level
+ get_elements, # get elements of list, tuple, ListExpression, etc.
+ apply_head, # e.g. lambda elements: Expression(head, *elements)
+ apply_f, # e.g. lambda current: Expression(f, *current)
+ join_elem, # join current lowest level elements (i.e. current) with a new one
+ if_flattened, # True for result as flattened list, False for result as nested list
+ evaluation, # evaluation: Evaluation
+ )
+ ```
+
+ For those unfamiliar with ``construct_outer``, ``ConstructOuterTest``
+ in ``test/eval/test_tensors.py`` provides a detailed introduction and
+ several good examples.
+ """
+ (
+ cond_next_list, # return True when the next list should be unpacked
+ get_elements, # get elements of list, tuple, ListExpression, etc.
+ apply_head, # e.g. lambda elements: Expression(head, *elements)
+ apply_f, # e.g. lambda current: Expression(f, *current)
+ join_elem, # join current lowest level elements (i.e. current) with a new one
+ if_flatten, # True for result as flattened list ({a,b,c,d}), False for result as nested list ({{a,b},{c,d}})
+ evaluation, # evaluation: Evaluation
+ ) = const_etc
+
+ _apply_f = (lambda current: (apply_f(current),)) if if_flatten else apply_f
+
+ # Recursive step of unpacking
+ def _unpack_outer(
+ item, rest_lists, current, level: int
+ ) -> Union[list, BaseElement]:
+ evaluation.check_stopped()
+ if cond_next_list(item, level): # unpack next list
+ if rest_lists:
+ return _unpack_outer(
+ rest_lists[0], rest_lists[1:], join_elem(current, item), 1
+ ) # unpacking of a list always start from level 1
+ else:
+ return _apply_f(join_elem(current, item))
+ else: # unpack this list at next level
+ elements = []
+ action = elements.extend if if_flatten else elements.append
+ # elements.extend flattens the result as list instead of as ListExpression
+ for element in get_elements(item):
+ action(_unpack_outer(element, rest_lists, current, level + 1))
+ return apply_head(elements)
+
+ return _unpack_outer(lists[0], lists[1:], current, 1)
+
+
+def eval_Inner(f, list1, list2, g, evaluation: Evaluation):
+ "Evaluates recursively the inner product of list1 and list2"
+
+ m = get_dimensions(list1)
+ n = get_dimensions(list2)
+
+ if not m or not n:
+ evaluation.message("Inner", "normal")
+ return
+ if list1.get_head() != list2.get_head():
+ evaluation.message("Inner", "heads", list1.get_head(), list2.get_head())
+ return
+ if m[-1] != n[0]:
+ evaluation.message("Inner", "incom", m[-1], len(m), list1, n[0], list2)
+ return
+
+ head = list1.get_head()
+ inner_dim = n[0]
+
+ def rec(i_cur, j_cur, i_rest, j_rest):
+ evaluation.check_stopped()
+ if i_rest:
+ elements = []
+ for i in range(1, i_rest[0] + 1):
+ elements.append(rec(i_cur + [i], j_cur, i_rest[1:], j_rest))
+ return Expression(head, *elements)
+ elif j_rest:
+ elements = []
+ for j in range(1, j_rest[0] + 1):
+ elements.append(rec(i_cur, j_cur + [j], i_rest, j_rest[1:]))
+ return Expression(head, *elements)
+ else:
+
+ def summand(i):
+ part1 = get_part(list1, i_cur + [i])
+ part2 = get_part(list2, [i] + j_cur)
+ return Expression(f, part1, part2)
+
+ part = Expression(g, *[summand(i) for i in range(1, inner_dim + 1)])
+ # cur_expr.elements.append(part)
+ return part
+
+ return rec([], [], m[:-1], n[1:])
+
+
+def eval_Outer(f, lists, evaluation: Evaluation):
+ "Evaluates recursively the outer product of lists"
+
+ if isinstance(lists, Atom):
+ evaluation.message("Outer", "normal")
+ return
+
+ # If f=!=Times, or lists contain both SparseArray and List, then convert all SparseArrays to Lists
+ lists = lists.get_sequence()
+ head = None
+ sparse_to_list = f != SymbolTimes
+ contain_sparse = False
+ contain_list = False
+ for _list in lists:
+ if _list.head.sameQ(SymbolSparseArray):
+ contain_sparse = True
+ if _list.head.sameQ(SymbolList):
+ contain_list = True
+ sparse_to_list = sparse_to_list or (contain_sparse and contain_list)
+ if sparse_to_list:
+ break
+ if sparse_to_list:
+ new_lists = []
+ for _list in lists:
+ if isinstance(_list, Atom):
+ evaluation.message("Outer", "normal")
+ return
+ if sparse_to_list:
+ if _list.head.sameQ(SymbolSparseArray):
+ _list = Expression(SymbolNormal, _list).evaluate(evaluation)
+ new_lists.append(_list)
+ if head is None:
+ head = _list.head
+ elif not _list.head.sameQ(head):
+ evaluation.message("Outer", "heads", head, _list.head)
+ return
+ if sparse_to_list:
+ lists = new_lists
+
+ # head != SparseArray
+ if not head.sameQ(SymbolSparseArray):
+
+ def cond_next_list(item, level) -> bool:
+ return isinstance(item, Atom) or not item.head.sameQ(head)
+
+ etc = (
+ cond_next_list,
+ (lambda item: item.elements), # get_elements
+ (lambda elements: Expression(head, *elements)), # apply_head
+ (lambda current: Expression(f, *current)), # apply_f
+ (lambda current, item: current + (item,)), # join_elem
+ False, # if_flatten
+ evaluation,
+ )
+ return construct_outer(lists, (), etc)
+
+ # head == SparseArray
+ dims = []
+ val = Integer1
+ for _list in lists:
+ _dims, _val = _list.elements[1:3]
+ dims.extend(_dims)
+ val *= _val
+ dims = ListExpression(*dims)
+
+ def sparse_cond_next_list(item, level) -> bool:
+ return isinstance(item, Atom) or not item.head.sameQ(head)
+
+ def sparse_apply_Rule(current) -> tuple:
+ return Expression(SymbolRule, ListExpression(*current[0]), current[1])
+
+ def sparse_join_elem(current, item) -> tuple:
+ return (current[0] + item.elements[0].elements, current[1] * item.elements[1])
+
+ etc = (
+ sparse_cond_next_list,
+ (lambda item: to_std_sparse_array(item, evaluation).elements[3].elements),
+ (lambda elements: elements), # apply_head
+ sparse_apply_Rule, # apply_f
+ sparse_join_elem, # join_elem
+ True, # if_flatten
+ evaluation,
+ )
+ return Expression(
+ SymbolSparseArray,
+ SymbolAutomatic,
+ dims,
+ val,
+ ListExpression(*construct_outer(lists, ((), Integer1), etc)),
+ )
+
+
+def eval_LeviCivitaTensor(d, type):
+ "Evaluates Levi-Civita tensor of rank d"
+
+ if isinstance(d, Integer) and type == SymbolSparseArray:
+ d = d.get_int_value()
+ perms = list(permutations(list(range(1, d + 1))))
+ rules = [
+ Expression(
+ SymbolRule,
+ from_python(p),
+ from_python(Permutation.from_sequence(p).signature()),
+ )
+ for p in perms
+ ]
+ return Expression(SymbolSparseArray, from_python(rules), from_python([d] * d))
diff --git a/mathics/eval/testing_expressions.py b/mathics/eval/testing_expressions.py
index 4046d0c8c..c37bb0f6e 100644
--- a/mathics/eval/testing_expressions.py
+++ b/mathics/eval/testing_expressions.py
@@ -2,13 +2,15 @@
import sympy
-from mathics.core.atoms import Complex, Integer0, Integer1, IntegerM1
+from mathics.core.atoms import Complex, Integer, Integer0, Integer1, IntegerM1
+from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
-from mathics.core.systemsymbols import SymbolDirectedInfinity
+from mathics.core.rules import Pattern
+from mathics.core.symbols import SymbolFalse, SymbolTimes, SymbolTrue
+from mathics.core.systemsymbols import SymbolDirectedInfinity, SymbolSparseArray
def do_cmp(x1, x2) -> Optional[int]:
-
# don't attempt to compare complex numbers
for x in (x1, x2):
# TODO: Send message General::nord
@@ -18,7 +20,11 @@ def do_cmp(x1, x2) -> Optional[int]:
return None
s1 = x1.to_sympy()
+ if s1 is None:
+ return None
s2 = x2.to_sympy()
+ if s2 is None:
+ return None
# Use internal comparisons only for Real which is uses
# WL's interpretation of equal (which allows for slop
@@ -34,11 +40,13 @@ def do_cmp(x1, x2) -> Optional[int]:
# we don't want to compare anything that
# cannot be represented as a numeric value
if s1.is_number and s2.is_number:
- if s1 == s2:
+ delta = s1 - s2
+ if delta.is_zero:
return 0
- if s1 < s2:
+ if delta.is_extended_negative:
return -1
- return 1
+ if delta.is_extended_positive:
+ return 1
return None
@@ -99,3 +107,64 @@ def expr_min(elements):
def is_number(sympy_value) -> bool:
return hasattr(sympy_value, "is_number") or isinstance(sympy_value, sympy.Float)
+
+
+def check_ArrayQ(expr, pattern, test, evaluation: Evaluation):
+ "Check if expr is an Array which test yields true for each of its elements."
+
+ pattern = Pattern.create(pattern)
+
+ dims = [len(expr.get_elements())] # to ensure an atom is not an array
+
+ def check(level, expr):
+ if not expr.has_form("List", None):
+ test_expr = Expression(test, expr)
+ if test_expr.evaluate(evaluation) != SymbolTrue:
+ return False
+ level_dim = None
+ else:
+ level_dim = len(expr.elements)
+
+ if len(dims) > level:
+ if dims[level] != level_dim:
+ return False
+ else:
+ dims.append(level_dim)
+ if level_dim is not None:
+ for element in expr.elements:
+ if not check(level + 1, element):
+ return False
+ return True
+
+ if not check(0, expr):
+ return SymbolFalse
+
+ depth = len(dims) - 1 # None doesn't count
+ if not pattern.does_match(Integer(depth), evaluation):
+ return SymbolFalse
+
+ return SymbolTrue
+
+
+def check_SparseArrayQ(expr, pattern, test, evaluation: Evaluation):
+ "Check if expr is a SparseArray which test yields true for each of its elements."
+
+ if not expr.head.sameQ(SymbolSparseArray):
+ return SymbolFalse
+
+ pattern = Pattern.create(pattern)
+ dims, default_value, rules = expr.elements[1:]
+ if not pattern.does_match(Integer(len(dims.elements)), evaluation):
+ return SymbolFalse
+
+ array_size = Expression(SymbolTimes, *dims.elements).evaluate(evaluation)
+ if array_size.value > len(rules.elements): # expr is not full
+ test_expr = Expression(test, default_value) # test default value
+ if test_expr.evaluate(evaluation) != SymbolTrue:
+ return SymbolFalse
+ for rule in rules.elements:
+ test_expr = Expression(test, rule.elements[-1])
+ if test_expr.evaluate(evaluation) != SymbolTrue:
+ return SymbolFalse
+
+ return SymbolTrue
diff --git a/mathics/format/__init__.py b/mathics/format/__init__.py
index 3c35e0041..62318a824 100644
--- a/mathics/format/__init__.py
+++ b/mathics/format/__init__.py
@@ -16,7 +16,7 @@
For example, in graphics we may be several different kinds of
renderers, SVG, or Asymptote for a particular kind of graphics Box.
-The front-end nees to decides which format it better suited for it.
+The front-end needs to decides which format it better suited for it.
The Box, however, is created via a particular high-level Form.
As another example, front-end may decide to use MathJaX to render
diff --git a/mathics/format/asy.py b/mathics/format/asy.py
index 3b328ee4b..69873c373 100644
--- a/mathics/format/asy.py
+++ b/mathics/format/asy.py
@@ -503,7 +503,6 @@ def point3dbox(self: Point3DBox, **options) -> str:
def pointbox(self: PointBox, **options) -> str:
-
point_size, _ = self.style.get_style(PointSize, face_element=False)
if point_size is None:
point_size = PointSize(self.graphics, value=DEFAULT_POINT_FACTOR)
@@ -707,7 +706,6 @@ def sphere3dbox(self: Sphere3DBox, **options) -> str:
def tube_3d_box(self: Tube3DBox, **options) -> str:
if not (hasattr(self.graphics, "tube_import_added") and self.tube_import_added):
-
self.graphics.tube_import_added = True
asy_head = "import tube;\n\n"
else:
diff --git a/mathics/format/latex.py b/mathics/format/latex.py
index ac074bbbb..6aee6d034 100644
--- a/mathics/format/latex.py
+++ b/mathics/format/latex.py
@@ -234,7 +234,7 @@ def superscriptbox(self, **options):
base = self.tex_block(tex1, True)
superidx_to_tex = lookup_conversion_method(self.superindex, "latex")
superindx = self.tex_block(superidx_to_tex(self.superindex, **options), True)
- if isinstance(self.superindex, (String, StyleBox)):
+ if len(superindx) == 1 and isinstance(self.superindex, (String, StyleBox)):
return "%s^%s" % (
base,
superindx,
@@ -354,6 +354,8 @@ def graphicsbox(self, elements=None, **options) -> str:
if self.background_color is not None:
color, opacity = asy_color(self.background_color)
+ if opacity is not None:
+ color = color + f"+opacity({opacity})"
asy_background = "filldraw(%s, %s);" % (asy_box, color)
else:
asy_background = ""
@@ -406,7 +408,7 @@ def graphics3dbox(self, elements=None, **options) -> str:
# TODO: Intelligently place the axes on the longest non-middle edge.
# See algorithm used by web graphics in mathics/web/media/graphics.js
- # for details of this. (Projection to sceen etc).
+ # for details of this. (Projection to screen etc).
# Choose axes placement (boundbox edge vertices)
axes_indices = []
@@ -551,13 +553,21 @@ def graphics3dbox(self, elements=None, **options) -> str:
boundbox_asy += "draw(({0}), {1});\n".format(path, pen)
(height, width) = (400, 400) # TODO: Proper size
+
+ # Background color
+ if self.background_color:
+ bg_color, opacity = asy_color(self.background_color)
+ background_directive = "background=" + bg_color + ", "
+ else:
+ background_directive = ""
+
tex = r"""
\begin{{asy}}
import three;
import solids;
size({0}cm, {1}cm);
currentprojection=perspective({2[0]},{2[1]},{2[2]});
-currentlight=light(rgb(0.5,0.5,1), specular=red, (2,0,2), (2,2,2), (0,2,2));
+currentlight=light(rgb(0.5,0.5,1), {5}specular=red, (2,0,2), (2,2,2), (0,2,2));
{3}
{4}
\end{{asy}}
@@ -568,6 +578,7 @@ def graphics3dbox(self, elements=None, **options) -> str:
[vp * max([xmax - xmin, ymax - ymin, zmax - zmin]) for vp in self.viewpoint],
asy,
boundbox_asy,
+ background_directive,
)
return tex
diff --git a/mathics/format/svg.py b/mathics/format/svg.py
index 1e18e472f..302ceecfd 100644
--- a/mathics/format/svg.py
+++ b/mathics/format/svg.py
@@ -199,7 +199,7 @@ def density_plot_box(self, **options):
# since it is a cute idea, it is worthy of comment space... Put
# two triangles together to get a parallelogram. Compute the
# midpoint color in the enter and along all four sides. Then use
- # two overlayed rectangular gradients each at opacity 0.5
+ # two overlaid rectangular gradients each at opacity 0.5
# to go from the center to each of the (square) sides.
svg_data = ["<--DensityPlot-->"]
@@ -258,10 +258,10 @@ def graphics_box(self, elements=None, **options: dict) -> str:
``elements`` could be a ``GraphicsElements`` object,
a tuple or a list.
- Options is a dictionary of Graphics options dictionary. Intersting Graphics options keys:
+ Options is a dictionary of Graphics options dictionary. Interesting Graphics options keys:
``data``: a tuple bounding box information as well as a copy of ``elements``. If given
- this supercedes the information in the ``elements`` parameter.
+ this supersedes the information in the ``elements`` parameter.
``evaluation``: an ``Evaluation`` object that can be used when further evaluation is needed.
"""
@@ -308,17 +308,27 @@ def graphics_box(self, elements=None, **options: dict) -> str:
self.boxwidth = options.get("width", self.boxwidth)
self.boxheight = options.get("height", self.boxheight)
+ tooltip_text = self.tooltip_text if hasattr(self, "tooltip_text") else ""
if self.background_color is not None:
- # FIXME: tests don't seem to cover this secton of code.
+ # FIXME: tests don't seem to cover this section of code.
# Wrap svg_elements in a rectangle
+
+ background = "rgba(100%,100%,100%,100%)"
+ if self.background_color:
+ components = self.background_color.to_rgba()
+ if len(components) == 3:
+ background = "rgb(" + ", ".join(f"{100*c}%" for c in components) + ")"
+ else:
+ background = "rgba(" + ", ".join(f"{100*c}%" for c in components) + ")"
+
svg_body = f"""
{tooltip_text}
{svg_body}
- />"""
+ """
if options.get("noheader", False):
return svg_body
diff --git a/mathics/format/text.py b/mathics/format/text.py
index 3d4be51e4..422ce940a 100644
--- a/mathics/format/text.py
+++ b/mathics/format/text.py
@@ -73,7 +73,7 @@ def gridbox(self, elements=None, **box_options) -> str:
cells = [
[
- # TODO: check if this evaluation is necesary.
+ # TODO: check if this evaluation is necessary.
boxes_to_text(item, **box_options).splitlines()
for item in row
]
diff --git a/mathics/main.py b/mathics/main.py
index 51eb3efbd..1998291d2 100755
--- a/mathics/main.py
+++ b/mathics/main.py
@@ -30,6 +30,7 @@
from mathics.core.rules import BuiltinRule
from mathics.core.streams import stream_manager
from mathics.core.symbols import SymbolNull, strip_context
+from mathics.eval.files_io.files import set_input_var
from mathics.timing import show_lru_cache_statistics
# from mathics.timing import TimeitContextManager
@@ -354,7 +355,7 @@ def main() -> int:
argparser.add_argument(
"--strict-wl-output",
- help="Most WL-output compatible (at the expense of useability).",
+ help="Most WL-output compatible (at the expense of usability).",
action="store_true",
)
@@ -423,6 +424,8 @@ def dump_tracing_stats():
definitions.set_line_no(0)
if args.FILE is not None:
+ set_input_var(args.FILE.name)
+ definitions.set_inputfile(args.FILE.name)
feeder = MathicsFileLineFeeder(args.FILE)
try:
while not feeder.empty():
diff --git a/mathics/packages/DiscreteMath/CombinatoricaV0.9.m b/mathics/packages/DiscreteMath/CombinatoricaV0.9.m
index fd4b2f482..cbcb92834 100644
--- a/mathics/packages/DiscreteMath/CombinatoricaV0.9.m
+++ b/mathics/packages/DiscreteMath/CombinatoricaV0.9.m
@@ -79,7 +79,7 @@
and Graph Theory with Mathematica",
Addison-Wesley Publishing Co.
*)
-(* :Mathematica Version: 2.3
+(* :Mathematica Version: 2.3, Mathics3 version 7.0.0
*)
BeginPackage["DiscreteMath`CombinatoricaV0.91`"]
@@ -204,7 +204,7 @@
Edges::usage = "Edges[g] returns the adjacency matrix of graph g."
-Element::usage = "Element[a,l] returns the lth element of nested list a, where l is a list of indices"
+Element::usage = "In Combinatorica, Element[a,l] returns the lth element of nested list a, where l is a list of indices"<>"\n also, in WMA,\n"<> Element::usage
EmptyGraph::usage = "EmptyGraph[n] generates an empty graph on n vertices."
@@ -596,7 +596,7 @@
Permute[l_List,p_?PermutationQ] := l [[ p ]]
Permute[l_List,p_List] := Map[ (Permute[l,#])&, p] /; (Apply[And, Map[PermutationQ, p]])
-(* Section 1.1.1 Lexicographically Ordered Permutions, Pages 3-4 *)
+(* Section 1.1.1 Lexicographically Ordered Permutations, Pages 3-4 *)
LexicographicPermutations[{}] := {{}}
@@ -683,7 +683,7 @@
]
]
-(* Section 1.1.5 Backtracking and Distict Permutations, Page 12-13 *)
+(* Section 1.1.5 Backtracking and Distinct Permutations, Page 12-13 *)
Backtrack[space_List,partialQ_,solutionQ_,flag_:One] :=
Module[{n=Length[space],all={},done,index,v=2,solution},
index=Prepend[ Table[0,{n-1}],1];
@@ -2600,7 +2600,13 @@
CostOfPath[Graph[g_,_],p_List] := Apply[Plus, Map[(Element[g,#])&,Partition[p,2,1]] ]
-Element[a_List,{index___}] := a[[ index ]]
+(*Element is a Builtin symbol with other meaning in WMA. To make this
+work in Combinatorica, let's just add this rule that does not collide
+with the standard behaviour:*)
+Unprotect[Element];
+Element[a_List,{index___}] := a[[ index ]];
+Protect[Element];
+(**)
TriangleInequalityQ[e_?SquareMatrixQ] :=
Module[{i,j,k,n=Length[e],flag=True},
diff --git a/mathics/packages/Utilities/CleanSlate.m b/mathics/packages/Utilities/CleanSlate.m
index ecf21fda1..fbff34895 100644
--- a/mathics/packages/Utilities/CleanSlate.m
+++ b/mathics/packages/Utilities/CleanSlate.m
@@ -202,7 +202,7 @@ the context search path ($ContextPath) since the CleanSlate package was \
incorrectly specified, or is not on $ContextPath.";
CleanSlate::nopurge = "The context `1` cannot be purged, because it was \
-present when the CleanSlate package was initally read in.";
+present when the CleanSlate package was initially read in.";
CleanSlate::noself = "CleanSlate cannot purge its own context.";
diff --git a/mathics/packages/VectorAnalysis/VectorAnalysis.m b/mathics/packages/VectorAnalysis/VectorAnalysis.m
index bac1eee2d..6efd750fc 100644
--- a/mathics/packages/VectorAnalysis/VectorAnalysis.m
+++ b/mathics/packages/VectorAnalysis/VectorAnalysis.m
@@ -26,7 +26,7 @@
DotProduct::usage =
"DotProduct[v1, v2] gives the dot product between v1 and v2 in three spatial
dimensions. DotProduct[v1, v2, coordsys] gives the dot product of vectors v1
-and v2 in the specified coodrinate system, coordsys.";
+and v2 in the specified coordinate system, coordsys.";
DotProduct[v1_?$IsVecQ, v2_?$IsVecQ, coordsys_:CoordinateSystem] :=
Module[{c1, c2},
@@ -42,7 +42,7 @@
CrossProduct::usage =
"CrossProduct[v1, v2] gives the cross product between v1 and v2 in three
spatial dimensions. DotProduct[v1, v2, coordsys] gives the cross product of
-vectors v1 and v2 in the specified coodrinate system, coordsys.";
+vectors v1 and v2 in the specified coordinate system, coordsys.";
CrossProduct[v1_?$IsVecQ, v2_?$IsVecQ, coordsys_:CoordinateSystem] :=
Module[{c1, c2},
@@ -59,7 +59,7 @@
"ScalarTripleProduct[v1, v2, v3] gives the scalar triple product product
between v1, v2 and v3 in three spatial dimensions.
ScalarTripleProduct[v1, v2, v3, coordsys] gives the scalar triple product of
-vectors v1, v2 and v3 in the specified coodrinate system, coordsys.";
+vectors v1, v2 and v3 in the specified coordinate system, coordsys.";
ScalarTripleProduct[v1_?$IsVecQ, v2_?$IsVecQ, v3_?$IsVecQ,
coordsys_:CoordinateSystem] :=
@@ -116,7 +116,7 @@
(* ============================ Coordinates ============================ *)
Coordinates::usage =
-"Coordinates[] gives the default cordinate variables of the current coordinate
+"Coordinates[] gives the default coordinate variables of the current coordinate
system. Coordinates[coordsys] gives the default coordinate variables of the
specified coordinate system, coordsys.";
@@ -133,8 +133,8 @@
(* ============================= Parameters ============================ *)
Parameters::usage =
-"Parameters[] gives the default paramater variables of the current coordinate
-system. Parameters[coordsys] gives the default paramater variables for the
+"Parameters[] gives the default parameter variables of the current coordinate
+system. Parameters[coordsys] gives the default parameter variables for the
specified coordinate system, coordsys.";
Parameters[] := Parameters[CoordinateSystem];
diff --git a/mathics/session.py b/mathics/session.py
index ae23eaa6c..ccb8dd801 100644
--- a/mathics/session.py
+++ b/mathics/session.py
@@ -13,7 +13,7 @@
from typing import Optional
from mathics.core.definitions import Definitions, autoload_files
-from mathics.core.evaluation import Evaluation
+from mathics.core.evaluation import Evaluation, Result
from mathics.core.parser import MathicsSingleLineFeeder, parse
@@ -79,13 +79,21 @@ def reset(self, add_builtin=True, catch_interrupt=False):
"""
reset the definitions and the evaluation objects.
"""
- self.definitions = Definitions(add_builtin)
+ try:
+ self.definitions = Definitions(add_builtin)
+ except KeyError:
+ from mathics.core.load_builtin import import_and_load_builtins
+
+ import_and_load_builtins()
+ self.definitions = Definitions(add_builtin)
+
self.evaluation = Evaluation(
definitions=self.definitions, catch_interrupt=catch_interrupt
)
self.last_result = None
def evaluate(self, str_expression, timeout=None, form=None):
+ """Parse str_expression and evaluate using the `evaluate` method of the Expression"""
self.evaluation.out.clear()
expr = parse(self.definitions, MathicsSingleLineFeeder(str_expression))
if form is None:
@@ -93,6 +101,23 @@ def evaluate(self, str_expression, timeout=None, form=None):
self.last_result = expr.evaluate(self.evaluation)
return self.last_result
+ def evaluate_as_in_cli(self, str_expression, timeout=None, form=None, src_name=""):
+ """This method parse and evaluate the expression using the session.evaluation.evaluate method"""
+ query = self.evaluation.parse(str_expression, src_name)
+ if query is not None:
+ res = self.evaluation.evaluate(query, timeout=timeout, format=form)
+ else:
+ res = Result(
+ self.evaluation.out,
+ None,
+ self.evaluation.definitions.get_line_no(),
+ None,
+ form,
+ )
+ self.evaluation.out = []
+ self.evaluation.stopped = False
+ return res
+
def format_result(self, str_expression=None, timeout=None, form=None):
if str_expression:
self.evaluate(str_expression, timeout=None, form=None)
@@ -102,8 +127,10 @@ def format_result(self, str_expression=None, timeout=None, form=None):
form = self.form
return res.do_format(self.evaluation, form)
- def parse(self, str_expression):
+ def parse(self, str_expression, src_name=""):
"""
Just parse the expression
"""
- return parse(self.definitions, MathicsSingleLineFeeder(str_expression))
+ return parse(
+ self.definitions, MathicsSingleLineFeeder(str_expression, src_name)
+ )
diff --git a/mathics/settings.py b/mathics/settings.py
index e000412eb..12e3df8c6 100644
--- a/mathics/settings.py
+++ b/mathics/settings.py
@@ -11,6 +11,8 @@
import pkg_resources
+from mathics.core.util import canonic_filename
+
def get_srcdir():
filename = osp.normcase(osp.dirname(osp.abspath(__file__)))
@@ -50,7 +52,7 @@ def get_srcdir():
ROOT_DIR = pkg_resources.resource_filename("mathics", "")
if sys.platform.startswith("win"):
- DATA_DIR = osp.join(os.environ["APPDATA"], "Python", "Mathics")
+ DATA_DIR = canonic_filename(osp.join(os.environ["APPDATA"], "Python", "Mathics"))
else:
DATA_DIR = osp.join(
os.environ.get("APPDATA", osp.expanduser("~/.local/var/mathics/"))
@@ -58,11 +60,11 @@ def get_srcdir():
# In contrast to ROOT_DIR, LOCAL_ROOT_DIR is used in building
# LaTeX documentation. When Mathics is installed, we don't want LaTeX file documentation.tex
-# to get put in the installation directory, but instead we build documentaiton
+# to get put in the installation directory, but instead we build documentation
# from checked-out source and that is where this should be put.
LOCAL_ROOT_DIR = get_srcdir()
-# Location of doctests and test results formated for LaTeX. This data
+# Location of doctests and test results formatted for LaTeX. This data
# is stoared as a Python Pickle format, but storing this in JSON if
# possible would be preferable and faster
diff --git a/mathics/timing.py b/mathics/timing.py
index f8ab01ac7..f33bc8528 100644
--- a/mathics/timing.py
+++ b/mathics/timing.py
@@ -22,10 +22,10 @@ def long_running_function():
def timed(*args, **kw):
method_name = method.__name__
# print(f"{date.today()} {method_name} starts")
- ts = time.time()
+ t_start = time.time()
result = method(*args, **kw)
- te = time.time()
- elapsed = (te - ts) * 1000
+ t_end = time.time()
+ elapsed = (t_end - t_start) * 1000
if elapsed > MIN_ELAPSE_REPORT:
if "log_time" in kw:
name = kw.get("log_name", method.__name__.upper())
@@ -52,11 +52,11 @@ def __init__(self, name: str):
def __enter__(self):
# print(f"{date.today()} {method_name} starts")
- self.ts = time.time()
+ self.t_start = time.time()
def __exit__(self, exc_type, exc_value, exc_tb):
- te = time.time()
- elapsed = (te - self.ts) * 1000
+ t_end = time.time()
+ elapsed = (t_end - self.t_start) * 1000
if elapsed > MIN_ELAPSE_REPORT:
print("%r %2.2f ms" % (self.name, elapsed))
@@ -65,10 +65,9 @@ def show_lru_cache_statistics():
"""
Print statistics from LRU caches (@lru_cache of functools)
"""
- from mathics.builtin.arithmetic import _MPMathFunction
from mathics.builtin.atomic.numbers import log_n_b
- from mathics.builtin.base import run_sympy
from mathics.core.atoms import Integer, Rational
+ from mathics.core.builtin import MPMathFunction, run_sympy
from mathics.core.convert.mpmath import from_mpmath
from mathics.eval.arithmetic import call_mpmath
@@ -77,6 +76,6 @@ def show_lru_cache_statistics():
print(f"call_mpmath {call_mpmath.cache_info()}")
print(f"log_n_b {log_n_b.cache_info()}")
print(f"from_mpmath {from_mpmath.cache_info()}")
- print(f"get_mpmath_function {_MPMathFunction.get_mpmath_function.cache_info()}")
+ print(f"get_mpmath_function {MPMathFunction.get_mpmath_function.cache_info()}")
print(f"run_sympy {run_sympy.cache_info()}")
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 000000000..105d39603
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,111 @@
+[build-system]
+requires = [
+ "setuptools>=70.0.0", # CVE-2024-38335 recommends this
+ "cython>=0.15.1; implementation_name!='pypy'",
+ # For mathics-generate-json-table
+ "Mathics-Scanner >= 1.3.0",
+]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "Mathics3"
+description = "A general-purpose computer algebra system."
+dependencies = [
+ "Mathics-Scanner >= 1.3.0",
+ "llvmlite",
+ "mpmath>=1.2.0",
+ "numpy<1.27",
+ "palettable",
+ # Pillow 9.1.0 supports BigTIFF with big-endian byte order.
+ # ExampleData image hedy.tif is in this format.
+ # Pillow 9.2 handles sunflowers.jpg
+ "pillow >= 9.2",
+ "pint",
+ "python-dateutil",
+ "requests",
+ "setuptools",
+ "sympy>=1.11,<1.13",
+]
+requires-python = ">=3.8" # Sympy 1.11 is supported only down to 3.8
+readme = "README.rst"
+license = {text = "GPL"}
+keywords = ["Mathematica", "Wolfram", "Interpreter", "Shell", "Math", "CAS"]
+maintainers = [
+ {name = "Mathics Group", email = "mathics-devel@googlegroups.com"},
+]
+classifiers = [
+ "Intended Audience :: Developers",
+ "Intended Audience :: Science/Research",
+ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+ "Topic :: Scientific/Engineering",
+ "Topic :: Scientific/Engineering :: Mathematics",
+ "Topic :: Scientific/Engineering :: Physics",
+ "Topic :: Software Development :: Interpreters",
+]
+dynamic = ["version"]
+
+[project.urls]
+Homepage = "https://mathics.org/"
+Downloads = "https://github.com/Mathics3/mathics-core/releases"
+
+[project.optional-dependencies]
+dev = [
+ "pexpect",
+ "pytest",
+]
+full = [
+ "ipywidgets",
+ "lxml",
+ "psutil",
+ "pyocr",
+ "scikit-image >= 0.17",
+ "unidecode",
+ "wordcloud >= 1.9.3",
+]
+cython = [
+ "cython",
+]
+
+[project.scripts]
+mathics = "mathics.main:main"
+
+[tool.setuptools]
+include-package-data = false
+
+[tool.setuptools.packages.find]
+include = ["mathics*"]
+
+[tool.setuptools.package-data]
+"mathics" = [
+ "data/*.csv",
+ "data/*.json",
+ "data/*.yml",
+ "data/*.yaml",
+ "data/*.pcl",
+ "data/ExampleData/*",
+ "doc/xml/data",
+ "doc/tex/data",
+ "autoload/*.m",
+ "autoload-cli/*.m",
+ "autoload/formats/*/Import.m",
+ "autoload/formats/*/Export.m",
+ "packages/*/*.m",
+ "packages/*/Kernel/init.m",
+]
+"mathics.doc" = [
+ "documentation/*.mdoc",
+ "xml/data",
+]
+"mathics.builtin.pymimesniffer" = [
+ "mimetypes.xml",
+]
+
+[tool.setuptools.dynamic]
+version = {attr = "mathics.version.__version__"}
diff --git a/requirements-full.txt b/requirements-full.txt
index 4514496f2..529cc51b7 100644
--- a/requirements-full.txt
+++ b/requirements-full.txt
@@ -5,4 +5,4 @@ psutil # SystemMemory and MemoryAvailable
pyocr # Used for TextRecognize
scikit-image >= 0.17 # FindMinimum can use this; used by Image as well
unidecode # Used in Transliterate
-wordcloud # Used in builtin/image.py by WordCloud()
+wordcloud >= 1.9.3 # Used in builtin/image.py by WordCloud(). Previous versions assume "image.textsize" which no longer exists
diff --git a/setup.py b/setup.py
index ab3663176..d2d61b7d2 100644
--- a/setup.py
+++ b/setup.py
@@ -21,72 +21,34 @@
python setup.py clean -> will clean all trash (*.pyc and stuff)
-To get a full list of avaiable commands, read the output of:
+To get a full list of available commands, read the output of:
python setup.py --help-commands
"""
+import logging
import os
import os.path as osp
import platform
-import re
import sys
from setuptools import Extension, setup
+from setuptools.command.build_py import build_py as setuptools_build_py
+
+log = logging.getLogger(__name__)
+
is_PyPy = platform.python_implementation() == "PyPy" or hasattr(
sys, "pypy_version_info"
)
-INSTALL_REQUIRES = [
- "Mathics-Scanner >= 1.3.0",
-]
-
-# Ensure user has the correct Python version
-# Address specific package dependencies based on Python version
-if sys.version_info < (3, 7):
- print("Mathics does not support Python %d.%d" % sys.version_info[:2])
- sys.exit(-1)
-
-INSTALL_REQUIRES += [
- "numpy<=1.24",
- "llvmlite",
- "sympy>=1.8",
- # Pillow 9.1.0 supports BigTIFF with big-endian byte order.
- # ExampleData image hedy.tif is in this format.
- # Pillow 9.2 handles sunflowers.jpg
- "pillow >= 9.2",
-]
-
-# if not is_PyPy:
-# INSTALL_REQUIRES += ["recordclass"]
-
def get_srcdir():
filename = osp.normcase(osp.dirname(osp.abspath(__file__)))
return osp.realpath(filename)
-def read(*rnames):
- return open(osp.join(get_srcdir(), *rnames)).read()
-
-
-long_description = read("README.rst") + "\n"
-
-# stores __version__ in the current namespace
-exec(compile(open("mathics/version.py").read(), "mathics/version.py", "exec"))
-
-EXTRAS_REQUIRE = {}
-for kind in ("dev", "full", "cython"):
- extras_require = []
- requirements_file = f"requirements-{kind}.txt"
- for line in open(requirements_file).read().split("\n"):
- if line and not line.startswith("#"):
- requires = re.sub(r"([^#]+)(\s*#.*$)?", r"\1", line)
- extras_require.append(requires)
- EXTRAS_REQUIRE[kind] = extras_require
-
DEPENDENCY_LINKS = []
# "http://github.com/Mathics3/mathics-scanner/tarball/master#egg=Mathics_Scanner-1.0.0.dev"
# ]
@@ -103,7 +65,7 @@ def read(*rnames):
pass
else:
if os.environ.get("USE_CYTHON", False):
- print("Running Cython over code base")
+ log.info("Running Cython over code base")
EXTENSIONS_DICT = {
"core": (
"expression",
@@ -134,130 +96,31 @@ def read(*rnames):
# for module in modules
# )
CMDCLASS = {"build_ext": build_ext}
- INSTALL_REQUIRES += ["cython>=0.15.1"]
-# General Requirements
-INSTALL_REQUIRES += [
- "mpmath>=1.2.0",
- "palettable",
- "pint",
- "python-dateutil",
- "requests",
- "setuptools",
-]
-print(f'Installation requires "{", ".join(INSTALL_REQUIRES)}')
+class build_py(setuptools_build_py):
+ def run(self):
+ if not os.path.exists("mathics/data/op-tables.json"):
+ os.system(
+ "mathics-generate-json-table"
+ " --field=ascii-operator-to-symbol"
+ " --field=ascii-operator-to-unicode"
+ " --field=ascii-operator-to-wl-unicode"
+ " --field=operator-to-ascii"
+ " --field=operator-to-unicode"
+ " -o mathics/data/op-tables.json"
+ )
+ self.distribution.package_data["mathics"].append("data/op-tables.json")
+ setuptools_build_py.run(self)
-def subdirs(root, file="*.*", depth=10):
- for k in range(depth):
- yield root + "*/" * k + file
+CMDCLASS["build_py"] = build_py
setup(
- name="Mathics3",
cmdclass=CMDCLASS,
ext_modules=EXTENSIONS,
- version=__version__,
- packages=[
- "mathics",
- "mathics.algorithm",
- "mathics.compile",
- "mathics.core",
- "mathics.core.convert",
- "mathics.core.parser",
- "mathics.builtin",
- "mathics.builtin.arithfns",
- "mathics.builtin.assignments",
- "mathics.builtin.atomic",
- "mathics.builtin.binary",
- "mathics.builtin.box",
- "mathics.builtin.colors",
- "mathics.builtin.distance",
- "mathics.builtin.exp_structure",
- "mathics.builtin.drawing",
- "mathics.builtin.fileformats",
- "mathics.builtin.files_io",
- "mathics.builtin.forms",
- "mathics.builtin.functional",
- "mathics.builtin.image",
- "mathics.builtin.intfns",
- "mathics.builtin.list",
- "mathics.builtin.matrices",
- "mathics.builtin.numbers",
- "mathics.builtin.numpy_utils",
- "mathics.builtin.pymimesniffer",
- "mathics.builtin.pympler",
- "mathics.builtin.quantum_mechanics",
- "mathics.builtin.scipy_utils",
- "mathics.builtin.specialfns",
- "mathics.builtin.statistics",
- "mathics.builtin.string",
- "mathics.builtin.testing_expressions",
- "mathics.builtin.vectors",
- "mathics.eval",
- "mathics.doc",
- "mathics.format",
- ],
- install_requires=INSTALL_REQUIRES,
- extras_require=EXTRAS_REQUIRE,
dependency_links=DEPENDENCY_LINKS,
- package_data={
- "mathics": [
- "data/*.csv",
- "data/*.json",
- "data/*.yml",
- "data/*.yaml",
- "data/*.pcl",
- "data/ExampleData/*",
- "doc/xml/data",
- "doc/tex/data",
- "autoload/*.m",
- "autoload-cli/*.m",
- "autoload/formats/*/Import.m",
- "autoload/formats/*/Export.m",
- "packages/*/*.m",
- "packages/*/Kernel/init.m",
- "requirements-cython.txt",
- "requirements-full.txt",
- ],
- "mathics.doc": ["documentation/*.mdoc", "xml/data"],
- "mathics.builtin.pymimesniffer": ["mimetypes.xml"],
- "pymathics": ["doc/documentation/*.mdoc", "doc/xml/data"],
- },
- entry_points={
- "console_scripts": [
- "mathics = mathics.main:main",
- ],
- },
- long_description=long_description,
- long_description_content_type="text/x-rst",
# don't pack Mathics in egg because of media files, etc.
zip_safe=False,
- # metadata for upload to PyPI
- maintainer="Mathics Group",
- maintainer_email="mathics-devel@googlegroups.com",
- description="A general-purpose computer algebra system.",
- license="GPL",
- url="https://mathics.org/",
- download_url="https://github.com/Mathics/mathics-core/releases",
- keywords=["Mathematica", "Wolfram", "Interpreter", "Shell", "Math", "CAS"],
- classifiers=[
- "Intended Audience :: Developers",
- "Intended Audience :: Science/Research",
- "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
- "Programming Language :: Python",
- "Programming Language :: Python :: 3.6",
- "Programming Language :: Python :: 3.7",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: Implementation :: CPython",
- "Programming Language :: Python :: Implementation :: PyPy",
- "Topic :: Scientific/Engineering",
- "Topic :: Scientific/Engineering :: Mathematics",
- "Topic :: Scientific/Engineering :: Physics",
- "Topic :: Software Development :: Interpreters",
- ],
- # TODO: could also include long_description, download_url,
)
diff --git a/test/builtin/arithmetic/test_basic.py b/test/builtin/arithmetic/test_basic.py
index 9a05aac28..20540bf15 100644
--- a/test/builtin/arithmetic/test_basic.py
+++ b/test/builtin/arithmetic/test_basic.py
@@ -129,6 +129,12 @@ def test_multiply(str_expr, str_expected, msg):
"msg",
),
[
+ (
+ "DirectedInfinity[1+I]+DirectedInfinity[2+I]",
+ "(2 / 5 + I / 5) Sqrt[5] Infinity + (1 / 2 + I / 2) Sqrt[2] Infinity",
+ None,
+ ),
+ ("DirectedInfinity[Sqrt[3]]", "Infinity", None),
(
"a b DirectedInfinity[1. + 2. I]",
"a b ((0.447214 + 0.894427 I) Infinity)",
@@ -168,33 +174,29 @@ def test_directed_infinity_precedence(str_expr, str_expected, msg):
@pytest.mark.parametrize(
- (
- "str_expr",
- "str_expected",
- "msg",
- ),
+ ("str_expr", "str_expected", "expected_message", "fail_msg"),
[
- ("2^0", "1", None),
- ("(2/3)^0", "1", None),
- ("2.^0", "1.", None),
- ("2^1", "2", None),
- ("(2/3)^1", "2 / 3", None),
- ("2.^1", "2.", None),
- ("2^(3)", "8", None),
- ("(1/2)^3", "1 / 8", None),
- ("2^(-3)", "1 / 8", None),
- ("(1/2)^(-3)", "8", None),
- ("(-7)^(5/3)", "-7 (-7) ^ (2 / 3)", None),
- ("3^(1/2)", "Sqrt[3]", None),
+ ("2^0", "1", None, None),
+ ("(2/3)^0", "1", None, None),
+ ("2.^0", "1.", None, None),
+ ("2^1", "2", None, None),
+ ("(2/3)^1", "2 / 3", None, None),
+ ("2.^1", "2.", None, None),
+ ("2^(3)", "8", None, None),
+ ("(1/2)^3", "1 / 8", None, None),
+ ("2^(-3)", "1 / 8", None, None),
+ ("(1/2)^(-3)", "8", None, None),
+ ("(-7)^(5/3)", "-7 (-7) ^ (2 / 3)", None, None),
+ ("3^(1/2)", "Sqrt[3]", None, None),
# WMA do not rationalize numbers
- ("(1/5)^(1/2)", "Sqrt[5] / 5", None),
+ ("(1/5)^(1/2)", "Sqrt[5] / 5", None, None),
# WMA do not rationalize numbers
- ("(3)^(-1/2)", "Sqrt[3] / 3", None),
- ("(1/3)^(-1/2)", "Sqrt[3]", None),
- ("(5/3)^(1/2)", "Sqrt[5 / 3]", None),
- ("(5/3)^(-1/2)", "Sqrt[3 / 5]", None),
- ("1/Sqrt[Pi]", "1 / Sqrt[Pi]", None),
- ("I^(2/3)", "(-1) ^ (1 / 3)", None),
+ ("(3)^(-1/2)", "Sqrt[3] / 3", None, None),
+ ("(1/3)^(-1/2)", "Sqrt[3]", None, None),
+ ("(5/3)^(1/2)", "Sqrt[5 / 3]", None, None),
+ ("(5/3)^(-1/2)", "Sqrt[3 / 5]", None, None),
+ ("1/Sqrt[Pi]", "1 / Sqrt[Pi]", None, None),
+ ("I^(2/3)", "(-1) ^ (1 / 3)", None, None),
# In WMA, the next test would return ``-(-I)^(2/3)``
# which is less compact and elegant...
("(-I)^(2/3)", "(-1) ^ (-1 / 3)", None),
@@ -206,7 +208,7 @@ def test_directed_infinity_precedence(str_expr, str_expected, msg):
# In WMA, the following expression returns
# ``(Pi/3)^I``. By now, this is handled by
# sympy, which produces the result
- ("(3/Pi)^(-I)", "(3 / Pi) ^ (-I)", None),
+ ("(3/Pi)^(-I)", "(3 / Pi) ^ (-I)", None, None),
# Association rules
('(a^"w")^2', 'a^(2 "w")', "Integer power of a power with string exponent"),
('(a^2)^"w"', '(a ^ 2) ^ "w"', None),
@@ -219,9 +221,263 @@ def test_directed_infinity_precedence(str_expr, str_expected, msg):
("(a^(.3))^3.", "a ^ 0.9", "Power associativity for real powers"),
("(a^(1.3))^3.", "(a ^ 1.3) ^ 3.", None),
# Exponentials involving expressions
- ("(a^(p-2 q))^3", "a ^ (3 p - 6 q)", None),
- ("(a^(p-2 q))^3.", "(a ^ (p - 2 q)) ^ 3.", None),
+ ("(a^(p-2 q))^3", "a ^ (3 p - 6 q)", None, None),
+ ("(a^(p-2 q))^3.", "(a ^ (p - 2 q)) ^ 3.", None, None),
+ # Indefinite / ComplexInfinity / Complex powers
+ ("1/0", "ComplexInfinity", "Infinite expression 1 / 0 encountered.", None),
+ (
+ "0 ^ -2",
+ "ComplexInfinity",
+ "Infinite expression 1 / 0 ^ 2 encountered.",
+ None,
+ ),
+ (
+ "0 ^ (-1/2)",
+ "ComplexInfinity",
+ "Infinite expression 1 / Sqrt[0] encountered.",
+ None,
+ ),
+ (
+ "0 ^ -Pi",
+ "ComplexInfinity",
+ "Infinite expression 1 / 0 ^ 3.14159 encountered.",
+ None,
+ ),
+ (
+ "0 ^ (2 I E)",
+ "Indeterminate",
+ "Indeterminate expression 0 ^ (0. + 5.43656 I) encountered.",
+ None,
+ ),
+ (
+ "0 ^ - (Pi + 2 E I)",
+ "ComplexInfinity",
+ "Infinite expression 0 ^ (-3.14159 - 5.43656 I) encountered.",
+ None,
+ ),
+ ("0 ^ 0", "Indeterminate", "Indeterminate expression 0 ^ 0 encountered.", None),
+ ("Sqrt[-3+2. I]", "0.550251 + 1.81735 I", None, None),
+ ("(3/2+1/2I)^2", "2 + 3 I / 2", None, None),
+ ("I ^ I", "(-1) ^ (I / 2)", None, None),
+ ("2 ^ 2.0", "4.", None, None),
+ ("Pi ^ 4.", "97.4091", None, None),
+ ("a ^ b", "a ^ b", None, None),
+ ],
+)
+def test_power(str_expr, str_expected, expected_message, fail_msg):
+ if expected_message is None:
+ check_evaluation(str_expr, str_expected, failure_message=fail_msg)
+ else:
+ check_evaluation(
+ str_expr,
+ str_expected,
+ failure_message=fail_msg,
+ expected_messages=[expected_message],
+ )
+
+
+@pytest.mark.parametrize(
+ (
+ "str_expr",
+ "str_expected",
+ "msg",
+ ),
+ [
+ (None, None, None),
+ # Private tests from mathics.arithmetic.Complex
+ ("Complex[1, Complex[0, 1]]", "0", "Iterated Complex (1 , I)"),
+ ("Complex[1, Complex[1, 0]]", "1 + I", "Iterated Complex (1, 1) "),
+ ("Complex[1, Complex[1, 1]]", "I", "Iterated Complex, (1, 1 + I)"),
+ ("Complex[0., 0.]", "0. + 0. I", "build complex 0.+0. I"),
+ ("Complex[10, 0.]", "10. + 0. I", "build complex"),
+ ("Complex[10, 0]", "10", "build complex"),
+ ("1 + 0. I", "1. + 0. I", None),
+ # Mathics produces "0."
+ # For some weird reason, the following tests
+ # pass if we run this unit test alone, but not
+ # if we run it together all the tests
+ ("0. + 0. I//FullForm", "Complex[0., 0.]", "WMA compatibility"),
+ ("0. I//FullForm", "Complex[0., 0.]", None),
+ ("1. + 0. I//FullForm", "Complex[1., 0.]", None),
+ ("0. + 1. I//FullForm", "Complex[0., 1.]", None),
+ ("1. + 0. I//OutputForm", "1. + 0. I", "Formatted"),
+ ("0. + 1. I//OutputForm", "0. + 1. I", "Formatting 1. I"),
+ ("-2/3-I//FullForm", "Complex[Rational[-2, 3], -1]", "Adding a rational"),
+ ],
+)
+def test_complex(str_expr, str_expected, msg):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ failure_message=msg,
+ to_string_expected=True,
+ # to_string_expr=True,
+ hold_expected=True,
+ )
+
+
+@pytest.mark.parametrize(
+ (
+ "str_expr",
+ "str_expected",
+ "msg",
+ ),
+ [
+ (None, None, None),
+ ("{Conjugate[Pi], Conjugate[E]}", "{Pi, E}", "Issue #272"),
+ ("-2/3", "-2 / 3", "Rational"),
+ ("-2/3//Head", "Rational", "Rational"),
+ (
+ "(-1 + a^n) Sum[a^(k n), {k, 0, m-1}] // Simplify",
+ "-1 + (a ^ n) ^ m",
+ "according to WMA. Now it fails",
+ ),
+ ("1 / 4.0", "0.25", None),
+ ("10 / 3 // FullForm", "Rational[10, 3]", None),
+ ("a / b // FullForm", "Times[a, Power[b, -1]]", None),
+ # Plus
+ ("-2a - 2b", "-2 a - 2 b", None),
+ ("-4+2x+2*Sqrt[3]", "-4 + 2 Sqrt[3] + 2 x", None),
+ ("2a-3b-c", "2 a - 3 b - c", None),
+ ("2a+5d-3b-2c-e", "2 a - 3 b - 2 c + 5 d - e", None),
+ ("1 - I * Sqrt[3]", "1 - I Sqrt[3]", None),
+ ("Head[3 + 2 I]", "Complex", None),
+ # Times
+ ("Times[]// FullForm", "1", None),
+ ("Times[-1]// FullForm", "-1", None),
+ ("Times[-5]// FullForm", "-5", None),
+ ("Times[-5, a]// FullForm", "Times[-5, a]", None),
+ ("-a*b // FullForm", "Times[-1, a, b]", None),
+ ("-(x - 2/3)", "2 / 3 - x", None),
+ ("-x*2", "-2 x", None),
+ ("-(h/2) // FullForm", "Times[Rational[-1, 2], h]", None),
+ ("x / x", "1", None),
+ ("2x^2 / x^2", "2", None),
+ ("3. Pi", "9.42478", None),
+ ("Head[3 * I]", "Complex", None),
+ ("Head[Times[I, 1/2]]", "Complex", None),
+ ("Head[Pi * I]", "Times", None),
+ ("3 * a //InputForm", "3*a", None),
+ ("3 * a //OutputForm", "3 a", None),
+ ("-2.123456789 x", "-2.12346 x", None),
+ ("-2.123456789 I", "0. - 2.12346 I", None),
+ ("N[Pi, 30] * I", "3.14159265358979323846264338328 I", None),
+ ("N[I Pi, 30]", "3.14159265358979323846264338328 I", None),
+ ("N[Pi * E, 30]", "8.53973422267356706546355086955", None),
+ ("N[Pi, 30] * N[E, 30]", "8.53973422267356706546355086955", None),
+ (
+ "N[Pi, 30] * E//{#1, Precision[#1]}&",
+ "{8.53973422267356706546355086955, 30.}",
+ None,
+ ),
+ # Precision
+ (
+ "N[Pi, 30] + N[E, 30]//{#1, Precision[#1]}&",
+ "{5.85987448204883847382293085463, 30.}",
+ None,
+ ),
+ (
+ "N[Sqrt[2], 50]",
+ "1.4142135623730950488016887242096980785696718753769",
+ "N[Sqrt[...]]",
+ ),
+ (
+ "Sum[i / Log[i], {i, 1, Infinity}]",
+ "Sum[i / Log[i], {i, 1, Infinity}]",
+ "Issue #302",
+ ),
+ (
+ "Sum[Cos[Pi i], {i, 1, Infinity}]",
+ "Sum[Cos[i Pi], {i, 1, Infinity}]",
+ "Issue #302",
+ ),
+ (
+ "Sum[x^k*Sum[y^l,{l,0,4}],{k,0,4}]",
+ "1 + x (1 + y + y ^ 2 + y ^ 3 + y ^ 4) + x ^ 2 (1 + y + y ^ 2 + y ^ 3 + y ^ 4) + x ^ 3 (1 + y + y ^ 2 + y ^ 3 + y ^ 4) + x ^ 4 (1 + y + y ^ 2 + y ^ 3 + y ^ 4) + y + y ^ 2 + y ^ 3 + y ^ 4",
+ "Iterated sum",
+ ),
],
)
-def test_power(str_expr, str_expected, msg):
+def test_miscelanea_private_tests(str_expr, str_expected, msg):
+ check_evaluation(str_expr, str_expected, failure_message=msg, hold_expected=True)
+
+
+@pytest.mark.parametrize(
+ (
+ "str_expr",
+ "str_expected",
+ "msg",
+ ),
+ [
+ (
+ "Product[1 + 1 / i ^ 2, {i, Infinity}]",
+ "1 / ((-I)! I!)",
+ (
+ "Used to be a bug in sympy, but now it is solved exactly!\n"
+ "Again a bug in sympy - regressions between 0.7.3 and 0.7.6 (and 0.7.7?)"
+ ),
+ ),
+ ],
+)
+@pytest.mark.xfail
+def test_miscelanea_private_tests_xfail(str_expr, str_expected, msg):
check_evaluation(str_expr, str_expected, failure_message=msg)
+
+
+@pytest.mark.parametrize(
+ (
+ "str_expr",
+ "str_expected",
+ "msgs",
+ "failmsg",
+ ),
+ [
+ ("CubeRoot[-5]", "-5 ^ (1 / 3)", None, None),
+ ("CubeRoot[-510000]", "-10 510 ^ (1 / 3)", None, None),
+ ("CubeRoot[-5.1]", "-1.7213", None, None),
+ ("CubeRoot[b]", "b ^ (1 / 3)", None, None),
+ ("CubeRoot[-0.5]", "-0.793701", None, None),
+ (
+ "CubeRoot[3 + 4 I]",
+ "(3 + 4 I) ^ (1 / 3)",
+ ["The parameter 3 + 4 I should be real valued."],
+ None,
+ ),
+ ],
+)
+def test_cuberoot(str_expr, str_expected, msgs, failmsg):
+ check_evaluation(
+ str_expr, str_expected, expected_messages=msgs, failure_message=failmsg
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ## Issue #302
+ ## The sum should not converge since the first term is 1/0.
+ (
+ "Sum[i / Log[i], {i, 1, Infinity}]",
+ None,
+ "Sum[i / Log[i], {i, 1, Infinity}]",
+ None,
+ ),
+ (
+ "Sum[Cos[Pi i], {i, 1, Infinity}]",
+ None,
+ "Sum[Cos[i Pi], {i, 1, Infinity}]",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_arithmetic(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/atomic/test_numbers.py b/test/builtin/atomic/test_numbers.py
index da4d17a16..7533c5e80 100644
--- a/test/builtin/atomic/test_numbers.py
+++ b/test/builtin/atomic/test_numbers.py
@@ -266,6 +266,9 @@ def test_accuracy(str_expr, str_expected):
('{{a, 2, 3.2`},{2.1``3, 3.2``5, "a"}}', "3."),
("{1, 0.}", "MachinePrecision"),
("{1, 0.``5}", "0."),
+ ("Re[0.5+2.3 I]", "MachinePrecision"),
+ ("Re[1+2.3 I]", "MachinePrecision"),
+ ("Im[0.5+2.3 I]", "MachinePrecision"),
],
)
def test_precision(str_expr, str_expected):
@@ -305,3 +308,132 @@ def test_precision(str_expr, str_expected):
)
def test_change_prec(str_expr, str_expected, msg):
check_evaluation(str_expr, str_expected, failure_message=msg)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "warnings", "str_expected", "fail_msg"),
+ [
+ ("IntegerLength /@ (10 ^ Range[100] - 1) == Range[1, 100]", None, "True", None),
+ (
+ "RealDigits[-1.25, -1]",
+ ("Base -1 is not a real number greater than 1.",),
+ "RealDigits[-1.25, -1]",
+ None,
+ ),
+ (
+ "RealDigits[-Pi]",
+ ("The number of digits to return cannot be determined.",),
+ "RealDigits[-Pi]",
+ None,
+ ),
+ (
+ "RealDigits[I, 7]",
+ ("The value I is not a real number.",),
+ "RealDigits[I, 7]",
+ None,
+ ),
+ (
+ "RealDigits[Pi]",
+ ("The number of digits to return cannot be determined.",),
+ "RealDigits[Pi]",
+ None,
+ ),
+ (
+ "RealDigits[3 + 4 I]",
+ ("The value 3 + 4 I is not a real number.",),
+ "RealDigits[3 + 4 I]",
+ None,
+ ),
+ (
+ "RealDigits[3.14, 10, 1.5]",
+ (
+ "Non-negative machine-sized integer expected at position 3 in RealDigits[3.14, 10, 1.5].",
+ ),
+ "RealDigits[3.14, 10, 1.5]",
+ None,
+ ),
+ (
+ "RealDigits[3.14, 10, 1, 1.5]",
+ (
+ "Machine-sized integer expected at position 4 in RealDigits[3.14, 10, 1, 1.5].",
+ ),
+ "RealDigits[3.14, 10, 1, 1.5]",
+ None,
+ ),
+ ("N[Pi, 10]", None, "3.141592654", None),
+ (
+ "$MaxPrecision = x",
+ (
+ "Cannot set $MaxPrecision to x; value must be a positive number or Infinity.",
+ ),
+ "x",
+ None,
+ ),
+ (
+ "$MaxPrecision = -Infinity",
+ (
+ "Cannot set $MaxPrecision to -Infinity; value must be a positive number or Infinity.",
+ ),
+ "-Infinity",
+ None,
+ ),
+ (
+ "$MaxPrecision = 0",
+ (
+ "Cannot set $MaxPrecision to 0; value must be a positive number or Infinity.",
+ ),
+ "0",
+ None,
+ ),
+ ("$MaxPrecision = Infinity;$MinPrecision = 15;", None, "Null", None),
+ (
+ "$MaxPrecision = 10",
+ ("Cannot set $MaxPrecision such that $MaxPrecision < $MinPrecision.",),
+ "10",
+ None,
+ ),
+ ("$MaxPrecision", None, "Infinity", None),
+ ("$MinPrecision = 0;", None, "Null", None),
+ ("N[E, MachinePrecision]", None, "2.71828", None),
+ ("Round[MachinePrecision]", None, "16", None),
+ ("N[Pi, 10]", None, "3.141592654", None),
+ (
+ "$MinPrecision = x",
+ ("Cannot set $MinPrecision to x; value must be a non-negative number.",),
+ "x",
+ None,
+ ),
+ (
+ "$MinPrecision = -Infinity",
+ (
+ "Cannot set $MinPrecision to -Infinity; value must be a non-negative number.",
+ ),
+ "-Infinity",
+ None,
+ ),
+ (
+ "$MinPrecision = -1",
+ ("Cannot set $MinPrecision to -1; value must be a non-negative number.",),
+ "-1",
+ None,
+ ),
+ ("$MinPrecision = 0;", None, "Null", None),
+ ("$MaxPrecision = 10;", None, "Null", None),
+ (
+ "$MinPrecision = 15",
+ ("Cannot set $MinPrecision such that $MaxPrecision < $MinPrecision.",),
+ "15",
+ None,
+ ),
+ ("$MinPrecision", None, "0", None),
+ ("$MaxPrecision = Infinity;", None, "Null", None),
+ ],
+)
+def test_private_doctests_string(str_expr, warnings, str_expected, fail_msg):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ failure_message="",
+ expected_messages=warnings,
+ hold_expected=True,
+ )
diff --git a/test/builtin/atomic/test_strings.py b/test/builtin/atomic/test_strings.py
index 45104e059..bdf4b0d01 100644
--- a/test/builtin/atomic/test_strings.py
+++ b/test/builtin/atomic/test_strings.py
@@ -30,3 +30,109 @@ def test_alphabet(str_expr, str_expected, fail_msg, warnings):
check_evaluation(
str_expr, str_expected, failure_message="", expected_messages=warnings
)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "warnings", "str_expected", "fail_msg"),
+ [
+ (
+ "LetterNumber[4]",
+ ("The argument 4 is not a string.",),
+ "LetterNumber[4]",
+ None,
+ ),
+ ('StringContainsQ["Hello", "o"]', None, "True", None),
+ ('StringContainsQ["a"]["abcd"]', None, "True", None),
+ ('StringContainsQ["Mathics", "ma", IgnoreCase -> False]', None, "False", None),
+ ('StringContainsQ["Mathics", "MA" , IgnoreCase -> True]', None, "True", None),
+ ('StringContainsQ["", "Empty String"]', None, "False", None),
+ ('StringContainsQ["", ___]', None, "True", None),
+ ('StringContainsQ["Empty Pattern", ""]', None, "True", None),
+ (
+ 'StringContainsQ[notastring, "n"]',
+ (
+ "String or list of strings expected at position 1 in StringContainsQ[notastring, n].",
+ ),
+ "StringContainsQ[notastring, n]",
+ None,
+ ),
+ (
+ 'StringContainsQ["Welcome", notapattern]',
+ (
+ "Element notapattern is not a valid string or pattern element in notapattern.",
+ ),
+ "StringContainsQ[Welcome, notapattern]",
+ None,
+ ),
+ ('StringContainsQ[{}, "list of string is empty"]', None, "{}", None),
+ ## special cases, Mathematica allows list of patterns
+ (
+ 'StringContainsQ[{"A", "Galaxy", "Far", "Far", "Away"}, {"F" ~~ __ ~~ "r", "aw" ~~ ___}]',
+ None,
+ "{False, False, True, True, False}",
+ None,
+ ),
+ (
+ 'StringContainsQ[{"A", "Galaxy", "Far", "Far", "Away"}, {"F" ~~ __ ~~ "r", "aw" ~~ ___}, IgnoreCase -> True]',
+ None,
+ "{False, False, True, True, True}",
+ None,
+ ),
+ (
+ 'StringContainsQ[{"A", "Galaxy", "Far", "Far", "Away"}, {}]',
+ None,
+ "{False, False, False, False, False}",
+ None,
+ ),
+ (
+ 'StringContainsQ[{"A", Galaxy, "Far", "Far", Away}, {"F" ~~ __ ~~ "r", "aw" ~~ ___}]',
+ (
+ "String or list of strings expected at position 1 in StringContainsQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}].",
+ ),
+ "StringContainsQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}]",
+ None,
+ ),
+ (
+ 'StringContainsQ[{"A", "Galaxy", "Far", "Far", "Away"}, {F ~~ __ ~~ "r", aw ~~ ___}]',
+ (
+ "Element F ~~ __ ~~ r is not a valid string or pattern element in {F ~~ __ ~~ r, aw ~~ ___}.",
+ ),
+ "StringContainsQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}]",
+ None,
+ ),
+ ## Mathematica can detemine correct invalid element in the pattern, it reports error:
+ ## Element F is not a valid string or pattern element in {F ~~ __ ~~ r, aw ~~ ___}.
+ (
+ 'StringRepeat["x", 0]',
+ ("A positive integer is expected at position 2 in StringRepeat[x, 0].",),
+ "StringRepeat[x, 0]",
+ None,
+ ),
+ ('ToExpression["log(x)", InputForm]', None, "log x", None),
+ (
+ 'ToExpression["1+"]',
+ (
+ "Incomplete expression; more input is needed (line 1 of \"ToExpression['1+']\").",
+ ),
+ "$Failed",
+ None,
+ ),
+ (
+ "ToExpression[]",
+ (
+ "ToExpression called with 0 arguments; between 1 and 3 arguments are expected.",
+ ),
+ "ToExpression[]",
+ None,
+ ),
+ # ('ToExpression["log(x)", StandardForm]', None, "log x", None),
+ ],
+)
+def test_private_doctests_string(str_expr, warnings, str_expected, fail_msg):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ failure_message="",
+ expected_messages=warnings,
+ hold_expected=True,
+ )
diff --git a/test/builtin/atomic/test_symbols.py b/test/builtin/atomic/test_symbols.py
index bba780e0c..b3c2960bc 100644
--- a/test/builtin/atomic/test_symbols.py
+++ b/test/builtin/atomic/test_symbols.py
@@ -2,9 +2,10 @@
"""
Unit tests from mathics.builtin.atomic.symbols.
"""
-
from test.helper import check_evaluation
+import pytest
+
def test_downvalues():
for str_expr, str_expected, message in (
@@ -25,3 +26,46 @@ def test_downvalues():
),
):
check_evaluation(str_expr, str_expected, message)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "warnings", "str_expected", "fail_msg"),
+ [
+ ## placeholder for general context-related tests
+ ("x === Global`x", None, "True", None),
+ ("`x === Global`x", None, "True", None),
+ ("a`x === Global`x", None, "False", None),
+ ("a`x === a`x", None, "True", None),
+ ("a`x === b`x", None, "False", None),
+ ## awkward parser cases
+ ("FullForm[a`b_]", None, "Pattern[a`b, Blank[]]", None),
+ ("a = 2;", None, "Null", None),
+ ("Information[a]", ("a = 2\n",), "Null", None),
+ ("f[x_] := x ^ 2;", None, "Null", None),
+ ("g[f] ^:= 2;", None, "Null", None),
+ ('f::usage = "f[x] returns the square of x";', None, "Null", None),
+ (
+ "Information[f]",
+ (("f[x] returns the square of x\n\n" "f[x_] = x ^ 2\n\n" "g[f] ^= 2\n"),),
+ "Null",
+ None,
+ ),
+ ('Length[Names["System`*"]] > 350', None, "True", None),
+ (
+ "{\\[Eta], \\[CapitalGamma]\\[Beta], Z\\[Infinity], \\[Angle]XYZ, \\[FilledSquare]r, i\\[Ellipsis]j}",
+ None,
+ "{\u03b7, \u0393\u03b2, Z\u221e, \u2220XYZ, \u25a0r, i\u2026j}",
+ None,
+ ),
+ ("SymbolName[a`b`x] // InputForm", None, '"x"', None),
+ ("ValueQ[True]", None, "False", None),
+ ],
+)
+def test_private_doctests_symbol(str_expr, warnings, str_expected, fail_msg):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ failure_message="",
+ expected_messages=warnings,
+ hold_expected=True,
+ )
diff --git a/test/builtin/box/test_custom_boxexpression.py b/test/builtin/box/test_custom_boxexpression.py
index f83335c92..6616a1e45 100644
--- a/test/builtin/box/test_custom_boxexpression.py
+++ b/test/builtin/box/test_custom_boxexpression.py
@@ -1,9 +1,9 @@
from test.helper import evaluate, session
-from mathics.builtin.base import Predefined
from mathics.builtin.box.expression import BoxExpression
from mathics.builtin.graphics import GRAPHICS_OPTIONS
from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED, A_READ_PROTECTED
+from mathics.core.builtin import Predefined
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.symbols import Symbol
diff --git a/test/builtin/colors/test_color_directives.py b/test/builtin/colors/test_color_directives.py
new file mode 100644
index 000000000..5e403c62d
--- /dev/null
+++ b/test/builtin/colors/test_color_directives.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.color.color_directives
+"""
+
+import sys
+import time
+from test.helper import check_evaluation, evaluate
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ 'ColorDistance[Blue, Red, DistanceFunction -> "CIE2000"]',
+ None,
+ "0.557976",
+ None,
+ ),
+ (
+ "ColorDistance[Red, Black, DistanceFunction -> (Abs[#1[[1]] - #2[[1]]] &)]",
+ None,
+ "0.542917",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_color_directives(str_expr, msgs, str_expected, fail_msg):
+ """builtin.color.color_directives"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/drawing/__init__.py b/test/builtin/drawing/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/builtin/drawing/test_image.py b/test/builtin/drawing/test_image.py
index 7f2835609..c7e52b74e 100644
--- a/test/builtin/drawing/test_image.py
+++ b/test/builtin/drawing/test_image.py
@@ -9,7 +9,7 @@
import pytest
-from mathics.builtin.base import check_requires_list
+from mathics.core.builtin import check_requires_list
from mathics.core.symbols import SymbolNull
# Note we test with tif, jpg, and gif. Add others?
diff --git a/test/builtin/drawing/test_plot.py b/test/builtin/drawing/test_plot.py
new file mode 100644
index 000000000..4e512a1b9
--- /dev/null
+++ b/test/builtin/drawing/test_plot.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.drawing.plot
+"""
+
+import sys
+import time
+from test.helper import check_evaluation, evaluate
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("Plot[1 / x, {x, -1, 1}]", None, "-Graphics-", None),
+ ("Plot[x, {y, 0, 2}]", None, "-Graphics-", None),
+ (
+ "Plot[{f[x],-49x/12+433/108},{x,-6,6}, PlotRange->{-10,10}, AspectRatio->{1}]",
+ None,
+ "-Graphics-",
+ None,
+ ),
+ (
+ "Plot[Sin[t], {t, 0, 2 Pi}, PlotPoints -> 1]",
+ ("Value of option PlotPoints -> 1 is not an integer >= 2.",),
+ "Plot[Sin[t], {t, 0, 2 Pi}, PlotPoints -> 1]",
+ None,
+ ),
+ ("Plot[x*y, {x, -1, 1}]", None, "-Graphics-", None),
+ ("Plot3D[z, {x, 1, 20}, {y, 1, 10}]", None, "-Graphics3D-", None),
+ (
+ "Graphics[{Disk[]}, Background->RGBColor[1,.1,.1]]//TeXForm//ToString",
+ None,
+ (
+ '\n\\begin{asy}\nusepackage("amsmath");\nsize(5.8333cm, 5.8333cm);\n'
+ "filldraw(box((0,0), (350,350)), rgb(1, 0.1, 0.1));\n"
+ "filldraw(ellipse((175,175),175,175), rgb(0, 0, 0), nullpen);\n"
+ "clip(box((0,0), (350,350)));\n\\end{asy}\n"
+ ),
+ "Background 2D",
+ ),
+ ## MaxRecursion Option
+ (
+ "Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> 0]",
+ None,
+ "-Graphics3D-",
+ None,
+ ),
+ (
+ "Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> 15]",
+ None,
+ "-Graphics3D-",
+ None,
+ ),
+ (
+ "Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> 16]",
+ (
+ "MaxRecursion must be a non-negative integer; the recursion value is limited to 15. Using MaxRecursion -> 15.",
+ ),
+ "-Graphics3D-",
+ None,
+ ),
+ (
+ "Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> -1]",
+ (
+ "MaxRecursion must be a non-negative integer; the recursion value is limited to 15. Using MaxRecursion -> 0.",
+ ),
+ "-Graphics3D-",
+ None,
+ ),
+ (
+ "Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> a]",
+ (
+ "MaxRecursion must be a non-negative integer; the recursion value is limited to 15. Using MaxRecursion -> 0.",
+ ),
+ "-Graphics3D-",
+ None,
+ ),
+ (
+ "Plot3D[0, {x, -2, 2}, {y, -2, 2}, MaxRecursion -> Infinity]",
+ (
+ "MaxRecursion must be a non-negative integer; the recursion value is limited to 15. Using MaxRecursion -> 15.",
+ ),
+ "-Graphics3D-",
+ None,
+ ),
+ (
+ "Plot3D[x ^ 2 + 1 / y, {x, -1, 1}, {y, 1, z}]",
+ ("Limiting value z in {y, 1, z} is not a machine-size real number.",),
+ "Plot3D[x ^ 2 + 1 / y, {x, -1, 1}, {y, 1, z}]",
+ None,
+ ),
+ (
+ "StringTake[Plot3D[x + 2y, {x, -2, 2}, {y, -2, 2}] // TeXForm//ToString,67]",
+ None,
+ "\n\\begin{asy}\nimport three;\nimport solids;\nsize(6.6667cm, 6.6667cm);",
+ None,
+ ),
+ (
+ "Graphics3D[{Sphere[]}, Background->RGBColor[1,.1,.1]]//TeXForm//ToString",
+ None,
+ (
+ "\n\\begin{asy}\n"
+ "import three;\n"
+ "import solids;\n"
+ "size(6.6667cm, 6.6667cm);\n"
+ "currentprojection=perspective(2.6,-4.8,4.0);\n"
+ "currentlight=light(rgb(0.5,0.5,1), background=rgb(1, 0.1, 0.1), specular=red, (2,0,2), (2,2,2), (0,2,2));\n"
+ "// Sphere3DBox\n"
+ "draw(surface(sphere((0, 0, 0), 1)), rgb(1,1,1)+opacity(1));\n"
+ "draw(((-1,-1,-1)--(1,-1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-1,1,-1)--(1,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-1,-1,1)--(1,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-1,1,1)--(1,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-1,-1,-1)--(-1,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((1,-1,-1)--(1,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-1,-1,1)--(-1,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((1,-1,1)--(1,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-1,-1,-1)--(-1,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((1,-1,-1)--(1,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-1,1,-1)--(-1,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((1,1,-1)--(1,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "\\end{asy}\n"
+ ),
+ "Background 3D",
+ ),
+ (
+ "Graphics3D[Point[Table[{Sin[t], Cos[t], 0}, {t, 0, 2. Pi, Pi / 15.}]]] // TeXForm//ToString",
+ None,
+ (
+ "\n\\begin{asy}\nimport three;\nimport solids;\nsize(6.6667cm, 6.6667cm);\n"
+ "currentprojection=perspective(2.6,-4.8,4.0);\n"
+ "currentlight=light(rgb(0.5,0.5,1), specular=red, (2,0,2), (2,2,2), (0,2,2));\n"
+ "// Point3DBox\npath3 g=(0,1,0)--(0.20791,0.97815,0)--(0.40674,0.91355,0)--"
+ "(0.58779,0.80902,0)--(0.74314,0.66913,0)--(0.86603,0.5,0)--(0.95106,0.30902,0)--"
+ "(0.99452,0.10453,0)--(0.99452,-0.10453,0)--(0.95106,-0.30902,0)--(0.86603,-0.5,0)"
+ "--(0.74314,-0.66913,0)--(0.58779,-0.80902,0)--(0.40674,-0.91355,0)--"
+ "(0.20791,-0.97815,0)--(5.6655e-16,-1,0)--(-0.20791,-0.97815,0)--"
+ "(-0.40674,-0.91355,0)--(-0.58779,-0.80902,0)--(-0.74314,-0.66913,0)--"
+ "(-0.86603,-0.5,0)--(-0.95106,-0.30902,0)--(-0.99452,-0.10453,0)--"
+ "(-0.99452,0.10453,0)--(-0.95106,0.30902,0)--(-0.86603,0.5,0)--"
+ "(-0.74314,0.66913,0)--(-0.58779,0.80902,0)--(-0.40674,0.91355,0)--"
+ "(-0.20791,0.97815,0)--(1.5314e-15,1,0)--cycle;dot(g, rgb(0, 0, 0));\n"
+ "draw(((-0.99452,-1,-1)--(0.99452,-1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-0.99452,1,-1)--(0.99452,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-0.99452,-1,1)--(0.99452,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-0.99452,1,1)--(0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-0.99452,-1,-1)--(-0.99452,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((0.99452,-1,-1)--(0.99452,1,-1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-0.99452,-1,1)--(-0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((0.99452,-1,1)--(0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-0.99452,-1,-1)--(-0.99452,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((0.99452,-1,-1)--(0.99452,-1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((-0.99452,1,-1)--(-0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n"
+ "draw(((0.99452,1,-1)--(0.99452,1,1)), rgb(0.4, 0.4, 0.4)+linewidth(1));\n\\end{asy}\n"
+ ),
+ None,
+ ),
+ ],
+)
+def test_private_doctests_plot(str_expr, msgs, str_expected, fail_msg):
+ """builtin.drawing.plot"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/files_io/test_files.py b/test/builtin/files_io/test_files.py
index 3e0a2bf6c..0b36d5395 100644
--- a/test/builtin/files_io/test_files.py
+++ b/test/builtin/files_io/test_files.py
@@ -2,12 +2,16 @@
"""
Unit tests from builtins/files_io/files.py
"""
+import os
import os.path as osp
import sys
+from tempfile import NamedTemporaryFile
from test.helper import check_evaluation, evaluate
import pytest
+from mathics.core.parser.convert import canonic_filename
+
def test_compress():
for text in ("", "abc", " "):
@@ -26,23 +30,38 @@ def test_unprotected():
check_evaluation(str_expr, str_expected, message)
-@pytest.mark.skipif(
- sys.platform in ("win32",), reason="POSIX pathname tests do not work on Windows"
-)
def test_get_and_put():
- temp_filename = evaluate('$TemporaryDirectory<>"/testfile"').to_python()
+ temp_filename = canonic_filename(
+ evaluate('$TemporaryDirectory<>"/testfile"').to_python()
+ )
temp_filename_strip = temp_filename[1:-1]
check_evaluation(f"40! >> {temp_filename_strip}", "Null")
check_evaluation(f"<< {temp_filename_strip}", "40!")
check_evaluation(f"DeleteFile[{temp_filename}]", "Null")
+def test_get_input():
+ # Check that $InputFileName and $Input are set inside running a Get[].
+ script_path = canonic_filename(
+ osp.normpath(
+ osp.join(osp.dirname(__file__), "..", "..", "data", "inputfile-bug.m")
+ )
+ )
+
+ check_evaluation(f'Get["{script_path}"]', script_path, hold_expected=True)
+
+ script_path = canonic_filename(
+ osp.normpath(osp.join(osp.dirname(__file__), "..", "..", "data", "input-bug.m"))
+ )
+ check_evaluation(f'Get["{script_path}"]', script_path, hold_expected=True)
+
+
@pytest.mark.skipif(
sys.platform in ("win32",), reason="$Path does not work on Windows?"
)
def test_get_path_search():
# Check that AppendTo[$Path] works in conjunction with Get[]
- dirname = osp.join(osp.dirname(osp.abspath(__file__)), "..", "..", "data")
+ dirname = osp.normpath(osp.join(osp.dirname(__file__), "..", "..", "data"))
evaled = evaluate(f"""AppendTo[$Path, "{dirname}"]""")
assert evaled.has_form("List", 1, None)
check_evaluation('Get["fortytwo.m"]', "42")
@@ -80,6 +99,339 @@ def test_close():
), f"temporary filename {temp_filename} should not appear"
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ('Close["abc"]', ("abc is not open.",), "Close[abc]", ""),
+ (
+ "exp = Sin[1]; FilePrint[exp]",
+ ("File specification Sin[1] is not a string of one or more characters.",),
+ "FilePrint[Sin[1]]",
+ "",
+ ),
+ (
+ 'FilePrint["somenonexistentpath_h47sdmk^&h4"]',
+ ("Cannot open somenonexistentpath_h47sdmk^&h4.",),
+ "FilePrint[somenonexistentpath_h47sdmk^&h4]",
+ "",
+ ),
+ (
+ 'FilePrint[""]',
+ ("File specification is not a string of one or more characters.",),
+ "FilePrint[]",
+ "",
+ ),
+ (
+ 'Get["SomeTypoPackage`"]',
+ ("Cannot open SomeTypoPackage`.",),
+ "$Failed",
+ "",
+ ),
+ (
+ "OpenRead[]",
+ ("OpenRead called with 0 arguments; 1 argument is expected.",),
+ "OpenRead[]",
+ "",
+ ),
+ (
+ "OpenRead[y]",
+ ("File specification y is not a string of one or more characters.",),
+ "OpenRead[y]",
+ "",
+ ),
+ (
+ 'OpenRead[""]',
+ ("File specification is not a string of one or more characters.",),
+ "OpenRead[]",
+ "",
+ ),
+ (
+ 'fd=OpenRead["ExampleData/EinsteinSzilLetter.txt", BinaryFormat -> True, CharacterEncoding->"UTF8"]//Head',
+ None,
+ "InputStream",
+ "",
+ ),
+ (
+ "Close[fd]; fd=.;fd=OpenWrite[BinaryFormat -> True]//Head",
+ None,
+ "OutputStream",
+ "",
+ ),
+ (
+ 'DeleteFile[Close[fd]];fd=.;appendFile = OpenAppend["MathicsNonExampleFile"]//{#1[[0]],#1[[1]]}&',
+ None,
+ "{OutputStream, MathicsNonExampleFile}",
+ "",
+ ),
+ (
+ "Close[appendFile]",
+ None,
+ "Close[{OutputStream, MathicsNonExampleFile}]",
+ "",
+ ),
+ ## writing to dir
+ ("x >>> /var/", ("Cannot open /var/.",), "x >>> /var/", ""),
+ ## writing to read only file
+ (
+ "x >>> /proc/uptime",
+ ("Cannot open /proc/uptime.",),
+ "x >>> /proc/uptime",
+ "",
+ ),
+ ## Malformed InputString
+ (
+ "Read[InputStream[String], {Word, Number}]",
+ None,
+ "Read[InputStream[String], {Word, Number}]",
+ "",
+ ),
+ ## Correctly formed InputString but not open
+ (
+ "Read[InputStream[String, -1], {Word, Number}]",
+ ("InputStream[String, -1] is not open.",),
+ "Read[InputStream[String, -1], {Word, Number}]",
+ "",
+ ),
+ ('stream = StringToStream[""];Read[stream, Word]', None, "EndOfFile", ""),
+ ("Read[stream, Word]", None, "EndOfFile", ""),
+ ("Close[stream];", None, "Null", ""),
+ (
+ 'stream = StringToStream["123xyz 321"]; Read[stream, Number]',
+ None,
+ "123",
+ "",
+ ),
+ ("Quiet[Read[stream, Number]]", None, "$Failed", ""),
+ ## Real
+ ('stream = StringToStream["123, 4abc"];Read[stream, Real]', None, "123.", ""),
+ ("Read[stream, Real]", None, "4.", ""),
+ ("Quiet[Read[stream, Number]]", None, "$Failed", ""),
+ ("Close[stream];", None, "Null", ""),
+ (
+ 'stream = StringToStream["1.523E-19"]; Read[stream, Real]',
+ None,
+ "1.523×10^-19",
+ "",
+ ),
+ ("Close[stream];", None, "Null", ""),
+ (
+ 'stream = StringToStream["-1.523e19"]; Read[stream, Real]',
+ None,
+ "-1.523×10^19",
+ "",
+ ),
+ ("Close[stream];", None, "Null", ""),
+ (
+ 'stream = StringToStream["3*^10"]; Read[stream, Real]',
+ None,
+ "3.×10^10",
+ "",
+ ),
+ ("Close[stream];", None, "Null", ""),
+ (
+ 'stream = StringToStream["3.*^10"]; Read[stream, Real]',
+ None,
+ "3.×10^10",
+ "",
+ ),
+ ("Close[stream];", None, "Null", ""),
+ ## Expression
+ (
+ 'stream = StringToStream["x + y Sin[z]"]; Read[stream, Expression]',
+ None,
+ "x + y Sin[z]",
+ "",
+ ),
+ ("Close[stream];", None, "Null", ""),
+ ## ('stream = Quiet[StringToStream["Sin[1 123"]; Read[stream, Expression]]', None,'$Failed', ""),
+ (
+ 'stream = StringToStream["123 abc"]; Quiet[Read[stream, {Word, Number}]]',
+ None,
+ "$Failed",
+ "",
+ ),
+ ("Close[stream];", None, "Null", ""),
+ (
+ 'stream = StringToStream["123 123"]; Read[stream, {Real, Number}]',
+ None,
+ "{123., 123}",
+ "",
+ ),
+ ("Close[stream];", None, "Null", ""),
+ (
+ "Quiet[Read[stream, {Real}]]//{#1[[0]],#1[[1]][[0]],#1[[1]][[1]],#1[[2]]}&",
+ None,
+ "{Read, InputStream, String, {Real}}",
+ "",
+ ),
+ (
+ r'stream = StringToStream["\"abc123\""];ReadList[stream, "Invalid"]//{#1[[0]],#1[[2]]}&',
+ ("Invalid is not a valid format specification.",),
+ "{ReadList, Invalid}",
+ "",
+ ),
+ ("Close[stream];", None, "Null", ""),
+ (
+ 'ReadList[StringToStream["a 1 b 2"], {Word, Number}, 1]',
+ None,
+ "{{a, 1}}",
+ "",
+ ),
+ ('stream = StringToStream["Mathics is cool!"];', None, "Null", ""),
+ ("SetStreamPosition[stream, -5]", ("Invalid I/O Seek.",), "0", ""),
+ (
+ '(strm = StringToStream["abc 123"])//{#1[[0]],#1[[1]]}&',
+ None,
+ "{InputStream, String}",
+ "",
+ ),
+ ("Read[strm, Word]", None, "abc", ""),
+ ("Read[strm, Number]", None, "123", ""),
+ ("Close[strm]", None, "String", ""),
+ ('Streams["some_nonexistent_name"]', None, "{}", ""),
+ (
+ "stream = OpenWrite[]; WriteString[stream, 100, 1 + x + y, Sin[x + y]]",
+ None,
+ "Null",
+ "",
+ ),
+ ("(pathname = Close[stream])//Head", None, "String", ""),
+ ("FilePrint[pathname]", ("1001 + x + ySin[x + y]",), "Null", ""),
+ ("DeleteFile[pathname];", None, "Null", ""),
+ (
+ "stream = OpenWrite[];WriteString[stream];(pathname = Close[stream])//Head",
+ None,
+ "String",
+ "",
+ ),
+ ("FilePrint[pathname]", None, "Null", ""),
+ ("DeleteFile[pathname];Clear[pathname];", None, "Null", ""),
+ ],
+)
+def test_private_doctests_files(str_expr, msgs, str_expected, fail_msg):
+ """Grab-bag tests from mathics.builtin.files_io.files. These need to be split out."""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ "Hold[<< ~/some_example/dir/] // FullForm",
+ None,
+ 'Hold[Get["~/some_example/dir/"]]',
+ 'We expect "<<" to get parsed as "Get[...]',
+ ),
+ # (
+ # r"Hold[<<`/.\-_:$*~?] // FullForm",
+ # None,
+ # r'Hold[Get["`/.\\\\-_:$*~?"]]',
+ # (
+ # 'We expect "<<" to get parse as "Get[...]" '
+ # "even when there are weird filename characters",
+ # ),
+ # ),
+ ],
+)
+def test_get_operator_parse(str_expr, msgs, str_expected, fail_msg):
+ """
+ Check that << is canonicalized to "Get"
+ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+def test_open_read():
+ """
+ Check OpenRead[] on a non-existent file name"""
+ # Below, we set "delete=False" because `os.unlink()` is used
+ # to delete the file.
+ new_temp_file = NamedTemporaryFile(mode="r", delete=False)
+ name = canonic_filename(new_temp_file.name)
+ try:
+ os.unlink(name)
+ except PermissionError:
+ # This can happen in MS Windows
+ pytest.mark.skip("Something went wrong in trying to set up test.")
+ return
+ check_evaluation(
+ str_expr=f'OpenRead["{name}"]',
+ str_expected=f"OpenRead[{name}]",
+ to_string_expr=True,
+ hold_expected=True,
+ failure_message="",
+ expected_messages=(f"Cannot open {name}.",),
+ )
+
+
+def test_streams():
+ """
+ Test Streams[] and Streams[name]
+ """
+ # Save original Streams[] count. Then add a new OutputStream,
+ # See that this is indeed a new OutputStream, and that
+ # See that Streams[] count is now one larger.
+ # See that we can find new stream by name in Streams[]
+ # Finally Close new stream.
+ orig_streams_count = evaluate("Length[Streams[]]").to_python()
+ check_evaluation(
+ str_expr="(newStream = OpenWrite[]) // Head",
+ str_expected="OutputStream",
+ failure_message="Expecting Head[] of a new OpenWrite stream to be an 'OutputStream'",
+ )
+ new_streams_count = evaluate("Length[Streams[]]").to_python()
+ assert (
+ orig_streams_count + 1 == new_streams_count
+ ), "should have added one more stream listed"
+ check_evaluation(
+ str_expr="Length[Streams[newStream]] == 1",
+ str_expected="True",
+ to_string_expr=False,
+ to_string_expected=False,
+ failure_message="Expecting to find new stream in list of existing streams",
+ )
+ check_evaluation(
+ str_expr="Streams[newStream][[1]] == newStream",
+ str_expected="True",
+ to_string_expr=False,
+ to_string_expected=False,
+ failure_message="Expecting stream found in list to be the one we just added",
+ )
+ evaluate("Close[newStream]")
+
+
+# rocky: I don't understand what these are supposed to test.
+
+# (
+# "WriteString[pathname, abc];(laststrm=Streams[pathname][[1]])//Head",
+# None,
+# "OutputStream",
+# None,
+# ),
+
+# (
+# "WriteString[pathname, abc];(laststrm=Streams[pathname][[1]])//Head",
+# None,
+# "OutputStream",
+# None,
+# ),
+# ("Close[laststrm];FilePrint[pathname]", ("abc",), "Null", ""),
+
# I do not know what this is it supposed to test with this...
# def test_Inputget_and_put():
# stream = Expression('Plus', Symbol('x'), Integer(2))
diff --git a/test/builtin/files_io/test_filesystem.py b/test/builtin/files_io/test_filesystem.py
new file mode 100644
index 000000000..32e2c5b82
--- /dev/null
+++ b/test/builtin/files_io/test_filesystem.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from builtins/files_io/filesystem.py
+"""
+import os.path as osp
+import sys
+from test.helper import check_evaluation, evaluate
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ 'AbsoluteFileName["Some/NonExistant/Path.ext"]',
+ ("File not found during AbsoluteFileName[Some/NonExistant/Path.ext].",),
+ "$Failed",
+ None,
+ ),
+ ('DirectoryName["a/b/c", 3] // InputForm', None, '""', None),
+ ('DirectoryName[""] // InputForm', None, '""', None),
+ (
+ 'DirectoryName["a/b/c", x]',
+ (
+ "Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, x].",
+ ),
+ "DirectoryName[a/b/c, x]",
+ None,
+ ),
+ (
+ 'DirectoryName["a/b/c", -1]',
+ (
+ "Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, -1].",
+ ),
+ "DirectoryName[a/b/c, -1]",
+ None,
+ ),
+ (
+ "DirectoryName[x]",
+ ("String expected at position 1 in DirectoryName[x].",),
+ "DirectoryName[x]",
+ None,
+ ),
+ ('FileBaseName["file."]', None, "file", None),
+ ('FileBaseName["file"]', None, "file", None),
+ ('FileExtension["file."]', None, "", None),
+ ('FileExtension["file"]', None, "", None),
+ ('FileInformation["ExampleData/missing_file.jpg"]', None, "{}", None),
+ ('FindFile["SomeTypoPackage`"]', None, "$Failed", None),
+ (
+ 'SetDirectory["MathicsNonExample"]',
+ ("Cannot set current directory to MathicsNonExample.",),
+ "$Failed",
+ None,
+ ),
+ (
+ 'Needs["SomeFakePackageOrTypo`"]',
+ (
+ "Cannot open SomeFakePackageOrTypo`.",
+ "Context SomeFakePackageOrTypo` was not created when Needs was evaluated.",
+ ),
+ "$Failed",
+ None,
+ ),
+ (
+ 'Needs["VectorAnalysis"]',
+ (
+ "Invalid context specified at position 1 in Needs[VectorAnalysis]. A context must consist of valid symbol names separated by and ending with `.",
+ ),
+ "Needs[VectorAnalysis]",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_filesystem(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/files_io/test_importexport.py b/test/builtin/files_io/test_importexport.py
index c85d8f59c..f51c38dfe 100644
--- a/test/builtin/files_io/test_importexport.py
+++ b/test/builtin/files_io/test_importexport.py
@@ -3,13 +3,12 @@
import os.path as osp
import sys
import tempfile
+from test.helper import check_evaluation, evaluate, session
import pytest
from mathics.builtin.atomic.strings import to_python_encoding
-from ...helper import session
-
# def test_import():
# eaccent = "\xe9"
# for str_expr, str_expected, message in (
@@ -82,6 +81,185 @@ def test_export():
assert data.endswith("")
+"""
+
+ ## Compression
+ ## #> Export["abc.txt", 1+x, "ZIP"] (* MMA Bug - Export::type *)
+ ## : {ZIP} is not a valid set of export elements for the Text format.
+ ## = $Failed
+ ## #> Export["abc.txt", 1+x, "BZIP"] (* MMA Bug - General::stop *)
+ ## : {BZIP} is not a valid set of export elements for the Text format.
+ ## = $Failed
+ ## #> Export["abc.txt", 1+x, {"BZIP", "ZIP", "Text"}]
+ ## = abc.txt
+ ## #> Export["abc.txt", 1+x, {"GZIP", "Text"}]
+ ## = abc.txt
+ ## #> Export["abc.txt", 1+x, {"BZIP2", "Text"}]
+ ## = abc.txt
+
+ ## Doesn't work on Microsoft Windows
+ ## S> FileFormat["ExampleData/benzene.xyz"]
+ ## = XYZ
+
+"""
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (r'Quiet[URLFetch["https://", {}]]', None, "$Failed", None),
+ # (r'Quiet[URLFetch["https://www.example.com", {}]]', None,
+ # "...", None),
+ (
+ 'Import["ExampleData/ExampleData.tx"]',
+ ("File not found during Import.",),
+ "$Failed",
+ None,
+ ),
+ (
+ "Import[x]",
+ ("First argument x is not a valid file, directory, or URL specification.",),
+ "$Failed",
+ None,
+ ),
+ ## CSV
+ (
+ 'Import["ExampleData/numberdata.csv", "Elements"]',
+ None,
+ "{Data, Grid}",
+ None,
+ ),
+ (
+ 'Import["ExampleData/numberdata.csv", "Data"]',
+ None,
+ "{{0.88, 0.60, 0.94}, {0.76, 0.19, 0.51}, {0.97, 0.04, 0.26}, {0.33, 0.74, 0.79}, {0.42, 0.64, 0.56}}",
+ None,
+ ),
+ (
+ 'Import["ExampleData/numberdata.csv"]',
+ None,
+ "{{0.88, 0.60, 0.94}, {0.76, 0.19, 0.51}, {0.97, 0.04, 0.26}, {0.33, 0.74, 0.79}, {0.42, 0.64, 0.56}}",
+ None,
+ ),
+ (
+ 'Import["ExampleData/numberdata.csv", "FieldSeparators" -> "."]',
+ None,
+ "{{0, 88,0, 60,0, 94}, {0, 76,0, 19,0, 51}, {0, 97,0, 04,0, 26}, {0, 33,0, 74,0, 79}, {0, 42,0, 64,0, 56}}",
+ None,
+ ),
+ (
+ 'Import["ExampleData/Middlemarch.txt"];',
+ ("An invalid unicode sequence was encountered and ignored.",),
+ "Null",
+ None,
+ ),
+ ## XML
+ (
+ 'MatchQ[Import["ExampleData/InventionNo1.xml", "Tags"],{__String}]',
+ None,
+ "True",
+ None,
+ ),
+ ("ImportString[x]", ("First argument x is not a string.",), "$Failed", None),
+ ## CSV
+ (
+ 'datastring = "0.88, 0.60, 0.94\\n.076, 0.19, .51\\n0.97, 0.04, .26";ImportString[datastring, "Elements"]',
+ None,
+ "{Data, Lines, Plaintext, String, Words}",
+ None,
+ ),
+ ('ImportString[datastring, {"CSV","Elements"}]', None, "{Data, Grid}", None),
+ (
+ 'ImportString[datastring, {"CSV", "Data"}]',
+ None,
+ "{{0.88, 0.60, 0.94}, {.076, 0.19, .51}, {0.97, 0.04, .26}}",
+ None,
+ ),
+ (
+ "ImportString[datastring]",
+ None,
+ "0.88, 0.60, 0.94\n.076, 0.19, .51\n0.97, 0.04, .26",
+ None,
+ ),
+ (
+ 'ImportString[datastring, "CSV","FieldSeparators" -> "."]',
+ None,
+ "{{0, 88, 0, 60, 0, 94}, {076, 0, 19, , 51}, {0, 97, 0, 04, , 26}}",
+ None,
+ ),
+ ## Invalid Filename
+ (
+ 'Export["abc.", 1+2]',
+ ("Cannot infer format of file abc..",),
+ "$Failed",
+ None,
+ ),
+ (
+ 'Export[".ext", 1+2]',
+ ("Cannot infer format of file .ext.",),
+ "$Failed",
+ None,
+ ),
+ (
+ "Export[x, 1+2]",
+ ("First argument x is not a valid file specification.",),
+ "$Failed",
+ None,
+ ),
+ ## Explicit Format
+ (
+ 'Export["abc.txt", 1+x, "JPF"]',
+ ("{JPF} is not a valid set of export elements for the Text format.",),
+ "$Failed",
+ None,
+ ),
+ (
+ 'Export["abc.txt", 1+x, {"JPF"}]',
+ ("{JPF} is not a valid set of export elements for the Text format.",),
+ "$Failed",
+ None,
+ ),
+ ## Empty elems
+ ('Export["123.txt", 1+x, {}]', None, "123.txt", None),
+ (
+ 'Export["123.jcp", 1+x, {}]',
+ ("Cannot infer format of file 123.jcp.",),
+ "$Failed",
+ None,
+ ),
+ ## FORMATS
+ ## ASCII text
+ ('FileFormat["ExampleData/BloodToilTearsSweat.txt"]', None, "Text", None),
+ ('FileFormat["ExampleData/MadTeaParty.gif"]', None, "GIF", None),
+ ('FileFormat["ExampleData/moon.tif"]', None, "TIFF", None),
+ ('FileFormat["ExampleData/numberdata.csv"]', None, "CSV", None),
+ ('FileFormat["ExampleData/EinsteinSzilLetter.txt"]', None, "Text", None),
+ ('FileFormat["ExampleData/BloodToilTearsSweat.txt"]', None, "Text", None),
+ ('FileFormat["ExampleData/colors.json"]', None, "JSON", None),
+ (
+ 'FileFormat["ExampleData/some-typo.extension"]',
+ ("File not found during FileFormat[ExampleData/some-typo.extension].",),
+ "$Failed",
+ None,
+ ),
+ ('FileFormat["ExampleData/Testosterone.svg"]', None, "SVG", None),
+ ('FileFormat["ExampleData/colors.json"]', None, "JSON", None),
+ ('FileFormat["ExampleData/InventionNo1.xml"]', None, "XML", None),
+ ],
+)
+def test_private_doctests_importexport(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
# TODO:
# mmatera: please put in pytest conditionally
# >> System`Convert`B64Dump`B64Encode["∫ f x"]
diff --git a/test/builtin/image/test_image.py b/test/builtin/image/test_image.py
new file mode 100644
index 000000000..fb79fe470
--- /dev/null
+++ b/test/builtin/image/test_image.py
@@ -0,0 +1,198 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtins.image.colors
+
+Largely tests error messages when parameters are incorrect.
+"""
+from test.helper import check_evaluation, session
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected", "msgs", "assert_failure_msg"),
+ [
+ (None, None, None, None),
+ #
+ # Base Image Atom
+ #
+ (
+ "Image[{{{1,1,0},{0,1,1}}, {{1,0,1},{1,1,0}}}]",
+ "-Image-",
+ None,
+ "Image Atom B&W",
+ ),
+ (
+ "Image[{{{0,0,0,0.25},{0,0,0,0.5}}, {{0,0,0,0.5},{0,0,0,0.75}}}]",
+ "-Image-",
+ None,
+ "Image Atom RGB",
+ ),
+ #
+ # Operations over images
+ #
+ ('hedy = Import["ExampleData/hedy.tif"];', "Null", None, "Load an image"),
+ (
+ 'ImageData[hedy, "Bytf"]',
+ "ImageData[-Image-, Bytf]",
+ ('Unsupported pixel format "Bytf".',),
+ "Wrong Image Data",
+ ),
+ (
+ "ImagePartition[hedy, 257]",
+ "{{-Image-, -Image-}, {-Image-, -Image-}, {-Image-, -Image-}}",
+ None,
+ None,
+ ),
+ ("ImagePartition[hedy, 646]", "{{-Image-}}", None, None),
+ ("ImagePartition[hedy, 647]", "{}", None, None),
+ (
+ "ImagePartition[hedy, {256, 300}]",
+ "{{-Image-, -Image-}, {-Image-, -Image-}}",
+ None,
+ None,
+ ),
+ (
+ "ImagePartition[hedy, {0, 300}]",
+ "ImagePartition[-Image-, {0, 300}]",
+ ("{0, 300} is not a valid size specification for image partitions.",),
+ None,
+ ),
+ (
+ "{82 / 255, 22 / 255, 57 / 255} // N",
+ "{0.321569, 0.0862745, 0.223529}",
+ None,
+ "pixel byte values from bottom left corner",
+ ),
+ (
+ "PixelValue[hedy, {0, 1}];",
+ "Null",
+ ("Padding not implemented for PixelValue.",),
+ None,
+ ),
+ ("PixelValue[hedy, {512, 1}]", "{0.0509804, 0.0509804, 0.0588235}", None, None),
+ (
+ "PixelValue[hedy, {647, 1}];",
+ "Null",
+ ("Padding not implemented for PixelValue.",),
+ None,
+ ),
+ (
+ "PixelValue[hedy, {1, 0}];",
+ "Null",
+ ("Padding not implemented for PixelValue.",),
+ None,
+ ),
+ ("PixelValue[hedy, {1, 512}]", "{0.286275, 0.4, 0.423529}", None, None),
+ (
+ "PixelValue[hedy, {1, 801}];",
+ "Null",
+ ("Padding not implemented for PixelValue.",),
+ None,
+ ),
+ #
+ # Composition
+ #
+ (
+ "i = Image[{{0, 0.5, 0.2, 0.1, 0.9}, {1.0, 0.1, 0.3, 0.8, 0.6}}];",
+ "Null",
+ None,
+ None,
+ ),
+ ("ImageAdd[i, 0.2, i, 0.1]", "-Image-", None, None),
+ (
+ "ImageAdd[i, x]",
+ "ImageAdd[-Image-, x]",
+ ("Expecting a number, image, or graphics instead of x.",),
+ None,
+ ),
+ ("ImageMultiply[i, 0.2, i, 0.1]", "-Image-", None, None),
+ (
+ "ImageMultiply[i, x]",
+ "ImageMultiply[-Image-, x]",
+ ("Expecting a number, image, or graphics instead of x.",),
+ None,
+ ),
+ (
+ 'ein = Import["ExampleData/Einstein.jpg"]; noise = RandomImage[{0.7, 1.3}, ImageDimensions[ein]];ImageMultiply[noise, ein]',
+ "-Image-",
+ None,
+ "Multiply Image by random noise",
+ ),
+ ("ImageSubtract[i, 0.2, i, 0.1]", "-Image-", None, None),
+ (
+ "ImageSubtract[i, x]",
+ "ImageSubtract[-Image-, x]",
+ ("Expecting a number, image, or graphics instead of x.",),
+ None,
+ ),
+ #
+ # Random
+ #
+ ("RandomImage[0.5]", "-Image-", None, None),
+ ("RandomImage[{0.1, 0.9}]", "-Image-", None, None),
+ ("RandomImage[0.9, {400, 600}]", "-Image-", None, None),
+ ("RandomImage[{0.1, 0.5}, {400, 600}]", "-Image-", None, None),
+ (
+ 'RandomImage[{0.1, 0.5}, {400, 600}, ColorSpace -> "RGB"]',
+ "-Image-",
+ None,
+ None,
+ ),
+ #
+ # Geometry
+ #
+ (
+ "ein == ImageReflect[ein, Left -> Left] == ImageReflect[ein, Right -> Right] == ImageReflect[ein, Top -> Top] == ImageReflect[ein, Bottom -> Bottom]",
+ "True",
+ None,
+ None,
+ ),
+ (
+ "ImageReflect[ein, Left -> Right] == ImageReflect[ein, Right -> Left] == ImageReflect[ein, Left] == ImageReflect[ein, Right]",
+ "True",
+ None,
+ None,
+ ),
+ (
+ "ImageReflect[ein, Bottom -> Top] == ImageReflect[ein, Top -> Bottom] == ImageReflect[ein, Top] == ImageReflect[ein, Bottom]",
+ "True",
+ None,
+ None,
+ ),
+ (
+ "ImageReflect[ein, Left -> Top] == ImageReflect[ein, Right -> Bottom]",
+ "True",
+ None,
+ "Transpose",
+ ),
+ (
+ "ImageReflect[ein, Left -> Bottom] == ImageReflect[ein, Right -> Top]",
+ "True",
+ None,
+ "Anti-Transpose",
+ ),
+ (
+ "ImageReflect[ein, x -> Top]",
+ "ImageReflect[-Image-, x -> Top]",
+ ("x -> Top is not a valid 2D reflection specification.",),
+ None,
+ ),
+ (
+ "ImageRotate[ein, ein]",
+ "ImageRotate[-Image-, -Image-]",
+ (
+ "Angle -Image- should be a real number, one of Top, Bottom, Left, Right, or a rule from one to another.",
+ ),
+ None,
+ ),
+ ],
+)
+def test_private_doctest(str_expr, str_expected, msgs, assert_failure_msg):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ hold_expected=True,
+ expected_messages=msgs,
+ failure_message=assert_failure_msg,
+ )
diff --git a/test/builtin/list/test_association.py b/test/builtin/list/test_association.py
new file mode 100644
index 000000000..999b2b850
--- /dev/null
+++ b/test/builtin/list/test_association.py
@@ -0,0 +1,195 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtins.list.constructing
+"""
+from test.helper import check_evaluation
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "expected_messages", "str_expected", "assert_message"),
+ [
+ (
+ "assoc=<|a -> x, b -> y, c -> <|d -> t|>|>",
+ None,
+ "<|a -> x, b -> y, c -> <|d -> t|>|>",
+ None,
+ ),
+ ('assoc["s"]', None, "Missing[KeyAbsent, s]", None),
+ (
+ "assoc=<|a -> x, b + c -> y, {<|{}|>, a -> {z}}|>",
+ None,
+ "<|a -> {z}, b + c -> y|>",
+ None,
+ ),
+ ("assoc[a]", None, "{z}", None),
+ ('assoc=<|"x" -> 1, {y} -> 1|>', None, "<|x -> 1, {y} -> 1|>", None),
+ ('assoc["x"]', None, "1", None),
+ (
+ "<|<|a -> v|> -> x, <|b -> y, a -> <|c -> z|>, {}, <||>|>, {d}|>[c]",
+ None,
+ "Association[Association[a -> v] -> x, Association[b -> y, a -> Association[c -> z], {}, Association[]], {d}][c]",
+ None,
+ ),
+ (
+ "<|<|a -> v|> -> x, <|b -> y, a -> <|c -> z|>, {d}|>, {}, <||>|>[a]",
+ None,
+ "Association[Association[a -> v] -> x, Association[b -> y, a -> Association[c -> z], {d}], {}, Association[]][a]",
+ None,
+ ),
+ (
+ "assoc=<|<|a -> v|> -> x, <|b -> y, a -> <|c -> z, {d}|>, {}, <||>|>, {}, <||>|>",
+ None,
+ "<|<|a -> v|> -> x, b -> y, a -> Association[c -> z, {d}]|>",
+ None,
+ ),
+ ("assoc[a]", None, "Association[c -> z, {d}]", None),
+ # (
+ # "<|a -> x, b -> y, c -> <|d -> t|>|> // ToBoxes",
+ # None,
+ # "RowBox[{<|, RowBox[{RowBox[{a, ->, x}], ,, RowBox[{b, ->, y}], ,, RowBox[{c, ->, RowBox[{<|, RowBox[{d, ->, t}], |>}]}]}], |>}]",
+ # None,
+ # ),
+ # (
+ # "Association[a -> x, b -> y, c -> Association[d -> t, Association[e -> u]]] // ToBoxes",
+ # None,
+ # "RowBox[{<|, RowBox[{RowBox[{a, ->, x}], ,, RowBox[{b, ->, y}], ,, RowBox[{c, ->, RowBox[{<|, RowBox[{RowBox[{d, ->, t}], ,, RowBox[{e, ->, u}]}], |>}]}]}], |>}]",
+ # None,
+ # ),
+ ("Keys[a -> x]", None, "a", None),
+ (
+ "Keys[{a -> x, a -> y, {a -> z, <|b -> t|>, <||>, {}}}]",
+ None,
+ "{a, a, {a, {b}, {}, {}}}",
+ None,
+ ),
+ (
+ "Keys[{a -> x, a -> y, <|a -> z, {b -> t}, <||>, {}|>}]",
+ None,
+ "{a, a, {a, b}}",
+ None,
+ ),
+ (
+ "Keys[<|a -> x, a -> y, <|a -> z, <|b -> t|>, <||>, {}|>|>]",
+ None,
+ "{a, b}",
+ None,
+ ),
+ (
+ "Keys[<|a -> x, a -> y, {a -> z, {b -> t}, <||>, {}}|>]",
+ None,
+ "{a, b}",
+ None,
+ ),
+ (
+ "Keys[<|a -> x, <|a -> y, b|>|>]",
+ (
+ "The argument Association[a -> x, Association[a -> y, b]] is not a valid Association or a list of rules.",
+ ),
+ "Keys[Association[a -> x, Association[a -> y, b]]]",
+ None,
+ ),
+ (
+ "Keys[<|a -> x, {a -> y, b}|>]",
+ (
+ "The argument Association[a -> x, {a -> y, b}] is not a valid Association or a list of rules.",
+ ),
+ "Keys[Association[a -> x, {a -> y, b}]]",
+ None,
+ ),
+ (
+ "Keys[{a -> x, <|a -> y, b|>}]",
+ (
+ "The argument Association[a -> y, b] is not a valid Association or a list of rules.",
+ ),
+ "Keys[{a -> x, Association[a -> y, b]}]",
+ None,
+ ),
+ (
+ "Keys[{a -> x, {a -> y, b}}]",
+ ("The argument b is not a valid Association or a list of rules.",),
+ "Keys[{a -> x, {a -> y, b}}]",
+ None,
+ ),
+ (
+ "Keys[a -> x, b -> y]",
+ ("Keys called with 2 arguments; 1 argument is expected.",),
+ "Keys[a -> x, b -> y]",
+ None,
+ ),
+ ("Values[a -> x]", None, "x", None),
+ (
+ "Values[{a -> x, a -> y, {a -> z, <|b -> t|>, <||>, {}}}]",
+ None,
+ "{x, y, {z, {t}, {}, {}}}",
+ None,
+ ),
+ (
+ "Values[{a -> x, a -> y, <|a -> z, {b -> t}, <||>, {}|>}]",
+ None,
+ "{x, y, {z, t}}",
+ None,
+ ),
+ (
+ "Values[<|a -> x, a -> y, <|a -> z, <|b -> t|>, <||>, {}|>|>]",
+ None,
+ "{z, t}",
+ None,
+ ),
+ (
+ "Values[<|a -> x, a -> y, {a -> z, {b -> t}, <||>, {}}|>]",
+ None,
+ "{z, t}",
+ None,
+ ),
+ (
+ "Values[<|a -> x, <|a -> y, b|>|>]",
+ (
+ "The argument Association[a -> x, Association[a -> y, b]] is not a valid Association or a list of rules.",
+ ),
+ "Values[Association[a -> x, Association[a -> y, b]]]",
+ None,
+ ),
+ (
+ "Values[<|a -> x, {a -> y, b}|>]",
+ (
+ "The argument Association[a -> x, {a -> y, b}] is not a valid Association or a list of rules.",
+ ),
+ "Values[Association[a -> x, {a -> y, b}]]",
+ None,
+ ),
+ (
+ "Values[{a -> x, <|a -> y, b|>}]",
+ (
+ "The argument {a -> x, Association[a -> y, b]} is not a valid Association or a list of rules.",
+ ),
+ "Values[{a -> x, Association[a -> y, b]}]",
+ None,
+ ),
+ (
+ "Values[{a -> x, {a -> y, b}}]",
+ (
+ "The argument {a -> x, {a -> y, b}} is not a valid Association or a list of rules.",
+ ),
+ "Values[{a -> x, {a -> y, b}}]",
+ None,
+ ),
+ (
+ "Values[a -> x, b -> y]",
+ ("Values called with 2 arguments; 1 argument is expected.",),
+ "Values[a -> x, b -> y]",
+ None,
+ ),
+ ("assoc=.;subassoc=.;", None, "Null", None),
+ ],
+)
+def test_associations_private_doctests(
+ str_expr, expected_messages, str_expected, assert_message
+):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ failure_message=assert_message,
+ expected_messages=expected_messages,
+ )
diff --git a/test/builtin/list/constructing.py b/test/builtin/list/test_constructing.py
similarity index 100%
rename from test/builtin/list/constructing.py
rename to test/builtin/list/test_constructing.py
diff --git a/test/builtin/list/test_eol.py b/test/builtin/list/test_eol.py
new file mode 100644
index 000000000..a0f223998
--- /dev/null
+++ b/test/builtin/list/test_eol.py
@@ -0,0 +1,256 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtins.list.constructing
+"""
+from test.helper import check_evaluation
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "expected_messages", "str_expected", "assert_message"),
+ [
+ ("Append[a, b]", ("Nonatomic expression expected.",), "Append[a, b]", None),
+ (
+ "AppendTo[{}, 1]",
+ ("{} is not a variable with a value, so its value cannot be changed.",),
+ "AppendTo[{}, 1]",
+ None,
+ ),
+ (
+ "AppendTo[a, b]",
+ ("a is not a variable with a value, so its value cannot be changed.",),
+ "AppendTo[a, b]",
+ None,
+ ),
+ ("Cases[1, 2]", None, "{}", None),
+ ("Cases[f[1, 2], 2]", None, "{2}", None),
+ ("Cases[f[f[1, 2], f[2]], 2]", None, "{}", None),
+ ("Cases[f[f[1, 2], f[2]], 2, 2]", None, "{2, 2}", None),
+ ("Cases[f[f[1, 2], f[2], 2], 2, Infinity]", None, "{2, 2, 2}", None),
+ (
+ "Cases[{1, f[2], f[3, 3, 3], 4, f[5, 5]}, f[x__] :> Plus[x]]",
+ None,
+ "{2, 9, 10}",
+ None,
+ ),
+ (
+ "Cases[{1, f[2], f[3, 3, 3], 4, f[5, 5]}, f[x__] -> Plus[x]]",
+ None,
+ "{2, 3, 3, 3, 5, 5}",
+ None,
+ ),
+ ("z = f[x, y]; x = 1; Cases[z, _Symbol, Infinity]", None, "{y}", "Issue 531"),
+ (
+ "x=.;a=.;b=.;c=.;f=.; g=.;d=.;m=.;n=.;Delete[1 + x ^ (a + b + c), {2, 2, 3}]",
+ None,
+ "1 + x ^ (a + b)",
+ "Faiing?",
+ ),
+ ("Delete[f[a, g[b, c], d], {{2}, {2, 1}}]", None, "f[a, d]", None),
+ (
+ "Delete[f[a, g[b, c], d], m + n]",
+ (
+ "The expression m + n cannot be used as a part specification. Use Key[m + n] instead.",
+ ),
+ "Delete[f[a, g[b, c], d], m + n]",
+ None,
+ ),
+ (
+ "Delete[{a, b, c, d}, {{1}, n}]",
+ (
+ "Position specification {n, {1}} in {a, b, c, d} is not a machine-sized integer or a list of machine-sized integers.",
+ ),
+ "Delete[{a, b, c, d}, {{1}, n}]",
+ None,
+ ),
+ (
+ "Delete[{a, b, c, d}, {{1}, {n}}]",
+ (
+ "Position specification n in {a, b, c, d} is not a machine-sized integer or a list of machine-sized integers.",
+ ),
+ "Delete[{a, b, c, d}, {{1}, {n}}]",
+ None,
+ ),
+ ("z = {x, y}; x = 1; DeleteCases[z, _Symbol]", None, "{1}", "Issue 531"),
+ ("x=.;z=.;", None, "Null", None),
+ ("Drop[Range[10], {-2, -6, -3}]", None, "{1, 2, 3, 4, 5, 7, 8, 10}", None),
+ ("Drop[Range[10], {10, 1, -3}]", None, "{2, 3, 5, 6, 8, 9}", None),
+ (
+ "Drop[Range[6], {-5, -2, -2}]",
+ ("Cannot drop positions -5 through -2 in {1, 2, 3, 4, 5, 6}.",),
+ "Drop[{1, 2, 3, 4, 5, 6}, {-5, -2, -2}]",
+ None,
+ ),
+ ('FirstPosition[{1, 2, 3}, _?StringQ, "NoStrings"]', None, "NoStrings", None),
+ ("FirstPosition[a, a]", None, "{}", None),
+ (
+ "FirstPosition[{{{1, 2}, {2, 3}, {3, 1}}, {{1, 2}, {2, 3}, {3, 1}}},3]",
+ None,
+ "{1, 2, 2}",
+ None,
+ ),
+ (
+ 'FirstPosition[{{1, {2, 1}}, {2, 3}, {3, 1}}, 2, Missing["NotFound"],2]',
+ None,
+ "{2, 1}",
+ None,
+ ),
+ (
+ 'FirstPosition[{{1, {2, 1}}, {2, 3}, {3, 1}}, 2, Missing["NotFound"],4]',
+ None,
+ "{1, 2, 1}",
+ None,
+ ),
+ (
+ 'FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing["NotFound"], {1}]',
+ None,
+ "Missing[NotFound]",
+ None,
+ ),
+ (
+ 'FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing["NotFound"], 0]',
+ None,
+ "Missing[NotFound]",
+ None,
+ ),
+ (
+ 'FirstPosition[{{1, 2}, {1, {2, 1}}, {2, 3}}, 2, Missing["NotFound"], {3}]',
+ None,
+ "{2, 2, 1}",
+ None,
+ ),
+ (
+ 'FirstPosition[{{1, 2}, {1, {2, 1}}, {2, 3}}, 2, Missing["NotFound"], 3]',
+ None,
+ "{1, 2}",
+ None,
+ ),
+ (
+ 'FirstPosition[{{1, 2}, {1, {2, 1}}, {2, 3}}, 2, Missing["NotFound"], {}]',
+ None,
+ "{1, 2}",
+ None,
+ ),
+ (
+ 'FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing["NotFound"], {1, 2, 3}]',
+ ("Level specification {1, 2, 3} is not of the form n, {n}, or {m, n}.",),
+ "FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing[NotFound], {1, 2, 3}]",
+ None,
+ ),
+ (
+ 'FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing["NotFound"], a]',
+ ("Level specification a is not of the form n, {n}, or {m, n}.",),
+ "FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing[NotFound], a]",
+ None,
+ ),
+ (
+ 'FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing["NotFound"], {1, a}]',
+ ("Level specification {1, a} is not of the form n, {n}, or {m, n}.",),
+ "FirstPosition[{{1, 2}, {2, 3}, {3, 1}}, 3, Missing[NotFound], {1, a}]",
+ None,
+ ),
+ ("A[x__] := 7 /; Length[{x}] == 3;Most[A[1, 2, 3, 4]]", None, "7", None),
+ ("ClearAll[A];", None, "Null", None),
+ ("a = {2,3,4}; i = 1; a[[i]] = 0; a", None, "{0, 3, 4}", None),
+ ## Negative step
+ ("{1,2,3,4,5}[[3;;1;;-1]]", None, "{3, 2, 1}", None),
+ ("{1, 2, 3, 4, 5}[[;; ;; -1]]", None, "{5, 4, 3, 2, 1}", "MMA bug"),
+ ("Range[11][[-3 ;; 2 ;; -2]]", None, "{9, 7, 5, 3}", None),
+ ("Range[11][[-3 ;; -7 ;; -3]]", None, "{9, 6}", None),
+ ("Range[11][[7 ;; -7;; -2]]", None, "{7, 5}", None),
+ (
+ "{1, 2, 3, 4}[[1;;3;;-1]]",
+ ("Cannot take positions 1 through 3 in {1, 2, 3, 4}.",),
+ "{1, 2, 3, 4}[[1 ;; 3 ;; -1]]",
+ None,
+ ),
+ (
+ "{1, 2, 3, 4}[[3;;1]]",
+ ("Cannot take positions 3 through 1 in {1, 2, 3, 4}.",),
+ "{1, 2, 3, 4}[[3 ;; 1]]",
+ None,
+ ),
+ (
+ "a=.;b=.;Prepend[a, b]",
+ ("Nonatomic expression expected.",),
+ "Prepend[a, b]",
+ "Prepend works with non-atomic expressions",
+ ),
+ (
+ "PrependTo[{a, b}, 1]",
+ ("{a, b} is not a variable with a value, so its value cannot be changed.",),
+ "PrependTo[{a, b}, 1]",
+ None,
+ ),
+ (
+ "PrependTo[a, b]",
+ ("a is not a variable with a value, so its value cannot be changed.",),
+ "PrependTo[a, b]",
+ None,
+ ),
+ (
+ "x = 1 + 2;PrependTo[x, {3, 4}]",
+ ("Nonatomic expression expected at position 1 in PrependTo[x, {3, 4}].",),
+ "PrependTo[x, {3, 4}]",
+ None,
+ ),
+ (
+ "A[x__] := 31415 /; Length[{x}] == 3; Select[A[5, 2, 7, 1], OddQ]",
+ None,
+ "31415",
+ None,
+ ),
+ ("ClearAll[A];", None, "Null", None),
+ ## Parsing: 8 cases to consider
+ ("a=.;b=.;c=.; a ;; b ;; c // FullForm", None, "Span[a, b, c]", None),
+ (" ;; b ;; c // FullForm", None, "Span[1, b, c]", None),
+ ("a ;; ;; c // FullForm", None, "Span[a, All, c]", None),
+ (" ;; ;; c // FullForm", None, "Span[1, All, c]", None),
+ ("a ;; b // FullForm", None, "Span[a, b]", None),
+ (" ;; b // FullForm", None, "Span[1, b]", None),
+ ("a ;; // FullForm", None, "Span[a, All]", None),
+ (" ;; // FullForm", None, "Span[1, All]", None),
+ ## Formatting
+ ("a ;; b ;; c", None, "a ;; b ;; c", None),
+ ("a ;; b", None, "a ;; b", None),
+ # TODO: Rework this test
+ ("{a ;; b ;; c ;; d}", None, "{a ;; b ;; c, 1 ;; d}", ";; association"),
+ ("Take[Range[10], {8, 2, -1}]", None, "{8, 7, 6, 5, 4, 3, 2}", None),
+ ("Take[Range[10], {-3, -7, -2}]", None, "{8, 6, 4}", None),
+ (
+ "Take[Range[6], {-5, -2, -2}]",
+ ("Cannot take positions -5 through -2 in {1, 2, 3, 4, 5, 6}.",),
+ "Take[{1, 2, 3, 4, 5, 6}, {-5, -2, -2}]",
+ None,
+ ),
+ (
+ "Take[l, {-1}]",
+ ("Nonatomic expression expected at position 1 in Take[l, {-1}].",),
+ "Take[l, {-1}]",
+ None,
+ ),
+ ## Empty case
+ ("Take[{1, 2, 3, 4, 5}, {-1, -2}]", None, "{}", None),
+ ("Take[{1, 2, 3, 4, 5}, {0, -1}]", None, "{}", None),
+ ("Take[{1, 2, 3, 4, 5}, {1, 0}]", None, "{}", None),
+ ("Take[{1, 2, 3, 4, 5}, {2, 1}]", None, "{}", None),
+ ("Take[{1, 2, 3, 4, 5}, {1, 0, 2}]", None, "{}", None),
+ (
+ "Take[{1, 2, 3, 4, 5}, {1, 0, -1}]",
+ ("Cannot take positions 1 through 0 in {1, 2, 3, 4, 5}.",),
+ "Take[{1, 2, 3, 4, 5}, {1, 0, -1}]",
+ None,
+ ),
+ ],
+)
+def test_eol_edicates_private_doctests(
+ str_expr, expected_messages, str_expected, assert_message
+):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ failure_message=assert_message,
+ expected_messages=expected_messages,
+ hold_expected=True,
+ )
diff --git a/test/builtin/list/test_list.py b/test/builtin/list/test_list.py
new file mode 100644
index 000000000..f0adfa807
--- /dev/null
+++ b/test/builtin/list/test_list.py
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtins.list.constructing
+"""
+from test.helper import check_evaluation
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "expected_messages", "str_expected", "assert_message"),
+ [
+ (
+ "Complement[a, b]",
+ ("Non-atomic expression expected at position 1 in Complement[a, b].",),
+ "Complement[a, b]",
+ None,
+ ),
+ (
+ "Complement[f[a], g[b]]",
+ ("Heads f and g at positions 1 and 2 are expected to be the same.",),
+ "Complement[f[a], g[b]]",
+ None,
+ ),
+ ("Complement[{a, b, c}, {a, c}, SameTest->(True&)]", None, "{}", None),
+ ("Complement[{a, b, c}, {a, c}, SameTest->(False&)]", None, "{a, b, c}", None),
+ ("DeleteDuplicates[{3,2,1,2,3,4}, Greater]", None, "{3, 3, 4}", None),
+ ("DeleteDuplicates[{}]", None, "{}", None),
+ #
+ ## Flatten
+ #
+ (
+ "Flatten[{{1, 2}, {3, 4}}, {{-1, 2}}]",
+ (
+ "Levels to be flattened together in {{-1, 2}} should be lists of positive integers.",
+ ),
+ "Flatten[{{1, 2}, {3, 4}}, {{-1, 2}}, List]",
+ None,
+ ),
+ (
+ "Flatten[{a, b}, {{1}, {2}}]",
+ (
+ "Level 2 specified in {{1}, {2}} exceeds the levels, 1, which can be flattened together in {a, b}.",
+ ),
+ "Flatten[{a, b}, {{1}, {2}}, List]",
+ None,
+ ),
+ (
+ "m = {{{1, 2}, {3}}, {{4}, {5, 6}}};Flatten[m, {{2}, {1}, {3}, {4}}]",
+ (
+ "Level 4 specified in {{2}, {1}, {3}, {4}} exceeds the levels, 3, which can be flattened together in {{{1, 2}, {3}}, {{4}, {5, 6}}}.",
+ ),
+ "Flatten[{{{1, 2}, {3}}, {{4}, {5, 6}}}, {{2}, {1}, {3}, {4}}, List]",
+ "Check `n` completion",
+ ),
+ (
+ "m = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};Flatten[m, {3}]",
+ (
+ "Level 3 specified in {3} exceeds the levels, 2, which can be flattened together in {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}.",
+ ),
+ "Flatten[{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, {3}, List]",
+ "Test from issue #251",
+ ),
+ (
+ "Flatten[{{1}, 2}, {1, 2}]",
+ (
+ "Level 2 specified in {1, 2} exceeds the levels, 1, which can be flattened together in {{1}, 2}.",
+ ),
+ "Flatten[{{1}, 2}, {1, 2}, List]",
+ "Reproduce strange head behaviour",
+ ),
+ (
+ "Flatten[a[b[1, 2], b[3]], {1, 2}, b]",
+ (
+ "Level 1 specified in {1, 2} exceeds the levels, 0, which can be flattened together in a[b[1, 2], b[3]].",
+ ),
+ "Flatten[a[b[1, 2], b[3]], {1, 2}, b]",
+ "MMA BUG: {{1, 2}} not {1, 2}",
+ ),
+ (
+ "Flatten[{{1, 2}, {3, {4}}}, {{1, 2, 3}}]",
+ (
+ "Level 3 specified in {{1, 2, 3}} exceeds the levels, 2, which can be flattened together in {{1, 2}, {3, {4}}}.",
+ ),
+ "Flatten[{{1, 2}, {3, {4}}}, {{1, 2, 3}}, List]",
+ None,
+ ),
+ #
+ # Join
+ #
+ ("x=.;y=.;z=.;a=.;m=.;", None, "Null", None),
+ ("Join[x, y]", None, "Join[x, y]", None),
+ ("Join[x + y, z]", None, "Join[x + y, z]", None),
+ (
+ "Join[x + y, y z, a]",
+ ("Heads Plus and Times are expected to be the same.",),
+ "Join[x + y, y z, a]",
+ None,
+ ),
+ ("Join[x, y + z, y z]", None, "Join[x, y + z, y z]", None),
+ # Partition
+ ("Partition[{a, b, c, d, e}, 2]", None, "{{a, b}, {c, d}}", None),
+ # Riffle
+ ("Riffle[{1, 2, 3, 4}, {x, y, z, t}]", None, "{1, x, 2, y, 3, z, 4, t}", None),
+ ("Riffle[{1, 2}, {1, 2, 3}]", None, "{1, 1, 2}", None),
+ ("Riffle[{1, 2}, {1, 2}]", None, "{1, 1, 2, 2}", None),
+ ("Riffle[{a,b,c}, {}]", None, "{a, {}, b, {}, c}", None),
+ ("Riffle[{}, {}]", None, "{}", None),
+ ("Riffle[{}, {a,b}]", None, "{}", None),
+ # Split
+ (
+ "Split[{x, x, x, y, x, y, y, z}, x]",
+ None,
+ "{{x}, {x}, {x}, {y}, {x}, {y}, {y}, {z}}",
+ None,
+ ),
+ ("Split[{}]", None, "{}", None),
+ (
+ "A[x__] := 321 /; Length[{x}] == 5;Split[A[x, x, x, y, x, y, y, z]]",
+ None,
+ "321",
+ None,
+ ),
+ ("ClearAll[A];", None, "Null", None),
+ # SplitBy
+ (
+ "SplitBy[Tuples[{1, 2}, 3], First]",
+ None,
+ "{{{1, 1, 1}, {1, 1, 2}, {1, 2, 1}, {1, 2, 2}}, {{2, 1, 1}, {2, 1, 2}, {2, 2, 1}, {2, 2, 2}}}",
+ None,
+ ),
+ # Union and Intersection
+ (
+ "Union[{1, -1, 2}, {-2, 3}, SameTest -> (Abs[#1] == Abs[#2] &)]",
+ None,
+ "{-2, 1, 3}",
+ "Union",
+ ),
+ (
+ "Intersection[{1, -1, -2, 2, -3}, {1, -2, 2, 3}, SameTest -> (Abs[#1] == Abs[#2] &)]",
+ None,
+ "{-3, -2, 1}",
+ "Intersection",
+ ),
+ ],
+)
+def test_rearrange_private_doctests(
+ str_expr, expected_messages, str_expected, assert_message
+):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ failure_message=assert_message,
+ expected_messages=expected_messages,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "expected_messages", "str_expected", "assert_message"),
+ [
+ (
+ "ContainsOnly[1, {1, 2, 3}]",
+ ("List or association expected instead of 1.",),
+ "ContainsOnly[1, {1, 2, 3}]",
+ None,
+ ),
+ (
+ "ContainsOnly[{1, 2, 3}, 4]",
+ ("List or association expected instead of 4.",),
+ "ContainsOnly[{1, 2, 3}, 4]",
+ None,
+ ),
+ (
+ "ContainsOnly[{c, a}, {a, b, c}, IgnoreCase -> True]",
+ (
+ "Unknown option IgnoreCase -> True in ContainsOnly.",
+ "Unknown option IgnoreCase in .",
+ ),
+ "True",
+ None,
+ ),
+ ],
+)
+def test_predicates_private_doctests(
+ str_expr, expected_messages, str_expected, assert_message
+):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ failure_message=assert_message,
+ expected_messages=expected_messages,
+ )
diff --git a/test/builtin/numbers/test_algebra.py b/test/builtin/numbers/test_algebra.py
index 71f95cc4c..e1cbee3b3 100644
--- a/test/builtin/numbers/test_algebra.py
+++ b/test/builtin/numbers/test_algebra.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""
-Unit tests for mathics.builtins.numbers.algebra
+Unit tests for mathics.builtins.numbers.algebra and
+mathics.builtins.numbers.integer
"""
from test.helper import check_evaluation
@@ -329,3 +330,183 @@ def test_fullsimplify():
),
):
check_evaluation(str_expr, str_expected, failure_message)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("Attributes[f] = {HoldAll}; Apart[f[x + x]]", None, "f[x + x]", None),
+ ("Attributes[f] = {}; Apart[f[x + x]]", None, "f[2 x]", None),
+ ## Errors:
+ (
+ "Coefficient[x + y + 3]",
+ ("Coefficient called with 1 argument; 2 or 3 arguments are expected.",),
+ "Coefficient[3 + x + y]",
+ None,
+ ),
+ (
+ "Coefficient[x + y + 3, 5]",
+ ("5 is not a valid variable.",),
+ "Coefficient[3 + x + y, 5]",
+ None,
+ ),
+ ## This is known bug of Sympy 1.0, next Sympy version will fix it by this commit
+ ## https://github.com/sympy/sympy/commit/25bf64b64d4d9a2dc563022818d29d06bc740d47
+ ("Coefficient[x * y, z, 0]", None, "x y", "Sympy 1.0 retuns 0"),
+ ## TODO: Support Modulus
+ # ("Coefficient[(x + 2)^3 + (x + 3)^2, x, 0, {Modulus -> 3, Modulus -> 2, Modulus -> 10}]",
+ # None,"{2, 1, 7}", None),
+ (
+ "CoefficientList[x + y, 5]",
+ ("5 is not a valid variable.",),
+ "CoefficientList[x + y, 5]",
+ None,
+ ),
+ (
+ "CoefficientList[(x - 2 y)^4, {x, 2}]",
+ ("2 is not a valid variable.",),
+ "CoefficientList[(x - 2 y) ^ 4, {x, 2}]",
+ None,
+ ),
+ (
+ "CoefficientList[x / y, {x, y}]",
+ ("x / y is not a polynomial.",),
+ "CoefficientList[x / y, {x, y}]",
+ None,
+ ),
+ ("Expand[x, Modulus -> -1] (* copy odd MMA behaviour *)", None, "0", None),
+ (
+ "Expand[x, Modulus -> x]",
+ ("Value of option Modulus -> x should be an integer.",),
+ "Expand[x, Modulus -> x]",
+ None,
+ ),
+ ("a(b(c+d)+e) // Expand", None, "a b c + a b d + a e", None),
+ ("(y^2)^(1/2)/(2x+2y)//Expand", None, "Sqrt[y ^ 2] / (2 x + 2 y)", None),
+ (
+ "2(3+2x)^2/(5+x^2+3x)^3 // Expand",
+ None,
+ "24 x / (5 + 3 x + x ^ 2) ^ 3 + 8 x ^ 2 / (5 + 3 x + x ^ 2) ^ 3 + 18 / (5 + 3 x + x ^ 2) ^ 3",
+ None,
+ ),
+ ## Modulus option
+ (
+ "ExpandDenominator[1 / (x + y)^3, Modulus -> 3]",
+ None,
+ "1 / (x ^ 3 + y ^ 3)",
+ None,
+ ),
+ (
+ "ExpandDenominator[1 / (x + y)^6, Modulus -> 4]",
+ None,
+ "1 / (x ^ 6 + 2 x ^ 5 y + 3 x ^ 4 y ^ 2 + 3 x ^ 2 y ^ 4 + 2 x y ^ 5 + y ^ 6)",
+ None,
+ ),
+ (
+ "ExpandDenominator[2(3+2x)^2/(5+x^2+3x)^3]",
+ None,
+ "2 (3 + 2 x) ^ 2 / (125 + 225 x + 210 x ^ 2 + 117 x ^ 3 + 42 x ^ 4 + 9 x ^ 5 + x ^ 6)",
+ None,
+ ),
+ ## errors:
+ (
+ "Exponent[x^2]",
+ ("Exponent called with 1 argument; 2 or 3 arguments are expected.",),
+ "Exponent[x ^ 2]",
+ None,
+ ),
+ ## Issue659
+ ("Factor[{x+x^2}]", None, "{x (1 + x)}", None),
+ ("FactorTermsList[2 x^2 - 2, x]", None, "{2, 1, -1 + x ^ 2}", None),
+ (
+ "MinimalPolynomial[7a, x]",
+ ("7 a is not an explicit algebraic number.",),
+ "MinimalPolynomial[7 a, x]",
+ None,
+ ),
+ (
+ "MinimalPolynomial[3x^3 + 2x^2 + y^2 + ab, x]",
+ ("ab + 2 x ^ 2 + 3 x ^ 3 + y ^ 2 is not an explicit algebraic number.",),
+ "MinimalPolynomial[ab + 2 x ^ 2 + 3 x ^ 3 + y ^ 2, x]",
+ None,
+ ),
+ ## PurePoly
+ ("MinimalPolynomial[Sqrt[2 + Sqrt[3]]]", None, "1 - 4 #1 ^ 2 + #1 ^ 4", None),
+ (
+ "PolynomialQ[x, x, y]",
+ ("PolynomialQ called with 3 arguments; 1 or 2 arguments are expected.",),
+ "PolynomialQ[x, x, y]",
+ None,
+ ),
+ ## Always return True if argument is Null
+ (
+ "PolynomialQ[x^3 - 2 x/y + 3xz, ]",
+ None,
+ "True",
+ "Always return True if argument is Null",
+ ),
+ (
+ "PolynomialQ[, {x, y, z}]",
+ None,
+ "True",
+ "True if the expression is Null",
+ ),
+ (
+ "PolynomialQ[, ]",
+ None,
+ "True",
+ None,
+ ),
+ ## TODO: MMA and Sympy handle these cases differently
+ ## #> PolynomialQ[x^(1/2) + 6xyz]
+ ## : No variable is not supported in PolynomialQ.
+ ## = True
+ ## #> PolynomialQ[x^(1/2) + 6xyz, {}]
+ ## : No variable is not supported in PolynomialQ.
+ ## = True
+ ## #> PolynomialQ[x^3 - 2 x/y + 3xz]
+ ## : No variable is not supported in PolynomialQ.
+ ## = False
+ ## #> PolynomialQ[x^3 - 2 x/y + 3xz, {}]
+ ## : No variable is not supported in PolynomialQ.
+ ## = False
+ ("f[x]/x+f[x]/x^2//Together", None, "f[x] (1 + x) / x ^ 2", None),
+ ## failing test case from MMA docs
+ ("Variables[E^x]", None, "{}", None),
+ ],
+)
+def test_private_doctests_algebra(str_expr, msgs, str_expected, fail_msg):
+ """doctests for algebra"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ "FromDigits[x]",
+ ("The input must be a string of digits or a list.",),
+ "FromDigits[x, 10]",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_integer(str_expr, msgs, str_expected, fail_msg):
+ """doctests for integer"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/numbers/test_calculus.py b/test/builtin/numbers/test_calculus.py
index 44d36a9e3..8a2ff5f48 100644
--- a/test/builtin/numbers/test_calculus.py
+++ b/test/builtin/numbers/test_calculus.py
@@ -2,7 +2,7 @@
"""
Unit tests for mathics.builtins.numbers.calculus
-In parituclar:
+In partiuclar:
FindRoot[], FindMinimum[], NFindMaximum[] tests
@@ -13,7 +13,7 @@
import pytest
-from mathics.builtin.base import check_requires_list
+from mathics.core.builtin import check_requires_list
if check_requires_list(["scipy", "scipy.integrate"]):
methods_findminimum = ["Automatic", "Newton", "brent", "golden"]
@@ -193,3 +193,109 @@ def test_Solve(str_expr: str, str_expected: str, expected_messages):
str_expected=str_expected,
expected_messages=expected_messages,
)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (None, None, None, None),
+ ("Maximize[1 - (x y - 3)^2, {x, y}]", None, "{{1, {x -> 3, y -> 1}}}", None),
+ (
+ "Maximize[{x - 2 y, x^2 + y^2 <= 1}, {x, y}]",
+ None,
+ "{{Sqrt[5], {x -> Sqrt[5] / 5, y -> -2 Sqrt[5] / 5}}}",
+ None,
+ ),
+ ("Minimize[(x y - 3)^2 + 1, {x, y}]", None, "{{1, {x -> 3, y -> 1}}}", None),
+ (
+ "Minimize[{x - 2 y, x^2 + y^2 <= 1}, {x, y}]",
+ None,
+ "{{-Sqrt[5], {x -> -Sqrt[5] / 5, y -> 2 Sqrt[5] / 5}}}",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_optimization(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ "D[2/3 Cos[x] - 1/3 x Cos[x] Sin[x] ^ 2,x]//Expand",
+ None,
+ "-2 x Cos[x] ^ 2 Sin[x] / 3 + x Sin[x] ^ 3 / 3 - 2 Sin[x] / 3 - Cos[x] Sin[x] ^ 2 / 3",
+ None,
+ ),
+ ("D[f[#1], {#1,2}]", None, "f''[#1]", None),
+ ("D[(#1&)[t],{t,4}]", None, "0", None),
+ ("Attributes[f] ={HoldAll}; Apart[f''[x + x]]", None, "f''[2 x]", None),
+ ("Attributes[f] = {}; Apart[f''[x + x]]", None, "f''[2 x]", None),
+ ## Issue #375
+ ("D[{#^2}, #]", None, "{2 #1}", None),
+ ("FindRoot[2.5==x,{x,0}]", None, "{x -> 2.5}", None),
+ ("DownValues[Integrate]", None, "{}", None),
+ (
+ "Definition[Integrate]",
+ None,
+ (
+ "Attributes[Integrate] = {Protected, ReadProtected}\n"
+ "\n"
+ "Options[Integrate] = {Assumptions -> $Assumptions, GenerateConditions -> Automatic, PrincipalValue -> False}\n"
+ ),
+ None,
+ ),
+ (
+ "Integrate[Hold[x + x], {x, a, b}]",
+ None,
+ "Integrate[Hold[x + x], {x, a, b}]",
+ None,
+ ),
+ ("Integrate[sin[x], x]", None, "Integrate[sin[x], x]", None),
+ ("Integrate[x ^ 3.5 + x, x]", None, "x ^ 2 / 2 + 0.222222 x ^ 4.5", None),
+ (
+ "Integrate[1/(x^5+1), x]",
+ None,
+ "RootSum[1 + 5 #1 + 25 #1 ^ 2 + 125 #1 ^ 3 + 625 #1 ^ 4&, Log[x + 5 #1] #1&] + Log[1 + x] / 5",
+ None,
+ ),
+ ("Integrate[ArcTan(x), x]", None, "x ^ 2 ArcTan / 2", None),
+ ("Integrate[E[x], x]", None, "Integrate[E[x], x]", None),
+ ("Integrate[Exp[-(x/2)^2],{x,-Infinity,+Infinity}]", None, "2 Sqrt[Pi]", None),
+ (
+ "Integrate[Exp[-1/(x^2)], x]",
+ None,
+ "x E ^ (-1 / x ^ 2) + Sqrt[Pi] Erf[1 / x]",
+ None,
+ ),
+ ("True'", None, "True'", None),
+ ("False'", None, "False'", None),
+ ("List'", None, "{1}&", None),
+ ("1'", None, "0&", None),
+ ("-1.4'", None, "-(0&)", None),
+ ("(2/3)'", None, "0&", None),
+ ("I'", None, "0&", None),
+ ("Derivative[0,0,1][List]", None, "{0, 0, 1}&", None),
+ ],
+)
+def test_private_doctests_calculus(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/numbers/test_constants.py b/test/builtin/numbers/test_constants.py
index 700419a5a..c5b74a8a7 100644
--- a/test/builtin/numbers/test_constants.py
+++ b/test/builtin/numbers/test_constants.py
@@ -4,6 +4,8 @@
"""
from test.helper import check_evaluation
+import pytest
+
def test_Undefined():
for fn in [
@@ -50,3 +52,47 @@ def test_Undefined():
]:
check_evaluation(f"{fn}[a, Undefined]", "Undefined")
check_evaluation(f"{fn}[Undefined, b]", "Undefined")
+
+
+# This is a miscelanea of private tests. I put here to make it easier to check
+# where these tests comes from. Then, we can move them to more suitable places.
+@pytest.mark.parametrize(
+ ("expr_str", "expected_str", "fail_msg", "msgs"),
+ [
+ (
+ "ComplexInfinity + ComplexInfinity",
+ "Indeterminate",
+ "Issue689",
+ ["Indeterminate expression ComplexInfinity + ComplexInfinity encountered."],
+ ),
+ (
+ "ComplexInfinity + Infinity",
+ "Indeterminate",
+ "Issue689",
+ ["Indeterminate expression ComplexInfinity + Infinity encountered."],
+ ),
+ ("Cos[Degree[x]]", "Cos[Degree[x]]", "Degree as a function", None),
+ ("N[Degree]//OutputForm", "0.0174533", "Degree", None),
+ ("5. E//OutputForm", "13.5914", "E", None),
+ ("N[Degree, 30]//OutputForm", "0.0174532925199432957692369076849", None, None),
+ ("FullForm[Infinity]", "DirectedInfinity[1]", None, None),
+ ("(2 + 3.5*I) / Infinity", "0. + 0. I", "Complex over Infinity", None),
+ ("Infinity + Infinity", "Infinity", "Infinity plus Infinity", None),
+ (
+ "Infinity / Infinity",
+ "Indeterminate",
+ "Infinity over Infinity",
+ ["Indeterminate expression 0 Infinity encountered."],
+ ),
+ ],
+)
+def test_constants_private(expr_str, expected_str, fail_msg, msgs):
+ check_evaluation(
+ expr_str,
+ expected_str,
+ fail_msg,
+ expected_messages=msgs,
+ hold_expected=True,
+ to_string_expected=True,
+ to_string_expr=True,
+ )
diff --git a/test/builtin/numbers/test_diffeqns.py b/test/builtin/numbers/test_diffeqns.py
new file mode 100644
index 000000000..46a8579d7
--- /dev/null
+++ b/test/builtin/numbers/test_diffeqns.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtins.numbers.diffeqns
+"""
+from test.helper import check_evaluation
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ## FIXME: sympy solves this as `Function[{x}, C[1] + Integrate[ArcSin[f[2 x]], x]]`
+ # (
+ # "Attributes[f] = {HoldAll}; DSolve[f[x + x] == Sin[f'[x]], f, x]",
+ # (
+ # (
+ # "To avoid possible ambiguity, the arguments of the dependent "
+ # "variable in f[x + x] == Sin[f'[x]] should literally match "
+ # "the independent variables."
+ # ),
+ # ),
+ # "DSolve[f[x + x] == Sin[f'[x]], f, x]",
+ # "sympy solves this as `Function[{x}, C[1] + Integrate[ArcSin[f[2 x]], x]]`",
+ # ),
+ # """
+ # (
+ # "Attributes[f] = {}; DSolve[f[x + x] == Sin[f'[x]], f, x]",
+ # (
+ # (
+ # "To avoid possible ambiguity, the arguments of the dependent "
+ # "variable in f[2 x] == Sin[f'[x]] should literally match "
+ # "the independent variables."
+ # ),
+ # ),
+ # "DSolve[f[2 x] == Sin[f'[x]], f, x]",
+ # None,
+ # ),
+ (
+ "DSolve[f'[x] == f[x], f, x] // FullForm",
+ None,
+ "{{Rule[f, Function[{x}, Times[C[1], Power[E, x]]]]}}",
+ None,
+ ),
+ (
+ "DSolve[f'[x] == f[x], f, x] /. {C[1] -> 1}",
+ None,
+ "{{f -> (Function[{x}, 1 E ^ x])}}",
+ None,
+ ),
+ (
+ "DSolve[f'[x] == f[x], f, x] /. {C -> D}",
+ None,
+ "{{f -> (Function[{x}, D[1] E ^ x])}}",
+ None,
+ ),
+ (
+ "DSolve[f'[x] == f[x], f, x] /. {C[1] -> C[0]}",
+ None,
+ "{{f -> (Function[{x}, C[0] E ^ x])}}",
+ None,
+ ),
+ (
+ "DSolve[f[x] == 0, f, {}]",
+ ("{} cannot be used as a variable.",),
+ "DSolve[f[x] == 0, f, {}]",
+ None,
+ ),
+ ## Order of arguments shoudn't matter
+ (
+ "DSolve[D[f[x, y], x] == D[f[x, y], y], f, {x, y}]",
+ None,
+ "{{f -> (Function[{x, y}, C[1][-x - y]])}}",
+ None,
+ ),
+ (
+ "DSolve[D[f[x, y], x] == D[f[x, y], y], f[x, y], {x, y}]",
+ None,
+ "{{f[x, y] -> C[1][-x - y]}}",
+ None,
+ ),
+ (
+ "DSolve[D[f[x, y], x] == D[f[x, y], y], f[x, y], {y, x}]",
+ None,
+ "{{f[x, y] -> C[1][-x - y]}}",
+ None,
+ ),
+ (
+ "DSolve[\\[Gamma]'[x] == 0, \\[Gamma], x]",
+ None,
+ "{{γ -> (Function[{x}, C[1]])}}",
+ "sympy #11669 test",
+ ),
+ ],
+)
+def test_private_doctests_diffeqns(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/numbers/test_hyperbolic.py b/test/builtin/numbers/test_hyperbolic.py
index 762cab81f..5b7a4cc31 100644
--- a/test/builtin/numbers/test_hyperbolic.py
+++ b/test/builtin/numbers/test_hyperbolic.py
@@ -1,12 +1,15 @@
-# -*- coding: utf-8 -*-
+## -*- coding: utf-8 -*-
"""
-Unit tests for mathics.builtins.numbers.hyperbolic
+Unit tests for mathics.builtins.numbers.hyperbolic and
+mathics.builtins.numbers.exp
These simple verify various rules from
from symja_android_library/symja_android_library/rules/Gudermannian.m
"""
from test.helper import check_evaluation
+import pytest
+
def test_gudermannian():
for str_expr, str_expected in (
@@ -34,3 +37,53 @@ def test_complexexpand():
),
):
check_evaluation(str_expr, str_expected)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("ArcCosh[1.4]", None, "0.867015", None),
+ (
+ "ArcCoth[0.000000000000000000000000000000000000000]",
+ None,
+ "1.57079632679489661923132169163975144210 I",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_hyperbolic(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("Exp[1.*^20]", ("Overflow occurred in computation.",), "Overflow[]", None),
+ ("Log[1000] / Log[10] // Simplify", None, "3", None),
+ ("Log[1.4]", None, "0.336472", None),
+ ("Log[Exp[1.4]]", None, "1.4", None),
+ ("Log[-1.4]", None, "0.336472 + 3.14159 I", None),
+ ("N[Log[10], 30]", None, "2.30258509299404568401799145468", None),
+ ("LogisticSigmoid[I Pi]", None, "LogisticSigmoid[I Pi]", None),
+ ],
+)
+def test_private_doctests_exp(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/numbers/test_linalg.py b/test/builtin/numbers/test_linalg.py
index 73a914082..3494e65e2 100644
--- a/test/builtin/numbers/test_linalg.py
+++ b/test/builtin/numbers/test_linalg.py
@@ -88,3 +88,139 @@ def test_inverse(str_expr, str_expected, fail_msg, warnings):
check_evaluation(
str_expr, str_expected, failure_message="", expected_messages=warnings
)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ "Eigenvalues[{{1, 0}, {0}}]",
+ (
+ "Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.",
+ ),
+ "Eigenvalues[{{1, 0}, {0}}]",
+ None,
+ ),
+ (
+ "Eigenvectors[{{-2, 1, -1}, {-3, 2, 1}, {-1, 1, 0}}]",
+ None,
+ "{{1, 7, 3}, {1, 1, 0}, {0, 0, 0}}",
+ None,
+ ),
+ ## Inconsistent system - ideally we'd print a different message
+ (
+ "LeastSquares[{{1, 1, 1}, {1, 1, 1}}, {1, 0}]",
+ ("Solving for underdetermined system not implemented.",),
+ "LeastSquares[{{1, 1, 1}, {1, 1, 1}}, {1, 0}]",
+ None,
+ ),
+ (
+ "LeastSquares[{1, {2}}, {1, 2}]",
+ ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",),
+ "LeastSquares[{1, {2}}, {1, 2}]",
+ None,
+ ),
+ (
+ "LeastSquares[{{1, 2}, {3, 4}}, {1, {2}}]",
+ ("Argument {1, {2}} at position 2 is not a non-empty rectangular matrix.",),
+ "LeastSquares[{{1, 2}, {3, 4}}, {1, {2}}]",
+ None,
+ ),
+ (
+ "LinearSolve[{1, {2}}, {1, 2}]",
+ ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",),
+ "LinearSolve[{1, {2}}, {1, 2}]",
+ None,
+ ),
+ (
+ "LinearSolve[{{1, 2}, {3, 4}}, {1, {2}}]",
+ ("Argument {1, {2}} at position 2 is not a non-empty rectangular matrix.",),
+ "LinearSolve[{{1, 2}, {3, 4}}, {1, {2}}]",
+ None,
+ ),
+ ("MatrixExp[{{a, 0}, {0, b}}]", None, "{{E ^ a, 0}, {0, E ^ b}}", None),
+ (
+ "MatrixExp[{{1, 0}, {0}}]",
+ (
+ "Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.",
+ ),
+ "MatrixExp[{{1, 0}, {0}}]",
+ None,
+ ),
+ (
+ "MatrixPower[{{0, x}, {0, 0}}, n]",
+ None,
+ "MatrixPower[{{0, x}, {0, 0}}, n]",
+ None,
+ ),
+ (
+ "MatrixPower[{{1, 0}, {0}}, 2]",
+ (
+ "Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.",
+ ),
+ "MatrixPower[{{1, 0}, {0}}, 2]",
+ None,
+ ),
+ (
+ "MatrixRank[{{1, 0}, {0}}]",
+ (
+ "Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.",
+ ),
+ "MatrixRank[{{1, 0}, {0}}]",
+ None,
+ ),
+ (
+ "NullSpace[{1, {2}}]",
+ ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",),
+ "NullSpace[{1, {2}}]",
+ None,
+ ),
+ (
+ "PseudoInverse[{1, {2}}]",
+ ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",),
+ "PseudoInverse[{1, {2}}]",
+ None,
+ ),
+ (
+ "QRDecomposition[{1, {2}}]",
+ ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",),
+ "QRDecomposition[{1, {2}}]",
+ None,
+ ),
+ (
+ "RowReduce[{{1, 0}, {0}}]",
+ (
+ "Argument {{1, 0}, {0}} at position 1 is not a non-empty rectangular matrix.",
+ ),
+ "RowReduce[{{1, 0}, {0}}]",
+ None,
+ ),
+ (
+ "SingularValueDecomposition[{{3/2, 2}, {5/2, 3}}]",
+ ("Symbolic SVD is not implemented, performing numerically.",),
+ (
+ "{{{0.538954, 0.842335}, {0.842335, -0.538954}}, "
+ "{{4.63555, 0.}, {0., 0.107862}}, "
+ "{{0.628678, 0.777666}, {-0.777666, 0.628678}}}"
+ ),
+ None,
+ ),
+ (
+ "SingularValueDecomposition[{1, {2}}]",
+ ("Argument {1, {2}} at position 1 is not a non-empty rectangular matrix.",),
+ "SingularValueDecomposition[{1, {2}}]",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_linalg(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/numbers/test_nintegrate.py b/test/builtin/numbers/test_nintegrate.py
index e88f8ae29..cfe1e2b05 100644
--- a/test/builtin/numbers/test_nintegrate.py
+++ b/test/builtin/numbers/test_nintegrate.py
@@ -10,7 +10,7 @@
import pytest
-from mathics.builtin.base import check_requires_list
+from mathics.core.builtin import check_requires_list
if check_requires_list(["scipy", "scipy.integrate"]):
methods = ["Automatic", "Romberg", "Internal", "NQuadrature"]
diff --git a/test/builtin/numbers/test_numbertheory.py b/test/builtin/numbers/test_numbertheory.py
new file mode 100644
index 000000000..8e5e149b4
--- /dev/null
+++ b/test/builtin/numbers/test_numbertheory.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtins.numbers.numbertheory
+"""
+from test.helper import check_evaluation
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("Divisors[0]", None, "Divisors[0]", None),
+ (
+ "Divisors[{-206, -502, -1702, 9}]",
+ None,
+ (
+ "{{1, 2, 103, 206}, "
+ "{1, 2, 251, 502}, "
+ "{1, 2, 23, 37, 46, 74, 851, 1702}, "
+ "{1, 3, 9}}"
+ ),
+ None,
+ ),
+ ("Length[Divisors[1000*369]]", None, "96", None),
+ ("Length[Divisors[305*176*369*100]]", None, "672", None),
+ ("FractionalPart[b]", None, "FractionalPart[b]", None),
+ ("FractionalPart[{-2.4, -2.5, -3.0}]", None, "{-0.4, -0.5, 0.}", None),
+ ("FractionalPart[14/32]", None, "7 / 16", None),
+ ("FractionalPart[4/(1 + 3 I)]", None, "2 / 5 - I / 5", None),
+ ("FractionalPart[Pi^20]", None, "-8769956796 + Pi ^ 20", None),
+ ("MantissaExponent[E, Pi]", None, "{E / Pi, 1}", None),
+ ("MantissaExponent[Pi, Pi]", None, "{1 / Pi, 2}", None),
+ ("MantissaExponent[5/2 + 3, Pi]", None, "{11 / (2 Pi ^ 2), 2}", None),
+ ("MantissaExponent[b]", None, "MantissaExponent[b]", None),
+ ("MantissaExponent[17, E]", None, "{17 / E ^ 3, 3}", None),
+ ("MantissaExponent[17., E]", None, "{0.84638, 3}", None),
+ ("MantissaExponent[Exp[Pi], 2]", None, "{E ^ Pi / 32, 5}", None),
+ (
+ "MantissaExponent[3 + 2 I, 2]",
+ ("The value 3 + 2 I is not a real number",),
+ "MantissaExponent[3 + 2 I, 2]",
+ None,
+ ),
+ (
+ "MantissaExponent[25, 0.4]",
+ ("Base 0.4 is not a real number greater than 1.",),
+ "MantissaExponent[25, 0.4]",
+ None,
+ ),
+ ("MantissaExponent[0.0000124]", None, "{0.124, -4}", None),
+ ("MantissaExponent[0.0000124, 2]", None, "{0.812646, -16}", None),
+ ("MantissaExponent[0]", None, "{0, 0}", None),
+ ("MantissaExponent[0, 2]", None, "{0, 0}", None),
+ ("PrimePowerQ[1]", None, "False", None),
+ ("RandomPrime[{10,12}, {2,2}]", None, "{{11, 11}, {11, 11}}", None),
+ ("RandomPrime[2, {3,2}]", None, "{{2, 2}, {2, 2}, {2, 2}}", None),
+ ],
+)
+def test_private_doctests_numbertheory(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/numbers/test_randomnumbers.py b/test/builtin/numbers/test_randomnumbers.py
index d2bf37277..9e0e7bccd 100644
--- a/test/builtin/numbers/test_randomnumbers.py
+++ b/test/builtin/numbers/test_randomnumbers.py
@@ -39,3 +39,70 @@ def test_random_sample(str_expr, str_expected):
to_string_expr=True,
to_string_expected=True,
)
+
+
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtins.specialfns.gamma
+"""
+from test.helper import check_evaluation
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ "RandomComplex[] //(0 <= Re[#1] <= 1 && 0 <= Im[#1] <= 1)&",
+ None,
+ "True",
+ None,
+ ),
+ (
+ "z=RandomComplex[{1+I, 5+5I}];1 <= Re[z] <= 5 && 1 <= Im[z] <= 5",
+ None,
+ "True",
+ None,
+ ),
+ (
+ "z=.;RandomComplex[{6.3, 2.5 I}] // Head",
+ None,
+ "Complex",
+ None,
+ ),
+ ("RandomInteger[{1, 5}]// (1<= #1 <= 5)&", None, "True", None),
+ ("RandomReal[]// (0<= #1 <= 1)&", None, "True", None),
+ (
+ "Length /@ RandomReal[100, {2, 3}]",
+ None,
+ "{3, 3}",
+ None,
+ ),
+ (
+ "RandomReal[{0, 1}, {1, -1}]",
+ (
+ "The array dimensions {1, -1} given in position 2 of RandomReal[{0, 1}, {1, -1}] should be a list of non-negative machine-sized integers giving the dimensions for the result.",
+ ),
+ "RandomReal[{0, 1}, {1, -1}]",
+ None,
+ ),
+ (
+ "SeedRandom[x]",
+ ("Argument x should be an integer or string.",),
+ "SeedRandom[x]",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_randomnumbers(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/numbers/test_trig.py b/test/builtin/numbers/test_trig.py
index 5aee15cb7..dbe01c2d4 100644
--- a/test/builtin/numbers/test_trig.py
+++ b/test/builtin/numbers/test_trig.py
@@ -7,6 +7,8 @@
"""
from test.helper import check_evaluation
+import pytest
+
def test_ArcCos():
for str_expr, str_expected in (
@@ -22,3 +24,31 @@ def test_ArcCos():
("ArcCos[(1 + Sqrt[3]) / (2*Sqrt[2])]", "1/12 Pi"),
):
check_evaluation(str_expr, str_expected)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("ArcTan[-1, 1]", None, "3 Pi / 4", None),
+ ("ArcTan[1, -1]", None, "-Pi / 4", None),
+ ("ArcTan[-1, -1]", None, "-3 Pi / 4", None),
+ ("ArcTan[1, 0]", None, "0", None),
+ ("ArcTan[-1, 0]", None, "Pi", None),
+ ("ArcTan[0, 1]", None, "Pi / 2", None),
+ ("ArcTan[0, -1]", None, "-Pi / 2", None),
+ ("Cos[1.5 Pi]", None, "-1.83697×10^-16", None),
+ ("N[Sin[1], 40]", None, "0.8414709848078965066525023216302989996226", None),
+ ("Tan[0.5 Pi]", None, "1.63312×10^16", None),
+ ],
+)
+def test_private_doctests_trig(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/specialfns/test_bessel.py b/test/builtin/specialfns/test_bessel.py
index b6201d76e..77a73ee98 100644
--- a/test/builtin/specialfns/test_bessel.py
+++ b/test/builtin/specialfns/test_bessel.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""
-Unit tests for mathics.builtins.arithmetic.bessel
+Unit tests for mathics.builtins.specialfns.bessel and
+mathics.builtins.specialfns.orthogonal
"""
from test.helper import check_evaluation
@@ -13,7 +14,7 @@
# by SymPy.
[
(
- "BesselI[1/2,z]",
+ "z=.;BesselI[1/2,z]",
"Sqrt[2] Sinh[z] / (Sqrt[z] Sqrt[Pi])",
"BesselI 1/2 rule",
),
@@ -30,3 +31,64 @@ def test_add(str_expr, str_expected, assert_failure_msg):
check_evaluation(
str_expr, str_expected, hold_expected=True, failure_message=assert_failure_msg
)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("AiryAiZero[1]", None, "AiryAiZero[1]", None),
+ ("AiryAiZero[1.]", None, "AiryAiZero[1.]", None),
+ ("AiryAi[AiryAiZero[1]]", None, "0", None),
+ (
+ "N[AiryAiZero[2], 100]",
+ None,
+ "-4.087949444130970616636988701457391060224764699108529754984160876025121946836047394331169160758270562",
+ None,
+ ),
+ ("AiryBiZero[1]", None, "AiryBiZero[1]", None),
+ ("AiryBiZero[1.]", None, "AiryBiZero[1.]", None),
+ ("AiryBi[AiryBiZero[1]]", None, "0", None),
+ (
+ "N[AiryBiZero[2], 100]",
+ None,
+ "-3.271093302836352715680228240166413806300935969100284801485032396261130864238742879252000673830055014",
+ None,
+ ),
+ ("BesselJ[2.5, 1]", None, "0.0494968", None),
+ ],
+)
+def test_private_doctests_bessel(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ "SphericalHarmonicY[1,1,x,y]",
+ None,
+ "-Sqrt[6] E ^ (I y) Sin[x] / (4 Sqrt[Pi])",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_orthogonal(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/specialfns/test_gamma.py b/test/builtin/specialfns/test_gamma.py
new file mode 100644
index 000000000..b5d1d4148
--- /dev/null
+++ b/test/builtin/specialfns/test_gamma.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtins.specialfns.gamma
+"""
+from test.helper import check_evaluation
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("0!", None, "1", None),
+ (
+ "N[Gamma[24/10], 100]",
+ None,
+ "1.242169344504305404913070252268300492431517240992022966055507541481863694148882652446155342679460339",
+ "Issue 203",
+ ),
+ (
+ "res=N[N[Gamma[24/10],100]/N[Gamma[14/10],100],100]",
+ None,
+ "1.400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "Issue 203",
+ ),
+ ("res // Precision", None, "100.", None),
+ (
+ "Gamma[1.*^20]",
+ ("Overflow occurred in computation.",),
+ "Overflow[]",
+ "Overflow",
+ ),
+ ("Gamma[1., 2.]", None, "Gamma[1., 2.]", "needs mpmath for lowergamma"),
+ ],
+)
+def test_private_doctests_gamma(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_assignment.py b/test/builtin/test_assignment.py
index d8d34bd2a..476180249 100644
--- a/test/builtin/test_assignment.py
+++ b/test/builtin/test_assignment.py
@@ -355,6 +355,11 @@ def test_set_and_clear_to_fix(str_expr, str_expected, msg):
"This clears A and B, but not $ContextPath",
("Special symbol $ContextPath cannot be cleared.",),
),
+ # `This test was in mathics.builtin.arithmetic.Sum`. It is clear that it does not
+ # belongs there. On the other hand, this is something to check at the level of the interpreter,
+ # and is not related with Sum, or Set.
+ # ("a=Sum[x^k*Sum[y^l,{l,0,4}],{k,0,4}]]", "None" , "syntax error",
+ # ('"a=Sum[x^k*Sum[y^l,{l,0,4}],{k,0,4}]" cannot be followed by "]" (line 1 of "").',))
],
)
def test_set_and_clear_messages(str_expr, str_expected, message, out_msgs):
@@ -408,3 +413,40 @@ def test_process_assign_other():
"Cannot set $ModuleNumber to -1; value must be a positive integer."
],
)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected", "msgs", "failure_msg"),
+ [
+ (None, None, None, None),
+ # From Clear
+ ("x = 2;OwnValues[x]=.;x", "x", None, "Erase Ownvalues"),
+ ("f[a][b] = 3; SubValues[f] =.;f[a][b]", "f[a][b]", None, "Erase Subvalues"),
+ ("PrimeQ[p] ^= True; PrimeQ[p]", "True", None, "Subvalues"),
+ ("UpValues[p]=.; PrimeQ[p]", "False", None, "Erase Subvalues"),
+ ("a + b ^= 5; a =.; a + b", "5", None, None),
+ ("{UpValues[a], UpValues[b]} =.; a+b", "a+b", None, None),
+ (
+ "Unset[Messages[1]]",
+ "$Failed",
+ [
+ "First argument in Messages[1] is not a symbol or a string naming a symbol."
+ ],
+ "Unset Message",
+ ),
+ # From assignent
+ (
+ "f[g, a + b, h] ^= 2",
+ "2",
+ ("Tag Plus in f[g, a + b, h] is Protected.",),
+ "Upset to protected symbols fails",
+ ),
+ ("UpValues[h]", "{HoldPattern[f[g, a + b, h]] :> 2}", None, None),
+ (" g[a+b] ^:= 2", "$Failed", ("Tag Plus in g[a + b] is Protected.",), None),
+ (" g[a+b]", "g[a + b]", None, None),
+ ],
+)
+def test_private_doctests(str_expr, str_expected, msgs, failure_msg):
+ check_evaluation(
+ str_expr, str_expected, expected_messages=msgs, failure_message=failure_msg
+ )
diff --git a/test/builtin/test_attributes.py b/test/builtin/test_attributes.py
index 65ee4020c..d145c246c 100644
--- a/test/builtin/test_attributes.py
+++ b/test/builtin/test_attributes.py
@@ -4,7 +4,7 @@
"""
import os
-from test.helper import check_evaluation
+from test.helper import check_evaluation, check_evaluation_as_in_cli, session
import pytest
@@ -226,3 +226,60 @@ def test_Attributes_wrong_args(str_expr, arg_count):
f"SetAttributes called with {arg_count} arguments; 2 arguments are expected.",
),
)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("CleanAll[u];CleanAll[v];", None, None, None),
+ ("SetAttributes[{u, v}, Flat];u[x_] := {x};u[]", None, "u[]", None),
+ ("u[a]", None, "{a}", None),
+ ("v[x_] := x;v[]", None, "v[]", None),
+ ("v[a]", None, "a", None),
+ (
+ "v[a, b]",
+ None,
+ "v[a, b]",
+ "in Mathematica: Iteration limit of 4096 exceeded.",
+ ),
+ ("CleanAll[u];CleanAll[v];", None, None, None),
+ ],
+)
+def test_private_doctests_attributes(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("CleanAll[u];CleanAll[v];", None, None, None),
+ (
+ "SetAttributes[{u, v}, Flat];u[x_] := {x};u[a, b]",
+ ("Iteration limit of 1000 exceeded.",),
+ "$Aborted",
+ None,
+ ),
+ ("u[a, b, c]", ("Iteration limit of 1000 exceeded.",), "$Aborted", None),
+ (
+ "v[x_] := x;v[a,b,c]",
+ ("Iteration limit of 1000 exceeded.",),
+ "$Aborted",
+ "in Mathematica: Iteration limit of 4096 exceeded.",
+ ),
+ ("CleanAll[u];CleanAll[v];", None, None, None),
+ ],
+)
+def test_private_doctests_attributes_with_exceptions(
+ str_expr, msgs, str_expected, fail_msg
+):
+ """These tests check the behavior of $RecursionLimit and $IterationLimit"""
+ check_evaluation_as_in_cli(str_expr, str_expected, fail_msg, msgs)
diff --git a/test/builtin/test_binary.py b/test/builtin/test_binary.py
new file mode 100644
index 000000000..0fdc113f6
--- /dev/null
+++ b/test/builtin/test_binary.py
@@ -0,0 +1,405 @@
+# -*- coding: utf-8 -*-
+
+import sys
+from test.helper import check_evaluation, session
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected", "fail_msg"),
+ [
+ ## Write as Bytes then Read
+ (
+ "WbR[bytes_, form_] := Module[{stream, res}, stream = OpenWrite[BinaryFormat -> True]; BinaryWrite[stream, bytes]; stream = OpenRead[Close[stream], BinaryFormat -> True]; res = BinaryRead[stream, form]; DeleteFile[Close[stream]]; res];",
+ "Null",
+ None,
+ ),
+ ## Byte
+ (
+ 'WbR[{149, 2, 177, 132}, {"Byte", "Byte", "Byte", "Byte"}]',
+ "{149, 2, 177, 132}",
+ None,
+ ),
+ (
+ '(# == WbR[#, Table["Byte", {50}]]) & [RandomInteger[{0, 255}, 50]]',
+ "True",
+ None,
+ ),
+ ## Character8
+ (
+ 'WbR[{97, 98, 99}, {"Character8", "Character8", "Character8"}]',
+ "{a, b, c}",
+ None,
+ ),
+ (
+ 'WbR[{34, 60, 39}, {"Character8", "Character8", "Character8"}]',
+ "{\", <, '}",
+ None,
+ ),
+ ## Character16
+ (
+ 'WbR[{97, 0, 98, 0, 99, 0}, {"Character16", "Character16", "Character16"}]',
+ "{a, b, c}",
+ None,
+ ),
+ (
+ 'ToCharacterCode[WbR[{50, 154, 182, 236}, {"Character16", "Character16"}]]',
+ "{{39474}, {60598}}",
+ None,
+ ),
+ ## #> WbR[ {91, 146, 206, 54}, {"Character16", "Character16"}]
+ ## = {\\:925b, \\:36ce}
+ ## Complex64
+ (
+ 'z=WbR[{80, 201, 77, 239, 201, 177, 76, 79}, "Complex64"];z // InputForm',
+ "-6.368779889243691*^28 + 3.434203392*^9*I",
+ None,
+ ),
+ ("z // Precision", "MachinePrecision", None),
+ (
+ 'z=.;WbR[{158, 2, 185, 232, 18, 237, 0, 102}, "Complex64"] // InputForm',
+ "-6.989488623351118*^24 + 1.522090212973691*^23*I",
+ None,
+ ),
+ (
+ 'WbR[{195, 142, 38, 160, 238, 252, 85, 188}, "Complex64"] // InputForm',
+ "-1.4107982814807285*^-19 - 0.013060791417956352*I",
+ None,
+ ),
+ ## Complex128
+ (
+ 'WbR[{15,114,1,163,234,98,40,15,214,127,116,15,48,57,208,180},"Complex128"] // InputForm',
+ "1.1983977035653814*^-235 - 2.6465639149433955*^-54*I",
+ None,
+ ),
+ (
+ 'z=WbR[{148,119,12,126,47,94,220,91,42,69,29,68,147,11,62,233},"Complex128"]; z // InputForm',
+ "3.2217026714156333*^134 - 8.98364297498066*^198*I",
+ None,
+ ),
+ ("z // Precision", "MachinePrecision", None),
+ (
+ 'WbR[{15,42,80,125,157,4,38,97, 0,0,0,0,0,0,240,255}, "Complex128"]',
+ "-I Infinity",
+ None,
+ ),
+ (
+ 'WbR[{15,42,80,125,157,4,38,97, 0,0,0,0,0,0,240,127}, "Complex128"]',
+ "I Infinity",
+ None,
+ ),
+ (
+ 'WbR[{15,42,80,125,157,4,38,97, 1,0,0,0,0,0,240,255}, "Complex128"]',
+ "Indeterminate",
+ None,
+ ),
+ (
+ 'WbR[{0,0,0,0,0,0,240,127, 15,42,80,125,157,4,38,97}, "Complex128"]',
+ "Infinity",
+ None,
+ ),
+ (
+ 'WbR[{0,0,0,0,0,0,240,255, 15,42,80,125,157,4,38,97}, "Complex128"]',
+ "-Infinity",
+ None,
+ ),
+ (
+ 'WbR[{1,0,0,0,0,0,240,255, 15,42,80,125,157,4,38,97}, "Complex128"]',
+ "Indeterminate",
+ None,
+ ),
+ (
+ 'WbR[{0,0,0,0,0,0,240,127, 0,0,0,0,0,0,240,127}, "Complex128"]',
+ "Indeterminate",
+ None,
+ ),
+ (
+ 'WbR[{0,0,0,0,0,0,240,127, 0,0,0,0,0,0,240,255}, "Complex128"]',
+ "Indeterminate",
+ None,
+ ),
+ ## Complex256
+ ## TODO
+ ## Integer8
+ (
+ 'WbR[{149, 2, 177, 132}, {"Integer8", "Integer8", "Integer8", "Integer8"}]',
+ "{-107, 2, -79, -124}",
+ None,
+ ),
+ (
+ 'WbR[{127, 128, 0, 255}, {"Integer8", "Integer8", "Integer8", "Integer8"}]',
+ "{127, -128, 0, -1}",
+ None,
+ ),
+ ## Integer16
+ (
+ 'WbR[{149, 2, 177, 132, 112, 24}, {"Integer16", "Integer16", "Integer16"}]',
+ "{661, -31567, 6256}",
+ None,
+ ),
+ (
+ 'WbR[{0, 0, 255, 0, 255, 255, 128, 127, 128, 128}, Table["Integer16", {5}]]',
+ "{0, 255, -1, 32640, -32640}",
+ None,
+ ),
+ ## Integer24
+ (
+ 'WbR[{152, 173, 160, 188, 207, 154}, {"Integer24", "Integer24"}]',
+ "{-6247016, -6631492}",
+ None,
+ ),
+ (
+ 'WbR[{145, 173, 231, 49, 90, 30}, {"Integer24", "Integer24"}]',
+ "{-1593967, 1989169}",
+ None,
+ ),
+ ## Integer32
+ (
+ 'WbR[{209, 99, 23, 218, 143, 187, 236, 241}, {"Integer32", "Integer32"}]',
+ "{-636001327, -236143729}",
+ None,
+ ),
+ (
+ 'WbR[{15, 31, 173, 120, 245, 100, 18, 188}, {"Integer32", "Integer32"}]',
+ "{2024611599, -1139645195}",
+ None,
+ ),
+ ## Integer64
+ (
+ 'WbR[{211, 18, 152, 2, 235, 102, 82, 16}, "Integer64"]',
+ "1176115612243989203",
+ None,
+ ),
+ (
+ 'WbR[{37, 217, 208, 88, 14, 241, 170, 137}, "Integer64"]',
+ "-8526737900550694619",
+ None,
+ ),
+ ## Integer128
+ (
+ 'WbR[{140,32,24,199,10,169,248,117,123,184,75,76,34,206,49,105}, "Integer128"]',
+ "139827542997232652313568968616424513676",
+ None,
+ ),
+ (
+ 'WbR[{101,57,184,108,43,214,186,120,153,51,132,225,56,165,209,77}, "Integer128"]',
+ "103439096823027953602112616165136677221",
+ None,
+ ),
+ (
+ 'WbR[{113,100,125,144,211,83,140,24,206,11,198,118,222,152,23,219}, "Integer128"]',
+ "-49058912464625098822365387707690163087",
+ None,
+ ),
+ ## Real32
+ (
+ 'WbR[{81, 72, 250, 79, 52, 227, 104, 90}, {"Real32", "Real32"}] // InputForm',
+ "{8.398086656*^9, 1.6388001768669184*^16}",
+ None,
+ ),
+ (
+ 'WbR[{251, 22, 221, 117, 165, 245, 18, 75}, {"Real32", "Real32"}] // InputForm',
+ "{5.605291528399748*^32, 9.631141*^6}",
+ None,
+ ),
+ (
+ 'z=WbR[{126, 82, 143, 43}, "Real32"]; z // InputForm',
+ "1.0183657302847982*^-12",
+ None,
+ ),
+ ("z // Precision", "MachinePrecision", None),
+ ('WbR[{0, 0, 128, 127}, "Real32"]', "Infinity", None),
+ ('WbR[{0, 0, 128, 255}, "Real32"]', "-Infinity", None),
+ ('WbR[{1, 0, 128, 255}, "Real32"]', "Indeterminate", None),
+ ('WbR[{1, 0, 128, 127}, "Real32"]', "Indeterminate", None),
+ ## Real64
+ (
+ 'WbR[{45, 243, 20, 87, 129, 185, 53, 239}, "Real64"] // InputForm',
+ "-5.146466194262116*^227",
+ None,
+ ),
+ (
+ 'WbR[{192, 60, 162, 67, 122, 71, 74, 196}, "Real64"] // InputForm',
+ "-9.695316988087658*^20",
+ None,
+ ),
+ (
+ 'z=WbR[{15, 42, 80, 125, 157, 4, 38, 97}, "Real64"]; z// InputForm',
+ "9.67355569763742*^159",
+ None,
+ ),
+ ("z // Precision", "MachinePrecision", None),
+ ('WbR[{0, 0, 0, 0, 0, 0, 240, 127}, "Real64"]', "Infinity", None),
+ ('WbR[{0, 0, 0, 0, 0, 0, 240, 255}, "Real64"]', "-Infinity", None),
+ ('WbR[{1, 0, 0, 0, 0, 0, 240, 127}, "Real64"]', "Indeterminate", None),
+ ('WbR[{1, 0, 0, 0, 0, 0, 240, 255}, "Real64"]', "Indeterminate", None),
+ ## Real128
+ ## 0x0000
+ ('WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0}, "Real128"]', "0.×10^-4965", None),
+ ('WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,128}, "Real128"]', "0.×10^-4965", None),
+ ## 0x0001 - 0x7FFE
+ (
+ 'WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,63}, "Real128"]',
+ "1.00000000000000000000000000000000",
+ None,
+ ),
+ (
+ 'WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,191}, "Real128"]',
+ "-1.00000000000000000000000000000000",
+ None,
+ ),
+ (
+ 'WbR[{135, 62, 233, 137, 22, 208, 233, 210, 133, 82, 251, 92, 220, 216, 255, 63}, "Real128"]',
+ "1.84711247573661489653389674493896",
+ None,
+ ),
+ (
+ 'WbR[{135, 62, 233, 137, 22, 208, 233, 210, 133, 82, 251, 92, 220, 216, 207, 72}, "Real128"]',
+ "2.45563355727491021879689747166252×10^679",
+ None,
+ ),
+ (
+ 'z=WbR[{74, 95, 30, 234, 116, 130, 1, 84, 20, 133, 245, 221, 113, 110, 219, 212}, "Real128"]',
+ "-4.52840681592341879518366539335138×10^1607",
+ None,
+ ),
+ ("z // Precision", "33.", None),
+ ## 0x7FFF
+ (
+ 'z=.;WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,127}, "Real128"]',
+ "Infinity",
+ None,
+ ),
+ ('WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,255}, "Real128"]', "-Infinity", None),
+ (
+ 'WbR[{1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,127}, "Real128"]',
+ "Indeterminate",
+ None,
+ ),
+ (
+ 'WbR[{1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,255}, "Real128"]',
+ "Indeterminate",
+ None,
+ ),
+ ## TerminatedString
+ ('WbR[{97, 98, 99, 0}, "TerminatedString"]', "abc", None),
+ (
+ 'WbR[{49, 50, 51, 0, 52, 53, 54, 0, 55, 56, 57}, Table["TerminatedString", {3}]]',
+ "{123, 456, EndOfFile}",
+ None,
+ ),
+ ('WbR[{0}, "TerminatedString"] // InputForm', '""', None),
+ ## UnsignedInteger8
+ (
+ 'WbR[{96, 94, 141, 162, 141}, Table["UnsignedInteger8", {5}]]',
+ "{96, 94, 141, 162, 141}",
+ None,
+ ),
+ (
+ '(#==WbR[#,Table["UnsignedInteger8",{50}]])&[RandomInteger[{0, 255}, 50]]',
+ "True",
+ None,
+ ),
+ ## UnsignedInteger16
+ (
+ 'WbR[{54, 71, 106, 185, 147, 38, 5, 231}, Table["UnsignedInteger16", {4}]]',
+ "{18230, 47466, 9875, 59141}",
+ None,
+ ),
+ (
+ 'WbR[{0, 0, 128, 128, 255, 255}, Table["UnsignedInteger16", {3}]]',
+ "{0, 32896, 65535}",
+ None,
+ ),
+ ## UnsignedInteger24
+ (
+ 'WbR[{78, 35, 226, 225, 84, 236}, Table["UnsignedInteger24", {2}]]',
+ "{14820174, 15488225}",
+ None,
+ ),
+ (
+ 'WbR[{165, 2, 82, 239, 88, 59}, Table["UnsignedInteger24", {2}]]',
+ "{5374629, 3889391}",
+ None,
+ ),
+ ## UnsignedInteger32
+ (
+ 'WbR[{213,143,98,112,141,183,203,247}, Table["UnsignedInteger32", {2}]]',
+ "{1885507541, 4157323149}",
+ None,
+ ),
+ (
+ 'WbR[{148,135,230,22,136,141,234,99}, Table["UnsignedInteger32", {2}]]',
+ "{384206740, 1676316040}",
+ None,
+ ),
+ ## UnsignedInteger64
+ (
+ 'WbR[{95, 5, 33, 229, 29, 62, 63, 98}, "UnsignedInteger64"]',
+ "7079445437368829279",
+ None,
+ ),
+ (
+ 'WbR[{134, 9, 161, 91, 93, 195, 173, 74}, "UnsignedInteger64"]',
+ "5381171935514265990",
+ None,
+ ),
+ ## UnsignedInteger128
+ (
+ 'WbR[{108,78,217,150,88,126,152,101,231,134,176,140,118,81,183,220}, "UnsignedInteger128"]',
+ "293382001665435747348222619884289871468",
+ None,
+ ),
+ (
+ 'WbR[{53,83,116,79,81,100,60,126,202,52,241,48,5,113,92,190}, "UnsignedInteger128"]',
+ "253033302833692126095975097811212718901",
+ None,
+ ),
+ ## EndOfFile
+ (
+ 'WbR[{148}, {"Integer32", "Integer32","Integer32"}]',
+ "{EndOfFile, EndOfFile, EndOfFile}",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_io(str_expr, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected", "fail_msg"),
+ [
+ ("ByteOrdering", "1" if sys.byteorder == "big" else "-1", None),
+ ("ByteOrdering == -1 || ByteOrdering == 1", "True", None),
+ (
+ "$ByteOrdering == ByteOrdering",
+ "True",
+ "By default, ByteOrdering must be equal to the System $ByteOrdering",
+ ),
+ (
+ "$ByteOrdering == -1 || $ByteOrdering == 1",
+ "True",
+ "Possible bit ordering are 1 and -1",
+ ),
+ ],
+)
+def test_private_doctests_system(str_expr, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ )
diff --git a/test/builtin/test_comparison.py b/test/builtin/test_comparison.py
index 432dd62b2..a2fc847c6 100644
--- a/test/builtin/test_comparison.py
+++ b/test/builtin/test_comparison.py
@@ -78,6 +78,14 @@
("g[a]3', "Wo[x] > 3", "isue #797"),
+ ('Wo["x"]<3', "Wo[x] < 3", "isue #797"),
+ ('Wo["x"]==3', "Wo[x] == 3", "isue #797"),
+ ('3>Wo["x"]', "3 > Wo[x]", "isue #797"),
+ ('30', "Wo[f[x], 2] > 0", "isue #797"),
+ #
# chained compare
("a != a != b", "False", "Strange MMA behavior"),
("a != b != a", "a != b != a", "incomparable values should be unchanged"),
@@ -647,3 +655,24 @@ def test_cmp_compare_numbers(str_expr, str_expected, message):
to_string_expr=True,
to_string_expected=True,
)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected", "message"),
+ [
+ (
+ "{a, b} = {2^10000, 2^10000 + 1}; {a == b, a < b, a <= b}",
+ "{False, True, True}",
+ "Test large Integer comparison bug",
+ ),
+ # (None, None, None),
+ ],
+)
+def test_misc_private_tests(str_expr, str_expected, message):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ failure_message=message,
+ to_string_expr=True,
+ to_string_expected=True,
+ )
diff --git a/test/builtin/test_compilation.py b/test/builtin/test_compilation.py
new file mode 100644
index 000000000..29dfa4825
--- /dev/null
+++ b/test/builtin/test_compilation.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.compilation.
+"""
+
+import sys
+import time
+from test.helper import check_evaluation, evaluate
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ "cf = Compile[{{x, _Real}}, Sin[x]]",
+ None,
+ "CompiledFunction[{x}, Sin[x], -CompiledCode-]",
+ None,
+ ),
+ ("cf[1/2]", None, "0.479426", None),
+ ("cf[4]", None, "-0.756802", None),
+ (
+ "cf[x]",
+ ("Invalid argument x should be Integer, Real or boolean.",),
+ "CompiledFunction[{x}, Sin[x], -CompiledCode-][x]",
+ None,
+ ),
+ (
+ "cf = Compile[{{x, _Real}, {x, _Integer}}, Sin[x + y]]",
+ ("Duplicate parameter x found in {{x, _Real}, {x, _Integer}}.",),
+ "Compile[{{x, _Real}, {x, _Integer}}, Sin[x + y]]",
+ None,
+ ),
+ (
+ "cf = Compile[{{x, _Real}, {y, _Integer}}, Sin[x + z]]",
+ None,
+ "CompiledFunction[{x, y}, Sin[x + z], -PythonizedCode-]",
+ None,
+ ),
+ (
+ "cf = Compile[{{x, _Real}, {y, _Integer}}, Sin[x + y]]",
+ None,
+ "CompiledFunction[{x, y}, Sin[x + y], -CompiledCode-]",
+ None,
+ ),
+ ("cf[1, 2]", None, "0.14112", None),
+ (
+ "cf[x + y]",
+ None,
+ "CompiledFunction[{x, y}, Sin[x + y], -CompiledCode-][x + y]",
+ None,
+ ),
+ (
+ "cf = Compile[{{x, _Real}, {y, _Integer}}, If[x == 0.0 && y <= 0, 0.0, Sin[x ^ y] + 1 / Min[x, 0.5]] + 0.5];cf[0, -2]",
+ None,
+ "0.5",
+ None,
+ ),
+ ("ClearAll[cf];", None, None, None),
+ ],
+)
+def test_private_doctests_compilation(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_compile.py b/test/builtin/test_compile.py
index f3909167f..f512533f8 100644
--- a/test/builtin/test_compile.py
+++ b/test/builtin/test_compile.py
@@ -50,7 +50,6 @@ def test_compile_code():
("BesselJ[0,x]", 0.0, 1.0),
("Exp[BesselJ[0,x]-1.]", 0.0, 1.0),
]:
-
expr = session.evaluate("Compile[{x}, " + str_expr + " ]")
assert expr.get_head_name() == "System`CompiledFunction"
assert len(expr.elements) == 3
diff --git a/test/builtin/test_datentime.py b/test/builtin/test_datentime.py
index ac9053f74..0fc894483 100644
--- a/test/builtin/test_datentime.py
+++ b/test/builtin/test_datentime.py
@@ -1,4 +1,8 @@
# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.datetime.
+"""
+
import sys
import time
from test.helper import check_evaluation, evaluate
@@ -69,3 +73,52 @@ def test_datestring():
('DateString["2000-12-1", "Year"]', "2000"),
):
check_evaluation(str_expr, str_expected, hold_expected=True)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("AbsoluteTime[1000]", None, "1000", "Mathematica Bug - Mathics gets it right"),
+ (
+ 'DateList["7/8/9"]',
+ ("The interpretation of 7/8/9 is ambiguous.",),
+ "{2009, 7, 8, 0, 0, 0.}",
+ None,
+ ),
+ (
+ 'DateString[{1979, 3, 14}, {"DayName", " ", "MonthShort", "-", "YearShort"}]',
+ None,
+ "Wednesday 3-79",
+ "Check Leading 0",
+ ),
+ (
+ 'DateString[{"DayName", " ", "Month", "/", "YearShort"}]==DateString[Now[[1]], {"DayName", " ", "Month", "/", "YearShort"}]',
+ None,
+ "True",
+ None,
+ ),
+ (
+ 'DateString[{"06/06/1991", {"Month", "Day", "Year"}}]',
+ None,
+ "Thu 6 Jun 1991 00:00:00",
+ "Assumed separators",
+ ),
+ (
+ 'DateString[{"06/06/1991", {"Month", "/", "Day", "/", "Year"}}]',
+ None,
+ "Thu 6 Jun 1991 00:00:00",
+ "Specified separators",
+ ),
+ ],
+)
+def test_private_doctests_datetime(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_directories.py b/test/builtin/test_directories.py
new file mode 100644
index 000000000..ec841466e
--- /dev/null
+++ b/test/builtin/test_directories.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtin.directories
+"""
+
+from test.helper import check_evaluation
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ('DirectoryName["a/b/c", 3] // InputForm', None, '""', None),
+ ('DirectoryName[""] // InputForm', None, '""', None),
+ (
+ 'DirectoryName["a/b/c", x]',
+ (
+ "Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, x].",
+ ),
+ "DirectoryName[a/b/c, x]",
+ None,
+ ),
+ (
+ 'DirectoryName["a/b/c", -1]',
+ (
+ "Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, -1].",
+ ),
+ "DirectoryName[a/b/c, -1]",
+ None,
+ ),
+ (
+ "DirectoryName[x]",
+ ("String expected at position 1 in DirectoryName[x].",),
+ "DirectoryName[x]",
+ None,
+ ),
+ ('DirectoryQ["ExampleData"]', None, "True", None),
+ ('DirectoryQ["ExampleData/MythicalSubdir/NestedDir/"]', None, "False", None),
+ ("FileNameDepth[x]", None, "FileNameDepth[x]", None),
+ ("FileNameDepth[$RootDirectory]", None, "0", None),
+ (
+ 'FileNameSplit["example/path", OperatingSystem -> x]',
+ (
+ 'The value of option OperatingSystem -> x must be one of "MacOSX", "Windows", or "Unix".',
+ ),
+ "{example, path}",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_directory_names(str_expr, msgs, str_expected, fail_msg):
+ """private doctests in builtin.directories"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_evaluation.py b/test/builtin/test_evaluation.py
new file mode 100644
index 000000000..4458d8df8
--- /dev/null
+++ b/test/builtin/test_evaluation.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.evaluation.
+"""
+
+
+import sys
+from test.helper import check_evaluation_as_in_cli, session
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ None,
+ None,
+ None,
+ None,
+ ),
+ ("$RecursionLimit = 20", None, "20", None),
+ ("a = a + a", ("Recursion depth of 20 exceeded.",), "$Aborted", None),
+ ("$RecursionLimit = 200", None, "200", None),
+ (
+ "ClearAll[f];f[x_, 0] := x; f[x_, n_] := f[x + 1, n - 1];Block[{$RecursionLimit = 20}, f[0, 100]]",
+ None,
+ "100",
+ None,
+ ),
+ (
+ "ClearAll[f];f[x_, 0] := x; f[x_, n_] := Module[{y = x + 1}, f[y, n - 1]];Block[{$RecursionLimit = 20}, f[0, 100]]",
+ ("Recursion depth of 20 exceeded.",),
+ "$Aborted",
+ None,
+ ),
+ (
+ "ClearAll[f]; f[x_] := f[x + 1];f[x]",
+ ("Iteration limit of 1000 exceeded.",),
+ "$Aborted",
+ None,
+ ),
+ (
+ "$IterationLimit = x;",
+ (
+ "Cannot set $IterationLimit to x; value must be an integer between 20 and Infinity.",
+ ),
+ None,
+ None,
+ ),
+ (
+ "ClearAll[f];f[x_, 0] := x; f[x_, n_] := f[x + 1, n - 1];Block[{$IterationLimit = 20}, f[0, 100]]",
+ ("Iteration limit of 20 exceeded.",),
+ "$Aborted",
+ None,
+ ),
+ ("ClearAll[f];", None, None, None),
+ (
+ "Attributes[h] = Flat;h[items___] := Plus[items];h[1, Unevaluated[Sequence[Unevaluated[2], 3]], Sequence[4, Unevaluated[5]]]",
+ None,
+ "15",
+ None,
+ ),
+ ("ClearAll[f];", None, None, None),
+ ],
+)
+def test_private_doctests_evaluation(str_expr, msgs, str_expected, fail_msg):
+ """These tests check the behavior of $RecursionLimit and $IterationLimit"""
+ check_evaluation_as_in_cli(str_expr, str_expected, fail_msg, msgs)
+
+
+@pytest.mark.skipif(
+ sys.platform.startswith("win"),
+ reason="Weird Block recursion test does not work on MS Windows",
+)
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ # FIX Later
+ (
+ "ClearAll[f];f[x_, 0] := x; f[x_, n_] := Module[{y = x + 1}, f[y, n - 1]];Block[{$IterationLimit = 20}, f[0, 100]]",
+ None,
+ "100",
+ "Fix me!",
+ ),
+ ],
+)
+def test_private_doctests_evaluation_non_mswindows(
+ str_expr, msgs, str_expected, fail_msg
+):
+ """These tests check the behavior of $RecursionLimit and $IterationLimit
+ that do not work on MS Windows.
+ """
+ check_evaluation_as_in_cli(str_expr, str_expected, fail_msg, msgs)
diff --git a/test/builtin/test_exp_structure.py b/test/builtin/test_exp_structure.py
new file mode 100644
index 000000000..d80594d56
--- /dev/null
+++ b/test/builtin/test_exp_structure.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtin.exp_structure
+"""
+
+import sys
+import time
+from test.helper import check_evaluation, evaluate
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("ClearAll[f,a,b,x,y];", None, "Null", None),
+ ("LeafCount[f[a, b][x, y]]", None, "5", None),
+ (
+ "data=NestList[# /. s[x_][y_][z_] -> x[z][y[z]] &, s[s][s][s[s]][s][s], 4];",
+ None,
+ "Null",
+ None,
+ ),
+ ("LeafCount /@ data", None, "{7, 8, 8, 11, 11}", None),
+ ("Clear[data];", None, "Null", None),
+ (
+ "LeafCount[1 / 3, 1 + I]",
+ ("LeafCount called with 2 arguments; 1 argument is expected.",),
+ "LeafCount[1 / 3, 1 + I]",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_exp_size_and_sig(str_expr, msgs, str_expected, fail_msg):
+ """exp_structure.size_and_sig"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ "Operate[p, f, -1]",
+ ("Non-negative integer expected at position 3 in Operate[p, f, -1].",),
+ "Operate[p, f, -1]",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_general(str_expr, msgs, str_expected, fail_msg):
+ """exp_structure.general"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_file_operations.py b/test/builtin/test_file_operations.py
new file mode 100644
index 000000000..3a9db5146
--- /dev/null
+++ b/test/builtin/test_file_operations.py
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtin.file_operations
+"""
+
+import sys
+import time
+from test.helper import check_evaluation, evaluate
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ 'FileDate["MathicsNonExistantExample"]',
+ ("File not found during FileDate[MathicsNonExistantExample].",),
+ "FileDate[MathicsNonExistantExample]",
+ None,
+ ),
+ (
+ 'FileDate["MathicsNonExistantExample", "Modification"]',
+ (
+ "File not found during FileDate[MathicsNonExistantExample, Modification].",
+ ),
+ "FileDate[MathicsNonExistantExample, Modification]",
+ None,
+ ),
+ (
+ 'FileDate["ExampleData/sunflowers.jpg", "Fail"]',
+ (
+ 'Date type Fail should be "Access", "Modification", "Creation" (Windows only), "Change" (Macintosh and Unix only), or "Rules".',
+ ),
+ "FileDate[ExampleData/sunflowers.jpg, Fail]",
+ None,
+ ),
+ ('FileHash["ExampleData/sunflowers.jpg", "CRC32"]', None, "933095683", None),
+ (
+ 'FileHash["ExampleData/sunflowers.jpg", "SHA"]',
+ None,
+ "851696818771101405642332645949480848295550938123",
+ None,
+ ),
+ (
+ 'FileHash["ExampleData/sunflowers.jpg", "SHA224"]',
+ None,
+ "8723805623766373862936267623913366865806344065103917676078120867011",
+ None,
+ ),
+ (
+ 'FileHash["ExampleData/sunflowers.jpg", "SHA384"]',
+ None,
+ "28288410602533803613059815846847184383722061845493818218404754864571944356226472174056863474016709057507799332611860",
+ None,
+ ),
+ (
+ 'FileHash["ExampleData/sunflowers.jpg", "SHA512"]',
+ None,
+ "10111462070211820348006107532340854103555369343736736045463376555356986226454343186097958657445421102793096729074874292511750542388324853755795387877480102",
+ None,
+ ),
+ (
+ 'FileHash["ExampleData/sunflowers.jpg", xyzsymbol]',
+ None,
+ "FileHash[ExampleData/sunflowers.jpg, xyzsymbol]",
+ None,
+ ),
+ (
+ 'FileHash["ExampleData/sunflowers.jpg", "xyzstr"]',
+ None,
+ "FileHash[ExampleData/sunflowers.jpg, xyzstr, Integer]",
+ None,
+ ),
+ ("FileHash[xyzsymbol]", None, "FileHash[xyzsymbol]", None),
+ (
+ "FileType[x]",
+ ("File specification x is not a string of one or more characters.",),
+ "FileType[x]",
+ None,
+ ),
+ (
+ 'tmpfilename = $TemporaryDirectory <> "/tmp0";Close[OpenWrite[tmpfilename]];',
+ None,
+ "Null",
+ None,
+ ),
+ (
+ 'SetFileDate[tmpfilename, {2002, 1, 1, 0, 0, 0.}];FileDate[tmpfilename, "Access"]',
+ None,
+ "{2002, 1, 1, 0, 0, 0.}",
+ None,
+ ),
+ ("SetFileDate[tmpfilename]", None, "Null", None),
+ ('FileDate[tmpfilename, "Access"]//Length', None, "6", None),
+ (
+ 'DeleteFile[tmpfilename];SetFileDate["MathicsNonExample"]',
+ ("File not found during SetFileDate[MathicsNonExample].",),
+ "$Failed",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_file_properties(str_expr, msgs, str_expected, fail_msg):
+ """file_opertions.file_properties"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ('FindList["ExampleData/EinsteinSzilLetter.txt", "project"]', None, "{}", None),
+ (
+ 'FindList["ExampleData/EinsteinSzilLetter.txt", "uranium", 0]',
+ None,
+ "$Failed",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_file_utilities(str_expr, msgs, str_expected, fail_msg):
+ """file_opertions.file_utilities"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_forms.py b/test/builtin/test_forms.py
new file mode 100644
index 000000000..894a9de18
--- /dev/null
+++ b/test/builtin/test_forms.py
@@ -0,0 +1,388 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.forms.
+"""
+
+from test.helper import check_evaluation, session
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("BaseForm[0, 2]", None, "0_2", None),
+ ("BaseForm[0.0, 2]", None, "0.0_2", None),
+ ("BaseForm[N[Pi, 30], 16]", None, "3.243f6a8885a308d313198a2e_16", None),
+ ("InputForm[2 x ^ 2 + 4z!]", None, "2*x^2 + 4*z!", None),
+ (r'InputForm["\$"]', None, r'"\\$"', None),
+ ## Undocumented edge cases
+ ("NumberForm[Pi, 20]", None, "Pi", None),
+ ("NumberForm[2/3, 10]", None, "2 / 3", None),
+ ## No n or f
+ ("NumberForm[N[Pi]]", None, "3.14159", None),
+ ("NumberForm[N[Pi, 20]]", None, "3.1415926535897932385", None),
+ ("NumberForm[14310983091809]", None, "14310983091809", None),
+ ## Zero case
+ ("z0 = 0.0;z1 = 0.0000000000000000000000000000;", None, "Null", None),
+ ("NumberForm[{z0, z1}, 10]", None, "{0., 0.×10^-28}", None),
+ ("NumberForm[{z0, z1}, {10, 4}]", None, "{0.0000, 0.0000×10^-28}", None),
+ ("z0=.;z1=.;", None, "Null", None),
+ ## Trailing zeros
+ ("NumberForm[1.0, 10]", None, "1.", None),
+ ("NumberForm[1.000000000000000000000000, 10]", None, "1.000000000", None),
+ ("NumberForm[1.0, {10, 8}]", None, "1.00000000", None),
+ ("NumberForm[N[Pi, 33], 33]", None, "3.14159265358979323846264338327950", None),
+ ## Correct rounding
+ ("NumberForm[0.645658509, 6]", None, "0.645659", "sympy/issues/11472"),
+ ("NumberForm[N[1/7], 30]", None, "0.1428571428571428", "sympy/issues/11472"),
+ ## Integer case
+ (
+ "NumberForm[{0, 2, -415, 83515161451}, 5]",
+ None,
+ "{0, 2, -415, 83515161451}",
+ None,
+ ),
+ (
+ "NumberForm[{2^123, 2^123.}, 4, ExponentFunction -> ((#1) &)]",
+ None,
+ "{10633823966279326983230456482242756608, 1.063×10^37}",
+ None,
+ ),
+ ("NumberForm[{0, 10, -512}, {10, 3}]", None, "{0.000, 10.000, -512.000}", None),
+ ## Check arguments
+ (
+ "NumberForm[1.5, -4]",
+ (
+ "Formatting specification -4 should be a positive integer or a pair of positive integers.",
+ ),
+ "1.5",
+ None,
+ ),
+ (
+ "NumberForm[1.5, {1.5, 2}]",
+ (
+ "Formatting specification {1.5, 2} should be a positive integer or a pair of positive integers.",
+ ),
+ "1.5",
+ None,
+ ),
+ (
+ "NumberForm[1.5, {1, 2.5}]",
+ (
+ "Formatting specification {1, 2.5} should be a positive integer or a pair of positive integers.",
+ ),
+ "1.5",
+ None,
+ ),
+ ## Right padding
+ (
+ "NumberForm[153., 2]",
+ (
+ "In addition to the number of digits requested, one or more zeros will appear as placeholders.",
+ ),
+ "150.",
+ None,
+ ),
+ ("NumberForm[0.00125, 1]", None, "0.001", None),
+ (
+ "NumberForm[10^5 N[Pi], {5, 3}]",
+ (
+ "In addition to the number of digits requested, one or more zeros will appear as placeholders.",
+ ),
+ "314160.000",
+ None,
+ ),
+ ("NumberForm[10^5 N[Pi], {6, 3}]", None, "314159.000", None),
+ ("NumberForm[10^5 N[Pi], {6, 10}]", None, "314159.0000000000", None),
+ (
+ 'NumberForm[1.0000000000000000000, 10, NumberPadding -> {"X", "Y"}]',
+ None,
+ "X1.000000000",
+ None,
+ ),
+ ## Check options
+ ## DigitBlock
+ (
+ "NumberForm[12345.123456789, 14, DigitBlock -> 3]",
+ None,
+ "12,345.123 456 789",
+ None,
+ ),
+ (
+ "NumberForm[12345.12345678, 14, DigitBlock -> 3]",
+ None,
+ "12,345.123 456 78",
+ None,
+ ),
+ (
+ "NumberForm[N[10^ 5 Pi], 15, DigitBlock -> {4, 2}]",
+ None,
+ "31,4159.26 53 58 97 9",
+ None,
+ ),
+ (
+ "NumberForm[1.2345, 3, DigitBlock -> -4]",
+ (
+ "Value for option DigitBlock should be a positive integer, Infinity, or a pair of positive integers.",
+ ),
+ "1.2345",
+ None,
+ ),
+ (
+ "NumberForm[1.2345, 3, DigitBlock -> x]",
+ (
+ "Value for option DigitBlock should be a positive integer, Infinity, or a pair of positive integers.",
+ ),
+ "1.2345",
+ None,
+ ),
+ (
+ "NumberForm[1.2345, 3, DigitBlock -> {x, 3}]",
+ (
+ "Value for option DigitBlock should be a positive integer, Infinity, or a pair of positive integers.",
+ ),
+ "1.2345",
+ None,
+ ),
+ (
+ "NumberForm[1.2345, 3, DigitBlock -> {5, -3}]",
+ (
+ "Value for option DigitBlock should be a positive integer, Infinity, or a pair of positive integers.",
+ ),
+ "1.2345",
+ None,
+ ),
+ ## ExponentFunction
+ (
+ "NumberForm[12345.123456789, 14, ExponentFunction -> ((#) &)]",
+ None,
+ "1.2345123456789×10^4",
+ None,
+ ),
+ (
+ "NumberForm[12345.123456789, 14, ExponentFunction -> (Null&)]",
+ None,
+ "12345.123456789",
+ None,
+ ),
+ ("y = N[Pi^Range[-20, 40, 15]];", None, "Null", None),
+ (
+ "NumberForm[y, 10, ExponentFunction -> (3 Quotient[#, 3] &)]",
+ None,
+ "{114.0256472×10^-12, 3.267763643×10^-3, 93.64804748×10^3, 2.683779414×10^12, 76.91214221×10^18}",
+ None,
+ ),
+ (
+ "NumberForm[y, 10, ExponentFunction -> (Null &)]",
+ (
+ "In addition to the number of digits requested, one or more zeros will appear as placeholders.",
+ "In addition to the number of digits requested, one or more zeros will appear as placeholders.",
+ ),
+ "{0.0000000001140256472, 0.003267763643, 93648.04748, 2683779414000., 76912142210000000000.}",
+ None,
+ ),
+ ## ExponentStep
+ (
+ "NumberForm[10^8 N[Pi], 10, ExponentStep -> 3]",
+ None,
+ "314.1592654×10^6",
+ None,
+ ),
+ (
+ "NumberForm[1.2345, 3, ExponentStep -> x]",
+ ("Value of option ExponentStep -> x is not a positive integer.",),
+ "1.2345",
+ None,
+ ),
+ (
+ "NumberForm[1.2345, 3, ExponentStep -> 0]",
+ ("Value of option ExponentStep -> 0 is not a positive integer.",),
+ "1.2345",
+ None,
+ ),
+ (
+ "NumberForm[y, 10, ExponentStep -> 6]",
+ None,
+ "{114.0256472×10^-12, 3267.763643×10^-6, 93648.04748, 2.683779414×10^12, 76.91214221×10^18}",
+ None,
+ ),
+ ## NumberFormat
+ (
+ "NumberForm[y, 10, NumberFormat -> (#1 &)]",
+ None,
+ "{1.140256472, 0.003267763643, 93648.04748, 2.683779414, 7.691214221}",
+ None,
+ ),
+ ## NumberMultiplier
+ (
+ "NumberForm[1.2345, 3, NumberMultiplier -> 0]",
+ ("Value for option NumberMultiplier -> 0 is expected to be a string.",),
+ "1.2345",
+ None,
+ ),
+ (
+ 'NumberForm[N[10^ 7 Pi], 15, NumberMultiplier -> "*"]',
+ None,
+ "3.14159265358979*10^7",
+ None,
+ ),
+ ## NumberPoint
+ ('NumberForm[1.2345, 5, NumberPoint -> ","]', None, "1,2345", None),
+ (
+ "NumberForm[1.2345, 3, NumberPoint -> 0]",
+ ("Value for option NumberPoint -> 0 is expected to be a string.",),
+ "1.2345",
+ None,
+ ),
+ ## NumberPadding
+ ("NumberForm[1.41, {10, 5}]", None, "1.41000", None),
+ (
+ 'NumberForm[1.41, {10, 5}, NumberPadding -> {"", "X"}]',
+ None,
+ "1.41XXX",
+ None,
+ ),
+ (
+ 'NumberForm[1.41, {10, 5}, NumberPadding -> {"X", "Y"}]',
+ None,
+ "XXXXX1.41YYY",
+ None,
+ ),
+ (
+ 'NumberForm[1.41, 10, NumberPadding -> {"X", "Y"}]',
+ None,
+ "XXXXXXXX1.41",
+ None,
+ ),
+ (
+ "NumberForm[1.2345, 3, NumberPadding -> 0]",
+ (
+ "Value for option NumberPadding -> 0 should be a string or a pair of strings.",
+ ),
+ "1.2345",
+ None,
+ ),
+ (
+ 'NumberForm[1.41, 10, NumberPadding -> {"X", "Y"}, NumberSigns -> {"-------------", ""}]',
+ None,
+ "XXXXXXXXXXXXXXXXXXXX1.41",
+ None,
+ ),
+ (
+ 'NumberForm[{1., -1., 2.5, -2.5}, {4, 6}, NumberPadding->{"X", "Y"}]',
+ None,
+ "{X1.YYYYYY, -1.YYYYYY, X2.5YYYYY, -2.5YYYYY}",
+ None,
+ ),
+ ## NumberSeparator
+ (
+ 'NumberForm[N[10^ 5 Pi], 15, DigitBlock -> 3, NumberSeparator -> " "]',
+ None,
+ "314 159.265 358 979",
+ None,
+ ),
+ (
+ 'NumberForm[N[10^ 5 Pi], 15, DigitBlock -> 3, NumberSeparator -> {" ", ","}]',
+ None,
+ "314 159.265,358,979",
+ None,
+ ),
+ (
+ 'NumberForm[N[10^ 5 Pi], 15, DigitBlock -> 3, NumberSeparator -> {",", " "}]',
+ None,
+ "314,159.265 358 979",
+ None,
+ ),
+ (
+ 'NumberForm[N[10^ 7 Pi], 15, DigitBlock -> 3, NumberSeparator -> {",", " "}]',
+ None,
+ "3.141 592 653 589 79×10^7",
+ None,
+ ),
+ (
+ "NumberForm[1.2345, 3, NumberSeparator -> 0]",
+ (
+ "Value for option NumberSeparator -> 0 should be a string or a pair of strings.",
+ ),
+ "1.2345",
+ None,
+ ),
+ ## NumberSigns
+ ('NumberForm[1.2345, 5, NumberSigns -> {"-", "+"}]', None, "+1.2345", None),
+ ('NumberForm[-1.2345, 5, NumberSigns -> {"- ", ""}]', None, "- 1.2345", None),
+ (
+ "NumberForm[1.2345, 3, NumberSigns -> 0]",
+ (
+ "Value for option NumberSigns -> 0 should be a pair of strings or two pairs of strings.",
+ ),
+ "1.2345",
+ None,
+ ),
+ ## SignPadding
+ (
+ 'NumberForm[1.234, 6, SignPadding -> True, NumberPadding -> {"X", "Y"}]',
+ None,
+ "XXX1.234",
+ None,
+ ),
+ (
+ 'NumberForm[-1.234, 6, SignPadding -> True, NumberPadding -> {"X", "Y"}]',
+ None,
+ "-XX1.234",
+ None,
+ ),
+ (
+ 'NumberForm[-1.234, 6, SignPadding -> False, NumberPadding -> {"X", "Y"}]',
+ None,
+ "XX-1.234",
+ None,
+ ),
+ (
+ 'NumberForm[-1.234, {6, 4}, SignPadding -> False, NumberPadding -> {"X", "Y"}]',
+ None,
+ "X-1.234Y",
+ None,
+ ),
+ ("NumberForm[34, ExponentFunction->(Null&)]", None, "34", "1-arg, Option case"),
+ ## zero padding integer x0.0 case
+ ("NumberForm[50.0, {5, 1}]", None, "50.0", None),
+ ("NumberForm[50, {5, 1}]", None, "50.0", None),
+ ## Rounding correctly
+ ("NumberForm[43.157, {10, 1}]", None, "43.2", None),
+ (
+ 'NumberForm[43.15752525, {10, 5}, NumberSeparator -> ",", DigitBlock -> 1]',
+ None,
+ "4,3.1,5,7,5,3",
+ None,
+ ),
+ ("NumberForm[80.96, {16, 1}]", None, "81.0", None),
+ ("NumberForm[142.25, {10, 1}]", None, "142.3", None),
+ (
+ '{"hi","you"} //InputForm //TeXForm',
+ None,
+ "\\left\\{\\text{``hi''}, \\text{``you''}\\right\\}",
+ None,
+ ),
+ ("a=.;b=.;c=.;TeXForm[a+b*c]", None, "a+b c", None),
+ ("TeXForm[InputForm[a+b*c]]", None, r"a\text{ + }b*c", None),
+ ("TableForm[{}]", None, "", None),
+ (
+ "{{2*a, 0},{0,0}}//MatrixForm",
+ None,
+ "2 \u2062 a 0\n\n0 0\n",
+ "Issue #182",
+ ),
+ ],
+)
+def test_private_doctests_output(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_functional.py b/test/builtin/test_functional.py
new file mode 100644
index 000000000..ef697c540
--- /dev/null
+++ b/test/builtin/test_functional.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtin.functional
+"""
+
+import sys
+import time
+from test.helper import check_evaluation, check_evaluation_as_in_cli, evaluate, session
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("ClearAll[f, g, h,x,y,a,b,c];", None, None, None),
+ (
+ "Apply[f, {a, b, c}, x+y]",
+ ("Level specification x + y is not of the form n, {n}, or {m, n}.",),
+ "Apply[f, {a, b, c}, x + y]",
+ None,
+ ),
+ (
+ "Map[f, expr, a+b, Heads->True]",
+ ("Level specification a + b is not of the form n, {n}, or {m, n}.",),
+ "Map[f, expr, a + b, Heads -> True]",
+ None,
+ ),
+ (
+ "MapIndexed[f, {1, 2}, a+b]",
+ ("Level specification a + b is not of the form n, {n}, or {m, n}.",),
+ "MapIndexed[f, {1, 2}, a + b]",
+ None,
+ ),
+ (
+ "MapThread[f, {{a, b}, {c, d}}, {1}]",
+ (
+ "Non-negative machine-sized integer expected at position 3 in MapThread[f, {{a, b}, {c, d}}, {1}].",
+ ),
+ "MapThread[f, {{a, b}, {c, d}}, {1}]",
+ None,
+ ),
+ (
+ "MapThread[f, {{a, b}, {c, d}}, 2]",
+ (
+ "Object {a, b} at position {2, 1} in MapThread[f, {{a, b}, {c, d}}, 2] has only 1 of required 2 dimensions.",
+ ),
+ "MapThread[f, {{a, b}, {c, d}}, 2]",
+ None,
+ ),
+ (
+ "MapThread[f, {{a}, {b, c}}]",
+ (
+ "Incompatible dimensions of objects at positions {2, 1} and {2, 2} of MapThread[f, {{a}, {b, c}}]; dimensions are 1 and 2.",
+ ),
+ "MapThread[f, {{a}, {b, c}}]",
+ None,
+ ),
+ ("MapThread[f, {}]", None, "{}", None),
+ ("MapThread[f, {a, b}, 0]", None, "f[a, b]", None),
+ (
+ "MapThread[f, {a, b}, 1]",
+ (
+ "Object a at position {2, 1} in MapThread[f, {a, b}, 1] has only 0 of required 1 dimensions.",
+ ),
+ "MapThread[f, {a, b}, 1]",
+ None,
+ ),
+ (
+ "MapThread[f, {{{a, b}, {c}}, {{d, e}, {f}}}, 2]",
+ None,
+ "{{f[a, d], f[b, e]}, {f[c, f]}}",
+ "Behaviour extends MMA",
+ ),
+ (
+ "Scan[Print, f[g[h[x]]], 2]",
+ (
+ "h[x]",
+ "g[h[x]]",
+ ),
+ None,
+ None,
+ ),
+ (
+ "Scan[Print][{1, 2}]",
+ (
+ "1",
+ "2",
+ ),
+ None,
+ None,
+ ),
+ ("Scan[Return, {1, 2}]", None, "1", None),
+ ],
+)
+def test_private_doctests_apply_fns_to_lists(str_expr, msgs, str_expected, fail_msg):
+ """functional.apply_fns_to_lists"""
+ check_evaluation_as_in_cli(str_expr, str_expected, fail_msg, msgs)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("g[x_,y_] := x+y;g[Sequence@@Slot/@Range[2]]&[1,2]", None, "#1 + #2", None),
+ ("Evaluate[g[Sequence@@Slot/@Range[2]]]&[1,2]", None, "3", None),
+ ("# // InputForm", None, "#1", None),
+ ("#0 // InputForm", None, "#0", None),
+ ("## // InputForm", None, "##1", None),
+ ("Clear[g];", None, "Null", None),
+ ],
+)
+def test_private_doctests_application(str_expr, msgs, str_expected, fail_msg):
+ """functional.application"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("FixedPoint[f, x, 0]", None, "x", None),
+ (
+ "FixedPoint[f, x, -1]",
+ ("Non-negative integer expected.",),
+ "FixedPoint[f, x, -1]",
+ None,
+ ),
+ ("FixedPoint[Cos, 1.0, Infinity]", None, "0.739085", None),
+ ("FixedPointList[f, x, 0]", None, "{x}", None),
+ (
+ "FixedPointList[f, x, -1]",
+ ("Non-negative integer expected.",),
+ "FixedPointList[f, x, -1]",
+ None,
+ ),
+ ("Last[FixedPointList[Cos, 1.0, Infinity]]", None, "0.739085", None),
+ ],
+)
+def test_private_doctests_functional_iteration(str_expr, msgs, str_expected, fail_msg):
+ """functional.functional_iteration"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_intfns.py b/test/builtin/test_intfns.py
new file mode 100644
index 000000000..c7c790728
--- /dev/null
+++ b/test/builtin/test_intfns.py
@@ -0,0 +1,213 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.intfns
+"""
+
+from test.helper import check_evaluation, session
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("HarmonicNumber[-1.5]", None, "0.613706", None),
+ ],
+)
+def test_private_doctests_recurrence(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ## TODO should be ComplexInfinity but mpmath returns +inf
+ ("Binomial[-10, -3.5]", None, "Infinity", None),
+ ("Subsets[{}]", None, "{{}}", None),
+ ("Subsets[]", None, "Subsets[]", None),
+ (
+ "Subsets[{a, b, c}, 2.5]",
+ (
+ "Position 2 of Subsets[{a, b, c}, 2.5] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.",
+ ),
+ "Subsets[{a, b, c}, 2.5]",
+ None,
+ ),
+ (
+ "Subsets[{a, b, c}, -1]",
+ (
+ "Position 2 of Subsets[{a, b, c}, -1] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.",
+ ),
+ "Subsets[{a, b, c}, -1]",
+ None,
+ ),
+ (
+ "Subsets[{a, b, c}, {3, 4, 5, 6}]",
+ (
+ "Position 2 of Subsets[{a, b, c}, {3, 4, 5, 6}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.",
+ ),
+ "Subsets[{a, b, c}, {3, 4, 5, 6}]",
+ None,
+ ),
+ (
+ "Subsets[{a, b, c}, {-1, 2}]",
+ (
+ "Position 2 of Subsets[{a, b, c}, {-1, 2}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.",
+ ),
+ "Subsets[{a, b, c}, {-1, 2}]",
+ None,
+ ),
+ (
+ "Subsets[{a, b, c}, All]",
+ None,
+ "{{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}}",
+ None,
+ ),
+ (
+ "Subsets[{a, b, c}, Infinity]",
+ None,
+ "{{}, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}}",
+ None,
+ ),
+ (
+ "Subsets[{a, b, c}, ALL]",
+ (
+ "Position 2 of Subsets[{a, b, c}, ALL] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.",
+ ),
+ "Subsets[{a, b, c}, ALL]",
+ None,
+ ),
+ (
+ "Subsets[{a, b, c}, {a}]",
+ (
+ "Position 2 of Subsets[{a, b, c}, {a}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.",
+ ),
+ "Subsets[{a, b, c}, {a}]",
+ None,
+ ),
+ (
+ "Subsets[{a, b, c}, {}]",
+ (
+ "Position 2 of Subsets[{a, b, c}, {}] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.",
+ ),
+ "Subsets[{a, b, c}, {}]",
+ None,
+ ),
+ ("Subsets[{a, b}, 0]", None, "{{}}", None),
+ (
+ "Subsets[{1, 2}, x]",
+ (
+ "Position 2 of Subsets[{1, 2}, x] must be All, Infinity, a non-negative integer, or a List whose first element (required) is a non-negative integer, second element (optional) is a non-negative integer or Infinity, and third element (optional) is a nonzero integer.",
+ ),
+ "Subsets[{1, 2}, x]",
+ None,
+ ),
+ (
+ "Subsets[x]",
+ ("Nonatomic expression expected at position 1 in Subsets[x].",),
+ "Subsets[x]",
+ None,
+ ),
+ (
+ "Subsets[x, {1, 2}]",
+ ("Nonatomic expression expected at position 1 in Subsets[x, {1, 2}].",),
+ "Subsets[x, {1, 2}]",
+ None,
+ ),
+ (
+ "Subsets[x, {1, 2, 3}, {1, 3}]",
+ (
+ "Nonatomic expression expected at position 1 in Subsets[x, {1, 2, 3}, {1, 3}].",
+ ),
+ "Subsets[x, {1, 2, 3}, {1, 3}]",
+ None,
+ ),
+ (
+ "Subsets[a + b + c]",
+ None,
+ "{0, a, b, c, a + b, a + c, b + c, a + b + c}",
+ None,
+ ),
+ (
+ "Subsets[f[a, b, c]]",
+ None,
+ "{f[], f[a], f[b], f[c], f[a, b], f[a, c], f[b, c], f[a, b, c]}",
+ None,
+ ),
+ ("Subsets[a + b + c, {1, 3, 2}]", None, "{a, b, c, a + b + c}", None),
+ ("Subsets[a* b * c, All, {6}]", None, "{a c}", None),
+ (
+ "Subsets[{a, b, c}, {1, Infinity}]",
+ None,
+ "{{a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}}",
+ None,
+ ),
+ (
+ "Subsets[{a, b, c}, {1, Infinity, 2}]",
+ None,
+ "{{a}, {b}, {c}, {a, b, c}}",
+ None,
+ ),
+ ("Subsets[{a, b, c}, {3, Infinity, -1}]", None, "{}", None),
+ ],
+)
+def test_private_doctests_combinatorial(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ "Quotient[13, 0]",
+ ("Infinite expression Quotient[13, 0] encountered.",),
+ "ComplexInfinity",
+ None,
+ ),
+ ("Quotient[-17, 7]", None, "-3", None),
+ ("Quotient[-17, -4]", None, "4", None),
+ ("Quotient[19, -4]", None, "-5", None),
+ (
+ "QuotientRemainder[13, 0]",
+ ("The argument 0 in QuotientRemainder[13, 0] should be nonzero.",),
+ "QuotientRemainder[13, 0]",
+ None,
+ ),
+ ("QuotientRemainder[-17, 7]", None, "{-3, 4}", None),
+ ("QuotientRemainder[-17, -4]", None, "{4, -1}", None),
+ ("QuotientRemainder[19, -4]", None, "{-5, -1}", None),
+ ("QuotientRemainder[a, 0]", None, "QuotientRemainder[a, 0]", None),
+ ("QuotientRemainder[a, b]", None, "QuotientRemainder[a, b]", None),
+ ("QuotientRemainder[5.2,2.5]", None, "{2, 0.2}", None),
+ ("QuotientRemainder[5, 2.]", None, "{2, 1.}", None),
+ ],
+)
+def test_private_doctests_divlike(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_makeboxes.py b/test/builtin/test_makeboxes.py
index 3650f9681..fdf31e420 100644
--- a/test/builtin/test_makeboxes.py
+++ b/test/builtin/test_makeboxes.py
@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
import os
-from test.helper import check_evaluation, session
+from test.helper import check_evaluation
import pytest
-from mathics_scanner.errors import IncompleteSyntaxError
# To check the progress in the improvement of formatting routines, set this variable to 1.
# Otherwise, the tests are going to be skipped.
diff --git a/test/builtin/test_messages.py b/test/builtin/test_messages.py
new file mode 100644
index 000000000..106e02894
--- /dev/null
+++ b/test/builtin/test_messages.py
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.messages.
+"""
+
+
+from test.helper import check_evaluation_as_in_cli, session
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("Check[1^0, err]", None, "1", None),
+ (
+ "Check[1 + 2]",
+ ("Check called with 1 argument; 2 or more arguments are expected.",),
+ "Check[1 + 2]",
+ None,
+ ),
+ (
+ "Check[1 + 2, err, 3 + 1]",
+ (
+ "Message name 3 + 1 is not of the form symbol::name or symbol::name::language.",
+ ),
+ "Check[1 + 2, err, 3 + 1]",
+ None,
+ ),
+ (
+ "Check[1 + 2, err, hello]",
+ (
+ "Message name hello is not of the form symbol::name or symbol::name::language.",
+ ),
+ "Check[1 + 2, err, hello]",
+ None,
+ ),
+ (
+ "Check[1/0, err, Compile::cpbool]",
+ ("Infinite expression 1 / 0 encountered.",),
+ "ComplexInfinity",
+ None,
+ ),
+ (
+ "Check[{0^0, 1/0}, err]",
+ (
+ "Indeterminate expression 0 ^ 0 encountered.",
+ "Infinite expression 1 / 0 encountered.",
+ ),
+ "err",
+ None,
+ ),
+ (
+ "Check[0^0/0, err, Power::indet]",
+ (
+ "Indeterminate expression 0 ^ 0 encountered.",
+ "Infinite expression 1 / 0 encountered.",
+ ),
+ "err",
+ None,
+ ),
+ (
+ "Check[{0^0, 3/0}, err, Power::indet]",
+ (
+ "Indeterminate expression 0 ^ 0 encountered.",
+ "Infinite expression 1 / 0 encountered.",
+ ),
+ "err",
+ None,
+ ),
+ (
+ "Check[1 + 2, err, {a::b, 2 + 5}]",
+ (
+ "Message name 2 + 5 is not of the form symbol::name or symbol::name::language.",
+ ),
+ "Check[1 + 2, err, {a::b, 2 + 5}]",
+ None,
+ ),
+ ("Off[Power::infy];Check[1 / 0, err]", None, "ComplexInfinity", None),
+ (
+ "On[Power::infy];Check[1 / 0, err]",
+ ("Infinite expression 1 / 0 encountered.",),
+ "err",
+ None,
+ ),
+ (
+ 'Get["nonexistent_file.m"]',
+ ("Cannot open nonexistent_file.m.",),
+ "$Failed",
+ None,
+ ),
+ (
+ "Off[1]",
+ (
+ "Message name 1 is not of the form symbol::name or symbol::name::language.",
+ ),
+ None,
+ None,
+ ),
+ ("Off[Message::name, 1]", None, None, None),
+ (
+ "On[Power::infy, Power::indet, Syntax::com];Quiet[expr, All, All]",
+ ("Arguments 2 and 3 of Quiet[expr, All, All] should not both be All.",),
+ "Quiet[expr, All, All]",
+ None,
+ ),
+ (
+ "{1,}",
+ (
+ 'Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").',
+ ),
+ "{1, Null}",
+ None,
+ ),
+ (
+ "{, 1}",
+ (
+ 'Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").',
+ ),
+ "{Null, 1}",
+ None,
+ ),
+ (
+ "{,,}",
+ (
+ 'Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").',
+ 'Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").',
+ 'Warning: comma encountered with no adjacent expression. The expression will be treated as Null (line 1 of "").',
+ ),
+ "{Null, Null, Null}",
+ None,
+ ),
+ # TODO:
+ # ("On[f::x]", ("Message f::x not found.",), None, None),
+ ],
+)
+def test_private_doctests_messages(str_expr, msgs, str_expected, fail_msg):
+ """These tests check the behavior the module messages"""
+ check_evaluation_as_in_cli(str_expr, str_expected, fail_msg, msgs)
diff --git a/test/builtin/test_number_form.py b/test/builtin/test_number_form.py
new file mode 100644
index 000000000..74b8fddf6
--- /dev/null
+++ b/test/builtin/test_number_form.py
@@ -0,0 +1,49 @@
+import pytest
+import sympy
+
+from mathics.builtin.makeboxes import int_to_tuple_info, real_to_tuple_info
+from mathics.core.atoms import Integer, Integer0, Integer1, IntegerM1, Real
+
+# from packaging.version import Version
+
+
+@pytest.mark.parametrize(
+ ("integer", "expected", "exponent", "is_nonnegative"),
+ [
+ (Integer0, "0", 0, True),
+ (Integer1, "1", 0, True),
+ (IntegerM1, "1", 0, False),
+ (Integer(999), "999", 2, True),
+ (Integer(1000), "1000", 3, True),
+ (Integer(-9999), "9999", 3, False),
+ (Integer(-10000), "10000", 4, False),
+ ],
+)
+def test_int_to_tuple_info(
+ integer: Integer, expected: str, exponent: int, is_nonnegative: bool
+):
+ assert int_to_tuple_info(integer) == (expected, exponent, is_nonnegative)
+
+
+@pytest.mark.parametrize(
+ ("real", "digits", "expected", "exponent", "is_nonnegative"),
+ [
+ # Using older uncorrected version of Real()
+ # (
+ # (Real(sympy.Float(0.0, 10)), 10, "0", -10, True)
+ # if Version(sympy.__version__) < Version("1.13.0")
+ # else (Real(sympy.Float(0.0, 10)), 10, "0000000000", -1, True)
+ # ),
+ (Real(sympy.Float(0.0, 10)), 10, "0", -10, True),
+ (Real(0), 1, "0", 0, True),
+ (Real(0), 2, "0", 0, True),
+ (Real(0.1), 2, "1", -1, True),
+ (Real(0.12), 2, "12", -1, True),
+ (Real(-0.12), 2, "12", -1, False),
+ (Real(3.141593), 10, "3141593", 0, True),
+ ],
+)
+def test_real_to_tuple_info(
+ real: Real, digits: int, expected: str, exponent: int, is_nonnegative: bool
+):
+ assert real_to_tuple_info(real, digits) == (expected, exponent, is_nonnegative)
diff --git a/test/builtin/test_numeric.py b/test/builtin/test_numeric.py
index dff0d72b9..ae5e6c603 100644
--- a/test/builtin/test_numeric.py
+++ b/test/builtin/test_numeric.py
@@ -4,9 +4,10 @@
In particular, Rationalize and RealValuNumberQ
"""
-
from test.helper import check_evaluation
+import pytest
+
def test_rationalize():
# Some of the Rationalize tests were taken from Symja's tests and docs
@@ -67,3 +68,73 @@ def test_realvalued():
),
):
check_evaluation(str_expr, str_expected)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ "p=N[Pi,100]",
+ None,
+ "3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068",
+ None,
+ ),
+ (
+ "ToString[p]",
+ None,
+ "3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068",
+ None,
+ ),
+ ("N[1.012345678901234567890123, 20]", None, "1.0123456789012345679", None),
+ ("N[I, 30]", None, "1.00000000000000000000000000000 I", None),
+ (
+ "N[1.012345678901234567890123, 50] //{#1, #1//Precision}&",
+ None,
+ "{1.01234567890123456789012, 24.}",
+ None,
+ ),
+ (
+ "p=.;x=.;y=.;Rationalize[N[Pi] + 0.8 I, x]",
+ ("Tolerance specification x must be a non-negative number.",),
+ "Rationalize[3.14159 + 0.8 I, x]",
+ None,
+ ),
+ (
+ "Rationalize[N[Pi] + 0.8 I, -1]",
+ ("Tolerance specification -1 must be a non-negative number.",),
+ "Rationalize[3.14159 + 0.8 I, -1]",
+ None,
+ ),
+ (
+ "Rationalize[x, y]",
+ ("Tolerance specification y must be a non-negative number.",),
+ "Rationalize[x, y]",
+ None,
+ ),
+ (
+ "Sign[{1, 2.3, 4/5, {-6.7, 0}, {8/9, -10}}]",
+ None,
+ "{1, 1, 1, {-1, 0}, {1, -1}}",
+ None,
+ ),
+ ("Sign[1 - 4*I] == (1/17 - 4 I/17) Sqrt[17]", None, "True", None),
+ (
+ "Sign[4, 5, 6]",
+ ("Sign called with 3 arguments; 1 argument is expected.",),
+ "Sign[4, 5, 6]",
+ None,
+ ),
+ ('Sign["20"]', None, "Sign[20]", None),
+ ],
+)
+def test_private_doctests_numeric(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_options.py b/test/builtin/test_options.py
new file mode 100644
index 000000000..195d2ce9d
--- /dev/null
+++ b/test/builtin/test_options.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.options.
+"""
+
+
+from test.helper import check_evaluation, session
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ (
+ 'f[x_, OptionsPattern[f]] := x ^ OptionValue["m"];'
+ 'Options[f] = {"m" -> 7};f[x]'
+ ),
+ None,
+ "x ^ 7",
+ None,
+ ),
+ ("f /: Options[f] = {a -> b}", None, "{a -> b}", None),
+ ("Options[f]", None, "{a :> b}", None),
+ (
+ "f /: Options[g] := {a -> b}",
+ ("Rule for Options can only be attached to g.",),
+ "$Failed",
+ None,
+ ),
+ (
+ "Options[f] = a /; True",
+ ("a /; True is not a valid list of option rules.",),
+ "a /; True",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_options(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_patterns.py b/test/builtin/test_patterns.py
index 9fb3d8a0f..3bdd932e0 100644
--- a/test/builtin/test_patterns.py
+++ b/test/builtin/test_patterns.py
@@ -5,8 +5,13 @@
from test.helper import check_evaluation
+import pytest
+
+# Clear all the variables
+
def test_blank():
+ check_evaluation(None, None, None)
for str_expr, str_expected, message in (
(
"g[i] /. _[i] :> a",
@@ -18,6 +23,7 @@ def test_blank():
def test_replace_all():
+ check_evaluation(None, None, None)
for str_expr, str_expected, message in (
(
"a == d b + d c /. a_ x_ + a_ y_ -> a (x + y)",
@@ -26,3 +32,104 @@ def test_replace_all():
),
):
check_evaluation(str_expr, str_expected, message)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("a + b /. x_ + y_ -> {x, y}", None, "{a, b}", None),
+ (
+ 'StringReplace["h1d9a f483", DigitCharacter | WhitespaceCharacter -> ""]',
+ None,
+ "hdaf",
+ None,
+ ),
+ (
+ 'StringReplace["abc DEF 123!", Except[LetterCharacter, WordCharacter] -> "0"]',
+ None,
+ "abc DEF 000!",
+ None,
+ ),
+ ("a:b:c", None, "a : b : c", None),
+ ("FullForm[a:b:c]", None, "Optional[Pattern[a, b], c]", None),
+ ("(a:b):c", None, "a : b : c", None),
+ ("a:(b:c)", None, "a : (b : c)", None),
+ ('StringReplace["hello world!", _ -> "x"]', None, "xxxxxxxxxxxx", None),
+ ("f[a, b, c, d] /. f[x__, c, y__] -> {{x},{y}}", None, "{{a, b}, {d}}", None),
+ ("a + b + c + d /. Plus[x__, c] -> {x}", None, "{a, b, d}", None),
+ (
+ 'StringReplace[{"ab", "abc", "abcd"}, "b" ~~ __ -> "x"]',
+ None,
+ "{ab, ax, ax}",
+ None,
+ ),
+ ## This test hits infinite recursion
+ ##
+ ##The value captured by a named 'BlankNullSequence' pattern is a
+ ##'Sequence' object, which can have no elements:
+ ## ('f[] /. f[x___] -> x', None,
+ ## 'Sequence[]', None),
+ ("___symbol", None, "___symbol", None),
+ ("___symbol //FullForm", None, "BlankNullSequence[symbol]", None),
+ (
+ 'StringReplace[{"ab", "abc", "abcd"}, "b" ~~ ___ -> "x"]',
+ None,
+ "{ax, ax, ax}",
+ None,
+ ),
+ ("1.. // FullForm", None, "Repeated[1]", None),
+ (
+ "8^^1.. // FullForm (* Mathematica gets this wrong *)",
+ None,
+ "Repeated[1]",
+ None,
+ ),
+ ('StringReplace["010110110001010", "01".. -> "a"]', None, "a1a100a0", None),
+ (
+ 'StringMatchQ[#, "a" ~~ ("b"..) ~~ "a"] &/@ {"aa", "aba", "abba"}',
+ None,
+ "{False, True, True}",
+ None,
+ ),
+ ("1... // FullForm", None, "RepeatedNull[1]", None),
+ (
+ "8^^1... // FullForm (* Mathematica gets this wrong *)",
+ None,
+ "RepeatedNull[1]",
+ None,
+ ),
+ (
+ 'StringMatchQ[#, "a" ~~ ("b"...) ~~ "a"] &/@ {"aa", "aba", "abba"}',
+ None,
+ "{True, True, True}",
+ None,
+ ),
+ ("{opt -> b} /. OptionsPattern[{}] -> t", None, "t", None),
+ ("Clear[f]", None, None, None),
+ (
+ "Options[f] = {Power -> 2}; f[x_, OptionsPattern[f]] := x ^ OptionValue[Power];",
+ None,
+ None,
+ None,
+ ),
+ ("f[10]", None, "100", None),
+ ("f[10, Power -> 3]", None, "1000", None),
+ ("Clear[f]", None, None, None),
+ ("Options[f] = {Power -> 2};", None, None, None),
+ ("f[x_, OptionsPattern[]] := x ^ OptionValue[Power];", None, None, None),
+ ("f[10]", None, "100", None),
+ ("f[10, Power -> 3]", None, "1000", None),
+ ("Clear[f]", None, None, None),
+ ],
+)
+def test_private_doctests_pattern(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_physchemdata.py b/test/builtin/test_physchemdata.py
new file mode 100644
index 000000000..6e12d913e
--- /dev/null
+++ b/test/builtin/test_physchemdata.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtins.physchemdata
+"""
+
+from test.helper import check_evaluation
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ 'Outer[ElementData, Range[118], ElementData["Properties"]];',
+ None,
+ "Null",
+ "Ensure all data parses #664",
+ ),
+ ],
+)
+def test_private_doctests_physchemdata(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=False,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_procedural.py b/test/builtin/test_procedural.py
index 779ec683c..ca0105bfb 100644
--- a/test/builtin/test_procedural.py
+++ b/test/builtin/test_procedural.py
@@ -1,5 +1,9 @@
# -*- coding: utf-8 -*-
-from test.helper import check_evaluation, session
+"""
+Unit tests from mathics.builtin.procedural.
+"""
+
+from test.helper import check_evaluation, check_evaluation_as_in_cli, session
import pytest
@@ -19,3 +23,130 @@ def test_nestwhile(str_expr, str_expected):
check_evaluation(
str_expr, str_expected, to_string_expr=True, to_string_expected=True
)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("res=CompoundExpression[x, y, z]", None, "z", None),
+ ("res", None, "z", "Issue 331"),
+ ("z = Max[1, 1 + x]; x = 2; z", None, "3", "Issue 531"),
+ ("Clear[x]; Clear[z]; Clear[res];", None, "Null", None),
+ (
+ 'Do[Print["hi"],{1+1}]',
+ (
+ "hi",
+ "hi",
+ ),
+ "Null",
+ None,
+ ),
+ (
+ "n := 1; For[i=1, i<=10, i=i+1, If[i > 5, Return[i]]; n = n * i]",
+ None,
+ "6",
+ None,
+ ),
+ ("n", None, "120", "Side effect of the previous test"),
+ ("h[x_] := (If[x < 0, Return[]]; x)", None, "Null", None),
+ ("h[1]", None, "1", None),
+ ("h[-1]", None, "Null", None),
+ ("f[x_] := Return[x];g[y_] := Module[{}, z = f[y]; 2]", None, "Null", None),
+ ("g[1]", None, "2", "Issue 513"),
+ (
+ "a; Switch[b, b]",
+ (
+ "Switch called with 2 arguments. Switch must be called with an odd number of arguments.",
+ ),
+ "Switch[b, b]",
+ None,
+ ),
+ ## Issue 531
+ (
+ "z = Switch[b, b];",
+ (
+ "Switch called with 2 arguments. Switch must be called with an odd number of arguments.",
+ ),
+ "Null",
+ "Issue 531",
+ ),
+ ("z", None, "Switch[b, b]", "Issue 531"),
+ ("i = 1; While[True, If[i^2 > 100, Return[i + 1], i++]]", None, "12", None),
+ # These tests check the result of a compound expression which finish with Null.
+ # The result is different to the one obtained if we use the history (`%`)
+ # which is test in `test_history_compound_expression`
+ ("res=CompoundExpression[x, y, Null]", None, "Null", None),
+ ("res", None, "Null", None),
+ (
+ "res=CompoundExpression[CompoundExpression[x, y, Null], Null]",
+ None,
+ "Null",
+ None,
+ ),
+ ("res", None, "Null", None),
+ ("res=CompoundExpression[x, Null, Null]", None, "Null", None),
+ ("res", None, "Null", None),
+ ("res=CompoundExpression[]", None, "Null", None),
+ ("res", None, "Null", None),
+ (
+ "{MatchQ[Infinity,Infinity],Switch[Infinity,Infinity,True,_,False]}",
+ None,
+ "{True, True}",
+ "Issue #956",
+ ),
+ (
+ "Clear[f];Clear[g];Clear[h];Clear[i];Clear[n];Clear[res];Clear[z]; ",
+ None,
+ "Null",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_procedural(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+def test_history_compound_expression():
+ """Test the effect in the history from the evaluation of a CompoundExpression"""
+ check_evaluation_as_in_cli("Clear[x];Clear[y]")
+ check_evaluation_as_in_cli("CompoundExpression[x, y, Null]")
+ check_evaluation_as_in_cli("ToString[%]", "y")
+ check_evaluation_as_in_cli(
+ "CompoundExpression[CompoundExpression[y, x, Null], Null]"
+ )
+ check_evaluation_as_in_cli("ToString[%]", "x")
+ check_evaluation_as_in_cli("CompoundExpression[x, y, Null, Null]")
+ check_evaluation_as_in_cli("ToString[%]", "y")
+ check_evaluation_as_in_cli("CompoundExpression[]")
+ check_evaluation_as_in_cli("ToString[%]", "Null")
+ check_evaluation_as_in_cli("Clear[x];Clear[y];")
+ return
+
+ def eval_expr(expr_str):
+ query = session.evaluation.parse(expr_str)
+ return session.evaluation.evaluate(query)
+
+ eval_expr("Clear[x];Clear[y]")
+ eval_expr("CompoundExpression[x, y, Null]")
+ assert eval_expr("ToString[%]").result == "y"
+ eval_expr("CompoundExpression[CompoundExpression[y, x, Null], Null])")
+ assert eval_expr("ToString[%]").result == "x"
+ eval_expr("CompoundExpression[x, y, Null, Null]")
+ assert eval_expr("ToString[%]").result == "y"
+ eval_expr("CompoundExpression[]")
+ assert eval_expr("ToString[%]").result == "Null"
+ eval_expr("Clear[x];Clear[y]")
+ # Calling `session.evaluation.evaluate` ends by
+ # set the flag `stopped` to `True`, which produces
+ # a timeout exception if we evaluate an expression from
+ # its `evaluate` method...
+ session.evaluation.stopped = False
diff --git a/test/builtin/test_quantities.py b/test/builtin/test_quantities.py
new file mode 100644
index 000000000..dd43a63ae
--- /dev/null
+++ b/test/builtin/test_quantities.py
@@ -0,0 +1,180 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtins.quantities
+
+In particular, Rationalize and RealValuNumberQ
+"""
+
+from test.helper import check_evaluation
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("Quantity[10, Meters]", None, "Quantity[10, Meters]", None),
+ (
+ "Quantity[Meters]",
+ ("Unable to interpret unit specification Meters.",),
+ "Quantity[Meters]",
+ None,
+ ),
+ ('Quantity[1, "foot"]', None, 'Quantity[1, "foot"]', None),
+ (
+ 'Quantity[1, "aaa"]',
+ ("Unable to interpret unit specification aaa.",),
+ 'Quantity[1, "aaa"]',
+ None,
+ ),
+ ('QuantityMagnitude[Quantity[1, "meter"], "centimeter"]', None, "100", None),
+ (
+ 'QuantityMagnitude[Quantity[{3, 1}, "meter"], "centimeter"]',
+ None,
+ "{300, 100}",
+ None,
+ ),
+ (
+ 'QuantityMagnitude[Quantity[{300,100}, "centimeter"], "meter"]',
+ None,
+ "{3, 1}",
+ None,
+ ),
+ (
+ 'QuantityMagnitude[Quantity[{3, 1}, "meter"], "inch"]',
+ None,
+ "{118.11, 39.3701}",
+ None,
+ ),
+ (
+ 'QuantityMagnitude[Quantity[{3, 1}, "meter"], Quantity[3, "centimeter"]]',
+ None,
+ "{300, 100}",
+ None,
+ ),
+ (
+ 'QuantityMagnitude[Quantity[3, "mater"]]',
+ ("Unable to interpret unit specification mater.",),
+ 'QuantityMagnitude[Quantity[3, "mater"]]',
+ None,
+ ),
+ ("QuantityQ[3]", None, "False", None),
+ (
+ 'QuantityUnit[Quantity[10, "aaa"]]',
+ ("Unable to interpret unit specification aaa.",),
+ 'QuantityUnit[Quantity[10, "aaa"]]',
+ None,
+ ),
+ (
+ 'UnitConvert[Quantity[{3, 10}, "centimeter"]]',
+ None,
+ '{Quantity[3/100, "meter"], Quantity[1/10, "meter"]}',
+ None,
+ ),
+ (
+ 'UnitConvert[Quantity[3, "aaa"]]',
+ ("Unable to interpret unit specification aaa.",),
+ 'UnitConvert[Quantity[3, "aaa"]]',
+ None,
+ ),
+ (
+ 'UnitConvert[Quantity[{300, 152}, "centimeter"], Quantity[10, "meter"]]',
+ None,
+ '{Quantity[3, "meter"], Quantity[38/25, "meter"]}',
+ None,
+ ),
+ (
+ 'UnitConvert[Quantity[{300, 152}, "km"], Quantity[10, "cm"]]',
+ None,
+ '{Quantity[30000000, "centimeter"], Quantity[15200000, "centimeter"]}',
+ None,
+ ),
+ (
+ 'UnitConvert[Quantity[{3, 1}, "meter"], "inch"]',
+ None,
+ '{Quantity[118.11, "inch"], Quantity[39.3701, "inch"]}',
+ None,
+ ),
+ (
+ 'UnitConvert[Quantity[20, "celsius"]]',
+ None,
+ '"293.15 kelvin"',
+ None,
+ ),
+ (
+ 'UnitConvert[Quantity[300, "fahrenheit"]]',
+ None,
+ '"422.039 kelvin"',
+ None,
+ ),
+ (
+ 'UnitConvert[Quantity[451, "fahrenheit"], "celsius"]',
+ None,
+ '"232.778 degree Celsius"',
+ None,
+ ),
+ (
+ 'UnitConvert[Quantity[20, "celsius"], "kelvin"]',
+ None,
+ '"293.15 kelvin"',
+ None,
+ ),
+ (
+ 'UnitConvert[Quantity[273, "kelvin"], "celsius"]',
+ None,
+ '"-0.15 degree Celsius"',
+ None,
+ ),
+ ],
+)
+def test_private_doctests_numeric(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=False,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected"),
+ [
+ ('a=.; 3*Quantity[a, "meter"^2]', "3 a meter ^ 2"),
+ ('a Quantity[1/a, "Meter"^2]', "1 meter ^ 2"),
+ ('Quantity[3, "Meter"^2]', "3 meter ^ 2"),
+ (
+ 'Quantity[2, "Meter"]^2',
+ "4 meter ^ 2",
+ ),
+ ('Quantity[5, "Meter"]^2-Quantity[3, "Meter"]^2', "16 meter ^ 2"),
+ (
+ 'Quantity[2, "kg"] * Quantity[9.8, "Meter/Second^2"]',
+ "19.6 kilogram meter / second ^ 2",
+ ),
+ (
+ 'UnitConvert[Quantity[2, "Ampere*Second"], "microcoulomb"]',
+ "2000000 microcoulomb",
+ ),
+ (
+ 'UnitConvert[Quantity[2., "Ampere*microSecond"], "microcoulomb"]',
+ "2. microcoulomb",
+ ),
+ # TODO Non integer powers:
+ # ('Quantity[4., "watt"]^(1/2)','2 square root watts'),
+ # ('Quantity[4., "watt"]^(1/3)','2^(2/3) cube root watts'),
+ # ('Quantity[4., "watt"]^(.24)','1.39474 watts to the 0.24'),
+ ],
+)
+def test_quantity_operations(str_expr, str_expected):
+ """test operations involving quantities"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ )
diff --git a/test/builtin/test_scoping.py b/test/builtin/test_scoping.py
index d0a1251f7..b91bc269d 100644
--- a/test/builtin/test_scoping.py
+++ b/test/builtin/test_scoping.py
@@ -2,8 +2,9 @@
"""
Unit tests from mathics.builtin.scoping.
"""
+from test.helper import check_evaluation, session
-from test.helper import session
+import pytest
from mathics.core.symbols import Symbol
@@ -34,3 +35,26 @@ def test_unique():
assert (
symbol not in symbol_set
), "Unique[{symbol_prefix}] should return different symbols; {symbol.name} is duplicated"
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("InputForm[$Context]", None, '"Global`"', None),
+ ## Test general context behaviour
+ ("Plus === Global`Plus", None, "False", None),
+ ("`Plus === Global`Plus", None, "True", None),
+ ("Unique[{}]", None, "{}", None),
+ ],
+)
+def test_private_doctests_scoping(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_statistics.py b/test/builtin/test_statistics.py
new file mode 100644
index 000000000..ff92e5725
--- /dev/null
+++ b/test/builtin/test_statistics.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.statistics.
+"""
+
+import sys
+import time
+from test.helper import check_evaluation, evaluate
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("Sort[{x_, y_}, PatternsOrderedQ]", None, "{x_, y_}", None),
+ ],
+)
+def test_private_doctests_statistics_orderstatistics(
+ str_expr, msgs, str_expected, fail_msg
+):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_strings.py b/test/builtin/test_strings.py
new file mode 100644
index 000000000..ea095a2ea
--- /dev/null
+++ b/test/builtin/test_strings.py
@@ -0,0 +1,591 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.string.
+"""
+
+from test.helper import check_evaluation, session
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ (
+ 'StringInsert["abcdefghijklm", "X", 15]',
+ ("Cannot insert at position 15 in abcdefghijklm.",),
+ "StringInsert[abcdefghijklm, X, 15]",
+ None,
+ ),
+ (
+ 'StringInsert[abcdefghijklm, "X", 4]',
+ (
+ "String or list of strings expected at position 1 in StringInsert[abcdefghijklm, X, 4].",
+ ),
+ "StringInsert[abcdefghijklm, X, 4]",
+ None,
+ ),
+ (
+ 'StringInsert["abcdefghijklm", X, 4]',
+ ("String expected at position 2 in StringInsert[abcdefghijklm, X, 4].",),
+ "StringInsert[abcdefghijklm, X, 4]",
+ None,
+ ),
+ (
+ 'StringInsert["abcdefghijklm", "X", a]',
+ (
+ "Position specification a in StringInsert[abcdefghijklm, X, a] is not a machine-sized integer or a list of machine-sized integers.",
+ ),
+ "StringInsert[abcdefghijklm, X, a]",
+ None,
+ ),
+ (
+ 'StringInsert["abcdefghijklm", "X", 0]',
+ ("Cannot insert at position 0 in abcdefghijklm.",),
+ "StringInsert[abcdefghijklm, X, 0]",
+ None,
+ ),
+ (
+ 'StringInsert["abcdefghijklm", "X", -15]',
+ ("Cannot insert at position -15 in abcdefghijklm.",),
+ "StringInsert[abcdefghijklm, X, -15]",
+ None,
+ ),
+ (
+ 'StringInsert["abcdefghijklm", "X", {1, -1, 14, -14}]',
+ None,
+ "XXabcdefghijklmXX",
+ None,
+ ),
+ (
+ 'StringInsert["abcdefghijklm", "X", {1, 0}]',
+ ("Cannot insert at position 0 in abcdefghijklm.",),
+ "StringInsert[abcdefghijklm, X, {1, 0}]",
+ None,
+ ),
+ ('StringInsert["", "X", {1}]', None, "X", None),
+ ('StringInsert["", "X", {1, -1}]', None, "XX", None),
+ ('StringInsert["", "", {1}]', None, "", None),
+ (
+ 'StringInsert["", "X", {1, 2}]',
+ ("Cannot insert at position 2 in .",),
+ "StringInsert[, X, {1, 2}]",
+ None,
+ ),
+ (
+ 'StringInsert["abcdefghijklm", "", {1, 2, 3, 4 ,5, -6}]',
+ None,
+ "abcdefghijklm",
+ None,
+ ),
+ ('StringInsert["abcdefghijklm", "X", {}]', None, "abcdefghijklm", None),
+ (
+ 'StringInsert[{"abcdefghijklm", "Mathics"}, "X", 13]',
+ ("Cannot insert at position 13 in Mathics.",),
+ "{abcdefghijklXm, StringInsert[Mathics, X, 13]}",
+ None,
+ ),
+ ('StringInsert[{"", ""}, "", {1, 1, 1, 1}]', None, "{, }", None),
+ (
+ 'StringInsert[{"abcdefghijklm", "Mathics"}, "X", {0, 2}]',
+ (
+ "Cannot insert at position 0 in abcdefghijklm.",
+ "Cannot insert at position 0 in Mathics.",
+ ),
+ "{StringInsert[abcdefghijklm, X, {0, 2}], StringInsert[Mathics, X, {0, 2}]}",
+ None,
+ ),
+ (
+ 'StringInsert[{"abcdefghijklm", Mathics}, "X", {1, 2}]',
+ (
+ "String or list of strings expected at position 1 in StringInsert[{abcdefghijklm, Mathics}, X, {1, 2}].",
+ ),
+ "StringInsert[{abcdefghijklm, Mathics}, X, {1, 2}]",
+ None,
+ ),
+ (
+ 'StringInsert[{"", "Mathics"}, "X", {1, 1, -1}]',
+ None,
+ "{XXX, XXMathicsX}",
+ None,
+ ),
+ (
+ 'StringPosition["123ABCxyABCzzzABCABC", "ABC", -1]',
+ (
+ "Non-negative integer or Infinity expected at position 3 in StringPosition[123ABCxyABCzzzABCABC, ABC, -1].",
+ ),
+ "StringPosition[123ABCxyABCzzzABCABC, ABC, -1]",
+ None,
+ ),
+ ## Overlaps
+ (
+ 'StringPosition["1231221312112332", RegularExpression["[12]+"]]',
+ None,
+ "{{1, 2}, {2, 2}, {4, 7}, {5, 7}, {6, 7}, {7, 7}, {9, 13}, {10, 13}, {11, 13}, {12, 13}, {13, 13}, {16, 16}}",
+ None,
+ ),
+ (
+ 'StringPosition["1231221312112332", RegularExpression["[12]+"], Overlaps -> False]',
+ None,
+ "{{1, 2}, {4, 7}, {9, 13}, {16, 16}}",
+ None,
+ ),
+ (
+ 'StringPosition["1231221312112332", RegularExpression["[12]+"], Overlaps -> x]',
+ None,
+ "{{1, 2}, {4, 7}, {9, 13}, {16, 16}}",
+ None,
+ ),
+ (
+ 'StringPosition["1231221312112332", RegularExpression["[12]+"], Overlaps -> All]',
+ ("Overlaps -> All option is not currently implemented in Mathics.",),
+ "{{1, 2}, {2, 2}, {4, 7}, {5, 7}, {6, 7}, {7, 7}, {9, 13}, {10, 13}, {11, 13}, {12, 13}, {13, 13}, {16, 16}}",
+ None,
+ ),
+ (
+ 'StringPosition["21211121122", {"121", "11"}]',
+ None,
+ "{{2, 4}, {4, 5}, {5, 6}, {6, 8}, {8, 9}}",
+ None,
+ ),
+ (
+ 'StringPosition["21211121122", {"121", "11"}, Overlaps -> False]',
+ None,
+ "{{2, 4}, {5, 6}, {8, 9}}",
+ None,
+ ),
+ (
+ 'StringPosition[{"abc", "abcda"}, "a"]',
+ None,
+ "{{{1, 1}}, {{1, 1}, {5, 5}}}",
+ None,
+ ),
+ ('StringPosition[{"abc"}, "a", Infinity]', None, "{{{1, 1}}}", None),
+ ('StringPosition["abc"]["123AabcDEabc"]', None, "{{5, 7}, {10, 12}}", None),
+ ('StringReplace["abcabc", "a" -> "b", Infinity]', None, "bbcbbc", None),
+ (
+ 'StringReplace[x, "a" -> "b"]',
+ (
+ "String or list of strings expected at position 1 in StringReplace[x, a -> b].",
+ ),
+ "StringReplace[x, a -> b]",
+ None,
+ ),
+ (
+ 'StringReplace["xyzwxyzwaxyzxyzw", x]',
+ ("x is not a valid string replacement rule.",),
+ "StringReplace[xyzwxyzwaxyzxyzw, x]",
+ None,
+ ),
+ (
+ 'StringReplace["xyzwxyzwaxyzxyzw", x -> y]',
+ ("Element x is not a valid string or pattern element in x.",),
+ "StringReplace[xyzwxyzwaxyzxyzw, x -> y]",
+ None,
+ ),
+ (
+ 'StringReplace["abcabc", "a" -> "b", -1]',
+ (
+ "Non-negative integer or Infinity expected at position 3 in StringReplace[abcabc, a -> b, -1].",
+ ),
+ "StringReplace[abcabc, a -> b, -1]",
+ None,
+ ),
+ ('StringReplace["abc", "b" -> 4]', ("String expected.",), "a <> 4 <> c", None),
+ ('StringReplace["01101100010", "01" .. -> "x"]', None, "x1x100x0", None),
+ ('StringReplace["abc abcb abdc", "ab" ~~ _ -> "X"]', None, "X Xb Xc", None),
+ (
+ 'StringReplace["abc abcd abcd", WordBoundary ~~ "abc" ~~ WordBoundary -> "XX"]',
+ None,
+ "XX abcd abcd",
+ None,
+ ),
+ (
+ 'StringReplace["abcd acbd", RegularExpression["[ab]"] -> "XX"]',
+ None,
+ "XXXXcd XXcXXd",
+ None,
+ ),
+ (
+ 'StringReplace["abcd acbd", RegularExpression["[ab]"] ~~ _ -> "YY"]',
+ None,
+ "YYcd YYYY",
+ None,
+ ),
+ (
+ 'StringReplace["abcdabcdaabcabcd", {"abc" -> "Y", "d" -> "XXX"}]',
+ None,
+ "YXXXYXXXaYYXXX",
+ None,
+ ),
+ (
+ 'StringReplace[" Have a nice day. ", (StartOfString ~~ Whitespace) | (Whitespace ~~ EndOfString) -> ""] // FullForm',
+ None,
+ '"Have a nice day."',
+ None,
+ ),
+ ('StringReplace["xyXY", "xy" -> "01"]', None, "01XY", None),
+ ('StringReplace["xyXY", "xy" -> "01", IgnoreCase -> True]', None, "0101", None),
+ ('StringRiffle[{a, b, c, "d", e, "f"}]', None, "a b c d e f", None),
+ ## 1st is not a list
+ (
+ 'StringRiffle["abcdef"]',
+ (
+ "List expected at position 1 in StringRiffle[abcdef].",
+ "StringRiffle called with 1 argument; 2 or more arguments are expected.",
+ ),
+ "StringRiffle[abcdef]",
+ None,
+ ),
+ ('StringRiffle[{"", "", ""}] // FullForm', None, '" "', None),
+ ## This form is not supported
+ (
+ 'StringRiffle[{{"a", "b"}, {"c", "d"}}]',
+ ("Sublist form in position 1 is is not implemented yet.",),
+ "StringRiffle[{{a, b}, {c, d}}]",
+ None,
+ ),
+ (
+ 'StringRiffle[{"a", "b", "c", "d", "e"}, sep]',
+ ("String expected at position 2 in StringRiffle[{a, b, c, d, e}, sep].",),
+ "StringRiffle[{a, b, c, d, e}, sep]",
+ None,
+ ),
+ (
+ 'StringRiffle[{"a", "b", "c", "d", "e"}, {" ", ")"}]',
+ (
+ "String expected at position 2 in StringRiffle[{a, b, c, d, e}, { , )}].",
+ ),
+ "StringRiffle[{a, b, c, d, e}, { , )}]",
+ None,
+ ),
+ (
+ 'StringRiffle[{"a", "b", "c", "d", "e"}, {left, " ", "."}]',
+ (
+ "String expected at position 2 in StringRiffle[{a, b, c, d, e}, {left, , .}].",
+ ),
+ "StringRiffle[{a, b, c, d, e}, {left, , .}]",
+ None,
+ ),
+ ## This form is not supported
+ (
+ 'StringRiffle[{"a", "b", "c"}, "+", "-"]',
+ ("Multiple separators form is not implemented yet.",),
+ "StringRiffle[{a, b, c}, +, -]",
+ "## Mathematica result: a+b+c, but we are not support multiple separators",
+ ),
+ (
+ "StringSplit[x]",
+ ("String or list of strings expected at position 1 in StringSplit[x].",),
+ "StringSplit[x, Whitespace]",
+ None,
+ ),
+ (
+ 'StringSplit["x", x]',
+ ("Element x is not a valid string or pattern element in x.",),
+ "StringSplit[x, x]",
+ None,
+ ),
+ ('StringTake["abcd", 0] // InputForm', None, '""', None),
+ ('StringTake["abcd", {3, 2}] // InputForm', None, '""', None),
+ ('StringTake["", {1, 0}] // InputForm', None, '""', None),
+ (
+ 'StringTake["abc", {0, 0}]',
+ ('Cannot take positions 0 through 0 in "abc".',),
+ "StringTake[abc, {0, 0}]",
+ None,
+ ),
+ (
+ "StringTake[{2, 4},2]",
+ ("String or list of strings expected at position 1.",),
+ "StringTake[{2, 4}, 2]",
+ None,
+ ),
+ (
+ 'StringTake["kkkl",Graphics[{}]]',
+ ("Integer or a list of sequence specifications expected at position 2.",),
+ "StringTake[kkkl, -Graphics-]",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_operations(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ('StringMatchQ["123245a6", DigitCharacter..]', None, "False", None),
+ (
+ 'StringCases["abc-abc xyz-uvw", Shortest[x : WordCharacter .. ~~ "-" ~~ x : LetterCharacter] -> x]',
+ (
+ "Ignored restriction given for x in x : LetterCharacter as it does not match previous occurrences of x.",
+ ),
+ "{abc}",
+ None,
+ ),
+ ('"a" ~~ "b" ~~ "c" // FullForm', None, '"abc"', None),
+ ("a ~~ b", None, "a ~~ b", None),
+ ('StringFreeQ["Hello", "o"]', None, "False", None),
+ ('StringFreeQ["a"]["abcd"]', None, "False", None),
+ ('StringFreeQ["Mathics", "ma", IgnoreCase -> False]', None, "True", None),
+ ('StringFreeQ["", "Empty String"]', None, "True", None),
+ ('StringFreeQ["", ___]', None, "False", None),
+ ('StringFreeQ["Empty Pattern", ""]', None, "False", None),
+ (
+ 'StringFreeQ[notastring, "n"]',
+ (
+ "String or list of strings expected at position 1 in StringFreeQ[notastring, n].",
+ ),
+ "StringFreeQ[notastring, n]",
+ None,
+ ),
+ (
+ 'StringFreeQ["Welcome", notapattern]',
+ (
+ "Element notapattern is not a valid string or pattern element in notapattern.",
+ ),
+ "StringFreeQ[Welcome, notapattern]",
+ None,
+ ),
+ ('StringFreeQ[{}, "list of string is empty"]', None, "{}", None),
+ (
+ 'StringFreeQ[{"A", "Galaxy", "Far", "Far", "Away"}, {"F" ~~ __ ~~ "r", "aw" ~~ ___}]',
+ None,
+ "{True, True, False, False, True}",
+ None,
+ ),
+ (
+ 'StringFreeQ[{"A", "Galaxy", "Far", "Far", "Away"}, {}]',
+ None,
+ "{True, True, True, True, True}",
+ None,
+ ),
+ (
+ 'StringFreeQ[{"A", Galaxy, "Far", "Far", Away}, {"F" ~~ __ ~~ "r", "aw" ~~ ___}]',
+ (
+ "String or list of strings expected at position 1 in StringFreeQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}].",
+ ),
+ "StringFreeQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}]",
+ None,
+ ),
+ (
+ 'StringFreeQ[{"A", "Galaxy", "Far", "Far", "Away"}, {F ~~ __ ~~ "r", aw ~~ ___}]',
+ (
+ "Element F ~~ __ ~~ r is not a valid string or pattern element in {F ~~ __ ~~ r, aw ~~ ___}.",
+ ),
+ "StringFreeQ[{A, Galaxy, Far, Far, Away}, {F ~~ __ ~~ r, aw ~~ ___}]",
+ None,
+ ),
+ ## Mathematica can detemine correct invalid element in the pattern, it reports error:
+ ## Element F is not a valid string or pattern element in {F ~~ __ ~~ r, aw ~~ ___}.
+ ('StringMatchQ["abc1", LetterCharacter]', None, "False", None),
+ ('StringMatchQ["abc", "ABC"]', None, "False", None),
+ ('StringMatchQ["abc", "ABC", IgnoreCase -> True]', None, "True", None),
+ ## Words containing nonword characters
+ (
+ 'StringMatchQ[{"monkey", "don \'t", "AAA", "S&P"}, ___ ~~ Except[WordCharacter] ~~ ___]',
+ None,
+ "{False, True, False, True}",
+ None,
+ ),
+ ## Try to match a literal number
+ (
+ "StringMatchQ[1.5, NumberString]",
+ (
+ "String or list of strings expected at position 1 in StringMatchQ[1.5, NumberString].",
+ ),
+ "StringMatchQ[1.5, NumberString]",
+ None,
+ ),
+ ## Abbreviated string patterns Issue #517
+ ('StringMatchQ["abcd", "abc*"]', None, "True", None),
+ ('StringMatchQ["abc", "abc*"]', None, "True", None),
+ (r'StringMatchQ["abc\\", "abc\\"]', None, "True", None),
+ (r'StringMatchQ["abc*d", "abc\\*d"]', None, "True", None),
+ (r'StringMatchQ["abc*d", "abc\\**"]', None, "True", None),
+ ('StringMatchQ["abcde", "a*f"]', None, "False", None),
+ ('StringMatchQ["abcde", "a@e"]', None, "True", None),
+ ('StringMatchQ["aBCDe", "a@e"]', None, "False", None),
+ ('StringMatchQ["ae", "a@e"]', None, "False", None),
+ ],
+)
+def test_private_doctests_patterns(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ('ToCharacterCode[{"ab"}]', None, "{{97, 98}}", None),
+ (
+ 'ToCharacterCode[{{"ab"}}]',
+ (
+ "String or list of strings expected at position 1 in ToCharacterCode[{{ab}}].",
+ ),
+ "ToCharacterCode[{{ab}}]",
+ None,
+ ),
+ (
+ "ToCharacterCode[x]",
+ (
+ "String or list of strings expected at position 1 in ToCharacterCode[x].",
+ ),
+ "ToCharacterCode[x]",
+ None,
+ ),
+ ('ToCharacterCode[""]', None, "{}", None),
+ (
+ "#1 == ToCharacterCode[FromCharacterCode[#1]] & [RandomInteger[{0, 65535}, 100]]",
+ None,
+ "True",
+ None,
+ ),
+ ("FromCharacterCode[{}] // InputForm", None, '""', None),
+ (
+ "FromCharacterCode[65536]",
+ (
+ "A character code, which should be a non-negative integer less than 65536, is expected at position 1 in {65536}.",
+ ),
+ "FromCharacterCode[65536]",
+ None,
+ ),
+ (
+ "FromCharacterCode[-1]",
+ (
+ "Non-negative machine-sized integer expected at position 1 in FromCharacterCode[-1].",
+ ),
+ "FromCharacterCode[-1]",
+ None,
+ ),
+ (
+ "FromCharacterCode[444444444444444444444444444444444444]",
+ (
+ "Non-negative machine-sized integer expected at position 1 in FromCharacterCode[444444444444444444444444444444444444].",
+ ),
+ "FromCharacterCode[444444444444444444444444444444444444]",
+ None,
+ ),
+ (
+ "FromCharacterCode[{100, 101, -1}]",
+ (
+ "A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, -1}.",
+ ),
+ "FromCharacterCode[{100, 101, -1}]",
+ None,
+ ),
+ (
+ "FromCharacterCode[{100, 101, 65536}]",
+ (
+ "A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, 65536}.",
+ ),
+ "FromCharacterCode[{100, 101, 65536}]",
+ None,
+ ),
+ (
+ "FromCharacterCode[{100, 101, x}]",
+ (
+ "A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, x}.",
+ ),
+ "FromCharacterCode[{100, 101, x}]",
+ None,
+ ),
+ (
+ "FromCharacterCode[{100, {101}}]",
+ (
+ "A character code, which should be a non-negative integer less than 65536, is expected at position 2 in {100, {101}}.",
+ ),
+ "FromCharacterCode[{100, {101}}]",
+ None,
+ ),
+ (
+ "FromCharacterCode[{{97, 98, 99}, {100, 101, x}}]",
+ (
+ "A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, x}.",
+ ),
+ "FromCharacterCode[{{97, 98, 99}, {100, 101, x}}]",
+ None,
+ ),
+ (
+ "FromCharacterCode[{{97, 98, x}, {100, 101, x}}]",
+ (
+ "A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {97, 98, x}.",
+ ),
+ "FromCharacterCode[{{97, 98, x}, {100, 101, x}}]",
+ None,
+ ),
+ # These tests are commented out due to the bug reported in issue #906
+ # Octal and hexadecimal notation works alone, but fails
+ # as a part of another expression. For example,
+ # F[\.78\.79\.7A] or "\.78\.79\.7A" produces a syntax error in Mathics.
+ # Here, this is put inside a ToString[...] and hence, it does not work.
+ # (r"\.78\.79\.7A=37; xyz", None, '37', "Octal characters. check me."),
+ # (r"\:0078\:0079\:007A=38;xyz", None, '38', "Hexadecimal characters. Check me."),
+ # (r"\101\102\103\061\062\063=39;ABC123", None, "39", None),
+ (r"xyz=.;ABC123=.;\[Alpha]\[Beta]\[Gamma]", None, "\u03B1\u03B2\u03B3", None),
+ ('LetterQ[""]', None, "True", None),
+ (
+ 'LetterQ["\\[Alpha]\\[Beta]\\[Gamma]\\[Delta]\\[Epsilon]\\[Zeta]\\[Eta]\\[Theta]"]',
+ None,
+ "True",
+ None,
+ ),
+ ],
+)
+def test_private_doctests_characters(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+# These tests are separated due to the bug reported in issue #906
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected", "fail_msg"),
+ [
+ (r"\.78\.79\.7A", "xyz", "variable name using hexadecimal characters"),
+ (r"\:0078\:0079\:007A", "xyz", "variable name using hexadecimal characters"),
+ (
+ r"\101\102\103\061\062\063",
+ "ABC123",
+ "variable name using hexadecimal characters",
+ ),
+ ],
+)
+def test_private_doctests_characters(str_expr, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=False,
+ to_string_expected=False,
+ hold_expected=False,
+ failure_message=fail_msg,
+ )
diff --git a/test/builtin/test_system.py b/test/builtin/test_system.py
new file mode 100644
index 000000000..ed35753ea
--- /dev/null
+++ b/test/builtin/test_system.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.system.
+"""
+
+
+from test.helper import check_evaluation, session
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected"),
+ [
+ ('MemberQ[$Packages, "System`"]', "True"),
+ ("Head[$ParentProcessID] == Integer", "True"),
+ ("Head[$ProcessID] == Integer", "True"),
+ ("Head[$SystemWordLength] == Integer", "True"),
+ ],
+)
+def test_private_doctests_system(str_expr, str_expected):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ )
diff --git a/test/builtin/test_tensor.py b/test/builtin/test_tensor.py
new file mode 100644
index 000000000..b48c5e830
--- /dev/null
+++ b/test/builtin/test_tensor.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.tensor.
+"""
+
+import sys
+import time
+from test.helper import check_evaluation, evaluate
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("Dimensions[{}]", None, "{0}", None),
+ ("Dimensions[{{}}]", None, "{1, 0}", None),
+ ## Issue #670
+ (
+ "A = {{ b ^ ( -1 / 2), 0}, {a * b ^ ( -1 / 2 ), b ^ ( 1 / 2 )}}",
+ None,
+ "{{1 / Sqrt[b], 0}, {a / Sqrt[b], Sqrt[b]}}",
+ None,
+ ),
+ ("A . Inverse[A]", None, "{{1, 0}, {0, 1}}", None),
+ ("A", None, "{{1 / Sqrt[b], 0}, {a / Sqrt[b], Sqrt[b]}}", None),
+ # Transpose
+ ("Transpose[x]", None, "Transpose[x]", None),
+ ],
+)
+def test_private_doctests_tensor(str_expr, msgs, str_expected, fail_msg):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_testing_expressions.py b/test/builtin/test_testing_expressions.py
new file mode 100644
index 000000000..fa4c1cfa4
--- /dev/null
+++ b/test/builtin/test_testing_expressions.py
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests for mathics.builtin.testing_expressions
+"""
+
+import sys
+import time
+from test.helper import check_evaluation, evaluate
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("AnyTrue[{}, EvenQ]", None, "False", None),
+ ("AllTrue[{}, EvenQ]", None, "True", None),
+ ("Equivalent[]", None, "True", None),
+ ("Equivalent[a]", None, "True", None),
+ ("NoneTrue[{}, EvenQ]", None, "True", None),
+ ("Xor[]", None, "False", None),
+ ("Xor[a]", None, "a", None),
+ ("Xor[False]", None, "False", None),
+ ("Xor[True]", None, "True", None),
+ ("Xor[a, b]", None, "a \\[Xor] b", None),
+ ],
+)
+def test_private_doctests_logic(str_expr, msgs, str_expected, fail_msg):
+ """text_expressions.logic"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("SubsetQ[{1, 2, 3}, {0, 1}]", None, "False", None),
+ ("SubsetQ[{1, 2, 3}, {1, 2, 3, 4}]", None, "False", None),
+ (
+ "SubsetQ[{1, 2, 3}]",
+ ("SubsetQ called with 1 argument; 2 arguments are expected.",),
+ "SubsetQ[{1, 2, 3}]",
+ None,
+ ),
+ (
+ "SubsetQ[{1, 2, 3}, {1, 2}, {3}]",
+ ("SubsetQ called with 3 arguments; 2 arguments are expected.",),
+ "SubsetQ[{1, 2, 3}, {1, 2}, {3}]",
+ None,
+ ),
+ (
+ "SubsetQ[a + b + c, {1}]",
+ ("Heads Plus and List at positions 1 and 2 are expected to be the same.",),
+ "SubsetQ[a + b + c, {1}]",
+ None,
+ ),
+ (
+ "SubsetQ[{1, 2, 3}, n]",
+ ("Nonatomic expression expected at position 2 in SubsetQ[{1, 2, 3}, n].",),
+ "SubsetQ[{1, 2, 3}, n]",
+ None,
+ ),
+ ("SubsetQ[f[a, b, c], f[a]]", None, "True", None),
+ ],
+)
+def test_private_doctests_list_oriented(str_expr, msgs, str_expected, fail_msg):
+ """text_expressions.logic"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ('BooleanQ["string"]', None, "False", None),
+ ("BooleanQ[Together[x/y + y/x]]", None, "False", None),
+ ("Max[x]", None, "x", None),
+ ("Min[x]", None, "x", None),
+ ("Pi != N[Pi]", None, "False", None),
+ ("a_ != b_", None, "a_ != b_", None),
+ ("Clear[a, b];a != a != a", None, "False", None),
+ ('"abc" != "def" != "abc"', None, "False", None),
+ ("a != b != a", None, "a != b != a", "Reproduce strange MMA behaviour"),
+ ],
+)
+def test_private_doctests_equality_inequality(str_expr, msgs, str_expected, fail_msg):
+ """text_expressions.logic"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("MachineNumberQ[1.5 + 3.14159265358979324 I]", None, "True", None),
+ ("MachineNumberQ[1.5 + 5 I]", None, "True", None),
+ ("Negative[-E]", None, "True", None),
+ ("Negative[Sin[{11, 14}]]", None, "{True, False}", None),
+ ("Positive[Pi]", None, "True", None),
+ ("Positive[x]", None, "Positive[x]", None),
+ ("Positive[Sin[{11, 14}]]", None, "{False, True}", None),
+ ("PrimeQ[1]", None, "False", None),
+ ("PrimeQ[2 ^ 255 - 1]", None, "False", None),
+ ],
+)
+def test_private_doctests_numerical_properties(str_expr, msgs, str_expected, fail_msg):
+ """text_expressions.numerical_properties"""
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/vectors/test_vector_space_operations.py b/test/builtin/vectors/test_vector_space_operations.py
new file mode 100644
index 000000000..9af88a5e2
--- /dev/null
+++ b/test/builtin/vectors/test_vector_space_operations.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.vectors.vector_space_operations.
+"""
+
+import sys
+import time
+from test.helper import check_evaluation, evaluate
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("Normalize[0]", None, "0", None),
+ ("Normalize[{0}]", None, "{0}", None),
+ ("Normalize[{}]", None, "{}", None),
+ ("VectorAngle[{0, 1}, {0, 1}]", None, "0", None),
+ ],
+)
+def test_private_doctests_vector_space_operations(
+ str_expr, msgs, str_expected, fail_msg
+):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/consistency-and-style/test_duplicate_builtins.py b/test/consistency-and-style/test_duplicate_builtins.py
index 5ece63ccb..c8dc3c6e5 100644
--- a/test/consistency-and-style/test_duplicate_builtins.py
+++ b/test/consistency-and-style/test_duplicate_builtins.py
@@ -2,13 +2,13 @@
Checks that builtin functions do not get redefined.
In the past when reorganizing builtin functions we sometimes
-had missing or duplicate build-in functions definitions.
+had missing or duplicate built-in functions definitions.
"""
import os
import pytest
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
from mathics.core.load_builtin import mathics3_builtins_modules, name_is_builtin_symbol
diff --git a/test/consistency-and-style/test_summary_text.py b/test/consistency-and-style/test_summary_text.py
index 88b87aa71..6fafd92f5 100644
--- a/test/consistency-and-style/test_summary_text.py
+++ b/test/consistency-and-style/test_summary_text.py
@@ -7,9 +7,9 @@
import pytest
from mathics import __file__ as mathics_initfile_path
-from mathics.builtin.base import Builtin
+from mathics.core.builtin import Builtin
from mathics.core.load_builtin import name_is_builtin_symbol
-from mathics.doc.common_doc import skip_doc
+from mathics.doc.gather import skip_doc
# Get file system path name for mathics.builtin
mathics_path = osp.dirname(mathics_initfile_path)
@@ -157,10 +157,10 @@ def check_well_formatted_docstring(docstr: str, instance: Builtin, module_name:
), f"missing
field {instance.get_name()} from {module_name}"
assert (
docstr.count("") == 0
- ), f"unnecesary {instance.get_name()} from {module_name}"
+ ), f"unnecessary {instance.get_name()} from {module_name}"
assert (
docstr.count("
") == 0
- ), f"unnecesary
field {instance.get_name()} from {module_name}"
+ ), f"unnecessary
field {instance.get_name()} from {module_name}"
assert (
docstr.count("") > 0
diff --git a/test/core/parser/test_convert.py b/test/core/parser/test_convert.py
index ea99bf362..9b6e9d3d3 100644
--- a/test/core/parser/test_convert.py
+++ b/test/core/parser/test_convert.py
@@ -58,6 +58,10 @@ def testInteger(self):
self.check("10*^3", Integer(10000))
self.check("10*^-3", Rational(1, 100))
self.check("8^^23*^2", Integer(1216))
+ self.check("2^^0101", Integer(5))
+ self.check(
+ "36^^0123456789abcDEFxyzXYZ", Integer(14142263610074677021975869033659)
+ )
n = random.randint(-sys.maxsize, sys.maxsize)
self.check(str(n), Integer(n))
@@ -65,6 +69,15 @@ def testInteger(self):
n = random.randint(sys.maxsize, sys.maxsize * sys.maxsize)
self.check(str(n), Integer(n))
+ # Requested base 1 in 1^^2 should be between 2 and 36.
+ self.invalid_error(r"1^^2")
+ # Requested base 37 in 37^^3 should be between 2 and 36.
+ self.invalid_error(r"37^^3")
+ # Digit at position 3 in 01210 is too large to be used in base 2.
+ self.invalid_error(r"2^^01210")
+ # "Digit at position 2 in 5g is too large to be used in base 16."
+ self.invalid_error(r"16^^5g")
+
def testReal(self):
self.check("1.5", Real("1.5"))
self.check("1.5`", Real("1.5"))
@@ -74,9 +87,20 @@ def testReal(self):
self.check("0``3", "0.000`3")
self.check("0.`3", "0.000`3")
self.check("0.``3", "0.000``3")
+ ## Mathematica treats zero strangely
self.check("0.00000000000000000", "0.")
self.check("0.000000000000000000`", "0.")
self.check("0.000000000000000000", "0.``18")
+ # Parse *^ notation
+ self.check("1.5×10^24", Real(1.5) * Integer(10) ** Integer(24))
+ self.check("1.5*^+24", Real("1.5e24"))
+ self.check("1.5*^-24", Real("1.5e-24"))
+ ## Don't accept *^ with spaces
+ # > 1.5 *^10
+ # "1.5*" cannot be followed by "^ 10"
+ self.invalid_error("1.5 *^10")
+ # "1.5*" cannot be followed by "^ 10"
+ self.invalid_error("1.5*^ 10")
def testString(self):
self.check(r'"abc"', String("abc"))
diff --git a/test/core/parser/test_parser.py b/test/core/parser/test_parser.py
index e697400f8..026599044 100644
--- a/test/core/parser/test_parser.py
+++ b/test/core/parser/test_parser.py
@@ -55,6 +55,7 @@ def test_minuslike(self):
self.check("- a / - b", "Times[-1, a, Power[Times[-1, b], -1]]")
self.check("a + b!", "Plus[a, Factorial[b]]")
self.check("!a!", "Not[Factorial[a]]")
+ self.check("a ;; b ;; c;; d", "(a;;b;;c) (1;;d)")
self.check("+ + a", "Plus[a]") # only one plus
@@ -74,6 +75,7 @@ def test_Subtract(self):
def test_nonassoc(self):
self.invalid_error("a ? b ? c")
+ self.invalid_error("a ~ b + c")
def test_Function(self):
self.check("a==b&", "Function[Equal[a, b]]")
@@ -118,6 +120,13 @@ def testNumber(self):
self.check("- 1", "-1")
self.check("- - 1", "Times[-1, -1]")
self.check("x=.01", "x = .01")
+ self.scan_error(r"\:000G")
+ self.scan_error(r"\:000")
+ self.scan_error(r"\009")
+ self.scan_error(r"\00")
+ self.scan_error(r"\.0G")
+ self.scan_error(r"\.0")
+ self.scan_error(r"\.0G")
def testNumberBase(self):
self.check_number("8^^23")
@@ -155,6 +164,7 @@ def testString(self):
self.check(r'"a\"b\\c"', String(r"a\"b\\c"))
self.incomplete_error(r'"\"')
self.invalid_error(r'\""')
+ self.invalid_error(r"abc \[fake]")
def testAccuracy(self):
self.scan_error("1.5``")
@@ -171,6 +181,8 @@ def testPrecision(self):
class GeneralTests(ParserTests):
def testCompound(self):
+ self.invalid_error("FullForm[Hold[; a]]")
+ self.invalid_error("FullForm[Hold[; a ;]]")
self.check(
"a ; {b}",
Node("CompoundExpression", Symbol("a"), Node("List", Symbol("b"))),
@@ -279,6 +291,8 @@ def testDerivative(self):
self.check("f'", "Derivative[1][f]")
self.check("f''", "Derivative[2][f]")
self.check("f' '", "Derivative[2][f]")
+ self.check("f '' ''", "Derivative[4][f]")
+ self.check("Derivative[x][4] '", "Derivative[1][Derivative[x][4]]")
def testPlus(self):
self.check("+1", Node("Plus", Number("1")))
@@ -830,6 +844,16 @@ def testBracketIncomplete(self):
self.incomplete_error("{x") # bktmcp
self.incomplete_error("f[[x") # bktmcp
+ def testBracketMismatch(self):
+ self.invalid_error("(x]") # sntxf
+ self.invalid_error("(x,)") # sntxf
+ self.invalid_error("{x]") # sntxf
+ self.invalid_error("f{x)") # sntxf
+ self.invalid_error("a[[x)]") # sntxf
+
+ self.invalid_error("x /: y , z") # sntxf
+ self.invalid_error("a :: 1") # sntxf
+
def testBracketIncompleteInvalid(self):
self.invalid_error("(x,")
self.incomplete_error("(x")
diff --git a/test/core/test_atoms.py b/test/core/test_atoms.py
index 7dbd155d0..66f68d96a 100644
--- a/test/core/test_atoms.py
+++ b/test/core/test_atoms.py
@@ -134,7 +134,7 @@ def test_Integer():
def test_MachineReal():
check_group(MachineReal(5), MachineReal(3.5), Integer(1.00001))
# MachineReal0 should be predefined; `int` and float arguments are allowed
- # `int` arguemnts are converted to float.
+ # `int` arguments are converted to float.
check_object_uniqueness(
MachineReal, [0.0], MachineReal0, MachineReal(0), MachineReal(0.0)
)
diff --git a/test/core/test_expression.py b/test/core/test_expression.py
index 3878de520..62676a23c 100644
--- a/test/core/test_expression.py
+++ b/test/core/test_expression.py
@@ -4,7 +4,7 @@
import pytest
-from mathics.builtin.base import check_requires_list
+from mathics.core.builtin import check_requires_list
from mathics.core.expression import Expression
from mathics.core.symbols import Symbol, SymbolPlus, SymbolTimes
diff --git a/test/core/test_expression_constructor.py b/test/core/test_expression_constructor.py
index 01381067a..eaf91906d 100644
--- a/test/core/test_expression_constructor.py
+++ b/test/core/test_expression_constructor.py
@@ -35,7 +35,7 @@ def attribute_check(e, varname: str):
e4 = Expression(
SymbolPlus,
*integer_ones,
- elements_properties=ElementsProperties(True, True, True)
+ elements_properties=ElementsProperties(True, True, True),
)
attribute_check(e4, "e4")
assert e1 == e4
diff --git a/test/core/test_rules.py b/test/core/test_rules.py
index 4c457c12f..280b5849d 100644
--- a/test/core/test_rules.py
+++ b/test/core/test_rules.py
@@ -28,7 +28,7 @@
because it ignores that the attribute is clean at the time in which the rule is applied.
-In Mathics, on the other hand, attributes are taken into accout just
+In Mathics, on the other hand, attributes are taken into account just
at the moment of the replacement, so the output of both expressions
are the opposite.
diff --git a/test/core/test_sympy_python_convert.py b/test/core/test_sympy_python_convert.py
index 1cfa65d6f..a6f4668e3 100644
--- a/test/core/test_sympy_python_convert.py
+++ b/test/core/test_sympy_python_convert.py
@@ -139,7 +139,7 @@ def testConvertedFunctions(self):
self.compare(
Expression(SymbolD, marg2, Symbol("Global`x")),
sympy.Derivative(sarg2, sympy.Symbol("_Mathics_User_Global`x")),
- **kwargs
+ **kwargs,
)
def testExpression(self):
diff --git a/test/data/input-bug.m b/test/data/input-bug.m
new file mode 100644
index 000000000..ecbfa826f
--- /dev/null
+++ b/test/data/input-bug.m
@@ -0,0 +1,4 @@
+(* For testing that $Input is set when Get[] is run.
+ See https://github.com/Mathics3/mathics-core/pull/1011
+ *)
+$Input
diff --git a/test/data/inputfile-bug.m b/test/data/inputfile-bug.m
new file mode 100644
index 000000000..dce5aca63
--- /dev/null
+++ b/test/data/inputfile-bug.m
@@ -0,0 +1,4 @@
+(* For testing that $InputFileName is set when Get[] is run.
+ See https://github.com/Mathics3/mathics-core/pull/1011
+ *)
+$InputFileName
diff --git a/test/doc/__init__.py b/test/doc/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/doc/test_common.py b/test/doc/test_common.py
new file mode 100644
index 000000000..8d7ff17e7
--- /dev/null
+++ b/test/doc/test_common.py
@@ -0,0 +1,219 @@
+"""
+Pytests for the documentation system. Basic functions and classes.
+"""
+import os.path as osp
+
+from mathics.core.evaluation import Message, Print
+from mathics.core.load_builtin import import_and_load_builtins
+from mathics.doc.common_doc import (
+ DocChapter,
+ DocPart,
+ DocSection,
+ Documentation,
+ MathicsMainDocumentation,
+)
+from mathics.doc.doc_entries import (
+ DocTest,
+ DocTests,
+ DocText,
+ DocumentationEntry,
+ parse_docstring_to_DocumentationEntry_items,
+)
+from mathics.settings import DOC_DIR
+
+DOCTEST_ENTRY = """
+