Skip to content

Commit 6da8d6e

Browse files
committed
runtime: support sys.stdout and sys.stderr and implement proper print
1 parent 62cc6f5 commit 6da8d6e

9 files changed

Lines changed: 369 additions & 102 deletions

File tree

integration/tests/file_io.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Regression test: closing a path-opened FileIO after reading must not crash.
2+
# Previously FileIO.close() called ferror() on the underlying FILE* *after*
3+
# closing it (which resets the pointer to NULL), segfaulting on ferror(NULL).
4+
# This is the same path the import machinery uses to read a module's source.
5+
6+
# Run with cwd == integration/ (as the integration runner does).
7+
DATA = "tests/file_io_data.txt"
8+
9+
# 1. read inside a `with` block -> __exit__ closes the file (the crashing path).
10+
with open(DATA, "rb") as f:
11+
data = f.read()
12+
assert data == b"line1\nline2\n", data
13+
14+
# 2. explicit close, and close() must be idempotent (callable more than once).
15+
g = open(DATA, "rb")
16+
assert g.read() == b"line1\nline2\n"
17+
g.close()
18+
g.close()
19+
20+
print("file_io: ok")

integration/tests/file_io_data.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
line1
2+
line2

src/interpreter/Interpreter.cpp

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "executable/Program.hpp"
2323
#include "executable/bytecode/Bytecode.hpp"
2424
#include "executable/bytecode/BytecodeProgram.hpp"
25+
#include "utilities.hpp"
2526

2627
#include <filesystem>
2728

@@ -168,20 +169,48 @@ void Interpreter::internal_setup(const std::string &name,
168169
m_codec_search_path = PyList::create().unwrap();
169170
m_codec_search_path_cache = PyDict::create().unwrap();
170171

172+
PyModule *io_module;
171173
for (const auto &[name, module_factory] : builtin_modules) {
172-
if (module_factory) { m_modules->insert(String{ std::string{ name } }, module_factory()); }
174+
if (module_factory) {
175+
auto mod = module_factory();
176+
if (name == "_io") { io_module = mod; }
177+
m_modules->insert(String{ std::string{ name } }, mod);
178+
}
173179
}
174180

175181
{
176-
auto open = io_module()->symbol_table()->map().at(String{ "open" });
182+
ASSERT(io_module);
183+
auto open = io_module->symbol_table()->map().at(String{ "open" });
177184
m_builtins->add_symbol(PyString::create("open").unwrap(), open);
185+
auto *file_io =
186+
std::get<PyObject *>(io_module->symbol_table()->map().at(String{ "FileIO" }));
187+
auto *buffered_writer =
188+
std::get<PyObject *>(io_module->symbol_table()->map().at(String{ "BufferedWriter" }));
189+
auto *text_io_wrapper =
190+
std::get<PyObject *>(io_module->symbol_table()->map().at(String{ "TextIOWrapper" }));
191+
auto py_stdout =
192+
file_io->call(PyTuple::create(Number{ 1 }, String{ "wb" }).unwrap(), nullptr)
193+
.and_then([buffered_writer](PyObject *stdout) {
194+
return buffered_writer->call(PyTuple::create(stdout).unwrap(), nullptr);
195+
})
196+
.and_then([text_io_wrapper](PyObject *stdout_buffer_writer) {
197+
return text_io_wrapper->call(
198+
PyTuple::create(stdout_buffer_writer).unwrap(), nullptr);
199+
});
200+
ASSERT(py_stdout.is_ok());
201+
auto py_stderr =
202+
file_io->call(PyTuple::create(Number{ 2 }, String{ "wb" }).unwrap(), nullptr)
203+
.and_then([buffered_writer](PyObject *stderr) {
204+
return buffered_writer->call(PyTuple::create(stderr).unwrap(), nullptr);
205+
})
206+
.and_then([text_io_wrapper](PyObject *stderr_buffer_writer) {
207+
return text_io_wrapper->call(
208+
PyTuple::create(stderr_buffer_writer).unwrap(), nullptr);
209+
});
210+
ASSERT(py_stderr.is_ok());
211+
sys->add_symbol(PyString::create("stdout").unwrap(), py_stdout.unwrap());
212+
sys->add_symbol(PyString::create("stderr").unwrap(), py_stderr.unwrap());
178213
}
179-
// {
180-
// auto open = io_module()->symbol_table()->map().at(String{ "open" });
181-
// m_builtins->add_symbol(PyString::create("open").unwrap(), open);
182-
// sys->add_symbol(PyString::create("stderr").unwrap(),
183-
// std::get<PyObject *>(open)->call(PyTuple::create().unwrap(), nullptr).unwrap());
184-
// }
185214

186215
if (config.requires_importlib) {
187216
auto *_imp = imp_module();

src/runtime/PyMemoryView.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,25 @@ PyResult<PyObject *> PyMemoryView::tolist()
336336

337337
PyResult<PyObject *> PyMemoryView::__repr__() const { return PyString::create(to_string()); }
338338

339+
PyResult<std::monostate> PyMemoryView::__getbuffer__(PyBuffer &view, int /*flags*/)
340+
{
341+
// TODO: validate flags
342+
view = PyBuffer{
343+
.buf = m_view.buf->view(),
344+
.obj = this,
345+
.len = m_view.len,
346+
.itemsize = m_view.itemsize,
347+
.readonly = m_view.readonly,
348+
.ndim = m_view.ndim,
349+
.format = m_view.format,
350+
.shape = m_view.shape,
351+
.strides = m_view.strides,
352+
.suboffsets = m_view.suboffsets,
353+
.internal = m_view.internal,
354+
};
355+
return Ok(std::monostate{});
356+
}
357+
339358
namespace {
340359
std::once_flag memoryview_flag;
341360

src/runtime/PyMemoryView.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ class PyMemoryView : public PyBaseObject
4141

4242
size_t itemsize() const { return m_view.itemsize; }
4343

44+
PyResult<std::monostate> __getbuffer__(PyBuffer &view, int /*flags*/);
45+
46+
4447
private:
4548
static PyResult<PyBuffer> create_view(PyBuffer &main_view);
4649
};

src/runtime/PyObject.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -468,12 +468,7 @@ PyResult<std::monostate> PyObject::get_buffer(PyBuffer &buffer, int flags)
468468
{
469469
return as_buffer().and_then(
470470
[&buffer, flags, this](const PyBufferProcs &bc) -> PyResult<std::monostate> {
471-
(void)buffer;
472-
(void)flags;
473-
(void)this;
474-
(void)bc;
475-
return Err(not_implemented_error("get_buffer not implemented!"));
476-
// return bc.getbuffer(this, buffer, flags);
471+
return bc.getbuffer(this, buffer, flags);
477472
});
478473
}
479474

src/runtime/PyObject.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ class PyObject : public Cell
433433
PyResult<PySequenceWrapper> as_sequence();
434434
PyResult<PyBufferProcs> as_buffer();
435435

436+
// TODO: add strongly typed flags
436437
PyResult<std::monostate> get_buffer(PyBuffer &, int flags);
437438

438439
PyResult<PyObject *> getattribute(PyObject *attribute) const;

src/runtime/modules/BuiltinsModule.cpp

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,20 @@ static PyModule *s_builtin_module = nullptr;
6767

6868
namespace {
6969

70-
PyResult<PyObject *> print(const PyTuple *args, const PyDict *kwargs, Interpreter &)
70+
PyResult<PyObject *> print(const PyTuple *args, const PyDict *kwargs, Interpreter &interpreter)
7171
{
7272
std::string separator = " ";
7373
std::string end = "\n";
74+
// TODO: handle error case?
75+
PyObject *file =
76+
PyObject::from(interpreter.get_imported_module(PyString::create("sys").unwrap())
77+
->symbol_table()
78+
->map()
79+
.at(String{ "stdout" }))
80+
.unwrap();
81+
// sys.stdout may be None when FILE* stdout isn't connected
82+
if (!file || file == py_none()) { return Ok(py_none()); }
83+
bool flush = true;
7484
if (kwargs) {
7585
static const Value separator_keyword = String{ "sep" };
7686
static const Value end_keyword = String{ "end" };
@@ -98,6 +108,9 @@ PyResult<PyObject *> print(const PyTuple *args, const PyDict *kwargs, Interprete
98108
end = std::get<String>(maybe_str).s;
99109
}
100110
}
111+
auto file_write_ = file->get_method(PyString::create("write").unwrap());
112+
if (file_write_.is_err()) { return file_write_; }
113+
auto file_write = file_write_.unwrap();
101114
auto strfunc = [](const PyResult<PyObject *> &arg) -> PyResult<PyString *> {
102115
if (arg.is_err()) return Err(arg.unwrap_err());
103116
return arg.unwrap()->str();
@@ -106,7 +119,11 @@ PyResult<PyObject *> print(const PyTuple *args, const PyDict *kwargs, Interprete
106119
auto arg_it = args->begin();
107120
auto arg_it_end = args->end();
108121
if (arg_it == arg_it_end) {
109-
std::cout << std::endl;
122+
if (flush) {
123+
auto file_flush_ = file->get_method(PyString::create("flush").unwrap());
124+
if (file_flush_.is_err()) { return file_flush_; }
125+
return file_flush_.unwrap()->call(nullptr, nullptr);
126+
}
110127
return Ok(py_none());
111128
}
112129
--arg_it_end;
@@ -117,7 +134,10 @@ PyResult<PyObject *> print(const PyTuple *args, const PyDict *kwargs, Interprete
117134
if (reprobj_.is_err()) { return reprobj_; }
118135
auto reprobj = reprobj_.unwrap();
119136
spdlog::debug("repr result: {}", reprobj->value());
120-
std::cout << reprobj->value() << separator;
137+
if (auto result = file_write->call(PyTuple::create(reprobj).unwrap(), nullptr);
138+
result.is_err()) {
139+
return result;
140+
}
121141
std::advance(arg_it, 1);
122142
}
123143

@@ -126,8 +146,23 @@ PyResult<PyObject *> print(const PyTuple *args, const PyDict *kwargs, Interprete
126146
if (reprobj_.is_err()) { return reprobj_; }
127147
auto reprobj = reprobj_.unwrap();
128148
spdlog::debug("repr result: {}", reprobj->value());
129-
std::cout << reprobj->value() << end;
149+
if (auto result = file_write->call(PyTuple::create(reprobj).unwrap(), nullptr);
150+
result.is_err()) {
151+
return result;
152+
}
130153

154+
if (!end.empty()) {
155+
if (auto result =
156+
file_write->call(PyTuple::create(PyString::create(end).unwrap()).unwrap(), nullptr);
157+
result.is_err()) {
158+
return result;
159+
}
160+
}
161+
if (flush) {
162+
auto file_flush_ = file->get_method(PyString::create("flush").unwrap());
163+
if (file_flush_.is_err()) { return file_flush_; }
164+
return file_flush_.unwrap()->call(nullptr, nullptr);
165+
}
131166
return Ok(py_none());
132167
}
133168

0 commit comments

Comments
 (0)