#[macro_use] extern crate log; use crsn::asm::data::literal::Value; use crsn::asm::error::CrsnError; use crsn::asm::instr::op::OpKind; use crsn::asm::parse::arg_parser::TokenParser; use crsn::module::{CrsnExtension, ParseRes, CrsnUniq}; use crsn::runtime::fault::Fault; use crsn::runtime::run_thread::{RunState}; use crsn::sexp::SourcePosition; use std::convert::TryFrom; use std::io; use crsn::asm::instr::cond::Flag; use std::fmt; use crsn::asm::data::Wr; mod console { use std::{io}; use std::os::unix::io::RawFd; use std::ffi::c_void; use std::mem::{self, MaybeUninit}; use crsn::runtime::fault::Fault; use std::time::{Instant}; struct ReadCharState { bytes: [u8; 4], cursor: usize, len: usize, last_timeout : u8, } static mut READ_CHAR_STATE: ReadCharState = ReadCharState { bytes: [0; 4], cursor: 0, len: 0, last_timeout : 0, }; fn setup_fd(fd: RawFd) -> Result { use libc::*; let mut tio = MaybeUninit::uninit(); if 0 != unsafe { tcgetattr(fd, tio.as_mut_ptr()) } { return Err(Fault::IOError(io::Error::last_os_error())); } let mut tio = unsafe { MaybeUninit::assume_init(tio) }; let old_tio : termios = unsafe { mem::transmute_copy(&tio) }; tio.c_iflag &= !(/*BRKINT |*/ /*ICRNL |*/ INPCK | ISTRIP | IXON); tio.c_oflag |= ONLCR; tio.c_cflag |= CS8; tio.c_lflag &= !(ECHO | ICANON | IEXTEN /*| ISIG*/); tio.c_cc[VMIN] = 1; tio.c_cc[VTIME] = 0; if 0 != unsafe { tcsetattr(fd, TCSANOW, &tio) } { return Err(Fault::IOError(io::Error::last_os_error())); } Ok(old_tio) } pub fn init_io() -> Result { setup_fd(libc::STDIN_FILENO) } pub fn read_byte(deadline : Option) -> Result { // Set TIO timeout let state = unsafe { &mut READ_CHAR_STATE }; if (state.last_timeout == 0) && deadline.is_none() { // Keep it like that } else { let vtime = if let Some(dl) = deadline { let timeout = dl.saturating_duration_since(Instant::now()); ((timeout.as_secs_f32() * 10.0).round() as u32).min(255).max(1) as u8 } else { 0 }; if state.last_timeout != vtime { // vtime changes state.last_timeout = vtime; let mut tio = MaybeUninit::uninit(); if 0 != unsafe { libc::tcgetattr(libc::STDIN_FILENO, tio.as_mut_ptr()) } { return Err(Fault::IOError(io::Error::last_os_error())); } let mut tio = unsafe { MaybeUninit::assume_init(tio) }; if vtime > 0 { tio.c_cc[libc::VTIME] = vtime; /* unit = 0.1 */ tio.c_cc[libc::VMIN] = 0; } else { tio.c_cc[libc::VTIME] = 0; // no counting tio.c_cc[libc::VMIN] = 1; // want at least one character } unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &tio) }; } } let mut buf = 0u8; let len = unsafe { libc::read(libc::STDIN_FILENO, &mut buf as *mut u8 as *mut c_void, 1) }; if len == 0 && state.last_timeout != 0 { return Err(Fault::IOError(io::Error::new(std::io::ErrorKind::TimedOut, ""))); } if len <= 0 { Err(Fault::IOError(io::Error::last_os_error())) } else { Ok(buf as u8) } } pub fn write_byte(b : u8) -> Result<(), Fault> { let len = unsafe { libc::write(libc::STDOUT_FILENO, &b as *const u8 as *const c_void, 1) }; if len <= 0 { Err(Fault::IOError(io::Error::last_os_error())) } else { Ok(()) } } pub fn write_char(c : char) -> Result<(), Fault> { let mut buf = [0u8; 4]; for b in c.encode_utf8(&mut buf).as_bytes() { write_byte(*b)?; } Ok(()) } pub fn read_char(deadline : Option) -> Result { let state = unsafe { &mut READ_CHAR_STATE }; if state.cursor == 0 { let first = read_byte(deadline)?; if first & 0x80 == 0 { return Ok(first as char); } state.bytes[0] = first; state.cursor = 1; state.len = if first & 0b1110_0000 == 0b1100_0000 { 2 } else if first & 0b1111_0000 == 0b1110_0000 { 3 } else /*if first & 0b1111_1000 == 0b1111_0000*/ { 4 }; } let len = state.len; while state.cursor < len { let b = read_byte(deadline)?; state.bytes[state.cursor] = b; state.cursor += 1; } let rv = std::str::from_utf8(&state.bytes[..=len]) .map(|s| s.chars().nth(0).unwrap()) .map_err(|e| Fault::IOError(io::Error::new(io::ErrorKind::InvalidData, e))); state.cursor = 0; state.len = 0; rv } } #[derive(Clone)] pub struct StdioOps { old_tio: Option, hdl_stdin : Value, hdl_stdin_raw : Value, hdl_stdout : Value, hdl_stdout_raw : Value, } impl fmt::Debug for StdioOps { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("StdioOps") .field("hdl_stdin", &format_args!("0x{:08x}", self.hdl_stdin)) .field("hdl_stdin_raw", &format_args!("0x{:08x}", self.hdl_stdin_raw)) .field("hdl_stdout", &format_args!("0x{:08x}", self.hdl_stdout)) .field("hdl_stdout_raw", &format_args!("0x{:08x}", self.hdl_stdout_raw)) .finish() } } impl StdioOps { pub fn new() -> Box { Box::new(Self { old_tio: None, hdl_stdin: 0, hdl_stdin_raw: 0, hdl_stdout: 0, hdl_stdout_raw: 0, }) } } impl Drop for StdioOps { fn drop(&mut self) { debug!("stdin restore"); // Un-break the terminal if let Some(tio) = self.old_tio.take() { let _ = unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &tio) }; } } } impl CrsnExtension for StdioOps { fn init(&mut self, uniq: &CrsnUniq) { self.hdl_stdin = uniq.unique_handle(); self.hdl_stdin_raw = uniq.unique_handle(); self.hdl_stdout = uniq.unique_handle(); self.hdl_stdout_raw = uniq.unique_handle(); // This can fail if the input is not a tty if let Ok(tio) = console::init_io() { self.old_tio = Some(tio); } } /// Get value of an extension-provided constant. /// This constant may be an object handle, or a constant value used as argument in some other instruction. fn get_constant_value<'a>(&self, name: &str) -> Option { match name { "cin" => Some(self.hdl_stdin), "cin_r" => Some(self.hdl_stdin_raw), "cout" => Some(self.hdl_stdout), "cout_r" => Some(self.hdl_stdout_raw), _ => None } } fn name(&self) -> &'static str { "stdio" } fn parse_op<'a>(&self, _pos: &SourcePosition, _keyword: &str, args: TokenParser<'a>) -> Result, CrsnError> { Ok(ParseRes::Unknown(args)) } /// Run-time method called to read an object (using the object handle syntax) fn read_obj(&self, state: &mut RunState, handle: Value) -> Result, Fault> { let deadline = state.cr_deadline; if handle == self.hdl_stdin { match console::read_char(deadline) { Ok(c) => { return Ok(Some(c as Value)); } Err(Fault::IOError(e)) => { if e.kind() == io::ErrorKind::TimedOut { return Err(Fault::Blocked); } state.set_flag(Flag::Invalid, true); if e.kind() != io::ErrorKind::InvalidData { state.set_flag(Flag::Eof, true); } return Ok(Some(0)); } Err(other) => { return Err(other); } } } else if handle == self.hdl_stdin_raw { match console::read_byte(deadline) { Ok(b) => { return Ok(Some(b as Value)); } Err(Fault::IOError(e)) => { if e.kind() == io::ErrorKind::TimedOut { return Err(Fault::Blocked); } state.set_flag(Flag::Invalid, true); state.set_flag(Flag::Eof, true); return Ok(Some(0)); } Err(other) => { return Err(other); } } } else if handle == self.hdl_stdout || handle == self.hdl_stdout_raw { state.set_flag(Flag::Invalid, true); return Ok(Some(0)); } Ok(None) } /// Run-time method called to write an object (using the object handle syntax) fn write_obj(&self, state: &mut RunState, handle: Value, value: Value) -> Result, Fault> { state.clear_status(); if handle == self.hdl_stdout { if let Ok(a_char) = char::try_from((value & 0xFFFF_FFFF) as u32) { if console::write_char(a_char).is_err() { state.set_flag(Flag::Eof, true); } } else { state.set_flag(Flag::Invalid, true); } return Ok(Some(())); } if handle == self.hdl_stdout_raw { if console::write_byte((value & 0xFF) as u8).is_err() { state.set_flag(Flag::Eof, true); } return Ok(Some(())); } if handle == self.hdl_stdin || handle == self.hdl_stdin_raw { state.set_flag(Flag::Invalid, true); return Ok(Some(())); } Ok(None) } /// Run-time method called to read all values from an object ("lds" using the object handle syntax) fn read_obj_all(&self, state: &mut RunState, whandle: Wr, rhandle: Value) -> Result, Fault> { // XXX This is blocking, there is no sensible way to split it up. if rhandle == self.hdl_stdin { loop { match console::read_char(None) { Ok(c) => { state.write(whandle, c as Value)?; } Err(Fault::IOError(e)) => { if e.kind() != io::ErrorKind::InvalidData { state.set_flag(Flag::Eof, true); } else { state.set_flag(Flag::Invalid, true); } return Ok(Some(())); } Err(other) => { return Err(other); } } } } if rhandle == self.hdl_stdin_raw { loop { match console::read_byte(None) { Ok(c) => { state.write(whandle, c as Value)?; } Err(Fault::IOError(e)) => { if e.kind() != io::ErrorKind::InvalidData { state.set_flag(Flag::Eof, true); } else { state.set_flag(Flag::Invalid, true); } return Ok(Some(())); } Err(other) => { return Err(other); } } } } if rhandle == self.hdl_stdout || rhandle == self.hdl_stdout_raw { state.set_flag(Flag::Invalid, true); return Ok(Some(())); } Ok(None) } }