Claude 0ac3a473d4 Replace .unwrap() with proper proc macro error handling in codegen
The generated proc macro code was using .unwrap() for parse failures,
causing panics instead of emitting proper Rust compile errors. Replace
both unwrap calls with match expressions that call
e.into_compile_error().into() to surface parse failures as compiler
diagnostics in the generated proc macros.

https://claude.ai/code/session_01D2g6zGJsBeUEzxBb7AvFDt
2026-03-08 09:11:41 +00:00

Misses

Misses is a DSL compiler that transforms .mrs macro definitions into ready-to-use Rust proc macro source files. Write your macro once in a concise, readable syntax; Misses generates the full syn/quote boilerplate for you.


Quick start

# Build everything
cargo build

# Run a bundled example (compare .mrs source with generated Rust)
cargo run --example make_id_type
cargo run --example derive_builder
cargo run --example derive_getters
cargo run --example declare_table
cargo run --example component

# Compile a .mrs file to Rust
cargo run --bin misses -- path/to/my_macro.mrs > src/generated.rs

The .mrs syntax

A .mrs file contains one or more macro definitions:

macro macro_name! {
    <pattern> => {
        <output>
    }
}

Pattern bindings

Syntax Parsed as
$name:ident A single identifier
$expr:expr Any Rust expression
$ty:ty A Rust type
$body:tt A raw token-tree (brace group)
$($field:ident : $ty:ty),* Variadic — zero-or-more comma-separated pairs

Literal tokens in the pattern are matched verbatim. Rust keywords use syn::Token![…]; arbitrary identifiers are matched by value.

Output blocks

An output block may contain any mix of:

Escape block @{ … } — arbitrary Rust code executed at macro-expansion time:

@{
    let builder_name = format_ident!("{}_Builder", name.to_string());
}

Token template T~{ … } — generates the quote::quote! { … } call. Inside a template you may use:

Syntax Meaning
$name Interpolate the bound variable name
$field.name / $field.ty Access a field of a variadic element
${ expr } Evaluate an arbitrary Rust expression and interpolate the result
for x in $xs => … Spread — iterate the variadic collection and repeat a line
#[a => b_$name] Construct an identifier from parts (uses format_ident!)

Examples

make_id_type.mrs — newtype ID wrapper

macro make_id_type! {
    $name:ident => {
        T~{
            #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
            pub struct $name(pub u64);

            impl $name {
                pub fn new(val: u64) -> Self { Self(val) }
                pub fn value(self) -> u64 { self.0 }
            }
        }
    }
}

derive_builder.mrs — builder pattern

macro derive_builder! {
    struct $name:ident { $($field:ident : $ty:ty),* } => {
        @{
            let builder_name = format_ident!("{}_Builder", name.to_string());
        }
        T~{
            pub struct ${ builder_name } {
                for field in $fields =>
                    pub $field.name: Option<$field.ty>,
            }
            impl ${ builder_name } {
                pub fn build(self) -> $name {
                    $name {
                        for field in $fields =>
                            $field.name: self.$field.name.unwrap(),
                    }
                }
            }
        }
    }
}

declare_table.mrs — DSL to struct with runtime helper

macro declare_table! {
    $name:ident { $body:tt } => {
        @{
            let table_str = misses_rt::to_table_name(&name);
        }
        T~{
            pub struct $name {
                pub id: u64,
            }
            impl $name {
                pub const TABLE: &'static str = ${ table_str.as_str() };
            }
        }
    }
}

The misses-rt runtime library

misses-rt provides helpers that generated proc macro code can call at expansion time. Add it to your proc macro crate:

[dependencies]
misses-rt = { path = "../misses-rt" }

Name helpers

misses_rt::to_table_name(&ident)  // UsersAccount → "users_accounts"
misses_rt::to_snake_case(&ident)  // MyType → "my_type"
misses_rt::to_snake_case_str(s)   // "MyType" → "my_type"
misses_rt::to_pascal_case(s)      // "my_type" → MyType (Ident)
misses_rt::concat_ident(&[a, b])  // ["foo", "Bar"] → fooBar (Ident)

Type helpers

misses_rt::is_option(&ty)         // Option<T> → true
misses_rt::unwrap_option(&ty)     // Option<T> → Some(&T)
misses_rt::to_sql_type(&ty)       // syn::Type → "TEXT", "INTEGER", …

HTML template helpers

let mut buf = misses_rt::Buffer::new();
buf.push_str("<b>");
buf.push_escaped("<script>"); // → "&lt;script&gt;"
let markup: misses_rt::Markup = misses_rt::Markup::from_buffer(buf);
println!("{}", markup.into_string());

Section / key-value parsing

// Parse `props { … } slots { … }` blocks from a token stream
let map = misses_rt::parse_sections(tokens, &["props", "slots"]);
let props_tokens = map.get("props");

// Parse `key = value, …` pairs
let kv = misses_rt::parse_kv(tokens);
let val = kv.get("name");

Project layout

misses/
├── misses-compiler/       # The .mrs → Rust transpiler
│   ├── src/
│   │   ├── ast.rs         # Full AST types
│   │   ├── parser.rs      # Hand-written recursive descent parser
│   │   ├── codegen.rs     # AST → Rust source string
│   │   ├── lib.rs         # Library API (parse + codegen)
│   │   └── main.rs        # CLI binary
│   └── examples/          # Runnable .mrs demonstrations
│       ├── make_id_type.{mrs,rs}
│       ├── derive_builder.{mrs,rs}
│       ├── derive_getters.{mrs,rs}
│       ├── declare_table.{mrs,rs}
│       └── component.{mrs,rs}
└── misses-rt/             # Runtime helpers for generated proc macros
    └── src/
        ├── lib.rs         # Buffer, Markup, name/type helpers
        ├── parse_helpers.rs
        └── template.rs    # HTML template AST + code-gen

How code generation works

For each .mrs macro, the compiler generates:

  1. A #[proc_macro] function that receives proc_macro::TokenStream.
  2. Parser statements that destructure the input according to the pattern.
  3. Any @{} escape-block statements, executed inline.
  4. A quote::quote! { … } call whose body mirrors the T~{} template, with spread iterators for variadic bindings.
  5. The quote! result is returned as proc_macro::TokenStream.

The generated file is formatted with prettyplease for readable output.

S
Description
No description provided
Readme
108 KiB
Languages
Rust 99.6%
Nix 0.4%