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

use pest::iterators::Pair;

use crate::{
    parser::{tokenize, Rule},
    Value,
};

fn eval_ctx(input: &str, ctx: &mut HashMap<String, Value>) -> Result<Value, String> {
    fn eval_node(pair: Pair<Rule>, ctx: &mut HashMap<String, Value>) -> Value {
        match pair.as_rule() {
            Rule::Num => Value::Num(pair.as_str().parse().unwrap()),
            Rule::Unit => Value::Unit,
            Rule::Seq => {
                let it = pair.into_inner();
                let mut result = Value::Unit;
                for pair in it {
                    result = eval_node(pair, ctx);
                }
                result
            }
            Rule::Ident => {
                let ident = pair
                    .into_inner()
                    .next()
                    .expect("No identifier found")
                    .to_string();
                ctx.get(&ident)
                    .unwrap_or_else(|| panic!("Ident error: `{ident}` not defined."))
                    .clone()
            }
            Rule::Let => {
                let mut pair = pair.into_inner();
                let key = pair.next().expect("Let expressions has no LHS").to_string();
                let value = eval_node(pair.next().expect("Let expression has no RHS"), ctx);
                // TODO: Remove shadowing
                ctx.try_insert(key.clone(), value).unwrap_or_else(|_| {
                    panic!("Let error: `{key}` shadows previously defined variable.")
                });
                ().into()
            }
            // The rest of the rules are silent, producing no tokens:
            Rule::EOI
            | Rule::WHITESPACE
            | Rule::COMMENT
            | Rule::Block
            | Rule::Expr
            | Rule::Value
            | Rule::Char => unreachable!(),
        }
    }
    let ast = tokenize(input).map_err(|e| e.to_string())?;
    Ok(eval_node(ast, ctx))
}

/// Evaluate program from input string
///
/// # Errors
///
/// Errors if parse is not successful.
pub fn eval(input: &str) -> Result<Value, String> {
    eval_ctx(input, &mut HashMap::new())
}

#[cfg(test)]
mod tests {
    use pest::Parser;

    use crate::parser::UrParser;

    use super::*;

    fn test(input: &str, result: impl Into<Value>) {
        assert_eq!(eval(input).unwrap(), result.into())
    }

    #[test]
    #[should_panic]
    fn empty_should_not_parse() {
        _ = eval("").unwrap();
    }

    #[test]
    #[should_panic]
    fn whitespace_should_not_parse() {
        _ = eval(" ").unwrap();
    }

    #[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]
    #[should_panic]
    fn ident_cannot_start_with_number() {
        UrParser::parse(Rule::Let, "let 1foo = 10").unwrap();
    }

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