548 lines
9.8 KiB
Rust
548 lines
9.8 KiB
Rust
#![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<String, String> {
|
|
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<T> = 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<T> = 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<T> = 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<T> = 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<T> = 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<T> = 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<T> = 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)",
|
|
);
|
|
}
|