use super::*;
const INFINITE_RIGHT_RECURSIVE: &str = r#"
A ::= 'a' A | 'b'
start ::= A
"#;
const EPSILON_HEAVY: &str = r#"
A ::= 'a' B | ε
B ::= 'b' C | ε
C ::= 'c' | ε
start ::= A B C
"#;
const DEEP_NESTING: &str = r#"
Atom ::= 'x'
L1 ::= '(' L2 ')' | Atom
L2 ::= '(' L3 ')' | L1
L3 ::= '(' L3 ')' | L2
start ::= L3
"#;
const DIAMOND: &str = r#"
Identifier ::= /[a-z]+/
Variable(var) ::= Identifier[x]
Left(left) ::= '<' Term[inner] '>'
Right(right) ::= '[' Term[inner] ']'
Term ::= Variable | Left | Right
Top ::= Left | Right
x ∈ Γ
----------- (var)
Γ(x)
Γ ⊢ inner : ?T
----------- (left)
?T
Γ ⊢ inner : ?T
----------- (right)
?T
"#;
const MUTUAL: &str = r#"
Identifier ::= /[a-z]+/
Type ::= 'Num' | 'Flag'
Literal(lit) ::= /[0-9]+/
Variable(var) ::= Identifier[x]
Bind(bind) ::= 'set' Identifier[name] ':' Type[τ] '=' Atom[value] 'then' Phrase[rest]
Atom ::= Literal | Variable | '(' Phrase ')'
Phrase ::= Bind | Atom
x ∈ Γ
----------- (var)
Γ(x)
----------- (lit)
'Num'
Γ ⊢ value : τ, Γ[name:τ] ⊢ rest : ?R
----------- (bind)
?R
"#;
const EPSILON_WRAPPED: &str = r#"
Identifier ::= /[a-z]+/
Variable(var) ::= Identifier[x]
Prefix ::= 'pre' | ε
Suffix ::= 'post' | ε
Wrapped(wrap) ::= Prefix[p] Core[c] Suffix[s]
Core ::= Variable
Start ::= Wrapped
x ∈ Γ
----------- (var)
Γ(x)
Γ ⊢ c : ?T
----------- (wrap)
?T
"#;
const REGEX_HEAVY: &str = r#"
Lower ::= /[a-z]+/
Upper ::= /[A-Z]+/
Digits ::= /[0-9]+/
Variable(var) ::= Lower[x]
Tag(tag) ::= Upper[t]
Num(num) ::= Digits[d]
Tagged(tagged) ::= Tag[t] '.' Expression[e]
Expression ::= Variable | Num | Tagged | '(' Expression ')'
x ∈ Γ
----------- (var)
Γ(x)
----------- (tag)
'Tag'
----------- (num)
'Num'
Γ ⊢ e : ?T
----------- (tagged)
?T
"#;
const SCOPED: &str = r#"
Identifier ::= /[a-z]+/
Type ::= 'X' | 'Y'
Variable(var) ::= Identifier[x]
Num(num) ::= /[0-9]+/
Let(letb) ::= 'def' Identifier[name] ':' Type[τ] '=' Atom[value] 'in' Expr[body]
Scoped(scoped) ::= '{' Expr[inner] '}'
Atom ::= Variable | Num | Scoped | '(' Expr ')'
Expr ::= Let | Atom
x ∈ Γ
----------- (var)
Γ(x)
----------- (num)
'X'
Γ ⊢ value : τ, Γ[name:τ] ⊢ body : ?R
----------- (letb)
?R
Γ ⊢ inner : ?T
----------- (scoped)
?T
"#;
const STMT: &str = r#"
Identifier ::= /[a-z]+/
Type ::= 'I' | 'B'
Variable(var) ::= Identifier[x]
Num(num) ::= /[0-9]+/
Decl(decl) ::= 'var' Identifier[name] ':' Type[τ] '=' Num[value] ';'
Seq(seq) ::= Statement[head] Statements[tail]
Statements ::= Seq | ε
Statement ::= Decl
Block(block) ::= '{' Statements[stmts] '}'
x ∈ Γ
----------- (var)
Γ(x)
----------- (num)
'I'
Γ ⊢ value : τ
----------- (decl)
Γ → Γ[name:τ] ⊢ ∅
"#;
const UNION_CHOICE: &str = r#"
Identifier ::= /[a-z]+/
Variable(var) ::= Identifier[x]
IntLit(ilit) ::= /[0-9]+/
BoolLit(blit) ::= 'yes' | 'no'
Choice(choice) ::= Expression[a] '?' Expression[b]
Expression ::= Variable | IntLit | BoolLit | Choice | '(' Expression ')'
x ∈ Γ
----------- (var)
Γ(x)
----------- (ilit)
'N'
----------- (blit)
'B'
Γ ⊢ a : ?A, Γ ⊢ b : ?B
----------- (choice)
?A | ?B
"#;
const CONTEXT_EXPORT: &str = r#"
Identifier ::= /[a-z]+/
Type ::= 'A'
Value(val) ::= 'v'
Decl(decl) ::= 'let' Identifier[name] ':' Type[τ] '=' Value[value] ';'
Variable(var) ::= Identifier[x]
Start ::= Decl Variable
x ∈ Γ
----------- (var)
Γ(x)
----------- (val)
'A'
Γ ⊢ value : τ
----------- (decl)
Γ → Γ[name:τ] ⊢ ∅
"#;
const CHILD_CONTEXT_LOCALITY: &str = r#"
Identifier ::= /[a-z]+/
Type ::= 'A'
Variable(var) ::= Identifier[x]
Abs(abs) ::= 'fun' Identifier[param] ':' Type[τ] Variable[body] Variable[tail]
Start ::= Abs
x ∈ Γ
----------- (var)
Γ(x)
Γ[param:τ] ⊢ body : τ, Γ ⊢ tail : τ
----------- (abs)
τ
"#;
fn load_inline_grammar(content: &str) -> Grammar {
Grammar::load(content).expect("failed to load inline grammar")
}
fn right_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("right b", "b"),
ParseTestCase::valid("right a b", "a b"),
ParseTestCase::valid("right a a b", "a a b"),
ParseTestCase::valid("right many a", "a a a a a b"),
ParseTestCase::valid("right single a", "a b"),
ParseTestCase::valid("right double a", "a a b"),
]
}
fn right_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid("right invalid char", "c"),
ParseTestCase::invalid("right wrong order", "b a"),
ParseTestCase::invalid("right invalid symbol", "@"),
ParseTestCase::invalid("right missing b", "a a a"),
ParseTestCase::invalid("right multiple b", "a b b"),
ParseTestCase::invalid("right empty", ""),
ParseTestCase::invalid("right only a", "a"),
]
}
fn epsilon_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("epsilon empty", ""),
ParseTestCase::valid("epsilon a", "a"),
ParseTestCase::valid("epsilon a b c", "a b c"),
ParseTestCase::valid("epsilon a only", "a"),
ParseTestCase::valid("epsilon b only", "b"),
ParseTestCase::valid("epsilon c only", "c"),
ParseTestCase::valid("epsilon a b", "a b"),
ParseTestCase::valid("epsilon b c", "b c"),
ParseTestCase::valid("epsilon a c", "a c"),
]
}
fn epsilon_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid("epsilon invalid", "x"),
ParseTestCase::invalid("epsilon wrong order", "c b a"),
ParseTestCase::invalid("epsilon wrong order 2", "b a c"),
ParseTestCase::invalid("epsilon wrong order 3", "c a b"),
ParseTestCase::invalid("epsilon invalid char", "d"),
ParseTestCase::invalid("epsilon invalid combo", "a x b"),
ParseTestCase::invalid("epsilon invalid end", "a b c d"),
]
}
fn deep_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("deep x", "x"),
ParseTestCase::valid("deep (x)", "(x)"),
ParseTestCase::valid("deep ((x))", "((x))"),
]
}
fn deep_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid("deep extra close", ")"),
ParseTestCase::invalid("deep invalid atom", "y"),
ParseTestCase::invalid("deep trailing close", "x)"),
]
}
fn diamond_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("diamond left", "< x >"),
ParseTestCase::valid("diamond right", "[ x ]"),
ParseTestCase::valid("diamond nested lr", "< [ x ] >"),
ParseTestCase::valid("diamond nested rl", "[ < x > ]"),
ParseTestCase::valid("diamond deep", "< < < x > > >"),
ParseTestCase::valid("diamond partial left", "< x"),
ParseTestCase::valid("diamond partial right", "[ x"),
]
.into_iter()
.map(|c| c.with_context(vec![("x", "X")]))
.collect()
}
fn diamond_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid("diamond mismatched", "< x ]"),
ParseTestCase::invalid("diamond lone close", ">"),
ParseTestCase::invalid("diamond lone bracket", "]"),
ParseTestCase::invalid("diamond empty angle", "< >"),
]
}
fn mutual_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("mutual number", "42"),
ParseTestCase::valid("mutual bind", "set x : Num = 1 then x"),
ParseTestCase::valid(
"mutual nested",
"set x : Num = 1 then set y : Num = 2 then x",
),
ParseTestCase::valid("mutual paren", "( 7 )"),
ParseTestCase::valid("mutual partial bind", "set x : Num ="),
ParseTestCase::valid("mutual partial then", "set x : Num = 1 then"),
]
}
fn mutual_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid("mutual missing type", "set x = 1 then x"),
ParseTestCase::invalid("mutual missing then", "set x : Num = 1 x"),
ParseTestCase::invalid("mutual bad type", "set x : Bad = 1 then x"),
ParseTestCase::invalid("mutual empty parens", "( )"),
]
}
fn epsilon_wrapped_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("ewrap bare", "x"),
ParseTestCase::valid("ewrap pre", "pre x"),
ParseTestCase::valid("ewrap post", "x post"),
ParseTestCase::valid("ewrap both", "pre x post"),
ParseTestCase::valid("ewrap partial pre", "pre"),
]
.into_iter()
.map(|c| c.with_context(vec![("x", "X")]))
.collect()
}
fn epsilon_wrapped_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid("ewrap number", "123"),
ParseTestCase::invalid("ewrap special", "@x"),
]
}
fn regex_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("regex number", "42"),
ParseTestCase::valid("regex var", "abc"),
ParseTestCase::valid("regex tag", "FOO . 99"),
ParseTestCase::valid("regex nested tag", "A . B . 1"),
ParseTestCase::valid("regex paren", "( 42 )"),
ParseTestCase::valid("regex partial tag", "XY ."),
]
.into_iter()
.map(|c| c.with_context(vec![("abc", "Num")]))
.collect()
}
fn regex_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid("regex dot alone", "."),
ParseTestCase::invalid("regex tag no dot", "FOO 99"),
ParseTestCase::invalid("regex special char", "@"),
]
}
fn scoped_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("scoped let", "def a : X = 1 in a"),
ParseTestCase::valid("scoped nested let", "def a : X = 1 in def b : X = 2 in a"),
ParseTestCase::valid("scoped block", "{ 5 }"),
ParseTestCase::valid("scoped let in block", "def a : X = 1 in { a }"),
ParseTestCase::valid("scoped partial def", "def a : X ="),
]
}
fn scoped_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid("scoped missing in", "def a : X = 1 a"),
ParseTestCase::invalid("scoped bad type", "def a : Z = 1 in a"),
ParseTestCase::invalid("scoped empty braces", "{ }"),
]
}
fn stmt_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("stmt empty block", "{ }"),
ParseTestCase::valid("stmt single decl", "{ var x : I = 1 ; }"),
ParseTestCase::valid("stmt two decls", "{ var x : I = 1 ; var y : I = 2 ; }"),
ParseTestCase::valid("stmt partial", "{ var x : I ="),
]
}
fn stmt_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid("stmt no brace", "var x : I = 1 ;"),
ParseTestCase::invalid("stmt missing semi", "{ var x : I = 1 }"),
ParseTestCase::invalid("stmt bad type", "{ var x : Z = 1 ; }"),
ParseTestCase::invalid("stmt missing eq", "{ var x : I 1 ; }"),
]
}
fn union_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("union int", "42"),
ParseTestCase::valid("union bool yes", "yes"),
ParseTestCase::valid("union bool no", "no"),
ParseTestCase::valid("union choice", "1 ? yes"),
ParseTestCase::valid("union nested", "1 ? 2 ? no"),
ParseTestCase::valid("union paren", "( yes )"),
ParseTestCase::valid("union partial", "1 ?"),
]
}
fn union_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid("union question alone", "?"),
ParseTestCase::invalid("union double question", "1 ? ? yes"),
ParseTestCase::invalid("union special", "@"),
]
}
fn context_export_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid(
"closed decl exports rightward",
"let x : A = v ; x",
),
ParseTestCase::valid(
"decl and var sequence",
"let y : A = v ; y",
),
ParseTestCase::valid(
"decl with matching var",
"let a : A = v ; a",
),
ParseTestCase::valid(
"simple one letter b",
"let b : A = v ; b",
),
ParseTestCase::valid(
"simple one letter c",
"let c : A = v ; c",
),
ParseTestCase::valid(
"simple one letter f",
"let f : A = v ; f",
),
ParseTestCase::valid(
"simple one letter z",
"let z : A = v ; z",
),
ParseTestCase::valid(
"two letter binding",
"let ab : A = v ; ab",
),
]
}
fn context_export_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid(
"right sibling needs exported name",
"let x : A = v ; y",
),
ParseTestCase::invalid(
"wrong variable name",
"let x : A = v ; z",
),
ParseTestCase::invalid(
"completely different var",
"let a : A = v ; b",
),
ParseTestCase::invalid(
"no variable after decl",
"let x : A = v ;",
),
ParseTestCase::invalid(
"mismatch x v",
"let x : A = v ; v",
),
ParseTestCase::invalid(
"mismatch a b",
"let a : A = v ; c",
),
ParseTestCase::invalid(
"mismatch c f",
"let c : A = v ; f",
),
ParseTestCase::invalid(
"missing semicolon",
"let x : A = v x",
),
ParseTestCase::invalid(
"extra identifiers",
"let x : A = v ; x y",
),
ParseTestCase::invalid(
"partial declaration",
"let x : A = v",
),
ParseTestCase::invalid(
"mismatched pair one two",
"let z : A = v ; a",
),
ParseTestCase::invalid(
"different two letter",
"let ab : A = v ; ba",
),
]
}
fn child_context_valid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::valid("body sees parameter tail sees ambient", "fun x : A x y")
.with_context(vec![("y", "A")]),
ParseTestCase::valid("param reference in body", "fun p : A p q")
.with_context(vec![("q", "A")]),
ParseTestCase::valid("different param names", "fun z : A z w")
.with_context(vec![("w", "A")]),
]
}
fn child_context_invalid_cases() -> Vec<ParseTestCase> {
vec![
ParseTestCase::invalid(
"parameter must not leak to tail",
"fun x : A x x",
),
ParseTestCase::invalid(
"param visible in tail position",
"fun p : A p p",
),
ParseTestCase::invalid(
"using bound variable outside body",
"fun z : A q z",
),
ParseTestCase::invalid(
"undefined tail variable",
"fun m : A m undefined",
),
]
}
pub fn suites() -> Vec<(
&'static str,
Grammar,
Vec<ParseTestCase>,
Vec<ParseTestCase>,
)> {
vec![
(
"weird::right",
load_inline_grammar(INFINITE_RIGHT_RECURSIVE),
right_valid_cases(),
right_invalid_cases(),
),
(
"weird::epsilon",
load_inline_grammar(EPSILON_HEAVY),
epsilon_valid_cases(),
epsilon_invalid_cases(),
),
(
"weird::deep",
load_inline_grammar(DEEP_NESTING),
deep_valid_cases(),
deep_invalid_cases(),
),
(
"weird::diamond",
load_inline_grammar(DIAMOND),
diamond_valid_cases(),
diamond_invalid_cases(),
),
(
"weird::mutual",
load_inline_grammar(MUTUAL),
mutual_valid_cases(),
mutual_invalid_cases(),
),
(
"weird::epsilon_wrapped",
load_inline_grammar(EPSILON_WRAPPED),
epsilon_wrapped_valid_cases(),
epsilon_wrapped_invalid_cases(),
),
(
"weird::regex_heavy",
load_inline_grammar(REGEX_HEAVY),
regex_valid_cases(),
regex_invalid_cases(),
),
(
"weird::scoped",
load_inline_grammar(SCOPED),
scoped_valid_cases(),
scoped_invalid_cases(),
),
(
"weird::stmt",
load_inline_grammar(STMT),
stmt_valid_cases(),
stmt_invalid_cases(),
),
(
"weird::union_choice",
load_inline_grammar(UNION_CHOICE),
union_valid_cases(),
union_invalid_cases(),
),
(
"weird::context_export",
load_inline_grammar(CONTEXT_EXPORT),
context_export_valid_cases(),
context_export_invalid_cases(),
),
(
"weird::child_context",
load_inline_grammar(CHILD_CONTEXT_LOCALITY),
child_context_valid_cases(),
child_context_invalid_cases(),
),
]
}
#[test]
fn check_weird_parseable() {
for (name, mut grammar, valids, invalids) in suites() {
println!(
"\n=== Weird suite: {} ({} valid + {} invalid) ===",
name,
valids.len(),
invalids.len()
);
let (res_v, _) = run_parse_batch(&mut grammar, &valids);
assert_eq!(
res_v.failed,
0,
"{} valid failures: {}",
name,
res_v.format_failures()
);
let (res_i, _) = run_parse_batch(&mut grammar, &invalids);
assert_eq!(
res_i.failed,
0,
"{} invalid failures: {}",
name,
res_i.format_failures()
);
}
}