use crate::ast::{ArgSep, Expr, Statement, Value, VarRef, VarType};
use crate::eval::{self, BuiltinFunction, CallableMetadata, CallableMetadataBuilder, Vars};
use crate::parser::{self, Parser};
use async_trait::async_trait;
use std::collections::HashMap;
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::rc::Rc;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{0}")]
EvalError(#[from] eval::Error),
#[error("{0}")]
IoError(#[from] io::Error),
#[error("{0}")]
ParseError(#[from] parser::Error),
#[error("{0}")]
SyntaxError(String),
#[error("{0}")]
UsageError(String),
}
pub type Result<T> = std::result::Result<T, Error>;
fn new_syntax_error<T, S: Into<String>>(message: S) -> Result<T> {
Err(Error::SyntaxError(message.into()))
}
pub fn new_usage_error<T, S: Into<String>>(message: S) -> Result<T> {
Err(Error::UsageError(message.into()))
}
#[async_trait(?Send)]
pub trait BuiltinCommand {
fn metadata(&self) -> &CallableMetadata;
async fn exec(&self, args: &[(Option<Expr>, ArgSep)], machine: &mut Machine) -> Result<()>;
}
#[derive(Default)]
pub struct MachineBuilder {
commands: HashMap<&'static str, Rc<dyn BuiltinCommand>>,
functions: HashMap<&'static str, Rc<dyn BuiltinFunction>>,
}
impl MachineBuilder {
pub fn add_command(mut self, command: Rc<dyn BuiltinCommand>) -> Self {
let metadata = command.metadata();
assert!(
self.commands.get(&metadata.name()).is_none(),
"Command with the same name already registered"
);
self.commands.insert(metadata.name(), command);
self
}
pub fn add_function(mut self, function: Rc<dyn BuiltinFunction>) -> Self {
let metadata = function.metadata();
assert!(
self.functions.get(&metadata.name()).is_none(),
"Function with the same name already registered"
);
self.functions.insert(metadata.name(), function);
self
}
pub fn add_commands(mut self, commands: Vec<Rc<dyn BuiltinCommand>>) -> Self {
for command in commands {
self = self.add_command(command);
}
self
}
pub fn add_functions(mut self, functions: Vec<Rc<dyn BuiltinFunction>>) -> Self {
for function in functions {
self = self.add_function(function);
}
self
}
pub fn build(self) -> Machine {
Machine { commands: self.commands, functions: self.functions, vars: Vars::default() }
}
}
#[derive(Default)]
pub struct Machine {
commands: HashMap<&'static str, Rc<dyn BuiltinCommand>>,
functions: HashMap<&'static str, Rc<dyn BuiltinFunction>>,
vars: Vars,
}
impl Machine {
pub fn clear(&mut self) {
self.vars.clear()
}
pub fn get_commands(&self) -> &HashMap<&'static str, Rc<dyn BuiltinCommand>> {
&self.commands
}
pub fn get_functions(&self) -> &HashMap<&'static str, Rc<dyn BuiltinFunction>> {
&self.functions
}
pub fn get_vars(&self) -> &Vars {
&self.vars
}
pub fn get_mut_vars(&mut self) -> &mut Vars {
&mut self.vars
}
pub fn get_var_as_bool(&self, name: &str) -> Result<bool> {
match self.vars.get(&VarRef::new(name, VarType::Boolean))? {
Value::Boolean(b) => Ok(*b),
_ => panic!("Invalid type check in get()"),
}
}
pub fn get_var_as_int(&self, name: &str) -> Result<i32> {
match self.vars.get(&VarRef::new(name, VarType::Integer))? {
Value::Integer(i) => Ok(*i),
_ => panic!("Invalid type check in get()"),
}
}
pub fn get_var_as_string(&self, name: &str) -> Result<&str> {
match self.vars.get(&VarRef::new(name, VarType::Text))? {
Value::Text(s) => Ok(s),
_ => panic!("Invalid type check in get()"),
}
}
fn assign(&mut self, vref: &VarRef, expr: &Expr) -> Result<()> {
let value = expr.eval(&self.vars, &self.functions)?;
self.vars.set(&vref, value)?;
Ok(())
}
async fn do_if(&mut self, branches: &[(Expr, Vec<Statement>)]) -> Result<()> {
for (expr, stmts) in branches {
match expr.eval(&self.vars, &self.functions)? {
Value::Boolean(true) => {
for s in stmts {
self.exec_one(s).await?;
}
break;
}
Value::Boolean(false) => (),
_ => return new_syntax_error("IF/ELSEIF require a boolean condition"),
};
}
Ok(())
}
async fn do_for(
&mut self,
iterator: &VarRef,
start: &Expr,
end: &Expr,
next: &Expr,
body: &[Statement],
) -> Result<()> {
debug_assert!(
iterator.ref_type() == VarType::Auto || iterator.ref_type() == VarType::Integer
);
let start_value = start.eval(&self.vars, &self.functions)?;
match start_value {
Value::Integer(_) => self.vars.set(iterator, start_value)?,
_ => return new_syntax_error("FOR supports integer iteration only"),
}
loop {
match end.eval(&self.vars, &self.functions)? {
Value::Boolean(false) => {
break;
}
Value::Boolean(true) => (),
_ => panic!("Built-in condition should have evaluated to a boolean"),
}
for s in body {
self.exec_one(s).await?;
}
self.assign(iterator, next)?;
}
Ok(())
}
async fn do_while(&mut self, condition: &Expr, body: &[Statement]) -> Result<()> {
loop {
match condition.eval(&self.vars, &self.functions)? {
Value::Boolean(true) => {
for s in body {
self.exec_one(s).await?;
}
}
Value::Boolean(false) => break,
_ => return new_syntax_error("WHILE requires a boolean condition"),
}
}
Ok(())
}
async fn exec_one<'a>(&'a mut self, stmt: &'a Statement) -> Result<()> {
match stmt {
Statement::Assignment(vref, expr) => self.assign(vref, expr)?,
Statement::BuiltinCall(name, args) => {
let cmd = match self.commands.get(name.as_str()) {
Some(cmd) => cmd.clone(),
None => return new_syntax_error(format!("Unknown builtin {}", name)),
};
cmd.exec(&args, self).await?
}
Statement::If(branches) => {
let f: Pin<Box<dyn Future<Output = Result<()>>>> = Box::pin(self.do_if(branches));
f.await?;
}
Statement::For(iterator, start, end, next, body) => {
let f: Pin<Box<dyn Future<Output = Result<()>>>> =
Box::pin(self.do_for(iterator, start, end, next, body));
f.await?;
}
Statement::While(condition, body) => {
let f: Pin<Box<dyn Future<Output = Result<()>>>> =
Box::pin(self.do_while(condition, body));
f.await?;
}
}
Ok(())
}
pub async fn exec(&mut self, input: &mut dyn io::Read) -> Result<()> {
let mut parser = Parser::from(input);
while let Some(stmt) = parser.parse()? {
self.exec_one(&stmt).await?;
}
Ok(())
}
}
pub struct ClearCommand {
metadata: CallableMetadata,
}
impl ClearCommand {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("CLEAR", VarType::Void)
.with_syntax("")
.with_category("Interpreter manipulation")
.with_description("Clears all variables to restore initial state.")
.build(),
})
}
}
#[async_trait(?Send)]
impl BuiltinCommand for ClearCommand {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, args: &[(Option<Expr>, ArgSep)], machine: &mut Machine) -> Result<()> {
if !args.is_empty() {
return new_usage_error("CLEAR takes no arguments");
}
machine.clear();
Ok(())
}
}
#[cfg(test)]
pub(crate) mod testutils {
use super::*;
use std::cell::RefCell;
pub(crate) struct InCommand {
metadata: CallableMetadata,
data: Box<RefCell<dyn Iterator<Item = &'static &'static str>>>,
}
impl InCommand {
pub(crate) fn new(
data: Box<RefCell<dyn Iterator<Item = &'static &'static str>>>,
) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("IN", VarType::Void).test_build(),
data,
})
}
}
#[async_trait(?Send)]
impl BuiltinCommand for InCommand {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, args: &[(Option<Expr>, ArgSep)], machine: &mut Machine) -> Result<()> {
if args.len() != 1 {
return new_usage_error("IN only takes one argument");
}
if args[0].1 != ArgSep::End {
return new_usage_error("Invalid separator");
}
let vref = match &args[0].0 {
Some(Expr::Symbol(vref)) => vref,
_ => return new_usage_error("IN requires a variable reference"),
};
let mut data = self.data.borrow_mut();
let raw_value = data.next().unwrap().to_owned();
let value = Value::parse_as(vref.ref_type(), raw_value)?;
machine.get_mut_vars().set(vref, value)?;
Ok(())
}
}
pub(crate) struct OutCommand {
metadata: CallableMetadata,
data: Rc<RefCell<Vec<String>>>,
}
impl OutCommand {
pub(crate) fn new(data: Rc<RefCell<Vec<String>>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("OUT", VarType::Void).test_build(),
data,
})
}
}
#[async_trait(?Send)]
impl BuiltinCommand for OutCommand {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, args: &[(Option<Expr>, ArgSep)], machine: &mut Machine) -> Result<()> {
let mut text = String::new();
for arg in args.iter() {
if let Some(expr) = arg.0.as_ref() {
text += &expr.eval(machine.get_vars(), machine.get_functions())?.to_string();
}
match arg.1 {
ArgSep::End => break,
ArgSep::Short => text += " ",
ArgSep::Long => return new_usage_error("Cannot use the ',' separator"),
}
}
self.data.borrow_mut().push(text);
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::testutils::*;
use super::*;
use crate::eval::testutils::*;
use futures_lite::future::block_on;
use std::cell::RefCell;
use std::rc::Rc;
#[test]
fn test_clear() {
let mut machine = Machine::default();
block_on(machine.exec(&mut b"a = TRUE: b = 1".as_ref())).expect("Execution failed");
assert!(machine.get_var_as_bool("a").is_ok());
assert!(machine.get_var_as_int("b").is_ok());
machine.clear();
assert!(machine.get_var_as_bool("a").is_err());
assert!(machine.get_var_as_int("b").is_err());
}
#[test]
fn test_get_var_as_bool() {
let mut machine = Machine::default();
block_on(machine.exec(&mut b"a = TRUE: b = 1".as_ref())).expect("Execution failed");
assert!(machine.get_var_as_bool("a").expect("Failed to query a"));
assert_eq!(
"Incompatible types in b? reference",
format!("{}", machine.get_var_as_bool("b").expect_err("Querying b succeeded"))
);
assert_eq!(
"Undefined variable c",
format!("{}", machine.get_var_as_bool("c").expect_err("Querying c succeeded"))
);
}
#[test]
fn test_get_var_as_int() {
let mut machine = Machine::default();
block_on(machine.exec(&mut b"a = 1: b = \"foo\"".as_ref())).expect("Execution failed");
assert_eq!(1, machine.get_var_as_int("a").expect("Failed to query a"));
assert_eq!(
"Incompatible types in b% reference",
format!("{}", machine.get_var_as_int("b").expect_err("Querying b succeeded"))
);
assert_eq!(
"Undefined variable c",
format!("{}", machine.get_var_as_int("c").expect_err("Querying c succeeded"))
);
}
#[test]
fn test_get_var_as_string() {
let mut machine = Machine::default();
block_on(machine.exec(&mut b"a = \"foo\": b = FALSE".as_ref())).expect("Execution failed");
assert_eq!("foo", machine.get_var_as_string("a").expect("Failed to query a"));
assert_eq!(
"Incompatible types in b$ reference",
format!("{}", machine.get_var_as_string("b").expect_err("Querying b succeeded"))
);
assert_eq!(
"Undefined variable c",
format!("{}", machine.get_var_as_string("c").expect_err("Querying c succeeded"))
);
}
fn run(
input: &str,
golden_in: &'static [&'static str],
captured_out: Rc<RefCell<Vec<String>>>,
) -> Result<()> {
let mut machine = MachineBuilder::default()
.add_command(InCommand::new(Box::from(RefCell::from(golden_in.iter()))))
.add_command(OutCommand::new(captured_out))
.add_function(SumFunction::new())
.build();
block_on(machine.exec(&mut input.as_bytes()))
}
fn do_ok_test(
input: &str,
golden_in: &'static [&'static str],
expected_out: &'static [&'static str],
) {
let captured_out = Rc::from(RefCell::from(vec![]));
run(input, golden_in, captured_out.clone()).expect("Execution failed");
assert_eq!(expected_out, captured_out.borrow().as_slice());
}
fn do_error_test(
input: &str,
golden_in: &'static [&'static str],
expected_out: &'static [&'static str],
expected_err: &str,
) {
let captured_out = Rc::from(RefCell::from(vec![]));
let err = run(input, golden_in, captured_out.clone()).expect_err("Execution did not fail");
assert_eq!(expected_err, format!("{}", err));
assert_eq!(expected_out, captured_out.borrow().as_slice());
}
fn do_simple_error_test(input: &str, expected_err: &str) {
do_error_test(input, &[], &[], expected_err);
}
#[test]
fn test_assignment_ok_types() {
do_ok_test("a = TRUE\nOUT a; a?", &[], &["TRUE TRUE"]);
do_ok_test("a? = FALSE\nOUT a; a?", &[], &["FALSE FALSE"]);
do_ok_test("a = 3\nOUT a; a%", &[], &["3 3"]);
do_ok_test("a% = 3\nOUT a; a%", &[], &["3 3"]);
do_ok_test("a = \"some text\"\nOUT a; a$", &[], &["some text some text"]);
do_ok_test("a$ = \"some text\"\nOUT a; a$", &[], &["some text some text"]);
do_ok_test("a = 1\na = a + 1\nOUT a", &[], &["2"]);
}
#[test]
fn test_assignment_ok_case_insensitive() {
do_ok_test("foo = 32\nOUT FOO", &[], &["32"]);
}
#[test]
fn test_assignment_errors() {
do_simple_error_test("a =\n", "Missing expression in assignment");
do_simple_error_test("a = b\n", "Undefined variable b");
do_simple_error_test("a = 3\na = TRUE\n", "Incompatible types in a assignment");
do_simple_error_test("a? = 3", "Incompatible types in a? assignment");
}
#[test]
fn test_if_ok() {
let code = r#"
IN n
IF n = 3 THEN
OUT "match"
END IF
IF n <> 3 THEN
OUT "no match"
END IF
"#;
do_ok_test(code, &["3"], &["match"]);
do_ok_test(code, &["5"], &["no match"]);
let code = r#"
IN n
IF n = 1 THEN
OUT "first"
ELSEIF n = 2 THEN
OUT "second"
ELSEIF n = 3 THEN
OUT "third"
ELSE
OUT "fourth"
END IF
"#;
do_ok_test(code, &["1"], &["first"]);
do_ok_test(code, &["2"], &["second"]);
do_ok_test(code, &["3"], &["third"]);
do_ok_test(code, &["4"], &["fourth"]);
}
#[test]
fn test_if_ok_on_malformed_branch() {
let code = r#"
IN n
IF n = 3 THEN
OUT "match"
ELSEIF "foo" THEN 'Invalid expression type but not evaluated.
OUT "no match"
END IF
"#;
do_ok_test(code, &["3"], &["match"]);
do_error_test(code, &["5"], &[], "IF/ELSEIF require a boolean condition");
}
#[test]
fn test_if_errors() {
do_simple_error_test("IF TRUE THEN END IF", "Expecting newline after THEN");
do_simple_error_test(
"IF TRUE THEN\nELSE IF TRUE THEN\nEND IF",
"Expecting newline after ELSE",
);
do_simple_error_test("IF TRUE\nEND IF\nOUT 3", "No THEN in IF statement");
do_simple_error_test("IF 2\nEND IF", "No THEN in IF statement");
do_simple_error_test("IF 2 THEN\nEND IF", "IF/ELSEIF require a boolean condition");
do_simple_error_test(
"IF FALSE THEN\nELSEIF 2 THEN\nEND IF",
"IF/ELSEIF require a boolean condition",
);
}
#[test]
fn test_for_incrementing() {
let code = r#"
IN first
IN last
FOR a = first TO last
OUT "a is"; a
NEXT
"#;
do_ok_test(code, &["0", "0"], &["a is 0"]);
do_ok_test(code, &["0", "3"], &["a is 0", "a is 1", "a is 2", "a is 3"]);
}
#[test]
fn test_for_incrementing_with_step() {
let code = r#"
IN first
IN last
FOR a = first TO last STEP 3
OUT "a is"; a
NEXT
"#;
do_ok_test(code, &["0", "0"], &["a is 0"]);
do_ok_test(code, &["0", "2"], &["a is 0"]);
do_ok_test(code, &["0", "3"], &["a is 0", "a is 3"]);
do_ok_test(code, &["0", "10"], &["a is 0", "a is 3", "a is 6", "a is 9"]);
}
#[test]
fn test_for_decrementing_with_step() {
let code = r#"
IN first
IN last
FOR a = first TO last STEP -2
OUT "a is"; a
NEXT
"#;
do_ok_test(code, &["0", "0"], &["a is 0"]);
do_ok_test(code, &["2", "0"], &["a is 2", "a is 0"]);
do_ok_test(code, &["-2", "-6"], &["a is -2", "a is -4", "a is -6"]);
do_ok_test(code, &["10", "1"], &["a is 10", "a is 8", "a is 6", "a is 4", "a is 2"]);
}
#[test]
fn test_for_already_done() {
do_ok_test("FOR i = 10 TO 9\nOUT i\nNEXT", &[], &[]);
do_ok_test("FOR i = 9 TO 10 STEP -1\nOUT i\nNEXT", &[], &[]);
}
#[test]
fn test_for_iterator_is_visible_after_next() {
let code = r#"
FOR something = 1 TO 10 STEP 8
NEXT
OUT something
"#;
do_ok_test(code, &[], &["17"]);
}
#[test]
fn test_for_iterator_can_be_modified() {
let code = r#"
FOR something = 1 TO 5
OUT something
something = something + 1
NEXT
"#;
do_ok_test(code, &[], &["1", "3", "5"]);
}
#[test]
fn test_for_errors() {
do_simple_error_test("FOR\nNEXT", "No iterator name in FOR statement");
do_simple_error_test("FOR a = 1 TO 10\nEND IF", "Unexpected token End in statement");
do_simple_error_test("FOR i = \"a\" TO 3\nNEXT", "FOR supports integer iteration only");
do_simple_error_test(
"FOR i = 1 TO \"a\"\nNEXT",
"Cannot compare Integer(1) and Text(\"a\") with <=",
);
do_simple_error_test(
"FOR i = \"b\" TO 7 STEP -8\nNEXT",
"FOR supports integer iteration only",
);
do_simple_error_test(
"FOR i = 1 TO \"b\" STEP -8\nNEXT",
"Cannot compare Integer(1) and Text(\"b\") with >=",
);
do_simple_error_test("FOR a = 1.0 TO 10.0\nNEXT", "FOR supports integer iteration only");
}
#[test]
fn test_function_call_ok() {
do_ok_test("x = 3\nOUT SUM(x, Sum%(4, 5), 1, sum())", &[], &["13"]);
}
#[test]
fn test_function_call_errors() {
do_simple_error_test("OUT SUM?()", "Incompatible type annotation for function call");
}
#[test]
fn test_while_ok() {
let code = r#"
IN n
WHILE n > 0
OUT "n is"; n
n = n - 1
END WHILE
"#;
do_ok_test(code, &["0"], &[]);
do_ok_test(code, &["3"], &["n is 3", "n is 2", "n is 1"]);
do_ok_test("WHILE FALSE\nOUT 1\nEND WHILE", &[], &[]);
}
#[test]
fn test_while_errors() {
do_simple_error_test("WHILE\nEND WHILE", "No expression in WHILE statement");
do_simple_error_test("WHILE\nEND IF", "WHILE without END WHILE");
do_simple_error_test("WHILE 2\n", "WHILE without END WHILE");
do_simple_error_test("WHILE 2\nEND WHILE", "WHILE requires a boolean condition");
}
#[test]
fn test_misc_comments_and_spaces() {
let code = r#"
REM This is the start of the program.
OUT "Hello" 'Some remark here.
IF TRUE THEN
OUT "Bye" 'And another remark here after a blank line.
END IF
"#;
do_ok_test(code, &[], &["Hello", "Bye"]);
}
#[test]
fn test_top_level_errors() {
do_simple_error_test("FOO BAR", "Unknown builtin FOO");
do_error_test("OUT \"a\"\nFOO BAR\nOUT \"b\"", &[], &["a"], "Unknown builtin FOO");
do_simple_error_test("+ b", "Unexpected token Plus in statement");
do_error_test(
"OUT \"a\"\n+ b\nOUT \"b\"",
&[],
&["a"],
"Unexpected token Plus in statement",
);
}
#[test]
fn test_exec_shares_state() {
let mut machine = Machine::default();
block_on(machine.exec(&mut b"a = 10".as_ref())).expect("Execution failed");
block_on(machine.exec(&mut b"b = a".as_ref())).expect("Execution failed");
}
#[test]
fn test_clear_ok() {
let mut machine = MachineBuilder::default().add_command(ClearCommand::new()).build();
block_on(machine.exec(&mut b"a = 1".as_ref())).unwrap();
assert!(machine.get_var_as_int("a").is_ok());
block_on(machine.exec(&mut b"CLEAR".as_ref())).unwrap();
assert!(machine.get_var_as_int("a").is_err());
}
#[test]
fn test_clear_errors() {
let mut machine = MachineBuilder::default().add_command(ClearCommand::new()).build();
assert_eq!(
"CLEAR takes no arguments",
format!("{}", block_on(machine.exec(&mut b"CLEAR 123".as_ref())).unwrap_err())
);
}
}