Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/target
.idea
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

128 changes: 62 additions & 66 deletions src/interpreter/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,44 @@ use crate::ir::ast::Statement;

type Environment = HashMap<String, i32>;

pub fn eval(exp: Expression, env: &Environment) -> Result<i32, String> {
pub fn eval(exp: &Expression, env: &Environment) -> Result<i32, String> {
match exp {
Expression::CInt(v) => Ok(v),
Expression::Add(lhs, rhs) => Ok(eval(*lhs, env)? + eval(*rhs, env)?),
Expression::Sub(lhs, rhs) => Ok(eval(*lhs, env)? - eval(*rhs, env)?),
Expression::Mul(lhs, rhs) => Ok(eval(*lhs, env)? * eval(*rhs, env)?),
Expression::Div(lhs, rhs) => Ok(eval(*lhs, env)? / eval(*rhs, env)?),
Expression::Var(name) => match env.get(&name) {
Some(value) => Ok(*value),
None => Err(String::from("Variable {name} not found"))
}
Expression::CInt(v) => Ok(*v),
Expression::Add(lhs, rhs) => Ok(eval(lhs, env)? + eval(rhs, env)?),
Expression::Sub(lhs, rhs) => Ok(eval(lhs, env)? - eval(rhs, env)?),
Expression::Mul(lhs, rhs) => Ok(eval(lhs, env)? * eval(rhs, env)?),
Expression::Div(lhs, rhs) => Ok(eval(lhs, env)? / eval(rhs, env)?),
Expression::Var(name) => match env.get(name) {
Some(&value) => Ok(value),
None => Err(String::from("Variable {name} not found")),
},
}
}

pub fn execute(stmt: Statement, env: & mut Environment) -> Result<&Environment, String> {
pub fn execute<'a>(stmt: &Statement, env: &'a mut Environment) -> Result<&'a Environment, String> {
match stmt {
Statement::Assignment(name, exp) => {
let value = eval(*exp, &env)?;
env.insert(*name, value);
Ok(env)
},
Statement::IfThenElse(cond, stmt_then, stmt_else) => {
let value = eval(*cond, &env)?;
if value > 0 {
execute(*stmt_then, env)
}
else {
execute(*stmt_else, env)
}
},
// Statement::While(cond, stmt) => {
// let mut value = eval(*cond, &env)?;
// let mut new_env = env.clone();
// while value > 0 {
// new_env = execute(*stmt, &mut new_env)?.clone();
// value = eval(*cond, &env)?;
// }
// Ok(&new_env)
// }
_ => Err(String::from("not implemented yet"))
Statement::Assignment(name, exp) => {
let value = eval(exp, env)?;
env.insert(*name.clone(), value);
Ok(env)
}
Statement::IfThenElse(cond, stmt_then, stmt_else) => {
let value = eval(cond, env)?;
if value > 0 {
execute(stmt_then, env)
} else {
execute(stmt_else, env)
}
}
Statement::While(cond, stmt) => {
let mut value = eval(cond, env)?;
while value > 0 {
execute(stmt, env)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi @helpmehelpus, thanks for the detailed pull request!

Question: In this line, should we make it more straightforward that we accumulate changes in the environment during each iteration of the while loop? Would something like env = execute(stmt, env) improve readability and make the code more "functional"? Otherwise, it currently feels as though env might be intended as a global variable.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@rbonifacio yes, we can. It looks like we two main options here.

If we just do

while value > 0 {
    env = execute(stmt, env)?;
    value = eval(cond, env)?;
}

we get two errors, both related to the current signature of execute:

pub fn execute<'a>(stmt: &Statement, env: &'a mut Environment) -> Result<&'a Environment, String> {

The first error is that we cannot assign multiple times to the immutable variable env. Note that, even though it holds a mutable reference, the parameter itself is not annotated with mut, and is therefore immutable.

The second error is that we are returning an immutable reference from the function, so there is a type mismatch between the input env and the output.

Option 1 (least preferred imo)

We can do

Statement::While(cond, stmt) => {
    let mut value = eval(cond, env)?;
    while value > 0 {
        let env = execute(stmt, env)?; // could also be let _ = execute(stmt, env)?; still smells bad
        value = eval(cond, env)?;
    }
    Ok(env)
}

Here we are basically leveraging Rust's shadowing to define an env variable that is scoped to the while block. We assume that we are mutating the outer env on each call to execute inside the while block and storing its result in a new variable to compute the result of eval.
Keep in mind that the inner let env goes out of scope at the end of the while block, and that Ok(env) will return the mutated environment parameter. This does not look good to me, as we are essentially using two different env objects in execute(stmt, env)? and in eval(cond, env)?. Just to explain this is confusing, let alone keeping this in mind as the code gets more complicated.

Option 2 (probably best)

If I understand what you are asking, we want to use the same env parameter throughout the entire function. So we can change the signature of execute to:

pub fn execute<'a>(stmt: &Statement, mut env: &'a mut Environment) -> Result<&'a mut Environment, String> {

and now we can do what you asked:

while value > 0 {
    env = execute(stmt, env)?;
    value = eval(cond, env)?;
}

There is now a single env, the mutation is explicitly and there is less confusion.

Please let me know if this is the desired outcome, and I will commit the changes.

value = eval(cond, env)?;
}
Ok(env)
}
_ => Err(String::from("not implemented yet")),
}
}

Expand All @@ -55,56 +53,54 @@ mod tests {

#[test]
fn eval_constant() {
let env = HashMap::new();
let c10 = Expression::CInt(10);
let c20 = Expression::CInt(20);
let env = HashMap::new();
let c10 = Expression::CInt(10);
let c20 = Expression::CInt(20);

assert_eq!(eval(c10, &env), Ok(10));
assert_eq!(eval(c20, &env), Ok(20));
assert_eq!(eval(&c10, &env), Ok(10));
assert_eq!(eval(&c20, &env), Ok(20));
}

#[test]
fn eval_add_expression1() {
let env = HashMap::new();
let c10 = Expression::CInt(10);
let c20 = Expression::CInt(20);
let add1 = Expression::Add(Box::new(c10), Box::new(c20));
assert_eq!(eval(add1, &env), Ok(30));
let env = HashMap::new();
let c10 = Expression::CInt(10);
let c20 = Expression::CInt(20);
let add1 = Expression::Add(Box::new(c10), Box::new(c20));
assert_eq!(eval(&add1, &env), Ok(30));
}

#[test]
fn eval_add_expression2() {
let env = HashMap::new();
let c10 = Expression::CInt(10);
let c20 = Expression::CInt(20);
let c30 = Expression::CInt(30);
let add1 = Expression::Add(Box::new(c10), Box::new(c20));
let add2 = Expression::Add(Box::new(add1), Box::new(c30));
assert_eq!(eval(add2, &env), Ok(60));
let env = HashMap::new();
let c10 = Expression::CInt(10);
let c20 = Expression::CInt(20);
let c30 = Expression::CInt(30);
let add1 = Expression::Add(Box::new(c10), Box::new(c20));
let add2 = Expression::Add(Box::new(add1), Box::new(c30));
assert_eq!(eval(&add2, &env), Ok(60));
}

#[test]
fn eval_mul_expression() {
let env = HashMap::new();
let c10 = Expression::CInt(10);
let c20 = Expression::CInt(20);
let mul1 = Expression::Mul(Box::new(c10), Box::new(c20));
assert_eq!(eval(mul1, &env), Ok(200));
let env = HashMap::new();
let c10 = Expression::CInt(10);
let c20 = Expression::CInt(20);
let mul1 = Expression::Mul(Box::new(c10), Box::new(c20));
assert_eq!(eval(&mul1, &env), Ok(200));
}

#[test]
fn eval_variable() {
let env = HashMap::from([(String::from("x"), 10), (String::from("y"), 20)]);
let v1 = Expression::Var(String::from("x"));
let v2 = Expression::Var(String::from("y"));
assert_eq!(eval(v1, &env), Ok(10));
assert_eq!(eval(v2, &env), Ok(20));
let env = HashMap::from([(String::from("x"), 10), (String::from("y"), 20)]);
let v1 = Expression::Var(String::from("x"));
let v2 = Expression::Var(String::from("y"));

assert_eq!(eval(&v1, &env), Ok(10));
assert_eq!(eval(&v2, &env), Ok(20));
}

// TODO: Write more unit tests here.
// (a) variable not found
// ...
}


4 changes: 2 additions & 2 deletions src/ir/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub enum Expression {
Add(Box<Expression>, Box<Expression>),
Sub(Box<Expression>, Box<Expression>),
Mul(Box<Expression>, Box<Expression>),
Div(Box<Expression>, Box<Expression>)
Div(Box<Expression>, Box<Expression>),
}

pub enum Statement {
Expand All @@ -15,5 +15,5 @@ pub enum Statement {
Assignment(Box<Name>, Box<Expression>),
IfThenElse(Box<Expression>, Box<Statement>, Box<Statement>),
While(Box<Expression>, Box<Statement>),
Sequence(Box<Statement>, Box<Statement>)
Sequence(Box<Statement>, Box<Statement>),
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
//use crate::ir::ast::Statement;
//use crate::interpreter::interpreter::eval;

pub mod ir;
pub mod interpreter;
pub mod ir;

fn main() {
println!("Hello, world!");
Expand Down