mute/mute-macros/src/lib.rs

179 lines
5.5 KiB
Rust
Raw Normal View History

2024-05-11 16:08:26 +00:00
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) => {
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);
}
}