Repository: Mathics3/mathics-core (language: python)

Issue:
Running test leaves a lot of  temporary files around
When we run doctests we are getting 20 or more temporary files created and not removed.

A fix in a PR will be added shortly.


Discussion / hints:

Fix patch:
diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py
index 71d4ed32a..2f7d76fc6 100644
--- a/mathics/builtin/files_io/files.py
+++ b/mathics/builtin/files_io/files.py
@@ -23,6 +23,7 @@
 from mathics.core import read
 from mathics.core.read import (
     channel_to_stream,
+    close_stream,
     MathicsOpen,
     read_get_separators,
     read_name_and_stream_from_channel,
@@ -65,6 +66,7 @@
 
 TMP_DIR = tempfile.gettempdir()
 SymbolPath = Symbol("$Path")
+SymbolBinaryWrite = Symbol("BinaryWrite")
 
 ### FIXME: All of this is related to Read[]
 ### it can be moved somewhere else.
@@ -90,930 +92,482 @@ def evaluate(self, evaluation):
         return String(INPUT_VAR)
 
 
-class InputFileName_(Predefined):
+# TODO: Improve docs for these Read[] arguments.
+class _BinaryFormat(object):
     """
-    <dl>
-    <dt>'$InputFileName'
-      <dd>is the name of the file from which input is currently being read.
-    </dl>
-
-    While in interactive mode, '$InputFileName' is "".
-    X> $InputFileName
+    Container for BinaryRead readers and BinaryWrite writers
     """
 
-    summary_text = (
-        "the full absolute path to the file from which input is currently being sought"
-    )
-    name = "$InputFileName"
-
-    def evaluate(self, evaluation):
-        return String(read.INPUTFILE_VAR)
+    @staticmethod
+    def _IEEE_real(real):
+        if math.isnan(real):
+            return Symbol("Indeterminate")
+        elif math.isinf(real):
+            return Expression("DirectedInfinity", Integer((-1) ** (real < 0)))
+        else:
+            return Real(real)
 
+    @staticmethod
+    def _IEEE_cmplx(real, imag):
+        if math.isnan(real) or math.isnan(imag):
+            return Symbol("Indeterminate")
+        elif math.isinf(real) or math.isinf(imag):
+            if math.isinf(real) and math.isinf(imag):
+                return Symbol("Indeterminate")
+            return Expression(
+                "DirectedInfinity",
+                Expression(
+                    "Complex",
+                    (-1) ** (real < 0) if math.isinf(real) else 0,
+                    (-1) ** (imag < 0) if math.isinf(imag) else 0,
+                ),
+            )
+        else:
+            return Complex(MachineReal(real), MachineReal(imag))
 
-class EndOfFile(Builtin):
-    """
-    <dl>
-    <dt>'EndOfFile'
-      <dd>is returned by 'Read' when the end of an input stream is reached.
-    </dl>
-    """
+    @classmethod
+    def get_readers(cls):
+        readers = {}
+        for funcname in dir(cls):
+            if funcname.startswith("_") and funcname.endswith("_reader"):
+                readers[funcname[1:-7]] = getattr(cls, funcname)
+        return readers
 
-    summary_text = "end of the file"
+    @classmethod
+    def get_writers(cls):
+        writers = {}
+        for funcname in dir(cls):
+            if funcname.startswith("_") and funcname.endswith("_writer"):
+                writers[funcname[1:-7]] = getattr(cls, funcname)
+        return writers
 
+    # Reader Functions
 
-# TODO: Improve docs for these Read[] arguments.
-class Byte(Builtin):
-    """
-    <dl>
-    <dt>'Byte'
-      <dd>is a data type for 'Read'.
-    </dl>
-    """
+    @staticmethod
+    def _Byte_reader(s):
+        "8-bit unsigned integer"
+        return Integer(*struct.unpack("B", s.read(1)))
 
-    summary_text = "single byte of data, returned as an integer"
+    @staticmethod
+    def _Character8_reader(s):
+        "8-bit character"
+        return String(struct.unpack("c", s.read(1))[0].decode("ascii"))
 
+    @staticmethod
+    def _Character16_reader(s):
+        "16-bit character"
+        return String(chr(*struct.unpack("H", s.read(2))))
 
-class Character(Builtin):
-    """
-    <dl>
-      <dt>'Character'
-      <dd>is a data type for 'Read'.
-    </dl>
-    """
+    @staticmethod
+    def _Complex64_reader(s):
+        "IEEE single-precision complex number"
+        return _BinaryFormat._IEEE_cmplx(*struct.unpack("ff", s.read(8)))
 
-    summary_text = "single character, returned as a one‐character string"
+    @staticmethod
+    def _Complex128_reader(s):
+        "IEEE double-precision complex number"
+        return _BinaryFormat._IEEE_cmplx(*struct.unpack("dd", s.read(16)))
 
+    def _Complex256_reader(self, s):
+        "IEEE quad-precision complex number"
+        return Complex(self._Real128_reader(s), self._Real128_reader(s))
 
-class Expression_(Builtin):
-    """
-    <dl>
-      <dt>'Expression'
-      <dd>is a data type for 'Read'.
-    </dl>
+    @staticmethod
+    def _Integer8_reader(s):
+        "8-bit signed integer"
+        return Integer(*struct.unpack("b", s.read(1)))
 
-    For information about underlying data structure Expression (a kind of M-expression) that is central in evaluation, see: <url>https://mathics-development-guide.readthedocs.io/en/latest/extending/code-overview/ast.html</url>
-    """
+    @staticmethod
+    def _Integer16_reader(s):
+        "16-bit signed integer"
+        return Integer(*struct.unpack("h", s.read(2)))
 
-    summary_text = "WL expression"
-    name = "Expression"
+    @staticmethod
+    def _Integer24_reader(s):
+        "24-bit signed integer"
+        b = s.read(3)
+        return Integer(struct.unpack("<i", b"\x00" + b)[0] >> 8)
 
+    @staticmethod
+    def _Integer32_reader(s):
+        "32-bit signed integer"
+        return Integer(*struct.unpack("i", s.read(4)))
 
-class Number_(Builtin):
-    """
-    <dl>
-    <dt>'Number'
-      <dd>is a data type for 'Read'.
-    </dl>
-    """
+    @staticmethod
+    def _Integer64_reader(s):
+        "64-bit signed integer"
+        return Integer(*struct.unpack("q", s.read(8)))
 
-    summary_text = "exact or approximate number in Fortran‐like notation"
-    name = "Number"
+    @staticmethod
+    def _Integer128_reader(s):
+        "128-bit signed integer"
+        a, b = struct.unpack("Qq", s.read(16))
+        return Integer((b << 64) + a)
 
+    @staticmethod
+    def _Real32_reader(s):
+        "IEEE single-precision real number"
+        return _BinaryFormat._IEEE_real(*struct.unpack("f", s.read(4)))
 
-class Record(Builtin):
-    """
-    <dl>
-    <dt>'Record'
-      <dd>is a data type for 'Read'.
-    </dl>
-    """
+    @staticmethod
+    def _Real64_reader(s):
+        "IEEE double-precision real number"
+        return _BinaryFormat._IEEE_real(*struct.unpack("d", s.read(8)))
 
-    summary_text = "sequence of characters delimited by record separators"
+    @staticmethod
+    def _Real128_reader(s):
+        "IEEE quad-precision real number"
+        # Workaround quad missing from struct
+        # correctness is not guaranteed
+        b = s.read(16)
+        sig, sexp = b[:14], b[14:]
 
+        # Sign / Exponent
+        (sexp,) = struct.unpack("H", sexp)
+        signbit = sexp // 0x8000
+        expbits = sexp % 0x8000
 
-class Word(Builtin):
-    """
-    <dl>
-    <dt>'Word'
-      <dd>is a data type for 'Read'.
-    </dl>
-    """
+        # Signifand
+        try:
+            fracbits = int.from_bytes(sig, byteorder="little")
+        except AttributeError:  # Py2
+            fracbits = int(sig[::-1].encode("hex"), 16)
 
-    summary_text = "sequence of characters delimited by word separators"
+        if expbits == 0x0000 and fracbits == 0:
+            return Real(sympy.Float(0, 4965))
+        elif expbits == 0x7FFF:
+            if fracbits == 0:
+                return Expression("DirectedInfinity", Integer((-1) ** signbit))
+            else:
+                return Symbol("Indeterminate")
 
+        with mpmath.workprec(112):
+            core = mpmath.fdiv(fracbits, 2 ** 112)
+            if expbits == 0x000:
+                assert fracbits != 0
+                exp = -16382
+                core = mpmath.fmul((-1) ** signbit, core)
+            else:
+                assert 0x0001 <= expbits <= 0x7FFE
+                exp = expbits - 16383
+                core = mpmath.fmul((-1) ** signbit, mpmath.fadd(1, core))
 
-class Read(Builtin):
-    """
-    <dl>
-      <dt>'Read[$stream$]'
-      <dd>reads the input stream and returns one expression.
+            if exp >= 0:
+                result = mpmath.fmul(core, 2 ** exp)
+            else:
+                result = mpmath.fdiv(core, 2 ** -exp)
 
-      <dt>'Read[$stream$, $type$]'
-      <dd>reads the input stream and returns an object of the given type.
+            return from_mpmath(result, dps(112))
 
-      <dt>'Read[$stream$, $type$]'
-      <dd>reads the input stream and returns an object of the given type.
+    @staticmethod
+    def _TerminatedString_reader(s):
+        "null-terminated string of 8-bit characters"
+        b = s.read(1)
+        contents = b""
+        while b != b"\x00":
+            if b == b"":
+                raise struct.error
+            contents += b
+            b = s.read(1)
+        return String(contents.decode("ascii"))
 
-      <dt>'Read[$stream$, Hold[Expression]]'
-      <dd>reads the input stream for an Expression and puts it inside 'Hold'.
+    @staticmethod
+    def _UnsignedInteger8_reader(s):
+        "8-bit unsigned integer"
+        return Integer(*struct.unpack("B", s.read(1)))
 
-    </dl>
-    $type$ is one of:
-    <ul>
-      <li>Byte
-      <li>Character
-      <li>Expression
-      <li>HoldExpression
-      <li>Number
-      <li>Real
-      <li>Record
-      <li>String
-      <li>Word
-    </ul>
+    @staticmethod
+    def _UnsignedInteger16_reader(s):
+        "16-bit unsigned integer"
+        return Integer(*struct.unpack("H", s.read(2)))
 
-    ## Malformed InputString
-    #> Read[InputStream[String], {Word, Number}]
-     = Read[InputStream[String], {Word, Number}]
+    @staticmethod
+    def _UnsignedInteger24_reader(s):
+        "24-bit unsigned integer"
+        return Integer(*struct.unpack("I", s.read(3) + b"\0"))
 
-    ## Correctly formed InputString but not open
-    #> Read[InputStream[String, -1], {Word, Number}]
-     : InputStream[String, -1] is not open.
-     = Read[InputStream[String, -1], {Word, Number}]
+    @staticmethod
+    def _UnsignedInteger32_reader(s):
+        "32-bit unsigned integer"
+        return Integer(*struct.unpack("I", s.read(4)))
 
-    ## Reading Strings
-    >> stream = StringToStream["abc123"];
-    >> Read[stream, String]
-     = abc123
-    #> Read[stream, String]
-     = EndOfFile
-    #> Close[stream];
+    @staticmethod
+    def _UnsignedInteger64_reader(s):
+        "64-bit unsigned integer"
+        return Integer(*struct.unpack("Q", s.read(8)))
 
-    ## Reading Words
-    >> stream = StringToStream["abc 123"];
-    >> Read[stream, Word]
-     = abc
-    >> Read[stream, Word]
-     = 123
-    #> Read[stream, Word]
-     = EndOfFile
-    #> Close[stream];
-    #> stream = StringToStream[""];
-    #> Read[stream, Word]
-     = EndOfFile
-    #> Read[stream, Word]
-     = EndOfFile
-    #> Close[stream];
+    @staticmethod
+    def _UnsignedInteger128_reader(s):
+        "128-bit unsigned integer"
+        a, b = struct.unpack("QQ", s.read(16))
+        return Integer((b << 64) + a)
 
-    ## Number
-    >> stream = StringToStream["123, 4"];
-    >> Read[stream, Number]
-     = 123
-    >> Read[stream, Number]
-     = 4
-    #> Read[stream, Number]
-     = EndOfFile
-    #> Close[stream];
-    #> stream = StringToStream["123xyz 321"];
-    #> Read[stream, Number]
-     = 123
-    #> Quiet[Read[stream, Number]]
-     = $Failed
+    # Writer Functions
 
-    ## Real
-    #> stream = StringToStream["123, 4abc"];
-    #> Read[stream, Real]
-     = 123.
-    #> Read[stream, Real]
-     = 4.
-    #> Quiet[Read[stream, Number]]
-     = $Failed
+    @staticmethod
+    def _Byte_writer(s, x):
+        "8-bit unsigned integer"
+        s.write(struct.pack("B", x))
 
-    #> Close[stream];
-    #> stream = StringToStream["1.523E-19"]; Read[stream, Real]
-     = 1.523×10^-19
-    #> Close[stream];
-    #> stream = StringToStream["-1.523e19"]; Read[stream, Real]
-     = -1.523×10^19
-    #> Close[stream];
-    #> stream = StringToStream["3*^10"]; Read[stream, Real]
-     = 3.×10^10
-    #> Close[stream];
-    #> stream = StringToStream["3.*^10"]; Read[stream, Real]
-     = 3.×10^10
-    #> Close[stream];
+    @staticmethod
+    def _Character8_writer(s, x):
+        "8-bit character"
+        s.write(struct.pack("c", x.encode("ascii")))
 
-    ## Expression
-    #> stream = StringToStream["x + y Sin[z]"]; Read[stream, Expression]
-     = x + y Sin[z]
-    #> Close[stream];
-    ## #> stream = Quiet[StringToStream["Sin[1 123"]; Read[stream, Expression]]
-    ##  = $Failed
+    # TODO
+    # @staticmethod
+    # def _Character16_writer(s, x):
+    #     "16-bit character"
+    #     pass
 
-    ## HoldExpression:
-    >> stream = StringToStream["2+2\\n2+3"];
+    @staticmethod
+    def _Complex64_writer(s, x):
+        "IEEE single-precision complex number"
+        s.write(struct.pack("ff", x.real, x.imag))
+        # return _BinaryFormat._IEEE_cmplx(*struct.unpack('ff', s.read(8)))
 
-    'Read' with a 'Hold[Expression]' returns the expression it reads unevaluated so it can be later inspected and evaluated:
+    @staticmethod
+    def _Complex128_writer(s, x):
+        "IEEE double-precision complex number"
+        s.write(struct.pack("dd", x.real, x.imag))
 
-    >> Read[stream, Hold[Expression]]
-     = Hold[2 + 2]
+    # TODO
+    # @staticmethod
+    # def _Complex256_writer(s, x):
+    #     "IEEE quad-precision complex number"
+    #     pass
 
-    >> Read[stream, Expression]
-     = 5
-    >> Close[stream];
+    @staticmethod
+    def _Integer8_writer(s, x):
+        "8-bit signed integer"
+        s.write(struct.pack("b", x))
 
-    Reading a comment however will return the empy list:
-    >> stream = StringToStream["(* ::Package:: *)"];
+    @staticmethod
+    def _Integer16_writer(s, x):
+        "16-bit signed integer"
+        s.write(struct.pack("h", x))
 
-    >> Read[stream, Hold[Expression]]
-     = {}
+    @staticmethod
+    def _Integer24_writer(s, x):
+        "24-bit signed integer"
+        s.write(struct.pack("i", x << 8)[1:])
 
-    >> Close[stream];
+    @staticmethod
+    def _Integer32_writer(s, x):
+        "32-bit signed integer"
+        s.write(struct.pack("i", x))
 
-    ## Multiple types
-    >> stream = StringToStream["123 abc"];
-    >> Read[stream, {Number, Word}]
-     = {123, abc}
-    #> Read[stream, {Number, Word}]
-     = EndOfFile
-    #> Close[stream];
+    @staticmethod
+    def _Integer64_writer(s, x):
+        "64-bit signed integer"
+        s.write(struct.pack("q", x))
 
-    #> stream = StringToStream["123 abc"];
-    #> Quiet[Read[stream, {Word, Number}]]
-     = $Failed
-    #> Close[stream];
+    @staticmethod
+    def _Integer128_writer(s, x):
+        "128-bit signed integer"
+        a, b = x & 0xFFFFFFFFFFFFFFFF, x >> 64
+        s.write(struct.pack("Qq", a, b))
 
-    #> stream = StringToStream["123 123"];  Read[stream, {Real, Number}]
-     = {123., 123}
-    #> Close[stream];
+    @staticmethod
+    def _Real32_writer(s, x):
+        "IEEE single-precision real number"
+        s.write(struct.pack("f", x))
 
-    #> Quiet[Read[stream, {Real}]]
-     = Read[InputStream[String, ...], {Real}]
+    @staticmethod
+    def _Real64_writer(s, x):
+        "IEEE double-precision real number"
+        s.write(struct.pack("d", x))
 
-    Multiple lines:
-    >> stream = StringToStream["\\"Tengo una\\nvaca lechera.\\""]; Read[stream]
-     = Tengo una
-     . vaca lechera.
+    # TODO
+    # @staticmethod
+    # def _Real128_writer(s, x):
+    #     "IEEE quad-precision real number"
+    #     pass
 
-    """
+    @staticmethod
+    def _TerminatedString_writer(s, x):
+        "null-terminated string of 8-bit characters"
+        s.write(x.encode("utf-8"))
 
-    summary_text = "read an object of the specified type from a stream"
-    messages = {
-        "openx": "`1` is not open.",
-        "readf": "`1` is not a valid format specification.",
-        "readn": "Invalid real number found when reading from `1`.",
-        "readt": "Invalid input found when reading `1` from `2`.",
-        "intnm": (
-            "Non-negative machine-sized integer expected at " "position 3 in `1`."
-        ),
-    }
+    @staticmethod
+    def _UnsignedInteger8_writer(s, x):
+        "8-bit unsigned integer"
+        s.write(struct.pack("B", x))
 
-    rules = {
-        "Read[stream_]": "Read[stream, Expression]",
-    }
+    @staticmethod
+    def _UnsignedInteger16_writer(s, x):
+        "16-bit unsigned integer"
+        s.write(struct.pack("H", x))
 
-    options = {
-        "NullRecords": "False",
-        "NullWords": "False",
-        "RecordSeparators": '{"\r\n", "\n", "\r"}',
-        "TokenWords": "{}",
-        "WordSeparators": '{" ", "\t"}',
-    }
+    @staticmethod
+    def _UnsignedInteger24_writer(s, x):
+        "24-bit unsigned integer"
+        s.write(struct.pack("I", x << 8)[1:])
 
-    def check_options(self, options):
-        # Options
-        # TODO Proper error messages
+    @staticmethod
+    def _UnsignedInteger32_writer(s, x):
+        "32-bit unsigned integer"
+        s.write(struct.pack("I", x))
 
-        result = {}
-        keys = list(options.keys())
+    @staticmethod
+    def _UnsignedInteger64_writer(s, x):
+        "64-bit unsigned integer"
+        s.write(struct.pack("Q", x))
 
-        # AnchoredSearch
-        if "System`AnchoredSearch" in keys:
-            anchored_search = options["System`AnchoredSearch"].to_python()
-            assert anchored_search in [True, False]
-            result["AnchoredSearch"] = anchored_search
+    @staticmethod
+    def _UnsignedInteger128_writer(s, x):
+        "128-bit unsigned integer"
+        a, b = x & 0xFFFFFFFFFFFFFFFF, x >> 64
+        s.write(struct.pack("QQ", a, b))
 
-        # IgnoreCase
-        if "System`IgnoreCase" in keys:
-            ignore_case = options["System`IgnoreCase"].to_python()
-            assert ignore_case in [True, False]
-            result["IgnoreCase"] = ignore_case
 
-        # WordSearch
-        if "System`WordSearch" in keys:
-            word_search = options["System`WordSearch"].to_python()
-            assert word_search in [True, False]
-            result["WordSearch"] = word_search
+class _OpenAction(Builtin):
 
-        # RecordSeparators
-        if "System`RecordSeparators" in keys:
-            record_separators = options["System`RecordSeparators"].to_python()
-            assert isinstance(record_separators, list)
-            assert all(
-                isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators
-            )
-            record_separators = [s[1:-1] for s in record_separators]
-            result["RecordSeparators"] = record_separators
+    # BinaryFormat: 'False',
+    # CharacterEncoding :> Automatic,
+    # DOSTextFormat :> True,
+    # FormatType -> InputForm,
+    # NumberMarks :> $NumberMarks,
+    # PageHeight -> 22, PageWidth -> 78,
+    # TotalHeight -> Infinity,
+    # TotalWidth -> Infinity
 
-        # WordSeparators
-        if "System`WordSeparators" in keys:
-            word_separators = options["System`WordSeparators"].to_python()
-            assert isinstance(word_separators, list)
-            assert all(
-                isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators
-            )
-            word_separators = [s[1:-1] for s in word_separators]
-            result["WordSeparators"] = word_separators
+    options = {
+        "BinaryFormat": "False",
+        "CharacterEncoding": "$CharacterEncoding",
+    }
 
-        # NullRecords
-        if "System`NullRecords" in keys:
-            null_records = options["System`NullRecords"].to_python()
-            assert null_records in [True, False]
-            result["NullRecords"] = null_records
+    messages = {
+        "argx": "OpenRead called with 0 arguments; 1 argument is expected.",
+        "fstr": (
+            "File specification `1` is not a string of " "one or more characters."
+        ),
+    }
 
-        # NullWords
-        if "System`NullWords" in keys:
-            null_words = options["System`NullWords"].to_python()
-            assert null_words in [True, False]
-            result["NullWords"] = null_words
+    def apply_empty(self, evaluation, options):
+        "%(name)s[OptionsPattern[]]"
 
-        # TokenWords
-        if "System`TokenWords" in keys:
-            token_words = options["System`TokenWords"].to_python()
-            assert token_words == []
-            result["TokenWords"] = token_words
+        if isinstance(self, (OpenWrite, OpenAppend)):
+            # We use delete=False because we write to the name *after*
+            # tfms.close() is done. In other words we are using
+            # NamedTempararyFile to get a unique name and ensure that
+            # no one else uses it.
+            # In Close[] we will explicitly remove the name from the
+            # filesystem.
+            tmpf = tempfile.NamedTemporaryFile(dir=TMP_DIR, delete=False)
+            path = String(tmpf.name)
+            tmpf.close()
+            return self.apply_path(path, evaluation, options)
+        else:
+            evaluation.message("OpenRead", "argx")
+            return
 
-        return result
+    def apply_path(self, path, evaluation, options):
+        "%(name)s[path_?NotOptionQ, OptionsPattern[]]"
 
-    def apply(self, channel, types, evaluation, options):
-        "Read[channel_, types_, OptionsPattern[Read]]"
+        # Options
+        # BinaryFormat
+        mode = self.mode
+        if options["System`BinaryFormat"] is SymbolTrue:
+            if not self.mode.endswith("b"):
+                mode += "b"
 
-        name, n, stream = read_name_and_stream_from_channel(channel, evaluation)
-        if name is None:
+        if not (isinstance(path, String) and len(path.to_python()) > 2):
+            evaluation.message(self.__class__.__name__, "fstr", path)
             return
 
-        # Wrap types in a list (if it isn't already one)
-        if types.has_form("List", None):
-            types = types.elements
+        path_string = path.get_string_value()
+
+        tmp, is_temporary_file = path_search(path_string)
+        if tmp is None:
+            if mode in ["r", "rb"]:
+                evaluation.message("General", "noopen", path)
+                return
         else:
-            types = (types,)
+            path_string = tmp
 
-        # TODO: look for a better implementation handling "Hold[Expression]".
-        #
-        types = (
-            Symbol("HoldExpression")
-            if (
-                typ.get_head_name() == "System`Hold"
-                and typ.leaves[0].get_name() == "System`Expression"
+        try:
+            encoding = self.get_option(options, "CharacterEncoding", evaluation)
+            if not isinstance(encoding, String):
+                return
+
+            opener = MathicsOpen(
+                path_string,
+                mode=mode,
+                encoding=encoding.get_string_value(),
+                is_temporary_file=is_temporary_file,
             )
-            else typ
-            for typ in types
-        )
-        types = Expression("List", *types)
+            opener.__enter__(is_temporary_file=is_temporary_file)
+            n = opener.n
+        except IOError:
+            evaluation.message("General", "noopen", path)
+            return
+        except MessageException as e:
+            e.message(evaluation)
+            return
 
-        for typ in types.leaves:
-            if typ not in READ_TYPES:
-                evaluation.message("Read", "readf", typ)
-                return SymbolFailed
+        return Expression(Symbol(self.stream_type), path, Integer(n))
 
-        record_separators, word_separators = read_get_separators(options)
 
-        name = name.to_python()
+class BinaryWrite(Builtin):
+    """
+    <dl>
+      <dt>'BinaryWrite[$channel$, $b$]'
+      <dd>writes a single byte given as an integer from 0 to 255.
 
-        result = []
+      <dt>'BinaryWrite[$channel$, {b1, b2, ...}]'
+      <dd>writes a sequence of byte.
 
-        read_word = read_from_stream(stream, word_separators, evaluation.message)
-        read_record = read_from_stream(stream, record_separators, evaluation.message)
-        read_number = read_from_stream(
-            stream,
-            word_separators + record_separators,
-            evaluation.message,
-            ["+", "-", "."] + [str(i) for i in range(10)],
-        )
-        read_real = read_from_stream(
-            stream,
-            word_separators + record_separators,
-            evaluation.message,
-            ["+", "-", ".", "e", "E", "^", "*"] + [str(i) for i in range(10)],
-        )
+      <dt>'BinaryWrite[$channel$, "string"]'
+      <dd>writes the raw characters in a string.
 
-        from mathics.core.expression import BaseElement
-        from mathics_scanner.errors import IncompleteSyntaxError, InvalidSyntaxError
-        from mathics.core.parser import MathicsMultiLineFeeder, parse
+      <dt>'BinaryWrite[$channel$, $x$, $type$]'
+      <dd>writes $x$ as the specified type.
 
-        for typ in types.leaves:
-            try:
-                if typ is Symbol("Byte"):
-                    tmp = stream.io.read(1)
-                    if tmp == "":
-                        raise EOFError
-                    result.append(ord(tmp))
-                elif typ is Symbol("Character"):
-                    tmp = stream.io.read(1)
-                    if tmp == "":
-                        raise EOFError
-                    result.append(tmp)
-                elif typ is Symbol("Expression") or typ is Symbol("HoldExpression"):
-                    tmp = next(read_record)
-                    while True:
-                        try:
-                            feeder = MathicsMultiLineFeeder(tmp)
-                            expr = parse(evaluation.definitions, feeder)
-                            break
-                        except (IncompleteSyntaxError, InvalidSyntaxError):
-                            try:
-                                nextline = next(read_record)
-                                tmp = tmp + "\n" + nextline
-                            except EOFError:
-                                expr = SymbolEndOfFile
-                                break
-                        except Exception as e:
-                            print(e)
+      <dt>'BinaryWrite[$channel$, {$x1$, $x2$, ...}, $type$]'
+      <dd>writes a sequence of objects as the specified type.
 
-                    if expr is SymbolEndOfFile:
-                        evaluation.message(
-                            "Read", "readt", tmp, Expression("InputSteam", name, n)
-                        )
-                        return SymbolFailed
-                    elif isinstance(expr, BaseElement):
-                        if typ is Symbol("HoldExpression"):
-                            expr = Expression("Hold", expr)
-                        result.append(expr)
-                    # else:
-                    #  TODO: Supposedly we can't get here
-                    # what code should we put here?
+      <dt>'BinaryWrite[$channel$, {$x1$, $x2$, ...}, {$type1$, $type2$, ...}]'
+      <dd>writes a sequence of objects using a sequence of specified types.
+    </dl>
 
-                elif typ is Symbol("Number"):
-                    tmp = next(read_number)
-                    try:
-                        tmp = int(tmp)
-                    except ValueError:
-                        try:
-                            tmp = float(tmp)
-                        except ValueError:
-                            evaluation.message(
-                                "Read", "readn", Expression("InputSteam", name, n)
-                            )
-                            return SymbolFailed
-                    result.append(tmp)
+    >> strm = OpenWrite[BinaryFormat -> True]
+     = OutputStream[...]
+    >> BinaryWrite[strm, {39, 4, 122}]
+     = OutputStream[...]
+    >> Close[strm];
+    >> strm = OpenRead[%, BinaryFormat -> True]
+     = InputStream[...]
+    >> BinaryRead[strm]
+     = 39
+    >> BinaryRead[strm, "Byte"]
+     = 4
+    >> BinaryRead[strm, "Character8"]
+     = z
+    >> DeleteFile[Close[strm]];
 
-                elif typ is Symbol("Real"):
-                    tmp = next(read_real)
-                    tmp = tmp.replace("*^", "E")
-                    try:
-                        tmp = float(tmp)
-                    except ValueError:
-                        evaluation.message(
-                            "Read", "readn", Expression("InputSteam", name, n)
-                        )
-                        return SymbolFailed
-                    result.append(tmp)
-                elif typ is Symbol("Record"):
-                    result.append(next(read_record))
-                elif typ is Symbol("String"):
-                    tmp = stream.io.readline()
-                    if len(tmp) == 0:
-                        raise EOFError
-                    result.append(tmp.rstrip("\n"))
-                elif typ is Symbol("Word"):
-                    result.append(next(read_word))
+    Write a String
+    >> strm = OpenWrite[BinaryFormat -> True]
+     = OutputStream[...]
+    >> BinaryWrite[strm, "abc123"]
+     = OutputStream[...]
+    >> pathname = Close[%]
+     = ...
 
-            except EOFError:
-                return SymbolEndOfFile
-            except UnicodeDecodeError:
-                evaluation.message("General", "ucdec")
+    Read as Bytes
+    >> strm = OpenRead[%, BinaryFormat -> True]
+     = InputStream[...]
+    >> BinaryRead[strm, {"Character8", "Character8", "Character8", "Character8", "Character8", "Character8", "Character8"}]
+     = {a, b, c, 1, 2, 3, EndOfFile}
+    >> pathname = Close[strm]
+     = ...
 
-        if isinstance(result, Symbol):
-            return result
-        if len(result) == 1:
-            return from_python(*result)
-
-        return from_python(result)
-
-    def apply_nostream(self, arg1, arg2, evaluation):
-        "Read[arg1_, arg2_]"
-        evaluation.message("General", "stream", arg1)
-        return
-
-
-class Write(Builtin):
-    """
-    <dl>
-    <dt>'Write[$channel$, $expr1$, $expr2$, ...]'
-      <dd>writes the expressions to the output channel followed by a newline.
-    </dl>
-
-    >> stream = OpenWrite[]
-     = ...
-    >> Write[stream, 10 x + 15 y ^ 2]
-    >> Write[stream, 3 Sin[z]]
-    >> Close[stream]
-     = ...
-    >> stream = OpenRead[%];
-    >> ReadList[stream]
-     = {10 x + 15 y ^ 2, 3 Sin[z]}
-    #> Close[stream];
-    """
-
-    summary_text = "write a sequence of expressions to a stream, ending the output with a newline (line feed)"
-
-    def apply(self, channel, expr, evaluation):
-        "Write[channel_, expr___]"
-
-        strm = channel_to_stream(channel)
-
-        if strm is None:
-            return
-
-        n = strm.leaves[1].get_int_value()
-        stream = stream_manager.lookup_stream(n)
-
-        if stream is None or stream.io is None or stream.io.closed:
-            evaluation.message("General", "openx", channel)
-            return SymbolNull
-
-        expr = expr.get_sequence()
-        expr = Expression("Row", Expression("List", *expr))
-
-        evaluation.format = "text"
-        text = evaluation.format_output(expr)
-        stream.io.write(str(text) + "\n")
-        return SymbolNull
-
-
-class _BinaryFormat(object):
-    """
-    Container for BinaryRead readers and BinaryWrite writers
-    """
-
-    @staticmethod
-    def _IEEE_real(real):
-        if math.isnan(real):
-            return Symbol("Indeterminate")
-        elif math.isinf(real):
-            return Expression("DirectedInfinity", Integer((-1) ** (real < 0)))
-        else:
-            return Real(real)
-
-    @staticmethod
-    def _IEEE_cmplx(real, imag):
-        if math.isnan(real) or math.isnan(imag):
-            return Symbol("Indeterminate")
-        elif math.isinf(real) or math.isinf(imag):
-            if math.isinf(real) and math.isinf(imag):
-                return Symbol("Indeterminate")
-            return Expression(
-                "DirectedInfinity",
-                Expression(
-                    "Complex",
-                    (-1) ** (real < 0) if math.isinf(real) else 0,
-                    (-1) ** (imag < 0) if math.isinf(imag) else 0,
-                ),
-            )
-        else:
-            return Complex(MachineReal(real), MachineReal(imag))
-
-    @classmethod
-    def get_readers(cls):
-        readers = {}
-        for funcname in dir(cls):
-            if funcname.startswith("_") and funcname.endswith("_reader"):
-                readers[funcname[1:-7]] = getattr(cls, funcname)
-        return readers
-
-    @classmethod
-    def get_writers(cls):
-        writers = {}
-        for funcname in dir(cls):
-            if funcname.startswith("_") and funcname.endswith("_writer"):
-                writers[funcname[1:-7]] = getattr(cls, funcname)
-        return writers
-
-    # Reader Functions
-
-    @staticmethod
-    def _Byte_reader(s):
-        "8-bit unsigned integer"
-        return Integer(*struct.unpack("B", s.read(1)))
-
-    @staticmethod
-    def _Character8_reader(s):
-        "8-bit character"
-        return String(struct.unpack("c", s.read(1))[0].decode("ascii"))
-
-    @staticmethod
-    def _Character16_reader(s):
-        "16-bit character"
-        return String(chr(*struct.unpack("H", s.read(2))))
-
-    @staticmethod
-    def _Complex64_reader(s):
-        "IEEE single-precision complex number"
-        return _BinaryFormat._IEEE_cmplx(*struct.unpack("ff", s.read(8)))
-
-    @staticmethod
-    def _Complex128_reader(s):
-        "IEEE double-precision complex number"
-        return _BinaryFormat._IEEE_cmplx(*struct.unpack("dd", s.read(16)))
-
-    def _Complex256_reader(self, s):
-        "IEEE quad-precision complex number"
-        return Complex(self._Real128_reader(s), self._Real128_reader(s))
-
-    @staticmethod
-    def _Integer8_reader(s):
-        "8-bit signed integer"
-        return Integer(*struct.unpack("b", s.read(1)))
-
-    @staticmethod
-    def _Integer16_reader(s):
-        "16-bit signed integer"
-        return Integer(*struct.unpack("h", s.read(2)))
-
-    @staticmethod
-    def _Integer24_reader(s):
-        "24-bit signed integer"
-        b = s.read(3)
-        return Integer(struct.unpack("<i", b"\x00" + b)[0] >> 8)
-
-    @staticmethod
-    def _Integer32_reader(s):
-        "32-bit signed integer"
-        return Integer(*struct.unpack("i", s.read(4)))
-
-    @staticmethod
-    def _Integer64_reader(s):
-        "64-bit signed integer"
-        return Integer(*struct.unpack("q", s.read(8)))
-
-    @staticmethod
-    def _Integer128_reader(s):
-        "128-bit signed integer"
-        a, b = struct.unpack("Qq", s.read(16))
-        return Integer((b << 64) + a)
-
-    @staticmethod
-    def _Real32_reader(s):
-        "IEEE single-precision real number"
-        return _BinaryFormat._IEEE_real(*struct.unpack("f", s.read(4)))
-
-    @staticmethod
-    def _Real64_reader(s):
-        "IEEE double-precision real number"
-        return _BinaryFormat._IEEE_real(*struct.unpack("d", s.read(8)))
-
-    @staticmethod
-    def _Real128_reader(s):
-        "IEEE quad-precision real number"
-        # Workaround quad missing from struct
-        # correctness is not guaranteed
-        b = s.read(16)
-        sig, sexp = b[:14], b[14:]
-
-        # Sign / Exponent
-        (sexp,) = struct.unpack("H", sexp)
-        signbit = sexp // 0x8000
-        expbits = sexp % 0x8000
-
-        # Signifand
-        try:
-            fracbits = int.from_bytes(sig, byteorder="little")
-        except AttributeError:  # Py2
-            fracbits = int(sig[::-1].encode("hex"), 16)
-
-        if expbits == 0x0000 and fracbits == 0:
-            return Real(sympy.Float(0, 4965))
-        elif expbits == 0x7FFF:
-            if fracbits == 0:
-                return Expression("DirectedInfinity", Integer((-1) ** signbit))
-            else:
-                return Symbol("Indeterminate")
-
-        with mpmath.workprec(112):
-            core = mpmath.fdiv(fracbits, 2 ** 112)
-            if expbits == 0x000:
-                assert fracbits != 0
-                exp = -16382
-                core = mpmath.fmul((-1) ** signbit, core)
-            else:
-                assert 0x0001 <= expbits <= 0x7FFE
-                exp = expbits - 16383
-                core = mpmath.fmul((-1) ** signbit, mpmath.fadd(1, core))
-
-            if exp >= 0:
-                result = mpmath.fmul(core, 2 ** exp)
-            else:
-                result = mpmath.fdiv(core, 2 ** -exp)
-
-            return from_mpmath(result, dps(112))
-
-    @staticmethod
-    def _TerminatedString_reader(s):
-        "null-terminated string of 8-bit characters"
-        b = s.read(1)
-        contents = b""
-        while b != b"\x00":
-            if b == b"":
-                raise struct.error
-            contents += b
-            b = s.read(1)
-        return String(contents.decode("ascii"))
-
-    @staticmethod
-    def _UnsignedInteger8_reader(s):
-        "8-bit unsigned integer"
-        return Integer(*struct.unpack("B", s.read(1)))
-
-    @staticmethod
-    def _UnsignedInteger16_reader(s):
-        "16-bit unsigned integer"
-        return Integer(*struct.unpack("H", s.read(2)))
-
-    @staticmethod
-    def _UnsignedInteger24_reader(s):
-        "24-bit unsigned integer"
-        return Integer(*struct.unpack("I", s.read(3) + b"\0"))
-
-    @staticmethod
-    def _UnsignedInteger32_reader(s):
-        "32-bit unsigned integer"
-        return Integer(*struct.unpack("I", s.read(4)))
-
-    @staticmethod
-    def _UnsignedInteger64_reader(s):
-        "64-bit unsigned integer"
-        return Integer(*struct.unpack("Q", s.read(8)))
-
-    @staticmethod
-    def _UnsignedInteger128_reader(s):
-        "128-bit unsigned integer"
-        a, b = struct.unpack("QQ", s.read(16))
-        return Integer((b << 64) + a)
-
-    # Writer Functions
-
-    @staticmethod
-    def _Byte_writer(s, x):
-        "8-bit unsigned integer"
-        s.write(struct.pack("B", x))
-
-    @staticmethod
-    def _Character8_writer(s, x):
-        "8-bit character"
-        s.write(struct.pack("c", x.encode("ascii")))
-
-    # TODO
-    # @staticmethod
-    # def _Character16_writer(s, x):
-    #     "16-bit character"
-    #     pass
-
-    @staticmethod
-    def _Complex64_writer(s, x):
-        "IEEE single-precision complex number"
-        s.write(struct.pack("ff", x.real, x.imag))
-        # return _BinaryFormat._IEEE_cmplx(*struct.unpack('ff', s.read(8)))
-
-    @staticmethod
-    def _Complex128_writer(s, x):
-        "IEEE double-precision complex number"
-        s.write(struct.pack("dd", x.real, x.imag))
-
-    # TODO
-    # @staticmethod
-    # def _Complex256_writer(s, x):
-    #     "IEEE quad-precision complex number"
-    #     pass
-
-    @staticmethod
-    def _Integer8_writer(s, x):
-        "8-bit signed integer"
-        s.write(struct.pack("b", x))
-
-    @staticmethod
-    def _Integer16_writer(s, x):
-        "16-bit signed integer"
-        s.write(struct.pack("h", x))
-
-    @staticmethod
-    def _Integer24_writer(s, x):
-        "24-bit signed integer"
-        s.write(struct.pack("i", x << 8)[1:])
-
-    @staticmethod
-    def _Integer32_writer(s, x):
-        "32-bit signed integer"
-        s.write(struct.pack("i", x))
-
-    @staticmethod
-    def _Integer64_writer(s, x):
-        "64-bit signed integer"
-        s.write(struct.pack("q", x))
-
-    @staticmethod
-    def _Integer128_writer(s, x):
-        "128-bit signed integer"
-        a, b = x & 0xFFFFFFFFFFFFFFFF, x >> 64
-        s.write(struct.pack("Qq", a, b))
-
-    @staticmethod
-    def _Real32_writer(s, x):
-        "IEEE single-precision real number"
-        s.write(struct.pack("f", x))
-
-    @staticmethod
-    def _Real64_writer(s, x):
-        "IEEE double-precision real number"
-        s.write(struct.pack("d", x))
-
-    # TODO
-    # @staticmethod
-    # def _Real128_writer(s, x):
-    #     "IEEE quad-precision real number"
-    #     pass
-
-    @staticmethod
-    def _TerminatedString_writer(s, x):
-        "null-terminated string of 8-bit characters"
-        s.write(x.encode("utf-8"))
-
-    @staticmethod
-    def _UnsignedInteger8_writer(s, x):
-        "8-bit unsigned integer"
-        s.write(struct.pack("B", x))
-
-    @staticmethod
-    def _UnsignedInteger16_writer(s, x):
-        "16-bit unsigned integer"
-        s.write(struct.pack("H", x))
-
-    @staticmethod
-    def _UnsignedInteger24_writer(s, x):
-        "24-bit unsigned integer"
-        s.write(struct.pack("I", x << 8)[1:])
-
-    @staticmethod
-    def _UnsignedInteger32_writer(s, x):
-        "32-bit unsigned integer"
-        s.write(struct.pack("I", x))
-
-    @staticmethod
-    def _UnsignedInteger64_writer(s, x):
-        "64-bit unsigned integer"
-        s.write(struct.pack("Q", x))
-
-    @staticmethod
-    def _UnsignedInteger128_writer(s, x):
-        "128-bit unsigned integer"
-        a, b = x & 0xFFFFFFFFFFFFFFFF, x >> 64
-        s.write(struct.pack("QQ", a, b))
-
-
-class BinaryWrite(Builtin):
-    """
-    <dl>
-    <dt>'BinaryWrite[$channel$, $b$]'
-      <dd>writes a single byte given as an integer from 0 to 255.
-    <dt>'BinaryWrite[$channel$, {b1, b2, ...}]'
-      <dd>writes a sequence of byte.
-    <dt>'BinaryWrite[$channel$, "string"]'
-      <dd>writes the raw characters in a string.
-    <dt>'BinaryWrite[$channel$, $x$, $type$]'
-      <dd>writes $x$ as the specified type.
-    <dt>'BinaryWrite[$channel$, {$x1$, $x2$, ...}, $type$]'
-      <dd>writes a sequence of objects as the specified type.
-    <dt>'BinaryWrite[$channel$, {$x1$, $x2$, ...}, {$type1$, $type2$, ...}]'
-      <dd>writes a sequence of objects using a sequence of specified types.
-    </dl>
-
-    >> strm = OpenWrite[BinaryFormat -> True]
-     = OutputStream[...]
-    >> BinaryWrite[strm, {39, 4, 122}]
-     = OutputStream[...]
-    >> Close[strm]
-     = ...
-    >> strm = OpenRead[%, BinaryFormat -> True]
-     = InputStream[...]
-    >> BinaryRead[strm]
-     = 39
-    >> BinaryRead[strm, "Byte"]
-     = 4
-    >> BinaryRead[strm, "Character8"]
-     = z
-    >> Close[strm];
-
-    Write a String
-    >> strm = OpenWrite[BinaryFormat -> True]
-     = OutputStream[...]
-    >> BinaryWrite[strm, "abc123"]
-     = OutputStream[...]
-    >> Close[%]
-     = ...
-
-    Read as Bytes
-    >> strm = OpenRead[%, BinaryFormat -> True]
-     = InputStream[...]
-    >> BinaryRead[strm, {"Character8", "Character8", "Character8", "Character8", "Character8", "Character8", "Character8"}]
-     = {a, b, c, 1, 2, 3, EndOfFile}
-    >> Close[strm]
-     = ...
-
-    Read as Characters
-    >> strm = OpenRead[%, BinaryFormat -> True]
-     = InputStream[...]
-    >> BinaryRead[strm, {"Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte"}]
-     = {97, 98, 99, 49, 50, 51, EndOfFile}
-    >> Close[strm]
-     = ...
+    Read as Characters
+    >> strm = OpenRead[%, BinaryFormat -> True]
+     = InputStream[...]
+    >> BinaryRead[strm, {"Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte"}]
+     = {97, 98, 99, 49, 50, 51, EndOfFile}
+    >> DeleteFile[Close[strm]];
 
     Write Type
     >> strm = OpenWrite[BinaryFormat -> True]
@@ -1022,11 +576,10 @@ class BinaryWrite(Builtin):
      = OutputStream[...]
     >> BinaryWrite[strm, {97, 98, 99}, {"Byte", "Byte", "Byte"}]
      = OutputStream[...]
-    >> Close[%]
-     = ...
+    >> DeleteFile[Close[%]];
 
     ## Write then Read as Bytes
-    #> WRb[bytes_, form_] := Module[{stream, res={}, byte}, stream = OpenWrite[BinaryFormat -> True]; BinaryWrite[stream, bytes, form]; stream = OpenRead[Close[stream], BinaryFormat -> True]; While[Not[SameQ[byte = BinaryRead[stream], EndOfFile]], res = Join[res, {byte}];]; Close[stream]; res]
+    #> WRb[bytes_, form_] := Module[{stream, res={}, byte}, stream = OpenWrite[BinaryFormat -> True]; BinaryWrite[stream, bytes, form]; stream = OpenRead[Close[stream], BinaryFormat -> True]; While[Not[SameQ[byte = BinaryRead[stream], EndOfFile]], res = Join[res, {byte}];]; DeleteFile[Close[stream]]; res]
 
     ## Byte
     #> WRb[{149, 2, 177, 132}, {"Byte", "Byte", "Byte", "Byte"}]
@@ -1206,10 +759,10 @@ def apply(self, name, n, b, typ, evaluation):
 
         # Check Empty Type
         if typ is None:
-            expr = Expression("BinaryWrite", channel, b)
+            expr = Expression(SymbolBinaryWrite, channel, b)
             typ = Expression("List")
         else:
-            expr = Expression("BinaryWrite", channel, b, typ)
+            expr = Expression(SymbolBinaryWrite, channel, b, typ)
 
         # Check channel
         stream = stream_manager.lookup_stream(n.get_int_value())
@@ -1219,7 +772,7 @@ def apply(self, name, n, b, typ, evaluation):
             return expr
 
         if stream.mode not in ["wb", "ab"]:
-            evaluation.message("BinaryWrite", "openr", channel)
+            evaluation.message(SymbolBinaryWrite, "openr", channel)
             return expr
 
         # Check b
@@ -1302,18 +855,18 @@ def apply(self, name, n, b, typ, evaluation):
                 x = x.get_int_value()
 
             if x is None:
-                return evaluation.message("BinaryWrite", "nocoerce", b)
+                return evaluation.message(SymbolBinaryWrite, "nocoerce", b)
 
             try:
                 self.writers[t](stream.io, x)
             except struct.error:
-                return evaluation.message("BinaryWrite", "nocoerce", b)
+                return evaluation.message(SymbolBinaryWrite, "nocoerce", b)
             i += 1
 
         try:
             stream.io.flush()
         except IOError as err:
-            evaluation.message("BinaryWrite", "writex", err.strerror)
+            evaluation.message(SymbolBinaryWrite, "writex", err.strerror)
         return channel
 
 
@@ -1332,16 +885,15 @@ class BinaryRead(Builtin):
      = OutputStream[...]
     >> BinaryWrite[strm, {97, 98, 99}]
      = OutputStream[...]
-    >> Close[strm]
-     = ...
+    >> Close[strm];
     >> strm = OpenRead[%, BinaryFormat -> True]
      = InputStream[...]
     >> BinaryRead[strm, {"Character8", "Character8", "Character8"}]
      = {a, b, c}
-    >> Close[strm];
+    >> DeleteFile[Close[strm]];
 
     ## Write as Bytes then Read
-    #> WbR[bytes_, form_] := Module[{stream, res}, stream = OpenWrite[BinaryFormat -> True]; BinaryWrite[stream, bytes]; stream = OpenRead[Close[stream], BinaryFormat -> True]; res = BinaryRead[stream, form]; Close[stream]; res]
+    #> WbR[bytes_, form_] := Module[{stream, res}, stream = OpenWrite[BinaryFormat -> True]; BinaryWrite[stream, bytes]; stream = OpenRead[Close[stream], BinaryFormat -> True]; res = BinaryRead[stream, form]; DeleteFile[Close[stream]]; res]
 
     ## Byte
     #> WbR[{149, 2, 177, 132}, {"Byte", "Byte", "Byte", "Byte"}]
@@ -1584,204 +1136,368 @@ def apply(self, name, n, typ, evaluation):
             evaluation.message("General", "openx", name)
             return expr
 
-        if stream.mode not in ["rb"]:
-            evaluation.message("BinaryRead", "bfmt", channel)
-            return expr
+        if stream.mode not in ["rb"]:
+            evaluation.message("BinaryRead", "bfmt", channel)
+            return expr
+
+        if typ.has_form("List", None):
+            types = typ.get_elements()
+        else:
+            types = [typ]
+
+        types = [t.get_string_value() for t in types]
+        if not all(t in self.readers for t in types):
+            evaluation.message("BinaryRead", "format", typ)
+            return expr
+
+        # Read from stream
+        result = []
+        for t in types:
+            try:
+                result.append(self.readers[t](stream.io))
+            except struct.error:
+                result.append(SymbolEndOfFile)
+
+        if typ.has_form("List", None):
+            return Expression("List", *result)
+        else:
+            if len(result) == 1:
+                return result[0]
+
+
+class Byte(Builtin):
+    """
+    <dl>
+    <dt>'Byte'
+      <dd>is a data type for 'Read'.
+    </dl>
+    """
+
+    summary_text = "single byte of data, returned as an integer"
+
+
+class Character(Builtin):
+    """
+    <dl>
+      <dt>'Character'
+      <dd>is a data type for 'Read'.
+    </dl>
+    """
+
+    summary_text = "single character, returned as a one‐character string"
+
+
+class Close(Builtin):
+    """
+    <dl>
+      <dt>'Close[$stream$]'
+      <dd>closes an input or output stream.
+    </dl>
+
+    >> Close[StringToStream["123abc"]]
+     = String
+
+    >> Close[OpenWrite[]]
+     = ...
+
+    #> Streams[] == (Close[OpenWrite[]]; Streams[])
+     = True
+
+    #> Close["abc"]
+     : abc is not open.
+     = Close[abc]
+
+    #> strm = OpenWrite[];
+    #> Close[strm];
+    #> Quiet[Close[strm]]
+     = Close[OutputStream[...]]
+    """
+
+    summary_text = "close a stream"
+    messages = {
+        "closex": "`1`.",
+    }
+
+    def apply(self, channel, evaluation):
+        "Close[channel_]"
+
+        if channel.has_form(("InputStream", "OutputStream"), 2):
+            [name, n] = channel.get_elements()
+            py_n = n.get_int_value()
+            stream = stream_manager.lookup_stream(py_n)
+        else:
+            stream = None
+
+        if stream is None or stream.io is None or stream.io.closed:
+            evaluation.message("General", "openx", channel)
+            return
+
+        close_stream(stream, n.value)
+        return name
+
+
+class EndOfFile(Builtin):
+    """
+    <dl>
+    <dt>'EndOfFile'
+      <dd>is returned by 'Read' when the end of an input stream is reached.
+    </dl>
+    """
+
+    summary_text = "end of the file"
+
+
+class Expression_(Builtin):
+    """
+    <dl>
+      <dt>'Expression'
+      <dd>is a data type for 'Read'.
+    </dl>
+
+    For information about underlying data structure Expression (a kind of M-expression) that is central in evaluation, see: <url>https://mathics-development-guide.readthedocs.io/en/latest/extending/code-overview/ast.html</url>
+    """
+
+    summary_text = "WL expression"
+    name = "Expression"
+
+
+class FilePrint(Builtin):
+    """
+    <dl>
+    <dt>'FilePrint[$file$]'
+      <dd>prints the raw contents of $file$.
+    </dl>
+
+    #> exp = Sin[1];
+    #> FilePrint[exp]
+     : File specification Sin[1] is not a string of one or more characters.
+     = FilePrint[Sin[1]]
+
+    #> FilePrint["somenonexistantpath_h47sdmk^&h4"]
+     : Cannot open somenonexistantpath_h47sdmk^&h4.
+     = FilePrint[somenonexistantpath_h47sdmk^&h4]
+
+    #> FilePrint[""]
+     : File specification  is not a string of one or more characters.
+     = FilePrint[]
+    """
+
+    summary_text = "display the contents of a file"
+    messages = {
+        "fstr": (
+            "File specification `1` is not a string of " "one or more characters."
+        ),
+    }
+
+    options = {
+        "CharacterEncoding": "$CharacterEncoding",
+        "RecordSeparators": '{"\r\n", "\n", "\r"}',
+        "WordSeparators": '{" ", "\t"}',
+    }
+
+    def apply(self, path, evaluation, options):
+        "FilePrint[path_ OptionsPattern[FilePrint]]"
+        pypath = path.to_python()
+        if not (
+            isinstance(pypath, str)
+            and pypath[0] == pypath[-1] == '"'
+            and len(pypath) > 2
+        ):
+            evaluation.message("FilePrint", "fstr", path)
+            return
+        pypath, is_temporary_file = path_search(pypath[1:-1])
+
+        # Options
+        record_separators = options["System`RecordSeparators"].to_python()
+        assert isinstance(record_separators, list)
+        assert all(
+            isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators
+        )
+        record_separators = [s[1:-1] for s in record_separators]
+
+        if pypath is None:
+            evaluation.message("General", "noopen", path)
+            return
+
+        if not osp.isfile(pypath):
+            return SymbolFailed
 
-        if typ.has_form("List", None):
-            types = typ.get_elements()
-        else:
-            types = [typ]
+        try:
+            with MathicsOpen(pypath, "r") as f:
+                result = f.read()
+        except IOError:
+            evaluation.message("General", "noopen", path)
+            return
+        except MessageException as e:
+            e.message(evaluation)
+            return
 
-        types = [t.get_string_value() for t in types]
-        if not all(t in self.readers for t in types):
-            evaluation.message("BinaryRead", "format", typ)
-            return expr
+        result = [result]
+        for sep in record_separators:
+            result = [item for res in result for item in res.split(sep)]
 
-        # Read from stream
-        result = []
-        for t in types:
-            try:
-                result.append(self.readers[t](stream.io))
-            except struct.error:
-                result.append(SymbolEndOfFile)
+        if result[-1] == "":
+            result = result[:-1]
 
-        if typ.has_form("List", None):
-            return Expression("List", *result)
-        else:
-            if len(result) == 1:
-                return result[0]
+        for res in result:
+            evaluation.print_out(String(res))
 
+        return SymbolNull
 
-class WriteString(Builtin):
+
+class Number_(Builtin):
     """
     <dl>
-    <dt>'WriteString[$stream$, $str1, $str2$, ... ]'
-      <dd>writes the strings to the output stream.
+    <dt>'Number'
+      <dd>is a data type for 'Read'.
     </dl>
+    """
 
-    >> stream = OpenWrite[];
-    >> WriteString[stream, "This is a test 1"]
-    >> WriteString[stream, "This is also a test 2"]
-    >> Close[stream]
-     = ...
-    >> FilePrint[%]
-     | This is a test 1This is also a test 2
-
-    >> stream = OpenWrite[];
-    >> WriteString[stream, "This is a test 1", "This is also a test 2"]
-    >> Close[stream]
-     = ...
-    >> FilePrint[%]
-     | This is a test 1This is also a test 2
-
-    #> stream = OpenWrite[];
-    #> WriteString[stream, 100, 1 + x + y, Sin[x  + y]]
-    #> Close[stream]
-     = ...
-    #> FilePrint[%]
-     | 1001 + x + ySin[x + y]
+    summary_text = "exact or approximate number in Fortran‐like notation"
+    name = "Number"
 
-    #> stream = OpenWrite[];
-    #> WriteString[stream]
-    #> Close[stream]
-     = ...
-    #> FilePrint[%]
 
-    #> WriteString[%%, abc]
-    #> Streams[%%%][[1]]
-     = ...
-    #> Close[%]
-     = ...
-    #> FilePrint[%]
-     | abc
+class Get(PrefixOperator):
+    r"""
+    <dl>
+      <dt>'<<$name$'
+      <dd>reads a file and evaluates each expression, returning only the last one.
 
-    """
+      <dt>'Get[$name$, Trace->True]'
+      <dd>Runs Get tracing each line before it is evaluated.
+    </dl>
 
-    summary_text = "write a sequence of strings to a stream, with no extra newlines"
-    messages = {
-        "strml": ("`1` is not a string, stream, " "or list of strings and streams."),
-        "writex": "`1`.",
-    }
+    S> filename = $TemporaryDirectory <> "/example_file";
+    S> Put[x + y, filename]
+    S> Get[filename]
+     = x + y
 
-    def apply(self, channel, expr, evaluation):
-        "WriteString[channel_, expr___]"
-        strm = channel_to_stream(channel, "w")
+    S> filename = $TemporaryDirectory <> "/example_file";
+    S> Put[x + y, 2x^2 + 4z!, Cos[x] + I Sin[x], filename]
+    S> Get[filename]
+     = Cos[x] + I Sin[x]
+    S> DeleteFile[filename]
 
-        if strm is None:
-            return
+    ## TODO: Requires EndPackage implemented
+    ## 'Get' can also load packages:
+    ## >> << "VectorAnalysis`"
 
-        stream = stream_manager.lookup_stream(strm.leaves[1].get_int_value())
+    #> Get["SomeTypoPackage`"]
+     : Cannot open SomeTypoPackage`.
+     = $Failed
 
-        if stream is None or stream.io is None or stream.io.closed:
-            return None
+    ## Parser Tests
+    #> Hold[<< ~/some_example/dir/] // FullForm
+     = Hold[Get["~/some_example/dir/"]]
+    #> Hold[<<`/.\-_:$*~?] // FullForm
+     = Hold[Get["`/.\\\\-_:$*~?"]]
+    """
+    summary_text = "read in a file and evaluate commands in it"
+    operator = "<<"
+    precedence = 720
+    options = {
+        "Trace": "False",
+    }
 
-        exprs = []
-        for expri in expr.get_sequence():
-            result = expri.format(evaluation, "System`OutputForm")
-            try:
-                result = result.boxes_to_text(evaluation=evaluation)
-            except BoxError:
-                return evaluation.message(
-                    "General",
-                    "notboxes",
-                    Expression("FullForm", result).evaluate(evaluation),
-                )
-            exprs.append(result)
-        line = "".join(exprs)
-        if type(stream) is BytesIO:
-            line = line.encode("utf8")
-        stream.io.write(line)
-        try:
-            stream.io.flush()
-        except IOError as err:
-            evaluation.message("WriteString", "writex", err.strerror)
-        return SymbolNull
+    def apply(self, path, evaluation, options):
+        "Get[path_String, OptionsPattern[Get]]"
 
+        def check_options(options):
+            # Options
+            # TODO Proper error messages
 
-class _OpenAction(Builtin):
+            result = {}
+            trace_get = evaluation.parse("Settings`$TraceGet")
+            if (
+                options["System`Trace"].to_python()
+                or trace_get.evaluate(evaluation) is SymbolTrue
+            ):
+                import builtins
 
-    # BinaryFormat: 'False',
-    # CharacterEncoding :> Automatic,
-    # DOSTextFormat :> True,
-    # FormatType -> InputForm,
-    # NumberMarks :> $NumberMarks,
-    # PageHeight -> 22, PageWidth -> 78,
-    # TotalHeight -> Infinity,
-    # TotalWidth -> Infinity
+                result["TraceFn"] = builtins.print
+            else:
+                result["TraceFn"] = None
 
-    options = {
-        "BinaryFormat": "False",
-        "CharacterEncoding": "$CharacterEncoding",
-    }
+            return result
 
-    messages = {
-        "argx": "OpenRead called with 0 arguments; 1 argument is expected.",
-        "fstr": (
-            "File specification `1` is not a string of " "one or more characters."
-        ),
-    }
+        py_options = check_options(options)
+        trace_fn = py_options["TraceFn"]
+        result = None
+        pypath = path.get_string_value()
+        definitions = evaluation.definitions
+        mathics.core.streams.PATH_VAR = SymbolPath.evaluate(evaluation).to_python(
+            string_quotes=False
+        )
+        try:
+            if trace_fn:
+                trace_fn(pypath)
+            with MathicsOpen(pypath, "r") as f:
+                feeder = MathicsFileLineFeeder(f, trace_fn)
+                while not feeder.empty():
+                    try:
+                        query = parse(definitions, feeder)
+                    except TranslateError:
+                        return SymbolNull
+                    finally:
+                        feeder.send_messages(evaluation)
+                    if query is None:  # blank line / comment
+                        continue
+                    result = query.evaluate(evaluation)
+        except IOError:
+            evaluation.message("General", "noopen", path)
+            return SymbolFailed
+        except MessageException as e:
+            e.message(evaluation)
+            return SymbolFailed
+        return result
 
-    def apply_empty(self, evaluation, options):
-        "%(name)s[OptionsPattern[]]"
+    def apply_default(self, filename, evaluation):
+        "Get[filename_]"
+        expr = Expression("Get", filename)
+        evaluation.message("General", "stream", filename)
+        return expr
 
-        if isinstance(self, (OpenWrite, OpenAppend)):
-            tmpf = tempfile.NamedTemporaryFile(dir=TMP_DIR, delete=True)
-            path = String(tmpf.name)
-            tmpf.close()
-            return self.apply_path(path, evaluation, options)
-        else:
-            evaluation.message("OpenRead", "argx")
-            return
 
-    def apply_path(self, path, evaluation, options):
-        "%(name)s[path_?NotOptionQ, OptionsPattern[]]"
+class InputFileName_(Predefined):
+    """
+    <dl>
+    <dt>'$InputFileName'
+      <dd>is the name of the file from which input is currently being read.
+    </dl>
 
-        # Options
-        # BinaryFormat
-        mode = self.mode
-        if options["System`BinaryFormat"] is SymbolTrue:
-            if not self.mode.endswith("b"):
-                mode += "b"
+    While in interactive mode, '$InputFileName' is "".
+    X> $InputFileName
+    """
 
-        if not (isinstance(path, String) and len(path.to_python()) > 2):
-            evaluation.message(self.__class__.__name__, "fstr", path)
-            return
+    summary_text = (
+        "the full absolute path to the file from which input is currently being sought"
+    )
+    name = "$InputFileName"
 
-        path_string = path.get_string_value()
+    def evaluate(self, evaluation):
+        return String(read.INPUTFILE_VAR)
 
-        tmp = path_search(path_string)
-        if tmp is None:
-            if mode in ["r", "rb"]:
-                evaluation.message("General", "noopen", path)
-                return
-        else:
-            path_string = tmp
 
-        try:
-            encoding = self.get_option(options, "CharacterEncoding", evaluation)
-            if not isinstance(encoding, String):
-                return
+class InputStream(Builtin):
+    """
+    <dl>
+    <dt>'InputStream[$name$, $n$]'
+      <dd>represents an input stream.
+    </dl>
 
-            opener = MathicsOpen(
-                path_string, mode=mode, encoding=encoding.get_string_value()
-            )
-            opener.__enter__()
-            n = opener.n
-        except IOError:
-            evaluation.message("General", "noopen", path)
-            return
-        except MessageException as e:
-            e.message(evaluation)
-            return
+    >> stream = StringToStream["Mathics is cool!"]
+     = ...
+    >> Close[stream]
+     = String
+    """
 
-        return Expression(self.stream_type, path, Integer(n))
+    summary_text = "an input stream"
 
 
 class OpenRead(_OpenAction):
     """
     <dl>
-    <dt>'OpenRead["file"]'
+      <dt>'OpenRead["file"]'
       <dd>opens a file and returns an InputStream.
     </dl>
 
@@ -1789,9 +1505,7 @@ class OpenRead(_OpenAction):
      = InputStream[...]
     #> Close[%];
 
-    S> OpenRead["https://raw.githubusercontent.com/Mathics3/mathics-core/master/README.rst"]
-     = InputStream[...]
-    S> Close[%];
+    S> Close[OpenRead["https://raw.githubusercontent.com/Mathics3/mathics-core/master/README.rst"]];
 
     #> OpenRead[]
      : OpenRead called with 0 arguments; 1 argument is expected.
@@ -1814,341 +1528,636 @@ class OpenRead(_OpenAction):
     #> Close[%];
     """
 
-    summary_text = "open a file for reading"
-    mode = "r"
-    stream_type = "InputStream"
+    summary_text = "open a file for reading"
+    mode = "r"
+    stream_type = "InputStream"
+
+
+class OpenWrite(_OpenAction):
+    """
+    <dl>
+    <dt>'OpenWrite["file"]'
+      <dd>opens a file and returns an OutputStream.
+    </dl>
+
+    >> OpenWrite[]
+     = OutputStream[...]
+    #> DeleteFile[Close[%]];
+
+    #> OpenWrite[BinaryFormat -> True]
+     = OutputStream[...]
+    #> DeleteFile[Close[%]];
+    """
+
+    summary_text = (
+        "send an output stream to a file, wiping out the previous contents of the file"
+    )
+    mode = "w"
+    stream_type = "OutputStream"
+
+
+class OpenAppend(_OpenAction):
+    """
+    <dl>
+    <dt>'OpenAppend["file"]'
+      <dd>opens a file and returns an OutputStream to which writes are appended.
+    </dl>
+
+    >> OpenAppend[]
+     = OutputStream[...]
+    #> DeleteFile[Close[%]];
+
+    #> appendFile = OpenAppend["MathicsNonExampleFile"]
+     = OutputStream[MathicsNonExampleFile, ...]
+
+    #> Close[appendFile]
+     = MathicsNonExampleFile
+    #> DeleteFile["MathicsNonExampleFile"]
+    """
+
+    summary_text = (
+        "open an output stream to a file, appending to what was already in the file"
+    )
+    mode = "a"
+    stream_type = "OutputStream"
+
+
+class Put(BinaryOperator):
+    """
+    <dl>
+    <dt>'$expr$ >> $filename$'
+      <dd>write $expr$ to a file.
+    <dt>'Put[$expr1$, $expr2$, ..., $filename$]'
+      <dd>write a sequence of expressions to a file.
+    </dl>
+
+    ## Note a lot of these tests are:
+    ## * a bit fragile, somewhat
+    ## * somewhat OS dependent,
+    ## * can leave crap in the filesystem
+    ## * put in a pytest
+    ##
+    ## For these reasons this should be done a a pure test
+    ## rather than intermingled with the doc system.
+
+    S> Put[40!, fortyfactorial]
+     : fortyfactorial is not string, InputStream[], or OutputStream[]
+     = 815915283247897734345611269596115894272000000000 >> fortyfactorial
+    ## FIXME: final line should be
+    ## = Put[815915283247897734345611269596115894272000000000, fortyfactorial]
+
+    S> filename = $TemporaryDirectory <> "/fortyfactorial";
+    S> Put[40!, filename]
+    S> FilePrint[filename]
+     | 815915283247897734345611269596115894272000000000
+    S> Get[filename]
+     = 815915283247897734345611269596115894272000000000
+    S> DeleteFile[filename]
+
+    S> filename = $TemporaryDirectory <> "/fiftyfactorial";
+    S> Put[10!, 20!, 30!, filename]
+    S> FilePrint[filename]
+     | 3628800
+     | 2432902008176640000
+     | 265252859812191058636308480000000
+
+    S> DeleteFile[filename]
+     =
+
+    S> filename = $TemporaryDirectory <> "/example_file";
+    S> Put[x + y, 2x^2 + 4z!, Cos[x] + I Sin[x], filename]
+    S> FilePrint[filename]
+     | x + y
+     | 2*x^2 + 4*z!
+     | Cos[x] + I*Sin[x]
+    S> DeleteFile[filename]
+    """
+
+    summary_text = "write an expression to a file"
+    operator = ">>"
+    precedence = 30
+
+    def apply(self, exprs, filename, evaluation):
+        "Put[exprs___, filename_String]"
+        instream = Expression("OpenWrite", filename).evaluate(evaluation)
+        if len(instream.leaves) == 2:
+            name, n = instream.leaves
+        else:
+            return  # opening failed
+        result = self.apply_input(exprs, name, n, evaluation)
+        instream_number = instream.elements[1].value
+        py_instream = stream_manager.lookup_stream(instream_number)
+
+        close_stream(py_instream, instream_number)
+        return result
+
+    def apply_input(self, exprs, name, n, evaluation):
+        "Put[exprs___, OutputStream[name_, n_]]"
+        stream = stream_manager.lookup_stream(n.get_int_value())
+
+        if stream is None or stream.io.closed:
+            evaluation.message("Put", "openx", Expression("OutputSteam", name, n))
+            return
+
+        text = [
+            evaluation.format_output(Expression("InputForm", expr))
+            for expr in exprs.get_sequence()
+        ]
+        text = "\n".join(text) + "\n"
+        text.encode("utf-8")
+
+        stream.io.write(text)
+
+        return SymbolNull
+
+    def apply_default(self, exprs, filename, evaluation):
+        "Put[exprs___, filename_]"
+        expr = Expression("Put", exprs, filename)
+        evaluation.message("General", "stream", filename)
+        return expr
+
+
+class PutAppend(BinaryOperator):
+    """
+    <dl>
+    <dt>'$expr$ >>> $filename$'
+      <dd>append $expr$ to a file.
+    <dt>'PutAppend[$expr1$, $expr2$, ..., $"filename"$]'
+      <dd>write a sequence of expressions to a file.
+    </dl>
+
+    >> Put[50!, "factorials"]
+    >> FilePrint["factorials"]
+     | 30414093201713378043612608166064768844377641568960512000000000000
+
+    >> PutAppend[10!, 20!, 30!, "factorials"]
+    >> FilePrint["factorials"]
+     | 30414093201713378043612608166064768844377641568960512000000000000
+     | 3628800
+     | 2432902008176640000
+     | 265252859812191058636308480000000
+
+    >> 60! >>> "factorials"
+    >> FilePrint["factorials"]
+     | 30414093201713378043612608166064768844377641568960512000000000000
+     | 3628800
+     | 2432902008176640000
+     | 265252859812191058636308480000000
+     | 8320987112741390144276341183223364380754172606361245952449277696409600000000000000
+
+    >> "string" >>> factorials
+    >> FilePrint["factorials"]
+     | 30414093201713378043612608166064768844377641568960512000000000000
+     | 3628800
+     | 2432902008176640000
+     | 265252859812191058636308480000000
+     | 8320987112741390144276341183223364380754172606361245952449277696409600000000000000
+     | "string"
+    #> DeleteFile["factorials"];
+
+    ## writing to dir
+    #> x >>> /var/
+     : Cannot open /var/.
+     = x >>> /var/
+
+    ## writing to read only file
+    #> x >>> /proc/uptime
+     : Cannot open /proc/uptime.
+     = x >>> /proc/uptime
+    """
+
+    summary_text = "append an expression to a file"
+    operator = ">>>"
+    precedence = 30
+
+    def apply(self, exprs, filename, evaluation):
+        "PutAppend[exprs___, filename_String]"
+        instream = Expression("OpenAppend", filename).evaluate(evaluation)
+        if len(instream.leaves) == 2:
+            name, n = instream.elements
+        else:
+            return  # opening failed
+        result = self.apply_input(exprs, name, n, evaluation)
+        Expression("Close", instream).evaluate(evaluation)
+        return result
+
+    def apply_input(self, exprs, name, n, evaluation):
+        "PutAppend[exprs___, OutputStream[name_, n_]]"
+        stream = stream_manager.lookup_stream(n.get_int_value())
+
+        if stream is None or stream.io.closed:
+            evaluation.message("Put", "openx", Expression("OutputSteam", name, n))
+            return
+
+        text = [
+            str(e.do_format(evaluation, "System`OutputForm").__str__())
+            for e in exprs.get_sequence()
+        ]
+        text = "\n".join(text) + "\n"
+        text.encode("ascii")
+
+        stream.io.write(text)
 
+        return SymbolNull
 
-class OpenWrite(_OpenAction):
-    """
-    <dl>
-    <dt>'OpenWrite["file"]'
-      <dd>opens a file and returns an OutputStream.
-    </dl>
+    def apply_default(self, exprs, filename, evaluation):
+        "PutAppend[exprs___, filename_]"
+        expr = Expression("PutAppend", exprs, filename)
+        evaluation.message("General", "stream", filename)
+        return expr
 
-    >> OpenWrite[]
-     = OutputStream[...]
-    #> Close[%];
 
-    #> OpenWrite[BinaryFormat -> True]
-     = OutputStream[...]
-    #> Close[%];
+class Read(Builtin):
     """
+    <dl>
+      <dt>'Read[$stream$]'
+      <dd>reads the input stream and returns one expression.
 
-    summary_text = (
-        "send an output stream to a file, wiping out the previous contents of the file"
-    )
-    mode = "w"
-    stream_type = "OutputStream"
+      <dt>'Read[$stream$, $type$]'
+      <dd>reads the input stream and returns an object of the given type.
 
+      <dt>'Read[$stream$, $type$]'
+      <dd>reads the input stream and returns an object of the given type.
+
+      <dt>'Read[$stream$, Hold[Expression]]'
+      <dd>reads the input stream for an Expression and puts it inside 'Hold'.
 
-class OpenAppend(_OpenAction):
-    """
-    <dl>
-    <dt>'OpenAppend["file"]'
-      <dd>opens a file and returns an OutputStream to which writes are appended.
     </dl>
+    $type$ is one of:
+    <ul>
+      <li>Byte
+      <li>Character
+      <li>Expression
+      <li>HoldExpression
+      <li>Number
+      <li>Real
+      <li>Record
+      <li>String
+      <li>Word
+    </ul>
 
-    >> OpenAppend[]
-     = OutputStream[...]
-    #> Close[%];
+    ## Malformed InputString
+    #> Read[InputStream[String], {Word, Number}]
+     = Read[InputStream[String], {Word, Number}]
 
-    #> appendFile = OpenAppend["MathicsNonExampleFile"]
-     = OutputStream[MathicsNonExampleFile, ...]
+    ## Correctly formed InputString but not open
+    #> Read[InputStream[String, -1], {Word, Number}]
+     : InputStream[String, -1] is not open.
+     = Read[InputStream[String, -1], {Word, Number}]
 
-    #> Close[appendFile]
-     = MathicsNonExampleFile
-    #> DeleteFile["MathicsNonExampleFile"]
-    """
+    ## Reading Strings
+    >> stream = StringToStream["abc123"];
+    >> Read[stream, String]
+     = abc123
+    #> Read[stream, String]
+     = EndOfFile
+    #> Close[stream];
 
-    summary_text = (
-        "open an output stream to a file, appending to what was already in the file"
-    )
-    mode = "a"
-    stream_type = "OutputStream"
+    ## Reading Words
+    >> stream = StringToStream["abc 123"];
+    >> Read[stream, Word]
+     = abc
+    >> Read[stream, Word]
+     = 123
+    #> Read[stream, Word]
+     = EndOfFile
+    #> Close[stream];
+    #> stream = StringToStream[""];
+    #> Read[stream, Word]
+     = EndOfFile
+    #> Read[stream, Word]
+     = EndOfFile
+    #> Close[stream];
 
+    ## Number
+    >> stream = StringToStream["123, 4"];
+    >> Read[stream, Number]
+     = 123
+    >> Read[stream, Number]
+     = 4
+    #> Read[stream, Number]
+     = EndOfFile
+    #> Close[stream];
+    #> stream = StringToStream["123xyz 321"];
+    #> Read[stream, Number]
+     = 123
+    #> Quiet[Read[stream, Number]]
+     = $Failed
 
-class Get(PrefixOperator):
-    r"""
-    <dl>
-      <dt>'<<$name$'
-      <dd>reads a file and evaluates each expression, returning only the last one.
+    ## Real
+    #> stream = StringToStream["123, 4abc"];
+    #> Read[stream, Real]
+     = 123.
+    #> Read[stream, Real]
+     = 4.
+    #> Quiet[Read[stream, Number]]
+     = $Failed
 
-      <dt>'Get[$name$, Trace->True]'
-      <dd>Runs Get tracing each line before it is evaluated.
-    </dl>
+    #> Close[stream];
+    #> stream = StringToStream["1.523E-19"]; Read[stream, Real]
+     = 1.523×10^-19
+    #> Close[stream];
+    #> stream = StringToStream["-1.523e19"]; Read[stream, Real]
+     = -1.523×10^19
+    #> Close[stream];
+    #> stream = StringToStream["3*^10"]; Read[stream, Real]
+     = 3.×10^10
+    #> Close[stream];
+    #> stream = StringToStream["3.*^10"]; Read[stream, Real]
+     = 3.×10^10
+    #> Close[stream];
 
-    S> filename = $TemporaryDirectory <> "/example_file";
-    S> Put[x + y, filename]
-    S> Get[filename]
-     = x + y
+    ## Expression
+    #> stream = StringToStream["x + y Sin[z]"]; Read[stream, Expression]
+     = x + y Sin[z]
+    #> Close[stream];
+    ## #> stream = Quiet[StringToStream["Sin[1 123"]; Read[stream, Expression]]
+    ##  = $Failed
 
-    S> filename = $TemporaryDirectory <> "/example_file";
-    S> Put[x + y, 2x^2 + 4z!, Cos[x] + I Sin[x], filename]
-    S> Get[filename]
-     = Cos[x] + I Sin[x]
-    S> DeleteFile[filename]
+    ## HoldExpression:
+    >> stream = StringToStream["2+2\\n2+3"];
 
-    ## TODO: Requires EndPackage implemented
-    ## 'Get' can also load packages:
-    ## >> << "VectorAnalysis`"
+    'Read' with a 'Hold[Expression]' returns the expression it reads unevaluated so it can be later inspected and evaluated:
 
-    #> Get["SomeTypoPackage`"]
-     : Cannot open SomeTypoPackage`.
-     = $Failed
+    >> Read[stream, Hold[Expression]]
+     = Hold[2 + 2]
 
-    ## Parser Tests
-    #> Hold[<< ~/some_example/dir/] // FullForm
-     = Hold[Get["~/some_example/dir/"]]
-    #> Hold[<<`/.\-_:$*~?] // FullForm
-     = Hold[Get["`/.\\\\-_:$*~?"]]
-    """
-    summary_text = "read in a file and evaluate commands in it"
-    operator = "<<"
-    precedence = 720
-    options = {
-        "Trace": "False",
-    }
+    >> Read[stream, Expression]
+     = 5
+    >> Close[stream];
 
-    def apply(self, path, evaluation, options):
-        "Get[path_String, OptionsPattern[Get]]"
+    Reading a comment however will return the empy list:
+    >> stream = StringToStream["(* ::Package:: *)"];
 
-        def check_options(options):
-            # Options
-            # TODO Proper error messages
+    >> Read[stream, Hold[Expression]]
+     = {}
 
-            result = {}
-            trace_get = evaluation.parse("Settings`$TraceGet")
-            if (
-                options["System`Trace"].to_python()
-                or trace_get.evaluate(evaluation) is SymbolTrue
-            ):
-                import builtins
+    >> Close[stream];
 
-                result["TraceFn"] = builtins.print
-            else:
-                result["TraceFn"] = None
+    ## Multiple types
+    >> stream = StringToStream["123 abc"];
+    >> Read[stream, {Number, Word}]
+     = {123, abc}
+    #> Read[stream, {Number, Word}]
+     = EndOfFile
+    #> lose[stream];
 
-            return result
+    #> stream = StringToStream["123 abc"];
+    #> Quiet[Read[stream, {Word, Number}]]
+     = $Failed
+    #> Close[stream];
 
-        py_options = check_options(options)
-        trace_fn = py_options["TraceFn"]
-        result = None
-        pypath = path.get_string_value()
-        definitions = evaluation.definitions
-        mathics.core.streams.PATH_VAR = SymbolPath.evaluate(evaluation).to_python(
-            string_quotes=False
-        )
-        try:
-            if trace_fn:
-                trace_fn(pypath)
-            with MathicsOpen(pypath, "r") as f:
-                feeder = MathicsFileLineFeeder(f, trace_fn)
-                while not feeder.empty():
-                    try:
-                        query = parse(definitions, feeder)
-                    except TranslateError:
-                        return SymbolNull
-                    finally:
-                        feeder.send_messages(evaluation)
-                    if query is None:  # blank line / comment
-                        continue
-                    result = query.evaluate(evaluation)
-        except IOError:
-            evaluation.message("General", "noopen", path)
-            return SymbolFailed
-        except MessageException as e:
-            e.message(evaluation)
-            return SymbolFailed
-        return result
+    #> stream = StringToStream["123 123"];  Read[stream, {Real, Number}]
+     = {123., 123}
+    #> Close[stream];
 
-    def apply_default(self, filename, evaluation):
-        "Get[filename_]"
-        expr = Expression("Get", filename)
-        evaluation.message("General", "stream", filename)
-        return expr
+    #> Quiet[Read[stream, {Real}]]
+     = Read[InputStream[String, ...], {Real}]
 
+    Multiple lines:
+    >> stream = StringToStream["\\"Tengo una\\nvaca lechera.\\""]; Read[stream]
+     = Tengo una
+     . vaca lechera.
 
-class Put(BinaryOperator):
     """
-    <dl>
-    <dt>'$expr$ >> $filename$'
-      <dd>write $expr$ to a file.
-    <dt>'Put[$expr1$, $expr2$, ..., $filename$]'
-      <dd>write a sequence of expressions to a file.
-    </dl>
 
-    ## Note a lot of these tests are:
-    ## * a bit fragile, somewhat
-    ## * somewhat OS dependent,
-    ## * can leave crap in the filesystem
-    ## * put in a pytest
-    ##
-    ## For these reasons this should be done a a pure test
-    ## rather than intermingled with the doc system.
+    summary_text = "read an object of the specified type from a stream"
+    messages = {
+        "openx": "`1` is not open.",
+        "readf": "`1` is not a valid format specification.",
+        "readn": "Invalid real number found when reading from `1`.",
+        "readt": "Invalid input found when reading `1` from `2`.",
+        "intnm": (
+            "Non-negative machine-sized integer expected at " "position 3 in `1`."
+        ),
+    }
 
-    S> Put[40!, fortyfactorial]
-     : fortyfactorial is not string, InputStream[], or OutputStream[]
-     = 815915283247897734345611269596115894272000000000 >> fortyfactorial
-    ## FIXME: final line should be
-    ## = Put[815915283247897734345611269596115894272000000000, fortyfactorial]
+    rules = {
+        "Read[stream_]": "Read[stream, Expression]",
+    }
 
-    S> filename = $TemporaryDirectory <> "/fortyfactorial";
-    S> Put[40!, filename]
-    S> FilePrint[filename]
-     | 815915283247897734345611269596115894272000000000
-    S> Get[filename]
-     = 815915283247897734345611269596115894272000000000
-    S> DeleteFile[filename]
+    options = {
+        "NullRecords": "False",
+        "NullWords": "False",
+        "RecordSeparators": '{"\r\n", "\n", "\r"}',
+        "TokenWords": "{}",
+        "WordSeparators": '{" ", "\t"}',
+    }
 
-    S> filename = $TemporaryDirectory <> "/fiftyfactorial";
-    S> Put[10!, 20!, 30!, filename]
-    S> FilePrint[filename]
-     | 3628800
-     | 2432902008176640000
-     | 265252859812191058636308480000000
+    def check_options(self, options):
+        # Options
+        # TODO Proper error messages
 
-    S> DeleteFile[filename]
-     =
+        result = {}
+        keys = list(options.keys())
 
-    S> filename = $TemporaryDirectory <> "/example_file";
-    S> Put[x + y, 2x^2 + 4z!, Cos[x] + I Sin[x], filename]
-    S> FilePrint[filename]
-     | x + y
-     | 2*x^2 + 4*z!
-     | Cos[x] + I*Sin[x]
-    S> DeleteFile[filename]
-    """
+        # AnchoredSearch
+        if "System`AnchoredSearch" in keys:
+            anchored_search = options["System`AnchoredSearch"].to_python()
+            assert anchored_search in [True, False]
+            result["AnchoredSearch"] = anchored_search
 
-    summary_text = "write an expression to a file"
-    operator = ">>"
-    precedence = 30
+        # IgnoreCase
+        if "System`IgnoreCase" in keys:
+            ignore_case = options["System`IgnoreCase"].to_python()
+            assert ignore_case in [True, False]
+            result["IgnoreCase"] = ignore_case
 
-    def apply(self, exprs, filename, evaluation):
-        "Put[exprs___, filename_String]"
-        instream = Expression("OpenWrite", filename).evaluate(evaluation)
-        if len(instream.leaves) == 2:
-            name, n = instream.leaves
-        else:
-            return  # opening failed
-        result = self.apply_input(exprs, name, n, evaluation)
-        Expression("Close", instream).evaluate(evaluation)
-        return result
+        # WordSearch
+        if "System`WordSearch" in keys:
+            word_search = options["System`WordSearch"].to_python()
+            assert word_search in [True, False]
+            result["WordSearch"] = word_search
 
-    def apply_input(self, exprs, name, n, evaluation):
-        "Put[exprs___, OutputStream[name_, n_]]"
-        stream = stream_manager.lookup_stream(n.get_int_value())
+        # RecordSeparators
+        if "System`RecordSeparators" in keys:
+            record_separators = options["System`RecordSeparators"].to_python()
+            assert isinstance(record_separators, list)
+            assert all(
+                isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators
+            )
+            record_separators = [s[1:-1] for s in record_separators]
+            result["RecordSeparators"] = record_separators
 
-        if stream is None or stream.io.closed:
-            evaluation.message("Put", "openx", Expression("OutputSteam", name, n))
-            return
+        # WordSeparators
+        if "System`WordSeparators" in keys:
+            word_separators = options["System`WordSeparators"].to_python()
+            assert isinstance(word_separators, list)
+            assert all(
+                isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators
+            )
+            word_separators = [s[1:-1] for s in word_separators]
+            result["WordSeparators"] = word_separators
 
-        text = [
-            evaluation.format_output(Expression("InputForm", expr))
-            for expr in exprs.get_sequence()
-        ]
-        text = "\n".join(text) + "\n"
-        text.encode("utf-8")
+        # NullRecords
+        if "System`NullRecords" in keys:
+            null_records = options["System`NullRecords"].to_python()
+            assert null_records in [True, False]
+            result["NullRecords"] = null_records
 
-        stream.io.write(text)
+        # NullWords
+        if "System`NullWords" in keys:
+            null_words = options["System`NullWords"].to_python()
+            assert null_words in [True, False]
+            result["NullWords"] = null_words
 
-        return SymbolNull
+        # TokenWords
+        if "System`TokenWords" in keys:
+            token_words = options["System`TokenWords"].to_python()
+            assert token_words == []
+            result["TokenWords"] = token_words
 
-    def apply_default(self, exprs, filename, evaluation):
-        "Put[exprs___, filename_]"
-        expr = Expression("Put", exprs, filename)
-        evaluation.message("General", "stream", filename)
-        return expr
+        return result
 
+    def apply(self, channel, types, evaluation, options):
+        "Read[channel_, types_, OptionsPattern[Read]]"
 
-class PutAppend(BinaryOperator):
-    """
-    <dl>
-    <dt>'$expr$ >>> $filename$'
-      <dd>append $expr$ to a file.
-    <dt>'PutAppend[$expr1$, $expr2$, ..., $"filename"$]'
-      <dd>write a sequence of expressions to a file.
-    </dl>
+        name, n, stream = read_name_and_stream_from_channel(channel, evaluation)
+        if name is None:
+            return
 
-    >> Put[50!, "factorials"]
-    >> FilePrint["factorials"]
-     | 30414093201713378043612608166064768844377641568960512000000000000
+        # Wrap types in a list (if it isn't already one)
+        if types.has_form("List", None):
+            types = types.elements
+        else:
+            types = (types,)
 
-    >> PutAppend[10!, 20!, 30!, "factorials"]
-    >> FilePrint["factorials"]
-     | 30414093201713378043612608166064768844377641568960512000000000000
-     | 3628800
-     | 2432902008176640000
-     | 265252859812191058636308480000000
+        # TODO: look for a better implementation handling "Hold[Expression]".
+        #
+        types = (
+            Symbol("HoldExpression")
+            if (
+                typ.get_head_name() == "System`Hold"
+                and typ.leaves[0].get_name() == "System`Expression"
+            )
+            else typ
+            for typ in types
+        )
+        types = Expression("List", *types)
 
-    >> 60! >>> "factorials"
-    >> FilePrint["factorials"]
-     | 30414093201713378043612608166064768844377641568960512000000000000
-     | 3628800
-     | 2432902008176640000
-     | 265252859812191058636308480000000
-     | 8320987112741390144276341183223364380754172606361245952449277696409600000000000000
+        for typ in types.leaves:
+            if typ not in READ_TYPES:
+                evaluation.message("Read", "readf", typ)
+                return SymbolFailed
 
-    >> "string" >>> factorials
-    >> FilePrint["factorials"]
-     | 30414093201713378043612608166064768844377641568960512000000000000
-     | 3628800
-     | 2432902008176640000
-     | 265252859812191058636308480000000
-     | 8320987112741390144276341183223364380754172606361245952449277696409600000000000000
-     | "string"
-    #> DeleteFile["factorials"];
+        record_separators, word_separators = read_get_separators(options)
 
-    ## writing to dir
-    #> x >>> /var/
-     : Cannot open /var/.
-     = x >>> /var/
+        name = name.to_python()
 
-    ## writing to read only file
-    #> x >>> /proc/uptime
-     : Cannot open /proc/uptime.
-     = x >>> /proc/uptime
-    """
+        result = []
 
-    summary_text = "append an expression to a file"
-    operator = ">>>"
-    precedence = 30
+        read_word = read_from_stream(stream, word_separators, evaluation.message)
+        read_record = read_from_stream(stream, record_separators, evaluation.message)
+        read_number = read_from_stream(
+            stream,
+            word_separators + record_separators,
+            evaluation.message,
+            ["+", "-", "."] + [str(i) for i in range(10)],
+        )
+        read_real = read_from_stream(
+            stream,
+            word_separators + record_separators,
+            evaluation.message,
+            ["+", "-", ".", "e", "E", "^", "*"] + [str(i) for i in range(10)],
+        )
+
+        from mathics.core.expression import BaseElement
+        from mathics_scanner.errors import IncompleteSyntaxError, InvalidSyntaxError
+        from mathics.core.parser import MathicsMultiLineFeeder, parse
+
+        for typ in types.leaves:
+            try:
+                if typ is Symbol("Byte"):
+                    tmp = stream.io.read(1)
+                    if tmp == "":
+                        raise EOFError
+                    result.append(ord(tmp))
+                elif typ is Symbol("Character"):
+                    tmp = stream.io.read(1)
+                    if tmp == "":
+                        raise EOFError
+                    result.append(tmp)
+                elif typ is Symbol("Expression") or typ is Symbol("HoldExpression"):
+                    tmp = next(read_record)
+                    while True:
+                        try:
+                            feeder = MathicsMultiLineFeeder(tmp)
+                            expr = parse(evaluation.definitions, feeder)
+                            break
+                        except (IncompleteSyntaxError, InvalidSyntaxError):
+                            try:
+                                nextline = next(read_record)
+                                tmp = tmp + "\n" + nextline
+                            except EOFError:
+                                expr = SymbolEndOfFile
+                                break
+                        except Exception as e:
+                            print(e)
 
-    def apply(self, exprs, filename, evaluation):
-        "PutAppend[exprs___, filename_String]"
-        instream = Expression("OpenAppend", filename).evaluate(evaluation)
-        if len(instream.leaves) == 2:
-            name, n = instream.leaves
-        else:
-            return  # opening failed
-        result = self.apply_input(exprs, name, n, evaluation)
-        Expression("Close", instream).evaluate(evaluation)
-        return result
+                    if expr is SymbolEndOfFile:
+                        evaluation.message(
+                            "Read", "readt", tmp, Expression("InputSteam", name, n)
+                        )
+                        return SymbolFailed
+                    elif isinstance(expr, BaseElement):
+                        if typ is Symbol("HoldExpression"):
+                            expr = Expression("Hold", expr)
+                        result.append(expr)
+                    # else:
+                    #  TODO: Supposedly we can't get here
+                    # what code should we put here?
 
-    def apply_input(self, exprs, name, n, evaluation):
-        "PutAppend[exprs___, OutputStream[name_, n_]]"
-        stream = stream_manager.lookup_stream(n.get_int_value())
+                elif typ is Symbol("Number"):
+                    tmp = next(read_number)
+                    try:
+                        tmp = int(tmp)
+                    except ValueError:
+                        try:
+                            tmp = float(tmp)
+                        except ValueError:
+                            evaluation.message(
+                                "Read", "readn", Expression("InputSteam", name, n)
+                            )
+                            return SymbolFailed
+                    result.append(tmp)
 
-        if stream is None or stream.io.closed:
-            evaluation.message("Put", "openx", Expression("OutputSteam", name, n))
-            return
+                elif typ is Symbol("Real"):
+                    tmp = next(read_real)
+                    tmp = tmp.replace("*^", "E")
+                    try:
+                        tmp = float(tmp)
+                    except ValueError:
+                        evaluation.message(
+                            "Read", "readn", Expression("InputSteam", name, n)
+                        )
+                        return SymbolFailed
+                    result.append(tmp)
+                elif typ is Symbol("Record"):
+                    result.append(next(read_record))
+                elif typ is Symbol("String"):
+                    tmp = stream.io.readline()
+                    if len(tmp) == 0:
+                        raise EOFError
+                    result.append(tmp.rstrip("\n"))
+                elif typ is Symbol("Word"):
+                    result.append(next(read_word))
 
-        text = [
-            str(e.do_format(evaluation, "System`OutputForm").__str__())
-            for e in exprs.get_sequence()
-        ]
-        text = "\n".join(text) + "\n"
-        text.encode("ascii")
+            except EOFError:
+                return SymbolEndOfFile
+            except UnicodeDecodeError:
+                evaluation.message("General", "ucdec")
 
-        stream.io.write(text)
+        if isinstance(result, Symbol):
+            return result
+        if len(result) == 1:
+            return from_python(*result)
 
-        return SymbolNull
+        return from_python(result)
 
-    def apply_default(self, exprs, filename, evaluation):
-        "PutAppend[exprs___, filename_]"
-        expr = Expression("PutAppend", exprs, filename)
-        evaluation.message("General", "stream", filename)
-        return expr
+    def apply_nostream(self, arg1, arg2, evaluation):
+        "Read[arg1_, arg2_]"
+        evaluation.message("General", "stream", arg1)
+        return
 
 
 class ReadList(Read):
@@ -2198,213 +2207,79 @@ class ReadList(Read):
     """
     >> ReadList[StringToStream["123\nabc"]]
      = {123, abc}
-    >> InputForm[%]
-     = {123, abc}
-    """
-    summary_text = "read a sequence of elements from a file, and put them in a WL list"
-    rules = {
-        "ReadList[stream_]": "ReadList[stream, Expression]",
-    }
-
-    options = {
-        "NullRecords": "False",
-        "NullWords": "False",
-        "RecordSeparators": '{"\r\n", "\n", "\r"}',
-        "TokenWords": "{}",
-        "WordSeparators": '{" ", "\t"}',
-    }
-
-    def apply(self, channel, types, evaluation, options):
-        "ReadList[channel_, types_, OptionsPattern[ReadList]]"
-
-        # Options
-        # TODO: Implement extra options
-        # py_options = self.check_options(options)
-        # null_records = py_options['NullRecords']
-        # null_words = py_options['NullWords']
-        # record_separators = py_options['RecordSeparators']
-        # token_words = py_options['TokenWords']
-        # word_separators = py_options['WordSeparators']
-
-        result = []
-        while True:
-            tmp = super(ReadList, self).apply(channel, types, evaluation, options)
-
-            if tmp is None:
-                return
-
-            if tmp is SymbolFailed:
-                return
-
-            if tmp is SymbolEndOfFile:
-                break
-            result.append(tmp)
-        return from_python(result)
-
-    def apply_m(self, channel, types, m, evaluation, options):
-        "ReadList[channel_, types_, m_, OptionsPattern[ReadList]]"
-
-        # Options
-        # TODO: Implement extra options
-        # py_options = self.check_options(options)
-        # null_records = py_options['NullRecords']
-        # null_words = py_options['NullWords']
-        # record_separators = py_options['RecordSeparators']
-        # token_words = py_options['TokenWords']
-        # word_separators = py_options['WordSeparators']
-
-        py_m = m.get_int_value()
-        if py_m < 0:
-            evaluation.message(
-                "ReadList", "intnm", Expression("ReadList", channel, types, m)
-            )
-            return
-
-        result = []
-        for i in range(py_m):
-            tmp = super(ReadList, self).apply(channel, types, evaluation, options)
-
-            if tmp is SymbolFailed:
-                return
-
-            if tmp.to_python() == "EndOfFile":
-                break
-            result.append(tmp)
-        return from_python(result)
-
-
-class FilePrint(Builtin):
-    """
-    <dl>
-    <dt>'FilePrint[$file$]'
-      <dd>prints the raw contents of $file$.
-    </dl>
-
-    #> exp = Sin[1];
-    #> FilePrint[exp]
-     : File specification Sin[1] is not a string of one or more characters.
-     = FilePrint[Sin[1]]
-
-    #> FilePrint["somenonexistantpath_h47sdmk^&h4"]
-     : Cannot open somenonexistantpath_h47sdmk^&h4.
-     = FilePrint[somenonexistantpath_h47sdmk^&h4]
-
-    #> FilePrint[""]
-     : File specification  is not a string of one or more characters.
-     = FilePrint[]
-    """
-
-    summary_text = "display the contents of a file"
-    messages = {
-        "fstr": (
-            "File specification `1` is not a string of " "one or more characters."
-        ),
-    }
-
-    options = {
-        "CharacterEncoding": "$CharacterEncoding",
-        "RecordSeparators": '{"\r\n", "\n", "\r"}',
-        "WordSeparators": '{" ", "\t"}',
-    }
-
-    def apply(self, path, evaluation, options):
-        "FilePrint[path_ OptionsPattern[FilePrint]]"
-        pypath = path.to_python()
-        if not (
-            isinstance(pypath, str)
-            and pypath[0] == pypath[-1] == '"'
-            and len(pypath) > 2
-        ):
-            evaluation.message("FilePrint", "fstr", path)
-            return
-        pypath = path_search(pypath[1:-1])
-
-        # Options
-        record_separators = options["System`RecordSeparators"].to_python()
-        assert isinstance(record_separators, list)
-        assert all(
-            isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators
-        )
-        record_separators = [s[1:-1] for s in record_separators]
-
-        if pypath is None:
-            evaluation.message("General", "noopen", path)
-            return
-
-        if not osp.isfile(pypath):
-            return SymbolFailed
-
-        try:
-            with MathicsOpen(pypath, "r") as f:
-                result = f.read()
-        except IOError:
-            evaluation.message("General", "noopen", path)
-            return
-        except MessageException as e:
-            e.message(evaluation)
-            return
-
-        result = [result]
-        for sep in record_separators:
-            result = [item for res in result for item in res.split(sep)]
-
-        if result[-1] == "":
-            result = result[:-1]
-
-        for res in result:
-            evaluation.print_out(String(res))
+    >> InputForm[%]
+     = {123, abc}
+    """
+    summary_text = "read a sequence of elements from a file, and put them in a WL list"
+    rules = {
+        "ReadList[stream_]": "ReadList[stream, Expression]",
+    }
 
-        return SymbolNull
+    options = {
+        "NullRecords": "False",
+        "NullWords": "False",
+        "RecordSeparators": '{"\r\n", "\n", "\r"}',
+        "TokenWords": "{}",
+        "WordSeparators": '{" ", "\t"}',
+    }
 
+    def apply(self, channel, types, evaluation, options):
+        "ReadList[channel_, types_, OptionsPattern[ReadList]]"
 
-class Close(Builtin):
-    """
-    <dl>
-    <dt>'Close[$stream$]'
-      <dd>closes an input or output stream.
-    </dl>
+        # Options
+        # TODO: Implement extra options
+        # py_options = self.check_options(options)
+        # null_records = py_options['NullRecords']
+        # null_words = py_options['NullWords']
+        # record_separators = py_options['RecordSeparators']
+        # token_words = py_options['TokenWords']
+        # word_separators = py_options['WordSeparators']
 
-    >> Close[StringToStream["123abc"]]
-     = String
+        result = []
+        while True:
+            tmp = super(ReadList, self).apply(channel, types, evaluation, options)
 
-    >> Close[OpenWrite[]]
-     = ...
+            if tmp is None:
+                return
 
-    #> Streams[] == (Close[OpenWrite[]]; Streams[])
-     = True
+            if tmp is SymbolFailed:
+                return
 
-    #> Close["abc"]
-     : abc is not open.
-     = Close[abc]
+            if tmp is SymbolEndOfFile:
+                break
+            result.append(tmp)
+        return from_python(result)
 
-    #> strm = OpenWrite[];
-    #> Close[strm];
-    #> Quiet[Close[strm]]
-     = Close[OutputStream[...]]
-    """
+    def apply_m(self, channel, types, m, evaluation, options):
+        "ReadList[channel_, types_, m_, OptionsPattern[ReadList]]"
 
-    summary_text = "close a stream"
-    messages = {
-        "closex": "`1`.",
-    }
+        # Options
+        # TODO: Implement extra options
+        # py_options = self.check_options(options)
+        # null_records = py_options['NullRecords']
+        # null_words = py_options['NullWords']
+        # record_separators = py_options['RecordSeparators']
+        # token_words = py_options['TokenWords']
+        # word_separators = py_options['WordSeparators']
 
-    def apply(self, channel, evaluation):
-        "Close[channel_]"
+        py_m = m.get_int_value()
+        if py_m < 0:
+            evaluation.message(
+                "ReadList", "intnm", Expression("ReadList", channel, types, m)
+            )
+            return
 
-        if channel.has_form(("InputStream", "OutputStream"), 2):
-            [name, n] = channel.get_elements()
-            py_n = n.get_int_value()
-            stream = stream_manager.lookup_stream(py_n)
-        else:
-            stream = None
+        result = []
+        for i in range(py_m):
+            tmp = super(ReadList, self).apply(channel, types, evaluation, options)
 
-        if stream is None or stream.io is None or stream.io.closed:
-            evaluation.message("General", "openx", channel)
-            return
+            if tmp is SymbolFailed:
+                return
 
-        stream.io.close()
-        stream_manager.delete(py_n)
-        return name
+            if tmp.to_python() == "EndOfFile":
+                break
+            result.append(tmp)
+        return from_python(result)
 
 
 class StreamPosition(Builtin):
@@ -2675,22 +2550,6 @@ def apply(self, name, n, text, evaluation, options):
                     return from_python(py_tmp)
 
 
-class InputStream(Builtin):
-    """
-    <dl>
-    <dt>'InputStream[$name$, $n$]'
-      <dd>represents an input stream.
-    </dl>
-
-    >> stream = StringToStream["Mathics is cool!"]
-     = ...
-    >> Close[stream]
-     = String
-    """
-
-    summary_text = "an input stream"
-
-
 class OutputStream(Builtin):
     """
     <dl>
@@ -2789,3 +2648,158 @@ def apply_name(self, name, evaluation):
             if name is None or _name == name:
                 result.append(expr)
         return Expression("List", *result)
+
+
+class Record(Builtin):
+    """
+    <dl>
+    <dt>'Record'
+      <dd>is a data type for 'Read'.
+    </dl>
+    """
+
+    summary_text = "sequence of characters delimited by record separators"
+
+
+class Word(Builtin):
+    """
+    <dl>
+    <dt>'Word'
+      <dd>is a data type for 'Read'.
+    </dl>
+    """
+
+    summary_text = "sequence of characters delimited by word separators"
+
+
+class Write(Builtin):
+    """
+    <dl>
+    <dt>'Write[$channel$, $expr1$, $expr2$, ...]'
+      <dd>writes the expressions to the output channel followed by a newline.
+    </dl>
+
+    >> stream = OpenWrite[]
+     = ...
+    >> Write[stream, 10 x + 15 y ^ 2]
+    >> Write[stream, 3 Sin[z]]
+    >> Close[stream];
+    >> stream = OpenRead[%];
+    >> ReadList[stream]
+     = {10 x + 15 y ^ 2, 3 Sin[z]}
+    #> DeleteFile[Close[stream]];
+    """
+
+    summary_text = "write a sequence of expressions to a stream, ending the output with a newline (line feed)"
+
+    def apply(self, channel, expr, evaluation):
+        "Write[channel_, expr___]"
+
+        strm = channel_to_stream(channel)
+
+        if strm is None:
+            return
+
+        n = strm.leaves[1].get_int_value()
+        stream = stream_manager.lookup_stream(n)
+
+        if stream is None or stream.io is None or stream.io.closed:
+            evaluation.message("General", "openx", channel)
+            return SymbolNull
+
+        expr = expr.get_sequence()
+        expr = Expression("Row", Expression("List", *expr))
+
+        evaluation.format = "text"
+        text = evaluation.format_output(expr)
+        stream.io.write(str(text) + "\n")
+        return SymbolNull
+
+
+class WriteString(Builtin):
+    """
+    <dl>
+    <dt>'WriteString[$stream$, $str1, $str2$, ... ]'
+      <dd>writes the strings to the output stream.
+    </dl>
+
+    >> stream = OpenWrite[];
+    >> WriteString[stream, "This is a test 1"]
+    >> WriteString[stream, "This is also a test 2"]
+    >> pathname = Close[stream];
+    >> FilePrint[%]
+     | This is a test 1This is also a test 2
+
+    #> DeleteFile[pathname];
+    >> stream = OpenWrite[];
+    >> WriteString[stream, "This is a test 1", "This is also a test 2"]
+    >> pathname = Close[stream]
+     = ...
+    >> FilePrint[%]
+     | This is a test 1This is also a test 2
+
+    #> DeleteFile[pathname];
+    #> stream = OpenWrite[];
+    #> WriteString[stream, 100, 1 + x + y, Sin[x  + y]]
+    #> pathname = Close[stream]
+     = ...
+    #> FilePrint[%]
+     | 1001 + x + ySin[x + y]
+
+    #> DeleteFile[pathname];
+    #> stream = OpenWrite[];
+    #> WriteString[stream]
+    #> pathame = Close[stream]
+     = ...
+    #> FilePrint[%]
+
+    #> WriteString[%%, abc]
+    #> Streams[%%%][[1]]
+     = ...
+    #> pathname = Close[%];
+    #> FilePrint[%]
+     | abc
+    #> DeleteFile[pathname];
+    #> Clear[pathname];
+
+    """
+
+    summary_text = "write a sequence of strings to a stream, with no extra newlines"
+    messages = {
+        "strml": ("`1` is not a string, stream, " "or list of strings and streams."),
+        "writex": "`1`.",
+    }
+
+    def apply(self, channel, expr, evaluation):
+        "WriteString[channel_, expr___]"
+        strm = channel_to_stream(channel, "w")
+
+        if strm is None:
+            return
+
+        stream = stream_manager.lookup_stream(strm.leaves[1].get_int_value())
+
+        if stream is None or stream.io is None or stream.io.closed:
+            return None
+
+        exprs = []
+        for expri in expr.get_sequence():
+            result = expri.format(evaluation, "System`OutputForm")
+            try:
+                result = result.boxes_to_text(evaluation=evaluation)
+            except BoxError:
+                return evaluation.message(
+                    "General",
+                    "notboxes",
+                    Expression("FullForm", result).evaluate(evaluation),
+                )
+            exprs.append(result)
+        line = "".join(exprs)
+        if type(stream) is BytesIO:
+            line = line.encode("utf8")
+        stream.io.write(line)
+        try:
+            stream.io.flush()
+        except IOError as err:
+            evaluation.message("WriteString", "writex", err.strerror)
+        return SymbolNull
diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py
index c28b74599..3db73e6be 100644
--- a/mathics/builtin/files_io/filesystem.py
+++ b/mathics/builtin/files_io/filesystem.py
@@ -79,7 +79,7 @@ def apply(self, name, evaluation):
             return
         py_name = py_name[1:-1]
 
-        result = path_search(py_name)
+        result, is_temporary_file = path_search(py_name)
 
         if result is None:
             evaluation.message(
@@ -196,7 +196,7 @@ def apply(self, source, dest, evaluation):
         py_source = py_source[1:-1]
         py_dest = py_dest[1:-1]
 
-        py_source = path_search(py_source)
+        py_source, is_temporary_file = path_search(py_source)
 
         if py_source is None:
             evaluation.message("CopyFile", "filex", source)
@@ -439,7 +439,7 @@ def apply(self, filename, evaluation):
                 return
 
             path = path[1:-1]
-            path = path_search(path)
+            path, is_temporary_file = path_search(path)
 
             if path is None:
                 evaluation.message(
@@ -602,7 +602,7 @@ def apply(self, pathname, evaluation):
             return
         path = path[1:-1]
 
-        path = path_search(path)
+        path, is_temporary_file = path_search(path)
 
         if path is not None and osp.isdir(path):
             return SymbolTrue
@@ -784,7 +784,7 @@ class FileDate(Builtin):
 
     def apply(self, path, timetype, evaluation):
         "FileDate[path_, timetype_]"
-        py_path = path_search(path.to_python()[1:-1])
+        py_path, is_temparary_file = path_search(path.to_python()[1:-1])
 
         if py_path is None:
             if timetype is None:
@@ -857,7 +857,7 @@ def apply(self, filename, evaluation):
             return
         path = path[1:-1]
 
-        path = path_search(path)
+        path, is_temporary_file = path_search(path)
 
         if path is None:
             return SymbolFalse
@@ -1122,7 +1122,7 @@ def apply(self, filename, evaluation):
             return
         path = filename.to_python()[1:-1]
 
-        path = path_search(path)
+        path, is_temporary_file = path_search(path)
 
         if path is None:
             return Symbol("None")
@@ -1170,7 +1170,7 @@ def apply(self, name, evaluation):
             return
         py_name = py_name[1:-1]
 
-        result = path_search(py_name)
+        result, is_temporary_file = path_search(py_name)
 
         if result is None:
             return SymbolFailed
@@ -1910,7 +1910,7 @@ def apply(self, source, dest, evaluation):
         py_source = py_source[1:-1]
         py_dest = py_dest[1:-1]
 
-        py_source = path_search(py_source)
+        py_source, is_temporary_file = path_search(py_source)
 
         if py_source is None:
             evaluation.message("RenameFile", "filex", source)
@@ -2104,7 +2104,7 @@ def apply(self, filename, datelist, attribute, evaluation):
         ):
             evaluation.message("SetFileDate", "fstr", filename)
             return
-        py_filename = path_search(py_filename[1:-1])
+        py_filename, is_temporary_file = path_search(py_filename[1:-1])
 
         if py_filename is None:
             evaluation.message("SetFileDate", "nffil", expr)
diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py
index 270f2d15f..3259b68ca 100644
--- a/mathics/builtin/files_io/importexport.py
+++ b/mathics/builtin/files_io/importexport.py
@@ -4,6 +4,8 @@
 Importing and Exporting
 """
 
+import os
+import sys
 
 from mathics.core.atoms import (
     ByteArrayAtom,
@@ -1888,9 +1890,10 @@ def _infer_form(self, filename, evaluation):
 class ExportString(Builtin):
     """
     <dl>
-    <dt>'ExportString[$expr$, $form$]'
+      <dt>'ExportString[$expr$, $form$]'
       <dd>exports $expr$ to a string, in the format $form$.
-    <dt>'Export["$file$", $exprs$, $elems$]'
+
+      <dt>'Export["$file$", $exprs$, $elems$]'
       <dd>exports $exprs$ to a string as elements specified by $elems$.
     </dl>
 
@@ -2014,6 +2017,12 @@ def apply_elements(self, expr, elems, evaluation, **options):
                         tmpstream = open(filename.value, "r")
                     res = tmpstream.read()
                     tmpstream.close()
+                    if sys.platform not in ("win32",):
+                        # On Windows unlink make the second NamedTemporaryFIle
+                        # fail giving something like:
+                        #   [WinError 32] The process cannot access the file because it is being used by another process: ...
+                        #    \\AppData\\Local\\Temp\\Mathics3-ExportString35eo_rih.svg'
+                        os.unlink(tmpstream.name)
                 except Exception as e:
                     print("something went wrong")
                     print(e)
diff --git a/mathics/core/read.py b/mathics/core/read.py
index b795e0024..f40c78d78 100644
--- a/mathics/core/read.py
+++ b/mathics/core/read.py
@@ -3,6 +3,7 @@
 """
 
 import io
+import os
 import os.path as osp
 
 from mathics.builtin.base import MessageException
@@ -37,7 +38,7 @@
 
 
 class MathicsOpen(Stream):
-    def __init__(self, name, mode="r", encoding=None):
+    def __init__(self, name, mode="r", encoding=None, is_temporary_file=False):
         if encoding is not None:
             encoding = to_python_encoding(encoding)
             if "b" in mode:
@@ -48,10 +49,11 @@ def __init__(self, name, mode="r", encoding=None):
         self.encoding = encoding
         super().__init__(name, mode, self.encoding)
         self.old_inputfile_var = None  # Set in __enter__ and __exit__
+        self.is_temporary_file = is_temporary_file
 
-    def __enter__(self):
+    def __enter__(self, is_temporary_file=False):
         # find path
-        path = path_search(self.name)
+        path, _ = path_search(self.name)
         if path is None and self.mode in ["w", "a", "wb", "ab"]:
             path = self.name
         if path is None:
@@ -68,6 +70,7 @@ def __enter__(self):
             encoding=self.encoding,
             io=fp,
             num=stream_manager.next,
+            is_temporary_file=is_temporary_file,
         )
         return fp
 
@@ -98,6 +101,15 @@ def channel_to_stream(channel, mode="r"):
         return None
 
 
+def close_stream(stream: Stream, stream_number: int):
+    """
+    Close stream: `stream` and delete it from the list of streams we manage.
+    If the stream was to a temporary file, remove the temporary file.
+    """
+    stream.io.close()
+    stream_manager.delete(stream_number)
+
+
 def read_name_and_stream_from_channel(channel, evaluation):
     if channel.has_form("OutputStream", 2):
         evaluation.message("General", "openw", channel)
diff --git a/mathics/core/streams.py b/mathics/core/streams.py
index 2b24569c9..c057cb18f 100644
--- a/mathics/core/streams.py
+++ b/mathics/core/streams.py
@@ -4,11 +4,12 @@
 File Stream Operations
 """
 from io import open as io_open
+import os
 import requests
 import sys
 import tempfile
 
-from typing import Optional
+from typing import Optional, Tuple
 
 import os.path as osp
 
@@ -47,19 +48,28 @@ def urlsave_tmp(url, location=None, **kwargs):
     return result
 
 
-def path_search(filename):
+def path_search(filename: str) -> Tuple[str, bool]:
+    """
+    Search for a Mathics `filename` possibly adding extensions ".mx", or ".m"
+    or as a file under directory PATH_VAR or as an Internet address.
+
+    Return the resolved file name and True if this is a file in the
+    a temporary file created, which happens for Internet addresses,
+    or False if the file is a file in the filesystem.
+    """
     # For names of the form "name`", search for name.mx and name.m
+    is_temporary_file = False
     if filename[-1] == "`":
         filename = filename[:-1].replace("`", osp.sep)
         for ext in [".mx", ".m"]:
-            result = path_search(filename + ext)
+            result, is_temporary_file = path_search(filename + ext)
             if result is not None:
                 filename = None
                 break
     if filename is not None:
         result = None
-        # If filename is an internet address, download the file
-        # and store it in a temporal location
+        # If filename is an Internet address, download the file
+        # and store it in a tempory file
         lenfn = len(filename)
         if (
             (lenfn > 7 and filename[:7] == "http://")
@@ -67,6 +77,7 @@ def path_search(filename):
             or (lenfn > 6 and filename[:6] == "ftp://")
         ):
             result = urlsave_tmp(filename)
+            is_temporary_file = True
         else:
             for p in PATH_VAR + [""]:
                 path = osp.join(p, filename)
@@ -74,13 +85,13 @@ def path_search(filename):
                     result = path
                     break
 
-            # If FindFile resolves to a dir, search within for Kernel/init.m and init.m
+            # If `result` resolves to a dir, search within for Kernel/init.m and init.m
             if result is not None and osp.isdir(result):
                 for ext in [osp.join("Kernel", "init.m"), "init.m"]:
                     tmp = osp.join(result, ext)
                     if osp.isfile(tmp):
-                        return tmp
-    return result
+                        return tmp, is_temporary_file
+    return result, is_temporary_file
 
 
 class StreamsManager(object):
@@ -108,6 +119,7 @@ def add(
         encoding=None,
         io=None,
         num: Optional[int] = None,
+        is_temporary_file: bool = False,
     ) -> Optional["Stream"]:
         if num is None:
             num = self.next
@@ -116,18 +128,21 @@ def add(
         found = self.lookup_stream(num)
         if found and found is not None:
             raise Exception(f"Stream {num} already open")
-        stream = Stream(name, mode, encoding, io, num)
+        stream = Stream(name, mode, encoding, io, num, is_temporary_file)
         self.STREAMS[num] = stream
         return stream
 
     def delete(self, n: int) -> bool:
         stream = self.STREAMS.get(n, None)
+        is_temporary_file = stream.is_temporary_file
+        if is_temporary_file:
+            os.unlink(stream.name)
         if stream is not None:
             del self.STREAMS[stream.n]
             return True
         return False
 
-    def lookup_stream(self, n=None) -> Optional["Stream"]:
+    def lookup_stream(self, n: Optional[int] = None) -> Optional["Stream"]:
         if n is None:
             return None
         return self.STREAMS.get(n, None)
@@ -153,7 +168,15 @@ class Stream(object):
     However see MathicsOpen which wraps this
     """
 
-    def __init__(self, name: str, mode="r", encoding=None, io=None, channel_num=None):
+    def __init__(
+        self,
+        name: str,
+        mode="r",
+        encoding=None,
+        io=None,
+        channel_num=None,
+        is_temporary_file: bool = False,
+    ):
         if channel_num is None:
             channel_num = stream_manager.next
         if mode is None:
@@ -163,13 +186,14 @@ def __init__(self, name: str, mode="r", encoding=None, io=None, channel_num=None
         self.encoding = encoding
         self.io = io
         self.n = channel_num
+        self.is_temporary_file = is_temporary_file
 
         if mode not in ["r", "w", "a", "rb", "wb", "ab"]:
             raise ValueError("Can't handle mode {0}".format(mode))
 
     def __enter__(self):
         # find path
-        path = path_search(self.name)
+        path, is_temporary_file = path_search(self.name)
         if path is None and self.mode in ["w", "a", "wb", "ab"]:
             path = self.name
         if path is None:
@@ -183,7 +207,13 @@ def __enter__(self):
 
         # open the stream
         fp = io_open(path, self.mode, encoding=encoding)
-        stream_manager.add(name=path, mode=self.mode, encoding=encoding, io=fp)
+        stream_manager.add(
+            name=path,
+            mode=self.mode,
+            encoding=encoding,
+            io=fp,
+            is_temporary_file=is_temporary_file,
+        )
         return fp
 
     def __exit__(self, type, value, traceback):


Test patch:
diff --git a/test/builtin/colors/test_colors.py b/test/builtin/colors/test_colors.py
index b8d665177..6b21fcf5b 100755
--- a/test/builtin/colors/test_colors.py
+++ b/test/builtin/colors/test_colors.py
@@ -1,7 +1,6 @@
-#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 """
-Unit tests from builtins ... colors.py
+Unit tests from builtins/colors/colors.py
 """
 
 
diff --git a/test/builtin/files_io/test_files.py b/test/builtin/files_io/test_files.py
new file mode 100644
index 000000000..b2ca80a5c
--- /dev/null
+++ b/test/builtin/files_io/test_files.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from builtins/files_io/files.py
+"""
+import os.path as osp
+import pytest
+import sys
+from test.helper import check_evaluation, evaluate
+
+
+def test_compress():
+    for text in ("", "abc", " "):
+        str_expr = f'Uncompress[Compress["{text}"]]'
+        str_expected = f'"{text}"'
+        check_evaluation(
+            str_expr, str_expected, to_string_expr=False, to_string_expected=False
+        )
+
+
+def test_unprotected():
+    for str_expr, str_expected, message in (
+        ("Attributes[$Path]", "{}", ""),
+        ("Attributes[$InstallationDirectory]", "{}", ""),
+    ):
+        check_evaluation(str_expr, str_expected, message)
+
+
+@pytest.mark.skipif(
+    sys.platform in ("win32",), reason="POSIX pathname tests do not work on Windows"
+)
+def test_get_and_put():
+    temp_filename = evaluate('$TemporaryDirectory<>"/testfile"').to_python()
+    temp_filename_strip = temp_filename[1:-1]
+    check_evaluation(f"40! >> {temp_filename_strip}", "Null")
+    check_evaluation(f"<< {temp_filename_strip}", "40!")
+    check_evaluation(f"DeleteFile[{temp_filename}]", "Null")
+
+
+@pytest.mark.skipif(
+    sys.platform in ("win32",), reason="$Path does not work on Windows?"
+)
+def test_get_path_search():
+    # Check that AppendTo[$Path] works in conjunction with Get[]
+    dirname = osp.join(osp.dirname(osp.abspath(__file__)), "..", "..", "data")
+    evaled = evaluate(f"""AppendTo[$Path, "{dirname}"]""")
+    assert evaled.has_form("List", 1, None)
+    check_evaluation('Get["fortytwo.m"]', "42")
+
+
+@pytest.mark.skipif(
+    sys.platform in ("win32",), reason="$Path does not work on Windows?"
+)
+def test_temptream():
+    temp_filename = evaluate("Close[OpenWrite[BinaryFormat -> True]]").value
+    assert osp.exists(
+        temp_filename
+    ), f"temporary filename {temp_filename} should not appear"
+    result = evaluate(f"""DeleteFile["{temp_filename}"]""").to_python()
+    assert result is None
+    assert not osp.exists(
+        temp_filename
+    ), f"temporary filename {temp_filename} should not appear"
+
+
+# I do not know what this is it supposed to test with this...
+# def test_Inputget_and_put():
+#    stream = Expression('Plus', Symbol('x'), Integer(2))
+
+# TODO: add these Unix-specific test. Be sure not to test
+# sys.platform for not Windows and to test for applicability
+# ## writing to dir
+# S> x >> /var/
+#  : Cannot open /var/.
+#  = x >> /var/
+
+# ## writing to read only file
+# S> x >> /proc/uptime
+#  : Cannot open /proc/uptime.
+#  = x >> /proc/uptime
+
+# ## writing to full file
+# S> x >> /dev/full
+#  : No space left on device.
+
+# #> WriteString[OpenWrite["/dev/zero"], "abc"]   (* Null *)
+#     ## Return $Failed on special files
+#     #> FilePrint["/dev/zero"]
+#      = $Failed
+#     #> FilePrint["/dev/random"]
+#      = $Failed
+#     #> FilePrint["/dev/null"]
+#      = $Failed
diff --git a/test/test_files.py b/test/test_files.py
deleted file mode 100644
index 95ba82d90..000000000
--- a/test/test_files.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-import os.path as osp
-import sys
-from .helper import check_evaluation, evaluate
-
-
-def test_compress():
-    for text in ("", "abc", " "):
-        str_expr = f'Uncompress[Compress["{text}"]]'
-        str_expected = f'"{text}"'
-        check_evaluation(
-            str_expr, str_expected, to_string_expr=False, to_string_expected=False
-        )
-
-
-def test_unprotected():
-    for str_expr, str_expected, message in (
-        ("Attributes[$Path]", "{}", ""),
-        ("Attributes[$InstallationDirectory]", "{}", ""),
-    ):
-        check_evaluation(str_expr, str_expected, message)
-
-
-if sys.platform not in ("win32",):
-
-    def test_get_and_put():
-        temp_filename = evaluate('$TemporaryDirectory<>"/testfile"').to_python()
-        temp_filename_strip = temp_filename[1:-1]
-        check_evaluation(f"40! >> {temp_filename_strip}", "Null")
-        check_evaluation(f"<< {temp_filename_strip}", "40!")
-        check_evaluation(f"DeleteFile[{temp_filename}]", "Null")
-
-    def test_get_path_search():
-        # Check that AppendTo[$Path] works in conjunction with Get[]
-        dirname = osp.join(osp.dirname(osp.abspath(__file__)), "data")
-        evaled = evaluate(f"""AppendTo[$Path, "{dirname}"]""")
-        assert evaled.has_form("List", 1, None)
-        check_evaluation('Get["fortytwo.m"]', "42")
-
-
-# I do not know what this is it supposed to test with this...
-# def test_Inputget_and_put():
-#    stream = Expression('Plus', Symbol('x'), Integer(2))
-
-# TODO: add these Unix-specific test. Be sure not to test
-# sys.platform for not Windows and to test for applicability
-# ## writing to dir
-# S> x >> /var/
-#  : Cannot open /var/.
-#  = x >> /var/
-
-# ## writing to read only file
-# S> x >> /proc/uptime
-#  : Cannot open /proc/uptime.
-#  = x >> /proc/uptime
-
-# ## writing to full file
-# S> x >> /dev/full
-#  : No space left on device.
-
-# #> WriteString[OpenWrite["/dev/zero"], "abc"]   (* Null *)
-#     ## Return $Failed on special files
-#     #> FilePrint["/dev/zero"]
-#      = $Failed
-#     #> FilePrint["/dev/random"]
-#      = $Failed
-#     #> FilePrint["/dev/null"]
-#      = $Failed