Add examples with cargo run --example support
- Move .mrs files into misses-compiler/examples/ alongside .rs runners - Add lib.rs exposing parse/codegen for use by example binaries and main.rs - Fix parser: #[attr] vs #[a => b] disambiguation (look for => before ]) - Fix parser: `for` keyword in `impl Foo for Bar` no longer triggers spread (is_spread_for lookahead: must match `for <ident> in $<ident> =>`) - Add derive_getters.mrs: getter methods via spread iteration - Add make_id_type.mrs: newtype u64 wrapper with From impls, derive attrs Run with: cargo run --example hello|derive_builder|declare_table|derive_getters|make_id_type https://claude.ai/code/session_01Tppkab4eLsL21asHGjQJoF
This commit is contained in:
@@ -1,9 +0,0 @@
|
|||||||
macro hello! {
|
|
||||||
$name:ident => {
|
|
||||||
T~{
|
|
||||||
pub fn #[say => hello]() -> &'static str {
|
|
||||||
${ format!("hello, {}!", stringify!(name)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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! {
|
macro declare_table! {
|
||||||
$name:ident { $body:tt } => {
|
$name:ident { $body:tt } => {
|
||||||
@{
|
@{
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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! {
|
macro derive_builder! {
|
||||||
struct $name:ident { $($field:ident : $ty:ty),* } => {
|
struct $name:ident { $($field:ident : $ty:ty),* } => {
|
||||||
@{
|
@{
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<u64> for $name {
|
||||||
|
fn from(val: u64) -> Self { Self(val) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<$name> for u64 {
|
||||||
|
fn from(id: $name) -> Self { id.0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
pub mod ast;
|
||||||
|
pub mod parser;
|
||||||
|
pub mod codegen;
|
||||||
|
|
||||||
|
pub use parser::{parse, ParseError};
|
||||||
|
pub use codegen::codegen;
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
mod ast;
|
|
||||||
mod parser;
|
|
||||||
mod codegen;
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -20,7 +16,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let ast = match parser::parse(&src) {
|
let ast = match misses::parse(&src) {
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Parse error: {}", 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 {
|
if args.len() >= 3 {
|
||||||
let output_path = PathBuf::from(&args[2]);
|
let output_path = PathBuf::from(&args[2]);
|
||||||
|
|||||||
@@ -317,7 +317,9 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for $var in $collection => template (spread iteration)
|
// 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
|
||||||
flush_literal!();
|
flush_literal!();
|
||||||
sub.skip_whitespace_and_comments();
|
sub.skip_whitespace_and_comments();
|
||||||
@@ -351,12 +353,19 @@ impl<'a> Parser<'a> {
|
|||||||
continue;
|
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("#[") {
|
if rest.starts_with("#[") {
|
||||||
flush_literal!();
|
let inner = &rest[2..];
|
||||||
sub.advance(2);
|
let close = inner.find(|c| c == ']' || c == '\n').unwrap_or(inner.len());
|
||||||
items.push(sub.parse_ident_construct()?);
|
let is_ident_construct = inner[..close].contains("=>");
|
||||||
continue;
|
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
|
// ${ expr } injection
|
||||||
@@ -494,6 +503,38 @@ fn find_matching_close(s: &str, close: char) -> Option<usize> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether a `for ...` line is a spread iteration: `for <ident> in $<ident> =>`
|
||||||
|
/// 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 `$<ident>`
|
||||||
|
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)
|
/// Rewrite `$name` -> `name` in raw Rust code (for @{} blocks)
|
||||||
fn rewrite_dollar_vars(s: &str) -> String {
|
fn rewrite_dollar_vars(s: &str) -> String {
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user