bugfix: in function environments which was causing incorrect environment state in some situations
This commit is contained in:
parent
c92f4e9e82
commit
90fa04b29f
|
@ -2,20 +2,22 @@ use {
|
||||||
crate::parser::ast::{BlockStatement, Identifier, Node},
|
crate::parser::ast::{BlockStatement, Identifier, Node},
|
||||||
itertools::Itertools,
|
itertools::Itertools,
|
||||||
std::{
|
std::{
|
||||||
|
cell::RefCell,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::{Display, Formatter, Result as FmtResult, Write},
|
fmt::{Display, Formatter, Result as FmtResult, Write},
|
||||||
|
rc::Rc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
pub mod tree_walker;
|
pub mod tree_walker;
|
||||||
|
|
||||||
pub trait Evaluator {
|
pub trait Evaluator {
|
||||||
fn eval(&self, node: Node, env: &mut Environment) -> Option<Object>;
|
fn eval(&self, node: Node, env: Rc<RefCell<Environment>>) -> Option<Object>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
store: HashMap<String, Object>,
|
store: HashMap<String, Object>,
|
||||||
outer: Option<Box<Environment>>,
|
outer: Option<Rc<RefCell<Environment>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Environment {
|
impl Environment {
|
||||||
|
@ -29,7 +31,10 @@ impl Environment {
|
||||||
match self.store.get(name) {
|
match self.store.get(name) {
|
||||||
Some(v) => Some(v.clone()),
|
Some(v) => Some(v.clone()),
|
||||||
None => match &self.outer {
|
None => match &self.outer {
|
||||||
Some(outer) => outer.get(name),
|
Some(outer) => {
|
||||||
|
let outer = outer.borrow();
|
||||||
|
outer.get(name)
|
||||||
|
}
|
||||||
None => None,
|
None => None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -38,10 +43,10 @@ impl Environment {
|
||||||
self.store.insert(name, val);
|
self.store.insert(name, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_enclosed(env: Environment) -> Self {
|
pub fn new_enclosed(env: Rc<RefCell<Environment>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
store: HashMap::new(),
|
store: HashMap::new(),
|
||||||
outer: Some(Box::new(env)),
|
outer: Some(env),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,12 +106,12 @@ impl Display for Object {
|
||||||
pub struct Function {
|
pub struct Function {
|
||||||
parameters: Vec<Identifier>,
|
parameters: Vec<Identifier>,
|
||||||
body: BlockStatement,
|
body: BlockStatement,
|
||||||
env: Environment,
|
env: Rc<RefCell<Environment>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::assert_matches::assert_matches;
|
use std::{assert_matches::assert_matches, cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
evaluator::{tree_walker::TreeWalker, Environment, Evaluator, Object, FALSE, NULL, TRUE},
|
evaluator::{tree_walker::TreeWalker, Environment, Evaluator, Object, FALSE, NULL, TRUE},
|
||||||
|
@ -122,8 +127,8 @@ mod tests {
|
||||||
assert!(program.is_some());
|
assert!(program.is_some());
|
||||||
let program = program.unwrap();
|
let program = program.unwrap();
|
||||||
let evaluator = TreeWalker::new();
|
let evaluator = TreeWalker::new();
|
||||||
let mut env = Environment::new();
|
let env = Rc::new(RefCell::new(Environment::new()));
|
||||||
let eval = evaluator.eval(Node::Program(program), &mut env);
|
let eval = evaluator.eval(Node::Program(program), env);
|
||||||
assert_eq!(eval, test.1);
|
assert_eq!(eval, test.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,8 +310,8 @@ mod tests {
|
||||||
assert!(program.is_some());
|
assert!(program.is_some());
|
||||||
let program = program.unwrap();
|
let program = program.unwrap();
|
||||||
let evaluator = TreeWalker::new();
|
let evaluator = TreeWalker::new();
|
||||||
let mut env = Environment::new();
|
let env = Rc::new(RefCell::new(Environment::new()));
|
||||||
let eval = evaluator.eval(Node::Program(program), &mut env);
|
let eval = evaluator.eval(Node::Program(program), env);
|
||||||
let node = eval.unwrap();
|
let node = eval.unwrap();
|
||||||
|
|
||||||
assert_matches!(node, Object::Function(_));
|
assert_matches!(node, Object::Function(_));
|
||||||
|
@ -364,6 +369,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn memory_test() {
|
fn memory_test() {
|
||||||
|
// This test case allocates memory for `foobar=9999` over and over
|
||||||
|
// even though it is never used.
|
||||||
let test_cases = [(
|
let test_cases = [(
|
||||||
"let counter = fn(x) {
|
"let counter = fn(x) {
|
||||||
if (x > 100 ) {
|
if (x > 100 ) {
|
||||||
|
@ -375,7 +382,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
counter(0);
|
counter(0);
|
||||||
",
|
",
|
||||||
Some(Object::Integer(15)),
|
Some(Object::Boolean(true)),
|
||||||
)];
|
)];
|
||||||
|
|
||||||
run_test_cases(&test_cases);
|
run_test_cases(&test_cases);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
// TODO: This is all a mess. Almost certainly because right now, I don't know any better way to do this.
|
// TODO: This is all a mess. Almost certainly because right now, I don't know any better way to do this.
|
||||||
// It's just constantly unwrapping enums from one place and rewrapping it to some other enum(or even the same enum) and returning it
|
// It's just constantly unwrapping enums from one place and rewrapping it to some other enum(or even the same enum) and returning it
|
||||||
// The error handling story is pretty bad too
|
// The error handling story is pretty bad too
|
||||||
|
@ -21,7 +23,7 @@ impl TreeWalker {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Evaluator for TreeWalker {
|
impl Evaluator for TreeWalker {
|
||||||
fn eval(&self, node: Node, env: &mut Environment) -> Option<Object> {
|
fn eval(&self, node: Node, env: Rc<RefCell<Environment>>) -> Option<Object> {
|
||||||
match node {
|
match node {
|
||||||
Node::Program(p) => self.eval_program(p, env),
|
Node::Program(p) => self.eval_program(p, env),
|
||||||
Node::Statement(stmt) => match stmt {
|
Node::Statement(stmt) => match stmt {
|
||||||
|
@ -34,8 +36,10 @@ impl Evaluator for TreeWalker {
|
||||||
Some(Object::ReturnValue(Box::new(ret_val)))
|
Some(Object::ReturnValue(Box::new(ret_val)))
|
||||||
}
|
}
|
||||||
Statement::Let(LetStatement { name, value }) => {
|
Statement::Let(LetStatement { name, value }) => {
|
||||||
let value = self.eval(Node::Expression(value?), env)?;
|
let value = self.eval(Node::Expression(value?), env.clone())?;
|
||||||
|
let mut env = env.borrow_mut();
|
||||||
env.set(name.to_string(), value.clone());
|
env.set(name.to_string(), value.clone());
|
||||||
|
|
||||||
Some(value)
|
Some(value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -48,7 +52,7 @@ impl Evaluator for TreeWalker {
|
||||||
self.eval_prefix_expression(p.operator, expr)
|
self.eval_prefix_expression(p.operator, expr)
|
||||||
}
|
}
|
||||||
Expression::InfixExpression(ie) => {
|
Expression::InfixExpression(ie) => {
|
||||||
let left = self.eval(Node::Expression(*ie.left), env)?;
|
let left = self.eval(Node::Expression(*ie.left), env.clone())?;
|
||||||
let right = self.eval(Node::Expression(*ie.right), env)?;
|
let right = self.eval(Node::Expression(*ie.right), env)?;
|
||||||
self.eval_infix_expression(left, ie.operator, right)
|
self.eval_infix_expression(left, ie.operator, right)
|
||||||
}
|
}
|
||||||
|
@ -56,11 +60,11 @@ impl Evaluator for TreeWalker {
|
||||||
return Some(Object::Function(Function {
|
return Some(Object::Function(Function {
|
||||||
body: fnl.body,
|
body: fnl.body,
|
||||||
parameters: fnl.parameters,
|
parameters: fnl.parameters,
|
||||||
env: env.clone(),
|
env: env,
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
Expression::CallExpression(v) => {
|
Expression::CallExpression(v) => {
|
||||||
let function = self.eval(Node::Expression(*v.function), env)?;
|
let function = self.eval(Node::Expression(*v.function), env.clone())?;
|
||||||
// Resolve function arguments and update the environment
|
// Resolve function arguments and update the environment
|
||||||
// before executing function body
|
// before executing function body
|
||||||
let args = match self.eval_expression(v.arguments, env) {
|
let args = match self.eval_expression(v.arguments, env) {
|
||||||
|
@ -71,7 +75,7 @@ impl Evaluator for TreeWalker {
|
||||||
self.apply_function(function, args)
|
self.apply_function(function, args)
|
||||||
}
|
}
|
||||||
Expression::IfExpression(ie) => {
|
Expression::IfExpression(ie) => {
|
||||||
let condition = self.eval(Node::Expression(*ie.condition), env)?;
|
let condition = self.eval(Node::Expression(*ie.condition), env.clone())?;
|
||||||
|
|
||||||
if self.is_truthy(&condition) {
|
if self.is_truthy(&condition) {
|
||||||
self.eval(
|
self.eval(
|
||||||
|
@ -90,10 +94,10 @@ impl Evaluator for TreeWalker {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TreeWalker {
|
impl TreeWalker {
|
||||||
fn eval_program(&self, prg: Program, env: &mut Environment) -> Option<Object> {
|
fn eval_program(&self, prg: Program, env: Rc<RefCell<Environment>>) -> Option<Object> {
|
||||||
let mut out: Option<Object> = None;
|
let mut out: Option<Object> = None;
|
||||||
for stmt in prg.statements {
|
for stmt in prg.statements {
|
||||||
out = self.eval(Node::Statement(stmt), env);
|
out = self.eval(Node::Statement(stmt), env.clone());
|
||||||
// No need to evaluate any more statements from a statements vector once we
|
// No need to evaluate any more statements from a statements vector once we
|
||||||
// get a return keyword. nothing after in the block matters.
|
// get a return keyword. nothing after in the block matters.
|
||||||
if let Some(out) = out.as_ref() {
|
if let Some(out) = out.as_ref() {
|
||||||
|
@ -107,11 +111,15 @@ impl TreeWalker {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_block_statement(&self, bs: BlockStatement, env: &mut Environment) -> Option<Object> {
|
fn eval_block_statement(
|
||||||
|
&self,
|
||||||
|
bs: BlockStatement,
|
||||||
|
env: Rc<RefCell<Environment>>,
|
||||||
|
) -> Option<Object> {
|
||||||
let mut out: Option<Object> = None;
|
let mut out: Option<Object> = None;
|
||||||
|
|
||||||
for stmt in bs.statements {
|
for stmt in bs.statements {
|
||||||
out = self.eval(Node::Statement(stmt), env);
|
out = self.eval(Node::Statement(stmt), env.clone());
|
||||||
|
|
||||||
// TODO: Find a nicer way to do this. :(
|
// TODO: Find a nicer way to do this. :(
|
||||||
// The objective here is,
|
// The objective here is,
|
||||||
|
@ -208,7 +216,8 @@ impl TreeWalker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_identifier(&self, node: Identifier, env: &mut Environment) -> Option<Object> {
|
fn eval_identifier(&self, node: Identifier, env: Rc<RefCell<Environment>>) -> Option<Object> {
|
||||||
|
let env = env.borrow();
|
||||||
env.get(&node.to_string()).or(Some(Object::Error(format!(
|
env.get(&node.to_string()).or(Some(Object::Error(format!(
|
||||||
"identifier not found: {}",
|
"identifier not found: {}",
|
||||||
node.to_string()
|
node.to_string()
|
||||||
|
@ -218,12 +227,12 @@ impl TreeWalker {
|
||||||
fn eval_expression(
|
fn eval_expression(
|
||||||
&self,
|
&self,
|
||||||
exprs: Vec<Expression>,
|
exprs: Vec<Expression>,
|
||||||
env: &mut Environment,
|
env: Rc<RefCell<Environment>>,
|
||||||
) -> Result<Vec<Object>, Object> {
|
) -> Result<Vec<Object>, Object> {
|
||||||
let mut out = vec![];
|
let mut out = vec![];
|
||||||
|
|
||||||
for expr in exprs {
|
for expr in exprs {
|
||||||
match self.eval(Node::Expression(expr), env) {
|
match self.eval(Node::Expression(expr), env.clone()) {
|
||||||
Some(v @ Object::Error(_)) => return Err(v),
|
Some(v @ Object::Error(_)) => return Err(v),
|
||||||
Some(v) => out.push(v),
|
Some(v) => out.push(v),
|
||||||
None => {
|
None => {
|
||||||
|
@ -243,7 +252,6 @@ impl TreeWalker {
|
||||||
}
|
}
|
||||||
v => return Some(Object::Error(format!("not a function: {}", v.to_string()))),
|
v => return Some(Object::Error(format!("not a function: {}", v.to_string()))),
|
||||||
};
|
};
|
||||||
println!("{:?}", function.env);
|
|
||||||
|
|
||||||
let mut enclosed_env = Environment::new_enclosed(function.env);
|
let mut enclosed_env = Environment::new_enclosed(function.env);
|
||||||
for (i, parameter) in function.parameters.iter().enumerate() {
|
for (i, parameter) in function.parameters.iter().enumerate() {
|
||||||
|
@ -256,7 +264,7 @@ impl TreeWalker {
|
||||||
|
|
||||||
let resp = self.eval(
|
let resp = self.eval(
|
||||||
Node::Statement(Statement::BlockStatement(function.body)),
|
Node::Statement(Statement::BlockStatement(function.body)),
|
||||||
&mut enclosed_env,
|
Rc::new(RefCell::new(enclosed_env)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Unwrap return here to prevent it from bubbling up the stack
|
// Unwrap return here to prevent it from bubbling up the stack
|
||||||
|
|
10
src/repl.rs
10
src/repl.rs
|
@ -4,7 +4,11 @@ use {
|
||||||
lexer::Lexer,
|
lexer::Lexer,
|
||||||
parser::{ast::Node, Error as ParserError, Parser},
|
parser::{ast::Node, Error as ParserError, Parser},
|
||||||
},
|
},
|
||||||
std::io::{self, BufRead, Result as IoResult, Write},
|
std::{
|
||||||
|
cell::RefCell,
|
||||||
|
io::{self, BufRead, Result as IoResult, Write},
|
||||||
|
rc::Rc,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const PROMPT: &[u8] = b">> ";
|
const PROMPT: &[u8] = b">> ";
|
||||||
|
@ -19,7 +23,7 @@ pub fn init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start<R: BufRead, W: Write>(mut ip: R, mut out: W) {
|
fn start<R: BufRead, W: Write>(mut ip: R, mut out: W) {
|
||||||
let mut environment = Environment::new();
|
let environment = Rc::new(RefCell::new(Environment::new()));
|
||||||
loop {
|
loop {
|
||||||
out.write_all(PROMPT).unwrap();
|
out.write_all(PROMPT).unwrap();
|
||||||
out.flush().unwrap();
|
out.flush().unwrap();
|
||||||
|
@ -35,7 +39,7 @@ fn start<R: BufRead, W: Write>(mut ip: R, mut out: W) {
|
||||||
}
|
}
|
||||||
let program = program.unwrap();
|
let program = program.unwrap();
|
||||||
let evaluator = TreeWalker::new();
|
let evaluator = TreeWalker::new();
|
||||||
let obj = evaluator.eval(Node::Program(program), &mut environment);
|
let obj = evaluator.eval(Node::Program(program), environment.clone());
|
||||||
if let Some(node) = obj {
|
if let Some(node) = obj {
|
||||||
out.write_fmt(format_args!("{}\n", node.inspect())).unwrap();
|
out.write_fmt(format_args!("{}\n", node.inspect())).unwrap();
|
||||||
out.flush().unwrap();
|
out.flush().unwrap();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user