use crate::adapters::shared::normalize::{normalize_body, NormalizedToken};
use NormalizedToken::{BoolLit, CharLit, FloatLit, IntLit, Keyword, MacroCall, Operator, StrLit};
fn toks(code: &str) -> Vec<NormalizedToken> {
let wrapped = format!("fn f() {{ {code} }}");
let file = syn::parse_file(&wrapped).unwrap_or_else(|e| panic!("parse {code:?}: {e}"));
let syn::Item::Fn(f) = &file.items[0] else {
unreachable!("wrapped code is a fn")
};
normalize_body(&f.block)
}
fn assert_emits(code: &str, want: &NormalizedToken) {
let got = toks(code);
assert!(
got.contains(want),
"{code:?} must emit {want:?}; got {got:?}"
);
}
#[test]
fn binary_operators_each_emit_their_token() {
let cases: [(&str, &str); 28] = [
("a + b;", "+"),
("a - b;", "-"),
("a * b;", "*"),
("a / b;", "/"),
("a % b;", "%"),
("a && b;", "&&"),
("a || b;", "||"),
("a ^ b;", "^"),
("a & b;", "&"),
("a | b;", "|"),
("a << b;", "<<"),
("a >> b;", ">>"),
("a == b;", "=="),
("a < b;", "<"),
("a <= b;", "<="),
("a != b;", "!="),
("a >= b;", ">="),
("a > b;", ">"),
("a += b;", "+="),
("a -= b;", "-="),
("a *= b;", "*="),
("a /= b;", "/="),
("a %= b;", "%="),
("a ^= b;", "^="),
("a &= b;", "&="),
("a |= b;", "|="),
("a <<= b;", "<<="),
("a >>= b;", ">>="),
];
for (code, op) in cases {
assert_emits(code, &Operator(op));
}
}
#[test]
fn unary_operators_each_emit_their_token() {
for (code, op) in [("-a;", "-"), ("!a;", "!"), ("*a;", "*")] {
assert_emits(code, &Operator(op));
}
}
#[test]
fn expr_kinds_each_emit_their_marker() {
let cases: [(&str, NormalizedToken); 17] = [
("a = b;", Operator("=")),
("while a {}", Keyword("while")),
("loop {}", Keyword("loop")),
("break;", Keyword("break")),
("continue;", Keyword("continue")),
("a[b];", Operator("[]")),
("(a, b);", Keyword("tuple")),
("[a, b];", Keyword("array")),
("a.await;", Keyword("await")),
("a..b;", Operator("..")),
("a as u8;", Keyword("as")),
("[a; 3];", Keyword("array")),
("if let Some(a) = b {}", Keyword("let")),
("yield a;", Keyword("yield")),
("m!();", MacroCall("m".to_string())),
("let _ = m!();", MacroCall("m".to_string())),
("1.5;", FloatLit),
];
for (code, want) in &cases {
assert_emits(code, want);
}
assert_emits("\"x\";", &StrLit);
assert_emits("'c';", &CharLit);
}
#[test]
fn pat_kinds_each_emit_their_marker() {
let cases: [(&str, NormalizedToken); 11] = [
("match v { (a, b) => {} }", Keyword("tuple")),
("match v { Foo(a) => {} }", Keyword("tuple")),
("match v { &a => {} }", Operator("&")),
("match v { [a, b] => {} }", Keyword("array")),
("match v { [a, ..] => {} }", Operator("..")),
("match v { 1..=5 => {} }", Operator("..")),
("match v { 1 => {} }", IntLit),
("match v { 1.5 => {} }", FloatLit),
("match v { \"x\" => {} }", StrLit),
("match v { true => {} }", BoolLit(true)),
("match v { 'c' => {} }", CharLit),
];
for (code, want) in &cases {
assert_emits(code, want);
}
}
#[test]
fn or_pattern_emits_a_pipe_between_each_case() {
let got = toks("match v { 1 | 2 | 3 => {} }");
let pipes = got.iter().filter(|t| **t == Operator("|")).count();
assert_eq!(
pipes, 2,
"`1 | 2 | 3` needs exactly 2 separating pipes; got {got:?}"
);
}