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) => { let vec = v .into_iter() .map(format_node) .reduce(|lhs, rhs| format!("{lhs}, {rhs}")) .unwrap_or_default(); format!("Node::List(vec![{vec}])") } 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(v) => { let vec = v .into_iter() .map(format_node) .reduce(|lhs, rhs| format!("{lhs}, {rhs}")) .unwrap_or_default(); format!("Node::Vector(vec![{vec}])") } Node::Map(v) => { let vec = v .into_iter() .map(|(k, v)| (k, format_node(v))) .map(|(k, v)| format!("({k}, {v})")) .reduce(|lhs, rhs| format!("{lhs}, {rhs}")) .unwrap_or_default(); format!("Node::Hashmap(vec![{vec}].into())") } Node::Define { args } => { let args = args .into_iter() .map(|(k, v)| format!("(\"{}\".to_string(), {})", k, format_node(v))) .reduce(|lhs, rhs| format!("{lhs}, {rhs}")) .unwrap_or_default(); format!("Node::Define(vec![{args}])") } Node::Let { args, body } => { let args = args .into_iter() .map(|(k, v)| (k, format_node(v))) .fold(String::new(), |lhs, (k, v)| { format!("{lhs}, ({k}.to_string(), {v})") }); let body = body .into_iter() .map(format_node) .reduce(|lhs, rhs| format!("{lhs}, {rhs}")) .unwrap_or_default(); format!("Node::Let(vec![{}], vec![{}]", args, body) } Node::Function { parameters, body } => { let parameters = parameters .into_iter() .map(|param| format!("\"{}\".to_string()", param)) .reduce(|lhs, rhs| format!("{lhs}, {rhs}")) .unwrap_or_default(); let body = body .into_iter() .map(format_node) .reduce(|lhs, rhs| format!("{lhs}, {rhs}")) .unwrap_or_default(); format!( "Node::Function {{ parameters: vec![{}], body: vec![{}] }}", parameters, body ) } Node::Macro { .. } => todo!(), Node::If { cond, consequence, alternative, } => format!( "Node::If(Box::new({}), Box::new({}), Box::new({}))", format_node(*cond), format_node(*consequence), format_node(*alternative) ), 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)), Node::Do { body } => { let vec = body .into_iter() .map(format_node) .reduce(|lhs, rhs| format!("{lhs}, {rhs}")) .unwrap_or_default(); format!("Node::Do(vec![{vec}])") } Node::Eval { body } => format!("Node::Eval(Box::new({}))", format_node(*body)), Node::Apply { func, args } => format!( "Node::Apply(Box::new({}), Box::new({}))", format_node(*func), format_node(*args) ), } } #[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); } }