Skip to main content

oxilean_runtime/io_runtime/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::object::RtObject;
6use std::collections::{HashMap, HashSet, VecDeque};
7use std::io::{self, Read, Write};
8
9use super::functions::IoResult;
10
11/// A single I/O event in the log.
12#[allow(dead_code)]
13#[derive(Clone, Debug)]
14pub struct IoEvent {
15    pub kind: IoEventKind,
16    pub path: Option<String>,
17    pub bytes: usize,
18    pub timestamp_ms: u64,
19    pub success: bool,
20}
21/// Executes IO actions.
22pub struct IoExecutor<'a> {
23    /// The I/O runtime.
24    runtime: &'a mut IoRuntime,
25}
26impl<'a> IoExecutor<'a> {
27    /// Create a new IO executor.
28    pub fn new(runtime: &'a mut IoRuntime) -> Self {
29        IoExecutor { runtime }
30    }
31    /// Execute a println action.
32    pub fn println(&mut self, s: &str) -> IoValue {
33        match self.runtime.exec_println(s) {
34            Ok(()) => IoValue::unit(),
35            Err(e) => IoValue::error(e),
36        }
37    }
38    /// Execute a getLine action.
39    pub fn get_line(&mut self) -> IoValue {
40        match self.runtime.exec_get_line() {
41            Ok(line) => IoValue::pure_val(RtObject::string(line)),
42            Err(e) => IoValue::error(e),
43        }
44    }
45    /// Execute a file read action.
46    pub fn read_file(&mut self, path: &str) -> IoValue {
47        match self.runtime.exec_read_file(path) {
48            Ok(contents) => IoValue::pure_val(RtObject::string(contents)),
49            Err(e) => IoValue::error(e),
50        }
51    }
52    /// Execute a file write action.
53    pub fn write_file(&mut self, path: &str, contents: &str) -> IoValue {
54        match self.runtime.exec_write_file(path, contents) {
55            Ok(()) => IoValue::unit(),
56            Err(e) => IoValue::error(e),
57        }
58    }
59    /// Execute a new ref action.
60    pub fn new_ref(&mut self, value: RtObject) -> IoValue {
61        let id = self.runtime.new_ref(value);
62        IoValue::pure_val(RtObject::nat(id))
63    }
64    /// Execute a read ref action.
65    pub fn read_ref(&mut self, id: u64) -> IoValue {
66        match self.runtime.read_ref(id) {
67            Ok(value) => IoValue::pure_val(value),
68            Err(e) => IoValue::error(e),
69        }
70    }
71    /// Execute a write ref action.
72    pub fn write_ref(&mut self, id: u64, value: RtObject) -> IoValue {
73        match self.runtime.write_ref(id, value) {
74            Ok(()) => IoValue::unit(),
75            Err(e) => IoValue::error(e),
76        }
77    }
78    /// Get the current time.
79    pub fn get_time(&mut self) -> IoValue {
80        match self.runtime.get_time_nanos() {
81            Ok(nanos) => IoValue::pure_val(RtObject::nat(nanos)),
82            Err(e) => IoValue::error(e),
83        }
84    }
85    /// Get an environment variable.
86    pub fn get_env(&self, key: &str) -> IoValue {
87        match self.runtime.get_env_var(key) {
88            Some(value) => IoValue::pure_val(RtObject::string(value)),
89            None => IoValue::pure_val(RtObject::string(String::new())),
90        }
91    }
92}
93/// An I/O interaction for replay.
94#[allow(dead_code)]
95#[derive(Clone, Debug)]
96pub enum MockIoOp {
97    Read { expected: Vec<u8>, result: Vec<u8> },
98    Write { expected: Vec<u8>, ok: bool },
99    ReadLine { result: String },
100    ReadError { kind: IoErrorKind },
101}
102/// A simple polling file watcher (in-memory simulation).
103#[allow(dead_code)]
104pub struct IoFileWatcher {
105    records: HashMap<String, FileRecord>,
106    poll_interval_ms: u64,
107}
108#[allow(dead_code)]
109impl IoFileWatcher {
110    /// Create a file watcher with the given poll interval.
111    pub fn new(poll_interval_ms: u64) -> Self {
112        Self {
113            records: HashMap::new(),
114            poll_interval_ms,
115        }
116    }
117    /// Register a file to watch.
118    pub fn watch(&mut self, path: &str, current_size: u64, now_ms: u64) {
119        self.records.insert(
120            path.to_string(),
121            FileRecord {
122                path: path.to_string(),
123                size: current_size,
124                last_seen_ms: now_ms,
125                change_count: 0,
126            },
127        );
128    }
129    /// Poll for changes. Returns paths that have changed.
130    pub fn poll(&mut self, sizes: &HashMap<String, u64>, now_ms: u64) -> Vec<String> {
131        let mut changed = Vec::new();
132        for (path, record) in self.records.iter_mut() {
133            if now_ms < record.last_seen_ms + self.poll_interval_ms {
134                continue;
135            }
136            if let Some(&new_size) = sizes.get(path.as_str()) {
137                if new_size != record.size {
138                    record.size = new_size;
139                    record.change_count += 1;
140                    changed.push(path.clone());
141                }
142            }
143            record.last_seen_ms = now_ms;
144        }
145        changed
146    }
147    /// Get the record for a path.
148    pub fn record(&self, path: &str) -> Option<&FileRecord> {
149        self.records.get(path)
150    }
151    /// Number of watched files.
152    pub fn watch_count(&self) -> usize {
153        self.records.len()
154    }
155    /// Unwatch a file.
156    pub fn unwatch(&mut self, path: &str) {
157        self.records.remove(path);
158    }
159}
160/// A pair of channels forming a bidirectional pipe.
161#[allow(dead_code)]
162pub struct PipePair {
163    pub a_to_b: IoChannel,
164    pub b_to_a: IoChannel,
165}
166#[allow(dead_code)]
167impl PipePair {
168    /// Create a fresh pipe pair.
169    pub fn new() -> Self {
170        Self {
171            a_to_b: IoChannel::new(),
172            b_to_a: IoChannel::new(),
173        }
174    }
175    /// Send data from A to B.
176    pub fn send_a_to_b(&mut self, data: &[u8]) -> bool {
177        self.a_to_b.write(data)
178    }
179    /// Send data from B to A.
180    pub fn send_b_to_a(&mut self, data: &[u8]) -> bool {
181        self.b_to_a.write(data)
182    }
183    /// Receive data on the B side.
184    pub fn recv_b(&mut self, n: usize) -> Vec<u8> {
185        self.a_to_b.read(n)
186    }
187    /// Receive data on the A side.
188    pub fn recv_a(&mut self, n: usize) -> Vec<u8> {
189        self.b_to_a.read(n)
190    }
191    /// Close both channels.
192    pub fn close(&mut self) {
193        self.a_to_b.close();
194        self.b_to_a.close();
195    }
196}
197/// What to do when an I/O error occurs.
198#[allow(dead_code)]
199#[derive(Clone, Debug, PartialEq, Eq)]
200pub enum IoErrorPolicy {
201    /// Propagate the error immediately.
202    Propagate,
203    /// Ignore the error and continue.
204    Ignore,
205    /// Retry the operation up to N times.
206    Retry { max: u32 },
207    /// Log the error and continue.
208    LogAndContinue,
209}
210/// String formatting operations.
211pub struct StringFormatter;
212impl StringFormatter {
213    /// Format a runtime object as a string.
214    pub fn format_object(obj: &RtObject) -> String {
215        format!("{}", obj)
216    }
217    /// Convert a natural number to a string.
218    pub fn nat_to_string(n: u64) -> String {
219        format!("{}", n)
220    }
221    /// Convert an integer to a string.
222    pub fn int_to_string(n: i64) -> String {
223        format!("{}", n)
224    }
225    /// Convert a float to a string.
226    pub fn float_to_string(f: f64) -> String {
227        format!("{}", f)
228    }
229    /// Convert a boolean to a string.
230    pub fn bool_to_string(b: bool) -> String {
231        if b {
232            "true".to_string()
233        } else {
234            "false".to_string()
235        }
236    }
237    /// Convert a character to a string.
238    pub fn char_to_string(c: char) -> String {
239        c.to_string()
240    }
241    /// Format a list of objects as a string.
242    pub fn format_list(elements: &[RtObject], separator: &str) -> String {
243        elements
244            .iter()
245            .map(|e| format!("{}", e))
246            .collect::<Vec<_>>()
247            .join(separator)
248    }
249    /// Pad a string on the left to a minimum width.
250    pub fn pad_left(s: &str, width: usize, pad_char: char) -> String {
251        if s.len() >= width {
252            return s.to_string();
253        }
254        let padding = std::iter::repeat(pad_char)
255            .take(width - s.len())
256            .collect::<String>();
257        format!("{}{}", padding, s)
258    }
259    /// Pad a string on the right to a minimum width.
260    pub fn pad_right(s: &str, width: usize, pad_char: char) -> String {
261        if s.len() >= width {
262            return s.to_string();
263        }
264        let padding = std::iter::repeat(pad_char)
265            .take(width - s.len())
266            .collect::<String>();
267        format!("{}{}", s, padding)
268    }
269    /// Convert a string to uppercase.
270    pub fn to_upper(s: &str) -> String {
271        s.to_uppercase()
272    }
273    /// Convert a string to lowercase.
274    pub fn to_lower(s: &str) -> String {
275        s.to_lowercase()
276    }
277    /// Trim whitespace from both ends.
278    pub fn trim(s: &str) -> String {
279        s.trim().to_string()
280    }
281    /// Split a string by a separator.
282    pub fn split(s: &str, sep: &str) -> Vec<String> {
283        s.split(sep).map(|p| p.to_string()).collect()
284    }
285    /// Join strings with a separator.
286    pub fn join(parts: &[String], sep: &str) -> String {
287        parts.join(sep)
288    }
289    /// Check if a string starts with a prefix.
290    pub fn starts_with(s: &str, prefix: &str) -> bool {
291        s.starts_with(prefix)
292    }
293    /// Check if a string ends with a suffix.
294    pub fn ends_with(s: &str, suffix: &str) -> bool {
295        s.ends_with(suffix)
296    }
297    /// Replace all occurrences of a pattern.
298    pub fn replace(s: &str, from: &str, to: &str) -> String {
299        s.replace(from, to)
300    }
301    /// Check if a string contains a substring.
302    pub fn contains(s: &str, substr: &str) -> bool {
303        s.contains(substr)
304    }
305}
306/// A category of I/O event.
307#[allow(dead_code)]
308#[derive(Clone, Debug, PartialEq, Eq)]
309pub enum IoEventKind {
310    Read,
311    Write,
312    Open,
313    Close,
314    Error,
315    Flush,
316    Seek,
317    Truncate,
318}
319/// The main I/O runtime that executes IO actions.
320pub struct IoRuntime {
321    /// Mutable references (for IO.Ref).
322    pub(super) refs: HashMap<u64, RtObject>,
323    /// Next reference ID.
324    next_ref_id: u64,
325    /// Whether I/O is enabled (can be disabled for sandboxing).
326    pub(super) io_enabled: bool,
327    /// Output buffer (for testing/capturing output).
328    pub(super) output_buffer: Option<Vec<String>>,
329    /// Input queue (for testing/mocking input).
330    pub(super) input_queue: Vec<String>,
331    /// Statistics.
332    stats: IoStats,
333    /// Environment variables.
334    env_vars: HashMap<String, String>,
335    /// Command-line arguments.
336    args: Vec<String>,
337}
338impl IoRuntime {
339    /// Create a new I/O runtime.
340    pub fn new() -> Self {
341        IoRuntime {
342            refs: HashMap::new(),
343            next_ref_id: 0,
344            io_enabled: true,
345            output_buffer: None,
346            input_queue: Vec::new(),
347            stats: IoStats::default(),
348            env_vars: HashMap::new(),
349            args: Vec::new(),
350        }
351    }
352    /// Create a sandboxed I/O runtime (no real I/O).
353    pub fn sandboxed() -> Self {
354        IoRuntime {
355            refs: HashMap::new(),
356            next_ref_id: 0,
357            io_enabled: false,
358            output_buffer: Some(Vec::new()),
359            input_queue: Vec::new(),
360            stats: IoStats::default(),
361            env_vars: HashMap::new(),
362            args: Vec::new(),
363        }
364    }
365    /// Enable output capturing.
366    pub fn enable_capture(&mut self) {
367        self.output_buffer = Some(Vec::new());
368    }
369    /// Get captured output.
370    pub fn captured_output(&self) -> Option<&[String]> {
371        self.output_buffer.as_deref()
372    }
373    /// Push input for mocking.
374    pub fn push_input(&mut self, input: String) {
375        self.input_queue.push(input);
376    }
377    /// Set command-line arguments.
378    pub fn set_args(&mut self, args: Vec<String>) {
379        self.args = args;
380    }
381    /// Set an environment variable.
382    pub fn set_env(&mut self, key: String, value: String) {
383        self.env_vars.insert(key, value);
384    }
385    /// Execute a println operation.
386    pub fn exec_println(&mut self, s: &str) -> IoResult<()> {
387        self.stats.console_outputs += 1;
388        self.stats.bytes_written += s.len() as u64 + 1;
389        if let Some(ref mut buf) = self.output_buffer {
390            buf.push(s.to_string());
391            return Ok(());
392        }
393        if !self.io_enabled {
394            return Err(IoError::new(
395                IoErrorKind::Unsupported,
396                "I/O disabled in sandbox mode",
397            ));
398        }
399        ConsoleOps::println(s)
400    }
401    /// Execute a print operation.
402    pub fn exec_print(&mut self, s: &str) -> IoResult<()> {
403        self.stats.console_outputs += 1;
404        self.stats.bytes_written += s.len() as u64;
405        if let Some(ref mut buf) = self.output_buffer {
406            buf.push(s.to_string());
407            return Ok(());
408        }
409        if !self.io_enabled {
410            return Err(IoError::new(
411                IoErrorKind::Unsupported,
412                "I/O disabled in sandbox mode",
413            ));
414        }
415        ConsoleOps::print(s)
416    }
417    /// Execute a getLine operation.
418    pub fn exec_get_line(&mut self) -> IoResult<String> {
419        self.stats.console_inputs += 1;
420        if !self.input_queue.is_empty() {
421            let input = self.input_queue.remove(0);
422            self.stats.bytes_read += input.len() as u64;
423            return Ok(input);
424        }
425        if !self.io_enabled {
426            return Err(IoError::new(
427                IoErrorKind::Unsupported,
428                "I/O disabled in sandbox mode",
429            ));
430        }
431        let line = ConsoleOps::get_line()?;
432        self.stats.bytes_read += line.len() as u64;
433        Ok(line)
434    }
435    /// Execute a file read operation.
436    pub fn exec_read_file(&mut self, path: &str) -> IoResult<String> {
437        self.stats.file_reads += 1;
438        if !self.io_enabled {
439            return Err(IoError::new(
440                IoErrorKind::Unsupported,
441                "I/O disabled in sandbox mode",
442            ));
443        }
444        let contents = FileOps::read_file(path)?;
445        self.stats.bytes_read += contents.len() as u64;
446        Ok(contents)
447    }
448    /// Execute a file write operation.
449    pub fn exec_write_file(&mut self, path: &str, contents: &str) -> IoResult<()> {
450        self.stats.file_writes += 1;
451        self.stats.bytes_written += contents.len() as u64;
452        if !self.io_enabled {
453            return Err(IoError::new(
454                IoErrorKind::Unsupported,
455                "I/O disabled in sandbox mode",
456            ));
457        }
458        FileOps::write_file(path, contents)
459    }
460    /// Create a new mutable reference.
461    pub fn new_ref(&mut self, value: RtObject) -> u64 {
462        let id = self.next_ref_id;
463        self.next_ref_id += 1;
464        self.refs.insert(id, value);
465        self.stats.refs_created += 1;
466        id
467    }
468    /// Read a mutable reference.
469    pub fn read_ref(&mut self, id: u64) -> IoResult<RtObject> {
470        self.stats.ref_reads += 1;
471        self.refs.get(&id).cloned().ok_or_else(|| {
472            IoError::new(IoErrorKind::InvalidData, format!("invalid ref id: {}", id))
473        })
474    }
475    /// Write to a mutable reference.
476    pub fn write_ref(&mut self, id: u64, value: RtObject) -> IoResult<()> {
477        self.stats.ref_writes += 1;
478        if let std::collections::hash_map::Entry::Occupied(mut e) = self.refs.entry(id) {
479            e.insert(value);
480            Ok(())
481        } else {
482            Err(IoError::new(
483                IoErrorKind::InvalidData,
484                format!("invalid ref id: {}", id),
485            ))
486        }
487    }
488    /// Modify a mutable reference with a function.
489    pub fn modify_ref(&mut self, id: u64, f: impl FnOnce(RtObject) -> RtObject) -> IoResult<()> {
490        let value = self.read_ref(id)?;
491        let new_value = f(value);
492        self.write_ref(id, new_value)
493    }
494    /// Get an environment variable.
495    pub fn get_env_var(&self, key: &str) -> Option<String> {
496        self.env_vars
497            .get(key)
498            .cloned()
499            .or_else(|| std::env::var(key).ok())
500    }
501    /// Get the command-line arguments.
502    pub fn get_args(&self) -> &[String] {
503        &self.args
504    }
505    /// Get the current time as nanoseconds since epoch.
506    pub fn get_time_nanos(&self) -> IoResult<u64> {
507        if !self.io_enabled {
508            return Ok(0);
509        }
510        Ok(std::time::SystemTime::now()
511            .duration_since(std::time::UNIX_EPOCH)
512            .map_err(|e| IoError::internal(format!("time error: {}", e)))?
513            .as_nanos() as u64)
514    }
515    /// Get the statistics.
516    pub fn stats(&self) -> &IoStats {
517        &self.stats
518    }
519    /// Check if I/O is enabled.
520    pub fn is_enabled(&self) -> bool {
521        self.io_enabled
522    }
523    /// Enable or disable I/O.
524    pub fn set_enabled(&mut self, enabled: bool) {
525        self.io_enabled = enabled;
526    }
527    /// Reset the runtime state.
528    pub fn reset(&mut self) {
529        self.refs.clear();
530        self.next_ref_id = 0;
531        self.stats = IoStats::default();
532        self.output_buffer = if self.output_buffer.is_some() {
533            Some(Vec::new())
534        } else {
535            None
536        };
537        self.input_queue.clear();
538    }
539}
540/// An in-memory virtual filesystem for use in tests and sandboxed execution.
541#[allow(dead_code)]
542pub struct VirtualFilesystem {
543    files: HashMap<String, Vec<u8>>,
544    dirs: std::collections::HashSet<String>,
545    read_only: bool,
546}
547#[allow(dead_code)]
548impl VirtualFilesystem {
549    /// Create an empty virtual filesystem.
550    pub fn new() -> Self {
551        let mut dirs = std::collections::HashSet::new();
552        dirs.insert("/".to_string());
553        Self {
554            files: HashMap::new(),
555            dirs,
556            read_only: false,
557        }
558    }
559    /// Mark the filesystem as read-only.
560    pub fn set_read_only(&mut self, ro: bool) {
561        self.read_only = ro;
562    }
563    /// Create a directory.
564    pub fn mkdir(&mut self, path: &str) -> bool {
565        if self.read_only {
566            return false;
567        }
568        self.dirs.insert(path.to_string());
569        true
570    }
571    /// Write a file.
572    pub fn write_file(&mut self, path: &str, contents: &[u8]) -> Result<(), IoError> {
573        if self.read_only {
574            return Err(IoError {
575                kind: IoErrorKind::PermissionDenied,
576                message: format!("filesystem is read-only"),
577                path: Some(path.to_string()),
578            });
579        }
580        self.files.insert(path.to_string(), contents.to_vec());
581        Ok(())
582    }
583    /// Read a file.
584    pub fn read_file(&self, path: &str) -> Result<Vec<u8>, IoError> {
585        self.files.get(path).cloned().ok_or_else(|| IoError {
586            kind: IoErrorKind::FileNotFound,
587            message: format!("file not found: {}", path),
588            path: Some(path.to_string()),
589        })
590    }
591    /// Delete a file.
592    pub fn delete_file(&mut self, path: &str) -> bool {
593        if self.read_only {
594            return false;
595        }
596        self.files.remove(path).is_some()
597    }
598    /// Check whether a file exists.
599    pub fn file_exists(&self, path: &str) -> bool {
600        self.files.contains_key(path)
601    }
602    /// Check whether a directory exists.
603    pub fn dir_exists(&self, path: &str) -> bool {
604        self.dirs.contains(path)
605    }
606    /// List files in a directory (shallow).
607    pub fn list_dir(&self, dir: &str) -> Vec<String> {
608        let prefix = if dir.ends_with('/') {
609            dir.to_string()
610        } else {
611            format!("{}/", dir)
612        };
613        self.files
614            .keys()
615            .filter(|p| p.starts_with(&prefix) && !p[prefix.len()..].contains('/'))
616            .cloned()
617            .collect()
618    }
619    /// File size.
620    pub fn file_size(&self, path: &str) -> Option<usize> {
621        self.files.get(path).map(|v| v.len())
622    }
623    /// Append to a file (creating it if it doesn't exist).
624    pub fn append_file(&mut self, path: &str, contents: &[u8]) -> Result<(), IoError> {
625        if self.read_only {
626            return Err(IoError {
627                kind: IoErrorKind::PermissionDenied,
628                message: format!("filesystem is read-only"),
629                path: Some(path.to_string()),
630            });
631        }
632        self.files
633            .entry(path.to_string())
634            .or_default()
635            .extend_from_slice(contents);
636        Ok(())
637    }
638    /// Copy a file.
639    pub fn copy_file(&mut self, src: &str, dst: &str) -> Result<(), IoError> {
640        let contents = self.read_file(src)?;
641        self.write_file(dst, &contents)
642    }
643    /// Rename a file.
644    pub fn rename_file(&mut self, src: &str, dst: &str) -> Result<(), IoError> {
645        let contents = self.read_file(src)?;
646        self.write_file(dst, &contents)?;
647        self.delete_file(src);
648        Ok(())
649    }
650    /// Total number of files.
651    pub fn file_count(&self) -> usize {
652        self.files.len()
653    }
654    /// Total bytes stored.
655    pub fn total_bytes(&self) -> usize {
656        self.files.values().map(|v| v.len()).sum()
657    }
658}
659/// A mock I/O object that replays a scripted sequence of operations.
660#[allow(dead_code)]
661pub struct IoMock {
662    script: std::collections::VecDeque<MockIoOp>,
663    actual_calls: Vec<String>,
664}
665#[allow(dead_code)]
666impl IoMock {
667    /// Create a mock from a scripted sequence.
668    pub fn new(script: Vec<MockIoOp>) -> Self {
669        Self {
670            script: script.into(),
671            actual_calls: Vec::new(),
672        }
673    }
674    /// Simulate a read, consuming the next scripted response.
675    pub fn read(&mut self, _buf: &mut Vec<u8>) -> Option<Vec<u8>> {
676        if let Some(op) = self.script.pop_front() {
677            match op {
678                MockIoOp::Read { result, .. } => {
679                    self.actual_calls.push(format!("read:{}", result.len()));
680                    Some(result)
681                }
682                MockIoOp::ReadError { .. } => {
683                    self.actual_calls.push("read:error".to_string());
684                    None
685                }
686                _ => None,
687            }
688        } else {
689            None
690        }
691    }
692    /// Simulate a write.
693    pub fn write(&mut self, data: &[u8]) -> bool {
694        if let Some(op) = self.script.pop_front() {
695            match op {
696                MockIoOp::Write { ok, .. } => {
697                    self.actual_calls.push(format!("write:{}", data.len()));
698                    ok
699                }
700                _ => false,
701            }
702        } else {
703            false
704        }
705    }
706    /// Get the actual call log.
707    pub fn calls(&self) -> &[String] {
708        &self.actual_calls
709    }
710    /// Whether all scripted operations were consumed.
711    pub fn is_exhausted(&self) -> bool {
712        self.script.is_empty()
713    }
714    /// Remaining scripted operations.
715    pub fn remaining(&self) -> usize {
716        self.script.len()
717    }
718}
719/// Real-time bandwidth and latency metrics for I/O.
720#[allow(dead_code)]
721pub struct IoMetrics {
722    read_bytes: Vec<(u64, u64)>,
723    write_bytes: Vec<(u64, u64)>,
724    window_ms: u64,
725    max_samples: usize,
726}
727#[allow(dead_code)]
728impl IoMetrics {
729    /// Create metrics tracker with the given window.
730    pub fn new(window_ms: u64, max_samples: usize) -> Self {
731        Self {
732            read_bytes: Vec::new(),
733            write_bytes: Vec::new(),
734            window_ms,
735            max_samples,
736        }
737    }
738    /// Record a read event.
739    pub fn record_read(&mut self, bytes: u64, now_ms: u64) {
740        self.read_bytes.push((now_ms, bytes));
741        if self.read_bytes.len() > self.max_samples {
742            self.read_bytes.remove(0);
743        }
744    }
745    /// Record a write event.
746    pub fn record_write(&mut self, bytes: u64, now_ms: u64) {
747        self.write_bytes.push((now_ms, bytes));
748        if self.write_bytes.len() > self.max_samples {
749            self.write_bytes.remove(0);
750        }
751    }
752    /// Read bandwidth in bytes/ms over the window.
753    pub fn read_bw(&self, now_ms: u64) -> f64 {
754        let cutoff = now_ms.saturating_sub(self.window_ms);
755        let total: u64 = self
756            .read_bytes
757            .iter()
758            .filter(|(t, _)| *t >= cutoff)
759            .map(|(_, b)| b)
760            .sum();
761        if self.window_ms == 0 {
762            0.0
763        } else {
764            total as f64 / self.window_ms as f64
765        }
766    }
767    /// Write bandwidth in bytes/ms over the window.
768    pub fn write_bw(&self, now_ms: u64) -> f64 {
769        let cutoff = now_ms.saturating_sub(self.window_ms);
770        let total: u64 = self
771            .write_bytes
772            .iter()
773            .filter(|(t, _)| *t >= cutoff)
774            .map(|(_, b)| b)
775            .sum();
776        if self.window_ms == 0 {
777            0.0
778        } else {
779            total as f64 / self.window_ms as f64
780        }
781    }
782    /// Total read bytes recorded.
783    pub fn total_read(&self) -> u64 {
784        self.read_bytes.iter().map(|(_, b)| b).sum()
785    }
786    /// Total write bytes recorded.
787    pub fn total_write(&self) -> u64 {
788        self.write_bytes.iter().map(|(_, b)| b).sum()
789    }
790}
791/// An in-memory byte channel connecting a writer and reader.
792#[allow(dead_code)]
793pub struct IoChannel {
794    buf: std::collections::VecDeque<u8>,
795    closed: bool,
796    bytes_written: u64,
797    bytes_read: u64,
798}
799#[allow(dead_code)]
800impl IoChannel {
801    /// Create an empty channel.
802    pub fn new() -> Self {
803        Self {
804            buf: std::collections::VecDeque::new(),
805            closed: false,
806            bytes_written: 0,
807            bytes_read: 0,
808        }
809    }
810    /// Write bytes into the channel.
811    pub fn write(&mut self, data: &[u8]) -> bool {
812        if self.closed {
813            return false;
814        }
815        self.buf.extend(data.iter().copied());
816        self.bytes_written += data.len() as u64;
817        true
818    }
819    /// Read up to `n` bytes from the channel.
820    pub fn read(&mut self, n: usize) -> Vec<u8> {
821        let take = n.min(self.buf.len());
822        let mut out = Vec::with_capacity(take);
823        for _ in 0..take {
824            if let Some(b) = self.buf.pop_front() {
825                out.push(b);
826            }
827        }
828        self.bytes_read += out.len() as u64;
829        out
830    }
831    /// Read all available bytes.
832    pub fn read_all(&mut self) -> Vec<u8> {
833        let out: Vec<u8> = self.buf.drain(..).collect();
834        self.bytes_read += out.len() as u64;
835        out
836    }
837    /// Read a line (up to and including `\n`).
838    pub fn read_line(&mut self) -> Option<String> {
839        let pos = self.buf.iter().position(|&b| b == b'\n')?;
840        let line_bytes: Vec<u8> = self.buf.drain(..=pos).collect();
841        self.bytes_read += line_bytes.len() as u64;
842        String::from_utf8(line_bytes).ok()
843    }
844    /// Close the channel (no more writes allowed).
845    pub fn close(&mut self) {
846        self.closed = true;
847    }
848    /// Whether the channel is closed.
849    pub fn is_closed(&self) -> bool {
850        self.closed
851    }
852    /// Bytes available to read.
853    pub fn available(&self) -> usize {
854        self.buf.len()
855    }
856    /// Total bytes written.
857    pub fn bytes_written(&self) -> u64 {
858        self.bytes_written
859    }
860    /// Total bytes read.
861    pub fn bytes_read(&self) -> u64 {
862        self.bytes_read
863    }
864}
865/// Console I/O operations.
866pub struct ConsoleOps;
867impl ConsoleOps {
868    /// Print a string to stdout (no newline).
869    pub fn print(s: &str) -> IoResult<()> {
870        print!("{}", s);
871        io::stdout().flush().map_err(|e| IoError::from_io_error(&e))
872    }
873    /// Print a string to stdout with a newline.
874    pub fn println(s: &str) -> IoResult<()> {
875        println!("{}", s);
876        Ok(())
877    }
878    /// Print a string to stderr.
879    pub fn eprint(s: &str) -> IoResult<()> {
880        eprint!("{}", s);
881        io::stderr().flush().map_err(|e| IoError::from_io_error(&e))
882    }
883    /// Print a string to stderr with a newline.
884    pub fn eprintln(s: &str) -> IoResult<()> {
885        eprintln!("{}", s);
886        Ok(())
887    }
888    /// Read a line from stdin.
889    pub fn get_line() -> IoResult<String> {
890        let mut line = String::new();
891        io::stdin()
892            .read_line(&mut line)
893            .map_err(|e| IoError::from_io_error(&e))?;
894        if line.ends_with('\n') {
895            line.pop();
896            if line.ends_with('\r') {
897                line.pop();
898            }
899        }
900        Ok(line)
901    }
902    /// Read all of stdin as a string.
903    pub fn read_stdin() -> IoResult<String> {
904        let mut buffer = String::new();
905        io::stdin()
906            .read_to_string(&mut buffer)
907            .map_err(|e| IoError::from_io_error(&e))?;
908        Ok(buffer)
909    }
910}
911/// A policy object that controls I/O error handling.
912#[allow(dead_code)]
913pub struct IoPolicy {
914    pub read_error: IoErrorPolicy,
915    pub write_error: IoErrorPolicy,
916    pub open_error: IoErrorPolicy,
917}
918#[allow(dead_code)]
919impl IoPolicy {
920    /// Strict policy: propagate all errors.
921    pub fn strict() -> Self {
922        Self {
923            read_error: IoErrorPolicy::Propagate,
924            write_error: IoErrorPolicy::Propagate,
925            open_error: IoErrorPolicy::Propagate,
926        }
927    }
928    /// Lenient policy: log and continue.
929    pub fn lenient() -> Self {
930        Self {
931            read_error: IoErrorPolicy::LogAndContinue,
932            write_error: IoErrorPolicy::LogAndContinue,
933            open_error: IoErrorPolicy::LogAndContinue,
934        }
935    }
936    /// Retry policy with 3 attempts.
937    pub fn retry() -> Self {
938        Self {
939            read_error: IoErrorPolicy::Retry { max: 3 },
940            write_error: IoErrorPolicy::Retry { max: 3 },
941            open_error: IoErrorPolicy::Retry { max: 3 },
942        }
943    }
944}
945/// Kind of I/O error.
946#[derive(Clone, Debug, PartialEq, Eq)]
947pub enum IoErrorKind {
948    /// File not found.
949    FileNotFound,
950    /// Permission denied.
951    PermissionDenied,
952    /// File already exists.
953    AlreadyExists,
954    /// I/O operation failed.
955    IoFailed,
956    /// Invalid input/data.
957    InvalidData,
958    /// Timeout.
959    TimedOut,
960    /// User-thrown exception.
961    UserError,
962    /// Internal runtime error.
963    InternalError,
964    /// End of file.
965    EndOfFile,
966    /// Interrupted operation.
967    Interrupted,
968    /// Unsupported operation.
969    Unsupported,
970}
971impl IoErrorKind {
972    /// Convert from standard I/O error kind.
973    fn from_io_error_kind(kind: io::ErrorKind) -> Self {
974        match kind {
975            io::ErrorKind::NotFound => IoErrorKind::FileNotFound,
976            io::ErrorKind::PermissionDenied => IoErrorKind::PermissionDenied,
977            io::ErrorKind::AlreadyExists => IoErrorKind::AlreadyExists,
978            io::ErrorKind::InvalidData => IoErrorKind::InvalidData,
979            io::ErrorKind::TimedOut => IoErrorKind::TimedOut,
980            io::ErrorKind::Interrupted => IoErrorKind::Interrupted,
981            io::ErrorKind::UnexpectedEof => IoErrorKind::EndOfFile,
982            _ => IoErrorKind::IoFailed,
983        }
984    }
985}
986/// A record of a file's last observed modification time.
987#[allow(dead_code)]
988#[derive(Clone, Debug)]
989pub struct FileRecord {
990    pub path: String,
991    pub size: u64,
992    pub last_seen_ms: u64,
993    pub change_count: u64,
994}
995/// A structured log of I/O events for tracing and debugging.
996#[allow(dead_code)]
997pub struct IoLog {
998    events: Vec<IoEvent>,
999    max_events: usize,
1000    overflowed: bool,
1001}
1002#[allow(dead_code)]
1003impl IoLog {
1004    /// Create a new I/O log with the given capacity.
1005    pub fn new(max_events: usize) -> Self {
1006        Self {
1007            events: Vec::new(),
1008            max_events: max_events.max(1),
1009            overflowed: false,
1010        }
1011    }
1012    /// Record an event.
1013    pub fn record(&mut self, event: IoEvent) {
1014        if self.events.len() >= self.max_events {
1015            self.overflowed = true;
1016            self.events.remove(0);
1017        }
1018        self.events.push(event);
1019    }
1020    /// Get all events.
1021    pub fn events(&self) -> &[IoEvent] {
1022        &self.events
1023    }
1024    /// Filter events by kind.
1025    pub fn events_of_kind(&self, kind: &IoEventKind) -> Vec<&IoEvent> {
1026        self.events.iter().filter(|e| &e.kind == kind).collect()
1027    }
1028    /// Total bytes read across all Read events.
1029    pub fn total_bytes_read(&self) -> usize {
1030        self.events
1031            .iter()
1032            .filter(|e| e.kind == IoEventKind::Read)
1033            .map(|e| e.bytes)
1034            .sum()
1035    }
1036    /// Total bytes written across all Write events.
1037    pub fn total_bytes_written(&self) -> usize {
1038        self.events
1039            .iter()
1040            .filter(|e| e.kind == IoEventKind::Write)
1041            .map(|e| e.bytes)
1042            .sum()
1043    }
1044    /// Number of errors.
1045    pub fn error_count(&self) -> usize {
1046        self.events
1047            .iter()
1048            .filter(|e| e.kind == IoEventKind::Error)
1049            .count()
1050    }
1051    /// Whether the log has overflowed.
1052    pub fn has_overflowed(&self) -> bool {
1053        self.overflowed
1054    }
1055    /// Clear the log.
1056    pub fn clear(&mut self) {
1057        self.events.clear();
1058        self.overflowed = false;
1059    }
1060    /// Total event count.
1061    pub fn len(&self) -> usize {
1062        self.events.len()
1063    }
1064    /// Whether the log is empty.
1065    pub fn is_empty(&self) -> bool {
1066        self.events.is_empty()
1067    }
1068}
1069/// File I/O operations.
1070pub struct FileOps;
1071impl FileOps {
1072    /// Read an entire file as a string.
1073    pub fn read_file(path: &str) -> IoResult<String> {
1074        std::fs::read_to_string(path).map_err(|e| {
1075            IoError::with_path(
1076                IoErrorKind::from_io_error_kind(e.kind()),
1077                e.to_string(),
1078                path,
1079            )
1080        })
1081    }
1082    /// Read a file as bytes.
1083    pub fn read_file_bytes(path: &str) -> IoResult<Vec<u8>> {
1084        std::fs::read(path).map_err(|e| {
1085            IoError::with_path(
1086                IoErrorKind::from_io_error_kind(e.kind()),
1087                e.to_string(),
1088                path,
1089            )
1090        })
1091    }
1092    /// Write a string to a file (overwrite).
1093    pub fn write_file(path: &str, contents: &str) -> IoResult<()> {
1094        std::fs::write(path, contents).map_err(|e| {
1095            IoError::with_path(
1096                IoErrorKind::from_io_error_kind(e.kind()),
1097                e.to_string(),
1098                path,
1099            )
1100        })
1101    }
1102    /// Write bytes to a file (overwrite).
1103    pub fn write_file_bytes(path: &str, contents: &[u8]) -> IoResult<()> {
1104        std::fs::write(path, contents).map_err(|e| {
1105            IoError::with_path(
1106                IoErrorKind::from_io_error_kind(e.kind()),
1107                e.to_string(),
1108                path,
1109            )
1110        })
1111    }
1112    /// Append a string to a file.
1113    pub fn append_file(path: &str, contents: &str) -> IoResult<()> {
1114        use std::fs::OpenOptions;
1115        let mut file = OpenOptions::new()
1116            .create(true)
1117            .append(true)
1118            .open(path)
1119            .map_err(|e| {
1120                IoError::with_path(
1121                    IoErrorKind::from_io_error_kind(e.kind()),
1122                    e.to_string(),
1123                    path,
1124                )
1125            })?;
1126        file.write_all(contents.as_bytes()).map_err(|e| {
1127            IoError::with_path(
1128                IoErrorKind::from_io_error_kind(e.kind()),
1129                e.to_string(),
1130                path,
1131            )
1132        })
1133    }
1134    /// Check if a file exists.
1135    pub fn file_exists(path: &str) -> bool {
1136        std::path::Path::new(path).exists()
1137    }
1138    /// Delete a file.
1139    pub fn delete_file(path: &str) -> IoResult<()> {
1140        std::fs::remove_file(path).map_err(|e| {
1141            IoError::with_path(
1142                IoErrorKind::from_io_error_kind(e.kind()),
1143                e.to_string(),
1144                path,
1145            )
1146        })
1147    }
1148    /// Get the size of a file in bytes.
1149    pub fn file_size(path: &str) -> IoResult<u64> {
1150        std::fs::metadata(path).map(|m| m.len()).map_err(|e| {
1151            IoError::with_path(
1152                IoErrorKind::from_io_error_kind(e.kind()),
1153                e.to_string(),
1154                path,
1155            )
1156        })
1157    }
1158    /// Create a directory (and parents).
1159    pub fn create_dir(path: &str) -> IoResult<()> {
1160        std::fs::create_dir_all(path).map_err(|e| {
1161            IoError::with_path(
1162                IoErrorKind::from_io_error_kind(e.kind()),
1163                e.to_string(),
1164                path,
1165            )
1166        })
1167    }
1168    /// List directory entries.
1169    pub fn list_dir(path: &str) -> IoResult<Vec<String>> {
1170        let entries = std::fs::read_dir(path).map_err(|e| {
1171            IoError::with_path(
1172                IoErrorKind::from_io_error_kind(e.kind()),
1173                e.to_string(),
1174                path,
1175            )
1176        })?;
1177        let mut result = Vec::new();
1178        for entry in entries {
1179            match entry {
1180                Ok(e) => {
1181                    if let Some(name) = e.file_name().to_str() {
1182                        result.push(name.to_string());
1183                    }
1184                }
1185                Err(e) => {
1186                    return Err(IoError::with_path(
1187                        IoErrorKind::from_io_error_kind(e.kind()),
1188                        e.to_string(),
1189                        path,
1190                    ));
1191                }
1192            }
1193        }
1194        Ok(result)
1195    }
1196}
1197/// A buffered I/O sink that accumulates writes and flushes on demand.
1198#[allow(dead_code)]
1199pub struct IoBuffer {
1200    buf: Vec<u8>,
1201    flush_threshold: usize,
1202    flush_count: u64,
1203    write_count: u64,
1204    total_bytes: u64,
1205}
1206#[allow(dead_code)]
1207impl IoBuffer {
1208    /// Create a buffer with the given flush threshold (bytes).
1209    pub fn new(flush_threshold: usize) -> Self {
1210        Self {
1211            buf: Vec::new(),
1212            flush_threshold,
1213            flush_count: 0,
1214            write_count: 0,
1215            total_bytes: 0,
1216        }
1217    }
1218    /// Write bytes to the buffer. Auto-flushes when threshold is exceeded.
1219    pub fn write(&mut self, data: &[u8]) -> Vec<u8> {
1220        self.buf.extend_from_slice(data);
1221        self.write_count += 1;
1222        self.total_bytes += data.len() as u64;
1223        if self.buf.len() >= self.flush_threshold {
1224            self.flush()
1225        } else {
1226            Vec::new()
1227        }
1228    }
1229    /// Write a UTF-8 string.
1230    pub fn write_str(&mut self, s: &str) -> Vec<u8> {
1231        self.write(s.as_bytes())
1232    }
1233    /// Flush the buffer, returning its contents.
1234    pub fn flush(&mut self) -> Vec<u8> {
1235        let out = std::mem::take(&mut self.buf);
1236        if !out.is_empty() {
1237            self.flush_count += 1;
1238        }
1239        out
1240    }
1241    /// Current buffer occupancy in bytes.
1242    pub fn buffered_bytes(&self) -> usize {
1243        self.buf.len()
1244    }
1245    /// Whether the buffer contains data.
1246    pub fn has_data(&self) -> bool {
1247        !self.buf.is_empty()
1248    }
1249    /// Number of write calls.
1250    pub fn write_count(&self) -> u64 {
1251        self.write_count
1252    }
1253    /// Number of flush calls.
1254    pub fn flush_count(&self) -> u64 {
1255        self.flush_count
1256    }
1257    /// Total bytes written (including not-yet-flushed data).
1258    pub fn total_bytes(&self) -> u64 {
1259        self.total_bytes
1260    }
1261}
1262/// Rate-limits I/O operations by byte budget per time window.
1263#[allow(dead_code)]
1264pub struct IoThrottle {
1265    bytes_per_window: u64,
1266    window_ms: u64,
1267    used_in_window: u64,
1268    window_start_ms: u64,
1269    total_throttled_bytes: u64,
1270    throttle_events: u64,
1271}
1272#[allow(dead_code)]
1273impl IoThrottle {
1274    /// Create a throttle allowing `bytes_per_window` bytes per `window_ms`.
1275    pub fn new(bytes_per_window: u64, window_ms: u64) -> Self {
1276        Self {
1277            bytes_per_window,
1278            window_ms,
1279            used_in_window: 0,
1280            window_start_ms: 0,
1281            total_throttled_bytes: 0,
1282            throttle_events: 0,
1283        }
1284    }
1285    /// Try to consume `bytes` from the current window.
1286    /// Returns `true` if allowed, `false` if throttled.
1287    pub fn try_consume(&mut self, bytes: u64, now_ms: u64) -> bool {
1288        if now_ms >= self.window_start_ms + self.window_ms {
1289            self.window_start_ms = now_ms;
1290            self.used_in_window = 0;
1291        }
1292        if self.used_in_window + bytes <= self.bytes_per_window {
1293            self.used_in_window += bytes;
1294            true
1295        } else {
1296            self.total_throttled_bytes += bytes;
1297            self.throttle_events += 1;
1298            false
1299        }
1300    }
1301    /// Bytes remaining in the current window.
1302    pub fn remaining(&self) -> u64 {
1303        self.bytes_per_window.saturating_sub(self.used_in_window)
1304    }
1305    /// Total throttle events so far.
1306    pub fn throttle_events(&self) -> u64 {
1307        self.throttle_events
1308    }
1309    /// Total bytes that were throttled.
1310    pub fn total_throttled_bytes(&self) -> u64 {
1311        self.total_throttled_bytes
1312    }
1313}
1314/// Exception handling for the I/O runtime.
1315pub struct ErrorHandling;
1316impl ErrorHandling {
1317    /// Create an exception object from an error message.
1318    pub fn make_exception(message: &str) -> RtObject {
1319        RtObject::string(message.to_string())
1320    }
1321    /// Create an exception object from an IoError.
1322    pub fn from_io_error(err: &IoError) -> RtObject {
1323        RtObject::string(err.to_string())
1324    }
1325    /// Try to extract an error message from an exception object.
1326    pub fn get_message(exception: &RtObject) -> Option<String> {
1327        crate::object::StringOps::as_str(exception)
1328    }
1329    /// Check if an object represents an error.
1330    pub fn is_error(obj: &RtObject) -> bool {
1331        if let Some(idx) = obj.as_small_ctor() {
1332            return idx == 1;
1333        }
1334        false
1335    }
1336    /// Create an "ok" result.
1337    pub fn ok(value: RtObject) -> RtObject {
1338        RtObject::constructor(0, vec![value])
1339    }
1340    /// Create an "error" result.
1341    pub fn error(message: String) -> RtObject {
1342        RtObject::constructor(1, vec![RtObject::string(message)])
1343    }
1344}
1345/// The result of executing an I/O action.
1346#[derive(Clone, Debug)]
1347pub enum IoValue {
1348    /// Pure value (no side effects performed).
1349    Pure(RtObject),
1350    /// Error occurred.
1351    Error(IoError),
1352    /// Operation returned nothing meaningful (e.g., println).
1353    Unit,
1354}
1355impl IoValue {
1356    /// Create a pure value.
1357    pub fn pure_val(obj: RtObject) -> Self {
1358        IoValue::Pure(obj)
1359    }
1360    /// Create an error.
1361    pub fn error(err: IoError) -> Self {
1362        IoValue::Error(err)
1363    }
1364    /// Create a unit value.
1365    pub fn unit() -> Self {
1366        IoValue::Unit
1367    }
1368    /// Check if this is an error.
1369    pub fn is_error(&self) -> bool {
1370        matches!(self, IoValue::Error(_))
1371    }
1372    /// Convert to a runtime object.
1373    pub fn to_rt_object(&self) -> RtObject {
1374        match self {
1375            IoValue::Pure(obj) => obj.clone(),
1376            IoValue::Error(err) => err.to_rt_object(),
1377            IoValue::Unit => RtObject::unit(),
1378        }
1379    }
1380    /// Convert to a result.
1381    pub fn to_result(self) -> IoResult<RtObject> {
1382        match self {
1383            IoValue::Pure(obj) => Ok(obj),
1384            IoValue::Error(err) => Err(err),
1385            IoValue::Unit => Ok(RtObject::unit()),
1386        }
1387    }
1388}
1389/// String operations that work directly on `RtObject` values.
1390pub struct StringRtOps;
1391impl StringRtOps {
1392    /// Concatenate two string objects.
1393    pub fn concat(a: &RtObject, b: &RtObject) -> Option<RtObject> {
1394        crate::object::StringOps::concat(a, b)
1395    }
1396    /// Get the length of a string object.
1397    pub fn length(obj: &RtObject) -> Option<RtObject> {
1398        crate::object::StringOps::byte_len(obj).map(|n| RtObject::nat(n as u64))
1399    }
1400    /// Convert a natural number to a string object.
1401    pub fn nat_repr(obj: &RtObject) -> Option<RtObject> {
1402        crate::object::StringOps::nat_to_string(obj)
1403    }
1404    /// Get a character at an index.
1405    pub fn get_char(obj: &RtObject, index: &RtObject) -> Option<RtObject> {
1406        let idx = index.as_small_nat()? as usize;
1407        crate::object::StringOps::char_at(obj, idx)
1408    }
1409    /// Take a substring.
1410    pub fn substr(obj: &RtObject, start: &RtObject, len: &RtObject) -> Option<RtObject> {
1411        let s = start.as_small_nat()? as usize;
1412        let l = len.as_small_nat()? as usize;
1413        crate::object::StringOps::substring(obj, s, l)
1414    }
1415}
1416/// Statistics for I/O operations.
1417#[derive(Clone, Debug, Default)]
1418pub struct IoStats {
1419    /// Number of file reads.
1420    pub file_reads: u64,
1421    /// Number of file writes.
1422    pub file_writes: u64,
1423    /// Number of console outputs.
1424    pub console_outputs: u64,
1425    /// Number of console inputs.
1426    pub console_inputs: u64,
1427    /// Number of exceptions thrown.
1428    pub exceptions_thrown: u64,
1429    /// Number of exceptions caught.
1430    pub exceptions_caught: u64,
1431    /// Number of refs created.
1432    pub refs_created: u64,
1433    /// Number of ref reads.
1434    pub ref_reads: u64,
1435    /// Number of ref writes.
1436    pub ref_writes: u64,
1437    /// Total bytes read.
1438    pub bytes_read: u64,
1439    /// Total bytes written.
1440    pub bytes_written: u64,
1441}
1442/// Aggregate statistics for an I/O session.
1443#[allow(dead_code)]
1444#[derive(Clone, Debug, Default)]
1445pub struct IoSessionStats {
1446    pub reads: u64,
1447    pub writes: u64,
1448    pub flushes: u64,
1449    pub errors: u64,
1450    pub bytes_read: u64,
1451    pub bytes_written: u64,
1452    pub open_count: u64,
1453    pub close_count: u64,
1454}
1455#[allow(dead_code)]
1456impl IoSessionStats {
1457    /// Record a read of `n` bytes.
1458    pub fn record_read(&mut self, n: u64) {
1459        self.reads += 1;
1460        self.bytes_read += n;
1461    }
1462    /// Record a write of `n` bytes.
1463    pub fn record_write(&mut self, n: u64) {
1464        self.writes += 1;
1465        self.bytes_written += n;
1466    }
1467    /// Record a flush.
1468    pub fn record_flush(&mut self) {
1469        self.flushes += 1;
1470    }
1471    /// Record an error.
1472    pub fn record_error(&mut self) {
1473        self.errors += 1;
1474    }
1475    /// Record a file open.
1476    pub fn record_open(&mut self) {
1477        self.open_count += 1;
1478    }
1479    /// Record a file close.
1480    pub fn record_close(&mut self) {
1481        self.close_count += 1;
1482    }
1483    /// Total I/O operations.
1484    pub fn total_ops(&self) -> u64 {
1485        self.reads + self.writes + self.flushes
1486    }
1487    /// Read/write ratio (reads / (reads + writes)).
1488    pub fn read_ratio(&self) -> f64 {
1489        let total = self.reads + self.writes;
1490        if total == 0 {
1491            0.0
1492        } else {
1493            self.reads as f64 / total as f64
1494        }
1495    }
1496}
1497/// Runtime I/O error.
1498#[derive(Clone, Debug)]
1499pub struct IoError {
1500    /// Error kind.
1501    pub kind: IoErrorKind,
1502    /// Error message.
1503    pub message: String,
1504    /// Optional file path associated with the error.
1505    pub path: Option<String>,
1506}
1507impl IoError {
1508    /// Create a new I/O error.
1509    pub fn new(kind: IoErrorKind, message: impl Into<String>) -> Self {
1510        IoError {
1511            kind,
1512            message: message.into(),
1513            path: None,
1514        }
1515    }
1516    /// Create a new I/O error with a file path.
1517    pub fn with_path(
1518        kind: IoErrorKind,
1519        message: impl Into<String>,
1520        path: impl Into<String>,
1521    ) -> Self {
1522        IoError {
1523            kind,
1524            message: message.into(),
1525            path: Some(path.into()),
1526        }
1527    }
1528    /// Create a file-not-found error.
1529    pub fn file_not_found(path: impl Into<String>) -> Self {
1530        let p = path.into();
1531        IoError {
1532            kind: IoErrorKind::FileNotFound,
1533            message: format!("file not found: {}", p),
1534            path: Some(p),
1535        }
1536    }
1537    /// Create a user error.
1538    pub fn user_error(message: impl Into<String>) -> Self {
1539        IoError::new(IoErrorKind::UserError, message)
1540    }
1541    /// Create an internal error.
1542    pub fn internal(message: impl Into<String>) -> Self {
1543        IoError::new(IoErrorKind::InternalError, message)
1544    }
1545    /// Convert from a standard I/O error.
1546    pub fn from_io_error(err: &io::Error) -> Self {
1547        let kind = match err.kind() {
1548            io::ErrorKind::NotFound => IoErrorKind::FileNotFound,
1549            io::ErrorKind::PermissionDenied => IoErrorKind::PermissionDenied,
1550            io::ErrorKind::AlreadyExists => IoErrorKind::AlreadyExists,
1551            io::ErrorKind::InvalidData => IoErrorKind::InvalidData,
1552            io::ErrorKind::TimedOut => IoErrorKind::TimedOut,
1553            io::ErrorKind::Interrupted => IoErrorKind::Interrupted,
1554            io::ErrorKind::UnexpectedEof => IoErrorKind::EndOfFile,
1555            _ => IoErrorKind::IoFailed,
1556        };
1557        IoError::new(kind, err.to_string())
1558    }
1559    /// Convert to a runtime object (for the error monad).
1560    pub fn to_rt_object(&self) -> RtObject {
1561        RtObject::string(self.message.clone())
1562    }
1563}