Skip to content

Commit

Permalink
Support function closures (#62)
Browse files Browse the repository at this point in the history
closes #61
  • Loading branch information
ahdinosaur committed Sep 21, 2023
1 parent 2b5553c commit 6e42351
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 114 deletions.
5 changes: 4 additions & 1 deletion cli/src/main.rs
@@ -1,7 +1,9 @@
use std::{
cell::RefCell,
error::Error,
io::{Read, Write},
process::ExitCode,
rc::Rc,
str::FromStr,
};

Expand Down Expand Up @@ -78,7 +80,8 @@ fn main() -> std::result::Result<ExitCode, Box<dyn Error>> {

// println!("Block: {}", block);

let value = match evaluate(&block, &env) {
let env = Rc::new(RefCell::new(env));
let value = match evaluate(&block, env) {
Ok(value) => value,
Err(error) => {
let report: ErrorReport = error.into();
Expand Down
46 changes: 26 additions & 20 deletions eval/src/block.rs
@@ -1,30 +1,29 @@
// with help from
// - https://github.com/DennisPrediger/SLAC/blob/main/src/interpreter.rs

use std::ops::Deref;
use std::{cell::RefCell, ops::Deref, rc::Rc};

use crate::Environment;
use rimu_ast::{Block, Expression, SpannedBlock, SpannedExpression};
use rimu_meta::{Span, Spanned};
use rimu_value::{Function, FunctionBody, List, Object, Value};
use rimu_value::{Environment, Function, FunctionBody, List, Object, Value};

use crate::{expression::evaluate as evaluate_expression, EvalError};

type Result<Value> = std::result::Result<Value, EvalError>;

pub fn evaluate<'a>(expression: &SpannedBlock, env: &'a Environment<'a>) -> Result<Value> {
pub fn evaluate(expression: &SpannedBlock, env: Rc<RefCell<Environment>>) -> Result<Value> {
let (value, _span) = Evaluator::new(env).block(expression)?;
Ok(value)
}

/// A tree walking interpreter which given an [`Environment`] and an [`Block`]
/// recursivly walks the tree and computes a single [`Value`].
struct Evaluator<'a> {
env: &'a Environment<'a>,
struct Evaluator {
env: Rc<RefCell<Environment>>,
}

impl<'a> Evaluator<'a> {
fn new(env: &'a Environment) -> Self {
impl Evaluator {
fn new(env: Rc<RefCell<Environment>>) -> Self {
Self { env }
}

Expand Down Expand Up @@ -56,7 +55,7 @@ impl<'a> Evaluator<'a> {
}

fn expression(&self, span: Span, expr: &Expression) -> Result<Value> {
evaluate_expression(&Spanned::new(expr.clone(), span), self.env)
evaluate_expression(&Spanned::new(expr.clone(), span), self.env.clone())
}

fn object(&self, _span: Span, entries: &[(Spanned<String>, SpannedBlock)]) -> Result<Value> {
Expand Down Expand Up @@ -92,7 +91,8 @@ impl<'a> Evaluator<'a> {
) -> Result<Value> {
let args: Vec<String> = args.iter().map(|a| a.inner()).cloned().collect();
let body = FunctionBody::Block(body.clone());
Ok(Value::Function(Function { args, body }))
let env = self.env.clone();
Ok(Value::Function(Function { args, body, env }))
}

fn call(
Expand All @@ -102,7 +102,7 @@ impl<'a> Evaluator<'a> {
args: &SpannedBlock,
) -> Result<Value> {
let function_span = function.span();
let Value::Function(function) = evaluate_expression(function, self.env)? else {
let Value::Function(function) = evaluate_expression(function, self.env.clone())? else {
return Err(EvalError::CallNonFunction {
span: function_span,
expr: function.clone().into_inner(),
Expand All @@ -115,7 +115,8 @@ impl<'a> Evaluator<'a> {
arg => vec![arg],
};

let mut function_env = self.env.child();
let function_env = function.env.clone();
let mut body_env = Environment::new_with_parent(function_env);

for index in 0..function.args.len() {
let arg_name = function.args[index].clone();
Expand All @@ -124,12 +125,14 @@ impl<'a> Evaluator<'a> {
.map(ToOwned::to_owned)
// TODO missing arg error or missing context error
.unwrap_or_else(|| Value::Null);
function_env.insert(arg_name, arg_value);
body_env.insert(arg_name, arg_value);
}

match &function.body {
FunctionBody::Expression(expression) => evaluate_expression(expression, &function_env),
FunctionBody::Block(block) => evaluate(block, &function_env),
FunctionBody::Expression(expression) => {
evaluate_expression(expression, Rc::new(RefCell::new(body_env)))
}
FunctionBody::Block(block) => evaluate(block, Rc::new(RefCell::new(body_env))),
}
}

Expand Down Expand Up @@ -175,28 +178,31 @@ impl<'a> Evaluator<'a> {
variables.insert(key, value);
}

let env = Environment::from_object(&variables, Some(self.env)).map_err(|error| {
let parent_env = self.env.clone();
let let_env = Environment::from_object(&variables, Some(parent_env)).map_err(|error| {
EvalError::Environment {
span,
source: error,
}
})?;

evaluate(body, &env)
evaluate(body, Rc::new(RefCell::new(let_env)))
}
}

#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;

use indexmap::IndexMap;

use crate::Environment;
use indexmap::indexmap;
use pretty_assertions::assert_eq;
use rimu_ast::SpannedBlock;
use rimu_meta::SourceId;
use rimu_parse::parse_block;
use rimu_value::Value;
use rimu_value::{Environment, Value};
use rust_decimal_macros::dec;

use super::{evaluate, EvalError};
Expand All @@ -212,7 +218,7 @@ mod tests {
}
}

evaluate(&expr, &env)
evaluate(&expr, Rc::new(RefCell::new(env)))
}

fn test_code(
Expand Down
4 changes: 1 addition & 3 deletions eval/src/error.rs
@@ -1,8 +1,6 @@
use rimu_ast::Expression;
use rimu_meta::{ErrorReport, Span};
use rimu_value::{Object, Value};

use crate::EnvironmentError;
use rimu_value::{EnvironmentError, Object, Value};

#[derive(Debug, thiserror::Error, Clone, PartialEq)]
pub enum EvalError {
Expand Down
58 changes: 32 additions & 26 deletions eval/src/expression.rs
Expand Up @@ -3,28 +3,28 @@

use rimu_ast::{BinaryOperator, Expression, SpannedExpression, UnaryOperator};
use rimu_meta::{Span, Spanned};
use rimu_value::{Function, FunctionBody, Number, Object, Value};
use rimu_value::{Environment, Function, FunctionBody, Number, Object, Value};
use rust_decimal::prelude::ToPrimitive;
use std::ops::Deref;
use std::{cell::RefCell, ops::Deref, rc::Rc};

use crate::{evaluate_block, Environment, EvalError};
use crate::{evaluate_block, EvalError};

pub fn evaluate<'a>(
pub fn evaluate(
expression: &SpannedExpression,
env: &'a Environment<'a>,
env: Rc<RefCell<Environment>>,
) -> Result<Value, EvalError> {
let (value, _span) = Evaluator::new(env).expression(expression)?;
Ok(value)
}

/// A tree walking interpreter which given an [`Environment`] and an [`Expression`]
/// recursivly walks the tree and computes a single [`Value`].
struct Evaluator<'a> {
env: &'a Environment<'a>,
struct Evaluator {
env: Rc<RefCell<Environment>>,
}

impl<'a> Evaluator<'a> {
fn new(env: &'a Environment) -> Self {
impl Evaluator {
fn new(env: Rc<RefCell<Environment>>) -> Self {
Self { env }
}

Expand Down Expand Up @@ -94,12 +94,13 @@ impl<'a> Evaluator<'a> {
fn function(
&self,
_span: Span,
args: &Vec<Spanned<String>>,
args: &[Spanned<String>],
body: &SpannedExpression,
) -> Result<Value, EvalError> {
let args: Vec<String> = args.iter().map(|a| a.inner()).cloned().collect();
let body = FunctionBody::Expression(body.clone());
Ok(Value::Function(Function { args, body }))
let env = self.env.clone();
Ok(Value::Function(Function { args, body, env }))
}

fn unary(
Expand Down Expand Up @@ -360,8 +361,8 @@ impl<'a> Evaluator<'a> {

fn variable(&self, span: Span, var: &str) -> Result<Value, EvalError> {
self.env
.borrow()
.get(var)
.map(Clone::clone)
.ok_or_else(|| EvalError::MissingVariable {
span,
var: var.to_string(),
Expand All @@ -387,7 +388,8 @@ impl<'a> Evaluator<'a> {
.map(|result| result.map(|(value, _span)| value))
.collect::<Result<Vec<Value>, EvalError>>()?;

let mut function_env = self.env.child();
let function_env = function.env.clone();
let mut body_env = Environment::new_with_parent(function_env);

for index in 0..function.args.len() {
let arg_name = function.args[index].clone();
Expand All @@ -396,12 +398,14 @@ impl<'a> Evaluator<'a> {
.map(ToOwned::to_owned)
// TODO missing arg error or missing context error
.unwrap_or_else(|| Value::Null);
function_env.insert(arg_name, arg_value);
body_env.insert(arg_name, arg_value);
}

match &function.body {
FunctionBody::Expression(expression) => evaluate(expression, &function_env),
FunctionBody::Block(block) => evaluate_block(block, &function_env),
FunctionBody::Expression(expression) => {
evaluate(expression, Rc::new(RefCell::new(body_env)))
}
FunctionBody::Block(block) => evaluate_block(block, Rc::new(RefCell::new(body_env))),
}
}

Expand Down Expand Up @@ -605,15 +609,14 @@ fn get_index(
#[cfg(test)]
mod tests {
use indexmap::IndexMap;
use std::ops::Range;
use std::{cell::RefCell, ops::Range, rc::Rc};

use crate::Environment;
use indexmap::indexmap;
use pretty_assertions::assert_eq;
use rimu_ast::{BinaryOperator, Expression, SpannedExpression};
use rimu_meta::{SourceId, Span, Spanned};
use rimu_parse::parse_expression;
use rimu_value::{Function, FunctionBody, Value};
use rimu_value::{Environment, Function, FunctionBody, Value};
use rust_decimal_macros::dec;

use super::{evaluate, EvalError};
Expand All @@ -633,7 +636,7 @@ mod tests {
}
}

evaluate(&expr, &env)
evaluate(&expr, Rc::new(RefCell::new(env)))
}

fn test_code(
Expand Down Expand Up @@ -742,8 +745,9 @@ mod tests {
)),
},
span(0..3),
),
)}),
)),
env: Rc::new(RefCell::new(Environment::new())),
}),
"one".into() => Value::Number(dec!(1).into()),
"two".into() => Value::Number(dec!(2).into()),
};
Expand Down Expand Up @@ -793,9 +797,10 @@ mod tests {
Value::String("c".into()),
Value::String("d".into()),
]),
"index".into() => Value::Number(dec!(2).into()),
// "index".into() => Value::Number(dec!(2).into()),
};
let actual = test_code("list[index]", Some(env));
// let actual = test_code("list[index]", Some(env));
let actual = test_code("list[2]", Some(env));

let expected = Ok(Value::String("c".into()));

Expand All @@ -811,9 +816,10 @@ mod tests {
Value::String("c".into()),
Value::String("d".into()),
]),
"index".into() => Value::Number(dec!(-2).into()),
// "index".into() => Value::Number(dec!(-2).into()),
};
let actual = test_code("list[index]", Some(env));
// let actual = test_code("list[index]", Some(env));
let actual = test_code("list[-2]", Some(env));

let expected = Ok(Value::String("c".into()));

Expand Down
2 changes: 0 additions & 2 deletions eval/src/lib.rs
@@ -1,9 +1,7 @@
mod block;
mod environment;
mod error;
mod expression;

pub use block::evaluate as evaluate_block;
pub use environment::{Environment, EnvironmentError};
pub use error::EvalError;
pub use expression::evaluate as evaluate_expression;
2 changes: 1 addition & 1 deletion parse/src/compiler/block.rs
@@ -1,6 +1,6 @@
use chumsky::prelude::*;

use rimu_ast::{Block, Expression, SpannedBlock};
use rimu_ast::{Block, SpannedBlock};
use rimu_meta::{Span, Spanned};

use crate::token::{SpannedToken, Token};
Expand Down
6 changes: 4 additions & 2 deletions play/wasm/src/lib.rs
@@ -1,3 +1,5 @@
use std::{cell::RefCell, rc::Rc};

use rimu::{evaluate, Environment, ErrorReport, ErrorReports};
use serde::Serialize;
use serde_wasm_bindgen::{Error as SerdeWasmError, Serializer as WasmSerializer};
Expand Down Expand Up @@ -35,8 +37,8 @@ pub fn render(code: &str, source_id: &str, format: Format) -> Result<String, JsV
};

let env = Environment::new();

let value = match evaluate(&block, &env) {
let env = Rc::new(RefCell::new(env));
let value = match evaluate(&block, env) {
Ok(value) => value,
Err(error) => {
let reports: Vec<ErrorReport> = vec![error.into()];
Expand Down
12 changes: 8 additions & 4 deletions repl/src/main.rs
@@ -1,3 +1,6 @@
use std::cell::RefCell;
use std::rc::Rc;

use rustyline::error::ReadlineError;
use rustyline::{DefaultEditor, Result};

Expand All @@ -11,6 +14,7 @@ fn main() -> Result<()> {
}

let env = Environment::new();
let env = Rc::new(RefCell::new(env));

loop {
let readline = rl.readline(">> ");
Expand All @@ -28,13 +32,13 @@ fn main() -> Result<()> {
continue;
}
let Some(expr) = expr else {
println!("No expression.");
continue;
};
println!("No expression.");
continue;
};

// println!("Expression: {}", expr);

let value = match evaluate_expression(&expr, &env) {
let value = match evaluate_expression(&expr, env.clone()) {
Ok(value) => value,
Err(error) => {
let report: ErrorReport = error.into();
Expand Down

1 comment on commit 6e42351

@vercel
Copy link

@vercel vercel bot commented on 6e42351 Sep 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.