mute/mute-macros/src/lib.rs
2024-05-12 21:48:47 -04:00

103 lines
3.3 KiB
Rust

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::<TokenStream>()
.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<T: IntoIterator<Item = Node>>(node: T) -> String {
let vec = node
.into_iter()
.map(format_node)
.reduce(|lhs, rhs| format!("{lhs}, {rhs}"))
.unwrap_or_default();
format!("vec![{vec}].into()")
}
#[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);
}
}