Skip to content

Commit

Permalink
MakeBoxes[Infix[...],form]-> Infix
Browse files Browse the repository at this point in the history
  • Loading branch information
mmatera committed Dec 26, 2022
1 parent 5e815b5 commit 9e286fd
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 185 deletions.
3 changes: 2 additions & 1 deletion mathics/builtin/colors/color_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

try:
import numpy
import PIL.Image as PILImage
import PIL.ImageOps

_enabled = True
Expand Down Expand Up @@ -377,7 +378,7 @@ def eval(self, image, n, prop, evaluation, options):
im = (
image.color_convert("RGB")
.pil()
.convert("P", palette=PIL.Image.ADAPTIVE, colors=256)
.convert("P", palette=PILImage.ADAPTIVE, colors=256)
)
pixels = numpy.array(list(im.getdata()))

Expand Down
15 changes: 8 additions & 7 deletions mathics/builtin/drawing/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@

import numpy
import PIL
import PIL.Image as PILImage
import PIL.ImageEnhance
import PIL.ImageFilter
import PIL.ImageOps
Expand Down Expand Up @@ -158,7 +159,7 @@ class ImageImport(_ImageBuiltin):

def eval(self, path: String, evaluation: Evaluation):
"""ImageImport[path_String]"""
pillow = PIL.Image.open(path.value)
pillow = PILImage.open(path.value)
pixels = numpy.asarray(pillow)
is_rgb = len(pixels.shape) >= 3 and pixels.shape[2] >= 3
options_from_exif = extract_exif(pillow, evaluation)
Expand Down Expand Up @@ -644,7 +645,7 @@ def eval(self, image, angle, evaluation: Evaluation):

def rotate(im):
return im.rotate(
180 * py_angle / math.pi, resample=PIL.Image.BICUBIC, expand=True
180 * py_angle / math.pi, resample=PILImage.BICUBIC, expand=True
)

return image.filter(rotate)
Expand Down Expand Up @@ -1308,7 +1309,7 @@ def eval(self, image, n: Integer, evaluation: Evaluation):
if converted is None:
return
pixels = pixels_as_ubyte(converted.pixels)
im = PIL.Image.fromarray(pixels).quantize(py_value)
im = PILImage.fromarray(pixels).quantize(py_value)
im = im.convert("RGB")
return Image(numpy.array(im), "RGB")

Expand Down Expand Up @@ -2038,7 +2039,7 @@ def atom_to_boxes(self, form, evaluation: Evaluation) -> ImageBox:
pillow = deepcopy(self.pillow)
else:
pixels_format = "RGBA" if len(shape) >= 3 and shape[2] == 4 else "RGB"
pillow = PIL.Image.fromarray(pixels, pixels_format)
pillow = PILImage.fromarray(pixels, pixels_format)

# if the image is very small, scale it up using nearest neighbour.
min_size = 128
Expand All @@ -2047,7 +2048,7 @@ def atom_to_boxes(self, form, evaluation: Evaluation) -> ImageBox:
scaled_width = int(scale * width)
scaled_height = int(scale * height)
pillow = pillow.resize(
(scaled_height, scaled_width), resample=PIL.Image.NEAREST
(scaled_height, scaled_width), resample=PILImage.NEAREST
)

with warnings.catch_warnings():
Expand Down Expand Up @@ -2105,7 +2106,7 @@ def filter(self, f): # apply PIL filters component-wise
pixels = self.pixels
n = pixels.shape[2]
channels = [
f(PIL.Image.fromarray(c, "L")) for c in (pixels[:, :, i] for i in range(n))
f(PILImage.fromarray(c, "L")) for c in (pixels[:, :, i] for i in range(n))
]
return Image(numpy.dstack(channels), self.color_space)

Expand Down Expand Up @@ -2171,7 +2172,7 @@ def pil(self):
else:
raise NotImplementedError

return PIL.Image.fromarray(pixels, mode)
return PILImage.fromarray(pixels, mode)

def options(self):
return ListExpression(
Expand Down
193 changes: 186 additions & 7 deletions mathics/builtin/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,32 @@
we can use ``Row``
"""

from typing import Optional, Union

from mathics.builtin.base import BinaryOperator, Builtin, Operator
from mathics.builtin.box.layout import GridBox, RowBox, to_boxes
from mathics.builtin.lists import list_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.atoms import Integer, Integer1, Real, String
from mathics.core.convert.op import operator_to_ascii, operator_to_unicode
from mathics.core.element import BaseElement
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.symbols import Symbol
from mathics.core.systemsymbols import SymbolMakeBoxes
from mathics.eval.makeboxes import format_element

from mathics.core.symbols import Atom, Symbol
from mathics.core.systemsymbols import (
SymbolFullForm,
SymbolInfix,
SymbolInputForm,
SymbolLeft,
SymbolMakeBoxes,
SymbolNone,
SymbolOutputForm,
SymbolRight,
)
from mathics.eval.makeboxes import format_element, make_boxes_infix

SymbolNonAssociative = Symbol("System`NonAssociative")
SymbolSubscriptBox = Symbol("System`SubscriptBox")


Expand Down Expand Up @@ -145,11 +157,178 @@ class Infix(Builtin):
"""

messages = {
"normal": "Nonatomic expression expected at position `1`",
"argb": "Infix called with `1` arguments; between 1 and 4 arguments are expected.",
"group": "Infix::group: Grouping specification `1` is not NonAssociative, None, Left, or Right.",
"intm": "Machine-sized integer expected at position 3 in `1`",
"normal": "Nonatomic expression expected at position `1`",
}
summary_text = "infix form"

# the right rule should be
# mbexpression:MakeBoxes[Infix[___], form]

def eval_infix_1(self, expr: Expression, form: Symbol, evaluation):
"""MakeBoxes[Infix[expr_],
form:StandardForm|TraditionalForm|OutputForm|InputForm]"""
return self.do_eval_infix(expr, String("~"), form, evaluation)

def eval_infix_2(
self, expr: Expression, operator: BaseElement, form: Symbol, evaluation
):
"""MakeBoxes[Infix[expr_, operator_],
form:StandardForm|TraditionalForm|OutputForm|InputForm]"""
return self.do_eval_infix(expr, operator, form, evaluation)

def eval_infix_3(
self,
expr: Expression,
operator: BaseElement,
precedence: BaseElement,
form: Symbol,
evaluation,
):
"""MakeBoxes[Infix[expr_, operator_, precedence_],
form:StandardForm|TraditionalForm|OutputForm|InputForm]"""

if not isinstance(precedence, Integer):
evaluation.message(
"Infix",
"intm",
Expression(
SymbolFullForm, Expression(SymbolInfix, expr, operator, precedence)
),
)
return

return self.do_eval_infix(expr, operator, form, evaluation, precedence.value)

def eval_infix_4(
self,
expr: Expression,
operator: BaseElement,
precedence: BaseElement,
grouping: BaseElement,
form: Symbol,
evaluation,
):
"""MakeBoxes[Infix[expr_, operator_, precedence_, grouping_],
form:StandardForm|TraditionalForm|OutputForm|InputForm]"""
if not isinstance(precedence, Integer):
fullform_expr = Expression(
SymbolFullForm,
Expression(SymbolInfix, expr, operator, precedence, grouping),
)
evaluation.message(
"Infix",
"intm",
Expression(
SymbolFullForm,
Expression(SymbolInfix, expr, operator, precedence, grouping),
),
)
return fullform_expr

if grouping is SymbolNone:
return self.do_eval_infix(
expr, operator, form, evaluation, precedence.value
)
if grouping in (SymbolNonAssociative, SymbolLeft, SymbolRight):
return self.do_eval_infix(
expr, operator, form, evaluation, precedence.value, grouping.get_name()
)

evaluation.message("Infix", "argb", grouping)
return Expression(
SymbolFullForm,
Expression(SymbolInfix, expr, operator, precedence, grouping),
)

def eval_infix_default(self, parms, form: Symbol, evaluation):
"""MakeBoxes[Infix[parms___],
form:StandardForm|TraditionalForm|OutputForm|InputForm]"""
evaluation.message("Infix", "argb", Integer(len(parms.get_sequence())))
return None

def do_eval_infix(
self,
expr: Expression,
operator: BaseElement,
form: Symbol,
evaluation,
precedence_value: Optional[int] = None,
grouping: Optional[str] = None,
):
"""Implements MakeBoxes[Infix[...]]"""

## FIXME: this should go into a some formatter.
def format_operator(operator) -> Union[String, BaseElement]:
"""
Format infix operator `operator`. To do this outside parameter form is used.
Sometimes no changes are made and operator is returned unchanged.
This function probably should be rewritten be more scalable across other forms
and moved to a module that contiaing similar formatting routines.
"""
if not isinstance(operator, String):
return MakeBoxes(operator, form)

op_str = operator.value

# FIXME: performing a check using the operator symbol representation feels a bit
# fragile. The operator name seems more straightforward and more robust.
if form == SymbolInputForm and op_str in ["*", "^", " "]:
return operator
elif (
form in (SymbolInputForm, SymbolOutputForm)
and not op_str.startswith(" ")
and not op_str.endswith(" ")
):
# FIXME: Again, testing on specific forms is fragile and not scalable.
op = String(" " + op_str + " ")
return op
return operator

if isinstance(expr, Atom):
evaluation.message("Infix", "normal", Integer1)
return None

elements = expr.elements
if len(elements) > 1:
if operator.has_form("List", len(elements) - 1):
operator = [format_operator(op) for op in operator.elements]
return make_boxes_infix(
elements, operator, precedence_value, grouping, form
)
else:
encoding_rule = evaluation.definitions.get_ownvalue(
"$CharacterEncoding"
)
encoding = (
"UTF8" if encoding_rule is None else encoding_rule.replace.value
)
op_str = (
operator.value
if isinstance(operator, String)
else operator.short_name
)
if encoding == "ASCII":
operator = format_operator(
String(operator_to_ascii.get(op_str, op_str))
)
else:
operator = format_operator(
String(operator_to_unicode.get(op_str, op_str))
)

return make_boxes_infix(
elements, operator, precedence_value, grouping, form
)

elif len(elements) == 1:
return MakeBoxes(elements[0], form)
else:
return MakeBoxes(expr, form)


class Left(Builtin):
"""
Expand Down
Loading

0 comments on commit 9e286fd

Please sign in to comment.