Skip to content
Snippets Groups Projects
simple.rs 8.48 KiB
Newer Older
	BinaryOp, DeclareFunction, DeclareIdent, ExpressionList, Print, Program, 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 } => {
				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()),
				}
			}
			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::FunctionCall(_) => Err(RuntimeError::Unsupported("FunctionCall")),
			ValueExpression::DeclareFunction(_) => {
				Err(RuntimeError::Unsupported("DeclareFunction"))
			}
		}
	}

	fn evaluate_void_expression(
		&mut self,
		expression: &VoidExpression,
	) -> Result<Self::Output, Self::Error> {
Louis's avatar
Louis committed
		match expression {
			VoidExpression::ConditionLoop(_) => {
				return Err(RuntimeError::Unsupported("ConditionLoop"))
			}
			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);
			}
		}

		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, SimpleExecutor};
	use crate::runtime::executor::Visitor;
	use crate::runtime::value::ForgeValue;

	#[test]
	fn the_basics() {
		let add_numbers = parse_program("1 + 1").expect("Failed to parse");
		let add_strings = parse_program("\"foo\" + \" \" + \"bar\"").expect("Failed to parse");
		let concat_string_num = parse_program("\"#\" + 1").expect("Failed to parse");
		let bad_mult = parse_program("false * 123").expect("Failed to parse");
		let mut vm = SimpleExecutor::default();

		assert_eq!(vm.evaluate_program(&add_numbers), Ok(ForgeValue::from(2)));
		assert_eq!(
			vm.evaluate_program(&add_strings),
			Ok(ForgeValue::from("foo bar"))
		);
		assert_eq!(
			vm.evaluate_program(&concat_string_num),
			Ok(ForgeValue::from("#1"))
		);
		assert!(matches!(
			vm.evaluate_program(&bad_mult),
			Err(RuntimeError::BadOperands(_))
		))
	}

	#[test]
	fn combos() {
		let add_numbers = parse_program("1 + -1").expect("Failed to parse");
		let mut vm = SimpleExecutor::default();
		assert_eq!(vm.evaluate_program(&add_numbers), Ok(ForgeValue::from(0)));
	}
Louis's avatar
Louis committed

	#[test]
	fn print() {
		let print_4 = parse_program("print 2 + 2").expect("Failed to parse");
		let mut vm = SimpleExecutor::default();
		assert_eq!(vm.evaluate_program(&print_4), Ok(ForgeValue::Null));
	}

	#[test]
	fn define_variables() {
		let simple_define = parse_program("let foo").expect("Failed to parse");
		let assignment = parse_program("let bar = 123").expect("Failed to parse");
		let redecl_err = parse_program("let foo = 123").expect("Failed to parse");
		let missing_err = parse_program("print missing").expect("Failed to parse");
		let use_vars = parse_program("let splop = bar * 2").expect("Failed to parse");

		let mut vm = SimpleExecutor::default();

		assert_eq!(vm.evaluate_program(&simple_define), Ok(ForgeValue::Null));
		assert_eq!(vm.evaluate_program(&assignment), Ok(ForgeValue::from(123)));
		assert_eq!(
			vm.evaluate_program(&redecl_err),
			Err(RuntimeError::VariableAlreadyDefined(String::from("foo")))
		);
		assert_eq!(
			vm.evaluate_program(&missing_err),
			Err(RuntimeError::UndefinedVariable(String::from("missing")))
		);
		assert_eq!(vm.evaluate_program(&use_vars), Ok(ForgeValue::from(246)));
		assert_eq!(vm.get_variable("splop"), Some(&ForgeValue::from(246)));
	}