Skip to main content

grift_core/
io.rs

1//! I/O trait boundary for the Grift Scheme evaluator.
2//!
3//! This module defines the [`IoProvider`] trait, which serves as the boundary
4//! between the pure `no_std` evaluator and platform-specific I/O implementations.
5//!
6//! The `grift_std` crate provides a standard implementation using Rust's `std::io`.
7//!
8//! ## Port Model
9//!
10//! R7RS defines I/O in terms of *ports*. A [`PortId`] identifies a port,
11//! with well-known constants for standard input, output, and error.
12
13use grift_arena::ArenaIndex;
14
15// ============================================================================
16// Port Identifier
17// ============================================================================
18
19/// Identifies an I/O port.
20///
21/// Standard ports ([`STDIN`](PortId::STDIN), [`STDOUT`](PortId::STDOUT),
22/// [`STDERR`](PortId::STDERR)) are pre-defined.  Implementations may allocate
23/// additional ports for file or string I/O.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct PortId(pub usize);
26
27impl PortId {
28    /// Standard input port.
29    pub const STDIN: PortId = PortId(0);
30    /// Standard output port.
31    pub const STDOUT: PortId = PortId(1);
32    /// Standard error port.
33    pub const STDERR: PortId = PortId(2);
34}
35
36// ============================================================================
37// I/O Error
38// ============================================================================
39
40/// Error kinds for I/O operations in `no_std` environments.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum IoErrorKind {
43    /// End of file reached.
44    Eof,
45    /// Port not found or invalid.
46    InvalidPort,
47    /// Write operation failed.
48    WriteFailed,
49    /// Read operation failed.
50    ReadFailed,
51    /// Port is closed.
52    PortClosed,
53    /// Operation not supported on this port.
54    Unsupported,
55}
56
57/// Result type for I/O operations.
58pub type IoResult<T> = Result<T, IoErrorKind>;
59
60// ============================================================================
61// IoProvider Trait
62// ============================================================================
63
64/// Trait for providing I/O operations to the evaluator.
65///
66/// This trait defines the boundary between the pure `no_std` evaluator
67/// and platform-specific I/O implementations.  The `grift_std` crate
68/// provides a standard implementation backed by Rust's `std::io`.
69///
70/// # Implementor Notes
71///
72/// * [`PortId::STDIN`], [`PortId::STDOUT`], and [`PortId::STDERR`] should
73///   always be accepted as valid ports.
74/// * Implementations may support additional dynamically-opened ports
75///   (e.g. file ports) by returning fresh [`PortId`] values.
76pub trait IoProvider {
77    /// Read a single character from the specified input port.
78    fn read_char(&mut self, port: PortId) -> IoResult<char>;
79
80    /// Peek at the next character without consuming it.
81    fn peek_char(&mut self, port: PortId) -> IoResult<char>;
82
83    /// Return `true` if a character is ready on the input port.
84    fn char_ready(&mut self, port: PortId) -> IoResult<bool>;
85
86    /// Write a single character to the specified output port.
87    fn write_char(&mut self, port: PortId, c: char) -> IoResult<()>;
88
89    /// Write a string slice to the specified output port.
90    fn write_str(&mut self, port: PortId, s: &str) -> IoResult<()>;
91
92    /// Flush the specified output port.
93    fn flush(&mut self, port: PortId) -> IoResult<()>;
94
95    /// Close the specified port.
96    fn close_port(&mut self, port: PortId) -> IoResult<()>;
97
98    /// Return `true` if the port is an input port.
99    fn is_input_port(&self, port: PortId) -> bool;
100
101    /// Return `true` if the port is an output port.
102    fn is_output_port(&self, port: PortId) -> bool;
103
104    /// Return `true` if the port is still open.
105    /// Default: returns `true` (assumes ports are open unless overridden).
106    fn is_port_open(&self, _port: PortId) -> bool {
107        true
108    }
109
110    /// Return `true` if the port is a textual port.
111    /// Default: returns `true` for any valid input or output port.
112    fn is_textual_port(&self, port: PortId) -> bool {
113        self.is_input_port(port) || self.is_output_port(port)
114    }
115
116    /// Return `true` if the port is a binary port.
117    /// Default: returns `false` (all ports are textual by default).
118    fn is_binary_port(&self, _port: PortId) -> bool {
119        false
120    }
121
122    /// Open an input port that reads from the given string.
123    ///
124    /// Returns a fresh [`PortId`] for the new port.
125    /// Default: returns [`IoErrorKind::Unsupported`].
126    fn open_input_string(&mut self, _s: &str) -> IoResult<PortId> {
127        Err(IoErrorKind::Unsupported)
128    }
129
130    /// Open an output port that accumulates characters into a string buffer.
131    ///
132    /// Returns a fresh [`PortId`] for the new port.
133    /// Default: returns [`IoErrorKind::Unsupported`].
134    fn open_output_string(&mut self) -> IoResult<PortId> {
135        Err(IoErrorKind::Unsupported)
136    }
137
138    /// Retrieve the accumulated string from an output string port.
139    ///
140    /// The port must have been created by [`open_output_string`](Self::open_output_string).
141    /// Default: returns [`IoErrorKind::Unsupported`].
142    fn get_output_string(&self, _port: PortId) -> IoResult<&str> {
143        Err(IoErrorKind::Unsupported)
144    }
145
146    // ----------------------------------------------------------------
147    // File system operations (R7RS §6.13)
148    // ----------------------------------------------------------------
149
150    /// Check whether a file exists at the given path.
151    /// Default: returns [`IoErrorKind::Unsupported`].
152    fn file_exists(&self, _path: &str) -> IoResult<bool> {
153        Err(IoErrorKind::Unsupported)
154    }
155
156    /// Delete the file at the given path.
157    /// Default: returns [`IoErrorKind::Unsupported`].
158    fn delete_file(&mut self, _path: &str) -> IoResult<()> {
159        Err(IoErrorKind::Unsupported)
160    }
161
162    /// Read the entire contents of a file as a string.
163    /// Default: returns [`IoErrorKind::Unsupported`].
164    fn read_file(&mut self, _path: &str) -> IoResult<&str> {
165        Err(IoErrorKind::Unsupported)
166    }
167
168    // ----------------------------------------------------------------
169    // Process / environment operations (R7RS §6.14)
170    // ----------------------------------------------------------------
171
172    /// Return the number of command-line arguments.
173    /// Default: returns [`IoErrorKind::Unsupported`].
174    fn command_line_count(&self) -> IoResult<usize> {
175        Err(IoErrorKind::Unsupported)
176    }
177
178    /// Return the command-line argument at the given index.
179    /// Default: returns [`IoErrorKind::Unsupported`].
180    fn command_line_arg(&self, _index: usize) -> IoResult<&str> {
181        Err(IoErrorKind::Unsupported)
182    }
183
184    /// Retrieve the value of an environment variable by name.
185    /// Returns `Ok(Some(value))` if found, `Ok(None)` if not set.
186    /// Default: returns [`IoErrorKind::Unsupported`].
187    fn get_environment_variable(&mut self, _name: &str) -> IoResult<Option<&str>> {
188        Err(IoErrorKind::Unsupported)
189    }
190
191    /// Return the number of environment variables.
192    /// Must be called before [`environment_variable_at`](Self::environment_variable_at)
193    /// to snapshot the current environment.
194    /// Default: returns [`IoErrorKind::Unsupported`].
195    fn environment_variables_count(&mut self) -> IoResult<usize> {
196        Err(IoErrorKind::Unsupported)
197    }
198
199    /// Return the environment variable name and value at the given index.
200    /// [`environment_variables_count`](Self::environment_variables_count) must be
201    /// called first to snapshot the environment.
202    /// Default: returns [`IoErrorKind::Unsupported`].
203    fn environment_variable_at(&self, _index: usize) -> IoResult<(&str, &str)> {
204        Err(IoErrorKind::Unsupported)
205    }
206
207    /// Exit the process with the given status code.
208    /// Default: returns [`IoErrorKind::Unsupported`].
209    fn exit_process(&mut self, _code: i32) -> IoResult<()> {
210        Err(IoErrorKind::Unsupported)
211    }
212
213    /// Emergency exit the process (no cleanup) with the given status code.
214    /// Default: returns [`IoErrorKind::Unsupported`].
215    fn emergency_exit_process(&mut self, _code: i32) -> IoResult<()> {
216        Err(IoErrorKind::Unsupported)
217    }
218}
219
220/// A no-op I/O provider that silently discards all output and returns
221/// [`IoErrorKind::Unsupported`] for reads.
222///
223/// Useful as a default when no I/O back-end is configured.
224pub struct NullIoProvider;
225
226impl IoProvider for NullIoProvider {
227    fn read_char(&mut self, _port: PortId) -> IoResult<char> {
228        Err(IoErrorKind::Unsupported)
229    }
230
231    fn peek_char(&mut self, _port: PortId) -> IoResult<char> {
232        Err(IoErrorKind::Unsupported)
233    }
234
235    fn char_ready(&mut self, _port: PortId) -> IoResult<bool> {
236        Err(IoErrorKind::Unsupported)
237    }
238
239    fn write_char(&mut self, _port: PortId, _c: char) -> IoResult<()> {
240        Ok(())
241    }
242
243    fn write_str(&mut self, _port: PortId, _s: &str) -> IoResult<()> {
244        Ok(())
245    }
246
247    fn flush(&mut self, _port: PortId) -> IoResult<()> {
248        Ok(())
249    }
250
251    fn close_port(&mut self, _port: PortId) -> IoResult<()> {
252        Ok(())
253    }
254
255    fn is_input_port(&self, _port: PortId) -> bool {
256        false
257    }
258
259    fn is_output_port(&self, _port: PortId) -> bool {
260        false
261    }
262}
263
264// ============================================================================
265// Display helper (for use with IoProvider)
266// ============================================================================
267
268/// Write a [`Value`](crate::Value) through an [`IoProvider`] port.
269///
270/// This is a convenience for the evaluator's `display` / `write` builtins
271/// to emit a value through the I/O abstraction without requiring `alloc`.
272/// The `idx` is an arena index that the caller can format into the port.
273///
274/// Implementations of higher-level display logic live in the evaluator
275/// (or in `grift_std`), but this marker type can carry the index through
276/// the trait boundary.
277#[derive(Debug, Clone, Copy)]
278pub struct DisplayPort {
279    /// The arena index of the value to display.
280    pub value: ArenaIndex,
281    /// The target port.
282    pub port: PortId,
283}