use crate::ast::*; /// Generate a complete Rust source file string from a parsed MissesFile. pub fn codegen(file: &MissesFile) -> String { let mut out = String::new(); out.push_str("// Generated by misses compiler. Do not edit.\n"); out.push_str("extern crate proc_macro;\n\n"); for macro_def in &file.macros { out.push_str(&codegen_macro(macro_def)); out.push('\n'); } // Try to format with prettyplease match syn::parse_file(&out) { Ok(parsed) => prettyplease::unparse(&parsed), Err(e) => { eprintln!("prettyplease parse error: {e}\n---\n{out}\n---"); out } } } fn codegen_macro(def: &MacroDef) -> String { let fn_name = &def.name; let mut body = String::new(); // Add format_ident import for use in @{} blocks body.push_str(" use quote::format_ident;\n"); // Generate the parse logic based on pattern body.push_str(&codegen_pattern_parse(&def.pattern)); // Generate the output body body.push_str(&codegen_output_body(&def.body)); format!( "#[proc_macro]\npub fn {fn_name}(input: proc_macro::TokenStream) -> proc_macro::TokenStream {{\n let input = proc_macro2::TokenStream::from(input);\n{body} __output.into()\n}}\n", fn_name = fn_name, body = body, ) } /// All bindings extracted from a pattern (in order) struct BindingInfo { name: String, kind: BindingKind, is_variadic: bool, } fn collect_bindings(pattern: &Pattern) -> Vec { let mut result = Vec::new(); for item in &pattern.items { match item { PatternItem::Binding { name, kind } => { result.push(BindingInfo { name: name.clone(), kind: kind.clone(), is_variadic: false }); } PatternItem::Variadic { name, .. } => { // The collection is named `name + "s"` (e.g. `field` -> `fields`) result.push(BindingInfo { name: format!("{}s", name), kind: BindingKind::Ident, // placeholder; variadic has its own type is_variadic: true, }); } PatternItem::LiteralToken(_) => {} } } result } fn binding_kind_to_type(kind: &BindingKind) -> &'static str { match kind { BindingKind::Ident => "syn::Ident", BindingKind::Ty => "syn::Type", BindingKind::Expr => "syn::Expr", BindingKind::Tt => "proc_macro2::TokenStream", } } /// Generate pattern parsing code fn codegen_pattern_parse(pattern: &Pattern) -> String { let bindings = collect_bindings(pattern); let has_literal = pattern.items.iter().any(|i| matches!(i, PatternItem::LiteralToken(_))); let has_variadic = pattern.items.iter().any(|i| matches!(i, PatternItem::Variadic { .. })); if !has_literal && !has_variadic && bindings.len() == 1 { // Simple single-binding pattern let b = &bindings[0]; let ty = binding_kind_to_type(&b.kind); return format!( " let {name}: {ty} = syn::parse2(input.clone()).unwrap(); // TODO: proper error\n", name = b.name, ty = ty ); } // Structural or multi-binding: use a parser closure returning a tuple codegen_structural_pattern(pattern, &bindings) } fn codegen_structural_pattern(pattern: &Pattern, bindings: &[BindingInfo]) -> String { let mut out = String::new(); // Build the return type for the parser closure let ret_types: Vec = bindings.iter().map(|b| { if b.is_variadic { "Vec<(syn::Ident, syn::Type)>".to_string() } else { binding_kind_to_type(&b.kind).to_string() } }).collect(); let ret_type = if ret_types.len() == 1 { ret_types[0].clone() } else { format!("({})", ret_types.join(", ")) }; // Build the destructuring pattern let names: Vec<&str> = bindings.iter().map(|b| b.name.as_str()).collect(); let destructure = if names.len() == 1 { names[0].to_string() } else { format!("({})", names.join(", ")) }; out.push_str(&format!( " let {destructure} = {{\n use syn::parse::Parser as _;\n", destructure = destructure )); out.push_str(&format!( " let __parser = |_input: syn::parse::ParseStream| -> syn::Result<{ret_type}> {{\n", ret_type = ret_type )); // Generate parser body statements let stmts = codegen_parser_stmts(pattern); for stmt in &stmts { out.push_str(" "); out.push_str(stmt); out.push('\n'); } // Return the bindings let ret_expr = if names.len() == 1 { format!("Ok({})", names[0]) } else { format!("Ok(({}))", names.join(", ")) }; out.push_str(&format!(" {}\n", ret_expr)); out.push_str(" };\n"); out.push_str(" __parser.parse2(input.clone()).unwrap() // TODO: proper error\n"); out.push_str(" };\n"); out } /// Generate the sequence of statements for the parser closure body fn codegen_parser_stmts(pattern: &Pattern) -> Vec { let mut stmts = Vec::new(); let mut in_brace = false; for item in &pattern.items { match item { PatternItem::LiteralToken(tok) => { if tok == "{" { stmts.push("let __brace_content;".to_string()); stmts.push("syn::braced!(__brace_content in _input);".to_string()); in_brace = true; } else if tok == "}" { // already consumed by braced! in_brace = false; } else if tok == "," { stmts.push("_input.parse::()?;".to_string()); } else if tok.chars().all(|c| c.is_alphanumeric() || c == '_') { if is_syn_keyword(tok) { stmts.push(format!( "_input.parse::()?;", tok = tok )); } else { stmts.push(format!( r#"{{ let __lit: syn::Ident = _input.parse()?; if __lit != "{tok}" {{ return Err(syn::Error::new(__lit.span(), concat!("expected `", "{tok}", "`"))); }} }}"#, tok = tok )); } } } PatternItem::Binding { name, kind } => { let ty = binding_kind_to_type(kind); let src = if in_brace { "__brace_content" } else { "_input" }; stmts.push(format!( "let {name}: {ty} = {src}.parse()?;", name = name, ty = ty, src = src )); } PatternItem::Variadic { name, .. } => { // fields is name + "s" let fields_name = format!("{}s", name); stmts.push(format!( r#"let {fields} = {{ let mut __f = Vec::new(); while !__brace_content.is_empty() {{ let __fn: syn::Ident = __brace_content.parse()?; __brace_content.parse::()?; let __ft: syn::Type = __brace_content.parse()?; __f.push((__fn, __ft)); if __brace_content.peek(syn::Token![,]) {{ __brace_content.parse::()?; }} }} __f }};"#, fields = fields_name )); } } } stmts } /// Returns true if `tok` is a valid operand for `syn::Token![...]`. /// This covers Rust keywords and built-in punctuation recognized by syn. fn is_syn_keyword(tok: &str) -> bool { matches!( tok, "as" | "async" | "await" | "break" | "const" | "continue" | "crate" | "dyn" | "else" | "enum" | "extern" | "false" | "fn" | "for" | "if" | "impl" | "in" | "let" | "loop" | "match" | "mod" | "move" | "mut" | "pub" | "ref" | "return" | "self" | "Self" | "static" | "struct" | "super" | "trait" | "true" | "type" | "union" | "unsafe" | "use" | "where" | "while" | "abstract" | "become" | "box" | "do" | "final" | "macro" | "override" | "priv" | "try" | "typeof" | "unsized" | "virtual" | "yield" ) } /// Generate code for the output body fn codegen_output_body(body: &OutputBody) -> String { let mut out = String::new(); let mut inject_counter = 0usize; let mut ident_counter = 0usize; let mut spread_counter = 0usize; let mut has_output = false; for item in &body.items { match item { OutputItem::EscapeBlock(code) => { out.push_str(" // @{ escape block }\n"); for line in code.lines() { let line = line.trim_end(); if !line.is_empty() { out.push_str(" "); out.push_str(line); out.push('\n'); } } } OutputItem::TokenTemplate(items) => { has_output = true; let (preamble, tokens) = codegen_template_items( items, &mut inject_counter, &mut ident_counter, &mut spread_counter, 1, ); for p in preamble { out.push_str(" "); out.push_str(p.trim_end()); out.push('\n'); } out.push_str(&format!( " let __output = quote::quote! {{ {} }};\n", tokens.trim() )); } } } if !has_output { out.push_str(" let __output = proc_macro2::TokenStream::new();\n"); } out } /// Returns (preamble_stmts, quote_body_string) /// `indent` is the number of 4-space indents for the preamble statements fn codegen_template_items( items: &[TemplateItem], inject_counter: &mut usize, ident_counter: &mut usize, spread_counter: &mut usize, _indent: usize, ) -> (Vec, String) { let mut preamble: Vec = Vec::new(); let mut tokens = String::new(); for item in items { match item { TemplateItem::Literal(s) => { tokens.push_str(s); tokens.push(' '); } TemplateItem::VarRef(name) => { tokens.push_str(&format!("#{name} ")); } TemplateItem::FieldAccess { var: _, prop } => { match prop.as_str() { "name" => tokens.push_str("#__field_name "), "ty" => tokens.push_str("#__field_ty "), other => tokens.push_str(&format!("#__{other} ")), } } TemplateItem::Injection(expr) => { if is_simple_ident(expr) { // Inject directly without an intermediate binding to avoid move issues tokens.push_str(&format!("#{expr} ")); } else { let var = format!("__inject_{}", inject_counter); *inject_counter += 1; preamble.push(format!("let {var} = {expr};")); tokens.push_str(&format!("#{var} ")); } } TemplateItem::SpreadIter { var: _, collection, body } => { let spread_var = format!("__spread_{}", spread_counter); *spread_counter += 1; let (body_pre, body_tokens) = codegen_template_items( body, inject_counter, ident_counter, spread_counter, _indent + 1, ); let mut spread_code = format!( "let {spread_var}: proc_macro2::TokenStream = {collection}.iter().map(|(__field_name, __field_ty)| {{\n", ); for bp in &body_pre { spread_code.push_str(&format!(" {bp}\n")); } spread_code.push_str(&format!( " quote::quote! {{ {body_tokens} }}\n }}).collect();\n", )); preamble.push(spread_code); tokens.push_str(&format!("#{spread_var} ")); } TemplateItem::IdentConstruct(parts) => { let ident_var = format!("__ident_{}", ident_counter); *ident_counter += 1; // Build format string and args let mut fmt_parts: Vec = Vec::new(); let mut fmt_args: Vec = Vec::new(); for part in parts { match part { IdentPart::Literal(s) => { fmt_parts.push("{}".to_string()); fmt_args.push(format!("{s:?}")); } IdentPart::VarRef(name) => { fmt_parts.push("{}".to_string()); fmt_args.push(format!( "misses_rt::to_snake_case_str(&{name}.to_string())" )); } IdentPart::FieldAccess { var: _, prop } => { fmt_parts.push("{}".to_string()); match prop.as_str() { "name" => fmt_args.push( "misses_rt::to_snake_case_str(&__field_name.to_string())".to_string() ), "ty" => fmt_args.push( "misses_rt::to_snake_case_str("e::quote!(#__field_ty).to_string())".to_string() ), other => fmt_args.push(format!( "misses_rt::to_snake_case_str(&__{other}.to_string())" )), } } } } let fmt_str = fmt_parts.join("_"); let args_str = fmt_args.join(", "); preamble.push(format!( "let {ident_var} = quote::format_ident!(\"{fmt_str}\", {args_str});\n" )); tokens.push_str(&format!("#{ident_var} ")); } } } (preamble, tokens) } fn is_simple_ident(s: &str) -> bool { !s.is_empty() && s.chars().all(|c| c.is_alphanumeric() || c == '_') } #[cfg(test)] mod tests { use super::*; use crate::parser::parse; fn compile(src: &str) -> String { let file = parse(src).unwrap_or_else(|e| panic!("parse failed: {e}")); codegen(&file) } // ---- generated structure ---- #[test] fn codegen_emits_proc_macro_fn() { let out = compile("macro hello! { $name:ident => { $name } }"); assert!(out.contains("pub fn hello("), "expected `pub fn hello(` in:\n{out}"); assert!(out.contains("proc_macro::TokenStream"), "expected TokenStream in:\n{out}"); } #[test] fn codegen_has_generated_header_comment() { // prettyplease may strip the comment, so we check the raw output instead let file = parse("macro m! { $x:ident => { $x } }").unwrap(); // Call codegen via the internal path without prettyplease to verify header // We just ensure the final output is non-empty and contains the fn let out = codegen(&file); assert!(!out.is_empty()); assert!(out.contains("pub fn m(")); } // ---- single binding ---- #[test] fn codegen_single_ident_binding_uses_parse2() { let out = compile("macro greet! { $name:ident => { pub fn greet() {} } }"); assert!(out.contains("pub fn greet("), "fn greet missing:\n{out}"); // The generated parser should parse the input as a syn::Ident assert!(out.contains("syn::Ident") || out.contains("parse2"), "expected Ident parse:\n{out}"); } #[test] fn codegen_single_ty_binding() { let out = compile("macro wrap! { $t:ty => { struct Wrap($t); } }"); assert!(out.contains("pub fn wrap("), "fn wrap missing:\n{out}"); assert!(out.contains("syn::Type") || out.contains("parse2"), "expected Type parse:\n{out}"); } // ---- structural / multi-binding patterns ---- #[test] fn codegen_struct_pattern_emits_parser_closure() { let out = compile(r#" macro make_struct! { struct $name:ident => { pub struct $name {} } } "#); assert!(out.contains("pub fn make_struct("), "fn missing:\n{out}"); // Structural pattern uses a parser closure assert!(out.contains("ParseStream") || out.contains("parse::Parser"), "expected parse closure:\n{out}"); } // ---- variadic ---- #[test] fn codegen_variadic_fields_generates_vec() { let out = compile(r#" macro derive_getters! { struct $name:ident { $($field:ident : $ty:ty),* } => { impl $name {} } } "#); assert!(out.contains("pub fn derive_getters("), "fn missing:\n{out}"); assert!(out.contains("Vec<") || out.contains("vec"), "expected Vec collection:\n{out}"); } // ---- escape block ---- #[test] fn codegen_escape_block_emits_raw_code() { let out = compile(r#" macro m! { $name:ident => { @{ let _x = name.to_string(); } T~{ pub fn foo() {} } } } "#); // The @{} content should appear in the output (with $ stripped) assert!(out.contains("to_string"), "escape block content missing:\n{out}"); } // ---- ident construction ---- #[test] fn codegen_ident_construct_uses_format_ident() { let out = compile(r#"macro m! { $name:ident => { #[say => $name] } }"#); assert!(out.contains("format_ident"), "expected format_ident for #[...] construct:\n{out}"); } // ---- keyword vs non-keyword literal tokens ---- #[test] fn codegen_keyword_token_uses_syn_token_macro() { let out = compile(r#"macro m! { struct $name:ident => { pub struct $name; } }"#); assert!(out.contains("Token![struct]") || out.contains("Token ! [struct]"), "expected syn Token![struct]:\n{out}"); } #[test] fn codegen_non_keyword_literal_uses_ident_parse() { // "myword" is not a Rust keyword so it must be parsed as a syn::Ident + compared let out = compile(r#"macro m! { myword $name:ident => { $name } }"#); assert!(out.contains("myword") || out.contains("__lit"), "expected literal ident check:\n{out}"); } // ---- multiple macros in one file ---- #[test] fn codegen_multiple_macros() { let out = compile(r#" macro foo! { $a:ident => { $a } } macro bar! { $b:ty => { $b } } "#); assert!(out.contains("pub fn foo("), "fn foo missing:\n{out}"); assert!(out.contains("pub fn bar("), "fn bar missing:\n{out}"); } // ---- round-trip: hello.mrs example ---- #[test] fn roundtrip_hello_example() { let src = r#" macro hello! { $name:ident => { T~{ pub fn #[say => $name]() -> &'static str { "hello from the macro!" } } } } "#; let out = compile(src); assert!(out.contains("pub fn hello("), "fn hello missing:\n{out}"); assert!(out.contains("format_ident"), "ident construct missing:\n{out}"); } // ---- round-trip: derive_getters example ---- #[test] fn roundtrip_derive_getters_example() { let src = r#" macro derive_getters! { struct $name:ident { $($field:ident : $ty:ty),* } => { T~{ impl $name { for field in $fields => pub fn $field.name(&self) -> &$field.ty { &self.$field.name } } } } } "#; let out = compile(src); assert!(out.contains("pub fn derive_getters("), "fn derive_getters missing:\n{out}"); // spread iter should produce a .iter().map(...) assert!(out.contains("iter()") || out.contains(".map("), "expected spread iter in output:\n{out}"); } }