Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ jobs:
python -m pip install --upgrade pip
- name: Install Mathics3 with full Python dependencies
run: |
python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
# We can comment out after next Mathics-Scanner release
# python -m pip install Mathics-Scanner[full]
# python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
pip install -e .
remake -x develop-full
- name: Test Mathics3
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/plot-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ jobs:
run: |
python -m pip install --upgrade pip
# We can comment out after next Mathics-Scanner release
# python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
git clone --depth 1 https://github.com/Mathics3/mathics-scanner.git
# git clone --single-branch --branch operator-refactor-part1.5 https://github.com/Mathics3/mathics-scanner.git
cd mathics-scanner/
pip install -e .
cd ..
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/pyodide.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,7 @@ jobs:
pip install "setuptools>=70.0.0" PyYAML click packaging pytest

# We can comment out after next Mathics-Scanner release
# python -m pip install --no-build-isolation -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner
# pip install --no-build-isolation -e .
# cd ..
python -m pip install --no-build-isolation -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner

pip install --no-build-isolation -e .
make mathics/data/op-tables.json mathics/data/operator-tables.json
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu-bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ jobs:
python -m pip install --upgrade pip
python -m pip install pytest-benchmark
# We can comment out after next Mathics-Scanner release
python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
# python -m pip install Mathics-Scanner[full]
# python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
pip install -e .
remake -x develop
- name: Test Mathics
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu-cython.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
sudo apt-get update -qq && sudo apt-get install -qq liblapack-dev llvm-dev tesseract-ocr
python -m pip install --upgrade pip
# We can comment out after next Mathics-Scanner release
# python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
pip install -e .
cd ..

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Install Mathics3 with Python dependencies
run: |
# We can comment out after next Mathics-Scanner release
# python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
pip install -e .

# python -m pip install Mathics-Scanner[full]
Expand Down
55 changes: 40 additions & 15 deletions mathics/builtin/forms/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,23 +186,48 @@ def eval_mathml(self, expr, evaluation) -> Expression:

class InputForm(FormBaseClass):
r"""
<url>
:WMA link:
https://reference.wolfram.com/language/ref/InputForm.html</url>
<url>
:WMA link:
https://reference.wolfram.com/language/ref/InputForm.html</url>

<dl>
<dt>'InputForm'[$expr$]
<dd>displays $expr$ in an unambiguous form suitable for input.
</dl>
<dl>
<dt>'InputForm'[$expr$]
<dd>displays $expr$ in an unambiguous form suitable for input to Mathics3.
</dl>

>> InputForm[a + b * c]
= a + b*c
>> InputForm["A string"]
= "A string"
>> InputForm[f'[x]]
= Derivative[1][f][x]
>> InputForm[Derivative[1, 0][f][x]]
= Derivative[1, 0][f][x]
'InputForm' produces one-dimensional output that is suitable for input to Mathics3:

>> InputForm["A string"]
= "A string"

>> InputForm[f'[x]]
= Derivative[1][f][x]

>> InputForm[Derivative[1, 0][f][x]]
= Derivative[1, 0][f][x]

'InputForm' shows arithmetic expressions in traditional mathematical notation:

>> 2+F[x] // InputForm
= 2 + F[x]

Compare this to 'FullForm':

>> 2+F[x] // FullForm
= Plus[2, F[x]]

'InputForm' output can be altered via 'Format' assignment :

>> Format[Foo[x], InputForm] := Bar

>> Foo[x] // InputForm
= Bar

In contrast, 'FullForm' output is not altered via 'Format' assignment :
>> Format[Foo[x], InputForm] := Baz

>> Foo[x] // FullForm
= Foo[x]
"""

in_outputforms = True
Expand Down
3 changes: 3 additions & 0 deletions mathics/core/parser/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
nonassoc_binary_operators = OPERATOR_DATA["non-associative-binary-operators"]
operator_precedences = OPERATOR_DATA["operator-precedences"]
operator_to_amslatex = OPERATOR_DATA["operator-to-amslatex"]
operator_to_string = OPERATOR_DATA[
"operator-to_string"
] # FIXME: should be operator-to-string
postfix_operators = OPERATOR_DATA["postfix-operators"]
prefix_operators = OPERATOR_DATA["prefix-operators"]
right_binary_operators = OPERATOR_DATA["right-binary-operators"]
Expand Down
2 changes: 1 addition & 1 deletion mathics/form/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Function that generate $PrintForms
Module containing functions for rendering $PrintForms Forms
"""

from mathics.form.inputform import render_input_form
Expand Down
116 changes: 48 additions & 68 deletions mathics/form/inputform.py
Original file line number Diff line number Diff line change
@@ -1,72 +1,37 @@
"""
This module builts the string associated to the InputForm.

`InputForm` produces a textual output suitable for being parsed and directly
evaluated in Mathics CLI. Differently from `FullForm`, `InputForm`
show arithmetic expressions using Infix/Prefix/Postfix forms. Apart from that,
the apareance of the result is almost the same that produce `FullForm`.

On the other hand, internally, there are more differences. In the first place,
InputForm always produces a single `String` object, while `FullForm` produces
a nested `RowBox` structure.

```
In[1]:= 2+F[x] // FullForm // MakeBoxes // InputForm
Out[1]//InputForm=
TagBox[StyleBox[RowBox[{"Plus", "[", RowBox[{"2", ",", RowBox[{"F", "[", "x", "]"}]}], "]"}], ShowSpecialCharacters -> False, ShowStringCharacters -> True,
NumberMarks -> True], FullForm]

In[2]:= 2+F[x] // InputForm // MakeBoxes // InputForm
Out[2]//InputForm= InterpretationBox[StyleBox["2 + F[x]", ShowStringCharacters -> True, NumberMarks -> True], InputForm[2 + F[x]], Editable -> True, AutoDelete -> True]
```
In the case of `FullForm`, we get a `TagBox`, which ensures the content to be interpreted as a `FullForm` boxed expression. In the case of the `InputForm`, we get an `InterpretationBox`, which keeps the information about the original expression. But the main difference is inside the `StyleBox`: for `InputForm` we have a shallow `String object, while for `FullForm` we have a nested `RowBox` expression.


Another important difference between `FullForm` and `InputForm` is that `FullForm` does not take into account `FormatValues`, while `InputForm` does:

"""This module contains functions for turning Mathics3 expressions to
InputForm-formatted strings.

`InputForm` produces textual output suitable for being parsed and
evaluated in Mathics CLI.

Differently from `FullForm`, which produces a nested `RowBox`
expression, InputForm produces a single `String` (not boxed), which can be
parsed and interpreted as an expression in the Mathics3 interpreter.
```
In[3]:= Format[F[x_],InputForm]:="-inputform formatted "<>ToString[x]<>"F-"
In[4]:= Format[F[x_],FullForm]:="-fullform formatted "<>ToString[x]<>"F-"
`InputForm` is not affected by MakeBox assignment.

In[5]:= 3 F[r] //InputForm
Out[5]//InputForm= 3*"-inputform formatted rF-"
InputForm versus FullForm
--------------------------

In[6]:= 3 F[r] //FullForm
Out[6]//FullForm= Times[3, F[r]]
```
In contrast to `FullForm`, `InputForm` shows arithmetic expressions in
traditional mathematical notation. Apart from that and the allowance
of `InputForm` output to be altered via a `Format` assignment, the
appearance of the result is about the same as `FullForm`.

On the other hand, neither `InputForm` or `FulLForm` do not take into accout MakeBoxes rules: setting
Internally, `FullForm` produces `String` object, while `FullForm`
produces a nested `RowBox` structure.

```
In[7]:= MakeBoxes[InputForm[G[x_]],f_]:=RowBox[{"--mb if G--", MakeBoxes[InputForm[x],f]}]
In[8]:= MakeBoxes[FullForm[G[x_]],f_]:=RowBox[{"--mb ff G--", MakeBoxes[FullForm[x],f]}]
```

Evaluating `G[3 F[t]]` in any of these forms we get

```
In[9]:= G[3 F[t]]//InputForm
Out[9]//InputForm= G[3*"-inputform formatted tF-"]

In[10]:= G[3 F[t]]//FullForm
Out[10]//FullForm= G[Times[3, F[t]]]
```
In the first case, the `FormatValue` rule for `F` is applied but not the `MakeBoxes` rules for `G`.
`InputForm` conversion produces an `InterpretationBox` The
`InterpetationBox` preserves information about the original
expression. In contrast, `FullForm` output produces a `TagBox`. In
both cases, underneath the `InterpretationBox` or `Tagbox` is a
`StyleBox`.

"""

from typing import Callable, Dict, List, Optional, Tuple
from typing import Callable, Dict, Final, FrozenSet, List, Optional, Tuple

from mathics.core.atoms import Integer, String
from mathics.core.convert.op import operator_to_ascii, operator_to_unicode
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.parser.operators import OPERATOR_DATA
from mathics.core.parser.operators import OPERATOR_DATA, operator_to_string
from mathics.core.symbols import Atom, Symbol
from mathics.core.systemsymbols import (
SymbolBlank,
Expand All @@ -87,11 +52,13 @@
SymbolPrefix = Symbol("System`Prefix")


PRECEDENCES = OPERATOR_DATA.get("operator-precedences")
PRECEDENCE_DEFAULT = PRECEDENCES.get("FunctionApply")
PRECEDENCE_PLUS = PRECEDENCES.get("Plus")
PRECEDENCE_TIMES = PRECEDENCES.get("Times")
PRECEDENCE_POWER = PRECEDENCES.get("Power")
# Use 670 until BoxGroup precedence gets in.
PRECEDENCE_BOX_GROUP: Final[int] = 670 # box_operators["BoxGroup"]

PRECEDENCES: Final = OPERATOR_DATA.get("operator-precedences")
PRECEDENCE_PLUS: Final[int] = PRECEDENCES.get("Plus", 310)
PRECEDENCE_TIMES: Final[int] = PRECEDENCES.get("Times", 400)
PRECEDENCE_POWER: Final[int] = PRECEDENCES.get("Power", 590)

EXPR_TO_INPUTFORM_TEXT_MAP: Dict[str, Callable] = {}

Expand Down Expand Up @@ -119,12 +86,12 @@ def get_operator_str(head, evaluation, **kwargs) -> str:


def bracket(expr_str: str) -> str:
"""wrap with parenthesis"""
"""Wrap `expr_str` with square braces"""
return f"[{expr_str}]"


def parenthesize(expr_str: str) -> str:
"""wrap with parenthesis"""
"""Wrap `expr_str` with parenthesis"""
return f"({expr_str})"


Expand Down Expand Up @@ -233,24 +200,26 @@ def collect_in_pre_post_arguments(

head = expr.head
group = None
precedence = PRECEDENCE_DEFAULT
precedence = PRECEDENCE_BOX_GROUP
operands = list(target.elements)

# Just one parameter:
if len(elements) == 1:
operator_spec = render_input_form(head, evaluation, **kwargs)
if head is SymbolInfix:
operator_spec = [f"~{operator_spec}~"]
operator_spec = [
f"{operator_to_string['Infix']}{operator_spec}{operator_to_string['Infix']}"
]
elif head is SymbolPrefix:
operator_spec = f"{operator_spec}@"
operator_spec = f"{operator_spec}{operator_to_string['Prefix']}"
elif head is SymbolPostfix:
operator_spec = f"//{operator_spec}"
operator_spec = f"{operator_to_string['Postfix']}{operator_spec}"
return operands, operator_spec, precedence, group

# At least two parameters: get the operator spec.
ops = elements[1]
if head is SymbolInfix:
# This is not the WMA behaviour, but the Mathics current implementation requires it:
# This is not the WMA behaviour, but the Mathics3 current implementation requires it:
ops = ops.elements if ops.has_form("List", None) else (ops,)
operator_spec = [get_operator_str(op, evaluation, **kwargs) for op in ops]
else:
Expand All @@ -274,6 +243,17 @@ def collect_in_pre_post_arguments(
return operands, operator_spec, precedence, group


ARITHMETIC_OPERATOR_STRINGS: Final[FrozenSet[str]] = frozenset(
[
*operator_to_string["Divide"],
*operator_to_string["NonCommutativeMultiply"],
*operator_to_string["Power"],
*operator_to_string["Times"],
" ",
]
)


@register_inputform("System`Infix")
def _infix_expression_to_inputform_text(
expr: Expression, evaluation: Evaluation, **kwargs
Expand Down Expand Up @@ -313,7 +293,7 @@ def _infix_expression_to_inputform_text(
else:
num_ops = len(ops_lst)
curr_op = ops_lst[index % num_ops]
if curr_op not in ("*", "**", "/", "^", " "):
if curr_op not in ARITHMETIC_OPERATOR_STRINGS:
# In the tests, we add spaces just for + and -:
curr_op = f" {curr_op} "

Expand Down
4 changes: 2 additions & 2 deletions test/format/format_tests-WMA.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Tests for formatting according WMA.
#
# These tests are prepared to compare the output obtained using WL code in
# both WMA and Mathics interpreters:
# both WMA and Mathics3 interpreters:
# ./convert_yaml2json.py format_tests.yaml && $(WMAINTERPRETER) -f format_tests.m
#
# In this case, 'text' are certificated against the WMA result,
Expand All @@ -12,7 +12,7 @@
# Running the same code against `mathics` CLI, most of these tests fails because
# - `ToString` with `CharacterEncoding->"ASCII"` does not produce string
# representations of Boxes.
# - `OutputForm` in Mathics does not produce *pretty-print-like* output.
# - `OutputForm` in Mathics3 does not produce *pretty-print-like* output.
# And eventually, if we start to do it, probably we can also do it better,
# so we are not going to coincide neither.
# - Box constructions are still too diferent in both interpreters.
Expand Down
Loading