You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
390 lines
12 KiB
390 lines
12 KiB
#[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;
|
|
use std::time::Instant;
|
|
|
|
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::{Duration, 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<libc::termios, Fault> {
|
|
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<libc::termios, Fault> {
|
|
setup_fd(libc::STDIN_FILENO)
|
|
}
|
|
|
|
pub fn read_byte(deadline : Option<Instant>) -> Result<u8, Fault> {
|
|
// 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<Instant>) -> Result<char, Fault> {
|
|
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<libc::termios>,
|
|
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<dyn CrsnExtension> {
|
|
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<Value>
|
|
{
|
|
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<ParseRes<'a, OpKind>, 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<Option<Value>, 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<Option<()>, 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<Option<()>, 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)
|
|
}
|
|
}
|
|
|