From d1082cb159ba66f2923cfa60e907a713727da856 Mon Sep 17 00:00:00 2001 From: minneelyyyy Date: Thu, 17 Oct 2024 15:53:00 -0400 Subject: [PATCH] fix recursive functions --- src/executor.rs | 64 ++++++++++++------------ src/lib.rs | 24 ++++++++- src/parser.rs | 126 +++++++++++++++++++++++++++--------------------- 3 files changed, 126 insertions(+), 88 deletions(-) diff --git a/src/executor.rs b/src/executor.rs index d4d4528..0c22c1a 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,4 +1,4 @@ -use super::{Value, Type, Function}; +use super::{Value, Type, Object, Evaluation, Function}; use super::parser::{ParseTree, ParseError}; use std::collections::HashMap; @@ -44,22 +44,6 @@ impl Display for RuntimeError { impl Error for RuntimeError {} -#[derive(Clone, Debug)] -enum Evaluation { - // at this point, it's type is set in stone - Computed(Value), - - // at this point, it's type is unknown, and may contradict a variable's type - // or not match the expected value of the expression, this is a runtime error - Uncomputed(Box), -} - -#[derive(Clone, Debug)] -enum Object { - Variable(Evaluation), - Function(Function), -} - /// Executes an input of ParseTrees pub struct Executor<'a, I> where @@ -114,6 +98,13 @@ where (Value::Int(x), Value::Float(y)) => Ok(Value::Float(x as f64 + y)), (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x + y)), (Value::String(x), Value::String(y)) => Ok(Value::String(format!("{x}{y}"))), + (Value::Array(xtype, x), Value::Array(ytype, y)) => { + if xtype != ytype { + return Err(RuntimeError::TypeError(xtype, ytype)); + } + + Ok(Value::Array(xtype, [x, y].concat())) + }, (Value::Array(t, x), y) => { let ytype = y.get_type(); @@ -296,25 +287,38 @@ where match obj { Some(Object::Function(f)) => { - let locals = locals.to_mut(); - assert!(f.arg_names.is_some()); - assert!(f.body.is_some()); - for ((t, name), tree) in std::iter::zip(std::iter::zip(f.t.1, f.arg_names.unwrap()), args) { - let v = self.exec(Box::new(tree), &mut Cow::Borrowed(locals))?; + let loc = std::iter::zip(std::iter::zip(f.t.1.clone(), f.arg_names.clone().unwrap()), args) + .map(|((t, name), tree)| { + let v = self.exec(Box::new(tree), locals)?; - if v.get_type() != t && t != Type::Any { - return Err(RuntimeError::TypeError(t, v.get_type())); - } + if t != v.get_type() { + return Err(RuntimeError::TypeError(t, v.get_type())); + } - locals.insert(name.clone(), match v { - Value::Function(func) => Object::Function(func), - _ => Object::Variable(Evaluation::Computed(v)) - }); + match v { + Value::Function(f) => Ok((Object::Function(f), name)), + v => Ok((Object::Variable(Evaluation::Computed(v)), name)), + } + }).collect::, RuntimeError>>()?; + + let mut locals = f.locals.clone(); + + for (obj, name) in loc.into_iter() { + locals.insert(name, obj); } - self.exec(f.body.unwrap(), &mut Cow::Borrowed(&locals)) + // the parser previously placed a copy of this function with the same name and type + // into it's locals, however it doesn't have a body. This would cause a + // panic later when attempting to execute the function during recursive calls. + // we fix this by replacing it with a *complete* copy of the function. + // also only do this if the function has a name in the first place, otherwise it panics with lambdas. + if let Some(name) = f.name.clone() { + locals.insert(name, Object::Function(f.clone())); + } + + self.exec(f.body.unwrap(), &mut Cow::Borrowed(&Box::new(locals))) } _ => Err(RuntimeError::FunctionUndefined(ident.clone())) } diff --git a/src/lib.rs b/src/lib.rs index 15de2c8..da3caa3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ use executor::{Executor, RuntimeError}; use parser::{ParseTree, Parser}; use tokenizer::Tokenizer; +use std::collections::HashMap; use std::fmt::Display; use std::io::{Write, Read, BufRead}; use std::fmt; @@ -104,28 +105,47 @@ impl Display for FunctionType { } } +#[derive(Clone, Debug, PartialEq)] +enum Evaluation { + // at this point, it's type is set in stone + Computed(Value), + + // at this point, it's type is unknown, and may contradict a variable's type + // or not match the expected value of the expression, this is a runtime error + Uncomputed(Box), +} + +#[derive(Clone, Debug, PartialEq)] +enum Object { + Variable(Evaluation), + Function(Function), +} + #[derive(Clone, Debug, PartialEq)] pub struct Function { name: Option, t: FunctionType, + locals: HashMap, arg_names: Option>, body: Option>, } impl Function { - fn lambda(t: FunctionType, arg_names: Vec, body: Option>) -> Self { + fn lambda(t: FunctionType, arg_names: Vec, locals: HashMap, body: Option>) -> Self { Self { name: None, t, + locals, arg_names: Some(arg_names), body } } - fn named(name: &str, t: FunctionType, arg_names: Option>, body: Option>) -> Self { + fn named(name: &str, t: FunctionType, arg_names: Option>, locals: HashMap, body: Option>) -> Self { Self { name: Some(name.to_string()), t, + locals, arg_names, body } diff --git a/src/parser.rs b/src/parser.rs index c9c1572..3b5646b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,4 +1,6 @@ -use super::{Value, Type, Function, FunctionType}; +use crate::Evaluation; + +use super::{Value, Type, Object, Function, FunctionType}; use super::tokenizer::{Token, TokenizeError, Op}; use std::error; @@ -13,8 +15,6 @@ pub enum ParseError { UnexpectedEndInput, IdentifierUndefined(String), InvalidIdentifier(Token), - FunctionUndefined(String), - VariableUndefined(String), UnmatchedArrayClose, UnwantedToken(Token), TokenizeError(TokenizeError), @@ -25,10 +25,8 @@ impl Display for ParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ParseError::UnexpectedEndInput => write!(f, "Input ended unexpectedly"), - ParseError::IdentifierUndefined(name) => write!(f, "Undefined variable `{name}`"), + ParseError::IdentifierUndefined(name) => write!(f, "Undefined identifier `{name}`"), ParseError::InvalidIdentifier(t) => write!(f, "Invalid identifier `{t:?}`"), - ParseError::FunctionUndefined(name) => write!(f, "Undefined function `{name}`"), - ParseError::VariableUndefined(name) => write!(f, "Undefined variable `{name}`"), ParseError::NoInput => write!(f, "No input given"), ParseError::UnmatchedArrayClose => write!(f, "there was an unmatched array closing operator `]`"), ParseError::TokenizeError(e) => write!(f, "Tokenizer Error: {e}"), @@ -122,8 +120,8 @@ macro_rules! three_arg { impl ParseTree { fn parse( tokens: &mut Peekable, - globals: &HashMap, - locals: &mut Cow>) -> Result + globals: &HashMap, + locals: &mut Cow>) -> Result where I: Iterator>, { @@ -132,16 +130,18 @@ impl ParseTree { match token { Token::Constant(c) => Ok(Self::Constant(c)), Token::Identifier(ident) => { - // If it is found to be a function, get its argument count. - // During parsing, we only keep track of function definitions - // so that we know how many arguments it takes - if let Some(f) = locals.clone().get(&ident).or(globals.clone().get(&ident)) { - let args = f.t.1.iter() - .map(|_| ParseTree::parse(tokens, globals, locals)).collect::, ParseError>>()?; - - Ok(ParseTree::FunctionCall(ident.clone(), args)) + if let Some(obj) = locals.clone().get(&ident).or(globals.clone().get(&ident)) { + match obj { + Object::Function(f) => { + let args = f.t.1.iter() + .map(|_| ParseTree::parse(tokens, globals, locals)).collect::, ParseError>>()?; + + Ok(ParseTree::FunctionCall(ident, args)) + } + Object::Variable(e) => Ok(ParseTree::Variable(ident)), + } } else { - Ok(ParseTree::Variable(ident.clone())) + Err(ParseError::IdentifierUndefined(ident)) } } Token::Operator(op) => { @@ -174,33 +174,38 @@ impl ParseTree { } } Op::FunctionDefine(arg_count) => { - let mut f = ParseTree::parse_function(tokens, arg_count)?; + let f = { + let mut f = ParseTree::parse_function(tokens, arg_count)?; - assert!(f.arg_names.is_some()); - assert!(f.name.is_some()); - assert!(f.body.is_none()); + if locals.contains_key(&f.name.clone().unwrap()) { + return Err(ParseError::ImmutableError(f.name.unwrap())); + } - if locals.contains_key(&f.name.clone().unwrap()) { - return Err(ParseError::ImmutableError(f.name.unwrap())); - } + f.locals = locals.to_mut().clone(); + + // recursion requires that f's prototype is present in locals + f.locals.insert(f.name.clone().unwrap(), Object::Function(f.clone())); + + // we also need any function parameters in local scope + for (name, t) in std::iter::zip(f.arg_names.clone().unwrap(), f.t.1.clone()) { + match t { + Type::Function(t) => { + f.locals.insert(name.clone(), Object::Function(Function::named(&name, t, None, HashMap::new(), None))); + } + _ => { + // the value isn't important, just that the identifier is there + f.locals.insert(name.clone(), Object::Variable(Evaluation::Computed(Value::Nil))); + } + } + } + + f.body = Some(Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&f.locals))?)); + + f + }; let locals = locals.to_mut(); - - // recursion requires that f's prototype is present in locals - locals.insert(f.name.clone().unwrap(), f.clone()); - - // we also need any function aprameters in local scope - for (name, t) in std::iter::zip(f.arg_names.clone().unwrap(), f.t.1.clone()) { - match t { - Type::Function(t) => { - locals.insert(name.clone(), Function::named(&name, t, None, None)); - } - _ => (), - } - } - - f.body = Some(Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&locals))?)); - assert!(f.body.is_some()); + locals.insert(f.name.clone().unwrap(), Object::Function(f.clone())); Ok(ParseTree::FunctionDefinition(f, Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&locals))?))) }, @@ -256,20 +261,29 @@ impl ParseTree { Op::And => two_arg!(And, tokens, globals, locals), Op::Or => two_arg!(Or, tokens, globals, locals), Op::LambdaDefine(arg_count) => { - let mut f = ParseTree::parse_lambda(tokens, arg_count)?; + let f = { + let mut f = ParseTree::parse_lambda(tokens, arg_count)?; - let locals = locals.to_mut(); - - for (name, t) in std::iter::zip(f.arg_names.clone().unwrap(), f.t.1.clone()) { - match t { - Type::Function(t) => { - locals.insert(name.clone(), Function::named(&name, t, None, None)); + let locals = locals.to_mut(); + f.locals = locals.clone(); + + // we need any function parameters in local scope + for (name, t) in std::iter::zip(f.arg_names.clone().unwrap(), f.t.1.clone()) { + match t { + Type::Function(t) => { + f.locals.insert(name.clone(), Object::Function(Function::named(&name, t, None, HashMap::new(), None))); + } + _ => { + // the value isn't important, just that the identifier is there + f.locals.insert(name.clone(), Object::Variable(Evaluation::Computed(Value::Nil))); + } } - _ => (), } - } + + f.body = Some(Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&f.locals))?)); - f.body = Some(Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&locals))?)); + f + }; Ok(ParseTree::LambdaDefinition(f)) } @@ -297,7 +311,7 @@ impl ParseTree { I: Iterator>, { let (t, args) = Self::parse_function_declaration(tokens, arg_count)?; - Ok(Function::lambda(t, args, None)) + Ok(Function::lambda(t, args, HashMap::new(), None)) } fn parse_function(tokens: &mut Peekable, arg_count: usize) -> Result @@ -307,7 +321,7 @@ impl ParseTree { let name = Self::get_identifier(tokens.next())?; let (t, args) = Self::parse_function_declaration(tokens, arg_count)?; - Ok(Function::named(&name, t, Some(args), None)) + Ok(Function::named(&name, t, Some(args), HashMap::new(), None)) } fn parse_function_declaration(tokens: &mut Peekable, arg_count: usize) -> Result<(FunctionType, Vec), ParseError> @@ -434,8 +448,8 @@ pub(crate) struct Parser>> { // These are used to keep track of functions in the current context // by the parser. otherwise the parser would have no way to tell // if the program `* a b 12` is supposed to be ((* a b) (12)) or (* (a b) 12) - globals: HashMap, - locals: HashMap, + globals: HashMap, + locals: HashMap, } impl>> Parser { @@ -447,7 +461,7 @@ impl>> Parser { } } - pub fn globals(self, globals: HashMap) -> Self { + pub fn globals(self, globals: HashMap) -> Self { Self { tokens: self.tokens, globals, @@ -455,7 +469,7 @@ impl>> Parser { } } - pub fn locals(self, locals: HashMap) -> Self { + pub fn locals(self, locals: HashMap) -> Self { Self { tokens: self.tokens, globals: self.globals,