//! Expression evaluator and statement interpreter. //! //! To interpret a piece of AbleScript code, you first need to //! construct an [ExecEnv], which is responsible for storing the stack //! of local variable and function definitions accessible from an //! AbleScript snippet. You can then call [ExecEnv::eval_stmts] to //! evaluate or execute any number of expressions or statements. #[deny(missing_docs)] use std::collections::HashMap; use std::{ convert::TryFrom, io::{stdout, Write}, process::exit, }; use rand::random; use crate::{ ast::{Expr, Stmt, StmtKind}, base_55, error::{Error, ErrorKind}, variables::{Functio, Value, Variable}, }; /// An environment for executing AbleScript code. pub struct ExecEnv { /// 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. stack: Vec, } /// A set of visible variable and function definitions in a single /// stack frame. #[derive(Default)] struct Scope { /// The mapping from variable names to values. variables: HashMap, // In the future, this will store functio definitions and possibly // other information. } /// The reason a successful series of statements halted. enum HaltStatus { /// The last statement in the list evaluated to this value. Value(Value), /// A `break` statement occurred and was not caught by a `loop` /// statement. Break, /// A `hopback` statement occurred and was not caught by a `loop` /// statement. Hopback, } impl ExecEnv { /// Create a new Scope with no predefined variable definitions or /// other information. pub fn new() -> Self { Self { stack: Default::default(), } } /// Execute a set of Statements in their own stack frame. Return /// an error if one or more of the Stmts failed to evaluate, or if /// a `break` or `hopback` statement occurred at the top level. pub fn eval_stmts(&mut self, stmts: &[Stmt]) -> Result { match self.eval_stmts_hs(stmts)? { HaltStatus::Value(v) => Ok(v), HaltStatus::Break | HaltStatus::Hopback => Err(Error { // It's an error to issue a `break` outside of a // `loop` statement. kind: ErrorKind::TopLevelBreak, span: 0..0, }), } } /// The same as `eval_stmts`, but report "break" and "hopback" /// exit codes as normal conditions in a HaltStatus enum. /// /// `interpret`-internal code should typically prefer this /// function over `eval_stmts`. fn eval_stmts_hs(&mut self, stmts: &[Stmt]) -> Result { let init_depth = self.stack.len(); self.stack.push(Default::default()); let mut final_result = Ok(HaltStatus::Value(Value::Nul)); for stmt in stmts { final_result = self.eval_stmt(stmt); if !matches!(final_result, Ok(HaltStatus::Value(_))) { break; } } self.stack.pop(); // Invariant: stack size must have net 0 change. debug_assert_eq!(self.stack.len(), init_depth); final_result } /// Evaluate an Expr, returning its value or an error. fn eval_expr(&self, expr: &Expr) -> Result { use crate::ast::BinOpKind::*; use crate::ast::ExprKind::*; use Value::*; Ok(match &expr.kind { BinOp { lhs, rhs, kind } => { let lhs = self.eval_expr(&lhs)?; let rhs = self.eval_expr(&rhs)?; match kind { // Arithmetic operators. Add | Subtract | Multiply | Divide => { let lhs = i32::try_from(lhs)?; let rhs = i32::try_from(rhs)?; let res = match kind { Add => lhs.checked_add(rhs), Subtract => lhs.checked_sub(rhs), Multiply => lhs.checked_mul(rhs), Divide => lhs.checked_div(rhs), _ => unreachable!(), } .ok_or(Error { kind: ErrorKind::ArithmeticError, span: expr.span.clone(), })?; Int(res) } // Numeric comparisons. Less | Greater => { let lhs = i32::try_from(lhs)?; let rhs = i32::try_from(rhs)?; let res = match kind { Less => lhs < rhs, Greater => lhs > rhs, _ => unreachable!(), }; Bool(res) } // General comparisons. Equal | NotEqual => { let res = match kind { Equal => lhs == rhs, NotEqual => lhs != rhs, _ => unreachable!(), }; Bool(res) } // Logical connectives. And | Or => { let lhs = bool::from(lhs); let rhs = bool::from(rhs); let res = match kind { And => lhs && rhs, Or => lhs || rhs, _ => unreachable!(), }; Bool(res) } } } Not(expr) => Bool(!bool::from(self.eval_expr(&expr)?)), Literal(value) => value.clone(), Variable(name) => self.get_var(&name)?, }) } /// Perform the action indicated by a statement. fn eval_stmt(&mut self, stmt: &Stmt) -> Result { match &stmt.kind { StmtKind::Print(expr) => { println!("{}", self.eval_expr(expr)?); } StmtKind::Var { iden, init } => { let init = match init { Some(e) => self.eval_expr(e)?, None => Value::Nul, }; self.decl_var(&iden.iden, init); } StmtKind::Functio { iden: _, args: _, body: _, } => todo!(), // This is missing from StmtKind after the interpreter // rewrite; presumably, parsing is not yet implemented for // it. ~~Alex // StmtKind::BfFDeclaration { iden, body } => { // self.decl_var( // &iden.0, // Value::Functio(Functio::BfFunctio(body.as_bytes().into())), // ); // } StmtKind::If { cond, body } => { if self.eval_expr(cond)?.into() { return self.eval_stmts_hs(&body.block); } } StmtKind::Call { iden, args } => { let func = self.get_var(&iden.iden)?; match func { Value::Functio(func) => { match func { Functio::BfFunctio(body) => { let mut input: Vec = vec![]; for arg in args { self.eval_expr(arg)?.bf_write(&mut input); } println!("input = {:?}", input); let mut output = vec![]; crate::brian::interpret_with_io(&body, &input as &[_], &mut output) .map_err(|e| Error { kind: ErrorKind::BfInterpretError(e), span: stmt.span.clone(), })?; // I guess Brainfuck functions write // output to stdout? It's not quite // clear to me what else to do. ~~Alex stdout() .write_all(&output) .expect("Failed to write to stdout"); } Functio::AbleFunctio(_) => { todo!() } } } _ => { return Err(Error { kind: ErrorKind::TypeError(iden.iden.to_owned()), span: stmt.span.clone(), }) } } } StmtKind::Loop { body } => loop { let res = self.eval_stmts_hs(&body.block)?; match res { HaltStatus::Value(_) => {} HaltStatus::Break => break, HaltStatus::Hopback => continue, } }, // This is missing as well. ~~Alex // StmtKind::VarAssignment { iden, value } => { // self.get_var_mut(&iden.0)?.value = self.eval_expr(value)?; // } StmtKind::Break => { return Ok(HaltStatus::Break); } StmtKind::HopBack => { return Ok(HaltStatus::Hopback); } StmtKind::Melo(iden) => { self.get_var_mut(&iden.iden)?.melo = true; } StmtKind::Rlyeh => { // Maybe print a creepy error message or something // here at some point. ~~Alex exit(random()); } } Ok(HaltStatus::Value(Value::Nul)) } /// Get the value of a variable. Throw an error if the variable is /// inaccessible or banned. fn get_var(&self, name: &str) -> Result { // One-letter names are reserved as base55 numbers. let mut chars = name.chars(); if let (Some(first), None) = (chars.next(), chars.next()) { return Ok(Value::Int(base_55::char2num(first))); } // Otherwise, search for the name in the stack from top to // bottom. match self .stack .iter() .rev() .find_map(|scope| scope.variables.get(name)) { Some(var) => { if !var.melo { Ok(var.value.clone()) } else { Err(Error { kind: ErrorKind::MeloVariable(name.to_owned()), // TODO: figure out some way to avoid this // 0..0 dumbness span: 0..0, }) } } None => Err(Error { kind: ErrorKind::UnknownVariable(name.to_owned()), span: 0..0, }), } } /// Get a mutable reference to a variable. Throw an error if the /// variable is inaccessible or banned. fn get_var_mut(&mut self, name: &str) -> Result<&mut Variable, Error> { // This function has a lot of duplicated code with `get_var`, // which I feel like is a bad sign... match self .stack .iter_mut() .rev() .find_map(|scope| scope.variables.get_mut(name)) { Some(var) => { if !var.melo { Ok(var) } else { Err(Error { kind: ErrorKind::MeloVariable(name.to_owned()), span: 0..0, }) } } None => Err(Error { kind: ErrorKind::UnknownVariable(name.to_owned()), span: 0..0, }), } } /// Declares a new variable, with the given initial value. fn decl_var(&mut self, name: &str, value: Value) { self.stack .iter_mut() .last() .expect("Declaring variable on empty stack") .variables .insert(name.to_owned(), Variable { melo: false, value }); } } #[cfg(test)] mod tests { use crate::ast::ExprKind; use super::*; #[test] fn basic_expression_test() { // Check that 2 + 2 = 4. let env = ExecEnv::new(); assert_eq!( env.eval_expr(&Expr { kind: ExprKind::BinOp { lhs: Box::new(Expr { kind: ExprKind::Literal(Value::Int(2)), span: 0..0, }), rhs: Box::new(Expr { kind: ExprKind::Literal(Value::Int(2)), span: 0..0, }), kind: crate::ast::BinOpKind::Add, }, span: 0..0 }) .unwrap(), Value::Int(4) ) } #[test] fn type_errors() { // The sum of an integer and a boolean results in a type // error. let env = ExecEnv::new(); assert!(matches!( env.eval_expr(&Expr { kind: ExprKind::BinOp { lhs: Box::new(Expr { kind: ExprKind::Literal(Value::Int(2)), span: 0..0, }), rhs: Box::new(Expr { kind: ExprKind::Literal(Value::Bool(true)), span: 0..0, }), kind: crate::ast::BinOpKind::Add, }, span: 0..0 }), Err(Error { kind: ErrorKind::TypeError(_), span: _, }) )); } #[test] fn overflow_should_not_panic() { // Integer overflow should throw a recoverable error instead // of panicking. let env = ExecEnv::new(); assert!(matches!( env.eval_expr(&Expr { kind: ExprKind::BinOp { lhs: Box::new(Expr { kind: ExprKind::Literal(Value::Int(i32::MAX)), span: 0..0, }), rhs: Box::new(Expr { kind: ExprKind::Literal(Value::Int(1)), span: 0..0, }), kind: crate::ast::BinOpKind::Add, }, span: 0..0 }), Err(Error { kind: ErrorKind::ArithmeticError, span: _, }) )); // And the same for divide by zero. assert!(matches!( env.eval_expr(&Expr { kind: ExprKind::BinOp { lhs: Box::new(Expr { kind: ExprKind::Literal(Value::Int(1)), span: 0..0, }), rhs: Box::new(Expr { kind: ExprKind::Literal(Value::Int(0)), span: 0..0, }), kind: crate::ast::BinOpKind::Divide, }, span: 0..0 }), Err(Error { kind: ErrorKind::ArithmeticError, span: _, }) )); } // 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 { let mut parser = crate::parser::Parser::new(src); // We can assume there won't be any syntax errors in the // interpreter tests. let ast = parser.init().unwrap(); env.eval_stmts(&ast) } #[test] #[ignore = "doesn't make sense anymore due to separation of statements & expressions"] fn variable_decl_and_assignment() { // Declaring and reading from a variable. assert_eq!( eval(&mut ExecEnv::new(), "var foo = 32; foo + 1").unwrap(), Value::Int(33) ); // It should be possible to overwrite variables as well. assert_eq!( eval(&mut ExecEnv::new(), "var bar = 10; bar = 20; bar").unwrap(), Value::Int(20) ); // But variable assignment should be illegal when the variable // hasn't been declared in advance. eval(&mut ExecEnv::new(), "baz = 10;").unwrap_err(); } #[test] #[ignore = "doesn't make sense anymore due to separation of statements & expressions"] fn variable_persistence() { // Global variables should persist between invocations of // ExecEnv::eval_items(). let mut env = ExecEnv::new(); eval(&mut env, "var foo = 32;").unwrap(); assert_eq!(eval(&mut env, "foo").unwrap(), Value::Int(32)); } #[test] #[ignore = "doesn't make sense anymore due to separation of statements & expressions"] fn scope_visibility_rules() { // Declaration and assignment of variables declared in an `if` // statement should have no effect on those declared outside // of it. assert_eq!( eval( &mut ExecEnv::new(), "var foo = 1; if (true) { var foo = 2; foo = 3; } foo" ) .unwrap(), Value::Int(1) ); } }