Implement Misses DSL compiler (parser + codegen)

- ast.rs: Full AST for .mrs files (MacroDef, Pattern, OutputBody, TemplateItem)
- parser.rs: Hand-written recursive descent parser with line/col error reporting
- codegen.rs: Transforms AST to Rust proc macro code using syn/quote/proc_macro2
- main.rs: CLI reads .mrs file, runs parse→codegen, writes to stdout or file
- Cargo.toml: Workspace with syn, quote, proc-macro2, prettyplease deps
- examples/: hello.mrs, derive_builder.mrs, declare_table.mrs reference examples

All three reference examples parse and produce prettyplease-formatted Rust output.
Generated code uses syn::parse::Parser closures for structural patterns, quote!
for token templates, spread iteration via .iter().map().collect(), and
format_ident! for identifier construction.

https://claude.ai/code/session_01Tppkab4eLsL21asHGjQJoF
This commit is contained in:
Claude
2026-03-07 11:37:58 +00:00
commit ed62264870
11 changed files with 1152 additions and 0 deletions
+381
View File
@@ -0,0 +1,381 @@
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<BindingInfo> {
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<String> = 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<String> {
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.chars().all(|c| c.is_alphanumeric() || c == '_') {
stmts.push(format!(
"_input.parse::<syn::Token![{tok}]>()?;",
tok = tok
));
} else if tok == "," {
stmts.push("_input.parse::<syn::Token![,]>()?;".to_string());
}
}
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::<syn::Token![:]>()?;
let __ft: syn::Type = __brace_content.parse()?;
__f.push((__fn, __ft));
if __brace_content.peek(syn::Token![,]) {{
__brace_content.parse::<syn::Token![,]>()?;
}}
}}
__f
}};"#,
fields = fields_name
));
}
}
}
stmts
}
/// 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>, String) {
let mut preamble: Vec<String> = 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<String> = Vec::new();
let mut fmt_args: Vec<String> = 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(&quote::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 == '_')
}