Skip to content

Commit

Permalink
Add BinaryReadList ..
Browse files Browse the repository at this point in the history
and split off eval_BinaryReadList. Put this evaluation function in
mathics.eval.binary.io.
  • Loading branch information
rocky committed Feb 16, 2025
1 parent 4643026 commit 1b835e3
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 34 deletions.
3 changes: 2 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ CHANGES
New Builtins
++++++++++++

* ``$SessionID
* ``$SessionID``
* ``BinaryReadList``

8.0.1
-----
Expand Down
1 change: 1 addition & 0 deletions SYMBOLS_MANIFEST.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ System`BezierFunction
System`Binarize
System`BinaryImageQ
System`BinaryRead
System`BinaryReadList
System`BinaryWrite
System`Binomial
System`BitLength
Expand Down
2 changes: 1 addition & 1 deletion mathics/autoload/formats/CSV/Import.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
};

ImportCSV[stream_InputStream, OptionsPattern[]]:=
Module[{data, grid, sep = OptionValue["FieldSeparators"]},
Module[{data, grid, sep = OptionValue["FieldSeparators"]},
data = StringSplit[#, sep]& /@ ReadList[stream, String];
grid = Grid[data];
{
Expand Down
128 changes: 96 additions & 32 deletions mathics/builtin/binary/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,28 @@
import math
import struct
from itertools import chain
from typing import Optional, Tuple

import mpmath
import sympy

from mathics.core.atoms import Complex, Integer, MachineReal, Real, String
from mathics.core.builtin import Builtin
from mathics.core.convert.expression import to_expression, to_mathics_list
from mathics.core.convert.expression import to_expression
from mathics.core.convert.mpmath import from_mpmath
from mathics.core.evaluation import Evaluation
from mathics.core.expression_predefined import (
MATHICS3_I_INFINITY,
MATHICS3_I_NEG_INFINITY,
MATHICS3_INFINITY,
MATHICS3_NEG_INFINITY,
Expression,
)
from mathics.core.streams import stream_manager
from mathics.core.list import ListExpression
from mathics.core.streams import Stream, stream_manager
from mathics.core.symbols import Symbol
from mathics.core.systemsymbols import SymbolIndeterminate
from mathics.eval.files_io.read import SymbolEndOfFile
from mathics.eval.binary.io import eval_BinaryReadList
from mathics.eval.nevaluator import eval_N

SymbolBinaryWrite = Symbol("BinaryWrite")
Expand Down Expand Up @@ -398,13 +401,9 @@ class BinaryRead(Builtin):
readers = _BinaryFormat.get_readers()
summary_text = "read an object of the specified type"

def eval_empty(self, name, n, evaluation):
"BinaryRead[InputStream[name_, n_Integer]]"
return self.eval(name, n, None, evaluation)

def eval(self, name, n, kind, evaluation):
"BinaryRead[InputStream[name_, n_Integer], kind_]"

def check_and_convert_parameters(
self, name, n: Integer, kind, evaluation: Evaluation
) -> Tuple[bool, Expression, Optional[Stream], list]:
channel = to_expression("InputStream", name, n)

# Check kind
Expand All @@ -414,40 +413,105 @@ def eval(self, name, n, kind, evaluation):
else:
expr = to_expression("BinaryRead", channel, kind)

if kind.has_form("List", None):
kinds = kind.elements
else:
kinds = [kind]

python_kinds = [t.get_string_value() for t in kinds]

# Check channel
stream = stream_manager.lookup_stream(n.value)

if stream is None or stream.io.closed:
evaluation.message("General", "openx", name)
return expr
return False, expr, None, python_kinds, []

if stream.mode not in ["rb"]:
evaluation.message("BinaryRead", "bfmt", channel)
return expr

if kind.has_form("List", None):
kinds = kind.elements
else:
kinds = [kind]
evaluation.message(self.__class__.__name__, "bfmt", channel)
return False, expr, None, python_kinds, []

python_kinds = [t.get_string_value() for t in kinds]
if not all(t in self.readers for t in python_kinds):
evaluation.message("BinaryRead", "format", kind)
return (
False,
expr,
None,
python_kinds,
)

return True, expr, stream, python_kinds

def eval_empty(self, name, n: Integer, evaluation):
"BinaryRead[InputStream[name_, n_Integer]]"
return self.eval(name, n, None, evaluation)

def eval(self, name, n: Integer, kind, evaluation):
"BinaryRead[InputStream[name_, n_Integer], kind_]"

all_ok, expr, stream, python_kinds = self.check_and_convert_parameters(
name, n, kind, evaluation
)

if not all_ok or stream is None:
return expr

# Read from stream
result = []
for t in python_kinds:
try:
result.append(self.readers[t](stream.io))
except struct.error:
result.append(SymbolEndOfFile)
return eval_BinaryReadList(
stream, self.readers, python_kinds, isinstance(kind, ListExpression), 1
)

if kind.has_form("List", None):
return to_mathics_list(*result)
else:
if len(result) == 1:
return result[0]

class BinaryReadList(BinaryRead):
"""
<url>
:WMA link:
https://reference.wolfram.com/language/ref/BinaryReadList.html</url>
<dl>
<dt>'BinaryReadList'[$stream$]
<dd>reads all remaining bytes from the stream or file as an integer from 0 to 255.
<dt>'BinaryReadList'[$stream$, $type$]
<dd>reads objects of the specified type file a stream or file until the end of the file. \
The list of objects is returned.
<dt>'BinaryReadList'[$stream$, {$type_1$, $type_2$, ...}]
<dd>reads a sequence of types, until the end of the file.
</dl>
>> strm = OpenWrite[BinaryFormat -> True]
= OutputStream[...]
>> BinaryWrite[strm, {97, 98, 99}]
= OutputStream[...]
>> Close[strm];
>> strm = OpenRead[%, BinaryFormat -> True]
= InputStream[...]
>> BinaryReadList[strm]
= {97, 98, 99}
>> DeleteFile[Close[strm]];
"""

messages = BinaryRead.messages
readers = _BinaryFormat.get_readers()
summary_text = "read a list of objects of the specified type"

def eval_empty(self, name, n, evaluation: Evaluation):
"BinaryReadList[InputStream[name_, n_Integer]]"
return self.eval(name, n, None, evaluation)

def eval(self, name, n: Integer, kind, evaluation: Evaluation):
"BinaryReadList[InputStream[name_, n_Integer], kind_]"

all_ok, expr, stream, python_kinds = self.check_and_convert_parameters(
name, n, kind, evaluation
)

if not all_ok or stream is None:
return expr

# TODO: improve speed when we are reading an entire binary file. Instead of read(1) we should be
# using read()
return eval_BinaryReadList(stream, self.readers, python_kinds, True, -1)


class BinaryWrite(Builtin):
Expand Down Expand Up @@ -663,4 +727,4 @@ def eval(self, name, n, b, kind, evaluation):
return channel


# TODO: BinaryReadList, BinaryWrite, BinaryReadList
# TODO: BinaryWrite, BinaryWriteList
43 changes: 43 additions & 0 deletions mathics/eval/binary/io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
evaluation functions for mathics.builtin.binary.io
"""

import struct

from mathics.core.list import ListExpression
from mathics.core.streams import Stream
from mathics.eval.files_io.read import SymbolEndOfFile


def eval_BinaryReadList(
stream: Stream, readers: dict, kinds: list, return_list: bool, count: int
):
"""
Evaluation function for BinaryRead[] and BinaryReadList[]
Read binary data from stream. `kinds` is a list of kinds to read.
If return_list is True, then the result is a list
"""

result = []
while count > 0 or count <= -1:
count -= 1
for t in kinds:
try:
result.append(readers[t](stream.io))
except struct.error:
result.append(SymbolEndOfFile)
if len(result) > 0 and result[-1] == SymbolEndOfFile:
break

if return_list:
# If we were doing BinaryReadList[], i.e. return_list is True,
# then strip off the EndOfFile at the end
if count < 0:
result = result[:-1]

return ListExpression(*result)
elif len(result) == 1:
return result[0]
else:
return ListExpression(*result)

0 comments on commit 1b835e3

Please sign in to comment.