Skip to content

Commit 4225c24

Browse files
committed
Adjust for Boxing ascii operators in strings...
and add more tests. Check more argument counts on some builtin functions.
1 parent 03dd535 commit 4225c24

File tree

11 files changed

+281
-234
lines changed

11 files changed

+281
-234
lines changed

mathics/builtin/forms/data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ class StringForm(FormBaseClass):
699699
}
700700
summary_text = "format a string from a template and a list of parameters"
701701

702-
def eval_makeboxes(self, s, args, form, evaluation):
702+
def eval_makeboxes(self, s: String, args, form, evaluation: Evaluation):
703703
"""MakeBoxes[StringForm[s_String, args___],
704704
form:StandardForm|TraditionalForm]"""
705705
try:

mathics/builtin/string/characters.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class Characters(Builtin):
2828
"""
2929

3030
attributes = A_LISTABLE | A_PROTECTED
31+
eval_error = Builtin.generic_argument_error
32+
expected_args = 1
3133
summary_text = "list the characters in a string"
3234

3335
def eval(self, string: String, evaluation: Evaluation) -> ListExpression:

mathics/builtin/string/charcodes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ class ToCharacterCode(Builtin):
6565
= -Graphics-
6666
"""
6767

68+
eval_error = Builtin.generic_argument_error
69+
expected_args = (1, 2)
6870
summary_text = "convert a string to a list of character codes"
6971

7072
def _encode(self, string, encoding, evaluation: Evaluation):

mathics/builtin/string/operations.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,8 @@ class StringLength(Builtin):
360360
"""
361361

362362
attributes = A_LISTABLE | A_PROTECTED
363+
eval_error = Builtin.generic_argument_error
364+
expected_args = 1
363365

364366
summary_text = "length of a string (in Unicode characters)"
365367

mathics/eval/strings.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import re
66

7+
from mathics_scanner.characters import replace_box_unicode_with_ascii
8+
79
from mathics.builtin.box.layout import RowBox
810
from mathics.core.atoms import Integer, Integer0, Integer1, Integer3, String
911
from mathics.core.convert.expression import to_mathics_list
@@ -155,7 +157,7 @@ def safe_backquotes(string: str):
155157
return string
156158

157159

158-
def eval_StringForm_MakeBoxes(strform, items, form, evaluation):
160+
def eval_StringForm_MakeBoxes(strform: String, items, form, evaluation: Evaluation):
159161
"""MakeBoxes[StringForm[s_String, items___], form_]"""
160162

161163
if not isinstance(strform, String):
@@ -164,10 +166,13 @@ def eval_StringForm_MakeBoxes(strform, items, form, evaluation):
164166
items = [format_element(item, evaluation, form) for item in items]
165167

166168
curr_indx = 0
167-
strform_str = safe_backquotes(strform.value)
169+
strform_str = safe_backquotes(replace_box_unicode_with_ascii(strform.value))
168170

169171
parts = strform_str.split("`")
170-
parts = [part.replace("\\[RawBackquote]", "`") for part in parts]
172+
173+
# Rocky: This looks like a hack to me: is it needed?
174+
parts = [part.replace(r"\[RawBackquote]", "`") for part in parts]
175+
171176
result = [String(parts[0])]
172177
if len(parts) <= 1:
173178
return result[0]

mathics/format/form/outputform.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import re
99
from typing import Callable, Dict, List, Union
1010

11+
from mathics_scanner.characters import replace_box_unicode_with_ascii
12+
1113
from mathics.core.atoms import (
1214
Integer,
1315
Integer0,
@@ -861,8 +863,9 @@ def stringform_render_output_form(
861863
items = [render_output_form(item, evaluation, **kwargs) for item in items]
862864

863865
curr_indx = 0
864-
strform_str = safe_backquotes(strform.value)
866+
strform_str = safe_backquotes(replace_box_unicode_with_ascii(strform.value))
865867
parts = strform_str.split("`")
868+
# Rocky: This looks like a hack to me: is it needed?
866869
parts = [part.replace("\\[RawBackquote]", "`") for part in parts]
867870
result = [parts[0]]
868871
if len(parts) <= 1:
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""
2+
Test builtin function Characters[]
3+
"""
4+
5+
import time
6+
from test.helper import check_evaluation, evaluate
7+
from typing import Optional
8+
9+
import pytest
10+
11+
from mathics.core.atoms import String
12+
from mathics.core.list import ListExpression
13+
from mathics.session import MathicsSession
14+
15+
# Set up a Mathics session with definitions.
16+
# For consistency set the character encoding ASCII which is
17+
# the lowest common denominator available on all systems.
18+
session = MathicsSession(character_encoding="ASCII")
19+
20+
21+
def check_characters_evaluation(
22+
str_expr: str,
23+
expected: ListExpression,
24+
failure_message: Optional[str] = "",
25+
):
26+
"""
27+
Helper function to test Mathics expression against
28+
its results.
29+
30+
Compares the expressions represented by ``str_expr`` and ``str_expected`` by
31+
evaluating the first, and optionally, the second. If omitted, `str_expected`
32+
is assumed to be `"Null"`.
33+
34+
str_expr: The expression to be tested. If its value is ``None``, the session is
35+
reset.
36+
At the beginning of each set of pytests, it is important to call
37+
``check_evaluation(None)`` to avoid that definitions introduced by
38+
other tests affect the results.
39+
40+
str_expected: The expected result. The value ``None`` is equivalent to ``"Null"``.
41+
42+
failure_message: message shown in case of failure. Use "" for no failure message.
43+
"""
44+
result = evaluate(str_expr)
45+
46+
print(time.asctime())
47+
if failure_message:
48+
print(f"got: \n{result}\nexpect:\n{expected}\n -- {failure_message}")
49+
assert result == expected, failure_message
50+
else:
51+
print(f"got: \n{result}\nexpect:\n{expected}\n --")
52+
assert result == expected
53+
54+
55+
@pytest.mark.parametrize(
56+
("str_expr", "msgs", "str_expected", "fail_msg"),
57+
[
58+
(
59+
r'Characters["\\\` "]',
60+
None,
61+
ListExpression(String("\\"), String(r"\`"), String(" ")),
62+
"Characters[] with an escape sequence that should be treated as one character",
63+
),
64+
],
65+
)
66+
def test_characters_with_escape_sequence(str_expr, msgs, str_expected, fail_msg):
67+
check_characters_evaluation(
68+
str_expr,
69+
str_expected,
70+
failure_message=fail_msg,
71+
)
72+
73+
74+
@pytest.mark.parametrize(
75+
("str_expr", "msgs", "str_expected", "fail_msg"),
76+
[
77+
(
78+
"Characters[]",
79+
["Characters called with 0 arguments; 1 argument is expected."],
80+
"Characters[]",
81+
"Characters argument checking",
82+
),
83+
],
84+
)
85+
def test_characters(str_expr, msgs, str_expected, fail_msg):
86+
""" """
87+
check_evaluation(
88+
str_expr,
89+
str_expected,
90+
to_string_expr=True,
91+
to_string_expected=True,
92+
hold_expected=True,
93+
failure_message=fail_msg,
94+
expected_messages=msgs,
95+
)
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Unit tests from mathics.builtin.string.
4+
"""
5+
6+
from test.helper import check_evaluation
7+
8+
import pytest
9+
10+
11+
@pytest.mark.parametrize(
12+
("str_expr", "msgs", "str_expected", "fail_msg"),
13+
[
14+
('ToCharacterCode[{"ab"}]', None, "{{97, 98}}", None),
15+
(r'ToCharacterCode[{"\(A\)"}]', None, "{{63433, 65, 63424}}", None),
16+
(
17+
'ToCharacterCode[{{"ab"}}]',
18+
(
19+
"String or list of strings expected at position 1 in ToCharacterCode[{{ab}}].",
20+
),
21+
"ToCharacterCode[{{ab}}]",
22+
None,
23+
),
24+
(
25+
"ToCharacterCode[x]",
26+
(
27+
"String or list of strings expected at position 1 in ToCharacterCode[x].",
28+
),
29+
"ToCharacterCode[x]",
30+
None,
31+
),
32+
('ToCharacterCode[""]', None, "{}", None),
33+
(
34+
"#1 == ToCharacterCode[FromCharacterCode[#1]] & [RandomInteger[{0, 65535}, 100]]",
35+
None,
36+
"True",
37+
None,
38+
),
39+
(
40+
r"ToCharacterCode[]",
41+
["ToCharacterCode called with 0 arguments; 1 or 2 arguments are expected."],
42+
"ToCharacterCode[]",
43+
"ToCharacterCode argument checking",
44+
),
45+
],
46+
)
47+
def test_to_character_code(str_expr, msgs, str_expected, fail_msg):
48+
""" """
49+
check_evaluation(
50+
str_expr,
51+
str_expected,
52+
to_string_expr=True,
53+
to_string_expected=True,
54+
hold_expected=True,
55+
failure_message=fail_msg,
56+
expected_messages=msgs,
57+
)
58+
59+
60+
@pytest.mark.parametrize(
61+
("str_expr", "msgs", "str_expected", "fail_msg"),
62+
[
63+
("FromCharacterCode[{}] // InputForm", None, '""', None),
64+
(
65+
"FromCharacterCode[65536]",
66+
(
67+
"A character code, which should be a non-negative integer less than 65536, is expected at position 1 in {65536}.",
68+
),
69+
"FromCharacterCode[65536]",
70+
None,
71+
),
72+
(
73+
"FromCharacterCode[-1]",
74+
(
75+
"Non-negative machine-sized integer expected at position 1 in FromCharacterCode[-1].",
76+
),
77+
"FromCharacterCode[-1]",
78+
None,
79+
),
80+
(
81+
"FromCharacterCode[444444444444444444444444444444444444]",
82+
(
83+
"Non-negative machine-sized integer expected at position 1 in FromCharacterCode[444444444444444444444444444444444444].",
84+
),
85+
"FromCharacterCode[444444444444444444444444444444444444]",
86+
None,
87+
),
88+
(
89+
"FromCharacterCode[{100, 101, -1}]",
90+
(
91+
"A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, -1}.",
92+
),
93+
"FromCharacterCode[{100, 101, -1}]",
94+
None,
95+
),
96+
(
97+
"FromCharacterCode[{100, 101, 65536}]",
98+
(
99+
"A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, 65536}.",
100+
),
101+
"FromCharacterCode[{100, 101, 65536}]",
102+
None,
103+
),
104+
(
105+
"FromCharacterCode[{100, 101, x}]",
106+
(
107+
"A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, x}.",
108+
),
109+
"FromCharacterCode[{100, 101, x}]",
110+
None,
111+
),
112+
(
113+
"FromCharacterCode[{100, {101}}]",
114+
(
115+
"A character code, which should be a non-negative integer less than 65536, is expected at position 2 in {100, {101}}.",
116+
),
117+
"FromCharacterCode[{100, {101}}]",
118+
None,
119+
),
120+
(
121+
"FromCharacterCode[{{97, 98, 99}, {100, 101, x}}]",
122+
(
123+
"A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {100, 101, x}.",
124+
),
125+
"FromCharacterCode[{{97, 98, 99}, {100, 101, x}}]",
126+
None,
127+
),
128+
(
129+
"FromCharacterCode[{{97, 98, x}, {100, 101, x}}]",
130+
(
131+
"A character code, which should be a non-negative integer less than 65536, is expected at position 3 in {97, 98, x}.",
132+
),
133+
"FromCharacterCode[{{97, 98, x}, {100, 101, x}}]",
134+
None,
135+
),
136+
],
137+
)
138+
def test_from_character_code(str_expr, msgs, str_expected, fail_msg):
139+
""" """
140+
check_evaluation(
141+
str_expr,
142+
str_expected,
143+
to_string_expr=True,
144+
to_string_expected=True,
145+
hold_expected=True,
146+
failure_message=fail_msg,
147+
expected_messages=msgs,
148+
)

test/builtin/strings/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)