(stub) src/vm/terminal_io: make KeyboardIO trait for generic VM I/O
This commit is contained in:
parent
04fa5454ef
commit
25a77ecfb7
@ -9,9 +9,9 @@ use std::env;
|
|||||||
fn main() {
|
fn main() {
|
||||||
let args: Vec<_> = env::args().collect();
|
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.read_program(args.get(1).expect("No program file given"));
|
||||||
vm.execute();
|
vm.execute();
|
||||||
}
|
}
|
||||||
|
@ -111,18 +111,25 @@ impl Registers {
|
|||||||
// VM interface
|
// 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,
|
mem: memory::Memory,
|
||||||
registers: Registers,
|
registers: Registers,
|
||||||
|
io: &'a dyn terminal_io::KeyboardIO,
|
||||||
running: bool,
|
running: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VM {
|
impl VM<'_> {
|
||||||
pub fn new() -> VM {
|
pub fn new(keyboard_io: &impl terminal_io::KeyboardIO) -> VM {
|
||||||
VM {
|
VM {
|
||||||
mem: memory::Memory::new(),
|
mem: memory::Memory::new(),
|
||||||
registers: Registers::new(),
|
registers: Registers::new(),
|
||||||
running: false,
|
running: false,
|
||||||
|
io: keyboard_io
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,8 +10,70 @@ use libc::STDIN_FILENO;
|
|||||||
|
|
||||||
extern crate ctrlc;
|
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<u8>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TerminalIO {
|
||||||
|
stdin_channel: Receiver<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TerminalIO {
|
||||||
|
pub fn new() -> TerminalIO {
|
||||||
|
setup_termios();
|
||||||
|
TerminalIO {
|
||||||
|
stdin_channel: Self::spawn_stdin_channel(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_stdin_channel() -> Receiver<u8> {
|
||||||
|
// https://stackoverflow.com/questions/30012995
|
||||||
|
let (tx, rx) = mpsc::channel::<u8>();
|
||||||
|
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<u8> {
|
||||||
|
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)
|
/// 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();
|
let mut term: Termios = Termios::from_fd(STDIN_FILENO).unwrap();
|
||||||
// ICANON (canonical) is line-by-line input (i.e. press enter to send)
|
// ICANON (canonical) is line-by-line input (i.e. press enter to send)
|
||||||
// ECHO is showing the characters you type
|
// ECHO is showing the characters you type
|
||||||
|
Loading…
Reference in New Issue
Block a user