Files
hakorune/src/macro/macro_box.rs
tomoaki 80f3403049 refactor(config): Phase 286A/B/287 - Config system consolidation
Phase 286A: Macro environment variable consolidation
- src/config/env/macro_flags.rs: NYASH_MACRO_* flags centralized
- Removed duplicate ny_compiler_* functions (keep in selfhost_flags.rs)
- Fixed deprecation warning logic (return None when env var not set)
- Updated callers: src/macro/{ctx,engine,macro_box,macro_box_ny,mod}.rs

Phase 286B: Box Factory environment variable consolidation
- src/config/env/box_factory_flags.rs: NYASH_BOX_FACTORY_* flags centralized
- Updated callers: src/box_factory/mod.rs, src/runtime/plugin_loader*.rs

Phase 287: Config Catalog implementation
- src/config/env/catalog.rs: New catalog for all config modules

Fixes:
- Type mismatches: Added .unwrap_or(false) for Option<bool> returns (7 locations)
- Deprecation warnings: Fixed macro_toplevel_allow() & macro_box_child_runner() logic
- Module organization: Added module declarations in src/config/env.rs

Note: Files force-added with git add -f due to .gitignore env/ pattern

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-24 08:43:48 +09:00

212 lines
7.2 KiB
Rust

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<Mutex<Vec<&'static dyn MacroBox>>> = OnceLock::new();
fn registry() -> &'static Mutex<Vec<&'static dyn MacroBox>> {
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);
}
}
}
}
});
}