use std::io::{self, BufRead, Read, Write};
use std::env;
use grift_core::{IoErrorKind, IoProvider, IoResult, PortId};
const DYNAMIC_PORT_BASE: usize = 3;
const MAX_DYNAMIC_PORTS: usize = 64;
enum DynPort {
InputString { data: Vec<char>, cursor: usize, closed: bool },
OutputString { buf: String, closed: bool },
}
pub struct StdIoProvider {
peeked: Option<char>,
ports: Vec<Option<DynPort>>,
command_line_args: Vec<String>,
env_vars: Vec<(String, String)>,
env_var_buf: Option<String>,
file_buf: String,
}
impl StdIoProvider {
pub fn new() -> Self {
StdIoProvider {
peeked: None,
ports: Vec::new(),
command_line_args: env::args().collect(),
env_vars: Vec::new(),
env_var_buf: None,
file_buf: String::new(),
}
}
fn alloc_port(&mut self, port: DynPort) -> IoResult<PortId> {
for (i, slot) in self.ports.iter_mut().enumerate() {
if slot.is_none() {
*slot = Some(port);
return Ok(PortId(DYNAMIC_PORT_BASE + i));
}
}
if self.ports.len() >= MAX_DYNAMIC_PORTS {
return Err(IoErrorKind::Unsupported);
}
let id = DYNAMIC_PORT_BASE + self.ports.len();
self.ports.push(Some(port));
Ok(PortId(id))
}
fn get_dyn(&self, port: PortId) -> Option<&DynPort> {
if port.0 < DYNAMIC_PORT_BASE { return None; }
let idx = port.0 - DYNAMIC_PORT_BASE;
self.ports.get(idx).and_then(|s| s.as_ref())
}
fn get_dyn_mut(&mut self, port: PortId) -> Option<&mut DynPort> {
if port.0 < DYNAMIC_PORT_BASE { return None; }
let idx = port.0 - DYNAMIC_PORT_BASE;
self.ports.get_mut(idx).and_then(|s| s.as_mut())
}
}
impl Default for StdIoProvider {
fn default() -> Self {
Self::new()
}
}
fn read_one_char() -> IoResult<char> {
let stdin = io::stdin();
let mut handle = stdin.lock();
let mut buf = [0u8; 4];
let first = {
let n = handle.read(&mut buf[..1]).map_err(|_| IoErrorKind::ReadFailed)?;
if n == 0 {
return Err(IoErrorKind::Eof);
}
buf[0]
};
let char_len = if first < 0x80 {
1
} else if first >= 0xC2 && first < 0xE0 {
2
} else if first >= 0xE0 && first < 0xF0 {
3
} else if first >= 0xF0 && first < 0xF5 {
4
} else {
return Err(IoErrorKind::ReadFailed);
};
if char_len > 1 {
let remaining = &mut buf[1..char_len];
handle
.read_exact(remaining)
.map_err(|_| IoErrorKind::ReadFailed)?;
}
core::str::from_utf8(&buf[..char_len])
.ok()
.and_then(|s| s.chars().next())
.ok_or(IoErrorKind::ReadFailed)
}
impl IoProvider for StdIoProvider {
fn read_char(&mut self, port: PortId) -> IoResult<char> {
if port == PortId::STDIN {
if let Some(c) = self.peeked.take() {
return Ok(c);
}
return read_one_char();
}
match self.get_dyn_mut(port) {
Some(DynPort::InputString { data, cursor, closed }) => {
if *closed { return Err(IoErrorKind::PortClosed); }
if *cursor >= data.len() { return Err(IoErrorKind::Eof); }
let c = data[*cursor];
*cursor += 1;
Ok(c)
}
_ => Err(IoErrorKind::InvalidPort),
}
}
fn peek_char(&mut self, port: PortId) -> IoResult<char> {
if port == PortId::STDIN {
if let Some(c) = self.peeked {
return Ok(c);
}
let c = read_one_char()?;
self.peeked = Some(c);
return Ok(c);
}
match self.get_dyn(port) {
Some(DynPort::InputString { data, cursor, closed }) => {
if *closed { return Err(IoErrorKind::PortClosed); }
if *cursor >= data.len() { return Err(IoErrorKind::Eof); }
Ok(data[*cursor])
}
_ => Err(IoErrorKind::InvalidPort),
}
}
fn char_ready(&mut self, port: PortId) -> IoResult<bool> {
if port == PortId::STDIN {
if self.peeked.is_some() {
return Ok(true);
}
let stdin = io::stdin();
let mut handle = stdin.lock();
return Ok(!handle.fill_buf().map_or(true, |b| b.is_empty()));
}
match self.get_dyn(port) {
Some(DynPort::InputString { data, cursor, closed }) => {
Ok(!closed && *cursor < data.len())
}
_ => Err(IoErrorKind::InvalidPort),
}
}
fn write_char(&mut self, port: PortId, c: char) -> IoResult<()> {
let mut buf = [0u8; 4];
let encoded = c.encode_utf8(&mut buf);
self.write_str(port, encoded)
}
fn write_str(&mut self, port: PortId, s: &str) -> IoResult<()> {
match port {
PortId::STDOUT => io::stdout()
.write_all(s.as_bytes())
.map_err(|_| IoErrorKind::WriteFailed),
PortId::STDERR => io::stderr()
.write_all(s.as_bytes())
.map_err(|_| IoErrorKind::WriteFailed),
_ => {
match self.get_dyn_mut(port) {
Some(DynPort::OutputString { buf, closed }) => {
if *closed { return Err(IoErrorKind::PortClosed); }
buf.push_str(s);
Ok(())
}
_ => Err(IoErrorKind::InvalidPort),
}
}
}
}
fn flush(&mut self, port: PortId) -> IoResult<()> {
match port {
PortId::STDOUT => io::stdout().flush().map_err(|_| IoErrorKind::WriteFailed),
PortId::STDERR => io::stderr().flush().map_err(|_| IoErrorKind::WriteFailed),
_ => Ok(()), }
}
fn close_port(&mut self, port: PortId) -> IoResult<()> {
if port.0 < DYNAMIC_PORT_BASE {
return Err(IoErrorKind::Unsupported); }
let idx = port.0 - DYNAMIC_PORT_BASE;
match self.ports.get_mut(idx) {
Some(Some(DynPort::InputString { closed, .. })) => { *closed = true; Ok(()) }
Some(Some(DynPort::OutputString { closed, .. })) => { *closed = true; Ok(()) }
_ => Err(IoErrorKind::InvalidPort),
}
}
fn is_input_port(&self, port: PortId) -> bool {
if port == PortId::STDIN { return true; }
matches!(self.get_dyn(port), Some(DynPort::InputString { .. }))
}
fn is_output_port(&self, port: PortId) -> bool {
if port == PortId::STDOUT || port == PortId::STDERR { return true; }
matches!(self.get_dyn(port), Some(DynPort::OutputString { .. }))
}
fn is_port_open(&self, port: PortId) -> bool {
if port.0 < DYNAMIC_PORT_BASE { return true; }
match self.get_dyn(port) {
Some(DynPort::InputString { closed, .. }) => !closed,
Some(DynPort::OutputString { closed, .. }) => !closed,
None => false,
}
}
fn open_input_string(&mut self, s: &str) -> IoResult<PortId> {
let data: Vec<char> = s.chars().collect();
self.alloc_port(DynPort::InputString { data, cursor: 0, closed: false })
}
fn open_output_string(&mut self) -> IoResult<PortId> {
self.alloc_port(DynPort::OutputString { buf: String::new(), closed: false })
}
fn get_output_string(&self, port: PortId) -> IoResult<&str> {
match self.get_dyn(port) {
Some(DynPort::OutputString { buf, .. }) => Ok(buf.as_str()),
_ => Err(IoErrorKind::InvalidPort),
}
}
fn file_exists(&self, path: &str) -> IoResult<bool> {
Ok(std::path::Path::new(path).exists())
}
fn delete_file(&mut self, path: &str) -> IoResult<()> {
std::fs::remove_file(path).map_err(|_| IoErrorKind::WriteFailed)
}
fn read_file(&mut self, path: &str) -> IoResult<&str> {
match std::fs::read_to_string(path) {
Ok(content) => {
self.file_buf = content;
Ok(self.file_buf.as_str())
}
Err(_) => Err(IoErrorKind::ReadFailed),
}
}
fn command_line_count(&self) -> IoResult<usize> {
Ok(self.command_line_args.len())
}
fn command_line_arg(&self, index: usize) -> IoResult<&str> {
self.command_line_args.get(index)
.map(|s| s.as_str())
.ok_or(IoErrorKind::ReadFailed)
}
fn get_environment_variable(&mut self, name: &str) -> IoResult<Option<&str>> {
match env::var(name) {
Ok(val) => {
self.env_var_buf = Some(val);
Ok(self.env_var_buf.as_deref())
}
Err(_) => Ok(None),
}
}
fn environment_variables_count(&mut self) -> IoResult<usize> {
self.env_vars = env::vars().collect();
Ok(self.env_vars.len())
}
fn environment_variable_at(&self, index: usize) -> IoResult<(&str, &str)> {
self.env_vars.get(index)
.map(|(k, v)| (k.as_str(), v.as_str()))
.ok_or(IoErrorKind::ReadFailed)
}
fn exit_process(&mut self, code: i32) -> IoResult<()> {
std::process::exit(code);
}
fn emergency_exit_process(&mut self, code: i32) -> IoResult<()> {
let _ = code; std::process::abort();
}
}