From 6da8d6e5822f21d076b39660cc04c38fbc2a90de Mon Sep 17 00:00:00 2001 From: gf712 Date: Fri, 26 Jun 2026 16:37:02 +0100 Subject: [PATCH] runtime: support sys.stdout and sys.stderr and implement proper print --- integration/tests/file_io.py | 20 ++ integration/tests/file_io_data.txt | 2 + src/interpreter/Interpreter.cpp | 45 +++- src/runtime/PyMemoryView.cpp | 19 ++ src/runtime/PyMemoryView.hpp | 3 + src/runtime/PyObject.cpp | 7 +- src/runtime/PyObject.hpp | 1 + src/runtime/modules/BuiltinsModule.cpp | 43 +++- src/runtime/modules/IOModule.cpp | 331 ++++++++++++++++++------- 9 files changed, 369 insertions(+), 102 deletions(-) create mode 100644 integration/tests/file_io.py create mode 100644 integration/tests/file_io_data.txt diff --git a/integration/tests/file_io.py b/integration/tests/file_io.py new file mode 100644 index 00000000..9529e95e --- /dev/null +++ b/integration/tests/file_io.py @@ -0,0 +1,20 @@ +# Regression test: closing a path-opened FileIO after reading must not crash. +# Previously FileIO.close() called ferror() on the underlying FILE* *after* +# closing it (which resets the pointer to NULL), segfaulting on ferror(NULL). +# This is the same path the import machinery uses to read a module's source. + +# Run with cwd == integration/ (as the integration runner does). +DATA = "tests/file_io_data.txt" + +# 1. read inside a `with` block -> __exit__ closes the file (the crashing path). +with open(DATA, "rb") as f: + data = f.read() +assert data == b"line1\nline2\n", data + +# 2. explicit close, and close() must be idempotent (callable more than once). +g = open(DATA, "rb") +assert g.read() == b"line1\nline2\n" +g.close() +g.close() + +print("file_io: ok") diff --git a/integration/tests/file_io_data.txt b/integration/tests/file_io_data.txt new file mode 100644 index 00000000..c0d0fb45 --- /dev/null +++ b/integration/tests/file_io_data.txt @@ -0,0 +1,2 @@ +line1 +line2 diff --git a/src/interpreter/Interpreter.cpp b/src/interpreter/Interpreter.cpp index eb43d508..fea3bbdc 100644 --- a/src/interpreter/Interpreter.cpp +++ b/src/interpreter/Interpreter.cpp @@ -22,6 +22,7 @@ #include "executable/Program.hpp" #include "executable/bytecode/Bytecode.hpp" #include "executable/bytecode/BytecodeProgram.hpp" +#include "utilities.hpp" #include @@ -168,20 +169,48 @@ void Interpreter::internal_setup(const std::string &name, m_codec_search_path = PyList::create().unwrap(); m_codec_search_path_cache = PyDict::create().unwrap(); + PyModule *io_module; for (const auto &[name, module_factory] : builtin_modules) { - if (module_factory) { m_modules->insert(String{ std::string{ name } }, module_factory()); } + if (module_factory) { + auto mod = module_factory(); + if (name == "_io") { io_module = mod; } + m_modules->insert(String{ std::string{ name } }, mod); + } } { - auto open = io_module()->symbol_table()->map().at(String{ "open" }); + ASSERT(io_module); + auto open = io_module->symbol_table()->map().at(String{ "open" }); m_builtins->add_symbol(PyString::create("open").unwrap(), open); + auto *file_io = + std::get(io_module->symbol_table()->map().at(String{ "FileIO" })); + auto *buffered_writer = + std::get(io_module->symbol_table()->map().at(String{ "BufferedWriter" })); + auto *text_io_wrapper = + std::get(io_module->symbol_table()->map().at(String{ "TextIOWrapper" })); + auto py_stdout = + file_io->call(PyTuple::create(Number{ 1 }, String{ "wb" }).unwrap(), nullptr) + .and_then([buffered_writer](PyObject *stdout) { + return buffered_writer->call(PyTuple::create(stdout).unwrap(), nullptr); + }) + .and_then([text_io_wrapper](PyObject *stdout_buffer_writer) { + return text_io_wrapper->call( + PyTuple::create(stdout_buffer_writer).unwrap(), nullptr); + }); + ASSERT(py_stdout.is_ok()); + auto py_stderr = + file_io->call(PyTuple::create(Number{ 2 }, String{ "wb" }).unwrap(), nullptr) + .and_then([buffered_writer](PyObject *stderr) { + return buffered_writer->call(PyTuple::create(stderr).unwrap(), nullptr); + }) + .and_then([text_io_wrapper](PyObject *stderr_buffer_writer) { + return text_io_wrapper->call( + PyTuple::create(stderr_buffer_writer).unwrap(), nullptr); + }); + ASSERT(py_stderr.is_ok()); + sys->add_symbol(PyString::create("stdout").unwrap(), py_stdout.unwrap()); + sys->add_symbol(PyString::create("stderr").unwrap(), py_stderr.unwrap()); } - // { - // auto open = io_module()->symbol_table()->map().at(String{ "open" }); - // m_builtins->add_symbol(PyString::create("open").unwrap(), open); - // sys->add_symbol(PyString::create("stderr").unwrap(), - // std::get(open)->call(PyTuple::create().unwrap(), nullptr).unwrap()); - // } if (config.requires_importlib) { auto *_imp = imp_module(); diff --git a/src/runtime/PyMemoryView.cpp b/src/runtime/PyMemoryView.cpp index c52c90ac..5c939d9a 100644 --- a/src/runtime/PyMemoryView.cpp +++ b/src/runtime/PyMemoryView.cpp @@ -336,6 +336,25 @@ PyResult PyMemoryView::tolist() PyResult PyMemoryView::__repr__() const { return PyString::create(to_string()); } +PyResult PyMemoryView::__getbuffer__(PyBuffer &view, int /*flags*/) +{ + // TODO: validate flags + view = PyBuffer{ + .buf = m_view.buf->view(), + .obj = this, + .len = m_view.len, + .itemsize = m_view.itemsize, + .readonly = m_view.readonly, + .ndim = m_view.ndim, + .format = m_view.format, + .shape = m_view.shape, + .strides = m_view.strides, + .suboffsets = m_view.suboffsets, + .internal = m_view.internal, + }; + return Ok(std::monostate{}); +} + namespace { std::once_flag memoryview_flag; diff --git a/src/runtime/PyMemoryView.hpp b/src/runtime/PyMemoryView.hpp index edaf893b..b5d21871 100644 --- a/src/runtime/PyMemoryView.hpp +++ b/src/runtime/PyMemoryView.hpp @@ -41,6 +41,9 @@ class PyMemoryView : public PyBaseObject size_t itemsize() const { return m_view.itemsize; } + PyResult __getbuffer__(PyBuffer &view, int /*flags*/); + + private: static PyResult create_view(PyBuffer &main_view); }; diff --git a/src/runtime/PyObject.cpp b/src/runtime/PyObject.cpp index a924a90e..a80657e1 100644 --- a/src/runtime/PyObject.cpp +++ b/src/runtime/PyObject.cpp @@ -468,12 +468,7 @@ PyResult PyObject::get_buffer(PyBuffer &buffer, int flags) { return as_buffer().and_then( [&buffer, flags, this](const PyBufferProcs &bc) -> PyResult { - (void)buffer; - (void)flags; - (void)this; - (void)bc; - return Err(not_implemented_error("get_buffer not implemented!")); - // return bc.getbuffer(this, buffer, flags); + return bc.getbuffer(this, buffer, flags); }); } diff --git a/src/runtime/PyObject.hpp b/src/runtime/PyObject.hpp index 92721f7a..4d16e027 100644 --- a/src/runtime/PyObject.hpp +++ b/src/runtime/PyObject.hpp @@ -433,6 +433,7 @@ class PyObject : public Cell PyResult as_sequence(); PyResult as_buffer(); + // TODO: add strongly typed flags PyResult get_buffer(PyBuffer &, int flags); PyResult getattribute(PyObject *attribute) const; diff --git a/src/runtime/modules/BuiltinsModule.cpp b/src/runtime/modules/BuiltinsModule.cpp index 4a2d8793..df502e28 100644 --- a/src/runtime/modules/BuiltinsModule.cpp +++ b/src/runtime/modules/BuiltinsModule.cpp @@ -67,10 +67,20 @@ static PyModule *s_builtin_module = nullptr; namespace { -PyResult print(const PyTuple *args, const PyDict *kwargs, Interpreter &) +PyResult print(const PyTuple *args, const PyDict *kwargs, Interpreter &interpreter) { std::string separator = " "; std::string end = "\n"; + // TODO: handle error case? + PyObject *file = + PyObject::from(interpreter.get_imported_module(PyString::create("sys").unwrap()) + ->symbol_table() + ->map() + .at(String{ "stdout" })) + .unwrap(); + // sys.stdout may be None when FILE* stdout isn't connected + if (!file || file == py_none()) { return Ok(py_none()); } + bool flush = true; if (kwargs) { static const Value separator_keyword = String{ "sep" }; static const Value end_keyword = String{ "end" }; @@ -98,6 +108,9 @@ PyResult print(const PyTuple *args, const PyDict *kwargs, Interprete end = std::get(maybe_str).s; } } + auto file_write_ = file->get_method(PyString::create("write").unwrap()); + if (file_write_.is_err()) { return file_write_; } + auto file_write = file_write_.unwrap(); auto strfunc = [](const PyResult &arg) -> PyResult { if (arg.is_err()) return Err(arg.unwrap_err()); return arg.unwrap()->str(); @@ -106,7 +119,11 @@ PyResult print(const PyTuple *args, const PyDict *kwargs, Interprete auto arg_it = args->begin(); auto arg_it_end = args->end(); if (arg_it == arg_it_end) { - std::cout << std::endl; + if (flush) { + auto file_flush_ = file->get_method(PyString::create("flush").unwrap()); + if (file_flush_.is_err()) { return file_flush_; } + return file_flush_.unwrap()->call(nullptr, nullptr); + } return Ok(py_none()); } --arg_it_end; @@ -117,7 +134,10 @@ PyResult print(const PyTuple *args, const PyDict *kwargs, Interprete if (reprobj_.is_err()) { return reprobj_; } auto reprobj = reprobj_.unwrap(); spdlog::debug("repr result: {}", reprobj->value()); - std::cout << reprobj->value() << separator; + if (auto result = file_write->call(PyTuple::create(reprobj).unwrap(), nullptr); + result.is_err()) { + return result; + } std::advance(arg_it, 1); } @@ -126,8 +146,23 @@ PyResult print(const PyTuple *args, const PyDict *kwargs, Interprete if (reprobj_.is_err()) { return reprobj_; } auto reprobj = reprobj_.unwrap(); spdlog::debug("repr result: {}", reprobj->value()); - std::cout << reprobj->value() << end; + if (auto result = file_write->call(PyTuple::create(reprobj).unwrap(), nullptr); + result.is_err()) { + return result; + } + if (!end.empty()) { + if (auto result = + file_write->call(PyTuple::create(PyString::create(end).unwrap()).unwrap(), nullptr); + result.is_err()) { + return result; + } + } + if (flush) { + auto file_flush_ = file->get_method(PyString::create("flush").unwrap()); + if (file_flush_.is_err()) { return file_flush_; } + return file_flush_.unwrap()->call(nullptr, nullptr); + } return Ok(py_none()); } diff --git a/src/runtime/modules/IOModule.cpp b/src/runtime/modules/IOModule.cpp index a926a616..17538467 100644 --- a/src/runtime/modules/IOModule.cpp +++ b/src/runtime/modules/IOModule.cpp @@ -2,11 +2,15 @@ #include "runtime/MemoryError.hpp" #include "runtime/NotImplementedError.hpp" #include "runtime/OSError.hpp" +#include "runtime/PyArgParser.hpp" #include "runtime/PyBool.hpp" #include "runtime/PyBytes.hpp" #include "runtime/PyDict.hpp" #include "runtime/PyFunction.hpp" +#include "runtime/PyInteger.hpp" #include "runtime/PyList.hpp" +#include "runtime/PyMemoryView.hpp" +#include "runtime/PyNone.hpp" #include "runtime/PyObject.hpp" #include "runtime/PyString.hpp" #include "runtime/PyTuple.hpp" @@ -18,6 +22,8 @@ #include "runtime/types/builtin.hpp" #include "utilities.hpp" #include "vm/VM.hpp" +#include +#include #include #include @@ -556,7 +562,10 @@ class RawIOBase : public IOBase return Err(not_implemented_error("_RawIOBase.readinto")); } - PyResult write() const { return Err(not_implemented_error("_RawIOBase.write")); } + PyResult write(PyObject *) const + { + return Err(not_implemented_error("_RawIOBase.write")); + } static PyType *register_type(PyModule *module) { @@ -796,9 +805,9 @@ struct Buffered bool fast_closed_checks{ false }; std::unique_ptr buffer; - bool valid_readbuffer() { return readable_ && buffer && buffer->in_avail() != -1; } + bool valid_readbuffer() const { return readable_ && buffer && buffer->in_avail() != -1; } - int64_t readahead() { return valid_readbuffer() ? buffer->in_avail() : 0; } + int64_t readahead() const { return valid_readbuffer() ? buffer->in_avail() : 0; } PyResult check_initialized() const { @@ -813,6 +822,12 @@ struct Buffered return Ok(std::monostate{}); } + PyResult check_closed(std::string_view err_msg) const + { + if (is_closed() && readahead() == 0) { return Err(value_error(std::string{ err_msg })); } + return Ok(std::monostate{}); + } + PyResult detach() { return static_cast(this) @@ -838,6 +853,8 @@ struct Buffered }); } + PyResult flush_and_rewind(); + PyResult closed() const { if (auto err = check_initialized(); err.is_err()) return Err(err.unwrap_err()); @@ -1326,11 +1343,88 @@ class BufferedWriter }); } + PyResult raw_write() + { + std::array to_write; + + while (true) { + // very innefficient - we shouldn't be getting data from buffer, create an intermediate + // bytes buffer copy just to then get a view of it. + auto written = buffer->sgetn(to_write.data(), BUFSIZ); + if (written <= 0) { break; } + auto *raw_bytes = bit_cast(to_write.data()); + auto bytes = PyBytes::create(Bytes{ .b = { raw_bytes, raw_bytes + written } }); + auto memobj = PyMemoryView::create(bytes.unwrap()); + if (memobj.is_err()) { return memobj; } + return raw->get_method(PyString::create("write").unwrap()) + .and_then([memobj](PyObject *write) { + return write->call(PyTuple::create(memobj.unwrap()).unwrap(), nullptr); + }); + } + return Ok(py_none()); + } + + PyResult write(PyTuple *args, PyDict *kwargs) + { + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "write", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) { return Err(result.unwrap_err()); } + auto [arg] = result.unwrap(); + PyBuffer buffer; + if (auto r = arg->get_buffer(buffer, 0); r.is_err()) { return Err(r.unwrap_err()); } + if (!buffer.is_ccontiguous()) { + return Err(type_error( + "write() argument must be contiguous buffer, not {}", arg->type()->name())); + } + + // implementation + if (Buffered::is_closed()) { + return Err(value_error("write to closed file")); + } + + auto written = + this->buffer->sputn(static_cast(buffer.buf->get_buffer()), buffer.len); + if (written == buffer.len) { + // fast path: everything was written - we are done + return PyInteger::create(written); + } + + // flush our internal buffer to raw + if (auto r = raw_write(); r.is_err()) { return r; } + + // TODO: this currently writes to internal buffer and then immediately makes a copy in + // write_raw. This is because streambuf doesn't tell us how much capacity it has left, so we + // just give it as much as we can each time + ssize_t remaining = buffer.len; + char *start = static_cast(buffer.buf->get_buffer()); + while (remaining > written) { + const auto count = this->buffer->sputn(start + written, buffer.len); + // flush our internal buffer to raw + if (auto r = raw_write(); r.is_err()) { return r; } + written += count; + remaining -= count; + } + + // now we are done copying everything from buffer to our internal buffer + // and we also have written it all out to raw (since we don't look up internal buffer + // capacity before write) + return PyInteger::create(written); + } + + + PyResult flush(); + static PyType *register_type(PyModule *module) { if (!s_io_buffered_writer) { s_io_buffered_writer = - klass(module, "BufferedWriter", s_io_buffered_io_base).finalize(); + klass(module, "BufferedWriter", s_io_buffered_io_base) + .def("write", &BufferedWriter::write) + .def("flush", &BufferedWriter::flush) + .finalize(); } module->add_symbol(PyString::create("BufferedWriter").unwrap(), s_io_buffered_writer); return s_io_buffered_writer; @@ -1347,6 +1441,21 @@ class BufferedWriter } }; +template<> PyResult Buffered::flush_and_rewind() +{ + if (readable_) { return Ok(py_none()); } + return static_cast(this)->flush(); +} + +PyResult BufferedWriter::flush() +{ + if (auto err = check_initialized(); err.is_err()) return Err(err.unwrap_err()); + if (auto err = Buffered::check_closed(""); err.is_err()) { + return Err(err.unwrap_err()); + } + return raw_write().and_then([](auto) { return Ok(py_none()); }); +} + template<> BufferedReader *as(PyObject *obj) { if (obj->type() == BufferedReader::class_type()) { return static_cast(obj); } @@ -1906,7 +2015,7 @@ class FileIO : public RawIOBase friend ::Heap; int m_file_descriptor{ -1 }; - std::fstream m_filestream; + __gnu_cxx::stdio_filebuf m_filebuffer; bool m_created{ false }; bool m_readable{ false }; bool m_writable{ false }; @@ -1962,10 +2071,14 @@ class FileIO : public RawIOBase PyResult __init__(PyTuple *args, PyDict *kwargs) { ASSERT(!kwargs || kwargs->map().empty()); - if (args->elements().size() != 2) { TODO(); } + if (args->elements().empty()) { TODO(); } auto *filename = PyObject::from(args->elements()[0]).unwrap(); - auto *mode_ = PyObject::from(args->elements()[1]).unwrap(); - if (!as(filename)) { TODO(); } + PyObject *mode_ = nullptr; + if (args->elements().size() > 1) { + mode_ = PyObject::from(args->elements()[1]).unwrap(); + } else { + mode_ = PyString::create("r").unwrap(); + } if (!as(mode_)) { TODO(); } return read_flags(as(mode_)->value()) @@ -1974,7 +2087,7 @@ class FileIO : public RawIOBase std::string to_string() const override { - if (!m_filestream.is_open()) { return "<_io.FileIO [closed]>"; } + if (!m_filebuffer.is_open()) { return "<_io.FileIO [closed]>"; } return fmt::format("<_io.FileIO fd={} mode={}>", m_file_descriptor, mode_string()); } @@ -1984,25 +2097,23 @@ class FileIO : public RawIOBase PyResult readall() { - if (!m_filestream.is_open()) { return Err(value_error("I/O operation on closed file")); } + if (!m_filebuffer.is_open()) { return Err(value_error("I/O operation on closed file")); } - m_filestream.seekg(0); + m_filebuffer.pubseekpos(0, std::ios::in); - if (m_filestream.fail()) { TODO(); } - const auto initial_position = m_filestream.tellg(); + // TODO: if (m_filestream.fail()) { TODO(); } + const auto initial_position = m_filebuffer.pubseekoff(0, std::ios::cur, std::ios::in); if (initial_position == -1) { TODO(); } - m_filestream.seekg(0, std::ios_base::end); - if (m_filestream.fail()) { TODO(); } - const auto end_position = m_filestream.tellg(); + const auto end_position = m_filebuffer.pubseekoff(0, std::ios::end, std::ios::in); if (end_position == -1) { TODO(); } - m_filestream.seekg(0); - if (m_filestream.fail()) { TODO(); } + m_filebuffer.pubseekpos(0, std::ios::in); + // TODO: if (m_filestream.fail()) { TODO(); } const auto file_size = end_position - initial_position; if (file_size == 0) { - m_filestream.seekg(0, std::ios_base::end); + m_filebuffer.pubseekoff(0, std::ios::end, std::ios::in); return PyBytes::create(); } std::vector result; @@ -2011,40 +2122,51 @@ class FileIO : public RawIOBase const auto buffer_size = file_size; do { - if (m_filestream.fail()) { TODO(); } - m_filestream.read(::bit_cast(result.data()), buffer_size); - bytes_read += m_filestream.gcount(); - } while (!m_filestream.eof()); + if (std::ferror(m_filebuffer.file())) { TODO(); } + const auto got = m_filebuffer.sgetn(::bit_cast(result.data()), buffer_size); + if (got <= 0) { break; } + bytes_read += got; + } while (true); - // we expect failbit to be set when we read up to eof, but not badbit to be set - if (m_filestream.rdstate() & std::ios_base::badbit) { TODO(); } + if (std::ferror(m_filebuffer.file())) { TODO(); } // we should always reach the end of the file, otherwise something went wrong if (bytes_read != file_size) { TODO(); } - // set to eof, which removes the failbit - m_filestream.clear(std::ios_base::eofbit); - // make sure that we are not failing anymore - if (m_filestream.fail()) { TODO(); } - return PyBytes::create(Bytes{ std::move(result) }); } PyResult close() { // RawIOBase::close(this); - if (m_filestream.fail()) { TODO(); } - if (!m_filestream.is_open()) { return Ok(py_none()); } - m_filestream.close(); - if (m_filestream.fail()) { TODO(); } + if (!m_filebuffer.is_open()) { return Ok(py_none()); } + // file() is only non-null while the underlying C FILE is open; after close() + // it is reset to nullptr, so any ferror() check must happen before closing. + if (auto *fp = m_filebuffer.file(); fp && std::ferror(fp)) { TODO(); } + m_filebuffer.close(); return Ok(py_none()); } + PyResult write(PyObject *bytes) + { + PyBuffer buffer; + if (auto result = bytes->get_buffer(buffer, 0); result.is_err()) { + return Err(result.unwrap_err()); + } + auto *data = static_cast(buffer.buf->get_buffer()); + const auto written = m_filebuffer.sputn(data, buffer.len); + // TODO: should we always flush? + m_filebuffer.pubsync(); + return PyInteger::create(written); + } + static PyType *register_type(PyModule *module) { if (!s_io_fileio) { s_io_fileio = klass(module, "FileIO", s_io_raw_iobase) .def("readall", &FileIO::readall) + .def("write", &FileIO::write) .def("close", &FileIO::close) + .def("close", &FileIO::flush) .finalize(); } module->add_symbol(PyString::create("FileIO").unwrap(), s_io_fileio); @@ -2052,35 +2174,20 @@ class FileIO : public RawIOBase } private: - PyResult init(PyObject *filename, const std::bitset<8> &rawmode) + PyResult init(PyObject *name, const std::bitset<8> &rawmode) { auto mode_result = get_mode(rawmode); if (mode_result.is_err()) return Err(mode_result.unwrap_err()); const auto mode = mode_result.unwrap(); - - const fs::path filepath = as(filename)->value(); - m_filestream.open(filepath, mode); - if (m_filestream.fail()) { - // FIXME: can we avoid using errno, and figure out the error using fs::perms - auto *msg = strerror(errno); - // FIXME: should be OSError - return Err(value_error("{}", msg)); - } - - if (auto *file_ptr = cfile(m_filestream)) { -#if defined(__linux__) - m_file_descriptor = file_ptr->_fileno; -#elif defined(__APPLE__) - m_file_descriptor = file_ptr->_file; -#else - static_assert(false, "unsupported platform"); -#endif - } else { - m_file_descriptor = -1; - } - - { + if (auto *filename = as(name)) { + const fs::path filepath = as(filename)->value(); + m_filebuffer.open(filepath, mode); + if (std::ferror(m_filebuffer.file())) { + auto *msg = strerror(errno); + // FIXME: should be OSError + return Err(value_error("{}", msg)); + } std::error_code ec; if (fs::is_directory(filepath, ec) && !ec) { // FIXME: can this error message be provided by the standard library? @@ -2088,15 +2195,26 @@ class FileIO : public RawIOBase // FIXME: should be OSError return Err(value_error("{}", msg)); } + m_file_descriptor = m_filebuffer.fd(); + } else if (auto *fd = as(name)) { + m_file_descriptor = static_cast(fd->as_i64()); + + std::string mode_str; + if (mode & std::ios::in) { mode_str.push_back('r'); } + if (mode & std::ios::out) { mode_str.push_back('w'); } + if (mode & std::ios::app) { mode_str.push_back('a'); } + FILE *fp = fdopen(m_file_descriptor, mode_str.c_str()); + m_filebuffer = __gnu_cxx::stdio_filebuf{ fp, mode }; } + if (auto dict = PyDict::create(); dict.is_ok()) { m_attributes = dict.unwrap(); } else { return Err(dict.unwrap_err()); } - if (auto err = setattribute(PyString::create("name").unwrap(), filename); err.is_err()) { + if (auto err = setattribute(PyString::create("name").unwrap(), name); err.is_err()) { return Err(err.unwrap_err()); } @@ -2118,7 +2236,7 @@ class FileIO : public RawIOBase rwa = true; m_created = true; m_writable = true; - mode |= std::ios_base::out; + mode |= std::ios::out; } if (rawmode.test(Mode::READ)) { if (rwa) { @@ -2128,7 +2246,7 @@ class FileIO : public RawIOBase } rwa = true; m_readable = true; - mode |= std::ios_base::in; + mode |= std::ios::in; } if (rawmode.test(Mode::WRITE)) { @@ -2139,7 +2257,7 @@ class FileIO : public RawIOBase } rwa = true; m_writable = true; - mode |= std::ios_base::out; + mode |= std::ios::out; } if (rawmode.test(Mode::APPEND)) { @@ -2151,8 +2269,8 @@ class FileIO : public RawIOBase rwa = true; m_writable = true; m_appending = true; - mode |= std::ios_base::out; - mode |= std::ios_base::app; + mode |= std::ios::out; + mode |= std::ios::app; } if (rawmode.test(Mode::BINARY)) { mode |= std::ios_base::binary; } @@ -2166,8 +2284,8 @@ class FileIO : public RawIOBase plus = true; m_readable = true; m_writable = true; - mode |= std::ios_base::in; - mode |= std::ios_base::out; + mode |= std::ios::in; + mode |= std::ios::out; } if (!rwa) { @@ -2698,6 +2816,8 @@ class TextIOWrapper : public TextIOBase std::optional m_buffer_bytes; size_t m_position; + std::optional m_pending_bytes;// bytes to be written + size_t m_pending_bytes_count; TextIOWrapper(PyType *type) : TextIOBase(type) {} @@ -2883,6 +3003,49 @@ class TextIOWrapper : public TextIOBase return Ok(result); } + PyResult writeflush() + { + if (!m_pending_bytes.has_value()) { return Ok(0); } + auto written = + m_buffer->get_method(PyString::create("write").unwrap()) + .and_then([this](PyObject *write) { + return write->call(PyTuple::create(m_pending_bytes.value()).unwrap(), nullptr); + }); + if (written.is_err()) { return Err(written.unwrap_err()); } + m_pending_bytes = {}; + m_pending_bytes_count = 0; + return Ok(0); + } + + PyResult write(PyTuple *args, PyDict *kwargs) + { + ASSERT(!kwargs || kwargs->map().empty()); + ASSERT(args && args->elements().size() == 1); + auto *text = PyObject::from(args->elements()[0]).unwrap(); + ASSERT(as(text)); + const auto text_len = as(text)->size(); + // TODO: Should use encoder + m_pending_bytes = Bytes::from_unescaped_string(as(text)->to_string()); + m_pending_bytes_count = m_pending_bytes->b.size(); + auto result = writeflush(); + while (result.is_ok()) { + ASSERT(m_pending_bytes_count >= result.unwrap()); + m_pending_bytes_count -= result.unwrap(); + if (m_pending_bytes_count == 0) { break; } + result = writeflush(); + } + if (result.is_err()) { return Err(result.unwrap_err()); } + + return PyInteger::create(text_len); + } + + PyResult flush() + { + if (auto result = writeflush(); result.is_err()) { return Err(result.unwrap_err()); } + return m_buffer->get_method(PyString::create("flush").unwrap()) + .and_then([](PyObject *flush_fn) { return flush_fn->call(nullptr, nullptr); }); + } + PyType *static_type() const override { return s_io_textiowrapper; } static PyType *register_type(PyModule *module) @@ -2891,9 +3054,11 @@ class TextIOWrapper : public TextIOBase s_io_textiowrapper = klass(module, "TextIOWrapper", s_io_textiobase) .def("readline", &TextIOWrapper::readline) .def("readlines", &TextIOWrapper::readlines) + .def("write", &TextIOWrapper::write) + .def("flush", &TextIOWrapper::flush) .finalize(); } - module->add_symbol(PyString::create("StringIO").unwrap(), s_io_textiowrapper); + module->add_symbol(PyString::create("TextIOWrapper").unwrap(), s_io_textiowrapper); return s_io_textiowrapper; } @@ -2921,6 +3086,7 @@ class TextIOWrapper : public TextIOBase m_line_buffering = line_buffering; m_write_through = write_through; m_newline = newline.value_or("'\n"); + m_pending_bytes_count = 0; return Ok(1); } @@ -3042,29 +3208,26 @@ PyModule *io_module() s_io_module->add_symbol(PyString::create("open").unwrap(), VirtualMachine::the().heap().allocate( - "open", [](PyTuple *args, PyDict *kwargs) -> PyResult { - auto result = PyArgsParser::unpack_tuple(args, - kwargs, - "open", - std::integral_constant{}, - std::integral_constant{}); - if (result.is_err()) return Err(result.unwrap_err()); - auto [file, mode] = result.unwrap(); + "open", [](PyTuple *args, PyDict *kwargs) { + ASSERT(!kwargs || kwargs->map().empty()); + ASSERT(args && args->elements().size() == 2); + auto arg0 = PyObject::from(args->elements()[0]).unwrap(); + auto arg1 = PyObject::from(args->elements()[1]).unwrap(); + + ASSERT(as(arg1)); + const std::string rawmode = as(arg1)->value(); - return open(file, mode->value()); + return open(arg0, rawmode); })); s_io_module->add_symbol(PyString::create("open_code").unwrap(), VirtualMachine::the().heap().allocate( - "open_code", [](PyTuple *args, PyDict *kwargs) -> PyResult { - auto result = PyArgsParser::unpack_tuple(args, - kwargs, - "open_code", - std::integral_constant{}, - std::integral_constant{}); - if (result.is_err()) return Err(result.unwrap_err()); + "open_code", [](PyTuple *args, PyDict *kwargs) { + ASSERT(!kwargs || kwargs->map().empty()); + ASSERT(args && args->elements().size() == 1); + auto arg0 = PyObject::from(args->elements()[0]).unwrap(); - return open(std::get<0>(result.unwrap()), "rb"); + return open(arg0, "rb"); })); // C++ standard streams currently do not provide an API to get default buffer size, and C's