Implemented host interface

This commit is contained in:
Erin 2022-07-02 00:17:29 +02:00 committed by ondra05
parent 2de2eb8914
commit 59097260de
6 changed files with 105 additions and 42 deletions

View file

@ -14,6 +14,7 @@ pub enum ErrorKind {
UnknownVariable(String), UnknownVariable(String),
MeloVariable(String), MeloVariable(String),
TopLevelEnough, TopLevelEnough,
NonExitingRlyeh(i32),
MissingLhs, MissingLhs,
Brian(InterpretError), Brian(InterpretError),
Io(io::Error), Io(io::Error),
@ -51,6 +52,7 @@ impl Display for ErrorKind {
ErrorKind::UnknownVariable(name) => write!(f, "unknown identifier \"{}\"", name), ErrorKind::UnknownVariable(name) => write!(f, "unknown identifier \"{}\"", name),
ErrorKind::MeloVariable(name) => write!(f, "banned variable \"{}\"", name), ErrorKind::MeloVariable(name) => write!(f, "banned variable \"{}\"", name),
ErrorKind::TopLevelEnough => write!(f, "can only `enough` out of a loop"), ErrorKind::TopLevelEnough => write!(f, "can only `enough` out of a loop"),
&ErrorKind::NonExitingRlyeh(code) => write!(f, "program exited with code {code}"),
ErrorKind::Brian(err) => write!(f, "brainfuck error: {}", err), ErrorKind::Brian(err) => write!(f, "brainfuck error: {}", err),
// TODO: give concrete numbers here. // TODO: give concrete numbers here.
ErrorKind::MissingLhs => write!(f, "missing expression before binary operation"), ErrorKind::MissingLhs => write!(f, "missing expression before binary operation"),

View file

@ -0,0 +1,47 @@
/// Host Environment Interface
pub trait HostInterface {
type Data;
/// Print a string
fn print(&mut self, string: &str, new_line: bool) -> std::io::Result<()>;
/// Read a byte
fn read_byte(&mut self) -> std::io::Result<u8>;
/// This function should exit the program with specified code.
///
/// For cases where exit is not desired, just let the function return
/// and interpreter will terminate with an error.
fn exit(&mut self, code: i32);
}
/// Standard [HostInterface] implementation
#[derive(Clone, Copy, Default)]
pub struct Standard;
impl HostInterface for Standard {
type Data = ();
fn print(&mut self, string: &str, new_line: bool) -> std::io::Result<()> {
use std::io::Write;
let mut stdout = std::io::stdout();
stdout.write_all(string.as_bytes())?;
if new_line {
stdout.write_all(b"\n")?;
}
Ok(())
}
fn read_byte(&mut self) -> std::io::Result<u8> {
use std::io::Read;
let mut buf = [0];
std::io::stdin().read_exact(&mut buf)?;
Ok(buf[0])
}
fn exit(&mut self, code: i32) {
std::process::exit(code);
}
}

View file

@ -12,20 +12,19 @@ use crate::{
ast::{Assignable, AssignableKind, Block, Expr, Spanned, Stmt}, ast::{Assignable, AssignableKind, Block, Expr, Spanned, Stmt},
consts::ablescript_consts, consts::ablescript_consts,
error::{Error, ErrorKind}, error::{Error, ErrorKind},
host_interface::HostInterface,
value::{Functio, Value, ValueRef, Variable}, value::{Functio, Value, ValueRef, Variable},
}; };
use rand::random; use rand::random;
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
io::{stdin, stdout, Read, Write},
mem::take, mem::take,
ops::Range, ops::Range,
process::exit,
}; };
/// An environment for executing AbleScript code. /// An environment for executing AbleScript code.
pub struct ExecEnv { pub struct ExecEnv<H> {
/// The stack, ordered such that `stack[stack.len() - 1]` is the /// The stack, ordered such that `stack[stack.len() - 1]` is the
/// top-most (newest) stack frame, and `stack[0]` is the /// top-most (newest) stack frame, and `stack[0]` is the
/// bottom-most (oldest) stack frame. /// bottom-most (oldest) stack frame.
@ -38,6 +37,9 @@ pub struct ExecEnv {
/// booleans to facilitate easy manipulation. /// booleans to facilitate easy manipulation.
read_buf: VecDeque<bool>, read_buf: VecDeque<bool>,
/// Interface to interact with the host interface
host_interface: H,
/// Vector of blocks to be executed at the end of the program /// Vector of blocks to be executed at the end of the program
finalisers: Vec<Block>, finalisers: Vec<Block>,
} }
@ -49,13 +51,12 @@ struct Scope {
variables: HashMap<String, Variable>, variables: HashMap<String, Variable>,
} }
impl Default for ExecEnv { impl<H> Default for ExecEnv<H>
where
H: Default + HostInterface,
{
fn default() -> Self { fn default() -> Self {
Self { Self::with_host_interface(H::default())
stack: vec![Default::default()],
read_buf: Default::default(),
finalisers: vec![],
}
} }
} }
@ -85,15 +86,20 @@ enum HaltStatus {
/// standard input. /// standard input.
pub const READ_BITS: u8 = 3; pub const READ_BITS: u8 = 3;
impl ExecEnv { impl<H: HostInterface> ExecEnv<H> {
/// Create a new Scope with no predefined variable definitions or /// Create a new Scope with no predefined variable definitions or
/// other information. /// other information.
pub fn new() -> Self { pub fn with_host_interface(host_interface: H) -> Self {
Self::default() Self {
stack: vec![Default::default()],
read_buf: Default::default(),
finalisers: vec![],
host_interface,
}
} }
/// Create a new Scope with predefined variables /// Create a new Scope with predefined variables
pub fn new_with_vars<I>(vars: I) -> Self pub fn new_with_vars<I>(host_interface: H, vars: I) -> Self
where where
I: IntoIterator<Item = (String, Variable)>, I: IntoIterator<Item = (String, Variable)>,
{ {
@ -103,7 +109,9 @@ impl ExecEnv {
Self { Self {
stack: vec![scope], stack: vec![scope],
..Default::default() read_buf: Default::default(),
finalisers: vec![],
host_interface,
} }
} }
@ -226,15 +234,9 @@ impl ExecEnv {
match &stmt.item { match &stmt.item {
Stmt::Print { expr, newline } => { Stmt::Print { expr, newline } => {
let value = self.eval_expr(expr)?; let value = self.eval_expr(expr)?;
if *newline { self.host_interface
println!("{value}"); .print(&value.to_string(), *newline)
} else { .map_err(|e| Error::new(e.into(), stmt.span.clone()))?;
print!("{value}");
stdout()
.lock()
.flush()
.map_err(|e| Error::new(e.into(), stmt.span.clone()))?;
}
} }
Stmt::Dim { ident, init } => { Stmt::Dim { ident, init } => {
let init = match init { let init = match init {
@ -315,12 +317,17 @@ impl ExecEnv {
Stmt::Rlyeh => { Stmt::Rlyeh => {
// Maybe print a creepy error message or something // Maybe print a creepy error message or something
// here at some point. ~~Alex // here at some point. ~~Alex
exit(random()); let code = random();
self.host_interface.exit(code);
return Err(Error::new(
ErrorKind::NonExitingRlyeh(code),
stmt.span.clone(),
));
} }
Stmt::Rickroll => { Stmt::Rickroll => {
stdout() self.host_interface
.write_all(include_str!("rickroll").as_bytes()) .print(include_str!("rickroll"), false)
.expect("Failed to write to stdout"); .map_err(|e| Error::new(e.into(), stmt.span.clone()))?;
} }
Stmt::Read(assignable) => { Stmt::Read(assignable) => {
let mut value = 0; let mut value = 0;
@ -442,9 +449,13 @@ impl ExecEnv {
span: span.to_owned(), span: span.to_owned(),
})?; })?;
stdout() match String::from_utf8(output) {
.write_all(&output) Ok(string) => self.host_interface.print(&string, false),
.expect("Failed to write to stdout"); Err(e) => self
.host_interface
.print(&format!("{:?}", e.as_bytes()), true),
}
.map_err(|e| Error::new(e.into(), span.clone()))?;
Ok(HaltStatus::Finished) Ok(HaltStatus::Finished)
} }
@ -537,11 +548,10 @@ impl ExecEnv {
const BITS_PER_BYTE: u8 = 8; const BITS_PER_BYTE: u8 = 8;
if self.read_buf.is_empty() { if self.read_buf.is_empty() {
let mut data = [0]; let byte = self.host_interface.read_byte()?;
stdin().read_exact(&mut data)?;
for n in (0..BITS_PER_BYTE).rev() { for n in (0..BITS_PER_BYTE).rev() {
self.read_buf.push_back(((data[0] >> n) & 1) != 0); self.read_buf.push_back(((byte >> n) & 1) != 0);
} }
} }
@ -619,12 +629,15 @@ impl ExecEnv {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::ast::{Expr, Literal}; use crate::{
ast::{Expr, Literal},
host_interface::Standard,
};
#[test] #[test]
fn basic_expression_test() { fn basic_expression_test() {
// Check that 2 + 2 = 4. // Check that 2 + 2 = 4.
let env = ExecEnv::new(); let env = ExecEnv::<Standard>::default();
assert_eq!( assert_eq!(
env.eval_expr(&Spanned { env.eval_expr(&Spanned {
item: Expr::BinOp { item: Expr::BinOp {
@ -650,7 +663,7 @@ mod tests {
// The sum of an integer and an aboolean causes an aboolean // The sum of an integer and an aboolean causes an aboolean
// coercion. // coercion.
let env = ExecEnv::new(); let env = ExecEnv::<Standard>::default();
assert_eq!( assert_eq!(
env.eval_expr(&Spanned { env.eval_expr(&Spanned {
item: Expr::BinOp { item: Expr::BinOp {
@ -675,7 +688,7 @@ mod tests {
fn overflow_should_not_panic() { fn overflow_should_not_panic() {
// Integer overflow should throw a recoverable error instead // Integer overflow should throw a recoverable error instead
// of panicking. // of panicking.
let env = ExecEnv::new(); let env = ExecEnv::<Standard>::default();
assert_eq!( assert_eq!(
env.eval_expr(&Spanned { env.eval_expr(&Spanned {
item: Expr::BinOp { item: Expr::BinOp {
@ -719,7 +732,7 @@ mod tests {
// From here on out, I'll use this function to parse and run // From here on out, I'll use this function to parse and run
// expressions, because writing out abstract syntax trees by hand // expressions, because writing out abstract syntax trees by hand
// takes forever and is error-prone. // takes forever and is error-prone.
fn eval(env: &mut ExecEnv, src: &str) -> Result<Value, Error> { fn eval(env: &mut ExecEnv<Standard>, src: &str) -> Result<Value, Error> {
// We can assume there won't be any syntax errors in the // We can assume there won't be any syntax errors in the
// interpreter tests. // interpreter tests.
let ast = crate::parser::parse(src).unwrap(); let ast = crate::parser::parse(src).unwrap();
@ -731,7 +744,7 @@ mod tests {
// Functions have no return values, so use some // Functions have no return values, so use some
// pass-by-reference hacks to detect the correct // pass-by-reference hacks to detect the correct
// functionality. // functionality.
let mut env = ExecEnv::new(); let mut env = ExecEnv::<Standard>::default();
// Declaring and reading from a variable. // Declaring and reading from a variable.
eval(&mut env, "foo dim 32; bar dim foo + 1;").unwrap(); eval(&mut env, "foo dim 32; bar dim foo + 1;").unwrap();
@ -765,7 +778,7 @@ mod tests {
// Declaration and assignment of variables declared in an `if` // Declaration and assignment of variables declared in an `if`
// statement should have no effect on those declared outside // statement should have no effect on those declared outside
// of it. // of it.
let mut env = ExecEnv::new(); let mut env = ExecEnv::<Standard>::default();
eval( eval(
&mut env, &mut env,
"foo dim 1; 2 =: foo; unless (never) { foo dim 3; 4 =: foo; }", "foo dim 1; 2 =: foo; unless (never) { foo dim 3; 4 =: foo; }",

View file

@ -6,6 +6,7 @@
pub mod ast; pub mod ast;
pub mod error; pub mod error;
pub mod host_interface;
pub mod interpret; pub mod interpret;
pub mod parser; pub mod parser;
pub mod value; pub mod value;

View file

@ -46,7 +46,7 @@ fn main() {
if ast_print { if ast_print {
println!("{:#?}", ast); println!("{:#?}", ast);
} }
ExecEnv::new().eval_stmts(&ast) ExecEnv::<ablescript::host_interface::Standard>::default().eval_stmts(&ast)
}) { }) {
println!( println!(
"Error `{:?}` occurred at span: {:?} = `{:?}`", "Error `{:?}` occurred at span: {:?} = `{:?}`",

View file

@ -4,7 +4,7 @@ use rustyline::Editor;
pub fn repl(ast_print: bool) { pub fn repl(ast_print: bool) {
let mut rl = Editor::<()>::new(); let mut rl = Editor::<()>::new();
let mut env = ExecEnv::new(); let mut env = ExecEnv::<ablescript::host_interface::Standard>::default();
// If this is `Some`, the user has previously entered an // If this is `Some`, the user has previously entered an
// incomplete statement and is now completing it; otherwise, the // incomplete statement and is now completing it; otherwise, the