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>
212 lines
7.2 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|