use std::{cell::RefCell, rc::Rc}; use thiserror::Error; use crate::{ ast::{Ast, BinOpType, BlockScope, Expression, FunDecl, If, Statement, UnOpType}, astoptimizer::{AstOptimizer, SimpleAstOptimizer}, lexer::lex, nice_panic, parser::parse, stringstore::{Sid, StringStore}, }; #[derive(Debug, Error)] pub enum RuntimeError { #[error("Invalid array Index: {}", 0.to_string())] InvalidArrayIndex(Value), #[error("Variable used but not declared: {0}")] VarUsedNotDeclared(String), #[error("Can't index into non-array variable: {0}")] TryingToIndexNonArray(String), #[error("Invalid value type for unary operation: {}", 0.to_string())] UnOpInvalidType(Value), #[error("Incompatible binary operations. Operands don't match: {} {}", 0.to_string(), 1.to_string())] BinOpIncompatibleTypes(Value, Value), #[error("Array access out of bounds: Accessed {0}, size is {1}")] ArrayOutOfBounds(usize, usize), #[error("Division by zero")] DivideByZero, #[error("Invalid number of arguments for function {0}. Expected {1}, got {2}")] InvalidNumberOfArgs(String, usize, usize), } #[derive(Debug, PartialEq, Eq, Clone)] pub enum Value { I64(i64), String(Sid), Array(Rc>>), Void, } #[derive(Debug, PartialEq, Eq, Clone)] pub enum BlockExit { Normal, Break, Continue, Return(Value), } #[derive(Default)] pub struct Interpreter { pub optimize_ast: bool, pub print_tokens: bool, pub print_ast: bool, pub capture_output: bool, output: Vec, // Variable table stores the runtime values of variables vartable: Vec, funtable: Vec, stringstore: StringStore, } impl Interpreter { pub fn new() -> Self { Self { optimize_ast: true, ..Self::default() } } pub fn output(&self) -> &[Value] { &self.output } fn get_var(&self, idx: usize) -> Option { self.vartable.get(self.vartable.len() - idx - 1).cloned() } fn get_var_mut(&mut self, idx: usize) -> Option<&mut Value> { let idx = self.vartable.len() - idx - 1; self.vartable.get_mut(idx) } pub fn run_str(&mut self, code: &str) { let tokens = match lex(code) { Ok(tokens) => tokens, Err(e) => nice_panic!("Lexing error: {}", e), }; if self.print_tokens { println!("Tokens: {:?}", tokens); } let ast = match parse(tokens) { Ok(ast) => ast, Err(e) => nice_panic!("Parsing error: {}", e), }; match self.run_ast(ast) { Ok(_) => (), Err(e) => nice_panic!("Runtime error: {}", e), } } pub fn run_ast(&mut self, mut ast: Ast) -> Result<(), RuntimeError> { if self.optimize_ast { ast = SimpleAstOptimizer::optimize(ast); } if self.print_ast { println!("{:#?}", ast.main); } self.stringstore = ast.stringstore; self.run_block(&ast.main)?; Ok(()) } pub fn run_block(&mut self, prog: &BlockScope) -> Result { self.run_block_fp_offset(prog, 0) } pub fn run_block_fp_offset( &mut self, prog: &BlockScope, framepointer_offset: usize, ) -> Result { let framepointer = self.vartable.len() - framepointer_offset; for stmt in prog { match stmt { Statement::Break => return Ok(BlockExit::Break), Statement::Continue => return Ok(BlockExit::Continue), Statement::Return(expr) => { let val = self.resolve_expr(expr)?; self.vartable.truncate(framepointer); return Ok(BlockExit::Return(val)); } Statement::Expr(expr) => { self.resolve_expr(expr)?; } Statement::Declaration(decl) => { let rhs = self.resolve_expr(&decl.rhs)?; self.vartable.push(rhs); } Statement::Block(block) => match self.run_block(block)? { // Propagate return, continue and break be @ (BlockExit::Return(_) | BlockExit::Continue | BlockExit::Break) => { self.vartable.truncate(framepointer); return Ok(be); } _ => (), }, Statement::Loop(looop) => { // loop runs as long condition != 0 loop { if let Some(condition) = &looop.condition { if matches!(self.resolve_expr(condition)?, Value::I64(0)) { break; } } let be = self.run_block(&looop.body)?; match be { // Propagate return be @ BlockExit::Return(_) => { self.vartable.truncate(framepointer); return Ok(be); } BlockExit::Break => break, BlockExit::Continue | BlockExit::Normal => (), } if let Some(adv) = &looop.advancement { self.resolve_expr(&adv)?; } } } Statement::Print(expr) => { let result = self.resolve_expr(expr)?; if self.capture_output { self.output.push(result) } else { print!("{}", self.value_to_string(&result)); } } Statement::If(If { condition, body_true, body_false, }) => { let exit = if matches!(self.resolve_expr(condition)?, Value::I64(0)) { self.run_block(body_false)? } else { self.run_block(body_true)? }; match exit { // Propagate return, continue and break be @ (BlockExit::Return(_) | BlockExit::Continue | BlockExit::Break) => { self.vartable.truncate(framepointer); return Ok(be); } _ => (), } } Statement::FunDeclare(fundec) => { self.funtable.push(fundec.clone()); } } } self.vartable.truncate(framepointer); Ok(BlockExit::Normal) } fn resolve_expr(&mut self, expr: &Expression) -> Result { let val = match expr { Expression::I64(val) => Value::I64(*val), Expression::ArrayLiteral(size) => { let size = match self.resolve_expr(size)? { Value::I64(size) if !size.is_negative() => size, val => return Err(RuntimeError::InvalidArrayIndex(val)), }; Value::Array(Rc::new(RefCell::new(vec![Value::I64(0); size as usize]))) } Expression::String(text) => Value::String(text.clone()), Expression::BinOp(bo, lhs, rhs) => self.resolve_binop(bo, lhs, rhs)?, Expression::UnOp(uo, operand) => self.resolve_unop(uo, operand)?, Expression::Var(name, idx) => self.resolve_var(*name, *idx)?, Expression::ArrayAccess(name, idx, arr_idx) => { self.resolve_array_access(*name, *idx, arr_idx)? } Expression::FunCall(fun_name, fun_stackpos, args) => { let args_len = args.len(); // All of the arg expressions must be resolved before pushing the vars on the stack, // otherwise the stack positions are incorrect while resolving let args = args .iter() .map(|arg| self.resolve_expr(arg)) .collect::>(); for arg in args { self.vartable.push(arg?); } // Function existance has been verified in the parser, so unwrap here shouldn't fail let expected_num_args = self.funtable.get(*fun_stackpos).unwrap().argnames.len(); if expected_num_args != args_len { let fun_name = self .stringstore .lookup(*fun_name) .cloned() .unwrap_or("".to_string()); return Err(RuntimeError::InvalidNumberOfArgs( fun_name, expected_num_args, args_len, )); } match self.run_block_fp_offset( &Rc::clone(&self.funtable.get(*fun_stackpos).unwrap().body), expected_num_args, )? { BlockExit::Normal | BlockExit::Continue | BlockExit::Break => Value::Void, BlockExit::Return(val) => val, } } }; Ok(val) } fn resolve_array_access( &mut self, name: Sid, idx: usize, arr_idx: &Expression, ) -> Result { let arr_idx = match self.resolve_expr(arr_idx)? { Value::I64(size) if !size.is_negative() => size, val => return Err(RuntimeError::InvalidArrayIndex(val)), }; let val = match self.get_var(idx) { Some(val) => val, None => { return Err(RuntimeError::VarUsedNotDeclared( self.stringstore .lookup(name) .cloned() .unwrap_or_else(|| "".to_string()), )) } }; let arr = match val { Value::Array(arr) => arr, _ => { return Err(RuntimeError::TryingToIndexNonArray( self.stringstore .lookup(name) .cloned() .unwrap_or_else(|| "".to_string()), )) } }; let arr = arr.borrow_mut(); arr.get(arr_idx as usize) .cloned() .ok_or(RuntimeError::ArrayOutOfBounds(arr_idx as usize, arr.len())) } fn resolve_var(&mut self, name: Sid, idx: usize) -> Result { match self.get_var(idx) { Some(val) => Ok(val), None => { return Err(RuntimeError::VarUsedNotDeclared( self.stringstore .lookup(name) .cloned() .unwrap_or_else(|| "".to_string()), )) } } } fn resolve_unop(&mut self, uo: &UnOpType, operand: &Expression) -> Result { let operand = self.resolve_expr(operand)?; Ok(match (operand, uo) { (Value::I64(val), UnOpType::Negate) => Value::I64(-val), (Value::I64(val), UnOpType::BNot) => Value::I64(!val), (Value::I64(val), UnOpType::LNot) => Value::I64(if val == 0 { 1 } else { 0 }), (val, _) => return Err(RuntimeError::UnOpInvalidType(val)), }) } fn resolve_binop( &mut self, bo: &BinOpType, lhs: &Expression, rhs: &Expression, ) -> Result { let rhs = self.resolve_expr(rhs)?; match (&bo, &lhs) { (BinOpType::Assign, Expression::Var(name, idx)) => { match self.get_var_mut(*idx) { Some(val) => *val = rhs.clone(), None => { return Err(RuntimeError::VarUsedNotDeclared( self.stringstore .lookup(*name) .cloned() .unwrap_or_else(|| "".to_string()), )) } } return Ok(rhs); } (BinOpType::Assign, Expression::ArrayAccess(name, idx, arr_idx)) => { let arr_idx = match self.resolve_expr(arr_idx)? { Value::I64(size) if !size.is_negative() => size, val => return Err(RuntimeError::InvalidArrayIndex(val)), }; let val = match self.get_var_mut(*idx) { Some(val) => val, None => { return Err(RuntimeError::VarUsedNotDeclared( self.stringstore .lookup(*name) .cloned() .unwrap_or_else(|| "".to_string()), )) } }; match val { Value::Array(arr) => arr.borrow_mut()[arr_idx as usize] = rhs.clone(), _ => { return Err(RuntimeError::TryingToIndexNonArray( self.stringstore .lookup(*name) .cloned() .unwrap_or_else(|| "".to_string()), )) } } return Ok(rhs); } _ => (), } let lhs = self.resolve_expr(lhs)?; let result = match (lhs, rhs) { (Value::I64(lhs), Value::I64(rhs)) => match bo { BinOpType::Add => Value::I64(lhs + rhs), BinOpType::Mul => Value::I64(lhs * rhs), BinOpType::Sub => Value::I64(lhs - rhs), BinOpType::Div => { Value::I64(lhs.checked_div(rhs).ok_or(RuntimeError::DivideByZero)?) } BinOpType::Mod => { Value::I64(lhs.checked_rem(rhs).ok_or(RuntimeError::DivideByZero)?) } BinOpType::BOr => Value::I64(lhs | rhs), BinOpType::BAnd => Value::I64(lhs & rhs), BinOpType::BXor => Value::I64(lhs ^ rhs), BinOpType::LAnd => Value::I64(if (lhs != 0) && (rhs != 0) { 1 } else { 0 }), BinOpType::LOr => Value::I64(if (lhs != 0) || (rhs != 0) { 1 } else { 0 }), BinOpType::Shr => Value::I64(lhs >> rhs), BinOpType::Shl => Value::I64(lhs << rhs), BinOpType::EquEqu => Value::I64(if lhs == rhs { 1 } else { 0 }), BinOpType::NotEqu => Value::I64(if lhs != rhs { 1 } else { 0 }), BinOpType::Less => Value::I64(if lhs < rhs { 1 } else { 0 }), BinOpType::LessEqu => Value::I64(if lhs <= rhs { 1 } else { 0 }), BinOpType::Greater => Value::I64(if lhs > rhs { 1 } else { 0 }), BinOpType::GreaterEqu => Value::I64(if lhs >= rhs { 1 } else { 0 }), BinOpType::Assign => unreachable!(), }, (lhs, rhs) => return Err(RuntimeError::BinOpIncompatibleTypes(lhs, rhs)), }; Ok(result) } fn value_to_string(&self, val: &Value) -> String { match val { Value::I64(val) => format!("{}", val), Value::Array(val) => format!("{:?}", val.borrow()), Value::String(text) => format!( "{}", self.stringstore .lookup(*text) .unwrap_or(&"".to_string()) ), Value::Void => format!("void"), } } } #[cfg(test)] mod test { use super::{Interpreter, Value}; use crate::ast::{BinOpType, Expression}; #[test] fn test_interpreter_expr() { // Expression: 1 + 2 * 3 + 4 // With precedence: (1 + (2 * 3)) + 4 let ast = Expression::BinOp( BinOpType::Add, Expression::BinOp( BinOpType::Add, Expression::I64(1).into(), Expression::BinOp( BinOpType::Mul, Expression::I64(2).into(), Expression::I64(3).into(), ) .into(), ) .into(), Expression::I64(4).into(), ); let expected = Value::I64(11); let mut interpreter = Interpreter::new(); let actual = interpreter.resolve_expr(&ast).unwrap(); assert_eq!(expected, actual); } }