diff --git a/Cargo.lock b/Cargo.lock index 5b5a2ae..09264f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,15 @@ dependencies = [ "syn", ] +[[package]] +name = "misses-rt" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "prettyplease" version = "0.2.37" diff --git a/Cargo.toml b/Cargo.toml index d0af0f8..b54ae9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["misses-compiler"] +members = ["misses-compiler", "misses-rt"] resolver = "2" diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad3bece --- /dev/null +++ b/README.md @@ -0,0 +1,229 @@ +# 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 + +```bash +# 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 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: + +```toml +[dependencies] +misses-rt = { path = "../misses-rt" } +``` + +### Name helpers + +```rust +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 + +```rust +misses_rt::is_option(&ty) // Option → true +misses_rt::unwrap_option(&ty) // Option → Some(&T) +misses_rt::to_sql_type(&ty) // syn::Type → "TEXT", "INTEGER", … +``` + +### HTML template helpers + +```rust +let mut buf = misses_rt::Buffer::new(); +buf.push_str(""); +buf.push_escaped("