fix recursive functions
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use super::{Value, Type, Function};
|
use super::{Value, Type, Object, Evaluation, Function};
|
||||||
use super::parser::{ParseTree, ParseError};
|
use super::parser::{ParseTree, ParseError};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -44,22 +44,6 @@ impl Display for RuntimeError {
|
|||||||
|
|
||||||
impl Error 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<ParseTree>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
enum Object {
|
|
||||||
Variable(Evaluation),
|
|
||||||
Function(Function),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Executes an input of ParseTrees
|
/// Executes an input of ParseTrees
|
||||||
pub struct Executor<'a, I>
|
pub struct Executor<'a, I>
|
||||||
where
|
where
|
||||||
@@ -114,6 +98,13 @@ where
|
|||||||
(Value::Int(x), Value::Float(y)) => Ok(Value::Float(x as f64 + y)),
|
(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::Float(x), Value::Float(y)) => Ok(Value::Float(x + y)),
|
||||||
(Value::String(x), Value::String(y)) => Ok(Value::String(format!("{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) => {
|
(Value::Array(t, x), y) => {
|
||||||
let ytype = y.get_type();
|
let ytype = y.get_type();
|
||||||
|
|
||||||
@@ -296,25 +287,38 @@ where
|
|||||||
|
|
||||||
match obj {
|
match obj {
|
||||||
Some(Object::Function(f)) => {
|
Some(Object::Function(f)) => {
|
||||||
let locals = locals.to_mut();
|
|
||||||
|
|
||||||
assert!(f.arg_names.is_some());
|
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 loc = std::iter::zip(std::iter::zip(f.t.1.clone(), f.arg_names.clone().unwrap()), args)
|
||||||
let v = self.exec(Box::new(tree), &mut Cow::Borrowed(locals))?;
|
.map(|((t, name), tree)| {
|
||||||
|
let v = self.exec(Box::new(tree), locals)?;
|
||||||
|
|
||||||
if v.get_type() != t && t != Type::Any {
|
if t != v.get_type() {
|
||||||
return Err(RuntimeError::TypeError(t, v.get_type()));
|
return Err(RuntimeError::TypeError(t, v.get_type()));
|
||||||
}
|
}
|
||||||
|
|
||||||
locals.insert(name.clone(), match v {
|
match v {
|
||||||
Value::Function(func) => Object::Function(func),
|
Value::Function(f) => Ok((Object::Function(f), name)),
|
||||||
_ => Object::Variable(Evaluation::Computed(v))
|
v => Ok((Object::Variable(Evaluation::Computed(v)), name)),
|
||||||
});
|
}
|
||||||
|
}).collect::<Result<Vec<(Object, String)>, 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()))
|
_ => Err(RuntimeError::FunctionUndefined(ident.clone()))
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/lib.rs
24
src/lib.rs
@@ -6,6 +6,7 @@ use executor::{Executor, RuntimeError};
|
|||||||
use parser::{ParseTree, Parser};
|
use parser::{ParseTree, Parser};
|
||||||
use tokenizer::Tokenizer;
|
use tokenizer::Tokenizer;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::io::{Write, Read, BufRead};
|
use std::io::{Write, Read, BufRead};
|
||||||
use std::fmt;
|
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<ParseTree>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
enum Object {
|
||||||
|
Variable(Evaluation),
|
||||||
|
Function(Function),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Function {
|
pub struct Function {
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
t: FunctionType,
|
t: FunctionType,
|
||||||
|
locals: HashMap<String, Object>,
|
||||||
arg_names: Option<Vec<String>>,
|
arg_names: Option<Vec<String>>,
|
||||||
body: Option<Box<ParseTree>>,
|
body: Option<Box<ParseTree>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Function {
|
impl Function {
|
||||||
fn lambda(t: FunctionType, arg_names: Vec<String>, body: Option<Box<ParseTree>>) -> Self {
|
fn lambda(t: FunctionType, arg_names: Vec<String>, locals: HashMap<String, Object>, body: Option<Box<ParseTree>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: None,
|
name: None,
|
||||||
t,
|
t,
|
||||||
|
locals,
|
||||||
arg_names: Some(arg_names),
|
arg_names: Some(arg_names),
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn named(name: &str, t: FunctionType, arg_names: Option<Vec<String>>, body: Option<Box<ParseTree>>) -> Self {
|
fn named(name: &str, t: FunctionType, arg_names: Option<Vec<String>>, locals: HashMap<String, Object>, body: Option<Box<ParseTree>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: Some(name.to_string()),
|
name: Some(name.to_string()),
|
||||||
t,
|
t,
|
||||||
|
locals,
|
||||||
arg_names,
|
arg_names,
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
|
|||||||
122
src/parser.rs
122
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 super::tokenizer::{Token, TokenizeError, Op};
|
||||||
|
|
||||||
use std::error;
|
use std::error;
|
||||||
@@ -13,8 +15,6 @@ pub enum ParseError {
|
|||||||
UnexpectedEndInput,
|
UnexpectedEndInput,
|
||||||
IdentifierUndefined(String),
|
IdentifierUndefined(String),
|
||||||
InvalidIdentifier(Token),
|
InvalidIdentifier(Token),
|
||||||
FunctionUndefined(String),
|
|
||||||
VariableUndefined(String),
|
|
||||||
UnmatchedArrayClose,
|
UnmatchedArrayClose,
|
||||||
UnwantedToken(Token),
|
UnwantedToken(Token),
|
||||||
TokenizeError(TokenizeError),
|
TokenizeError(TokenizeError),
|
||||||
@@ -25,10 +25,8 @@ impl Display for ParseError {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ParseError::UnexpectedEndInput => write!(f, "Input ended unexpectedly"),
|
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::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::NoInput => write!(f, "No input given"),
|
||||||
ParseError::UnmatchedArrayClose => write!(f, "there was an unmatched array closing operator `]`"),
|
ParseError::UnmatchedArrayClose => write!(f, "there was an unmatched array closing operator `]`"),
|
||||||
ParseError::TokenizeError(e) => write!(f, "Tokenizer Error: {e}"),
|
ParseError::TokenizeError(e) => write!(f, "Tokenizer Error: {e}"),
|
||||||
@@ -122,8 +120,8 @@ macro_rules! three_arg {
|
|||||||
impl ParseTree {
|
impl ParseTree {
|
||||||
fn parse<I>(
|
fn parse<I>(
|
||||||
tokens: &mut Peekable<I>,
|
tokens: &mut Peekable<I>,
|
||||||
globals: &HashMap<String, Function>,
|
globals: &HashMap<String, Object>,
|
||||||
locals: &mut Cow<HashMap<String, Function>>) -> Result<Self, ParseError>
|
locals: &mut Cow<HashMap<String, Object>>) -> Result<Self, ParseError>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = Result<Token, TokenizeError>>,
|
I: Iterator<Item = Result<Token, TokenizeError>>,
|
||||||
{
|
{
|
||||||
@@ -132,16 +130,18 @@ impl ParseTree {
|
|||||||
match token {
|
match token {
|
||||||
Token::Constant(c) => Ok(Self::Constant(c)),
|
Token::Constant(c) => Ok(Self::Constant(c)),
|
||||||
Token::Identifier(ident) => {
|
Token::Identifier(ident) => {
|
||||||
// If it is found to be a function, get its argument count.
|
if let Some(obj) = locals.clone().get(&ident).or(globals.clone().get(&ident)) {
|
||||||
// During parsing, we only keep track of function definitions
|
match obj {
|
||||||
// so that we know how many arguments it takes
|
Object::Function(f) => {
|
||||||
if let Some(f) = locals.clone().get(&ident).or(globals.clone().get(&ident)) {
|
let args = f.t.1.iter()
|
||||||
let args = f.t.1.iter()
|
.map(|_| ParseTree::parse(tokens, globals, locals)).collect::<Result<Vec<_>, ParseError>>()?;
|
||||||
.map(|_| ParseTree::parse(tokens, globals, locals)).collect::<Result<Vec<_>, ParseError>>()?;
|
|
||||||
|
|
||||||
Ok(ParseTree::FunctionCall(ident.clone(), args))
|
Ok(ParseTree::FunctionCall(ident, args))
|
||||||
|
}
|
||||||
|
Object::Variable(e) => Ok(ParseTree::Variable(ident)),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(ParseTree::Variable(ident.clone()))
|
Err(ParseError::IdentifierUndefined(ident))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Token::Operator(op) => {
|
Token::Operator(op) => {
|
||||||
@@ -174,33 +174,38 @@ impl ParseTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Op::FunctionDefine(arg_count) => {
|
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());
|
if locals.contains_key(&f.name.clone().unwrap()) {
|
||||||
assert!(f.name.is_some());
|
return Err(ParseError::ImmutableError(f.name.unwrap()));
|
||||||
assert!(f.body.is_none());
|
}
|
||||||
|
|
||||||
if locals.contains_key(&f.name.clone().unwrap()) {
|
f.locals = locals.to_mut().clone();
|
||||||
return Err(ParseError::ImmutableError(f.name.unwrap()));
|
|
||||||
}
|
// 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();
|
let locals = locals.to_mut();
|
||||||
|
locals.insert(f.name.clone().unwrap(), Object::Function(f.clone()));
|
||||||
// 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());
|
|
||||||
|
|
||||||
Ok(ParseTree::FunctionDefinition(f, Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&locals))?)))
|
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::And => two_arg!(And, tokens, globals, locals),
|
||||||
Op::Or => two_arg!(Or, tokens, globals, locals),
|
Op::Or => two_arg!(Or, tokens, globals, locals),
|
||||||
Op::LambdaDefine(arg_count) => {
|
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();
|
let locals = locals.to_mut();
|
||||||
|
f.locals = locals.clone();
|
||||||
|
|
||||||
for (name, t) in std::iter::zip(f.arg_names.clone().unwrap(), f.t.1.clone()) {
|
// we need any function parameters in local scope
|
||||||
match t {
|
for (name, t) in std::iter::zip(f.arg_names.clone().unwrap(), f.t.1.clone()) {
|
||||||
Type::Function(t) => {
|
match t {
|
||||||
locals.insert(name.clone(), Function::named(&name, t, None, None));
|
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(&locals))?));
|
f.body = Some(Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&f.locals))?));
|
||||||
|
|
||||||
|
f
|
||||||
|
};
|
||||||
|
|
||||||
Ok(ParseTree::LambdaDefinition(f))
|
Ok(ParseTree::LambdaDefinition(f))
|
||||||
}
|
}
|
||||||
@@ -297,7 +311,7 @@ impl ParseTree {
|
|||||||
I: Iterator<Item = Result<Token, TokenizeError>>,
|
I: Iterator<Item = Result<Token, TokenizeError>>,
|
||||||
{
|
{
|
||||||
let (t, args) = Self::parse_function_declaration(tokens, arg_count)?;
|
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<I>(tokens: &mut Peekable<I>, arg_count: usize) -> Result<Function, ParseError>
|
fn parse_function<I>(tokens: &mut Peekable<I>, arg_count: usize) -> Result<Function, ParseError>
|
||||||
@@ -307,7 +321,7 @@ impl ParseTree {
|
|||||||
let name = Self::get_identifier(tokens.next())?;
|
let name = Self::get_identifier(tokens.next())?;
|
||||||
let (t, args) = Self::parse_function_declaration(tokens, arg_count)?;
|
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<I>(tokens: &mut Peekable<I>, arg_count: usize) -> Result<(FunctionType, Vec<String>), ParseError>
|
fn parse_function_declaration<I>(tokens: &mut Peekable<I>, arg_count: usize) -> Result<(FunctionType, Vec<String>), ParseError>
|
||||||
@@ -434,8 +448,8 @@ pub(crate) struct Parser<I: Iterator<Item = Result<Token, TokenizeError>>> {
|
|||||||
// These are used to keep track of functions in the current context
|
// These are used to keep track of functions in the current context
|
||||||
// by the parser. otherwise the parser would have no way to tell
|
// 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)
|
// if the program `* a b 12` is supposed to be ((* a b) (12)) or (* (a b) 12)
|
||||||
globals: HashMap<String, Function>,
|
globals: HashMap<String, Object>,
|
||||||
locals: HashMap<String, Function>,
|
locals: HashMap<String, Object>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: Iterator<Item = Result<Token, TokenizeError>>> Parser<I> {
|
impl<I: Iterator<Item = Result<Token, TokenizeError>>> Parser<I> {
|
||||||
@@ -447,7 +461,7 @@ impl<I: Iterator<Item = Result<Token, TokenizeError>>> Parser<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn globals(self, globals: HashMap<String, Function>) -> Self {
|
pub fn globals(self, globals: HashMap<String, Object>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tokens: self.tokens,
|
tokens: self.tokens,
|
||||||
globals,
|
globals,
|
||||||
@@ -455,7 +469,7 @@ impl<I: Iterator<Item = Result<Token, TokenizeError>>> Parser<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn locals(self, locals: HashMap<String, Function>) -> Self {
|
pub fn locals(self, locals: HashMap<String, Object>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tokens: self.tokens,
|
tokens: self.tokens,
|
||||||
globals: self.globals,
|
globals: self.globals,
|
||||||
|
|||||||
Reference in New Issue
Block a user