11 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.
Why Misses?
Writing proc macros means wrestling with syn parsing, quote! boilerplate, error handling, and variadic patterns. You write the same patterns over and over.
Misses lets you write the macro once, in readable syntax, and generates all the boilerplate for you.
Here's a real example:
derive_getters.mrs
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 }
}
}
}
}
Generated output
extern crate proc_macro;
#[proc_macro]
pub fn derive_rpc(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = proc_macro2::TokenStream::from(input);
use quote::format_ident;
let (name, params, ret) = {
use syn::parse::Parser as _;
let __parser = |
_input: syn::parse::ParseStream,
| -> syn::Result<(syn::Ident, Vec<(syn::Ident, syn::Type)>, syn::Type)> {
let name: syn::Ident = _input.parse()?;
let params = {
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
};
let ret: syn::Type = _input.parse()?;
Ok((name, params, ret))
};
match __parser.parse2(input.clone()) {
Ok(v) => v,
Err(e) => return e.into_compile_error().into(),
}
};
let req_name = format_ident!("{}Request", name);
let res_name = format_ident!("{}Response", name);
let __spread_0: proc_macro2::TokenStream = params
.iter()
.map(|(__field_name, __field_ty)| {
quote::quote! {
pub # __field_name : # __field_ty,
}
})
.collect();
let __output = quote::quote! {
pub struct # req_name { # __spread_0 } pub struct # res_name { pub result : #
ret, }
};
__output.into()
}
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>"); // → "<script>"
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:
- A
#[proc_macro]function that receivesproc_macro::TokenStream. - Parser statements that destructure the input according to the pattern.
- Any
@{}escape-block statements, executed inline. - A
quote::quote! { … }call whose body mirrors theT~{}template, with spread iterators for variadic bindings. - The
quote!result is returned asproc_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.