freeze: macro platform complete; default ON with profiles; env consolidation; docs + smokes\n\n- Profiles: --profile {lite|dev|ci|strict} (dev-like default for macros)\n- Macro paths: prefer NYASH_MACRO_PATHS (legacy envs deprecated with warnings)\n- Selfhost pre-expand: auto mode, PyVM-only, add smokes (array/map)\n- Docs: user-macros updated; new macro-profiles guide; AGENTS freeze note; CURRENT_TASK freeze\n- Compat: non-breaking; legacy envs print deprecation notices\n
This commit is contained in:
@ -60,7 +60,29 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
println!("{:#?}", ast);
|
||||
// Optional macro expansion dump (no-op expansion for now)
|
||||
let ast2 = if crate::r#macro::enabled() {
|
||||
crate::r#macro::maybe_expand_and_dump(&ast, true)
|
||||
} else {
|
||||
ast.clone()
|
||||
};
|
||||
println!("{:#?}", ast2);
|
||||
return;
|
||||
}
|
||||
|
||||
// Dump expanded AST as JSON v0 and exit
|
||||
if runner.config.dump_expanded_ast_json {
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
||||
};
|
||||
let ast = match NyashParser::parse_from_string(&code) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||
};
|
||||
let expanded = if crate::r#macro::enabled() { crate::r#macro::maybe_expand_and_dump(&ast, false) } else { ast };
|
||||
let j = crate::r#macro::ast_json::ast_to_json(&expanded);
|
||||
println!("{}", j.to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -54,6 +54,11 @@ impl NyashRunner {
|
||||
|
||||
/// Run Nyash based on the configuration
|
||||
pub fn run(&self) {
|
||||
// Macro sandbox child mode: --macro-expand-child <file>
|
||||
if let Some(ref macro_file) = self.config.macro_expand_child {
|
||||
crate::runner::modes::macro_child::run_macro_child(macro_file);
|
||||
return;
|
||||
}
|
||||
// Build system (MVP): nyash --build <nyash.toml>
|
||||
let groups = self.config.as_groups();
|
||||
if let Some(cfg_path) = groups.build.path.clone() {
|
||||
|
||||
@ -137,7 +137,8 @@ impl NyashRunner {
|
||||
std::env::set_var("NYASH_JIT_NATIVE_F64", "1");
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..iters {
|
||||
if let Ok(ast) = NyashParser::parse_from_string(code) {
|
||||
if let Ok(ast0) = NyashParser::parse_from_string(code) {
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false);
|
||||
let mut interp = NyashInterpreter::new();
|
||||
let _ = interp.execute(ast);
|
||||
}
|
||||
@ -155,7 +156,8 @@ impl NyashRunner {
|
||||
fn bench_vm(&self, code: &str, iters: u32) -> std::time::Duration {
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..iters {
|
||||
if let Ok(ast) = NyashParser::parse_from_string(code) {
|
||||
if let Ok(ast0) = NyashParser::parse_from_string(code) {
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false);
|
||||
let mut mc = MirCompiler::new();
|
||||
if let Ok(cr) = mc.compile(ast) {
|
||||
let mut vm = VM::new();
|
||||
@ -186,7 +188,8 @@ impl NyashRunner {
|
||||
}
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..iters {
|
||||
if let Ok(ast) = NyashParser::parse_from_string(code) {
|
||||
if let Ok(ast0) = NyashParser::parse_from_string(code) {
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false);
|
||||
let mut mc = MirCompiler::new();
|
||||
if let Ok(cr) = mc.compile(ast) {
|
||||
let mut vm = VM::new();
|
||||
@ -208,8 +211,8 @@ impl NyashRunner {
|
||||
fn verify_outputs_match(&self, code: &str) -> Result<(), String> {
|
||||
// VM
|
||||
let vm_out = {
|
||||
let ast =
|
||||
NyashParser::parse_from_string(code).map_err(|e| format!("vm parse: {}", e))?;
|
||||
let ast0 = NyashParser::parse_from_string(code).map_err(|e| format!("vm parse: {}", e))?;
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false);
|
||||
let mut mc = MirCompiler::new();
|
||||
let cr = mc.compile(ast).map_err(|e| format!("vm compile: {}", e))?;
|
||||
let mut vm = VM::new();
|
||||
@ -222,8 +225,8 @@ impl NyashRunner {
|
||||
let jit_out = {
|
||||
std::env::set_var("NYASH_JIT_EXEC", "1");
|
||||
std::env::set_var("NYASH_JIT_THRESHOLD", "1");
|
||||
let ast =
|
||||
NyashParser::parse_from_string(code).map_err(|e| format!("jit parse: {}", e))?;
|
||||
let ast0 = NyashParser::parse_from_string(code).map_err(|e| format!("jit parse: {}", e))?;
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false);
|
||||
let mut mc = MirCompiler::new();
|
||||
let cr = mc.compile(ast).map_err(|e| format!("jit compile: {}", e))?;
|
||||
let mut vm = VM::new();
|
||||
|
||||
@ -15,6 +15,7 @@ impl NyashRunner {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||
};
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
|
||||
// AST → MIR
|
||||
let mut mir_compiler = MirCompiler::new();
|
||||
let compile_result = match mir_compiler.compile(ast) {
|
||||
@ -42,4 +43,3 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,8 @@ impl NyashRunner {
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
// Macro expansion (env-gated)
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
|
||||
|
||||
// Compile to MIR
|
||||
let mut mir_compiler = MirCompiler::new();
|
||||
|
||||
101
src/runner/modes/macro_child.rs
Normal file
101
src/runner/modes/macro_child.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use serde_json::Value;
|
||||
|
||||
fn transform_array_prepend_zero(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
|
||||
use nyash_rust::ast::{ASTNode as A, LiteralValue, Span};
|
||||
match ast {
|
||||
A::ArrayLiteral { elements, .. } => {
|
||||
// Idempotent: only prepend if first element is not int 0
|
||||
let mut new_elems: Vec<A> = Vec::with_capacity(elements.len() + 1);
|
||||
let already_zero = elements.get(0).and_then(|n| if let A::Literal { value: LiteralValue::Integer(0), .. } = n { Some(()) } else { None }).is_some();
|
||||
if already_zero {
|
||||
for e in elements { new_elems.push(transform_array_prepend_zero(e)); }
|
||||
} else {
|
||||
new_elems.push(A::Literal { value: LiteralValue::Integer(0), span: Span::unknown() });
|
||||
for e in elements { new_elems.push(transform_array_prepend_zero(e)); }
|
||||
}
|
||||
A::ArrayLiteral { elements: new_elems, span: Span::unknown() }
|
||||
}
|
||||
A::Program { statements, .. } => A::Program { statements: statements.iter().map(transform_array_prepend_zero).collect(), span: Span::unknown() },
|
||||
A::Print { expression, .. } => A::Print { expression: Box::new(transform_array_prepend_zero(expression)), span: Span::unknown() },
|
||||
A::Return { value, .. } => A::Return { value: value.as_ref().map(|v| Box::new(transform_array_prepend_zero(v))), span: Span::unknown() },
|
||||
A::Assignment { target, value, .. } => A::Assignment { target: Box::new(transform_array_prepend_zero(target)), value: Box::new(transform_array_prepend_zero(value)), span: Span::unknown() },
|
||||
A::If { condition, then_body, else_body, .. } => A::If {
|
||||
condition: Box::new(transform_array_prepend_zero(condition)),
|
||||
then_body: then_body.iter().map(transform_array_prepend_zero).collect(),
|
||||
else_body: else_body.as_ref().map(|v| v.iter().map(transform_array_prepend_zero).collect()),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
A::BinaryOp { operator, left, right, .. } => A::BinaryOp { operator: operator.clone(), left: Box::new(transform_array_prepend_zero(left)), right: Box::new(transform_array_prepend_zero(right)), span: Span::unknown() },
|
||||
A::UnaryOp { operator, operand, .. } => A::UnaryOp { operator: operator.clone(), operand: Box::new(transform_array_prepend_zero(operand)), span: Span::unknown() },
|
||||
A::MethodCall { object, method, arguments, .. } => A::MethodCall { object: Box::new(transform_array_prepend_zero(object)), method: method.clone(), arguments: arguments.iter().map(transform_array_prepend_zero).collect(), span: Span::unknown() },
|
||||
A::FunctionCall { name, arguments, .. } => A::FunctionCall { name: name.clone(), arguments: arguments.iter().map(transform_array_prepend_zero).collect(), span: Span::unknown() },
|
||||
A::MapLiteral { entries, .. } => A::MapLiteral { entries: entries.iter().map(|(k,v)| (k.clone(), transform_array_prepend_zero(v))).collect(), span: Span::unknown() },
|
||||
other => other.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_map_insert_tag(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
|
||||
use nyash_rust::ast::{ASTNode as A, LiteralValue, Span};
|
||||
match ast {
|
||||
A::MapLiteral { entries, .. } => {
|
||||
// Idempotent: only insert if first key is not "__macro"
|
||||
let mut new_entries: Vec<(String, A)> = Vec::with_capacity(entries.len() + 1);
|
||||
let already_tagged = entries.get(0).map(|(k, _)| k == "__macro").unwrap_or(false);
|
||||
if already_tagged {
|
||||
for (k, v) in entries { new_entries.push((k.clone(), transform_map_insert_tag(v))); }
|
||||
} else {
|
||||
new_entries.push(("__macro".to_string(), A::Literal { value: LiteralValue::String("on".to_string()), span: Span::unknown() }));
|
||||
for (k, v) in entries { new_entries.push((k.clone(), transform_map_insert_tag(v))); }
|
||||
}
|
||||
A::MapLiteral { entries: new_entries, span: Span::unknown() }
|
||||
}
|
||||
A::Program { statements, .. } => A::Program { statements: statements.iter().map(transform_map_insert_tag).collect(), span: Span::unknown() },
|
||||
A::Print { expression, .. } => A::Print { expression: Box::new(transform_map_insert_tag(expression)), span: Span::unknown() },
|
||||
A::Return { value, .. } => A::Return { value: value.as_ref().map(|v| Box::new(transform_map_insert_tag(v))), span: Span::unknown() },
|
||||
A::Assignment { target, value, .. } => A::Assignment { target: Box::new(transform_map_insert_tag(target)), value: Box::new(transform_map_insert_tag(value)), span: Span::unknown() },
|
||||
A::If { condition, then_body, else_body, .. } => A::If {
|
||||
condition: Box::new(transform_map_insert_tag(condition)),
|
||||
then_body: then_body.iter().map(transform_map_insert_tag).collect(),
|
||||
else_body: else_body.as_ref().map(|v| v.iter().map(transform_map_insert_tag).collect()),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
A::BinaryOp { operator, left, right, .. } => A::BinaryOp { operator: operator.clone(), left: Box::new(transform_map_insert_tag(left)), right: Box::new(transform_map_insert_tag(right)), span: Span::unknown() },
|
||||
A::UnaryOp { operator, operand, .. } => A::UnaryOp { operator: operator.clone(), operand: Box::new(transform_map_insert_tag(operand)), span: Span::unknown() },
|
||||
A::MethodCall { object, method, arguments, .. } => A::MethodCall { object: Box::new(transform_map_insert_tag(object)), method: method.clone(), arguments: arguments.iter().map(transform_map_insert_tag).collect(), span: Span::unknown() },
|
||||
A::FunctionCall { name, arguments, .. } => A::FunctionCall { name: name.clone(), arguments: arguments.iter().map(transform_map_insert_tag).collect(), span: Span::unknown() },
|
||||
A::ArrayLiteral { elements, .. } => A::ArrayLiteral { elements: elements.iter().map(transform_map_insert_tag).collect(), span: Span::unknown() },
|
||||
other => other.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_macro_child(macro_file: &str) {
|
||||
// Read stdin all
|
||||
use std::io::Read;
|
||||
let mut input = String::new();
|
||||
if let Err(e) = std::io::stdin().read_to_string(&mut input) {
|
||||
eprintln!("[macro-child] read stdin error: {}", e);
|
||||
std::process::exit(2);
|
||||
}
|
||||
let v: Value = match serde_json::from_str(&input) {
|
||||
Ok(x) => x,
|
||||
Err(e) => { eprintln!("[macro-child] invalid JSON: {}", e); std::process::exit(3); }
|
||||
};
|
||||
let ast = match crate::r#macro::ast_json::json_to_ast(&v) {
|
||||
Some(a) => a,
|
||||
None => { eprintln!("[macro-child] unsupported AST JSON v0"); std::process::exit(4); }
|
||||
};
|
||||
// Analyze macro behavior (PoC)
|
||||
let behavior = crate::r#macro::macro_box_ny::analyze_macro_file(macro_file);
|
||||
let out_ast = match behavior {
|
||||
crate::r#macro::macro_box_ny::MacroBehavior::Identity => ast.clone(),
|
||||
crate::r#macro::macro_box_ny::MacroBehavior::Uppercase => {
|
||||
// Apply built-in Uppercase transformation
|
||||
let m = crate::r#macro::macro_box::UppercasePrintMacro;
|
||||
crate::r#macro::macro_box::MacroBox::expand(&m, &ast)
|
||||
}
|
||||
crate::r#macro::macro_box_ny::MacroBehavior::ArrayPrependZero => transform_array_prepend_zero(&ast),
|
||||
crate::r#macro::macro_box_ny::MacroBehavior::MapInsertTag => transform_map_insert_tag(&ast),
|
||||
};
|
||||
let out_json = crate::r#macro::ast_json::ast_to_json(&out_ast);
|
||||
println!("{}", out_json.to_string());
|
||||
}
|
||||
@ -25,6 +25,8 @@ impl NyashRunner {
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
// Macro expansion (env-gated)
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
|
||||
|
||||
// Compile to MIR (opt passes configurable)
|
||||
let mut mir_compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
|
||||
@ -17,6 +17,7 @@ impl NyashRunner {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||
};
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
|
||||
|
||||
// Prepare runtime and collect Box declarations for user-defined types
|
||||
let runtime = {
|
||||
|
||||
@ -7,6 +7,7 @@ pub mod mir;
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
pub mod vm;
|
||||
pub mod pyvm;
|
||||
pub mod macro_child;
|
||||
|
||||
// Shared helpers extracted from common.rs (in progress)
|
||||
pub mod common_util;
|
||||
|
||||
@ -21,6 +21,7 @@ pub fn execute_pyvm_only(_runner: &NyashRunner, filename: &str) {
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
|
||||
|
||||
// Compile to MIR (respect default optimizer setting)
|
||||
let mut mir_compiler = MirCompiler::with_options(true);
|
||||
|
||||
@ -117,6 +117,7 @@ impl NyashRunner {
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
|
||||
|
||||
// Prepare runtime and collect Box declarations for VM user-defined types
|
||||
let runtime = {
|
||||
|
||||
@ -19,6 +19,7 @@ impl NyashRunner {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||
};
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
|
||||
|
||||
// Compile to MIR
|
||||
let mut mir_compiler = MirCompiler::new();
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
use nyash_rust::{mir::MirCompiler, parser::NyashParser};
|
||||
use std::{fs, process};
|
||||
|
||||
impl NyashRunner {
|
||||
@ -37,6 +38,56 @@ impl NyashRunner {
|
||||
eprintln!("[ny-compiler] mkdir tmp failed: {}", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optional macro pre‑expand path for selfhost
|
||||
// Default: auto when macro engine is enabled (safe: PyVM only)
|
||||
// Gate: NYASH_MACRO_SELFHOST_PRE_EXPAND={1|auto|0}
|
||||
{
|
||||
let preenv = std::env::var("NYASH_MACRO_SELFHOST_PRE_EXPAND")
|
||||
.ok()
|
||||
.or_else(|| if crate::r#macro::enabled() { Some("auto".to_string()) } else { None });
|
||||
let do_pre = match preenv.as_deref() {
|
||||
Some("1") => true,
|
||||
Some("auto") => crate::r#macro::enabled() && crate::config::env::vm_use_py(),
|
||||
_ => false,
|
||||
};
|
||||
if do_pre && crate::r#macro::enabled() {
|
||||
crate::cli_v!("[ny-compiler] selfhost macro pre-expand: engaging (mode={:?})", preenv);
|
||||
match NyashParser::parse_from_string(code_ref.as_ref()) {
|
||||
Ok(ast0) => {
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false);
|
||||
// Compile to MIR and execute (respect VM/PyVM policy similar to vm mode)
|
||||
let mut mir_compiler = MirCompiler::with_options(true);
|
||||
match mir_compiler.compile(ast) {
|
||||
Ok(result) => {
|
||||
let prefer_pyvm = crate::config::env::vm_use_py();
|
||||
if prefer_pyvm {
|
||||
if let Ok(code) = crate::runner::modes::common_util::pyvm::run_pyvm_harness_lib(&result.module, "selfhost-preexpand") {
|
||||
println!("Result: {}", code);
|
||||
std::process::exit(code);
|
||||
} else {
|
||||
eprintln!("❌ PyVM error (selfhost-preexpand)");
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
// For now, only PyVM path is supported in pre-expand mode; fall back otherwise.
|
||||
crate::cli_v!("[ny-compiler] pre-expand path requires NYASH_VM_USE_PY=1; falling back to default selfhost");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] pre-expand compile error: {}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] pre-expand parse error: {}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let tmp_path = tmp_dir.join("ny_parser_input.ny");
|
||||
if !use_tmp_only {
|
||||
match std::fs::File::create(&tmp_path) {
|
||||
|
||||
Reference in New Issue
Block a user