1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// Copyright 2026 Thomas Santerre and Moderately AI Inc.
//
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Minimal emulation of Python's `io` module: `io.StringIO`, an in-memory text
//! stream. Backed by [`crate::value::SharedStringIo`] so it is reference-
//! semantic and mutations are visible through every alias.
use crate::{
error::{EvalResult, InterpreterError},
value::Value,
};
pub fn has_function(name: &str) -> bool {
matches!(name, "StringIO")
}
/// `io.StringIO([initial])` — construct a text stream seeded with `initial`.
pub fn call(func: &str, args: &[Value]) -> EvalResult {
match func {
"StringIO" => {
let initial = match args.first() {
None | Some(Value::None) => String::new(),
Some(Value::String(s)) => s.to_string(),
Some(other) => {
return Err(InterpreterError::TypeError(format!(
"initial_value must be str or None, not {}",
other.type_name()
))
.into());
}
};
// A fresh StringIO seeded with text positions the cursor at the end
// (CPython leaves it at 0, but write() overwrites from pos and the
// common flow is write-then-getvalue, so seed pos at 0 to match).
Ok(Value::StringIO(crate::value::shared_stringio(initial)))
}
_ => {
Err(InterpreterError::AttributeError(format!("module 'io' has no attribute '{func}'"))
.into())
}
}
}
/// `io` module registration.
pub struct IoModule;
#[async_trait::async_trait]
impl crate::eval::modules::Module for IoModule {
fn name(&self) -> &'static str {
"io"
}
fn has_function(&self, name: &str) -> bool {
has_function(name)
}
async fn call(
&self,
_state: &mut crate::state::InterpreterState,
func: &str,
args: &[Value],
_kwargs: &indexmap::IndexMap<String, Value>,
_tools: &crate::tools::Tools,
) -> EvalResult {
call(func, args)
}
}