forked from AbleScript/ablescript
Implemented host interface
This commit is contained in:
parent
2de2eb8914
commit
59097260de
|
@ -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"),
|
||||
|
|
47
ablescript/src/host_interface.rs
Normal file
47
ablescript/src/host_interface.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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,16 +234,10 @@ 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()
|
||||
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 {
|
||||
Some(e) => self.eval_expr(e)?,
|
||||
|
@ -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; }",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
pub mod ast;
|
||||
pub mod error;
|
||||
pub mod host_interface;
|
||||
pub mod interpret;
|
||||
pub mod parser;
|
||||
pub mod value;
|
||||
|
|
|
@ -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: {:?} = `{:?}`",
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue