Newer
Older
use crate::parse::ast::{

Louis
committed
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)]
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 {
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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)
}
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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())),

Louis
committed
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> {
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);
}

Louis
committed
VoidExpression::Return(Return { expr }) => {
let value = self.evaluate_value_expression(expr.as_ref())?;
eprintln!("Currently Unsupported: Return. Value: {}", value);
}
}
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();
}
#[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();
}
#[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");
}
#[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)