diff --git a/examples/hello.mrs b/examples/hello.mrs deleted file mode 100644 index f4bc66b..0000000 --- a/examples/hello.mrs +++ /dev/null @@ -1,9 +0,0 @@ -macro hello! { - $name:ident => { - T~{ - pub fn #[say => hello]() -> &'static str { - ${ format!("hello, {}!", stringify!(name)) } - } - } - } -} diff --git a/examples/declare_table.mrs b/misses-compiler/examples/declare_table.mrs similarity index 67% rename from examples/declare_table.mrs rename to misses-compiler/examples/declare_table.mrs index a0b325b..25ef33e 100644 --- a/examples/declare_table.mrs +++ b/misses-compiler/examples/declare_table.mrs @@ -1,3 +1,6 @@ +// declare_table.mrs — DSL-to-struct mapping +// Demonstrates: $body:tt raw token capture, escape block calling misses_rt, +// expression injection of a &str macro declare_table! { $name:ident { $body:tt } => { @{ diff --git a/misses-compiler/examples/declare_table.rs b/misses-compiler/examples/declare_table.rs new file mode 100644 index 0000000..f5f8561 --- /dev/null +++ b/misses-compiler/examples/declare_table.rs @@ -0,0 +1,20 @@ +fn main() { + let src = include_str!("declare_table.mrs"); + let bar = "=".repeat(60); + println!("{bar}"); + println!("SOURCE declare_table.mrs"); + println!("{bar}"); + println!("{}", src.trim()); + println!(); + + match misses::parse(src) { + Err(e) => eprintln!("parse error: {e}"), + Ok(ast) => { + let generated = misses::codegen(&ast); + println!("{bar}"); + println!("GENERATED"); + println!("{bar}"); + println!("{}", generated.trim()); + } + } +} diff --git a/examples/derive_builder.mrs b/misses-compiler/examples/derive_builder.mrs similarity index 75% rename from examples/derive_builder.mrs rename to misses-compiler/examples/derive_builder.mrs index a1d5de0..a10d24b 100644 --- a/examples/derive_builder.mrs +++ b/misses-compiler/examples/derive_builder.mrs @@ -1,3 +1,6 @@ +// derive_builder.mrs — builder pattern generator +// Demonstrates: struct pattern, variadic field binding, escape block, +// expression injection, spread iteration in struct and impl bodies macro derive_builder! { struct $name:ident { $($field:ident : $ty:ty),* } => { @{ diff --git a/misses-compiler/examples/derive_builder.rs b/misses-compiler/examples/derive_builder.rs new file mode 100644 index 0000000..5945633 --- /dev/null +++ b/misses-compiler/examples/derive_builder.rs @@ -0,0 +1,20 @@ +fn main() { + let src = include_str!("derive_builder.mrs"); + let bar = "=".repeat(60); + println!("{bar}"); + println!("SOURCE derive_builder.mrs"); + println!("{bar}"); + println!("{}", src.trim()); + println!(); + + match misses::parse(src) { + Err(e) => eprintln!("parse error: {e}"), + Ok(ast) => { + let generated = misses::codegen(&ast); + println!("{bar}"); + println!("GENERATED"); + println!("{bar}"); + println!("{}", generated.trim()); + } + } +} diff --git a/misses-compiler/examples/derive_getters.mrs b/misses-compiler/examples/derive_getters.mrs new file mode 100644 index 0000000..210c2f8 --- /dev/null +++ b/misses-compiler/examples/derive_getters.mrs @@ -0,0 +1,13 @@ +// derive_getters.mrs — auto-generate getter methods +// Demonstrates: struct pattern, variadic binding, spread iteration generating +// impl methods with reference return types +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 } + } + } + } +} diff --git a/misses-compiler/examples/derive_getters.rs b/misses-compiler/examples/derive_getters.rs new file mode 100644 index 0000000..3b08ea8 --- /dev/null +++ b/misses-compiler/examples/derive_getters.rs @@ -0,0 +1,20 @@ +fn main() { + let src = include_str!("derive_getters.mrs"); + let bar = "=".repeat(60); + println!("{bar}"); + println!("SOURCE derive_getters.mrs"); + println!("{bar}"); + println!("{}", src.trim()); + println!(); + + match misses::parse(src) { + Err(e) => eprintln!("parse error: {e}"), + Ok(ast) => { + let generated = misses::codegen(&ast); + println!("{bar}"); + println!("GENERATED"); + println!("{bar}"); + println!("{}", generated.trim()); + } + } +} diff --git a/misses-compiler/examples/hello.mrs b/misses-compiler/examples/hello.mrs new file mode 100644 index 0000000..176e863 --- /dev/null +++ b/misses-compiler/examples/hello.mrs @@ -0,0 +1,11 @@ +// hello.mrs — smoke test +// Demonstrates: single ident binding, ident construction with #[a => b] +macro hello! { + $name:ident => { + T~{ + pub fn #[say => $name]() -> &'static str { + "hello from the macro!" + } + } + } +} diff --git a/misses-compiler/examples/hello.rs b/misses-compiler/examples/hello.rs new file mode 100644 index 0000000..22bd132 --- /dev/null +++ b/misses-compiler/examples/hello.rs @@ -0,0 +1,24 @@ +fn main() { + let src = include_str!("hello.mrs"); + show("hello.mrs", src); +} + +fn show(name: &str, src: &str) { + let bar = "=".repeat(60); + println!("{bar}"); + println!("SOURCE {name}"); + println!("{bar}"); + println!("{}", src.trim()); + println!(); + + match misses::parse(src) { + Err(e) => eprintln!("parse error: {e}"), + Ok(ast) => { + let generated = misses::codegen(&ast); + println!("{bar}"); + println!("GENERATED"); + println!("{bar}"); + println!("{}", generated.trim()); + } + } +} diff --git a/misses-compiler/examples/make_id_type.mrs b/misses-compiler/examples/make_id_type.mrs new file mode 100644 index 0000000..526ec1c --- /dev/null +++ b/misses-compiler/examples/make_id_type.mrs @@ -0,0 +1,24 @@ +// make_id_type.mrs — newtype ID wrapper generator +// Demonstrates: single ident binding, multi-line template output, +// derive attributes, impl blocks, no escape block needed +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 } + } + + impl From for $name { + fn from(val: u64) -> Self { Self(val) } + } + + impl From<$name> for u64 { + fn from(id: $name) -> Self { id.0 } + } + } + } +} diff --git a/misses-compiler/examples/make_id_type.rs b/misses-compiler/examples/make_id_type.rs new file mode 100644 index 0000000..b0604a5 --- /dev/null +++ b/misses-compiler/examples/make_id_type.rs @@ -0,0 +1,20 @@ +fn main() { + let src = include_str!("make_id_type.mrs"); + let bar = "=".repeat(60); + println!("{bar}"); + println!("SOURCE make_id_type.mrs"); + println!("{bar}"); + println!("{}", src.trim()); + println!(); + + match misses::parse(src) { + Err(e) => eprintln!("parse error: {e}"), + Ok(ast) => { + let generated = misses::codegen(&ast); + println!("{bar}"); + println!("GENERATED"); + println!("{bar}"); + println!("{}", generated.trim()); + } + } +} diff --git a/misses-compiler/src/lib.rs b/misses-compiler/src/lib.rs new file mode 100644 index 0000000..4fd6fb2 --- /dev/null +++ b/misses-compiler/src/lib.rs @@ -0,0 +1,6 @@ +pub mod ast; +pub mod parser; +pub mod codegen; + +pub use parser::{parse, ParseError}; +pub use codegen::codegen; diff --git a/misses-compiler/src/main.rs b/misses-compiler/src/main.rs index d225165..66331d4 100644 --- a/misses-compiler/src/main.rs +++ b/misses-compiler/src/main.rs @@ -1,7 +1,3 @@ -mod ast; -mod parser; -mod codegen; - use std::path::PathBuf; fn main() { @@ -20,7 +16,7 @@ fn main() { } }; - let ast = match parser::parse(&src) { + let ast = match misses::parse(&src) { Ok(a) => a, Err(e) => { eprintln!("Parse error: {}", e); @@ -28,7 +24,7 @@ fn main() { } }; - let generated = codegen::codegen(&ast); + let generated = misses::codegen(&ast); if args.len() >= 3 { let output_path = PathBuf::from(&args[2]); diff --git a/misses-compiler/src/parser.rs b/misses-compiler/src/parser.rs index 8869d24..97348b8 100644 --- a/misses-compiler/src/parser.rs +++ b/misses-compiler/src/parser.rs @@ -317,7 +317,9 @@ impl<'a> Parser<'a> { } // for $var in $collection => template (spread iteration) - if rest.trim_start().starts_with("for ") { + // Use lookahead to distinguish from Rust's `for` keyword in other contexts + // (e.g. `impl Foo for Bar` or `for x in iter`). + if rest.trim_start().starts_with("for ") && is_spread_for(rest.trim_start()) { // flush flush_literal!(); sub.skip_whitespace_and_comments(); @@ -351,12 +353,19 @@ impl<'a> Parser<'a> { continue; } - // #[ ident construct + // #[ — either ident construction (#[say => hello]) or a Rust attribute (#[derive(Debug)]) + // Distinguish by looking for => before the closing ] on the same line. if rest.starts_with("#[") { - flush_literal!(); - sub.advance(2); - items.push(sub.parse_ident_construct()?); - continue; + let inner = &rest[2..]; + let close = inner.find(|c| c == ']' || c == '\n').unwrap_or(inner.len()); + let is_ident_construct = inner[..close].contains("=>"); + if is_ident_construct { + flush_literal!(); + sub.advance(2); + items.push(sub.parse_ident_construct()?); + continue; + } + // Otherwise fall through to literal accumulation below } // ${ expr } injection @@ -494,6 +503,38 @@ fn find_matching_close(s: &str, close: char) -> Option { None } +/// Check whether a `for ...` line is a spread iteration: `for in $ =>` +/// Returns false for ordinary Rust `for`/`impl Foo for Bar` patterns. +fn is_spread_for(s: &str) -> bool { + // s starts with "for " + let s = s.strip_prefix("for ").unwrap_or(s).trim_start(); + // loop variable: plain ident (no leading $) + if s.starts_with('$') { + return false; + } + let var_end: usize = s.chars().take_while(|&c| c.is_alphanumeric() || c == '_').map(|c| c.len_utf8()).sum(); + if var_end == 0 { + return false; + } + let s = s[var_end..].trim_start(); + // must be followed by `in` + let s = match s.strip_prefix("in") { + Some(rest) if rest.starts_with(|c: char| c.is_whitespace()) => rest.trim_start(), + _ => return false, + }; + // then `$` + let s = match s.strip_prefix('$') { + Some(rest) => rest, + None => return false, + }; + let col_end: usize = s.chars().take_while(|&c| c.is_alphanumeric() || c == '_').map(|c| c.len_utf8()).sum(); + if col_end == 0 { + return false; + } + // then optional whitespace and `=>` + s[col_end..].trim_start().starts_with("=>") +} + /// Rewrite `$name` -> `name` in raw Rust code (for @{} blocks) fn rewrite_dollar_vars(s: &str) -> String { let mut out = String::new();