From 25a77ecfb74d7e5c3fad405da758a206f57fa99d Mon Sep 17 00:00:00 2001 From: dogeystamp Date: Sat, 6 Jan 2024 22:25:42 -0500 Subject: [PATCH] (stub) src/vm/terminal_io: make KeyboardIO trait for generic VM I/O --- src/main.rs | 4 +-- src/vm/mod.rs | 13 +++++++-- src/vm/terminal_io.rs | 64 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 435545a..f7ab8af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,9 +9,9 @@ use std::env; fn main() { let args: Vec<_> = env::args().collect(); - terminal_io::setup_terminal(); + let term = terminal_io::TerminalIO::new(); - let mut vm = VM::new(); + let mut vm = VM::new(&term); vm.read_program(args.get(1).expect("No program file given")); vm.execute(); } diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 80fa6d8..6b4d65e 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -111,18 +111,25 @@ impl Registers { // VM interface //////////////// -pub struct VM { +// NOTE +// https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html +// tl;dr the 'a is like a generic type name except it means that for some "lifetime" 'a, we will +// hold a reference to `io` and we promise not to have it outlive the struct +// this helps prevent dangling references +pub struct VM<'a> { mem: memory::Memory, registers: Registers, + io: &'a dyn terminal_io::KeyboardIO, running: bool, } -impl VM { - pub fn new() -> VM { +impl VM<'_> { + pub fn new(keyboard_io: &impl terminal_io::KeyboardIO) -> VM { VM { mem: memory::Memory::new(), registers: Registers::new(), running: false, + io: keyboard_io } } diff --git a/src/vm/terminal_io.rs b/src/vm/terminal_io.rs index ca1b82d..82462b8 100644 --- a/src/vm/terminal_io.rs +++ b/src/vm/terminal_io.rs @@ -10,8 +10,70 @@ use libc::STDIN_FILENO; extern crate ctrlc; +use std::io; +use std::io::BufRead; +use std::sync::mpsc; +use std::sync::mpsc::Receiver; +use std::thread; + +//////////////// +// keyboard I/O interface +//////////////// + +pub trait KeyboardIO { + /// Poll stdin for a keypress + fn get_key(&mut self) -> Option; +} + +pub struct TerminalIO { + stdin_channel: Receiver, +} + +impl TerminalIO { + pub fn new() -> TerminalIO { + setup_termios(); + TerminalIO { + stdin_channel: Self::spawn_stdin_channel(), + } + } + + fn spawn_stdin_channel() -> Receiver { + // https://stackoverflow.com/questions/30012995 + let (tx, rx) = mpsc::channel::(); + let mut buffer = Vec::new(); + thread::spawn(move || loop { + buffer.clear(); + let _ = io::stdin().lock().read_until(1, &mut buffer); + for c in &buffer { + let _ = tx.send(*c); + } + }); + rx + } +} + +impl Drop for TerminalIO { + fn drop(&mut self) { + restore_terminal(); + } +} + +impl KeyboardIO for TerminalIO { + fn get_key(&mut self) -> Option { + match self.stdin_channel.try_recv() { + Ok(key) => return Some(key), + Err(mpsc::TryRecvError::Empty) => return None, + Err(mpsc::TryRecvError::Disconnected) => panic!("terminal keyboard stream broke"), + } + } +} + +//////////////// +// termios stuff +//////////////// + /// Configure raw input (see termios(3) man-page) -pub fn setup_terminal() { +fn setup_termios() { let mut term: Termios = Termios::from_fd(STDIN_FILENO).unwrap(); // ICANON (canonical) is line-by-line input (i.e. press enter to send) // ECHO is showing the characters you type