use crate::parse::ast::{ BinaryOp, DeclareFunction, DeclareIdent, ExpressionList, Print, Program, Return, UnaryOp, ValueExpression, VoidExpression, }; use crate::parser::ast::Assignment; use crate::runtime::executor::Visitor; use crate::runtime::value::{ForgeValue, UnsupportedOperation}; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; #[derive(Clone, Default)] pub struct SimpleScope { data: HashMap<String, ForgeValue>, vtable: HashMap<String, DeclareFunction>, } #[derive(Clone, Default)] pub struct SimpleExecutor { scope: SimpleScope, } #[derive(Clone, Debug, PartialEq)] pub enum RuntimeError { Unsupported(&'static str), UndefinedVariable(String), VariableAlreadyDefined(String), BadOperands(UnsupportedOperation), } impl From<UnsupportedOperation> for RuntimeError { fn from(value: UnsupportedOperation) -> Self { Self::BadOperands(value) } } impl Display for RuntimeError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { RuntimeError::Unsupported(expr) => { write!( f, "[Runtime] Encountered an unsupported expression: {}", expr ) } RuntimeError::BadOperands(unsupported) => write!(f, "[Runtime] {}", unsupported), RuntimeError::UndefinedVariable(var) => { write!(f, "[Runtime] Attempt to access undefined variable {}", var) } RuntimeError::VariableAlreadyDefined(var) => write!( f, "[Runtime] Attempt to redefine already defined variable {}", var ), } } } impl Error for RuntimeError {} pub type RuntimeResult<T> = Result<T, RuntimeError>; impl SimpleExecutor { pub fn get_variable(&self, identifier: impl ToString) -> Option<&ForgeValue> { self.scope.data.get(&identifier.to_string()) } pub fn set_variable( &mut self, identifier: impl ToString, value: ForgeValue, ) -> RuntimeResult<()> { let entry = self.scope.data.entry(identifier.to_string()); match entry { Entry::Occupied(mut e) => { e.insert(value); Ok(()) } Entry::Vacant(_) => Err(RuntimeError::UndefinedVariable(identifier.to_string())), } } pub fn define_variable( &mut self, identifier: impl ToString, value: ForgeValue, ) -> RuntimeResult<()> { let entry = self.scope.data.entry(identifier.to_string()); match entry { Entry::Vacant(e) => { e.insert(value); Ok(()) } Entry::Occupied(_) => Err(RuntimeError::VariableAlreadyDefined(identifier.to_string())), } } fn process_assignment(&mut self, assignment: &Assignment) -> RuntimeResult<ForgeValue> { let value = self.evaluate_value_expression(assignment.value.as_ref())?; self.set_variable(&assignment.ident, value.clone())?; Ok(value) } fn evaluate_expression_list( &mut self, list: &ExpressionList, ) -> Result<ForgeValue, RuntimeError> { let mut last_val = Ok(ForgeValue::Null); for expr in &list.expressions { last_val = Ok(self.evaluate_expression(expr)?); } if list.is_void { Ok(ForgeValue::Null) } else { last_val } } } impl Visitor for SimpleExecutor { type Output = ForgeValue; type Error = RuntimeError; fn evaluate_value_expression( &mut self, expression: &ValueExpression, ) -> Result<Self::Output, Self::Error> { match expression { ValueExpression::Unary { operator, operand } => { let value = self.evaluate_value_expression(operand.as_ref())?; match operator { UnaryOp::Negate => Ok(value.invert()), UnaryOp::Not => Ok(!value), } } ValueExpression::Binary { operator, lhs, rhs } => { if operator.short_circuits() { let lhs = self.evaluate_value_expression(lhs.as_ref())?; match (operator, lhs.as_bool()) { (BinaryOp::BoolOr, true) => Ok(true.into()), (BinaryOp::BoolAnd, false) => Ok(false.into()), (BinaryOp::BoolOr, false) | (BinaryOp::BoolAnd, true) => Ok(self .evaluate_value_expression(rhs.as_ref())? .as_bool() .into()), _ => Ok(ForgeValue::Null), } } else { let lhs = self.evaluate_value_expression(lhs.as_ref())?; let rhs = self.evaluate_value_expression(rhs.as_ref())?; match operator { BinaryOp::Add => Ok(lhs + rhs), BinaryOp::Subtract => Ok((lhs - rhs)?), BinaryOp::Divide => Ok((lhs / rhs)?), BinaryOp::Multiply => Ok((lhs * rhs)?), BinaryOp::Modulo => Ok((lhs % rhs)?), BinaryOp::Equals => Ok((lhs == rhs).into()), BinaryOp::BoolOr | BinaryOp::BoolAnd | BinaryOp::NotEquals | BinaryOp::LessThan | BinaryOp::GreaterThan | BinaryOp::LessThanEqual | BinaryOp::GreaterThanEqual => todo!("Implement Binary Op"), } } } ValueExpression::Grouped(group) => self.evaluate_value_expression(group.inner.as_ref()), ValueExpression::Block(block) => self.evaluate_expression_list(block), ValueExpression::Literal(lit) => Ok(ForgeValue::from(lit.clone())), ValueExpression::DeclareIdentifier(decl) => match decl { DeclareIdent::WithValue(assignment) => { self.define_variable(&assignment.ident, ForgeValue::Null)?; self.process_assignment(assignment) } DeclareIdent::WithoutValue(identifier) => { self.define_variable(identifier, ForgeValue::Null)?; Ok(ForgeValue::Null) } }, ValueExpression::Assignment(assign) => self.process_assignment(assign), ValueExpression::ConditionalBlock(condition) => { let mut has_found = false; let mut last_value = ForgeValue::Null; for block in condition.blocks.iter() { let guard_val = self.evaluate_value_expression(block.guard.as_ref())?; if guard_val.as_bool() { has_found = true; last_value = self.evaluate_expression_list(&block.block)?; break; } } if !has_found { if let Some(value) = &condition.fallback { last_value = self.evaluate_expression_list(value)?; } } Ok(last_value) } ValueExpression::Identifier(ident) => self .get_variable(ident) .cloned() .ok_or_else(|| RuntimeError::UndefinedVariable(ident.to_string())), ValueExpression::Accessor(ident) => Err(RuntimeError::Unsupported("Accessor")), ValueExpression::FunctionCall(_) => Err(RuntimeError::Unsupported("FunctionCall")), ValueExpression::DeclareFunction(_) => { Err(RuntimeError::Unsupported("DeclareFunction")) } ValueExpression::Typeof(type_of) => Ok(ForgeValue::from( self.evaluate_value_expression(type_of.0.as_ref())? .type_name(), )), } } fn evaluate_void_expression( &mut self, expression: &VoidExpression, ) -> Result<Self::Output, Self::Error> { match expression { VoidExpression::ConditionLoop(cond_loop) => { while self .evaluate_value_expression(cond_loop.block.guard.as_ref())? .as_bool() { self.evaluate_expression_list(&cond_loop.block.block)?; } if let Some(fallback) = &cond_loop.fallback { self.evaluate_expression_list(fallback)?; } } VoidExpression::Import(_) => return Err(RuntimeError::Unsupported("Import")), VoidExpression::Export(_) => return Err(RuntimeError::Unsupported("Export")), VoidExpression::Print(Print { expr }) => { let value = self.evaluate_value_expression(expr.as_ref())?; println!("{}", value); } VoidExpression::Return(Return { expr }) => { let value = self.evaluate_value_expression(expr.as_ref())?; eprintln!("Currently Unsupported: Return. Value: {}", value); } } Ok(ForgeValue::Null) } fn evaluate_program(&mut self, program: &Program) -> Result<Self::Output, Self::Error> { self.evaluate_expression_list(&program.0) } } #[cfg(test)] mod interpreter_test { use crate::parse::parse_program; use crate::runtime::executor::simple::{RuntimeError, RuntimeResult, SimpleExecutor}; use crate::runtime::executor::Visitor; use crate::runtime::value::{ForgeValue, UnsupportedOperation}; use test_case::test_case; #[test_case("1 + 1" => Ok(ForgeValue::from(2)) ; "numeric addition")] #[test_case(r#""foo" + " " + "bar""# => Ok(ForgeValue::from("foo bar")) ; "string concatenation")] #[test_case( r#"false * 132"# => Err(RuntimeError::BadOperands(UnsupportedOperation::mul("non-number", "number"))); "bad multiplication error" )] #[test_case("\"#\" + 1" => Ok(ForgeValue::from("#1")) ; "coerce number to string with concat")] #[test_case("1 + -1" => Ok(ForgeValue::from(0)) ; "unary nested in binary")] #[test_case("print 2 + 2" => Ok(ForgeValue::Null) ; "print is nullish")] fn simple_expr(prog: &'static str) -> RuntimeResult<ForgeValue> { let ast = parse_program(prog).expect("Failed to parse program"); let mut vm = SimpleExecutor::default(); vm.evaluate_program(&ast) } #[test_case("let foo" => Ok(ForgeValue::Null); "Initialise as null")] #[test_case("let bar = 123" => Ok(ForgeValue::from(123)); "Initialise with value")] #[test_case("let bar = 123; let bar = 456" => Err(RuntimeError::VariableAlreadyDefined(String::from("bar"))); "Double initialise error")] #[test_case("print missing" => Err(RuntimeError::UndefinedVariable(String::from("missing"))); "Access undefined error")] #[test_case("let bar = 123; let foo = bar * 2" =>Ok(ForgeValue::from(246)); "Initialise from other var")] fn declare_variables(prog: &'static str) -> RuntimeResult<ForgeValue> { let ast = parse_program(prog).expect("Failed to parse program"); let mut vm = SimpleExecutor::default(); vm.evaluate_program(&ast) } #[test_case("let value = true; if value { 123 } else { 456 }" => Ok(ForgeValue::from(123)); "eval if branch")] #[test_case("let value = true; if !value { 123 } else { 456 }" => Ok(ForgeValue::from(456)); "eval else branch")] #[test_case("let value = 2; if value == 1 { 123 } else if value == 2 { 456 } else { \"Nothing\" }" => Ok(ForgeValue::from(456)); "eval middle if else branch")] // #[test_case("let value = 0; while value < 4 { value = value + 4 }; value" => Ok(ForgeValue::from(4)); "simple conditional loop")] // #[test_case("let value = 0; while value < 4 { value = value + 4 } finally { value = 1234 }; value" => Ok(ForgeValue::from(1234)); "conditional loop with cleanup")] fn conditional_blocks(prog: &'static str) -> RuntimeResult<ForgeValue> { let ast = parse_program(prog).expect("Failed to parse program"); let mut vm = SimpleExecutor::default(); vm.evaluate_program(&ast) } #[test] /// When the first term in a binary op has already decided the outcome, don't evaluate the following terms fn short_circuit() { let prog = parse_program("if true || let bar = 123 { 9001 }").expect("Failed to parse"); let mut vm = SimpleExecutor::default(); assert_eq!(vm.evaluate_program(&prog), Ok(ForgeValue::from(9001))); assert!(vm.get_variable("bar").is_none()); } #[test_case("typeof 123" => Ok(ForgeValue::from("number")); "typeof number")] #[test_case(r#"typeof "some string""# => Ok(ForgeValue::from("string")); "typeof string")] #[test_case("typeof false" => Ok(ForgeValue::from("boolean")); "typeof bool")] fn variable_type(prog: &'static str) -> RuntimeResult<ForgeValue> { let ast = parse_program(prog).expect("Failed to parse program"); let mut vm = SimpleExecutor::default(); vm.evaluate_program(&ast) } }