From f8b051c15b3197a8514895d37a3bd6f0ff159a32 Mon Sep 17 00:00:00 2001 From: Louis Capitanchik <contact@louiscap.co> Date: Tue, 16 May 2023 18:30:20 +0100 Subject: [PATCH] Support variable assignment, state, basic environment --- .../src/runtime/executor/simple.rs | 107 ++++++++++++++++-- forge-script-lang/src/runtime/value.rs | 3 +- 2 files changed, 102 insertions(+), 8 deletions(-) diff --git a/forge-script-lang/src/runtime/executor/simple.rs b/forge-script-lang/src/runtime/executor/simple.rs index 13cad9a..f4f99ac 100644 --- a/forge-script-lang/src/runtime/executor/simple.rs +++ b/forge-script-lang/src/runtime/executor/simple.rs @@ -1,22 +1,31 @@ use crate::parse::ast::{ - BinaryOp, DeclareFunction, ExpressionList, Print, Program, UnaryOp, ValueExpression, - VoidExpression, + 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 SimpleExecutor { +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), } @@ -37,13 +46,63 @@ impl Display for RuntimeError { ) } 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, @@ -92,14 +151,24 @@ impl Visitor for SimpleExecutor { ValueExpression::Grouped(group) => self.evaluate_value_expression(group.inner.as_ref()), ValueExpression::Block(_) => Err(RuntimeError::Unsupported("Block")), ValueExpression::Literal(lit) => Ok(ForgeValue::from(lit.clone())), - ValueExpression::DeclareIdentifier(_) => { - Err(RuntimeError::Unsupported("DeclareIdentifier")) - } + 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(_) => Err(RuntimeError::Unsupported("Assignment")), ValueExpression::ConditionalBlock(_) => { Err(RuntimeError::Unsupported("ConditionalBlock")) } - ValueExpression::Identifier(_) => Err(RuntimeError::Unsupported("Identifier")), + 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")) @@ -174,4 +243,28 @@ mod interpreter_test { 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))); + } } diff --git a/forge-script-lang/src/runtime/value.rs b/forge-script-lang/src/runtime/value.rs index 1078730..ad6e04f 100644 --- a/forge-script-lang/src/runtime/value.rs +++ b/forge-script-lang/src/runtime/value.rs @@ -4,7 +4,7 @@ use crate::runtime::numbers::Number; use std::fmt::{Display, Formatter}; use std::ops::{Add, Div, Mul, Not, Rem, Sub}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -23,6 +23,7 @@ pub enum ForgeValue { Boolean(bool), String(String), List(Vec<ForgeValue>), + #[default] Null, } -- GitLab