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,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user