Skip to content
Snippets Groups Projects
eval.rs 3.93 KiB
Newer Older
Jasper Gräflich's avatar
Jasper Gräflich committed
use std::collections::HashMap;

Jasper Gräflich's avatar
Jasper Gräflich committed
use crate::{Error, Node, Result, Value};

/// Evaluate given AST using given context.
Jasper Gräflich's avatar
Jasper Gräflich committed
fn eval_ctx(ast: Node, ctx: &mut HashMap<String, Value>) -> Result<Value> {
    Ok(match ast {
Jasper Gräflich's avatar
Jasper Gräflich committed
        crate::Node::Num(n) => Value::Num(n),
        crate::Node::Unit => Value::Unit,
        crate::Node::Seq(nodes) => {
Jasper Gräflich's avatar
Jasper Gräflich committed
            let mut result = Value::Unit;
Jasper Gräflich's avatar
Jasper Gräflich committed
                result = eval_ctx(node, ctx)?;
Jasper Gräflich's avatar
Jasper Gräflich committed
            }
Jasper Gräflich's avatar
Jasper Gräflich committed
        }
Jasper Gräflich's avatar
Jasper Gräflich committed
        crate::Node::Let { ident, expr, .. } => {
Jasper Gräflich's avatar
Jasper Gräflich committed
            let value = eval_ctx(*expr, ctx)?;
Jasper Gräflich's avatar
Jasper Gräflich committed
            // Shadowing is caught by the typechecker, no check here.
            ctx.insert(ident.clone(), value);
            Value::Unit
Jasper Gräflich's avatar
Jasper Gräflich committed
        crate::Node::Ident(ident) => *ctx.get(&ident).ok_or(Error::Eval(format!(
            "Ident error: `{ident}` is not defined"
        )))?,
Jasper Gräflich's avatar
Jasper Gräflich committed
    })
Jasper Gräflich's avatar
Jasper Gräflich committed
}

/// Evaluate given AST using a fresh context.
Jasper Gräflich's avatar
Jasper Gräflich committed
///
/// # Errors
///
/// Returns error if evaluation fails.
pub fn eval(ast: Node) -> Result<Value> {
    eval_ctx(ast, &mut HashMap::new())
Jasper Gräflich's avatar
Jasper Gräflich committed
}

#[cfg(test)]
mod tests {
    use crate::parser::ast;
Jasper Gräflich's avatar
Jasper Gräflich committed

    use super::*;

    fn test(input: &str, result: impl Into<Value>) {
Jasper Gräflich's avatar
Jasper Gräflich committed
        assert_eq!(
            eval(ast(input).expect("Parse in test unsuccessful"))
                .expect("Eval in test unsuccessful"),
            result.into()
        )
Jasper Gräflich's avatar
Jasper Gräflich committed
    }

    #[test]
    fn unit() {
        test("{}", ());
    }

    #[test]
    fn unit_with_space() {
        test("{ }", ());
    }

    #[test]
    fn block_of_unit_with_space() {
        test("{ { } }", ());
    }

    #[test]
    fn num() {
        test("42", 42);
    }

    #[test]
    fn block_of_num() {
        test("{42}", 42);
    }

    #[test]
    fn block_of_num_whitespace() {
        test("{ 42 }", 42);
    }

    #[test]
    fn seq_of_unit() {
        test("{}; { }", ());
    }

    #[test]
    fn block_of_seq_of_unit() {
        test("{{ }; {}}", ());
    }

    #[test]
    fn seq_of_num() {
        test("1; 2", 2);
    }

    #[test]
    #[should_panic]
    fn empty_seq_snd_fail() {
        test("1;", ());
    }

    #[test]
    #[should_panic]
    fn empty_seq_fst_fail() {
        test(";1", ());
    }

    #[test]
    #[should_panic]
    fn empty_seq_fail() {
        test(";", ());
    }

    #[test]
    fn block_of_seq_of_num() {
        test("{1; 2}", 2);
    }

    #[test]
    fn block_of_seq_of_mixed() {
        test("{{ }; 2}", 2);
    }

    #[test]
    fn block_of_seq_len_3() {
        test("{1; 3; 2}", 2);
    }

    #[test]
    fn seq_len_3() {
        test("1; 3; 2", 2);
    }

    #[test]
    fn block_of_seq_len_3_mixed() {
        test("{1; { } ; 2}", 2);
    }

    #[test]
    fn seq_len_3_mixed() {
        test("{} ; 3; { }", ());
    }

    #[test]
    fn let_char() {
        test("let x = {}", ());
    }

    #[test]
    fn let_long() {
        test("let foo = 42", ());
    }

    #[test]
    fn let_ident_with_number() {
        test("let foo1 = 42", ());
    }

    #[test]
    fn let_then_unit() {
        test("let x = 10; {}", ());
    }
Jasper Gräflich's avatar
Jasper Gräflich committed

    #[test]
    #[should_panic]
    fn bad_lookup() {
        test("x", ())
    }

    #[test]
    #[should_panic]
    fn bad_lookup_long() {
        test("foo", ())
    }

    #[test]
    #[should_panic]
    fn bad_lookup_in_seq() {
        test("let foo = 42; bar", ())
    }

    #[test]
    fn lookup() {
        test("let foo = 42; foo", 42)
    }

    #[test]
    #[should_panic]
    fn shadowing() {
        test("let foo = {}; let foo = 42; foo", ())
    }

    #[test]
    fn program_of_block_unit() {
        test("{let foo = {}; foo}", ())
    }

    #[test]
    fn program_of_block_number() {
        test("{let foo = 42; foo}", 42)
    }

    #[test]
    fn number_with_leading_zeros() {
        test("001", 1)
    }

    #[test]
    fn two_blocks() {
        test("{1; 2}; {3; 4}", 4)
    }

    #[test]
    fn nested_let_blocks() {
        test("let foo = {let bar = 4; {let baz = 5}; bar}; foo", 4)
    }
Jasper Gräflich's avatar
Jasper Gräflich committed
}