Implemented host interface

trunk
ondra05 2022-07-02 00:17:29 +02:00
parent 32072e9dea
commit 43439b7e58
6 changed files with 105 additions and 42 deletions

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ use rustyline::Editor;
pub fn repl(ast_print: bool) {
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
// incomplete statement and is now completing it; otherwise, the