use super::ParseTestCase;
#[cfg(test)]
use {
super::{load_example_grammar, run_parse_batch},
crate::engine::grammar::SPG,
};
#[cfg(test)]
fn ml_grammar() -> SPG {
load_example_grammar("ml")
}
#[must_use]
pub fn valid_expressions_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("identity", "fun (x : int) -> x"),
ParseTestCase::valid("curried const", "fun (x : int) -> fun (y : bool) -> x"),
ParseTestCase::valid("apply identity", "(fun (x : int) -> x)(5)"),
ParseTestCase::valid("let int", "let a : int = 5 in a"),
ParseTestCase::valid("let arith", "let a : int = 5 in a + 1"),
ParseTestCase::valid("compare", "1 < 2"),
ParseTestCase::valid("let then compare", "let a : int = 5 in a < 10"),
ParseTestCase::valid("if literals", "if true then 1 else 2"),
ParseTestCase::valid("if compare", "if 1 < 2 then 1 else 0"),
ParseTestCase::valid("pair", "(1, true)"),
ParseTestCase::valid("fst", "fst (1, true)"),
ParseTestCase::valid("snd", "snd (1, true)"),
ParseTestCase::valid("nested pair", "((1, 2), true)"),
ParseTestCase::valid("fst snd compose", "fst (snd ((1, (2, 3))))"),
ParseTestCase::valid("nil", "[]"),
ParseTestCase::valid("singleton", "1 :: []"),
ParseTestCase::valid("cons chain", "1 :: 2 :: 3 :: []"),
ParseTestCase::valid("list of pairs", "(1, true) :: []"),
ParseTestCase::valid("cons in let", "let xs : int list = 1 :: [] in xs"),
ParseTestCase::valid(
"let rec",
"let rec f : int -> int = fun (n : int) -> f(n) in f(0)",
),
ParseTestCase::valid("diverge bare", "assert false"),
ParseTestCase::valid("diverge at int", "let a : int = assert false in a"),
ParseTestCase::valid("diverge in branch", "if true then 1 else assert false"),
ParseTestCase::valid(
"diverge as function",
"let f : int -> bool = assert false in f(0)",
),
]
}
#[must_use]
pub fn invalid_expressions_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid("unbound var", "fun (x : int) -> y"),
ParseTestCase::invalid("add bool", "1 + true"),
ParseTestCase::invalid("if non-bool cond", "if 1 then 2 else 3"),
ParseTestCase::invalid("if branch mismatch", "if true then 1 else false"),
ParseTestCase::invalid("fst of non-pair", "fst 5"),
ParseTestCase::invalid("let type mismatch", "let a : bool = 5 in a"),
ParseTestCase::invalid("compare bool", "true < 2"),
ParseTestCase::invalid("apply non-function", "5(3)"),
ParseTestCase::invalid("cons mixed elements", "1 :: true :: []"),
ParseTestCase::invalid("cons onto non-list", "1 :: 2"),
ParseTestCase::invalid(
"list annotation mismatch",
"let xs : bool list = 1 :: [] in xs",
),
]
}
#[test]
fn valid_expressions_ml() {
let mut grammar = ml_grammar();
let cases = valid_expressions_cases();
let (res, _) = run_parse_batch(&mut grammar, &cases);
assert_eq!(res.failed, 0, "{}", res.format_failures());
}
#[test]
fn invalid_expressions_ml() {
let mut grammar = ml_grammar();
let cases = invalid_expressions_cases();
let (res, _) = run_parse_batch(&mut grammar, &cases);
assert_eq!(res.failed, 0, "{}", res.format_failures());
}
#[must_use]
pub fn valid_programs_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid(
"length",
"let rec length : int list -> int = fun (xs : int list) -> match xs with [] -> 0 | h :: t -> 1 + length(t) in length(1 :: 2 :: 3 :: [])",
),
ParseTestCase::valid(
"sum",
"let rec sum : int list -> int = fun (xs : int list) -> match xs with [] -> 0 | h :: t -> h + sum(t) in sum(1 :: 2 :: [])",
),
ParseTestCase::valid(
"map increment",
"let rec inc : int list -> int list = fun (xs : int list) -> match xs with [] -> [] | h :: t -> (h + 1) :: inc(t) in inc(1 :: 2 :: [])",
),
ParseTestCase::valid(
"member returns bool",
"let rec member : int list -> bool = fun (xs : int list) -> match xs with [] -> false | h :: t -> if h = 0 then true else member(t) in member(0 :: 1 :: [])",
),
ParseTestCase::valid(
"copy via cons",
"let rec copy : int list -> int list = fun (xs : int list) -> match xs with [] -> [] | h :: t -> h :: copy(t) in copy(1 :: 2 :: [])",
),
ParseTestCase::valid(
"match on nil, consistent head",
"match [] with [] -> 0 | h :: t -> h + 1",
),
]
}
#[must_use]
pub fn invalid_programs_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid(
"match arms disagree",
"match 1 :: [] with [] -> 0 | h :: t -> true",
),
ParseTestCase::invalid("match on non-list", "match 5 with [] -> 0 | h :: t -> 1"),
ParseTestCase::invalid(
"return type mismatch",
"let rec bad : int list -> int = fun (xs : int list) -> match xs with [] -> [] | h :: t -> 0 in bad([])",
),
ParseTestCase::invalid(
"head used at two types",
"match [] with [] -> 0 | h :: t -> if h then 1 else h + 1",
),
]
}
#[cfg(test)]
mod known_limitations {
use crate::typing::TypingSynth;
fn type_checks(s: &str) -> bool {
let mut synth = TypingSynth::new(super::ml_grammar(), s);
synth.ast().is_ok_and(|a| a.is_complete())
}
#[test]
#[ignore = "higher-order recursion over lists (map/filter/fold) does not type yet"]
fn higher_order_map() {
assert!(type_checks(
"let rec map : (int -> int) -> int list -> int list = fun (f : int -> int) -> fun (xs : int list) -> match xs with [] -> [] | h :: t -> f(h) :: map(f)(t) in map(fun (n : int) -> n + 1)(1 :: 2 :: [])"
));
}
#[test]
fn match_head_element_type_is_tied_to_scrutinee() {
assert!(!type_checks(
"let rec f : int list -> int = fun (xs : int list) -> match xs with [] -> 0 | h :: t -> f(h) in f(1 :: [])"
));
}
#[test]
#[ignore = "prefix-completeness gap: the full program types, but a mid-construction prefix does not parse"]
fn nested_list_prefix() {
assert!(type_checks(
"let xss : int list list = (1 :: []) :: [] in xss"
));
}
}
#[test]
fn valid_programs_ml() {
let mut grammar = ml_grammar();
let cases = valid_programs_cases();
let (res, _) = run_parse_batch(&mut grammar, &cases);
assert_eq!(res.failed, 0, "{}", res.format_failures());
}
#[test]
fn invalid_programs_ml() {
let mut grammar = ml_grammar();
let cases = invalid_programs_cases();
let (res, _) = run_parse_batch(&mut grammar, &cases);
assert_eq!(res.failed, 0, "{}", res.format_failures());
}