Skip to content

Commit 5cbd6b8

Browse files
rockymmatera
andauthored
Revise ByteArray[]. Make it Atomic and conform to WMA more closely. (#1505)
This is work is in prepration for handling NumericArray which might be similar. --------- Co-authored-by: Juan Mauricio Matera <[email protected]>
1 parent 647fff8 commit 5cbd6b8

File tree

20 files changed

+380
-205
lines changed

20 files changed

+380
-205
lines changed
Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
# -*- coding: utf-8 -*-
22
"""
3-
Byte Arrays
3+
ByteArrays
44
"""
55

6-
from mathics.core.atoms import ByteArrayAtom, Integer, String
6+
from typing import Optional
7+
8+
from mathics.core.atoms import ByteArray, Integer, String
79
from mathics.core.builtin import Builtin
810
from mathics.core.convert.expression import to_mathics_list
9-
from mathics.core.expression import Expression
10-
from mathics.core.systemsymbols import SymbolByteArray, SymbolFailed
11+
from mathics.core.evaluation import Evaluation
12+
from mathics.core.list import ListExpression
1113

1214

13-
class ByteArray(Builtin):
15+
class ByteArray_(Builtin):
1416
r"""
1517
<url>:WMA link:
1618
https://reference.wolfram.com/language/ref/ByteArray.html</url>
@@ -35,44 +37,55 @@ class ByteArray(Builtin):
3537
>> ByteArray["ARkD"]
3638
= ByteArray[<3>]
3739
>> B=ByteArray["asy"]
38-
: The first argument in Bytearray[asy] should be a B64 encoded string or a vector of integers.
39-
= $Failed
40+
: The argument at position 1 in ByteArray[asy] should be a vector of unsigned byte values or a Base64-encoded string.
41+
= ByteArray[asy]
42+
43+
A 'ByteArray" is a kind of Atom:
44+
45+
>> AtomQ[ByteArray[{4, 2}]]
46+
= True
4047
"""
4148

4249
messages = {
43-
"aotd": "Elements in `1` are inconsistent with type Byte",
44-
"lend": "The first argument in Bytearray[`1`] should "
45-
+ "be a B64 encoded string or a vector of integers.",
50+
"batd": "Elements in `1` are not unsigned byte values.",
51+
"lend": (
52+
"The argument at position 1 in ByteArray[`1`] should "
53+
"be a vector of unsigned byte values or a Base64-encoded string."
54+
),
4655
}
56+
57+
name = "ByteArray"
4758
summary_text = "array of bytes"
4859

49-
def eval_str(self, string, evaluation):
60+
def eval_str(self, string, evaluation: Evaluation) -> Optional[ByteArray]:
5061
"ByteArray[string_String]"
5162
try:
52-
atom = ByteArrayAtom(string.value)
53-
except Exception:
63+
atom = ByteArray(string.value)
64+
except TypeError:
5465
evaluation.message("ByteArray", "lend", string)
55-
return SymbolFailed
56-
return Expression(SymbolByteArray, atom)
66+
return None
67+
return atom
5768

58-
def eval_to_str(self, baa, evaluation):
59-
"ToString[ByteArray[baa_ByteArrayAtom]]"
69+
def eval_to_str(self, baa, evaluation: Evaluation):
70+
"ToString[baa_ByteArray]"
6071
return String(f"ByteArray[<{len(baa.value)}>]")
6172

62-
def eval_normal(self, baa, evaluation):
63-
"System`Normal[ByteArray[baa_ByteArrayAtom]]"
73+
def eval_normal(self, baa, evaluation: Evaluation):
74+
"System`Normal[baa_ByteArray]"
6475
return to_mathics_list(*baa.value, elements_conversion_fn=Integer)
6576

66-
def eval_list(self, values, evaluation):
67-
"ByteArray[values_List]"
68-
if not values.has_form("List", None):
69-
return
77+
def eval_list(self, values, evaluation) -> Optional[ByteArray]:
78+
"ByteArray[values_]"
79+
if not isinstance(values, ListExpression):
80+
evaluation.message("ByteArray", "lend", values)
81+
return None
82+
7083
try:
71-
ba = bytearray([b.get_int_value() for b in values.elements])
84+
ba = ByteArray(bytearray([b.value for b in values.elements]))
7285
except Exception:
73-
evaluation.message("ByteArray", "aotd", values)
74-
return
75-
return Expression(SymbolByteArray, ByteArrayAtom(ba))
86+
evaluation.message("ByteArray", "batd", values)
87+
return None
88+
return ba
7689

7790

7891
# TODO: BaseEncode, BaseDecode, ByteArrayQ, ByteArrayToString, StringToByteArray, ImportByteArray, ExportByteArray

mathics/builtin/exp_structure/size_and_sig.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import platform
66
import zlib
77

8-
from mathics.core.atoms import ByteArrayAtom, Integer, String
8+
from mathics.core.atoms import ByteArray, Integer, String
99
from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED
1010
from mathics.core.builtin import Builtin
1111
from mathics.core.evaluation import Evaluation
@@ -132,7 +132,7 @@ def compute(user_hash, py_hashtype, py_format):
132132
if py_format == "DecimalString":
133133
return String(str(res))
134134
elif py_format == "ByteArray":
135-
return Expression(SymbolByteArray, ByteArrayAtom(res))
135+
return Expression(SymbolByteArray, ByteArray(res))
136136
return Integer(res)
137137

138138
def eval(self, expr, hashtype: String, outformat: String, evaluation: Evaluation):

mathics/builtin/files_io/importexport.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from urllib.error import HTTPError, URLError
2727

2828
from mathics.builtin.pymimesniffer import magic
29-
from mathics.core.atoms import ByteArrayAtom
29+
from mathics.core.atoms import ByteArray
3030
from mathics.core.attributes import A_NO_ATTRIBUTES, A_PROTECTED, A_READ_PROTECTED
3131
from mathics.core.builtin import Builtin, Integer, Predefined, String, get_option
3232
from mathics.core.convert.expression import to_mathics_list
@@ -2005,7 +2005,7 @@ def eval_elements(self, expr, elems, evaluation: Evaluation, **options):
20052005
evaluation.predetermined_out = current_predetermined_out
20062006
return SymbolFailed
20072007
if is_binary:
2008-
res = Expression(SymbolByteArray, ByteArrayAtom(res))
2008+
res = Expression(SymbolByteArray, ByteArray(res))
20092009
else:
20102010
res = String(str(res))
20112011
elif function_channels == ListExpression(String("Streams")):
@@ -2030,9 +2030,7 @@ def eval_elements(self, expr, elems, evaluation: Evaluation, **options):
20302030
res = exporter_function.evaluate(evaluation)
20312031
if res is SymbolNull:
20322032
if is_binary:
2033-
res = Expression(
2034-
SymbolByteArray, ByteArrayAtom(pystream.getvalue())
2035-
)
2033+
res = Expression(SymbolByteArray, ByteArray(pystream.getvalue()))
20362034
else:
20372035
res = String(str(pystream.getvalue()))
20382036
else:

mathics/builtin/list/constructing.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from typing import Optional, Tuple
1414

1515
from mathics.builtin.box.layout import RowBox
16-
from mathics.core.atoms import Integer, Integer1, is_integer_rational_or_real
16+
from mathics.core.atoms import ByteArray, Integer, Integer1, is_integer_rational_or_real
1717
from mathics.core.attributes import A_HOLD_FIRST, A_LISTABLE, A_LOCKED, A_PROTECTED
1818
from mathics.core.builtin import BasePattern, Builtin, IterationFunction
1919
from mathics.core.convert.expression import to_expression
@@ -198,6 +198,8 @@ class Normal(Builtin):
198198
def eval_general(self, expr: Expression, evaluation: Evaluation):
199199
"Normal[expr_]"
200200
if isinstance(expr, Atom):
201+
if isinstance(expr, ByteArray):
202+
return ListExpression(*expr.items)
201203
return expr
202204
if expr.has_form("RootSum", 2):
203205
return from_sympy(expr.to_sympy().doit(roots=True))

mathics/builtin/list/eol.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,16 @@
1010
from itertools import chain
1111

1212
from mathics.builtin.box.layout import RowBox
13-
from mathics.core.atoms import Integer, Integer0, Integer1, Integer3, Integer4, String
13+
from mathics.core.atoms import (
14+
ByteArray,
15+
Integer,
16+
Integer0,
17+
Integer1,
18+
Integer2,
19+
Integer3,
20+
Integer4,
21+
String,
22+
)
1423
from mathics.core.attributes import (
1524
A_HOLD_FIRST,
1625
A_HOLD_REST,
@@ -40,8 +49,10 @@
4049
SymbolByteArray,
4150
SymbolDrop,
4251
SymbolFailed,
52+
SymbolFirst,
4353
SymbolInfinity,
4454
SymbolKey,
55+
SymbolLast,
4556
SymbolMakeBoxes,
4657
SymbolMissing,
4758
SymbolSelect,
@@ -675,7 +686,6 @@ class First(Builtin):
675686

676687
attributes = A_HOLD_REST | A_PROTECTED
677688
messages = {
678-
"argt": "First called with `1` arguments; 1 or 2 arguments are expected.",
679689
"nofirst": "`1` has zero length and no first element.",
680690
}
681691
summary_text = "first element of a list or expression"
@@ -685,14 +695,23 @@ def eval(self, expr, evaluation: Evaluation, expression: Expression):
685695
"expression: First[expr__]"
686696

687697
if isinstance(expr, Atom):
688-
evaluation.message("First", "normal", Integer1, expression)
689-
return
690-
expr_len = len(expr.elements)
698+
if not hasattr(expr, "items"):
699+
evaluation.message("First", "normal", Integer1, expression)
700+
return
701+
expr_len = len(expr.items)
702+
else:
703+
expr_len = len(expr.elements)
691704
if expr_len == 0:
692705
evaluation.message("First", "nofirst", expr)
693706
return
707+
708+
if isinstance(expr, ByteArray):
709+
return expr.items[0]
710+
694711
if expr_len > 2 and expr.head is SymbolSequence:
695-
evaluation.message("First", "argt", expr_len)
712+
evaluation.message(
713+
"First", "argt", SymbolFirst, Integer(expr_len), Integer1, Integer2
714+
)
696715
return
697716

698717
first_elem = expr.elements[0]
@@ -949,7 +968,6 @@ class Last(Builtin):
949968

950969
attributes = A_HOLD_REST | A_PROTECTED
951970
messages = {
952-
"argt": "Last called with `1` arguments; 1 or 2 arguments are expected.",
953971
"nolast": "`1` has zero length and no last element.",
954972
}
955973
summary_text = "last element of a list or expression"
@@ -959,14 +977,24 @@ def eval(self, expression: Expression, expr, evaluation: Evaluation):
959977
"expression: Last[expr__]"
960978

961979
if isinstance(expr, Atom):
962-
evaluation.message("Last", "normal", Integer1, expression)
963-
return
964-
expr_len = len(expr.elements)
980+
if not hasattr(expr, "items"):
981+
evaluation.message("First", "normal", Integer1, expression)
982+
return
983+
expr_len = len(expr.items)
984+
else:
985+
expr_len = len(expr.elements)
965986
if expr_len == 0:
966987
evaluation.message("Last", "nolast", expr)
967988
return
989+
990+
if isinstance(expr, ByteArray):
991+
# ByteArray or NumericArray, ...
992+
return expr.items[-1]
993+
968994
if expr_len > 2 and expr.head is SymbolSequence:
969-
evaluation.message("Last", "argt", expr_len)
995+
evaluation.message(
996+
"Last", "argt", SymbolLast, Integer(expr_len), Integer1, Integer2
997+
)
970998
return
971999

9721000
return expr.elements[-1]
@@ -1172,19 +1200,18 @@ def eval(self, list, i, evaluation):
11721200
idx = idx.value
11731201
if idx == 0:
11741202
return SymbolByteArray
1175-
data = list.elements[0].value
1176-
lendata = len(data)
1203+
n = len(list.items)
11771204
if idx < 0:
1178-
idx = data - idx
1205+
idx = n - idx
11791206
if idx < 0:
11801207
evaluation.message("Part", "partw", i, list)
11811208
return
11821209
else:
11831210
idx = idx - 1
1184-
if idx > lendata:
1211+
if idx > n:
11851212
evaluation.message("Part", "partw", i, list)
11861213
return
1187-
return Integer(data[idx])
1214+
return list.items[idx]
11881215
if idx is Symbol("System`All"):
11891216
return list
11901217
# TODO: handling ranges and lists...

mathics/builtin/statistics/orderstats.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,32 @@ class ReverseSort(Builtin):
300300
}
301301

302302

303+
# FIXME: there might be a bug in sorting...
304+
#
305+
# Sort[{
306+
# "a","b", 1,
307+
# ByteArray[{1,2,4,1}],
308+
# 2, 1.2, I, 2I-3, A,
309+
# a+b, a*b, a+1, a*2, b^3, 2/3,
310+
# A[x], F[2], F[x], F[x_], F[x___], F[x,t], F[x__],
311+
# Condition[A,b>2], Pattern[expr, A]
312+
# }]
313+
#
314+
# should be:
315+
#
316+
# {-3 + 2*I, I, 2/3, 1, 1.2, 2,
317+
# "a", "b", 2*a,
318+
# 1 + a, A, a*b, b^3, a + b,
319+
# A[x], A /; b > 2,
320+
# F[2], F[x], F[x_], F[x___], F[x__], F[x, t],
321+
# ByteArray["AQIEAQ=="], expr:A}
322+
#
323+
# But this is too complicated a case to run as a test. It needs
324+
# to be isolated. Break this down to smaller pieces,
325+
# and also use Order[] to check smaller components.
326+
# The problem might also be in boxing-order output.
327+
328+
303329
class Sort(Builtin):
304330
"""
305331
<url>:WMA link:https://reference.wolfram.com/language/ref/Sort.html</url>
@@ -316,7 +342,7 @@ class Sort(Builtin):
316342
>> Sort[{4, 1.0, a, 3+I}]
317343
= {1., 3 + I, 4, a}
318344
319-
Sort uses 'OrderedQ' to determine ordering by default.
345+
Sort uses 'Order' to determine ordering by default.
320346
You can sort patterns according to their precedence using 'PatternsOrderedQ':
321347
>> Sort[{items___, item_, OptionsPattern[], item_symbol, item_?test}, PatternsOrderedQ]
322348
= {item_symbol, item_ ? test, item_, items___, OptionsPattern[]}

0 commit comments

Comments
 (0)