use tatara_lisp::{Atom, Sexp};
pub fn erase_types(forms: &[Sexp]) -> Vec<Sexp> {
forms.iter().map(erase_form).collect()
}
fn erase_form(s: &Sexp) -> Sexp {
match s {
Sexp::List(items) if is_define_typed(items) => {
let Sexp::List(sig) = &items[1] else {
return s.clone();
};
let mut plain = Vec::with_capacity(sig.len());
plain.push(sig[0].clone());
for p in &sig[1..] {
match p {
Sexp::List(pair) if !pair.is_empty() => plain.push(pair[0].clone()),
other => plain.push(other.clone()),
}
}
Sexp::List(vec![
Sexp::Atom(Atom::Symbol("define".into())),
Sexp::List(plain),
erase_form(&items[3]),
])
}
Sexp::List(items) => Sexp::List(items.iter().map(erase_form).collect()),
other => other.clone(),
}
}
fn is_define_typed(items: &[Sexp]) -> bool {
items.len() == 4
&& matches!(&items[0], Sexp::Atom(Atom::Symbol(n)) if &**n == "define-typed")
&& matches!(&items[1], Sexp::List(sig) if !sig.is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
fn erased(src: &str) -> String {
let forms = blue_lang_syntax::parse_program(src).expect("parse");
erase_types(&forms)
.iter()
.map(|f| f.to_string())
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn an_annotated_def_erases_to_a_plain_def() {
assert_eq!(
erased("def add(a: Int, b: Int) -> Int\n a + b\nend"),
"(define (add a b) (+ a b))"
);
}
#[test]
fn annotating_does_not_change_the_erased_program() {
assert_eq!(
erased("def add(a: Int, b: Int) -> Int\n a + b\nend"),
erased("def add(a, b)\n a + b\nend"),
"an annotation must not survive into the code that runs"
);
}
#[test]
fn erasure_preserves_arity() {
assert_eq!(
erased("def three(a: Int, b, c: Str) -> dyn\n a\nend"),
"(define (three a b c) a)"
);
}
#[test]
fn a_nested_annotated_def_is_erased() {
let out = erased("def outer(x: Int) -> Int\n def inner(y: Int) -> Int\n y\n end\nend");
assert!(
!out.contains("define-typed"),
"no annotation may survive anywhere in the tree: {out}"
);
}
#[test]
fn erasure_leaves_untyped_code_untouched() {
for src in ["1 + 2", "def f(x)\n x\nend", "[1, 2, 3]", "a.b(c)"] {
let forms = blue_lang_syntax::parse_program(src).expect("parse");
assert_eq!(
erase_types(&forms),
forms,
"erasure changed untyped source {src:?}"
);
}
}
}