1# -*- coding: utf-8 -*-
2# cython: language_level=3
3
4"""
5File and Stream Operations
6"""
7
8import io
9import math
10import mpmath
11import os
12import struct
13import sympy
14import tempfile
15
16from io import BytesIO
17import os.path as osp
18from itertools import chain
19
20from mathics.version import __version__  # noqa used in loading to check consistency.
21
22from mathics_scanner.errors import IncompleteSyntaxError, InvalidSyntaxError
23from mathics_scanner import TranslateError
24from mathics.core.parser import MathicsFileLineFeeder, MathicsMultiLineFeeder, parse
25
26
27from mathics.core.expression import (
28    BoxError,
29    Complex,
30    BaseExpression,
31    Expression,
32    Integer,
33    MachineReal,
34    Real,
35    String,
36    Symbol,
37    SymbolFailed,
38    SymbolNull,
39    SymbolTrue,
40    from_mpmath,
41    from_python,
42)
43from mathics.core.numbers import dps
44from mathics.core.streams import (
45    Stream,
46    path_search,
47    stream_manager,
48)
49import mathics
50from mathics.builtin.base import Builtin, Predefined, BinaryOperator, PrefixOperator
51from mathics.builtin.strings import to_python_encoding
52from mathics.builtin.base import MessageException
53
54INITIAL_DIR = os.getcwd()
55DIRECTORY_STACK = [INITIAL_DIR]
56
57INPUT_VAR = ""
58INPUTFILE_VAR = ""
59
60TMP_DIR = tempfile.gettempdir()
61SymbolPath = Symbol("$Path")
62
63def _channel_to_stream(channel, mode="r"):
64    if isinstance(channel, String):
65        name = channel.get_string_value()
66        opener = mathics_open(name, mode)
67        opener.__enter__()
68        n = opener.n
69        if mode in ["r", "rb"]:
70            head = "InputStream"
71        elif mode in ["w", "a", "wb", "ab"]:
72            head = "OutputStream"
73        else:
74            raise ValueError(f"Unknown format {mode}")
75        return Expression(head, channel, Integer(n))
76    elif channel.has_form("InputStream", 2):
77        return channel
78    elif channel.has_form("OutputStream", 2):
79        return channel
80    else:
81        return None
82
83
84class mathics_open(Stream):
85    def __init__(self, name, mode="r", encoding=None):
86        if encoding is not None:
87            encoding = to_python_encoding(encoding)
88            if "b" in mode:
89                # We should not specify an encoding for a binary mode
90                encoding = None
91            elif encoding is None:
92                raise MessageException("General", "charcode", self.encoding)
93        self.encoding = encoding
94        super().__init__(name, mode, self.encoding)
95        self.old_inputfile_var = None  # Set in __enter__ and __exit__
96
97    def __enter__(self):
98        # find path
99        path = path_search(self.name)
100        if path is None and self.mode in ["w", "a", "wb", "ab"]:
101            path = self.name
102        if path is None:
103            raise IOError
104
105        # open the stream
106        fp = io.open(path, self.mode, encoding=self.encoding)
107        global INPUTFILE_VAR
108        INPUTFILE_VAR = osp.abspath(path)
109
110        stream_manager.add(
111            name=path,
112            mode=self.mode,
113            encoding=self.encoding,
114            io=fp,
115            num=stream_manager.next,
116        )
117        return fp
118
119    def __exit__(self, type, value, traceback):
120        global INPUTFILE_VAR
121        INPUTFILE_VAR = self.old_inputfile_var or ""
122        super().__exit__(type, value, traceback)
123
124
125class Input(Predefined):
126    """
127    <dl>
128    <dt>'$Input'
129      <dd>is the name of the stream from which input is currently being read.
130    </dl>
131
132    >> $Input
133     = #<--#
134    """
135
136    attributes = ("Protected", "ReadProtected")
137    name = "$Input"
138
139    def evaluate(self, evaluation):
140        global INPUT_VAR
141        return String(INPUT_VAR)
142
143
144class InputFileName(Predefined):
145    """
146    <dl>
147    <dt>'$InputFileName'
148      <dd>is the name of the file from which input is currently being read.
149    </dl>
150
151    While in interactive mode, '$InputFileName' is "".
152    X> $InputFileName
153    """
154
155    name = "$InputFileName"
156
157    def evaluate(self, evaluation):
158        global INPUTFILE_VAR
159        return String(INPUTFILE_VAR)
160
161
162class EndOfFile(Builtin):
163    """
164    <dl>
165    <dt>'EndOfFile'
166      <dd>is returned by 'Read' when the end of an input stream is reached.
167    </dl>
168    """
169
170SymbolEndOfFile = Symbol("EndOfFile")
171
172
173# TODO: Improve docs for these Read[] arguments.
174class Byte(Builtin):
175    """
176    <dl>
177    <dt>'Byte'
178      <dd>is a data type for 'Read'.
179    </dl>
180    """
181
182
183class Character(Builtin):
184    """
185    <dl>
186    <dt>'Character'
187      <dd>is a data type for 'Read'.
188    </dl>
189    """
190
191
192class Expression_(Builtin):
193    """
194    <dl>
195    <dt>'Expression'
196      <dd>is a data type for 'Read'.
197    </dl>
198    """
199
200    name = "Expression"
201
202
203class Number_(Builtin):
204    """
205    <dl>
206    <dt>'Number'
207      <dd>is a data type for 'Read'.
208    </dl>
209    """
210
211    name = "Number"
212
213
214class Record(Builtin):
215    """
216    <dl>
217    <dt>'Record'
218      <dd>is a data type for 'Read'.
219    </dl>
220    """
221
222
223class Word(Builtin):
224    """
225    <dl>
226    <dt>'Word'
227      <dd>is a data type for 'Read'.
228    </dl>
229    """
230
231
232class Read(Builtin):
233    """
234    <dl>
235      <dt>'Read[$stream$]'
236      <dd>reads the input stream and returns one expression.
237
238      <dt>'Read[$stream$, $type$]'
239      <dd>reads the input stream and returns an object of the given type.
240
241      <dt>'Read[$stream$, $type$]'
242      <dd>reads the input stream and returns an object of the given type.
243
244      <dt>'Read[$stream$, Hold[Expression]]'
245      <dd>reads the input stream for an Expression and puts it inside 'Hold'.
246
247    </dl>
248    $type$ is one of:
249    <ul>
250      <li>Byte
251      <li>Character
252      <li>Expression
253      <li>HoldExpression
254      <li>Number
255      <li>Real
256      <li>Record
257      <li>String
258      <li>Word
259    </ul>
260
261    ## Malformed InputString
262    #> Read[InputStream[String], {Word, Number}]
263     = Read[InputStream[String], {Word, Number}]
264
265    ## Correctly formed InputString but not open
266    #> Read[InputStream[String, -1], {Word, Number}]
267     : InputStream[String, -1] is not open.
268     = Read[InputStream[String, -1], {Word, Number}]
269
270    ## Reading Strings
271    >> stream = StringToStream["abc123"];
272    >> Read[stream, String]
273     = abc123
274    #> Read[stream, String]
275     = EndOfFile
276    #> Close[stream];
277
278    ## Reading Words
279    >> stream = StringToStream["abc 123"];
280    >> Read[stream, Word]
281     = abc
282    >> Read[stream, Word]
283     = 123
284    #> Read[stream, Word]
285     = EndOfFile
286    #> Close[stream];
287    #> stream = StringToStream[""];
288    #> Read[stream, Word]
289     = EndOfFile
290    #> Read[stream, Word]
291     = EndOfFile
292    #> Close[stream];
293
294    ## Number
295    >> stream = StringToStream["123, 4"];
296    >> Read[stream, Number]
297     = 123
298    >> Read[stream, Number]
299     = 4
300    #> Read[stream, Number]
301     = EndOfFile
302    #> Close[stream];
303    #> stream = StringToStream["123xyz 321"];
304    #> Read[stream, Number]
305     = 123
306    #> Quiet[Read[stream, Number]]
307     = $Failed
308
309    ## Real
310    #> stream = StringToStream["123, 4abc"];
311    #> Read[stream, Real]
312     = 123.
313    #> Read[stream, Real]
314     = 4.
315    #> Quiet[Read[stream, Number]]
316     = $Failed
317
318    #> Close[stream];
319    #> stream = StringToStream["1.523E-19"]; Read[stream, Real]
320     = 1.523*^-19
321    #> Close[stream];
322    #> stream = StringToStream["-1.523e19"]; Read[stream, Real]
323     = -1.523*^19
324    #> Close[stream];
325    #> stream = StringToStream["3*^10"]; Read[stream, Real]
326     = 3.*^10
327    #> Close[stream];
328    #> stream = StringToStream["3.*^10"]; Read[stream, Real]
329     = 3.*^10
330    #> Close[stream];
331
332    ## Expression
333    #> stream = StringToStream["x + y Sin[z]"]; Read[stream, Expression]
334     = x + y Sin[z]
335    #> Close[stream];
336    ## #> stream = Quiet[StringToStream["Sin[1 123"]; Read[stream, Expression]]
337    ##  = $Failed
338
339    ## HoldExpression:
340    >> stream = StringToStream["2+2\\n2+3"];
341
342    'Read' with a 'Hold[Expression]' returns the expression it reads unevaluated so it can be later inspected and evaluated:
343
344    >> Read[stream, Hold[Expression]]
345     = Hold[2 + 2]
346
347    >> Read[stream, Expression]
348     = 5
349    >> Close[stream];
350
351    Reading a comment however will return the empy list:
352    >> stream = StringToStream["(* ::Package:: *)"];
353
354    >> Read[stream, Hold[Expression]]
355     = {}
356
357    >> Close[stream];
358
359    ## Multiple types
360    >> stream = StringToStream["123 abc"];
361    >> Read[stream, {Number, Word}]
362     = {123, abc}
363    #> Read[stream, {Number, Word}]
364     = EndOfFile
365    #> Close[stream];
366
367    #> stream = StringToStream["123 abc"];
368    #> Quiet[Read[stream, {Word, Number}]]
369     = $Failed
370    #> Close[stream];
371
372    #> stream = StringToStream["123 123"];  Read[stream, {Real, Number}]
373     = {123., 123}
374    #> Close[stream];
375
376    #> Quiet[Read[stream, {Real}]]
377     = Read[InputStream[String, ...], {Real}]
378
379    Multiple lines:
380    >> stream = StringToStream["\\"Tengo una\\nvaca lechera.\\""]; Read[stream]
381     = Tengo una
382     . vaca lechera.
383
384    """
385
386    messages = {
387        "openx": "`1` is not open.",
388        "readf": "`1` is not a valid format specification.",
389        "readn": "Invalid real number found when reading from `1`.",
390        "readt": "Invalid input found when reading `1` from `2`.",
391        "intnm": (
392            "Non-negative machine-sized integer expected at " "position 3 in `1`."
393        ),
394    }
395
396    rules = {
397        "Read[stream_]": "Read[stream, Expression]",
398    }
399
400    options = {
401        "NullRecords": "False",
402        "NullWords": "False",
403        "RecordSeparators": '{"\r\n", "\n", "\r"}',
404        "TokenWords": "{}",
405        "WordSeparators": '{" ", "\t"}',
406    }
407
408    attributes = "Protected"
409
410    def check_options(self, options):
411        # Options
412        # TODO Proper error messages
413
414        result = {}
415        keys = list(options.keys())
416
417        # AnchoredSearch
418        if "System`AnchoredSearch" in keys:
419            anchored_search = options["System`AnchoredSearch"].to_python()
420            assert anchored_search in [True, False]
421            result["AnchoredSearch"] = anchored_search
422
423        # IgnoreCase
424        if "System`IgnoreCase" in keys:
425            ignore_case = options["System`IgnoreCase"].to_python()
426            assert ignore_case in [True, False]
427            result["IgnoreCase"] = ignore_case
428
429        # WordSearch
430        if "System`WordSearch" in keys:
431            word_search = options["System`WordSearch"].to_python()
432            assert word_search in [True, False]
433            result["WordSearch"] = word_search
434
435        # RecordSeparators
436        if "System`RecordSeparators" in keys:
437            record_separators = options["System`RecordSeparators"].to_python()
438            assert isinstance(record_separators, list)
439            assert all(
440                isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators
441            )
442            record_separators = [s[1:-1] for s in record_separators]
443            result["RecordSeparators"] = record_separators
444
445        # WordSeparators
446        if "System`WordSeparators" in keys:
447            word_separators = options["System`WordSeparators"].to_python()
448            assert isinstance(word_separators, list)
449            assert all(
450                isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators
451            )
452            word_separators = [s[1:-1] for s in word_separators]
453            result["WordSeparators"] = word_separators
454
455        # NullRecords
456        if "System`NullRecords" in keys:
457            null_records = options["System`NullRecords"].to_python()
458            assert null_records in [True, False]
459            result["NullRecords"] = null_records
460
461        # NullWords
462        if "System`NullWords" in keys:
463            null_words = options["System`NullWords"].to_python()
464            assert null_words in [True, False]
465            result["NullWords"] = null_words
466
467        # TokenWords
468        if "System`TokenWords" in keys:
469            token_words = options["System`TokenWords"].to_python()
470            assert token_words == []
471            result["TokenWords"] = token_words
472
473        return result
474
475    def apply(self, channel, types, evaluation, options):
476        "Read[channel_, types_, OptionsPattern[Read]]"
477
478        if channel.has_form("OutputStream", 2):
479            evaluation.message("General", "openw", channel)
480            return
481
482        strm = _channel_to_stream(channel, "r")
483
484        if strm is None:
485            return
486
487        [name, n] = strm.get_leaves()
488
489        stream = stream_manager.lookup_stream(n.get_int_value())
490        if stream is None:
491            evaluation.message("Read", "openx", strm)
492            return
493        if stream.io is None:
494            stream.__enter__()
495
496        if stream.io.closed:
497            evaluation.message("Read", "openx", strm)
498            return
499
500        # Wrap types in a list (if it isn't already one)
501        if types.has_form("List", None):
502            types = types._leaves
503        else:
504            types = (types,)
505
506        # TODO: look for a better implementation handling "Hold[Expression]".
507        #
508        types = (
509            Symbol("HoldExpression")
510            if (
511                typ.get_head_name() == "System`Hold"
512                and typ.leaves[0].get_name() == "System`Expression"
513            )
514            else typ
515            for typ in types
516        )
517        types = Expression("List", *types)
518
519        READ_TYPES = [
520            Symbol(k)
521            for k in [
522                "Byte",
523                "Character",
524                "Expression",
525                "HoldExpression",
526                "Number",
527                "Real",
528                "Record",
529                "String",
530                "Word",
531            ]
532        ]
533
534        for typ in types.leaves:
535            if typ not in READ_TYPES:
536                evaluation.message("Read", "readf", typ)
537                return SymbolFailed
538
539        # Options
540        # TODO Implement extra options
541        py_options = self.check_options(options)
542        # null_records = py_options['NullRecords']
543        # null_words = py_options['NullWords']
544        record_separators = py_options["RecordSeparators"]
545        # token_words = py_options['TokenWords']
546        word_separators = py_options["WordSeparators"]
547
548        name = name.to_python()
549
550        result = []
551
552        def reader(stream, word_separators, accepted=None):
553            while True:
554                word = ""
555                while True:
556                    try:
557                        tmp = stream.io.read(1)
558                    except UnicodeDecodeError:
559                        tmp = " "  # ignore
560                        evaluation.message("General", "ucdec")
561
562                    if tmp == "":
563                        if word == "":
564                            pos = stream.io.tell()
565                            newchar = stream.io.read(1)
566                            if pos == stream.io.tell():
567                                raise EOFError
568                            else:
569                                if newchar:
570                                    word = newchar
571                                    continue
572                                else:
573                                    yield word
574                                    continue
575                        last_word = word
576                        word = ""
577                        yield last_word
578                        break
579
580                    if tmp in word_separators:
581                        if word == "":
582                            continue
583                        if stream.io.seekable():
584                            # stream.io.seek(-1, 1) #Python3
585                            stream.io.seek(stream.io.tell() - 1)
586                        last_word = word
587                        word = ""
588                        yield last_word
589                        break
590
591                    if accepted is not None and tmp not in accepted:
592                        last_word = word
593                        word = ""
594                        yield last_word
595                        break
596
597                    word += tmp
598
599        read_word = reader(stream, word_separators)
600        read_record = reader(stream, record_separators)
601        read_number = reader(
602            stream,
603            word_separators + record_separators,
604            ["+", "-", "."] + [str(i) for i in range(10)],
605        )
606        read_real = reader(
607            stream,
608            word_separators + record_separators,
609            ["+", "-", ".", "e", "E", "^", "*"] + [str(i) for i in range(10)],
610        )
611        for typ in types.leaves:
612            try:
613                if typ == Symbol("Byte"):
614                    tmp = stream.io.read(1)
615                    if tmp == "":
616                        raise EOFError
617                    result.append(ord(tmp))
618                elif typ == Symbol("Character"):
619                    tmp = stream.io.read(1)
620                    if tmp == "":
621                        raise EOFError
622                    result.append(tmp)
623                elif typ == Symbol("Expression") or typ == Symbol("HoldExpression"):
624                    tmp = next(read_record)
625                    while True:
626                        try:
627                            feeder = MathicsMultiLineFeeder(tmp)
628                            expr = parse(evaluation.definitions, feeder)
629                            break
630                        except (IncompleteSyntaxError, InvalidSyntaxError):
631                            try:
632                                nextline = next(read_record)
633                                tmp = tmp + "\n" + nextline
634                            except EOFError:
635                                expr = SymbolEndOfFile
636                                break
637                        except Exception as e:
638                            print(e)
639
640                    if expr == SymbolEndOfFile:
641                        evaluation.message(
642                            "Read", "readt", tmp, Expression("InputSteam", name, n)
643                        )
644                        return SymbolFailed
645                    elif isinstance(expr, BaseExpression):
646                        if typ == Symbol("HoldExpression"):
647                            expr = Expression("Hold", expr)
648                        result.append(expr)
649                    # else:
650                    #  TODO: Supposedly we can't get here
651                    # what code should we put here?
652
653                elif typ == Symbol("Number"):
654                    tmp = next(read_number)
655                    try:
656                        tmp = int(tmp)
657                    except ValueError:
658                        try:
659                            tmp = float(tmp)
660                        except ValueError:
661                            evaluation.message(
662                                "Read", "readn", Expression("InputSteam", name, n)
663                            )
664                            return SymbolFailed
665                    result.append(tmp)
666
667                elif typ == Symbol("Real"):
668                    tmp = next(read_real)
669                    tmp = tmp.replace("*^", "E")
670                    try:
671                        tmp = float(tmp)
672                    except ValueError:
673                        evaluation.message(
674                            "Read", "readn", Expression("InputSteam", name, n)
675                        )
676                        return SymbolFailed
677                    result.append(tmp)
678                elif typ == Symbol("Record"):
679                    result.append(next(read_record))
680                elif typ == Symbol("String"):
681                    tmp = stream.io.readline()
682                    if len(tmp) == 0:
683                        raise EOFError
684                    result.append(tmp.rstrip("\n"))
685                elif typ == Symbol("Word"):
686                    result.append(next(read_word))
687
688            except EOFError:
689                return SymbolEndOfFile
690            except UnicodeDecodeError:
691                evaluation.message("General", "ucdec")
692
693        if len(result) == 1:
694            return from_python(*result)
695
696        return from_python(result)
697
698    def apply_nostream(self, arg1, arg2, evaluation):
699        "Read[arg1_, arg2_]"
700        evaluation.message("General", "stream", arg1)
701        return
702
703
704class Write(Builtin):
705    """
706    <dl>
707    <dt>'Write[$channel$, $expr1$, $expr2$, ...]'
708      <dd>writes the expressions to the output channel followed by a newline.
709    </dl>
710
711    >> stream = OpenWrite[]
712     = ...
713    >> Write[stream, 10 x + 15 y ^ 2]
714    >> Write[stream, 3 Sin[z]]
715    >> Close[stream]
716     = ...
717    >> stream = OpenRead[%];
718    >> ReadList[stream]
719     = {10 x + 15 y ^ 2, 3 Sin[z]}
720    #> Close[stream];
721    """
722
723    attributes = "Protected"
724
725    def apply(self, channel, expr, evaluation):
726        "Write[channel_, expr___]"
727
728        strm = _channel_to_stream(channel)
729
730        if strm is None:
731            return
732
733        n = strm.leaves[1].get_int_value()
734        stream = stream_manager.lookup_stream(n)
735
736        if stream is None or stream.io is None or stream.io.closed:
737            evaluation.message("General", "openx", channel)
738            return SymbolNull
739
740        expr = expr.get_sequence()
741        expr = Expression("Row", Expression("List", *expr))
742
743        evaluation.format = "text"
744        text = evaluation.format_output(from_python(expr))
745        stream.io.write(str(text) + "\n")
746        return SymbolNull
747
748
749class _BinaryFormat(object):
750    """
751    Container for BinaryRead readers and BinaryWrite writers
752    """
753
754    @staticmethod
755    def _IEEE_real(real):
756        if math.isnan(real):
757            return Symbol("Indeterminate")
758        elif math.isinf(real):
759            return Expression("DirectedInfinity", Integer((-1) ** (real < 0)))
760        else:
761            return Real(real)
762
763    @staticmethod
764    def _IEEE_cmplx(real, imag):
765        if math.isnan(real) or math.isnan(imag):
766            return Symbol("Indeterminate")
767        elif math.isinf(real) or math.isinf(imag):
768            if math.isinf(real) and math.isinf(imag):
769                return Symbol("Indeterminate")
770            return Expression(
771                "DirectedInfinity",
772                Expression(
773                    "Complex",
774                    (-1) ** (real < 0) if math.isinf(real) else 0,
775                    (-1) ** (imag < 0) if math.isinf(imag) else 0,
776                ),
777            )
778        else:
779            return Complex(MachineReal(real), MachineReal(imag))
780
781    @classmethod
782    def get_readers(cls):
783        readers = {}
784        for funcname in dir(cls):
785            if funcname.startswith("_") and funcname.endswith("_reader"):
786                readers[funcname[1:-7]] = getattr(cls, funcname)
787        return readers
788
789    @classmethod
790    def get_writers(cls):
791        writers = {}
792        for funcname in dir(cls):
793            if funcname.startswith("_") and funcname.endswith("_writer"):
794                writers[funcname[1:-7]] = getattr(cls, funcname)
795        return writers
796
797    # Reader Functions
798
799    @staticmethod
800    def _Byte_reader(s):
801        "8-bit unsigned integer"
802        return Integer(*struct.unpack("B", s.read(1)))
803
804    @staticmethod
805    def _Character8_reader(s):
806        "8-bit character"
807        return String(struct.unpack("c", s.read(1))[0].decode("ascii"))
808
809    @staticmethod
810    def _Character16_reader(s):
811        "16-bit character"
812        return String(chr(*struct.unpack("H", s.read(2))))
813
814    @staticmethod
815    def _Complex64_reader(s):
816        "IEEE single-precision complex number"
817        return _BinaryFormat._IEEE_cmplx(*struct.unpack("ff", s.read(8)))
818
819    @staticmethod
820    def _Complex128_reader(s):
821        "IEEE double-precision complex number"
822        return _BinaryFormat._IEEE_cmplx(*struct.unpack("dd", s.read(16)))
823
824    def _Complex256_reader(self, s):
825        "IEEE quad-precision complex number"
826        return Complex(self._Real128_reader(s), self._Real128_reader(s))
827
828    @staticmethod
829    def _Integer8_reader(s):
830        "8-bit signed integer"
831        return Integer(*struct.unpack("b", s.read(1)))
832
833    @staticmethod
834    def _Integer16_reader(s):
835        "16-bit signed integer"
836        return Integer(*struct.unpack("h", s.read(2)))
837
838    @staticmethod
839    def _Integer24_reader(s):
840        "24-bit signed integer"
841        b = s.read(3)
842        return Integer(struct.unpack("<i", b"\x00" + b)[0] >> 8)
843
844    @staticmethod
845    def _Integer32_reader(s):
846        "32-bit signed integer"
847        return Integer(*struct.unpack("i", s.read(4)))
848
849    @staticmethod
850    def _Integer64_reader(s):
851        "64-bit signed integer"
852        return Integer(*struct.unpack("q", s.read(8)))
853
854    @staticmethod
855    def _Integer128_reader(s):
856        "128-bit signed integer"
857        a, b = struct.unpack("Qq", s.read(16))
858        return Integer((b << 64) + a)
859
860    @staticmethod
861    def _Real32_reader(s):
862        "IEEE single-precision real number"
863        return _BinaryFormat._IEEE_real(*struct.unpack("f", s.read(4)))
864
865    @staticmethod
866    def _Real64_reader(s):
867        "IEEE double-precision real number"
868        return _BinaryFormat._IEEE_real(*struct.unpack("d", s.read(8)))
869
870    @staticmethod
871    def _Real128_reader(s):
872        "IEEE quad-precision real number"
873        # Workaround quad missing from struct
874        # correctness is not guaranteed
875        b = s.read(16)
876        sig, sexp = b[:14], b[14:]
877
878        # Sign / Exponent
879        (sexp,) = struct.unpack("H", sexp)
880        signbit = sexp // 0x8000
881        expbits = sexp % 0x8000
882
883        # Signifand
884        try:
885            fracbits = int.from_bytes(sig, byteorder="little")
886        except AttributeError:  # Py2
887            fracbits = int(sig[::-1].encode("hex"), 16)
888
889        if expbits == 0x0000 and fracbits == 0:
890            return Real(sympy.Float(0, 4965))
891        elif expbits == 0x7FFF:
892            if fracbits == 0:
893                return Expression("DirectedInfinity", Integer((-1) ** signbit))
894            else:
895                return Symbol("Indeterminate")
896
897        with mpmath.workprec(112):
898            core = mpmath.fdiv(fracbits, 2 ** 112)
899            if expbits == 0x000:
900                assert fracbits != 0
901                exp = -16382
902                core = mpmath.fmul((-1) ** signbit, core)
903            else:
904                assert 0x0001 <= expbits <= 0x7FFE
905                exp = expbits - 16383
906                core = mpmath.fmul((-1) ** signbit, mpmath.fadd(1, core))
907
908            if exp >= 0:
909                result = mpmath.fmul(core, 2 ** exp)
910            else:
911                result = mpmath.fdiv(core, 2 ** -exp)
912
913            return from_mpmath(result, dps(112))
914
915    @staticmethod
916    def _TerminatedString_reader(s):
917        "null-terminated string of 8-bit characters"
918        b = s.read(1)
919        contents = b""
920        while b != b"\x00":
921            if b == b"":
922                raise struct.error
923            contents += b
924            b = s.read(1)
925        return String(contents.decode("ascii"))
926
927    @staticmethod
928    def _UnsignedInteger8_reader(s):
929        "8-bit unsigned integer"
930        return Integer(*struct.unpack("B", s.read(1)))
931
932    @staticmethod
933    def _UnsignedInteger16_reader(s):
934        "16-bit unsigned integer"
935        return Integer(*struct.unpack("H", s.read(2)))
936
937    @staticmethod
938    def _UnsignedInteger24_reader(s):
939        "24-bit unsigned integer"
940        return Integer(*struct.unpack("I", s.read(3) + b"\0"))
941
942    @staticmethod
943    def _UnsignedInteger32_reader(s):
944        "32-bit unsigned integer"
945        return Integer(*struct.unpack("I", s.read(4)))
946
947    @staticmethod
948    def _UnsignedInteger64_reader(s):
949        "64-bit unsigned integer"
950        return Integer(*struct.unpack("Q", s.read(8)))
951
952    @staticmethod
953    def _UnsignedInteger128_reader(s):
954        "128-bit unsigned integer"
955        a, b = struct.unpack("QQ", s.read(16))
956        return Integer((b << 64) + a)
957
958    # Writer Functions
959
960    @staticmethod
961    def _Byte_writer(s, x):
962        "8-bit unsigned integer"
963        s.write(struct.pack("B", x))
964
965    @staticmethod
966    def _Character8_writer(s, x):
967        "8-bit character"
968        s.write(struct.pack("c", x.encode("ascii")))
969
970    # TODO
971    # @staticmethod
972    # def _Character16_writer(s, x):
973    #     "16-bit character"
974    #     pass
975
976    @staticmethod
977    def _Complex64_writer(s, x):
978        "IEEE single-precision complex number"
979        s.write(struct.pack("ff", x.real, x.imag))
980        # return _BinaryFormat._IEEE_cmplx(*struct.unpack('ff', s.read(8)))
981
982    @staticmethod
983    def _Complex128_writer(s, x):
984        "IEEE double-precision complex number"
985        s.write(struct.pack("dd", x.real, x.imag))
986
987    # TODO
988    # @staticmethod
989    # def _Complex256_writer(s, x):
990    #     "IEEE quad-precision complex number"
991    #     pass
992
993    @staticmethod
994    def _Integer8_writer(s, x):
995        "8-bit signed integer"
996        s.write(struct.pack("b", x))
997
998    @staticmethod
999    def _Integer16_writer(s, x):
1000        "16-bit signed integer"
1001        s.write(struct.pack("h", x))
1002
1003    @staticmethod
1004    def _Integer24_writer(s, x):
1005        "24-bit signed integer"
1006        s.write(struct.pack("i", x << 8)[1:])
1007
1008    @staticmethod
1009    def _Integer32_writer(s, x):
1010        "32-bit signed integer"
1011        s.write(struct.pack("i", x))
1012
1013    @staticmethod
1014    def _Integer64_writer(s, x):
1015        "64-bit signed integer"
1016        s.write(struct.pack("q", x))
1017
1018    @staticmethod
1019    def _Integer128_writer(s, x):
1020        "128-bit signed integer"
1021        a, b = x & 0xFFFFFFFFFFFFFFFF, x >> 64
1022        s.write(struct.pack("Qq", a, b))
1023
1024    @staticmethod
1025    def _Real32_writer(s, x):
1026        "IEEE single-precision real number"
1027        s.write(struct.pack("f", x))
1028
1029    @staticmethod
1030    def _Real64_writer(s, x):
1031        "IEEE double-precision real number"
1032        s.write(struct.pack("d", x))
1033
1034    # TODO
1035    # @staticmethod
1036    # def _Real128_writer(s, x):
1037    #     "IEEE quad-precision real number"
1038    #     pass
1039
1040    @staticmethod
1041    def _TerminatedString_writer(s, x):
1042        "null-terminated string of 8-bit characters"
1043        s.write(x.encode("utf-8"))
1044
1045    @staticmethod
1046    def _UnsignedInteger8_writer(s, x):
1047        "8-bit unsigned integer"
1048        s.write(struct.pack("B", x))
1049
1050    @staticmethod
1051    def _UnsignedInteger16_writer(s, x):
1052        "16-bit unsigned integer"
1053        s.write(struct.pack("H", x))
1054
1055    @staticmethod
1056    def _UnsignedInteger24_writer(s, x):
1057        "24-bit unsigned integer"
1058        s.write(struct.pack("I", x << 8)[1:])
1059
1060    @staticmethod
1061    def _UnsignedInteger32_writer(s, x):
1062        "32-bit unsigned integer"
1063        s.write(struct.pack("I", x))
1064
1065    @staticmethod
1066    def _UnsignedInteger64_writer(s, x):
1067        "64-bit unsigned integer"
1068        s.write(struct.pack("Q", x))
1069
1070    @staticmethod
1071    def _UnsignedInteger128_writer(s, x):
1072        "128-bit unsigned integer"
1073        a, b = x & 0xFFFFFFFFFFFFFFFF, x >> 64
1074        s.write(struct.pack("QQ", a, b))
1075
1076
1077class BinaryWrite(Builtin):
1078    """
1079    <dl>
1080    <dt>'BinaryWrite[$channel$, $b$]'
1081      <dd>writes a single byte given as an integer from 0 to 255.
1082    <dt>'BinaryWrite[$channel$, {b1, b2, ...}]'
1083      <dd>writes a sequence of byte.
1084    <dt>'BinaryWrite[$channel$, "string"]'
1085      <dd>writes the raw characters in a string.
1086    <dt>'BinaryWrite[$channel$, $x$, $type$]'
1087      <dd>writes $x$ as the specified type.
1088    <dt>'BinaryWrite[$channel$, {$x1$, $x2$, ...}, $type$]'
1089      <dd>writes a sequence of objects as the specified type.
1090    <dt>'BinaryWrite[$channel$, {$x1$, $x2$, ...}, {$type1$, $type2$, ...}]'
1091      <dd>writes a sequence of objects using a sequence of specified types.
1092    </dl>
1093
1094    >> strm = OpenWrite[BinaryFormat -> True]
1095     = OutputStream[...]
1096    >> BinaryWrite[strm, {39, 4, 122}]
1097     = OutputStream[...]
1098    >> Close[strm]
1099     = ...
1100    >> strm = OpenRead[%, BinaryFormat -> True]
1101     = InputStream[...]
1102    >> BinaryRead[strm]
1103     = 39
1104    >> BinaryRead[strm, "Byte"]
1105     = 4
1106    >> BinaryRead[strm, "Character8"]
1107     = z
1108    >> Close[strm];
1109
1110    Write a String
1111    >> strm = OpenWrite[BinaryFormat -> True]
1112     = OutputStream[...]
1113    >> BinaryWrite[strm, "abc123"]
1114     = OutputStream[...]
1115    >> Close[%]
1116     = ...
1117
1118    Read as Bytes
1119    >> strm = OpenRead[%, BinaryFormat -> True]
1120     = InputStream[...]
1121    >> BinaryRead[strm, {"Character8", "Character8", "Character8", "Character8", "Character8", "Character8", "Character8"}]
1122     = {a, b, c, 1, 2, 3, EndOfFile}
1123    >> Close[strm]
1124     = ...
1125
1126    Read as Characters
1127    >> strm = OpenRead[%, BinaryFormat -> True]
1128     = InputStream[...]
1129    >> BinaryRead[strm, {"Byte", "Byte", "Byte", "Byte", "Byte", "Byte", "Byte"}]
1130     = {97, 98, 99, 49, 50, 51, EndOfFile}
1131    >> Close[strm]
1132     = ...
1133
1134    Write Type
1135    >> strm = OpenWrite[BinaryFormat -> True]
1136     = OutputStream[...]
1137    >> BinaryWrite[strm, 97, "Byte"]
1138     = OutputStream[...]
1139    >> BinaryWrite[strm, {97, 98, 99}, {"Byte", "Byte", "Byte"}]
1140     = OutputStream[...]
1141    >> Close[%]
1142     = ...
1143
1144    ## Write then Read as Bytes
1145    #> 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]
1146
1147    ## Byte
1148    #> WRb[{149, 2, 177, 132}, {"Byte", "Byte", "Byte", "Byte"}]
1149     = {149, 2, 177, 132}
1150    #> WRb[{149, 2, 177, 132}, {"Byte", "Byte", "Byte", "Byte"}]
1151     = {149, 2, 177, 132}
1152    #> (# == WRb[#, Table["Byte", {50}]]) & [RandomInteger[{0, 255}, 50]]
1153     = True
1154
1155    ## Character8
1156    #> WRb[{"a", "b", "c"}, {"Character8", "Character8", "Character8"}]
1157     = {97, 98, 99}
1158    #> WRb[{34, 60, 39}, {"Character8", "Character8", "Character8"}]
1159     = {51, 52, 54, 48, 51, 57}
1160    #> WRb[{"ab", "c", "d"}, {"Character8", "Character8", "Character8", "Character8"}]
1161     = {97, 98, 99, 100}
1162
1163    ## Character16
1164    ## TODO
1165
1166    ## Complex64
1167    #> WRb[-6.36877988924*^28 + 3.434203392*^9 I, "Complex64"]
1168     = {80, 201, 77, 239, 201, 177, 76, 79}
1169    #> WRb[-6.98948862335*^24 + 1.52209021297*^23 I, "Complex64"]
1170     = {158, 2, 185, 232, 18, 237, 0, 102}
1171    #> WRb[-1.41079828148*^-19 - 0.013060791418 I, "Complex64"]
1172     = {195, 142, 38, 160, 238, 252, 85, 188}
1173    #> WRb[{5, -2054}, "Complex64"]
1174     = {0, 0, 160, 64, 0, 0, 0, 0, 0, 96, 0, 197, 0, 0, 0, 0}
1175    #> WRb[Infinity, "Complex64"]
1176     = {0, 0, 128, 127, 0, 0, 0, 0}
1177    #> WRb[-Infinity, "Complex64"]
1178     = {0, 0, 128, 255, 0, 0, 0, 0}
1179    #> WRb[DirectedInfinity[1 + I], "Complex64"]
1180     = {0, 0, 128, 127, 0, 0, 128, 127}
1181    #> WRb[DirectedInfinity[I], "Complex64"]
1182     = {0, 0, 0, 0, 0, 0, 128, 127}
1183    ## FIXME (different convention to MMA)
1184    #> WRb[Indeterminate, "Complex64"]
1185     = {0, 0, 192, 127, 0, 0, 192, 127}
1186
1187    ## Complex128
1188    #> WRb[1.19839770357*^-235 - 2.64656391494*^-54 I,"Complex128"]
1189     = {102, 217, 1, 163, 234, 98, 40, 15, 243, 104, 116, 15, 48, 57, 208, 180}
1190    #> WRb[3.22170267142*^134 - 8.98364297498*^198 I,"Complex128"]
1191     = {219, 161, 12, 126, 47, 94, 220, 91, 189, 66, 29, 68, 147, 11, 62, 233}
1192    #> WRb[-Infinity, "Complex128"]
1193     = {0, 0, 0, 0, 0, 0, 240, 255, 0, 0, 0, 0, 0, 0, 0, 0}
1194    #> WRb[DirectedInfinity[1 - I], "Complex128"]
1195     = {0, 0, 0, 0, 0, 0, 240, 127, 0, 0, 0, 0, 0, 0, 240, 255}
1196    #> WRb[DirectedInfinity[I], "Complex128"]
1197     = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 127}
1198    ## FIXME (different convention to MMA)
1199    #> WRb[Indeterminate, "Complex128"]
1200     = {0, 0, 0, 0, 0, 0, 248, 127, 0, 0, 0, 0, 0, 0, 248, 127}
1201
1202    ## Complex256
1203    ## TODO
1204
1205    ## Integer8
1206    #> WRb[{5, 2, 11, -4}, {"Integer8", "Integer8", "Integer8", "Integer8"}]
1207     = {5, 2, 11, 252}
1208    #> WRb[{127, -128, 0}, {"Integer8", "Integer8", "Integer8"}]
1209     = {127, 128, 0}
1210
1211    ## Integer16
1212    #> WRb[{661, -31567, 6256}, {"Integer16", "Integer16", "Integer16"}]
1213     = {149, 2, 177, 132, 112, 24}
1214    #> WRb[{0, 255, -1, 32640, -32640}, Table["Integer16", {5}]]
1215     = {0, 0, 255, 0, 255, 255, 128, 127, 128, 128}
1216
1217    ## Integer24
1218    #> WRb[{-6247016, -6631492}, {"Integer24", "Integer24"}]
1219     = {152, 173, 160, 188, 207, 154}
1220    #> WRb[{-1593967, 1989169}, {"Integer24", "Integer24"}]
1221     = {145, 173, 231, 49, 90, 30}
1222
1223    ## Integer32
1224    #> WRb[{-636001327, -236143729}, {"Integer32", "Integer32"}]
1225     = {209, 99, 23, 218, 143, 187, 236, 241}
1226    #> WRb[{2024611599, -1139645195}, {"Integer32", "Integer32"}]
1227     = {15, 31, 173, 120, 245, 100, 18, 188}
1228
1229    ## Integer64
1230    #> WRb[{1176115612243989203}, "Integer64"]
1231     = {211, 18, 152, 2, 235, 102, 82, 16}
1232    #> WRb[{-8526737900550694619}, "Integer64"]
1233     = {37, 217, 208, 88, 14, 241, 170, 137}
1234
1235    ## Integer128
1236    #> WRb[139827542997232652313568968616424513676, "Integer128"]
1237     = {140, 32, 24, 199, 10, 169, 248, 117, 123, 184, 75, 76, 34, 206, 49, 105}
1238    #> WRb[103439096823027953602112616165136677221, "Integer128"]
1239     = {101, 57, 184, 108, 43, 214, 186, 120, 153, 51, 132, 225, 56, 165, 209, 77}
1240    #> WRb[-49058912464625098822365387707690163087, "Integer128"]
1241     = {113, 100, 125, 144, 211, 83, 140, 24, 206, 11, 198, 118, 222, 152, 23, 219}
1242
1243    ## Real32
1244    #> WRb[{8.398086656*^9, 1.63880017681*^16}, {"Real32", "Real32"}]
1245     = {81, 72, 250, 79, 52, 227, 104, 90}
1246    #> WRb[{5.6052915284*^32, 9.631141*^6}, {"Real32", "Real32"}]
1247     = {251, 22, 221, 117, 165, 245, 18, 75}
1248    #> WRb[Infinity, "Real32"]
1249     = {0, 0, 128, 127}
1250    #> WRb[-Infinity, "Real32"]
1251     = {0, 0, 128, 255}
1252    ## FIXME (different convention to MMA)
1253    #> WRb[Indeterminate, "Real32"]
1254     = {0, 0, 192, 127}
1255
1256    ## Real64
1257    #> WRb[-5.14646619426*^227, "Real64"]
1258     = {91, 233, 20, 87, 129, 185, 53, 239}
1259    #> WRb[-9.69531698809*^20, "Real64"]
1260     = {187, 67, 162, 67, 122, 71, 74, 196}
1261    #> WRb[9.67355569764*^159, "Real64"]
1262     = {132, 48, 80, 125, 157, 4, 38, 97}
1263    #> WRb[Infinity, "Real64"]
1264     = {0, 0, 0, 0, 0, 0, 240, 127}
1265    #> WRb[-Infinity, "Real64"]
1266     = {0, 0, 0, 0, 0, 0, 240, 255}
1267    ## FIXME (different convention to MMA)
1268    #> WRb[Indeterminate, "Real64"]
1269     = {0, 0, 0, 0, 0, 0, 248, 127}
1270
1271    ## Real128
1272    ## TODO
1273
1274    ## TerminatedString
1275    #> WRb["abc", "TerminatedString"]
1276     = {97, 98, 99, 0}
1277    #> WRb[{"123", "456"}, {"TerminatedString", "TerminatedString", "TerminatedString"}]
1278     = {49, 50, 51, 0, 52, 53, 54, 0}
1279    #> WRb["", "TerminatedString"]
1280    = {0}
1281
1282    ## UnsignedInteger8
1283    #> WRb[{96, 94, 141, 162, 141}, Table["UnsignedInteger8", {5}]]
1284     = {96, 94, 141, 162, 141}
1285    #> (#==WRb[#,Table["UnsignedInteger8",{50}]])&[RandomInteger[{0, 255}, 50]]
1286     = True
1287
1288    ## UnsignedInteger16
1289    #> WRb[{18230, 47466, 9875, 59141}, Table["UnsignedInteger16", {4}]]
1290     = {54, 71, 106, 185, 147, 38, 5, 231}
1291    #> WRb[{0, 32896, 65535}, Table["UnsignedInteger16", {3}]]
1292     = {0, 0, 128, 128, 255, 255}
1293
1294    ## UnsignedInteger24
1295    #> WRb[{14820174, 15488225}, Table["UnsignedInteger24", {2}]]
1296     = {78, 35, 226, 225, 84, 236}
1297    #> WRb[{5374629, 3889391}, Table["UnsignedInteger24", {2}]]
1298     = {165, 2, 82, 239, 88, 59}
1299
1300    ## UnsignedInteger32
1301    #> WRb[{1885507541, 4157323149}, Table["UnsignedInteger32", {2}]]
1302     = {213, 143, 98, 112, 141, 183, 203, 247}
1303    #> WRb[{384206740, 1676316040}, Table["UnsignedInteger32", {2}]]
1304     = {148, 135, 230, 22, 136, 141, 234, 99}
1305    """
1306
1307    messages = {
1308        "writex": "`1`.",
1309    }
1310
1311    writers = _BinaryFormat.get_writers()
1312
1313    def apply_notype(self, name, n, b, evaluation):
1314        "BinaryWrite[OutputStream[name_, n_], b_]"
1315        return self.apply(name, n, b, None, evaluation)
1316
1317    def apply(self, name, n, b, typ, evaluation):
1318        "BinaryWrite[OutputStream[name_, n_], b_, typ_]"
1319
1320        channel = Expression("OutputStream", name, n)
1321
1322        # Check Empty Type
1323        if typ is None:
1324            expr = Expression("BinaryWrite", channel, b)
1325            typ = Expression("List")
1326        else:
1327            expr = Expression("BinaryWrite", channel, b, typ)
1328
1329        # Check channel
1330        stream = stream_manager.lookup_stream(n.get_int_value())
1331
1332        if stream is None or stream.io.closed:
1333            evaluation.message("General", "openx", name)
1334            return expr
1335
1336        if stream.mode not in ["wb", "ab"]:
1337            evaluation.message("BinaryWrite", "openr", channel)
1338            return expr
1339
1340        # Check b
1341        if b.has_form("List", None):
1342            pyb = b.leaves
1343        else:
1344            pyb = [b]
1345
1346        # Check Type
1347        if typ.has_form("List", None):
1348            types = typ.get_leaves()
1349        else:
1350            types = [typ]
1351
1352        if len(types) == 0:  # Default type is "Bytes"
1353            types = [String("Byte")]
1354
1355        types = [t.get_string_value() for t in types]
1356        if not all(t in self.writers for t in types):
1357            evaluation.message("BinaryRead", "format", typ)
1358            return expr
1359
1360        # Write to stream
1361        i = 0
1362        while i < len(pyb):
1363            x = pyb[i]
1364            # Types are "repeated as many times as necessary"
1365            t = types[i % len(types)]
1366
1367            # Coerce x
1368            if t == "TerminatedString":
1369                x = x.get_string_value() + "\x00"
1370            elif t.startswith("Real"):
1371                if isinstance(x, Real):
1372                    x = x.to_python()
1373                elif x.has_form("DirectedInfinity", 1):
1374                    if x.leaves[0].get_int_value() == 1:
1375                        x = float("+inf")
1376                    elif x.leaves[0].get_int_value() == -1:
1377                        x = float("-inf")
1378                    else:
1379                        x = None
1380                elif isinstance(x, Symbol) and x.get_name() == "System`Indeterminate":
1381                    x = float("nan")
1382                else:
1383                    x = None
1384                assert x is None or isinstance(x, float)
1385            elif t.startswith("Complex"):
1386                if isinstance(x, (Complex, Real, Integer)):
1387                    x = x.to_python()
1388                elif x.has_form("DirectedInfinity", 1):
1389                    x = x.leaves[0].to_python(n_evaluation=evaluation)
1390
1391                    # x*float('+inf') creates nan if x.real or x.imag are zero
1392                    x = complex(
1393                        x.real * float("+inf") if x.real != 0 else 0,
1394                        x.imag * float("+inf") if x.imag != 0 else 0,
1395                    )
1396                elif isinstance(x, Symbol) and x.get_name() == "System`Indeterminate":
1397                    x = complex(float("nan"), float("nan"))
1398                else:
1399                    x = None
1400            elif t.startswith("Character"):
1401                if isinstance(x, Integer):
1402                    x = [String(char) for char in str(x.get_int_value())]
1403                    pyb = list(chain(pyb[:i], x, pyb[i + 1 :]))
1404                    x = pyb[i]
1405                if isinstance(x, String) and len(x.get_string_value()) > 1:
1406                    x = [String(char) for char in x.get_string_value()]
1407                    pyb = list(chain(pyb[:i], x, pyb[i + 1 :]))
1408                    x = pyb[i]
1409                x = x.get_string_value()
1410            elif t == "Byte" and isinstance(x, String):
1411                if len(x.get_string_value()) > 1:
1412                    x = [String(char) for char in x.get_string_value()]
1413                    pyb = list(chain(pyb[:i], x, pyb[i + 1 :]))
1414                    x = pyb[i]
1415                x = ord(x.get_string_value())
1416            else:
1417                x = x.get_int_value()
1418
1419            if x is None:
1420                return evaluation.message("BinaryWrite", "nocoerce", b)
1421
1422            try:
1423                self.writers[t](stream.io, x)
1424            except struct.error:
1425                return evaluation.message("BinaryWrite", "nocoerce", b)
1426            i += 1
1427
1428        try:
1429            stream.io.flush()
1430        except IOError as err:
1431            evaluation.message("BinaryWrite", "writex", err.strerror)
1432        return channel
1433
1434
1435class BinaryRead(Builtin):
1436    """
1437    <dl>
1438    <dt>'BinaryRead[$stream$]'
1439      <dd>reads one byte from the stream as an integer from 0 to 255.
1440    <dt>'BinaryRead[$stream$, $type$]'
1441      <dd>reads one object of specified type from the stream.
1442    <dt>'BinaryRead[$stream$, {$type1$, $type2$, ...}]'
1443      <dd>reads a sequence of objects of specified types.
1444    </dl>
1445
1446    >> strm = OpenWrite[BinaryFormat -> True]
1447     = OutputStream[...]
1448    >> BinaryWrite[strm, {97, 98, 99}]
1449     = OutputStream[...]
1450    >> Close[strm]
1451     = ...
1452    >> strm = OpenRead[%, BinaryFormat -> True]
1453     = InputStream[...]
1454    >> BinaryRead[strm, {"Character8", "Character8", "Character8"}]
1455     = {a, b, c}
1456    >> Close[strm];
1457
1458    ## Write as Bytes then Read
1459    #> 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]
1460
1461    ## Byte
1462    #> WbR[{149, 2, 177, 132}, {"Byte", "Byte", "Byte", "Byte"}]
1463     = {149, 2, 177, 132}
1464    #> (# == WbR[#, Table["Byte", {50}]]) & [RandomInteger[{0, 255}, 50]]
1465     = True
1466
1467    ## Character8
1468    #> WbR[{97, 98, 99}, {"Character8", "Character8", "Character8"}]
1469     = {a, b, c}
1470    #> WbR[{34, 60, 39}, {"Character8", "Character8", "Character8"}]
1471     = {", <, '}
1472
1473    ## Character16
1474    #> WbR[{97, 0, 98, 0, 99, 0}, {"Character16", "Character16", "Character16"}]
1475     = {a, b, c}
1476    #> ToCharacterCode[WbR[{50, 154, 182, 236}, {"Character16", "Character16"}]]
1477     = {{39474}, {60598}}
1478    ## #> WbR[ {91, 146, 206, 54}, {"Character16", "Character16"}]
1479    ##  = {\\:925b, \\:36ce}
1480
1481    ## Complex64
1482    #> WbR[{80, 201, 77, 239, 201, 177, 76, 79}, "Complex64"] // InputForm
1483     = -6.368779889243691*^28 + 3.434203392*^9*I
1484    #> % // Precision
1485     = MachinePrecision
1486    #> WbR[{158, 2, 185, 232, 18, 237, 0, 102}, "Complex64"] // InputForm
1487     = -6.989488623351118*^24 + 1.522090212973691*^23*I
1488    #> WbR[{195, 142, 38, 160, 238, 252, 85, 188}, "Complex64"] // InputForm
1489     = -1.4107982814807285*^-19 - 0.013060791417956352*I
1490
1491    ## Complex128
1492    #> WbR[{15,114,1,163,234,98,40,15,214,127,116,15,48,57,208,180},"Complex128"] // InputForm
1493     = 1.1983977035653814*^-235 - 2.6465639149433955*^-54*I
1494    #> WbR[{148,119,12,126,47,94,220,91,42,69,29,68,147,11,62,233},"Complex128"] // InputForm
1495     = 3.2217026714156333*^134 - 8.98364297498066*^198*I
1496    #> % // Precision
1497     = MachinePrecision
1498    #> WbR[{15,42,80,125,157,4,38,97, 0,0,0,0,0,0,240,255}, "Complex128"]
1499      = -I Infinity
1500    #> WbR[{15,42,80,125,157,4,38,97, 0,0,0,0,0,0,240,127}, "Complex128"]
1501      = I Infinity
1502    #> WbR[{15,42,80,125,157,4,38,97, 1,0,0,0,0,0,240,255}, "Complex128"]
1503     = Indeterminate
1504    #> WbR[{0,0,0,0,0,0,240,127, 15,42,80,125,157,4,38,97}, "Complex128"]
1505     = Infinity
1506    #> WbR[{0,0,0,0,0,0,240,255, 15,42,80,125,157,4,38,97}, "Complex128"]
1507     = -Infinity
1508    #> WbR[{1,0,0,0,0,0,240,255, 15,42,80,125,157,4,38,97}, "Complex128"]
1509     = Indeterminate
1510    #> WbR[{0,0,0,0,0,0,240,127, 0,0,0,0,0,0,240,127}, "Complex128"]
1511     = Indeterminate
1512    #> WbR[{0,0,0,0,0,0,240,127, 0,0,0,0,0,0,240,255}, "Complex128"]
1513     = Indeterminate
1514
1515    ## Complex256
1516    ## TODO
1517
1518    ## Integer8
1519    #> WbR[{149, 2, 177, 132}, {"Integer8", "Integer8", "Integer8", "Integer8"}]
1520     = {-107, 2, -79, -124}
1521    #> WbR[{127, 128, 0, 255}, {"Integer8", "Integer8", "Integer8", "Integer8"}]
1522     = {127, -128, 0, -1}
1523
1524    ## Integer16
1525    #> WbR[{149, 2, 177, 132, 112, 24}, {"Integer16", "Integer16", "Integer16"}]
1526     = {661, -31567, 6256}
1527    #> WbR[{0, 0, 255, 0, 255, 255, 128, 127, 128, 128}, Table["Integer16", {5}]]
1528     = {0, 255, -1, 32640, -32640}
1529
1530    ## Integer24
1531    #> WbR[{152, 173, 160, 188, 207, 154}, {"Integer24", "Integer24"}]
1532     = {-6247016, -6631492}
1533    #> WbR[{145, 173, 231, 49, 90, 30}, {"Integer24", "Integer24"}]
1534     = {-1593967, 1989169}
1535
1536    ## Integer32
1537    #> WbR[{209, 99, 23, 218, 143, 187, 236, 241}, {"Integer32", "Integer32"}]
1538     = {-636001327, -236143729}
1539    #> WbR[{15, 31, 173, 120, 245, 100, 18, 188}, {"Integer32", "Integer32"}]
1540     = {2024611599, -1139645195}
1541
1542    ## Integer64
1543    #> WbR[{211, 18, 152, 2, 235, 102, 82, 16}, "Integer64"]
1544     = 1176115612243989203
1545    #> WbR[{37, 217, 208, 88, 14, 241, 170, 137}, "Integer64"]
1546     = -8526737900550694619
1547
1548    ## Integer128
1549    #> WbR[{140,32,24,199,10,169,248,117,123,184,75,76,34,206,49,105}, "Integer128"]
1550     = 139827542997232652313568968616424513676
1551    #> WbR[{101,57,184,108,43,214,186,120,153,51,132,225,56,165,209,77}, "Integer128"]
1552     = 103439096823027953602112616165136677221
1553    #> WbR[{113,100,125,144,211,83,140,24,206,11,198,118,222,152,23,219}, "Integer128"]
1554     = -49058912464625098822365387707690163087
1555
1556    ## Real32
1557    #> WbR[{81, 72, 250, 79, 52, 227, 104, 90}, {"Real32", "Real32"}] // InputForm
1558     = {8.398086656*^9, 1.6388001768669184*^16}
1559    #> WbR[{251, 22, 221, 117, 165, 245, 18, 75}, {"Real32", "Real32"}] // InputForm
1560     = {5.605291528399748*^32, 9.631141*^6}
1561    #> WbR[{126, 82, 143, 43}, "Real32"] // InputForm
1562     = 1.0183657302847982*^-12
1563    #> % // Precision
1564     = MachinePrecision
1565    #> WbR[{0, 0, 128, 127}, "Real32"]
1566     = Infinity
1567    #> WbR[{0, 0, 128, 255}, "Real32"]
1568     = -Infinity
1569    #> WbR[{1, 0, 128, 255}, "Real32"]
1570     = Indeterminate
1571    #> WbR[{1, 0, 128, 127}, "Real32"]
1572     = Indeterminate
1573
1574    ## Real64
1575    #> WbR[{45, 243, 20, 87, 129, 185, 53, 239}, "Real64"] // InputForm
1576     = -5.146466194262116*^227
1577    #> WbR[{192, 60, 162, 67, 122, 71, 74, 196}, "Real64"] // InputForm
1578     = -9.695316988087658*^20
1579    #> WbR[{15, 42, 80, 125, 157, 4, 38, 97}, "Real64"] // InputForm
1580     = 9.67355569763742*^159
1581    #> % // Precision
1582     = MachinePrecision
1583    #> WbR[{0, 0, 0, 0, 0, 0, 240, 127}, "Real64"]
1584     = Infinity
1585    #> WbR[{0, 0, 0, 0, 0, 0, 240, 255}, "Real64"]
1586     = -Infinity
1587    #> WbR[{1, 0, 0, 0, 0, 0, 240, 127}, "Real64"]
1588     = Indeterminate
1589    #> WbR[{1, 0, 0, 0, 0, 0, 240, 255}, "Real64"]
1590     = Indeterminate
1591
1592    ## Real128
1593    ## 0x0000
1594    #> WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0}, "Real128"]
1595     = 0.*^-4965
1596    #> WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,128}, "Real128"]
1597     = 0.*^-4965
1598    ## 0x0001 - 0x7FFE
1599    #> WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,63}, "Real128"]
1600     = 1.00000000000000000000000000000000
1601    #> WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,191}, "Real128"]
1602     = -1.00000000000000000000000000000000
1603    #> WbR[{135, 62, 233, 137, 22, 208, 233, 210, 133, 82, 251, 92, 220, 216, 255, 63}, "Real128"]
1604     = 1.84711247573661489653389674493896
1605    #> WbR[{135, 62, 233, 137, 22, 208, 233, 210, 133, 82, 251, 92, 220, 216, 207, 72}, "Real128"]
1606     = 2.45563355727491021879689747166252*^679
1607    #> WbR[{74, 95, 30, 234, 116, 130, 1, 84, 20, 133, 245, 221, 113, 110, 219, 212}, "Real128"]
1608     = -4.52840681592341879518366539335138*^1607
1609    #> % // Precision
1610     = 33.
1611    ## 0x7FFF
1612    #> WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,127}, "Real128"]
1613     = Infinity
1614    #> WbR[{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,255}, "Real128"]
1615     = -Infinity
1616    #> WbR[{1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,127}, "Real128"]
1617     = Indeterminate
1618    #> WbR[{1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,255,255}, "Real128"]
1619     = Indeterminate
1620
1621    ## TerminatedString
1622    #> WbR[{97, 98, 99, 0}, "TerminatedString"]
1623     = abc
1624    #> WbR[{49, 50, 51, 0, 52, 53, 54, 0, 55, 56, 57}, Table["TerminatedString", {3}]]
1625     = {123, 456, EndOfFile}
1626    #> WbR[{0}, "TerminatedString"] // InputForm
1627     = ""
1628
1629    ## UnsignedInteger8
1630    #> WbR[{96, 94, 141, 162, 141}, Table["UnsignedInteger8", {5}]]
1631     = {96, 94, 141, 162, 141}
1632    #> (#==WbR[#,Table["UnsignedInteger8",{50}]])&[RandomInteger[{0, 255}, 50]]
1633     = True
1634
1635    ## UnsignedInteger16
1636    #> WbR[{54, 71, 106, 185, 147, 38, 5, 231}, Table["UnsignedInteger16", {4}]]
1637     = {18230, 47466, 9875, 59141}
1638    #> WbR[{0, 0, 128, 128, 255, 255}, Table["UnsignedInteger16", {3}]]
1639     = {0, 32896, 65535}
1640
1641    ## UnsignedInteger24
1642    #> WbR[{78, 35, 226, 225, 84, 236}, Table["UnsignedInteger24", {2}]]
1643     = {14820174, 15488225}
1644    #> WbR[{165, 2, 82, 239, 88, 59}, Table["UnsignedInteger24", {2}]]
1645     = {5374629, 3889391}
1646
1647    ## UnsignedInteger32
1648    #> WbR[{213,143,98,112,141,183,203,247}, Table["UnsignedInteger32", {2}]]
1649     = {1885507541, 4157323149}
1650    #> WbR[{148,135,230,22,136,141,234,99}, Table["UnsignedInteger32", {2}]]
1651     = {384206740, 1676316040}
1652
1653    ## UnsignedInteger64
1654    #> WbR[{95, 5, 33, 229, 29, 62, 63, 98}, "UnsignedInteger64"]
1655     = 7079445437368829279
1656    #> WbR[{134, 9, 161, 91, 93, 195, 173, 74}, "UnsignedInteger64"]
1657     = 5381171935514265990
1658
1659    ## UnsignedInteger128
1660    #> WbR[{108,78,217,150,88,126,152,101,231,134,176,140,118,81,183,220}, "UnsignedInteger128"]
1661     = 293382001665435747348222619884289871468
1662    #> WbR[{53,83,116,79,81,100,60,126,202,52,241,48,5,113,92,190}, "UnsignedInteger128"]
1663     = 253033302833692126095975097811212718901
1664
1665    ## EndOfFile
1666    #> WbR[{148}, {"Integer32", "Integer32","Integer32"}]
1667     = {EndOfFile, EndOfFile, EndOfFile}
1668    """
1669
1670    readers = _BinaryFormat.get_readers()
1671
1672    messages = {
1673        "format": "`1` is not a recognized binary format.",
1674        "openw": "`1` is open for output.",
1675        "bfmt": "The stream `1` has been opened with BinaryFormat -> False and cannot be used with binary data.",
1676    }
1677
1678    def apply_empty(self, name, n, evaluation):
1679        "BinaryRead[InputStream[name_, n_]]"
1680        return self.apply(name, n, None, evaluation)
1681
1682    def apply(self, name, n, typ, evaluation):
1683        "BinaryRead[InputStream[name_, n_], typ_]"
1684
1685        channel = Expression("InputStream", name, n)
1686
1687        # Check typ
1688        if typ is None:
1689            expr = Expression("BinaryRead", channel)
1690            typ = String("Byte")
1691        else:
1692            expr = Expression("BinaryRead", channel, typ)
1693
1694        # Check channel
1695        stream = stream_manager.lookup_stream(n.get_int_value())
1696
1697        if stream is None or stream.io.closed:
1698            evaluation.message("General", "openx", name)
1699            return expr
1700
1701        if stream.mode not in ["rb"]:
1702            evaluation.message("BinaryRead", "bfmt", channel)
1703            return expr
1704
1705        if typ.has_form("List", None):
1706            types = typ.get_leaves()
1707        else:
1708            types = [typ]
1709
1710        types = [t.get_string_value() for t in types]
1711        if not all(t in self.readers for t in types):
1712            evaluation.message("BinaryRead", "format", typ)
1713            return expr
1714
1715        # Read from stream
1716        result = []
1717        for t in types:
1718            try:
1719                result.append(self.readers[t](stream.io))
1720            except struct.error:
1721                result.append(SymbolEndOfFile)
1722
1723        if typ.has_form("List", None):
1724            return Expression("List", *result)
1725        else:
1726            if len(result) == 1:
1727                return result[0]
1728
1729
1730class WriteString(Builtin):
1731    """
1732    <dl>
1733    <dt>'WriteString[$stream$, $str1, $str2$, ... ]'
1734      <dd>writes the strings to the output stream.
1735    </dl>
1736
1737    >> stream = OpenWrite[];
1738    >> WriteString[stream, "This is a test 1"]
1739    >> WriteString[stream, "This is also a test 2"]
1740    >> Close[stream]
1741     = ...
1742    >> FilePrint[%]
1743     | This is a test 1This is also a test 2
1744
1745    >> stream = OpenWrite[];
1746    >> WriteString[stream, "This is a test 1", "This is also a test 2"]
1747    >> Close[stream]
1748     = ...
1749    >> FilePrint[%]
1750     | This is a test 1This is also a test 2
1751
1752    #> stream = OpenWrite[];
1753    #> WriteString[stream, 100, 1 + x + y, Sin[x  + y]]
1754    #> Close[stream]
1755     = ...
1756    #> FilePrint[%]
1757     | 1001 + x + ySin[x + y]
1758
1759    #> stream = OpenWrite[];
1760    #> WriteString[stream]
1761    #> Close[stream]
1762     = ...
1763    #> FilePrint[%]
1764
1765    #> WriteString[%%, abc]
1766    #> Streams[%%%][[1]]
1767     = ...
1768    #> Close[%]
1769     = ...
1770    #> FilePrint[%]
1771     | abc
1772
1773    """
1774
1775    messages = {
1776        "strml": ("`1` is not a string, stream, " "or list of strings and streams."),
1777        "writex": "`1`.",
1778    }
1779
1780    attributes = "Protected"
1781
1782    def apply(self, channel, expr, evaluation):
1783        "WriteString[channel_, expr___]"
1784        strm = _channel_to_stream(channel, "w")
1785
1786        if strm is None:
1787            return
1788
1789        stream = stream_manager.lookup_stream(strm.leaves[1].get_int_value())
1790
1791        if stream is None or stream.io is None or stream.io.closed:
1792            return None
1793
1794        exprs = []
1795        for expri in expr.get_sequence():
1796            result = expri.format(evaluation, "System`OutputForm")
1797            try:
1798                result = result.boxes_to_text(evaluation=evaluation)
1799            except BoxError:
1800                return evaluation.message(
1801                    "General",
1802                    "notboxes",
1803                    Expression("FullForm", result).evaluate(evaluation),
1804                )
1805            exprs.append(result)
1806        line = "".join(exprs)
1807        if type(stream) is BytesIO:
1808            line = line.encode("utf8")
1809        stream.io.write(line)
1810        try:
1811            stream.io.flush()
1812        except IOError as err:
1813            evaluation.message("WriteString", "writex", err.strerror)
1814        return SymbolNull
1815
1816
1817class _OpenAction(Builtin):
1818
1819    attributes = "Protected"
1820
1821    # BinaryFormat: 'False',
1822    # CharacterEncoding :> Automatic,
1823    # DOSTextFormat :> True,
1824    # FormatType -> InputForm,
1825    # NumberMarks :> $NumberMarks,
1826    # PageHeight -> 22, PageWidth -> 78,
1827    # TotalHeight -> Infinity,
1828    # TotalWidth -> Infinity
1829
1830    options = {
1831        "BinaryFormat": "False",
1832        "CharacterEncoding": "$CharacterEncoding",
1833    }
1834
1835    messages = {
1836        "argx": "OpenRead called with 0 arguments; 1 argument is expected.",
1837        "fstr": (
1838            "File specification `1` is not a string of " "one or more characters."
1839        ),
1840    }
1841
1842    def apply_empty(self, evaluation, options):
1843        "%(name)s[OptionsPattern[]]"
1844
1845        if isinstance(self, (OpenWrite, OpenAppend)):
1846            tmpf = tempfile.NamedTemporaryFile(dir=TMP_DIR)
1847            path = String(tmpf.name)
1848            tmpf.close()
1849            return self.apply_path(path, evaluation, options)
1850        else:
1851            evaluation.message("OpenRead", "argx")
1852            return
1853
1854    def apply_path(self, path, evaluation, options):
1855        "%(name)s[path_?NotOptionQ, OptionsPattern[]]"
1856
1857        # Options
1858        # BinaryFormat
1859        mode = self.mode
1860        if options["System`BinaryFormat"].is_true():
1861            if not self.mode.endswith("b"):
1862                mode += "b"
1863
1864        if not (isinstance(path, String) and len(path.to_python()) > 2):
1865            evaluation.message(self.__class__.__name__, "fstr", path)
1866            return
1867
1868        path_string = path.get_string_value()
1869
1870        tmp = path_search(path_string)
1871        if tmp is None:
1872            if mode in ["r", "rb"]:
1873                evaluation.message("General", "noopen", path)
1874                return
1875        else:
1876            path_string = tmp
1877
1878        try:
1879            encoding = self.get_option(options, "CharacterEncoding", evaluation)
1880            if not isinstance(encoding, String):
1881                return
1882
1883            opener = mathics_open(
1884                path_string, mode=mode, encoding=encoding.get_string_value()
1885            )
1886            opener.__enter__()
1887            n = opener.n
1888        except IOError:
1889            evaluation.message("General", "noopen", path)
1890            return
1891        except MessageException as e:
1892            e.message(evaluation)
1893            return
1894
1895        return Expression(self.stream_type, path, Integer(n))
1896
1897
1898class OpenRead(_OpenAction):
1899    """
1900    <dl>
1901    <dt>'OpenRead["file"]'
1902      <dd>opens a file and returns an InputStream.
1903    </dl>
1904
1905    >> OpenRead["ExampleData/EinsteinSzilLetter.txt"]
1906     = InputStream[...]
1907    #> Close[%];
1908
1909    S> OpenRead["https://raw.githubusercontent.com/mathics/Mathics/master/README.rst"]
1910     = InputStream[...]
1911    S> Close[%];
1912
1913    #> OpenRead[]
1914     : OpenRead called with 0 arguments; 1 argument is expected.
1915     = OpenRead[]
1916
1917    #> OpenRead[y]
1918     : File specification y is not a string of one or more characters.
1919     = OpenRead[y]
1920
1921    #> OpenRead[""]
1922     : File specification  is not a string of one or more characters.
1923     = OpenRead[]
1924
1925    #> OpenRead["MathicsNonExampleFile"]
1926     : Cannot open MathicsNonExampleFile.
1927     = OpenRead[MathicsNonExampleFile]
1928
1929    #> OpenRead["ExampleData/EinsteinSzilLetter.txt", BinaryFormat -> True]
1930     = InputStream[...]
1931    #> Close[%];
1932    """
1933
1934    mode = "r"
1935    stream_type = "InputStream"
1936
1937
1938class OpenWrite(_OpenAction):
1939    """
1940    <dl>
1941    <dt>'OpenWrite["file"]'
1942      <dd>opens a file and returns an OutputStream.
1943    </dl>
1944
1945    >> OpenWrite[]
1946     = OutputStream[...]
1947    #> Close[%];
1948
1949    #> OpenWrite[BinaryFormat -> True]
1950     = OutputStream[...]
1951    #> Close[%];
1952    """
1953
1954    mode = "w"
1955    stream_type = "OutputStream"
1956
1957
1958class OpenAppend(_OpenAction):
1959    """
1960    <dl>
1961    <dt>'OpenAppend["file"]'
1962      <dd>opens a file and returns an OutputStream to which writes are appended.
1963    </dl>
1964
1965    >> OpenAppend[]
1966     = OutputStream[...]
1967    #> Close[%];
1968
1969    #> appendFile = OpenAppend["MathicsNonExampleFile"]
1970     = OutputStream[MathicsNonExampleFile, ...]
1971
1972    #> Close[appendFile]
1973     = MathicsNonExampleFile
1974    #> DeleteFile["MathicsNonExampleFile"]
1975    """
1976
1977    mode = "a"
1978    stream_type = "OutputStream"
1979
1980
1981class Get(PrefixOperator):
1982    r"""
1983    <dl>
1984      <dt>'<<$name$'
1985      <dd>reads a file and evaluates each expression, returning only the last one.
1986
1987      <dt>'Get[$name$, Trace->True]'
1988      <dd>Runs Get tracing each line before it is evaluated.
1989    </dl>
1990
1991    S> filename = $TemporaryDirectory <> "/example_file";
1992    S> Put[x + y, filename]
1993    S> Get[filename]
1994     = x + y
1995
1996    S> filename = $TemporaryDirectory <> "/example_file";
1997    S> Put[x + y, 2x^2 + 4z!, Cos[x] + I Sin[x], filename]
1998    S> Get[filename]
1999     = Cos[x] + I Sin[x]
2000    S> DeleteFile[filename]
2001
2002    ## TODO: Requires EndPackage implemented
2003    ## 'Get' can also load packages:
2004    ## >> << "VectorAnalysis`"
2005
2006    #> Get["SomeTypoPackage`"]
2007     : Cannot open SomeTypoPackage`.
2008     = $Failed
2009
2010    ## Parser Tests
2011    #> Hold[<< ~/some_example/dir/] // FullForm
2012     = Hold[Get["~/some_example/dir/"]]
2013    #> Hold[<<`/.\-_:$*~?] // FullForm
2014     = Hold[Get["`/.\\\\-_:$*~?"]]
2015    """
2016
2017    operator = "<<"
2018    precedence = 720
2019    attributes = "Protected"
2020
2021    options = {
2022        "Trace": "False",
2023    }
2024
2025    def apply(self, path, evaluation, options):
2026        "Get[path_String, OptionsPattern[Get]]"
2027
2028        def check_options(options):
2029            # Options
2030            # TODO Proper error messages
2031
2032            result = {}
2033            trace_get = evaluation.parse("Settings`$TraceGet")
2034            if (
2035                options["System`Trace"].to_python()
2036                or trace_get.evaluate(evaluation) == SymbolTrue
2037            ):
2038                import builtins
2039
2040                result["TraceFn"] = builtins.print
2041            else:
2042                result["TraceFn"] = None
2043
2044            return result
2045
2046        py_options = check_options(options)
2047        trace_fn = py_options["TraceFn"]
2048        result = None
2049        pypath = path.get_string_value()
2050        definitions = evaluation.definitions
2051        mathics.core.streams.PATH_VAR = SymbolPath.evaluate(evaluation).to_python(string_quotes=False)
2052        try:
2053            if trace_fn:
2054                trace_fn(pypath)
2055            with mathics_open(pypath, "r") as f:
2056                feeder = MathicsFileLineFeeder(f, trace_fn)
2057                while not feeder.empty():
2058                    try:
2059                        query = parse(definitions, feeder)
2060                    except TranslateError:
2061                        return SymbolNull
2062                    finally:
2063                        feeder.send_messages(evaluation)
2064                    if query is None:  # blank line / comment
2065                        continue
2066                    result = query.evaluate(evaluation)
2067        except IOError:
2068            evaluation.message("General", "noopen", path)
2069            return SymbolFailed
2070        except MessageException as e:
2071            e.message(evaluation)
2072            return SymbolFailed
2073        return result
2074
2075    def apply_default(self, filename, evaluation):
2076        "Get[filename_]"
2077        expr = Expression("Get", filename)
2078        evaluation.message("General", "stream", filename)
2079        return expr
2080
2081
2082class Put(BinaryOperator):
2083    """
2084    <dl>
2085    <dt>'$expr$ >> $filename$'
2086      <dd>write $expr$ to a file.
2087    <dt>'Put[$expr1$, $expr2$, ..., $filename$]'
2088      <dd>write a sequence of expressions to a file.
2089    </dl>
2090
2091    ## Note a lot of these tests are:
2092    ## * a bit fragile, somewhat
2093    ## * somewhat OS dependent,
2094    ## * can leave crap in the filesystem
2095    ## * put in a pytest
2096    ##
2097    ## For these reasons this should be done a a pure test
2098    ## rather than intermingled with the doc system.
2099
2100    S> Put[40!, fortyfactorial]
2101     : fortyfactorial is not string, InputStream[], or OutputStream[]
2102     = 815915283247897734345611269596115894272000000000 >> fortyfactorial
2103    ## FIXME: final line should be
2104    ## = Put[815915283247897734345611269596115894272000000000, fortyfactorial]
2105
2106    S> filename = $TemporaryDirectory <> "/fortyfactorial";
2107    S> Put[40!, filename]
2108    S> FilePrint[filename]
2109     | 815915283247897734345611269596115894272000000000
2110    S> Get[filename]
2111     = 815915283247897734345611269596115894272000000000
2112    S> DeleteFile[filename]
2113
2114    S> filename = $TemporaryDirectory <> "/fiftyfactorial";
2115    S> Put[10!, 20!, 30!, filename]
2116    S> FilePrint[filename]
2117     | 3628800
2118     | 2432902008176640000
2119     | 265252859812191058636308480000000
2120
2121    S> DeleteFile[filename]
2122     =
2123
2124    S> filename = $TemporaryDirectory <> "/example_file";
2125    S> Put[x + y, 2x^2 + 4z!, Cos[x] + I Sin[x], filename]
2126    S> FilePrint[filename]
2127     | x + y
2128     | 2*x^2 + 4*z!
2129     | Cos[x] + I*Sin[x]
2130    S> DeleteFile[filename]
2131    """
2132
2133    operator = ">>"
2134    precedence = 30
2135
2136    def apply(self, exprs, filename, evaluation):
2137        "Put[exprs___, filename_String]"
2138        instream = Expression("OpenWrite", filename).evaluate(evaluation)
2139        if len(instream.leaves) == 2:
2140            name, n = instream.leaves
2141        else:
2142            return  # opening failed
2143        result = self.apply_input(exprs, name, n, evaluation)
2144        Expression("Close", instream).evaluate(evaluation)
2145        return result
2146
2147    def apply_input(self, exprs, name, n, evaluation):
2148        "Put[exprs___, OutputStream[name_, n_]]"
2149        stream = stream_manager.lookup_stream(n.get_int_value())
2150
2151        if stream is None or stream.io.closed:
2152            evaluation.message("Put", "openx", Expression("OutputSteam", name, n))
2153            return
2154
2155        text = [
2156            evaluation.format_output(Expression("InputForm", expr))
2157            for expr in exprs.get_sequence()
2158        ]
2159        text = "\n".join(text) + "\n"
2160        text.encode("utf-8")
2161
2162        stream.io.write(text)
2163
2164        return SymbolNull
2165
2166    def apply_default(self, exprs, filename, evaluation):
2167        "Put[exprs___, filename_]"
2168        expr = Expression("Put", exprs, filename)
2169        evaluation.message("General", "stream", filename)
2170        return expr
2171
2172
2173class PutAppend(BinaryOperator):
2174    """
2175    <dl>
2176    <dt>'$expr$ >>> $filename$'
2177      <dd>append $expr$ to a file.
2178    <dt>'PutAppend[$expr1$, $expr2$, ..., $"filename"$]'
2179      <dd>write a sequence of expressions to a file.
2180    </dl>
2181
2182    >> Put[50!, "factorials"]
2183    >> FilePrint["factorials"]
2184     | 30414093201713378043612608166064768844377641568960512000000000000
2185
2186    >> PutAppend[10!, 20!, 30!, "factorials"]
2187    >> FilePrint["factorials"]
2188     | 30414093201713378043612608166064768844377641568960512000000000000
2189     | 3628800
2190     | 2432902008176640000
2191     | 265252859812191058636308480000000
2192
2193    >> 60! >>> "factorials"
2194    >> FilePrint["factorials"]
2195     | 30414093201713378043612608166064768844377641568960512000000000000
2196     | 3628800
2197     | 2432902008176640000
2198     | 265252859812191058636308480000000
2199     | 8320987112741390144276341183223364380754172606361245952449277696409600000000000000
2200
2201    >> "string" >>> factorials
2202    >> FilePrint["factorials"]
2203     | 30414093201713378043612608166064768844377641568960512000000000000
2204     | 3628800
2205     | 2432902008176640000
2206     | 265252859812191058636308480000000
2207     | 8320987112741390144276341183223364380754172606361245952449277696409600000000000000
2208     | "string"
2209    #> DeleteFile["factorials"];
2210
2211    ## writing to dir
2212    #> x >>> /var/
2213     : Cannot open /var/.
2214     = x >>> /var/
2215
2216    ## writing to read only file
2217    #> x >>> /proc/uptime
2218     : Cannot open /proc/uptime.
2219     = x >>> /proc/uptime
2220    """
2221
2222    operator = ">>>"
2223    precedence = 30
2224    attributes = "Protected"
2225
2226    def apply(self, exprs, filename, evaluation):
2227        "PutAppend[exprs___, filename_String]"
2228        instream = Expression("OpenAppend", filename).evaluate(evaluation)
2229        if len(instream.leaves) == 2:
2230            name, n = instream.leaves
2231        else:
2232            return  # opening failed
2233        result = self.apply_input(exprs, name, n, evaluation)
2234        Expression("Close", instream).evaluate(evaluation)
2235        return result
2236
2237    def apply_input(self, exprs, name, n, evaluation):
2238        "PutAppend[exprs___, OutputStream[name_, n_]]"
2239        stream = stream_manager.lookup_stream(n.get_int_value())
2240
2241        if stream is None or stream.io.closed:
2242            evaluation.message("Put", "openx", Expression("OutputSteam", name, n))
2243            return
2244
2245        text = [
2246            str(e.do_format(evaluation, "System`OutputForm").__str__())
2247            for e in exprs.get_sequence()
2248        ]
2249        text = "\n".join(text) + "\n"
2250        text.encode("ascii")
2251
2252        stream.io.write(text)
2253
2254        return SymbolNull
2255
2256    def apply_default(self, exprs, filename, evaluation):
2257        "PutAppend[exprs___, filename_]"
2258        expr = Expression("PutAppend", exprs, filename)
2259        evaluation.message("General", "stream", filename)
2260        return expr
2261
2262
2263class ReadList(Read):
2264    """
2265    <dl>
2266    <dt>'ReadList["$file$"]'
2267      <dd>Reads all the expressions until the end of file.
2268    <dt>'ReadList["$file$", $type$]'
2269      <dd>Reads objects of a specified type until the end of file.
2270    <dt>'ReadList["$file$", {$type1$, $type2$, ...}]'
2271      <dd>Reads a sequence of specified types until the end of file.
2272    </dl>
2273
2274    >> ReadList[StringToStream["a 1 b 2"], {Word, Number}]
2275     = {{a, 1}, {b, 2}}
2276
2277    >> stream = StringToStream["\\"abc123\\""];
2278    >> ReadList[stream]
2279     = {abc123}
2280    >> InputForm[%]
2281     = {"abc123"}
2282
2283    #> ReadList[stream, "Invalid"]
2284     : Invalid is not a valid format specification.
2285     = ReadList[..., Invalid]
2286    #> Close[stream];
2287
2288
2289    #> ReadList[StringToStream["a 1 b 2"], {Word, Number}, 1]
2290     = {{a, 1}}
2291    """
2292
2293    # TODO
2294    """
2295    #> ReadList[StringToStream["a 1 b 2"], {Word, Number}, -1]
2296     : Non-negative machine-sized integer expected at position 3 in ReadList[InputStream[String, ...], {Word, Number}, -1].
2297     = ReadList[InputStream[String, ...], {Word, Number}, -1]
2298    """
2299
2300    # TODO: Expression type
2301    """
2302    #> ReadList[StringToStream["123 45 x y"], Expression]
2303     = {5535 x y}
2304    """
2305
2306    # TODO: Accept newlines in input
2307    """
2308    >> ReadList[StringToStream["123\nabc"]]
2309     = {123, abc}
2310    >> InputForm[%]
2311     = {123, abc}
2312    """
2313
2314    rules = {
2315        "ReadList[stream_]": "ReadList[stream, Expression]",
2316    }
2317
2318    attributes = "Protected"
2319
2320    options = {
2321        "NullRecords": "False",
2322        "NullWords": "False",
2323        "RecordSeparators": '{"\r\n", "\n", "\r"}',
2324        "TokenWords": "{}",
2325        "WordSeparators": '{" ", "\t"}',
2326    }
2327
2328    def apply(self, channel, types, evaluation, options):
2329        "ReadList[channel_, types_, OptionsPattern[ReadList]]"
2330
2331        # Options
2332        # TODO: Implement extra options
2333        # py_options = self.check_options(options)
2334        # null_records = py_options['NullRecords']
2335        # null_words = py_options['NullWords']
2336        # record_separators = py_options['RecordSeparators']
2337        # token_words = py_options['TokenWords']
2338        # word_separators = py_options['WordSeparators']
2339
2340        result = []
2341        while True:
2342            tmp = super(ReadList, self).apply(channel, types, evaluation, options)
2343
2344            if tmp is None:
2345                return
2346
2347            if tmp == SymbolFailed:
2348                return
2349
2350            if tmp == SymbolEndOfFile:
2351                break
2352            result.append(tmp)
2353        return from_python(result)
2354
2355    def apply_m(self, channel, types, m, evaluation, options):
2356        "ReadList[channel_, types_, m_, OptionsPattern[ReadList]]"
2357
2358        # Options
2359        # TODO: Implement extra options
2360        # py_options = self.check_options(options)
2361        # null_records = py_options['NullRecords']
2362        # null_words = py_options['NullWords']
2363        # record_separators = py_options['RecordSeparators']
2364        # token_words = py_options['TokenWords']
2365        # word_separators = py_options['WordSeparators']
2366
2367        py_m = m.get_int_value()
2368        if py_m < 0:
2369            evaluation.message(
2370                "ReadList", "intnm", Expression("ReadList", channel, types, m)
2371            )
2372            return
2373
2374        result = []
2375        for i in range(py_m):
2376            tmp = super(ReadList, self).apply(channel, types, evaluation, options)
2377
2378            if tmp == SymbolFailed:
2379                return
2380
2381            if tmp.to_python() == "EndOfFile":
2382                break
2383            result.append(tmp)
2384        return from_python(result)
2385
2386
2387class FilePrint(Builtin):
2388    """
2389    <dl>
2390    <dt>'FilePrint[$file$]'
2391      <dd>prints the raw contents of $file$.
2392    </dl>
2393
2394    #> exp = Sin[1];
2395    #> FilePrint[exp]
2396     : File specification Sin[1] is not a string of one or more characters.
2397     = FilePrint[Sin[1]]
2398
2399    #> FilePrint["somenonexistantpath_h47sdmk^&h4"]
2400     : Cannot open somenonexistantpath_h47sdmk^&h4.
2401     = FilePrint[somenonexistantpath_h47sdmk^&h4]
2402
2403    #> FilePrint[""]
2404     : File specification  is not a string of one or more characters.
2405     = FilePrint[]
2406    """
2407
2408    messages = {
2409        "fstr": (
2410            "File specification `1` is not a string of " "one or more characters."
2411        ),
2412    }
2413
2414    options = {
2415        "CharacterEncoding": "$CharacterEncoding",
2416        "RecordSeparators": '{"\r\n", "\n", "\r"}',
2417        "WordSeparators": '{" ", "\t"}',
2418    }
2419
2420    attributes = "Protected"
2421
2422    def apply(self, path, evaluation, options):
2423        "FilePrint[path_ OptionsPattern[FilePrint]]"
2424        pypath = path.to_python()
2425        if not (
2426            isinstance(pypath, str)
2427            and pypath[0] == pypath[-1] == '"'
2428            and len(pypath) > 2
2429        ):
2430            evaluation.message("FilePrint", "fstr", path)
2431            return
2432        pypath = path_search(pypath[1:-1])
2433
2434        # Options
2435        record_separators = options["System`RecordSeparators"].to_python()
2436        assert isinstance(record_separators, list)
2437        assert all(
2438            isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators
2439        )
2440        record_separators = [s[1:-1] for s in record_separators]
2441
2442        if pypath is None:
2443            evaluation.message("General", "noopen", path)
2444            return
2445
2446        if not osp.isfile(pypath):
2447            return SymbolFailed
2448
2449        try:
2450            with mathics_open(pypath, "r") as f:
2451                result = f.read()
2452        except IOError:
2453            evaluation.message("General", "noopen", path)
2454            return
2455        except MessageException as e:
2456            e.message(evaluation)
2457            return
2458
2459        result = [result]
2460        for sep in record_separators:
2461            result = [item for res in result for item in res.split(sep)]
2462
2463        if result[-1] == "":
2464            result = result[:-1]
2465
2466        for res in result:
2467            evaluation.print_out(from_python(res))
2468
2469        return SymbolNull
2470
2471
2472class Close(Builtin):
2473    """
2474    <dl>
2475    <dt>'Close[$stream$]'
2476      <dd>closes an input or output stream.
2477    </dl>
2478
2479    >> Close[StringToStream["123abc"]]
2480     = String
2481
2482    >> Close[OpenWrite[]]
2483     = ...
2484
2485    #> Streams[] == (Close[OpenWrite[]]; Streams[])
2486     = True
2487
2488    #> Close["abc"]
2489     : abc is not open.
2490     = Close[abc]
2491
2492    #> strm = OpenWrite[];
2493    #> Close[strm];
2494    #> Quiet[Close[strm]]
2495     = Close[OutputStream[...]]
2496    """
2497
2498    attributes = "Protected"
2499
2500    messages = {
2501        "closex": "`1`.",
2502    }
2503
2504    def apply(self, channel, evaluation):
2505        "Close[channel_]"
2506
2507        if channel.has_form(("InputStream", "OutputStream"), 2):
2508            [name, n] = channel.get_leaves()
2509            stream = stream_manager.lookup_stream(n.get_int_value())
2510        else:
2511            stream = None
2512
2513        if stream is None or stream.io is None or stream.io.closed:
2514            evaluation.message("General", "openx", channel)
2515            return
2516
2517        stream.io.close()
2518        return name
2519
2520
2521class StreamPosition(Builtin):
2522    """
2523    <dl>
2524    <dt>'StreamPosition[$stream$]'
2525      <dd>returns the current position in a stream as an integer.
2526    </dl>
2527
2528    >> stream = StringToStream["Mathics is cool!"]
2529     = ...
2530
2531    >> Read[stream, Word]
2532     = Mathics
2533
2534    >> StreamPosition[stream]
2535     = 7
2536    """
2537
2538    attributes = "Protected"
2539
2540    def apply_input(self, name, n, evaluation):
2541        "StreamPosition[InputStream[name_, n_]]"
2542        stream = stream_manager.lookup_stream(n.get_int_value())
2543
2544        if stream is None or stream.io is None or stream.io.closed:
2545            evaluation.message("General", "openx", name)
2546            return
2547
2548        return from_python(stream.io.tell())
2549
2550    def apply_output(self, name, n, evaluation):
2551        "StreamPosition[OutputStream[name_, n_]]"
2552        self.input_apply(name, n, evaluation)
2553
2554    def apply_default(self, stream, evaluation):
2555        "StreamPosition[stream_]"
2556        evaluation.message("General", "stream", stream)
2557        return
2558
2559
2560class SetStreamPosition(Builtin):
2561    """
2562    <dl>
2563    <dt>'SetStreamPosition[$stream$, $n$]'
2564      <dd>sets the current position in a stream.
2565    </dl>
2566
2567    >> stream = StringToStream["Mathics is cool!"]
2568     = ...
2569
2570    >> SetStreamPosition[stream, 8]
2571     = 8
2572
2573    >> Read[stream, Word]
2574     = is
2575
2576    #> SetStreamPosition[stream, -5]
2577     : Python2 cannot handle negative seeks.
2578     = 10
2579
2580    >> SetStreamPosition[stream, Infinity]
2581     = 16
2582    """
2583
2584    # TODO: Seeks beyond stream should return stmrng message
2585    """
2586    #> SetStreamPosition[stream, 40]
2587     = ERROR_MESSAGE_HERE
2588    """
2589
2590    messages = {
2591        "int": "Integer expected at position 2 in `1`.",
2592        "stmrng": (
2593            "Cannot set the current point in stream `1` to position `2`. The "
2594            "requested position exceeds the number of characters in the file"
2595        ),
2596        "python2": "Python2 cannot handle negative seeks.",  # FIXME: Python3?
2597    }
2598
2599    attributes = "Protected"
2600
2601    def apply_input(self, name, n, m, evaluation):
2602        "SetStreamPosition[InputStream[name_, n_], m_]"
2603        stream = stream_manager.lookup_stream(n.get_int_value())
2604
2605        if stream is None or stream.io is None or stream.io.closed:
2606            evaluation.message("General", "openx", name)
2607            return
2608
2609        if not stream.io.seekable:
2610            raise NotImplementedError
2611
2612        seekpos = m.to_python()
2613        if not (isinstance(seekpos, int) or seekpos == float("inf")):
2614            evaluation.message(
2615                "SetStreamPosition", "stmrng", Expression("InputStream", name, n), m
2616            )
2617            return
2618
2619        try:
2620            if seekpos == float("inf"):
2621                stream.io.seek(0, 2)
2622            else:
2623                if seekpos < 0:
2624                    stream.io.seek(seekpos, 2)
2625                else:
2626                    stream.io.seek(seekpos)
2627        except IOError:
2628            evaluation.message("SetStreamPosition", "python2")
2629
2630        return from_python(stream.io.tell())
2631
2632    def apply_output(self, name, n, m, evaluation):
2633        "SetStreamPosition[OutputStream[name_, n_], m_]"
2634        return self.apply_input(name, n, m, evaluation)
2635
2636    def apply_default(self, stream, evaluation):
2637        "SetStreamPosition[stream_]"
2638        evaluation.message("General", "stream", stream)
2639        return
2640
2641
2642class Skip(Read):
2643    """
2644    <dl>
2645    <dt>'Skip[$stream$, $type$]'
2646      <dd>skips ahead in an input steream by one object of the specified $type$.
2647    <dt>'Skip[$stream$, $type$, $n$]'
2648      <dd>skips ahead in an input steream by $n$ objects of the specified $type$.
2649    </dl>
2650
2651    >> stream = StringToStream["a b c d"];
2652    >> Read[stream, Word]
2653     = a
2654    >> Skip[stream, Word]
2655    >> Read[stream, Word]
2656     = c
2657    #> Close[stream];
2658
2659    >> stream = StringToStream["a b c d"];
2660    >> Read[stream, Word]
2661     = a
2662    >> Skip[stream, Word, 2]
2663    >> Read[stream, Word]
2664     = d
2665    #> Skip[stream, Word]
2666     = EndOfFile
2667    #> Close[stream];
2668    """
2669
2670    rules = {
2671        "Skip[InputStream[name_, n_], types_]": "Skip[InputStream[name, n], types, 1]",
2672    }
2673
2674    messages = {
2675        "intm": "Non-negative machine-sized integer expected at position 3 in `1`",
2676    }
2677
2678    options = {
2679        "AnchoredSearch": "False",
2680        "IgnoreCase": "False",
2681        "WordSearch": "False",
2682        "RecordSeparators": '{"\r\n", "\n", "\r"}',
2683        "WordSeparators": '{" ", "\t"}',
2684    }
2685
2686    attributes = "Protected"
2687
2688    def apply(self, name, n, types, m, evaluation, options):
2689        "Skip[InputStream[name_, n_], types_, m_, OptionsPattern[Skip]]"
2690
2691        channel = Expression("InputStream", name, n)
2692
2693        # Options
2694        # TODO Implement extra options
2695        # py_options = self.check_options(options)
2696        # null_records = py_options['NullRecords']
2697        # null_words = py_options['NullWords']
2698        # record_separators = py_options['RecordSeparators']
2699        # token_words = py_options['TokenWords']
2700        # word_separators = py_options['WordSeparators']
2701
2702        py_m = m.to_python()
2703        if not (isinstance(py_m, int) and py_m > 0):
2704            evaluation.message(
2705                "Skip",
2706                "intm",
2707                Expression("Skip", Expression("InputStream", name, n), types, m),
2708            )
2709            return
2710        for i in range(py_m):
2711            result = super(Skip, self).apply(channel, types, evaluation, options)
2712            if result == SymbolEndOfFile:
2713                return result
2714        return SymbolNull
2715
2716
2717class Find(Read):
2718    """
2719    <dl>
2720    <dt>'Find[$stream$, $text$]'
2721      <dd>find the first line in $stream$ that contains $text$.
2722    </dl>
2723
2724    >> stream = OpenRead["ExampleData/EinsteinSzilLetter.txt"];
2725    >> Find[stream, "uranium"]
2726     = in manuscript, leads me to expect that the element uranium may be turned into
2727    >> Find[stream, "uranium"]
2728     = become possible to set up a nuclear chain reaction in a large mass of uranium,
2729    >> Close[stream]
2730     = ...
2731
2732    >> stream = OpenRead["ExampleData/EinsteinSzilLetter.txt"];
2733    >> Find[stream, {"energy", "power"} ]
2734     = a new and important source of energy in the immediate future. Certain aspects
2735    >> Find[stream, {"energy", "power"} ]
2736     = by which vast amounts of power and large quantities of new radium-like
2737    >> Close[stream]
2738     = ...
2739    """
2740
2741    attributes = "Protected"
2742
2743    options = {
2744        "AnchoredSearch": "False",
2745        "IgnoreCase": "False",
2746        "WordSearch": "False",
2747        "RecordSeparators": '{"\r\n", "\n", "\r"}',
2748        "WordSeparators": '{" ", "\t"}',
2749    }
2750
2751    def apply(self, name, n, text, evaluation, options):
2752        "Find[InputStream[name_, n_], text_, OptionsPattern[Find]]"
2753
2754        # Options
2755        # TODO Implement extra options
2756        # py_options = self.check_options(options)
2757        # anchored_search = py_options['AnchoredSearch']
2758        # ignore_case = py_options['IgnoreCase']
2759        # word_search = py_options['WordSearch']
2760        # record_separators = py_options['RecordSeparators']
2761        # word_separators = py_options['WordSeparators']
2762
2763        py_text = text.to_python()
2764
2765        channel = Expression("InputStream", name, n)
2766
2767        if not isinstance(py_text, list):
2768            py_text = [py_text]
2769
2770        if not all(isinstance(t, str) and t[0] == t[-1] == '"' for t in py_text):
2771            evaluation.message("Find", "unknown", Expression("Find", channel, text))
2772            return
2773
2774        py_text = [t[1:-1] for t in py_text]
2775
2776        while True:
2777            tmp = super(Find, self).apply(
2778                channel, Symbol("Record"), evaluation, options
2779            )
2780            py_tmp = tmp.to_python()[1:-1]
2781
2782            if py_tmp == "System`EndOfFile":
2783                evaluation.message(
2784                    "Find", "notfound", Expression("Find", channel, text)
2785                )
2786                return SymbolFailed
2787
2788            for t in py_text:
2789                if py_tmp.find(t) != -1:
2790                    return from_python(py_tmp)
2791
2792
2793class InputStream(Builtin):
2794    """
2795    <dl>
2796    <dt>'InputStream[$name$, $n$]'
2797      <dd>represents an input stream.
2798    </dl>
2799
2800    >> stream = StringToStream["Mathics is cool!"]
2801     = ...
2802    >> Close[stream]
2803     = String
2804    """
2805
2806    attributes = "Protected"
2807
2808    def apply(self, name, n, evaluation):
2809        "InputStream[name_, n_]"
2810        return
2811
2812
2813class OutputStream(Builtin):
2814    """
2815    <dl>
2816    <dt>'OutputStream[$name$, $n$]'
2817      <dd>represents an output stream.
2818    </dl>
2819
2820    >> OpenWrite[]
2821     = ...
2822    >> Close[%]
2823     = ...
2824    """
2825
2826    attributes = "Protected"
2827
2828    def apply(self, name, n, evaluation):
2829        "OutputStream[name_, n_]"
2830        return
2831
2832
2833class StringToStream(Builtin):
2834    """
2835    <dl>
2836    <dt>'StringToStream[$string$]'
2837      <dd>converts a $string$ to an open input stream.
2838    </dl>
2839
2840    >> strm = StringToStream["abc 123"]
2841     = InputStream[String, ...]
2842
2843    #> Read[strm, Word]
2844     = abc
2845
2846    #> Read[strm, Number]
2847     = 123
2848
2849    #> Close[strm]
2850     = String
2851    """
2852
2853    attributes = "Protected"
2854
2855    def apply(self, string, evaluation):
2856        "StringToStream[string_]"
2857        pystring = string.to_python()[1:-1]
2858        fp = io.StringIO(str(pystring))
2859
2860        name = Symbol("String")
2861        stream = stream_manager.add(pystring, io=fp)
2862        return Expression("InputStream", name, Integer(stream.n))
2863
2864
2865class Streams(Builtin):
2866    """
2867    <dl>
2868    <dt>'Streams[]'
2869      <dd>returns a list of all open streams.
2870    </dl>
2871
2872    >> Streams[]
2873     = ...
2874
2875    >> Streams["stdout"]
2876     = ...
2877
2878    #> OpenWrite[]
2879     = ...
2880    #> Streams[%[[1]]]
2881     = {OutputStream[...]}
2882
2883    #> Streams["some_nonexistant_name"]
2884     = {}
2885    """
2886
2887    attributes = "Protected"
2888
2889    def apply(self, evaluation):
2890        "Streams[]"
2891        return self.apply_name(None, evaluation)
2892
2893    def apply_name(self, name, evaluation):
2894        "Streams[name_String]"
2895        result = []
2896        for stream in stream_manager.STREAMS.values():
2897            if stream is None or stream.io.closed:
2898                continue
2899            if isinstance(stream.io, io.StringIO):
2900                head = "InputStream"
2901                _name = Symbol("String")
2902            else:
2903                mode = stream.mode
2904                if mode in ["r", "rb"]:
2905                    head = "InputStream"
2906                elif mode in ["w", "a", "wb", "ab"]:
2907                    head = "OutputStream"
2908                else:
2909                    raise ValueError("Unknown mode {0}".format(mode))
2910                _name = String(stream.name)
2911            expr = Expression(head, _name, Integer(stream.n))
2912            if name is None or _name == name:
2913                result.append(expr)
2914        return Expression("List", *result)
2915