1use crate::object::RtObject;
6use std::collections::{HashMap, HashSet, VecDeque};
7use std::io::{self, Read, Write};
8
9use super::functions::IoResult;
10
11#[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}
21pub struct IoExecutor<'a> {
23 runtime: &'a mut IoRuntime,
25}
26impl<'a> IoExecutor<'a> {
27 pub fn new(runtime: &'a mut IoRuntime) -> Self {
29 IoExecutor { runtime }
30 }
31 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 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 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 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 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 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 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 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 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#[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#[allow(dead_code)]
104pub struct IoFileWatcher {
105 records: HashMap<String, FileRecord>,
106 poll_interval_ms: u64,
107}
108#[allow(dead_code)]
109impl IoFileWatcher {
110 pub fn new(poll_interval_ms: u64) -> Self {
112 Self {
113 records: HashMap::new(),
114 poll_interval_ms,
115 }
116 }
117 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 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 pub fn record(&self, path: &str) -> Option<&FileRecord> {
149 self.records.get(path)
150 }
151 pub fn watch_count(&self) -> usize {
153 self.records.len()
154 }
155 pub fn unwatch(&mut self, path: &str) {
157 self.records.remove(path);
158 }
159}
160#[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 pub fn new() -> Self {
170 Self {
171 a_to_b: IoChannel::new(),
172 b_to_a: IoChannel::new(),
173 }
174 }
175 pub fn send_a_to_b(&mut self, data: &[u8]) -> bool {
177 self.a_to_b.write(data)
178 }
179 pub fn send_b_to_a(&mut self, data: &[u8]) -> bool {
181 self.b_to_a.write(data)
182 }
183 pub fn recv_b(&mut self, n: usize) -> Vec<u8> {
185 self.a_to_b.read(n)
186 }
187 pub fn recv_a(&mut self, n: usize) -> Vec<u8> {
189 self.b_to_a.read(n)
190 }
191 pub fn close(&mut self) {
193 self.a_to_b.close();
194 self.b_to_a.close();
195 }
196}
197#[allow(dead_code)]
199#[derive(Clone, Debug, PartialEq, Eq)]
200pub enum IoErrorPolicy {
201 Propagate,
203 Ignore,
205 Retry { max: u32 },
207 LogAndContinue,
209}
210pub struct StringFormatter;
212impl StringFormatter {
213 pub fn format_object(obj: &RtObject) -> String {
215 format!("{}", obj)
216 }
217 pub fn nat_to_string(n: u64) -> String {
219 format!("{}", n)
220 }
221 pub fn int_to_string(n: i64) -> String {
223 format!("{}", n)
224 }
225 pub fn float_to_string(f: f64) -> String {
227 format!("{}", f)
228 }
229 pub fn bool_to_string(b: bool) -> String {
231 if b {
232 "true".to_string()
233 } else {
234 "false".to_string()
235 }
236 }
237 pub fn char_to_string(c: char) -> String {
239 c.to_string()
240 }
241 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 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 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 pub fn to_upper(s: &str) -> String {
271 s.to_uppercase()
272 }
273 pub fn to_lower(s: &str) -> String {
275 s.to_lowercase()
276 }
277 pub fn trim(s: &str) -> String {
279 s.trim().to_string()
280 }
281 pub fn split(s: &str, sep: &str) -> Vec<String> {
283 s.split(sep).map(|p| p.to_string()).collect()
284 }
285 pub fn join(parts: &[String], sep: &str) -> String {
287 parts.join(sep)
288 }
289 pub fn starts_with(s: &str, prefix: &str) -> bool {
291 s.starts_with(prefix)
292 }
293 pub fn ends_with(s: &str, suffix: &str) -> bool {
295 s.ends_with(suffix)
296 }
297 pub fn replace(s: &str, from: &str, to: &str) -> String {
299 s.replace(from, to)
300 }
301 pub fn contains(s: &str, substr: &str) -> bool {
303 s.contains(substr)
304 }
305}
306#[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}
319pub struct IoRuntime {
321 pub(super) refs: HashMap<u64, RtObject>,
323 next_ref_id: u64,
325 pub(super) io_enabled: bool,
327 pub(super) output_buffer: Option<Vec<String>>,
329 pub(super) input_queue: Vec<String>,
331 stats: IoStats,
333 env_vars: HashMap<String, String>,
335 args: Vec<String>,
337}
338impl IoRuntime {
339 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 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 pub fn enable_capture(&mut self) {
367 self.output_buffer = Some(Vec::new());
368 }
369 pub fn captured_output(&self) -> Option<&[String]> {
371 self.output_buffer.as_deref()
372 }
373 pub fn push_input(&mut self, input: String) {
375 self.input_queue.push(input);
376 }
377 pub fn set_args(&mut self, args: Vec<String>) {
379 self.args = args;
380 }
381 pub fn set_env(&mut self, key: String, value: String) {
383 self.env_vars.insert(key, value);
384 }
385 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 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 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 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 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 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 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 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 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 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 pub fn get_args(&self) -> &[String] {
503 &self.args
504 }
505 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 pub fn stats(&self) -> &IoStats {
517 &self.stats
518 }
519 pub fn is_enabled(&self) -> bool {
521 self.io_enabled
522 }
523 pub fn set_enabled(&mut self, enabled: bool) {
525 self.io_enabled = enabled;
526 }
527 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#[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 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 pub fn set_read_only(&mut self, ro: bool) {
561 self.read_only = ro;
562 }
563 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 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 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 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 pub fn file_exists(&self, path: &str) -> bool {
600 self.files.contains_key(path)
601 }
602 pub fn dir_exists(&self, path: &str) -> bool {
604 self.dirs.contains(path)
605 }
606 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 pub fn file_size(&self, path: &str) -> Option<usize> {
621 self.files.get(path).map(|v| v.len())
622 }
623 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 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 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 pub fn file_count(&self) -> usize {
652 self.files.len()
653 }
654 pub fn total_bytes(&self) -> usize {
656 self.files.values().map(|v| v.len()).sum()
657 }
658}
659#[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 pub fn new(script: Vec<MockIoOp>) -> Self {
669 Self {
670 script: script.into(),
671 actual_calls: Vec::new(),
672 }
673 }
674 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 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 pub fn calls(&self) -> &[String] {
708 &self.actual_calls
709 }
710 pub fn is_exhausted(&self) -> bool {
712 self.script.is_empty()
713 }
714 pub fn remaining(&self) -> usize {
716 self.script.len()
717 }
718}
719#[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 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 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 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 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 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 pub fn total_read(&self) -> u64 {
784 self.read_bytes.iter().map(|(_, b)| b).sum()
785 }
786 pub fn total_write(&self) -> u64 {
788 self.write_bytes.iter().map(|(_, b)| b).sum()
789 }
790}
791#[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 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 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 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 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 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 pub fn close(&mut self) {
846 self.closed = true;
847 }
848 pub fn is_closed(&self) -> bool {
850 self.closed
851 }
852 pub fn available(&self) -> usize {
854 self.buf.len()
855 }
856 pub fn bytes_written(&self) -> u64 {
858 self.bytes_written
859 }
860 pub fn bytes_read(&self) -> u64 {
862 self.bytes_read
863 }
864}
865pub struct ConsoleOps;
867impl ConsoleOps {
868 pub fn print(s: &str) -> IoResult<()> {
870 print!("{}", s);
871 io::stdout().flush().map_err(|e| IoError::from_io_error(&e))
872 }
873 pub fn println(s: &str) -> IoResult<()> {
875 println!("{}", s);
876 Ok(())
877 }
878 pub fn eprint(s: &str) -> IoResult<()> {
880 eprint!("{}", s);
881 io::stderr().flush().map_err(|e| IoError::from_io_error(&e))
882 }
883 pub fn eprintln(s: &str) -> IoResult<()> {
885 eprintln!("{}", s);
886 Ok(())
887 }
888 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 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#[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 pub fn strict() -> Self {
922 Self {
923 read_error: IoErrorPolicy::Propagate,
924 write_error: IoErrorPolicy::Propagate,
925 open_error: IoErrorPolicy::Propagate,
926 }
927 }
928 pub fn lenient() -> Self {
930 Self {
931 read_error: IoErrorPolicy::LogAndContinue,
932 write_error: IoErrorPolicy::LogAndContinue,
933 open_error: IoErrorPolicy::LogAndContinue,
934 }
935 }
936 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#[derive(Clone, Debug, PartialEq, Eq)]
947pub enum IoErrorKind {
948 FileNotFound,
950 PermissionDenied,
952 AlreadyExists,
954 IoFailed,
956 InvalidData,
958 TimedOut,
960 UserError,
962 InternalError,
964 EndOfFile,
966 Interrupted,
968 Unsupported,
970}
971impl IoErrorKind {
972 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#[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#[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 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 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 pub fn events(&self) -> &[IoEvent] {
1022 &self.events
1023 }
1024 pub fn events_of_kind(&self, kind: &IoEventKind) -> Vec<&IoEvent> {
1026 self.events.iter().filter(|e| &e.kind == kind).collect()
1027 }
1028 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 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 pub fn error_count(&self) -> usize {
1046 self.events
1047 .iter()
1048 .filter(|e| e.kind == IoEventKind::Error)
1049 .count()
1050 }
1051 pub fn has_overflowed(&self) -> bool {
1053 self.overflowed
1054 }
1055 pub fn clear(&mut self) {
1057 self.events.clear();
1058 self.overflowed = false;
1059 }
1060 pub fn len(&self) -> usize {
1062 self.events.len()
1063 }
1064 pub fn is_empty(&self) -> bool {
1066 self.events.is_empty()
1067 }
1068}
1069pub struct FileOps;
1071impl FileOps {
1072 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 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 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 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 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 pub fn file_exists(path: &str) -> bool {
1136 std::path::Path::new(path).exists()
1137 }
1138 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 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 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 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#[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 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 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 pub fn write_str(&mut self, s: &str) -> Vec<u8> {
1231 self.write(s.as_bytes())
1232 }
1233 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 pub fn buffered_bytes(&self) -> usize {
1243 self.buf.len()
1244 }
1245 pub fn has_data(&self) -> bool {
1247 !self.buf.is_empty()
1248 }
1249 pub fn write_count(&self) -> u64 {
1251 self.write_count
1252 }
1253 pub fn flush_count(&self) -> u64 {
1255 self.flush_count
1256 }
1257 pub fn total_bytes(&self) -> u64 {
1259 self.total_bytes
1260 }
1261}
1262#[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 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 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 pub fn remaining(&self) -> u64 {
1303 self.bytes_per_window.saturating_sub(self.used_in_window)
1304 }
1305 pub fn throttle_events(&self) -> u64 {
1307 self.throttle_events
1308 }
1309 pub fn total_throttled_bytes(&self) -> u64 {
1311 self.total_throttled_bytes
1312 }
1313}
1314pub struct ErrorHandling;
1316impl ErrorHandling {
1317 pub fn make_exception(message: &str) -> RtObject {
1319 RtObject::string(message.to_string())
1320 }
1321 pub fn from_io_error(err: &IoError) -> RtObject {
1323 RtObject::string(err.to_string())
1324 }
1325 pub fn get_message(exception: &RtObject) -> Option<String> {
1327 crate::object::StringOps::as_str(exception)
1328 }
1329 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 pub fn ok(value: RtObject) -> RtObject {
1338 RtObject::constructor(0, vec![value])
1339 }
1340 pub fn error(message: String) -> RtObject {
1342 RtObject::constructor(1, vec![RtObject::string(message)])
1343 }
1344}
1345#[derive(Clone, Debug)]
1347pub enum IoValue {
1348 Pure(RtObject),
1350 Error(IoError),
1352 Unit,
1354}
1355impl IoValue {
1356 pub fn pure_val(obj: RtObject) -> Self {
1358 IoValue::Pure(obj)
1359 }
1360 pub fn error(err: IoError) -> Self {
1362 IoValue::Error(err)
1363 }
1364 pub fn unit() -> Self {
1366 IoValue::Unit
1367 }
1368 pub fn is_error(&self) -> bool {
1370 matches!(self, IoValue::Error(_))
1371 }
1372 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 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}
1389pub struct StringRtOps;
1391impl StringRtOps {
1392 pub fn concat(a: &RtObject, b: &RtObject) -> Option<RtObject> {
1394 crate::object::StringOps::concat(a, b)
1395 }
1396 pub fn length(obj: &RtObject) -> Option<RtObject> {
1398 crate::object::StringOps::byte_len(obj).map(|n| RtObject::nat(n as u64))
1399 }
1400 pub fn nat_repr(obj: &RtObject) -> Option<RtObject> {
1402 crate::object::StringOps::nat_to_string(obj)
1403 }
1404 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 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#[derive(Clone, Debug, Default)]
1418pub struct IoStats {
1419 pub file_reads: u64,
1421 pub file_writes: u64,
1423 pub console_outputs: u64,
1425 pub console_inputs: u64,
1427 pub exceptions_thrown: u64,
1429 pub exceptions_caught: u64,
1431 pub refs_created: u64,
1433 pub ref_reads: u64,
1435 pub ref_writes: u64,
1437 pub bytes_read: u64,
1439 pub bytes_written: u64,
1441}
1442#[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 pub fn record_read(&mut self, n: u64) {
1459 self.reads += 1;
1460 self.bytes_read += n;
1461 }
1462 pub fn record_write(&mut self, n: u64) {
1464 self.writes += 1;
1465 self.bytes_written += n;
1466 }
1467 pub fn record_flush(&mut self) {
1469 self.flushes += 1;
1470 }
1471 pub fn record_error(&mut self) {
1473 self.errors += 1;
1474 }
1475 pub fn record_open(&mut self) {
1477 self.open_count += 1;
1478 }
1479 pub fn record_close(&mut self) {
1481 self.close_count += 1;
1482 }
1483 pub fn total_ops(&self) -> u64 {
1485 self.reads + self.writes + self.flushes
1486 }
1487 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#[derive(Clone, Debug)]
1499pub struct IoError {
1500 pub kind: IoErrorKind,
1502 pub message: String,
1504 pub path: Option<String>,
1506}
1507impl IoError {
1508 pub fn new(kind: IoErrorKind, message: impl Into<String>) -> Self {
1510 IoError {
1511 kind,
1512 message: message.into(),
1513 path: None,
1514 }
1515 }
1516 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 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 pub fn user_error(message: impl Into<String>) -> Self {
1539 IoError::new(IoErrorKind::UserError, message)
1540 }
1541 pub fn internal(message: impl Into<String>) -> Self {
1543 IoError::new(IoErrorKind::InternalError, message)
1544 }
1545 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 pub fn to_rt_object(&self) -> RtObject {
1561 RtObject::string(self.message.clone())
1562 }
1563}