Add misses-rt crate, component example, README, and fix codegen for non-keyword literal tokens
- misses-rt: add Buffer/Markup HTML helpers, name/type helpers, parse_sections/parse_kv, and template AST - codegen: add is_syn_keyword() — non-keyword idents in patterns now generate parse+compare instead of syn::Token![…] - parser: split punct vs ident chars in literal pattern tokens so `props:` becomes two tokens - examples: add component.mrs/.rs demonstrating named literal tokens and tt capture - README: comprehensive guide covering .mrs syntax, running the compiler, misses-rt helpers, and examples https://claude.ai/code/session_01Tppkab4eLsL21asHGjQJoF
This commit is contained in:
@@ -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> => {
|
||||
<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:
|
||||
|
||||
```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<T> → true
|
||||
misses_rt::unwrap_option(&ty) // Option<T> → 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("<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
|
||||
|
||||
```rust
|
||||
// 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](https://github.com/dtolnay/prettyplease) for readable output.
|
||||
Reference in New Issue
Block a user