From 22d79c182d01f263053f096420318f9ddf1d6ca7 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 23 Sep 2024 21:53:47 -0400 Subject: [PATCH] Report Read noopen error... and move parse Read ReadList options to eval. --- mathics/builtin/files_io/files.py | 91 +++++++----------------------- mathics/eval/files_io/read.py | 92 +++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 77 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index c717f8054..6b1141266 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -798,6 +798,7 @@ class Read(Builtin): messages = { "openx": "`1` is not open.", + "noopen": "Cannot open `1`.", "readf": "`1` is not a valid format specification.", "readn": "Invalid real number found when reading from `1`.", "readt": "Invalid input found when reading `1` from `2`.", @@ -819,75 +820,15 @@ class Read(Builtin): } summary_text = "read an object of the specified type from a stream" - def check_options(self, options) -> dict: - # Options - # TODO Proper error messages - - result = {} - keys = list(options.keys()) - - # AnchoredSearch - if "System`AnchoredSearch" in keys: - anchored_search = options["System`AnchoredSearch"].to_python() - assert anchored_search in [True, False] - result["AnchoredSearch"] = anchored_search - - # IgnoreCase - if "System`IgnoreCase" in keys: - ignore_case = options["System`IgnoreCase"].to_python() - assert ignore_case in [True, False] - result["IgnoreCase"] = ignore_case - - # WordSearch - if "System`WordSearch" in keys: - word_search = options["System`WordSearch"].to_python() - assert word_search in [True, False] - result["WordSearch"] = word_search - - # RecordSeparators - if "System`RecordSeparators" in keys: - record_separators = options["System`RecordSeparators"].to_python() - assert isinstance(record_separators, list) - assert all( - isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators - ) - record_separators = [s[1:-1] for s in record_separators] - result["RecordSeparators"] = record_separators - - # WordSeparators - if "System`WordSeparators" in keys: - word_separators = options["System`WordSeparators"].to_python() - assert isinstance(word_separators, list) - assert all( - isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators - ) - word_separators = [s[1:-1] for s in word_separators] - result["WordSeparators"] = word_separators - - # NullRecords - if "System`NullRecords" in keys: - null_records = options["System`NullRecords"].to_python() - assert null_records in [True, False] - result["NullRecords"] = null_records - - # NullWords - if "System`NullWords" in keys: - null_words = options["System`NullWords"].to_python() - assert null_words in [True, False] - result["NullWords"] = null_words - - # TokenWords - if "System`TokenWords" in keys: - token_words = options["System`TokenWords"].to_python() - assert token_words == [] - result["TokenWords"] = token_words - - return result - def eval(self, channel, types, evaluation: Evaluation, options: dict): "Read[channel_, types_, OptionsPattern[Read]]" - name, n, stream = read_name_and_stream_from_channel(channel, evaluation) + try: + name, n, stream = read_name_and_stream_from_channel(channel, evaluation) + except IOError as e: + evaluation.message("Read", "noopen", str(e)) + return SymbolFailed + if name is None: return @@ -927,17 +868,23 @@ def eval(self, channel, types, evaluation: Evaluation, options: dict): result = [] - read_word = read_from_stream(stream, word_separators, evaluation.message) - read_record = read_from_stream(stream, record_separators, evaluation.message) + read_word = read_from_stream( + stream, word_separators, token_words, evaluation.message + ) + read_record = read_from_stream( + stream, record_separators, token_words, evaluation.message + ) read_number = read_from_stream( stream, word_separators + record_separators, + token_words, evaluation.message, ["+", "-", "."] + [str(i) for i in range(10)], ) read_real = read_from_stream( stream, word_separators + record_separators, + token_words, evaluation.message, ["+", "-", ".", "e", "E", "^", "*"] + [str(i) for i in range(10)], ) @@ -1103,7 +1050,7 @@ def eval(self, channel, types, evaluation: Evaluation, options: dict): # Options # TODO: Implement extra options - # py_options = self.check_options(options) + # py_options = parse_read_options(options) # null_records = py_options['NullRecords'] # null_words = py_options['NullWords'] # record_separators = py_options['RecordSeparators'] @@ -1130,7 +1077,7 @@ def eval_n(self, channel, types, n: Integer, evaluation: Evaluation, options: di # Options # TODO: Implement extra options - # py_options = self.check_options(options) + # py_options = parse_read_options(options) # null_records = py_options['NullRecords'] # null_words = py_options['NullWords'] # record_separators = py_options['RecordSeparators'] @@ -1337,7 +1284,7 @@ def eval(self, name, n, types, m, evaluation: Evaluation, options: dict): # Options # TODO Implement extra options - # py_options = self.check_options(options) + # py_options = parse_read_options(options) # null_records = py_options['NullRecords'] # null_words = py_options['NullWords'] # record_separators = py_options['RecordSeparators'] @@ -1399,7 +1346,7 @@ def eval(self, name, n, text, evaluation: Evaluation, options: dict): # Options # TODO Implement extra options - # py_options = self.check_options(options) + # py_options = parse_read_options(options) # anchored_search = py_options['AnchoredSearch'] # ignore_case = py_options['IgnoreCase'] # word_search = py_options['WordSearch'] diff --git a/mathics/eval/files_io/read.py b/mathics/eval/files_io/read.py index 6e4808364..49c1dd3f6 100644 --- a/mathics/eval/files_io/read.py +++ b/mathics/eval/files_io/read.py @@ -3,7 +3,7 @@ """ import io -from typing import Optional, Tuple +from typing import Callable, Optional, Tuple from mathics.builtin.atomic.strings import to_python_encoding from mathics.core.atoms import Integer, String @@ -77,7 +77,7 @@ def __enter__(self, is_temporary_file=False): if path is None and self.mode in ["w", "a", "wb", "ab"]: path = self.name if path is None: - raise IOError + raise IOError(self.name) # Open the file self.fp = io.open(path, self.mode, encoding=self.encoding) @@ -122,6 +122,73 @@ def channel_to_stream(channel, mode="r"): return None +def parse_read_options(options) -> dict: + """ + Parses and checks Read[] or ReadList[] options + """ + # Options + # TODO Proper error messages + + result = {} + keys = list(options.keys()) + + # AnchoredSearch + if "System`AnchoredSearch" in keys: + anchored_search = options["System`AnchoredSearch"].to_python() + assert anchored_search in [True, False] + result["AnchoredSearch"] = anchored_search + + # IgnoreCase + if "System`IgnoreCase" in keys: + ignore_case = options["System`IgnoreCase"].to_python() + assert ignore_case in [True, False] + result["IgnoreCase"] = ignore_case + + # WordSearch + if "System`WordSearch" in keys: + word_search = options["System`WordSearch"].to_python() + assert word_search in [True, False] + result["WordSearch"] = word_search + + # RecordSeparators + if "System`RecordSeparators" in keys: + record_separators = options["System`RecordSeparators"].to_python() + assert isinstance(record_separators, list) + assert all( + isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators + ) + record_separators = [s[1:-1] for s in record_separators] + result["RecordSeparators"] = record_separators + + # WordSeparators + if "System`WordSeparators" in keys: + word_separators = options["System`WordSeparators"].to_python() + assert isinstance(word_separators, list) + assert all(isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators) + word_separators = [s[1:-1] for s in word_separators] + result["WordSeparators"] = word_separators + + # NullRecords + if "System`NullRecords" in keys: + null_records = options["System`NullRecords"].to_python() + assert null_records in [True, False] + result["NullRecords"] = null_records + + # NullWords + if "System`NullWords" in keys: + null_words = options["System`NullWords"].to_python() + assert null_words in [True, False] + result["NullWords"] = null_words + + # TokenWords + if "System`TokenWords" in keys: + token_words = options["System`TokenWords"].to_python() + assert token_words == [] + result["TokenWords"] = token_words + + return result + + def close_stream(stream: Stream, stream_number: int): """ Close stream: `stream` and delete it from the list of streams we manage. @@ -242,6 +309,7 @@ def read_check_options(options: dict, evaluation: Evaluation) -> Optional[dict]: if not (isinstance(token_words, list) or isinstance(token_words, String)): evaluation.message("ReadList", "opstl", token_words) return None + # from trepan.api import debug; debug() result["TokenWords"] = token_words return result @@ -265,13 +333,17 @@ def read_get_separators( return record_separators, token_words, word_separators -def read_from_stream(stream, word_separators, msgfn, accepted=None): +def read_from_stream( + stream, word_separators: list, token_words: list, msgfn: Callable, accepted=None +): """ This is a generator that returns "words" from stream deliminated by - "word_separators" + "word_separators" or "token_words". """ + # from trepan.api import debug; debug() while True: word = "" + some_token_word_prefix = "" while True: try: tmp = stream.io.read(1) @@ -304,15 +376,25 @@ def read_from_stream(stream, word_separators, msgfn, accepted=None): continue if stream.io.seekable(): stream.io.seek(stream.io.tell() - 1) + word += some_token_word_prefix last_word = word word = "" + some_token_word_prefix = "" yield last_word break if accepted is not None and tmp not in accepted: + word += some_token_word_prefix last_word = word word = "" + some_token_word_prefix = "" yield last_word break - word += tmp + some_token_word_prefix += tmp + for token_word in token_words: + if token_word == some_token_word_prefix: + continue + else: + word += some_token_word_prefix + some_token_word_prefix = ""