#![cfg(test)] use pretty_assertions::assert_eq; use test_case::test_case; use crate::{ symbol_table::SymbolTable, tree_walk_eval::{evaluator::Evaluator, State}, type_inference::TypeContext, }; fn evaluate_input(input: &str) -> Result { let ast = crate::util::quick_ast(input); let mut symbol_table = SymbolTable::new(); let mut type_context = TypeContext::new(); symbol_table.process_ast(&ast, &mut type_context).unwrap(); let reduced_ir = crate::reduced_ir::reduce(&ast, &symbol_table, &type_context); reduced_ir.debug(&symbol_table); println!("========"); symbol_table.debug(); let mut state = State::new(); let mut evaluator = Evaluator::new(&mut state, &type_context); let mut outputs = evaluator.evaluate(reduced_ir, true); outputs.pop().unwrap() } fn eval_assert(input: &str, expected: &str) { assert_eq!(evaluate_input(input), Ok(expected.to_string())); } fn eval_assert_failure(input: &str, expected: &str) { assert_eq!(evaluate_input(input), Err(expected.to_string())); } #[test] fn test_basic_eval() { eval_assert("1 + 2", "3"); eval_assert("let mut a = 1; a = 2", "()"); eval_assert("let mut a = 1; a = a + 2; a", "3"); } #[test] fn op_eval() { eval_assert("-13", "-13"); eval_assert("10 - 2", "8"); } #[test] fn function_eval() { eval_assert("fn oi(x) { x + 1 }; oi(4)", "5"); eval_assert("fn oi(x) { x + 1 }; oi(1+2)", "4"); } #[test] fn scopes() { let scope_ok = r#" let a = 20 fn haha() { let something = 38 let a = 10 a } haha() "#; eval_assert(scope_ok, "10"); let scope_ok = r#" let a = 20 fn queque() { let a = 10 a } a "#; eval_assert(scope_ok, "20"); } #[test] fn eval_scopes_2() { eval_assert( r#" fn trad() { let a = 10 fn jinner() { let b = 20 b } a + jinner() } trad()"#, "30", ); let err = "No symbol found for name: `a`"; eval_assert_failure( r#" fn trad() { let a = 10 fn inner() { let b = 20 a + b } inner() } trad() "#, err, ); } #[test] fn adt_output_1() { let source = r#" type Option = Some(T) | None let a = Option::None let b = Option::Some(10) (b, a) "#; eval_assert(source, "(Some(10), None)"); } #[test] fn adt_output_2() { let source = r#" type Gobble = Unknown | Rufus { a: Int, torrid: Nat } let b = Gobble::Rufus { a: 3, torrid: 99 } b "#; eval_assert(source, "Rufus { a: 3, torrid: 99 }"); let source = r#" type Gobble = Unknown | Rufus { a: Int, torrid: Nat } let b = Gobble::Rufus { torrid: 3, a: 84 } b "#; eval_assert(source, "Rufus { a: 84, torrid: 3 }"); let source = r#" type Gobble = Unknown | Rufus { a: Int, torrid: Nat } let b = Gobble::Rufus { a: 84 } b "#; eval_assert_failure(source, "Field torrid not specified for record Gobble::Rufus"); } #[test] fn basic_if_statement() { let source = r#" let a = 10 let b = 10 if a == b then { 69 } else { 420 } "#; eval_assert(source, "69"); } #[test] fn basic_patterns_1() { let source = r#" let x = 10 let a = if x is 10 then { 255 } else { 256 } let b = if 23 is 99 then { 255 } else { 256 } let c = if true is false then { 9 } else { 10 } let d = if "xxx" is "yyy" then { 20 } else { 30 } (a, b, c, d) "#; eval_assert(source, "(255, 256, 10, 30)"); } #[test_case("sanchez", "1")] #[test_case("mouri", "2")] #[test_case("hella", "3")] #[test_case("cyrus", "4")] fn basic_patterns_2(input: &str, expected: &str) { let mut source = format!(r#"let x = "{}""#, input); source.push_str( r#" if x { is "sanchez" then 1 is "mouri" then 2 is "hella" then 3 is _ then 4 } "#, ); eval_assert(&source, expected); } #[test_case(r#"(45, "panda", false, 2.2)"#, r#""yes""#)] #[test_case(r#"(99, "panda", false, -2.45)"#, r#""maybe""#)] fn tuple_patterns(input: &str, expected: &str) { let mut source = format!("let x = {}", input); source.push_str( r#" if x { is (45, "pablo", _, 28.4) then "no" is (_, "panda", _, 2.2) then "yes" is _ then "maybe" }"#, ); eval_assert(&source, expected); } #[test] fn record_patterns_1() { let source = r#" type Ara = Kueh { a: Int, b: String } | Morbuk let alpha = Ara::Kueh { a: 10, b: "sanchez" } if alpha { is Ara::Kueh { a, b } then (b, a) is _ then ("nooo", 8888) }"#; eval_assert(source, r#"("sanchez", 10)"#); } #[test] fn record_patterns_2() { let source = r#" type Ara = Kueh { a: Int, b: String } | Morbuk let alpha = Ara::Kueh { a: 10, b: "sanchez" } if alpha { is Ara::Kueh { a, b: le_value } then (le_value, (a*2)) is _ then ("nooo", 8888) }"#; eval_assert(source, r#"("sanchez", 20)"#); } #[test] fn record_patterns_3() { let source = r#" type Vstsavlobs = { tkveni: Int, b: Ia } type Ia = { sitqva: Int, ghmerts: String } let b = Vstsavlobs { tkveni: 3, b: Ia::Ia { sitqva: 5, ghmerts: "ooo" } } if b { is Vstsavlobs::Vstsavlobs { tkveni: _, b: Ia::Ia { sitqva, ghmerts } } then sitqva is _ then 5000 }"#; eval_assert(source, "5"); } #[test] fn if_is_patterns() { let source = r#" type Option = Some(T) | None let q = "a string" let x = Option::Some(9); if x is Option::Some(q) then { q } else { 0 }"#; eval_assert(source, "9"); let source = r#" type Option = Some(T) | None let q = "a string" let outer = 2 let x = Option::None; if x is Option::Some(q) then { q } else { -2 + outer }"#; eval_assert(source, "0"); } #[test] fn full_if_matching() { let source = r#" type Option = Some(T) | None let a = Option::None if a { is Option::None then 4; is Option::Some(x) then x } "#; eval_assert(source, "4"); let source = r#" type Option = Some(T) | None let sara = Option::Some(99) if sara { is Option::None then 1 + 3; is Option::Some(x) then x } "#; eval_assert(source, "99"); let source = r#" let a = 10 if a { is 10 then "x"; is 4 then "y" } "#; eval_assert(source, "\"x\""); let source = r#" let a = 10 if a { is 15 then "x"; is 10 then "y" } "#; eval_assert(source, "\"y\""); } //TODO - I can probably cut down some of these #[test] fn string_pattern() { let source = r#" let a = "foo" if a { is "foo" then "x"; is _ then "y" } "#; eval_assert(source, "\"x\""); } #[test] fn boolean_pattern() { let source = r#" let a = true if a { is true then "x" is false then "y" } "#; eval_assert(source, "\"x\""); } #[test] fn boolean_pattern_2() { let source = r#" let a = false if a { is true then "x"; is false then "y" } "#; eval_assert(source, "\"y\""); } #[test] fn ignore_pattern() { let source = r#" type Option = Some(T) | None if Option::Some(10) { is _ then "hella" } "#; eval_assert(source, "\"hella\""); } #[test] fn tuple_pattern() { let source = r#" if (1, 2) { is (1, x) then x; is _ then 99 } "#; eval_assert(source, "2"); } #[test] fn tuple_pattern_2() { let source = r#" if (1, 2) { is (10, x) then x is (y, x) then x + y } "#; eval_assert(source, "3"); } #[test] fn tuple_pattern_3() { let source = r#" if (1, 5) { is (10, x) then x is (1, x) then x } "#; eval_assert(source, "5"); } #[test] fn tuple_pattern_4() { let source = r#" if (1, 5) { is (10, x) then x is (1, x) then x } "#; eval_assert(source, "5"); } #[test] fn prim_obj_pattern() { let source = r#" type Stuff = Mulch(Nat) | Jugs(Nat, String) | Mardok let a = Stuff::Mulch(20) let b = Stuff::Jugs(1, "haha") let c = Stuff::Mardok let x = if a { is Stuff::Mulch(20) then "x" is _ then "ERR" } let y = if b { is Stuff::Mulch(n) then "ERR" is Stuff::Jugs(2, _) then "ERR" is Stuff::Jugs(1, s) then s is _ then "ERR" } let z = if c { is Stuff::Jugs(_, _) then "ERR" is Stuff::Mardok then "NIGH" is _ then "ERR" } (x, y, z) "#; eval_assert(source, r#"("x", "haha", "NIGH")"#); } #[test] fn basic_lambda_evaluation_1() { let source = r#" let q = \(x, y) { x * y } let x = q(5, 2) let y = \(m, n, o) { m + n + o }(1,2,3) (x, y) "#; eval_assert(source, r"(10, 6)"); } #[test] fn basic_lambda_evaluation_2() { let source = r#" fn milta() { \(x) { x + 33 } } milta()(10) "#; eval_assert(source, "43"); } #[test] fn import_all() { let source = r#" type Option = Some(T) | None import Option::* let x = Some(9); if x is Some(q) then { q } else { 0 }"#; eval_assert(source, "9"); } #[test] fn accessors() { let source = r#" type Klewos = { a: Int, b: String } let value = Klewos::Klewos { a: 50, b: "nah" } (value.a, value.b) "#; eval_assert(source, r#"(50, "nah")"#); } #[test] fn early_return() { let source = r#" fn chnurmek(a: Int): Int { if a == 5 then { return 9999; } return (a + 2); } (chnurmek(5), chnurmek(0)) "#; eval_assert(source, r#"(9999, 2)"#); let source = r#" fn marbuk(a: Int, b: Int): (Int, Int) { if a == 5 then { if b == 6 then { return (50, 50); } return (a, b + 1) } (a * 100, b * 100) } let x = marbuk(1, 1) let y = marbuk(5, 1) let z = marbuk(5, 6) (x, y, z) "#; eval_assert(source, "((100, 100), (5, 2), (50, 50))"); } #[test] fn loops() { let source = r#" let mut a = 0 let mut count = 0 while a != 5 { a = a + 1 count = count + 100 } count "#; eval_assert(source, "500"); } #[test] fn loops_2() { let source = r#" let mut a = 0 let mut acc = 0 while a < 10 { acc = acc + 1 a = a + 1 // Without this continue, the output would be 20 if a == 5 then { continue } acc = acc + 1 } acc"#; eval_assert(source, "19"); } #[test] fn list_literals() { eval_assert( r#" let a = [7, 8, 9] a "#, "[7, 8, 9]", ); eval_assert( r#" let a = [7, 8, 9] fn foo() { return 2 } (a[0], a[foo()]) "#, "(7, 9)", ); }