use nyash_rust::ASTNode; use std::sync::{Mutex, OnceLock}; /// MacroBox API — user-extensible macro expansion units (experimental) /// /// Philosophy: /// - Deterministic, side-effect free, no IO. Pure AST -> AST transforms. /// - Prefer operating on public interfaces (Box public fields/methods) and avoid /// coupling to private internals. pub trait MacroBox: Send + Sync { fn name(&self) -> &'static str; fn expand(&self, ast: &ASTNode) -> ASTNode; } static REG: OnceLock>> = OnceLock::new(); fn registry() -> &'static Mutex> { REG.get_or_init(|| Mutex::new(Vec::new())) } /// Register a MacroBox. Intended to be called at startup/init paths. pub fn register(m: &'static dyn MacroBox) { let reg = registry(); let mut guard = reg.lock().expect("macro registry poisoned"); // avoid duplicates if !guard.iter().any(|e| e.name() == m.name()) { guard.push(m); } } /// Gate for MacroBox execution. /// /// Legacy env `NYASH_MACRO_BOX=1` still forces ON, but by default we /// synchronize with the macro system gate so user macros run when macros are enabled. pub fn enabled() -> bool { if crate::config::env::macro_box() { return true; } super::enabled() } /// Expand AST by applying all registered MacroBoxes in order once. pub fn expand_all_once(ast: &ASTNode) -> ASTNode { if !enabled() { return ast.clone(); } let reg = registry(); let guard = reg.lock().expect("macro registry poisoned"); let mut cur = ast.clone(); for m in guard.iter() { let out = m.expand(&cur); cur = out; } cur } // ---- Built-in example (optional) ---- pub struct UppercasePrintMacro; impl MacroBox for UppercasePrintMacro { fn name(&self) -> &'static str { "UppercasePrintMacro" } fn expand(&self, ast: &ASTNode) -> ASTNode { use nyash_rust::ast::{ASTNode as A, LiteralValue, Span}; fn go(n: &A) -> A { match n.clone() { A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|c| go(&c)).collect(), span, }, A::Print { expression, span } => { match &*expression { A::Literal { value: LiteralValue::String(s), .. } => { // Demo: if string starts with "UPPER:", uppercase the rest. if let Some(rest) = s.strip_prefix("UPPER:") { let up = rest.to_uppercase(); A::Print { expression: Box::new(A::Literal { value: LiteralValue::String(up), span: Span::unknown(), }), span, } } else { A::Print { expression: Box::new(go(&*expression)), span, } } } other => A::Print { expression: Box::new(go(other)), span, }, } } A::Assignment { target, value, span, } => A::Assignment { target: Box::new(go(&*target)), value: Box::new(go(&*value)), span, }, A::If { condition, then_body, else_body, span, } => A::If { condition: Box::new(go(&*condition)), then_body: then_body.into_iter().map(|c| go(&c)).collect(), else_body: else_body.map(|v| v.into_iter().map(|c| go(&c)).collect()), span, }, A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(go(v))), span, }, A::FieldAccess { object, field, span, } => A::FieldAccess { object: Box::new(go(&*object)), field, span, }, A::MethodCall { object, method, arguments, span, } => A::MethodCall { object: Box::new(go(&*object)), method, arguments: arguments.into_iter().map(|c| go(&c)).collect(), span, }, A::FunctionCall { name, arguments, span, } => A::FunctionCall { name, arguments: arguments.into_iter().map(|c| go(&c)).collect(), span, }, A::BinaryOp { operator, left, right, span, } => A::BinaryOp { operator, left: Box::new(go(&*left)), right: Box::new(go(&*right)), span, }, A::UnaryOp { operator, operand, span, } => A::UnaryOp { operator, operand: Box::new(go(&*operand)), span, }, A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|c| go(&c)).collect(), span, }, A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k, v)| (k, go(&v))).collect(), span, }, other => other, } } go(ast) } } static INIT_FLAG: OnceLock<()> = OnceLock::new(); /// Initialize built-in demo MacroBoxes when enabled by env flags. pub fn init_builtin() { INIT_FLAG.get_or_init(|| { // Explicit example toggle if crate::config::env::macro_box_example() { register(&UppercasePrintMacro); } // Comma-separated names: NYASH_MACRO_BOX_ENABLE="UppercasePrintMacro,Other" if let Some(list) = crate::config::env::macro_box_enable() { for name in list.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) { match name { "UppercasePrintMacro" => register(&UppercasePrintMacro), _ => { eprintln!("[macro][box] unknown MacroBox '{}', ignoring", name); } } } } }); }