use mute_parser::{parse_str, Node}; use proc_macro::TokenStream; #[proc_macro] pub fn inline(input: TokenStream) -> TokenStream { let ast = parse_str(input.to_string().as_str()).unwrap(); if ast.len() != 1 { panic!("Expected a single expression, got multiple"); } ast.into_iter() .map(format_node) .reduce(|lhs, rhs| format!("{lhs}\n{rhs}")) .unwrap_or("Node::Nil".to_string()) .parse::() .unwrap() } fn format_node(node: Node) -> String { match node { Node::List(v) => format!("Node::List({})", reduce_list(v)), Node::Symbol(s) => format!("Node::Symbol(\"{s}\".to_string())"), Node::Keyword(k) => format!("Node::Keyword(\"{k}\".to_string())"), Node::Int(i) => format!("Node::Int({i})"), Node::Float(f) => format!("Node::Float({f})"), Node::String(s) => format!("Node::String(\"{s}\".to_string())"), Node::Boolean(b) => format!("Node::Bool({b})"), Node::Nil => "Node::Nil".to_string(), Node::Void => "Node::Void".to_string(), Node::Error(e) => format!("Node::Error(\"{e}\".to_string())"), Node::Vector(_) => todo!(), Node::Map(_) => todo!(), // Quoting Node::Quote(node) => format!("Node::Quote(Box::new({}))", format_node(*node)), Node::Quasiquote(node) => format!("Node::Quasiquote(Box::new({}))", format_node(*node)), Node::Unquote(node) => format!("Node::Unquote(Box::new({}))", format_node(*node)), // Specials Node::Define(body) => format!("Node::Define({})", reduce_list(body)), Node::Let(body) => format!("Node::Let({})", reduce_list(body)), Node::Function(body) => format!("Node::Function({})", reduce_list(body)), Node::Macro(body) => format!("Node::Macro({})", reduce_list(body)), Node::If(body) => format!("Node::If({})", reduce_list(body)), Node::Do(body) => format!("Node::Do({})", reduce_list(body)), Node::Eval(body) => format!("Node::Eval({})", reduce_list(body)), Node::Apply(body) => format!("Node::Apply({})", reduce_list(body),), } } fn reduce_list(node: Vec) -> String { let vec = node .into_iter() .map(format_node) .reduce(|lhs, rhs| format!("{lhs}, {rhs}")) .unwrap_or_default(); format!("vec![{vec}]") } #[cfg(test)] mod tests { use super::*; use rstest::rstest; macro_rules! set_snapshot_suffix { ($($expr:expr),*) => { let mut settings = insta::Settings::clone_current(); settings.set_snapshot_suffix(format!($($expr,)*)); let _guard = settings.bind_to_scope(); } } #[rstest] // Basic values #[case("()")] #[case("(+ 1 2 3)")] #[case("1")] #[case("1.0")] #[case("\"uwu\"")] #[case("true")] #[case("false")] #[case("asdf")] #[case("nil")] #[case("[1 2 3]")] #[case("{:a 1 :b 2 :c 3}")] // Specials #[case("(define (a 1))")] #[case("(define (a 1) (b 2))")] #[case("(fn* () (+ 1 1))")] #[case("(fn* (a b) (+ a b))")] #[case("(if true 1 2)")] fn test_format_node(#[case] input: &str) { set_snapshot_suffix!("{}", input); let ast = parse_str(input).unwrap(); let res = format_node(ast[0].clone()); insta::assert_snapshot!(res); } }