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:
Claude
2026-03-07 19:23:53 +00:00
parent f90953a3bb
commit e8d835e196
11 changed files with 1156 additions and 21 deletions
+32
View File
@@ -0,0 +1,32 @@
// component.mrs — Vue-like component macro generator
// Demonstrates: named literal tokens, variadic binding, $body:tt capture,
// escape block using misses_rt, expression injection
macro component! {
$name:ident { $($prop_name:ident : $prop_type:ty),* } $body:tt => {
@{
let struct_name = format_ident!("{}", name.to_string());
}
T~{
pub struct $name {
for prop in $prop_names =>
pub $prop_name.name: $prop_name.ty,
}
impl $name {
pub fn new(${ struct_name }(
for prop in $prop_names =>
$prop_name.name: $prop_name.ty,
)) -> Self {
Self {
for prop in $prop_names =>
$prop_name.name,
}
}
pub fn render(&self) -> misses_rt::Markup {
misses_rt::parse_template($body)
}
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
fn main() {
let source = include_str!("component.mrs");
println!("=== SOURCE ===\n{source}");
let file = misses::parse(source).expect("parse error");
let generated = misses::codegen(&file);
println!("=== GENERATED ===\n{generated}");
}
+28 -5
View File
@@ -170,13 +170,20 @@ fn codegen_parser_stmts(pattern: &Pattern) -> Vec<String> {
} else if tok == "}" {
// already consumed by braced!
in_brace = false;
} else if tok.chars().all(|c| c.is_alphanumeric() || c == '_') {
stmts.push(format!(
"_input.parse::<syn::Token![{tok}]>()?;",
tok = tok
));
} else if tok == "," {
stmts.push("_input.parse::<syn::Token![,]>()?;".to_string());
} else if tok.chars().all(|c| c.is_alphanumeric() || c == '_') {
if is_syn_keyword(tok) {
stmts.push(format!(
"_input.parse::<syn::Token![{tok}]>()?;",
tok = tok
));
} else {
stmts.push(format!(
r#"{{ let __lit: syn::Ident = _input.parse()?; if __lit != "{tok}" {{ return Err(syn::Error::new(__lit.span(), concat!("expected `", "{tok}", "`"))); }} }}"#,
tok = tok
));
}
}
}
PatternItem::Binding { name, kind } => {
@@ -213,6 +220,22 @@ fn codegen_parser_stmts(pattern: &Pattern) -> Vec<String> {
stmts
}
/// Returns true if `tok` is a valid operand for `syn::Token![...]`.
/// This covers Rust keywords and built-in punctuation recognized by syn.
fn is_syn_keyword(tok: &str) -> bool {
matches!(
tok,
"as" | "async" | "await" | "break" | "const" | "continue" | "crate"
| "dyn" | "else" | "enum" | "extern" | "false" | "fn" | "for"
| "if" | "impl" | "in" | "let" | "loop" | "match" | "mod"
| "move" | "mut" | "pub" | "ref" | "return" | "self" | "Self"
| "static" | "struct" | "super" | "trait" | "true" | "type"
| "union" | "unsafe" | "use" | "where" | "while" | "abstract"
| "become" | "box" | "do" | "final" | "macro" | "override"
| "priv" | "try" | "typeof" | "unsized" | "virtual" | "yield"
)
}
/// Generate code for the output body
fn codegen_output_body(body: &OutputBody) -> String {
let mut out = String::new();
+18 -15
View File
@@ -224,22 +224,25 @@ impl<'a> Parser<'a> {
self.advance(1);
items.push(self.parse_binding()?);
} else {
// literal token: collect until whitespace or $ or =>
let len: usize = rest.chars()
.take_while(|&c| !c.is_whitespace() && c != '$')
.map(|c| c.len_utf8())
.sum();
// check if it starts with =>
let tok = &rest[..len];
if tok.is_empty() {
break;
let c = rest.chars().next().unwrap();
if c.is_ascii_punctuation() {
// Single-character punctuation token (`:`, `{`, `}`, `,`, `;`, etc.)
// But first check that it's not the start of `=>`
if rest.starts_with("=>") {
break;
}
items.push(PatternItem::LiteralToken(c.to_string()));
self.advance(c.len_utf8());
} else {
// Identifier / keyword token: collect alphanumeric + underscore
let len: usize = rest.chars()
.take_while(|&c| c.is_alphanumeric() || c == '_')
.map(|c| c.len_utf8())
.sum();
if len == 0 { break; }
items.push(PatternItem::LiteralToken(rest[..len].to_string()));
self.advance(len);
}
// Check if this token starts with =>
if tok.starts_with("=>") {
break;
}
items.push(PatternItem::LiteralToken(tok.to_string()));
self.advance(len);
}
}