Files
misses/README.md
T
Claude a86ba9760c Update README with misses init docs and error handling note
- Rewrite Quick start to show `cargo install misses` and the new
  `misses init` subcommand as the primary entry point
- Add a dedicated `misses init` section explaining binary mode
  (build-time codegen via OUT_DIR, nothing committed) and library
  mode (--lib: generated/ committed, misses-source feature to
  rebuild, zero misses dependency for downstream users)
- Update project layout to include init.rs
- Add note that parse errors surface as compiler diagnostics via
  into_compile_error() rather than panicking

https://claude.ai/code/session_01D2g6zGJsBeUEzxBb7AvFDt
2026-03-08 09:34:43 +00:00

8.7 KiB

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

# Install the CLI
cargo install misses

# Scaffold a new proc-macro crate (binary mode)
misses init my-macros

# Scaffold a library-friendly crate (generated code committed, no misses required downstream)
misses init --lib my-macros

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

misses init — project scaffolding

misses init creates a ready-to-build proc-macro crate. Two modes are available depending on whether you're shipping a binary or a library.

Binary mode (default)

misses init my-macros

Use this when you control the build environment (e.g. an application binary). The .mrs sources are compiled to Rust at build time via build.rs; the generated code lives in Cargo's $OUT_DIR and is never committed.

my-macros/
├── Cargo.toml            # misses as a plain [build-dependencies]
├── build.rs              # compiles src/macros/*.mrs → $OUT_DIR/*.rs
└── src/
    ├── lib.rs            # include!(concat!(env!("OUT_DIR"), "/hello.rs"))
    └── macros/
        └── hello.mrs     # your macro source (committed)

Users only need misses installed to build from source, which is fine for an app you control.

Library mode (--lib)

misses init --lib my-macros

Use this when you're publishing a library crate. Downstream users of your crate should not be forced to install misses. The generated Rust code is committed to generated/ and shipped on crates.io.

my-macros/
├── Cargo.toml            # misses behind [features] misses-source = ["dep:misses"]
├── build.rs              # no-op unless --features misses-source
├── generated/
│   └── hello.rs          # pre-generated, committed to git
└── src/
    ├── lib.rs            # include!(concat!(env!("CARGO_MANIFEST_DIR"), "/generated/hello.rs"))
    └── macros/
        └── hello.mrs     # your macro source (committed)

When you modify a .mrs file, regenerate with:

cargo build --features misses-source

The misses-source feature makes misses an optional build-dependency and gates regeneration in build.rs. Downstream users build normally with no misses dependency.


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() };
            }
        }
    }
}

Run the bundled examples to 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

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 and CLI
│   ├── src/
│   │   ├── ast.rs         # Full AST types
│   │   ├── parser.rs      # Hand-written recursive descent parser
│   │   ├── codegen.rs     # AST → Rust source string
│   │   ├── init.rs        # `misses init` scaffolding logic
│   │   ├── lib.rs         # Library API (parse + codegen)
│   │   └── main.rs        # CLI binary (compile + init subcommands)
│   └── 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.

Parse errors are surfaced as proper Rust compiler diagnostics via syn::Error::into_compile_error() rather than panicking.

The generated file is formatted with prettyplease for readable output.