Skip to content
Snippets Groups Projects
simple.rs 10.9 KiB
Newer Older
	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
			),
pub type RuntimeResult<T> = Result<T, RuntimeError>;

	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()),
Louis's avatar
Louis committed
						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()),
Louis's avatar
Louis committed
			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)
				}
			},
Louis's avatar
Louis committed
			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> {
Louis's avatar
Louis committed
		match expression {
Louis's avatar
Louis committed
			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)?;
				}
Louis's avatar
Louis committed
			}
			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);
			}
Louis's avatar
Louis committed
		}

		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")]
Louis's avatar
Louis committed
	// #[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");
Louis's avatar
Louis committed
		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)