forked from AbleScript/ablescript
Implemented host interface
This commit is contained in:
parent
e883010580
commit
83b25b2b89
|
@ -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"),
|
||||||
|
|
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},
|
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,16 +234,10 @@ 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 {
|
|
||||||
print!("{value}");
|
|
||||||
stdout()
|
|
||||||
.lock()
|
|
||||||
.flush()
|
|
||||||
.map_err(|e| Error::new(e.into(), stmt.span.clone()))?;
|
.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 {
|
||||||
Some(e) => self.eval_expr(e)?,
|
Some(e) => self.eval_expr(e)?,
|
||||||
|
@ -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; }",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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: {:?} = `{:?}`",
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue