selfhost(pyvm): MiniVmPrints – prefer JSON route early-return (ok==1) to avoid fallback loops; keep default behavior unchanged elsewhere

This commit is contained in:
Selfhosting Dev
2025-09-22 07:54:25 +09:00
parent 27568eb4a6
commit 8e4cadd349
348 changed files with 9981 additions and 30074 deletions

152
src/runner/jit_direct.rs Normal file
View File

@ -0,0 +1,152 @@
#![cfg(feature = "jit-direct-only")]
use super::*;
impl NyashRunner {
/// Run a file through independent JIT engine (no VM execute loop)
pub(crate) fn run_file_jit_direct(&self, filename: &str) {
use nyash_rust::{mir::MirCompiler, parser::NyashParser};
use std::fs;
let emit_err = |phase: &str, code: &str, msg: &str| {
if std::env::var("NYASH_JIT_STATS_JSON").ok().as_deref() == Some("1")
|| std::env::var("NYASH_JIT_ERROR_JSON").ok().as_deref() == Some("1")
{
let payload = serde_json::json!({
"kind": "jit_direct_error",
"phase": phase,
"code": code,
"message": msg,
"file": filename,
});
println!("{}", payload.to_string());
} else {
eprintln!("[JIT-direct][{}][{}] {}", phase, code, msg);
}
};
let code = match fs::read_to_string(filename) {
Ok(s) => s,
Err(e) => { emit_err("read_file", "IO", &format!("{}", e)); std::process::exit(1); }
};
let ast = match NyashParser::parse_from_string(&code) {
Ok(a) => a,
Err(e) => { emit_err("parse", "SYNTAX", &format!("{}", e)); std::process::exit(1); }
};
let mut mc = MirCompiler::new();
let cr = match mc.compile(ast) {
Ok(m) => m,
Err(e) => { emit_err("mir", "MIR_COMPILE", &format!("{}", e)); std::process::exit(1); }
};
let func = match cr.module.functions.get("main") {
Some(f) => f,
None => { emit_err("mir", "NO_MAIN", "No main function found"); std::process::exit(1); }
};
// Refuse write-effects in jit-direct when policy.read_only
{
use nyash_rust::mir::effect::Effect;
let policy = nyash_rust::jit::policy::current();
let mut writes = 0usize;
for (_bbid, bb) in func.blocks.iter() {
for inst in bb.instructions.iter() {
let mask = inst.effects();
if mask.contains(Effect::WriteHeap) { writes += 1; }
}
if let Some(term) = &bb.terminator {
if term.effects().contains(Effect::WriteHeap) { writes += 1; }
}
}
if policy.read_only && writes > 0 {
emit_err("policy","WRITE_EFFECTS", &format!("write-effects detected ({} ops). jit-direct is read-only at this stage.", writes));
std::process::exit(1);
}
}
// PHI-min config for jit-direct
{
let mut cfg = nyash_rust::jit::config::current();
cfg.phi_min = true;
nyash_rust::jit::config::set_current(cfg);
}
// minimal runtime hooks
{
let rt = nyash_rust::runtime::NyashRuntime::new();
nyash_rust::runtime::global_hooks::set_from_runtime(&rt);
}
let mut engine = nyash_rust::jit::engine::JitEngine::new();
match engine.compile_function("main", func) {
Some(h) => {
nyash_rust::jit::events::emit("compile", &func.signature.name, Some(h), None, serde_json::json!({}));
// parse NYASH_JIT_ARGS
let mut jit_args: Vec<nyash_rust::jit::abi::JitValue> = Vec::new();
if let Ok(s) = std::env::var("NYASH_JIT_ARGS") { for raw in s.split(',') { let t = raw.trim(); if t.is_empty() { continue; } let v = if let Some(rest) = t.strip_prefix("i:") { rest.parse::<i64>().ok().map(nyash_rust::jit::abi::JitValue::I64) } else if let Some(rest) = t.strip_prefix("f:") { rest.parse::<f64>().ok().map(nyash_rust::jit::abi::JitValue::F64) } else if let Some(rest) = t.strip_prefix("b:") { let b = matches!(rest, "1"|"true"|"True"|"TRUE"); Some(nyash_rust::jit::abi::JitValue::Bool(b)) } else if let Some(rest) = t.strip_prefix("h:") { rest.parse::<u64>().ok().map(nyash_rust::jit::abi::JitValue::Handle) } else if t.eq_ignore_ascii_case("true") || t == "1" { Some(nyash_rust::jit::abi::JitValue::Bool(true)) } else if t.eq_ignore_ascii_case("false") || t == "0" { Some(nyash_rust::jit::abi::JitValue::Bool(false)) } else if let Ok(iv) = t.parse::<i64>() { Some(nyash_rust::jit::abi::JitValue::I64(iv)) } else if let Ok(fv) = t.parse::<f64>() { Some(nyash_rust::jit::abi::JitValue::F64(fv)) } else { None }; if let Some(jv) = v { jit_args.push(jv); } } }
// coerce to MIR signature
use nyash_rust::mir::MirType;
let expected = &func.signature.params;
if expected.len() != jit_args.len() { emit_err("args","COUNT_MISMATCH", &format!("expected={}, passed={}", expected.len(), jit_args.len())); eprintln!("Hint: set NYASH_JIT_ARGS as comma-separated values, e.g., i:42,f:3.14,b:true"); std::process::exit(1); }
let mut coerced: Vec<nyash_rust::jit::abi::JitValue> = Vec::with_capacity(jit_args.len());
for (exp, got) in expected.iter().zip(jit_args.iter()) {
let cv = match exp {
MirType::Integer => match got { nyash_rust::jit::abi::JitValue::I64(v)=>nyash_rust::jit::abi::JitValue::I64(*v), nyash_rust::jit::abi::JitValue::F64(f)=>nyash_rust::jit::abi::JitValue::I64(*f as i64), nyash_rust::jit::abi::JitValue::Bool(b)=>nyash_rust::jit::abi::JitValue::I64(if *b {1}else{0}), _=>nyash_rust::jit::abi::adapter::from_jit_value(got) },
MirType::Float => match got { nyash_rust::jit::abi::JitValue::F64(v)=>nyash_rust::jit::abi::JitValue::F64(*v), nyash_rust::jit::abi::JitValue::I64(i)=>nyash_rust::jit::abi::JitValue::F64(*i as f64), _=>nyash_rust::jit::abi::adapter::from_jit_value(got) },
MirType::Bool => match got { nyash_rust::jit::abi::JitValue::Bool(b)=>nyash_rust::jit::abi::JitValue::Bool(*b), nyash_rust::jit::abi::JitValue::I64(i)=>nyash_rust::jit::abi::JitValue::Bool(*i!=0), _=>nyash_rust::jit::abi::adapter::from_jit_value(got) },
_ => nyash_rust::jit::abi::adapter::from_jit_value(got),
};
coerced.push(cv);
}
match engine.execute_function(h, &coerced) {
Some(v) => {
let ret_ty = &func.signature.return_type;
let vmv = match (ret_ty, v) {
(MirType::Bool, nyash_rust::jit::abi::JitValue::Bool(b)) => nyash_rust::backend::vm::VMValue::Bool(b),
(MirType::Float, nyash_rust::jit::abi::JitValue::F64(f)) => nyash_rust::backend::vm::VMValue::Float(f),
(MirType::Integer, nyash_rust::jit::abi::JitValue::I64(i)) => nyash_rust::backend::vm::VMValue::Integer(i),
(_, v) => nyash_rust::jit::abi::adapter::from_jit_value(&v),
};
println!("✅ JIT-direct execution completed successfully!");
let (ety, sval) = match (ret_ty, &vmv) {
(MirType::Bool, nyash_rust::backend::vm::VMValue::Bool(b)) => ("Bool", b.to_string()),
(MirType::Float, nyash_rust::backend::vm::VMValue::Float(f)) => ("Float", format!("{}", f)),
(MirType::Integer, nyash_rust::backend::vm::VMValue::Integer(i)) => ("Integer", i.to_string()),
(_, nyash_rust::backend::vm::VMValue::Integer(i)) => ("Integer", i.to_string()),
(_, nyash_rust::backend::vm::VMValue::Float(f)) => ("Float", format!("{}", f)),
(_, nyash_rust::backend::vm::VMValue::Bool(b)) => ("Bool", b.to_string()),
(_, nyash_rust::backend::vm::VMValue::String(s)) => ("String", s.clone()),
(_, nyash_rust::backend::vm::VMValue::BoxRef(arc)) => ("BoxRef", arc.type_name().to_string()),
(_, nyash_rust::backend::vm::VMValue::Future(_)) => ("Future", "<future>".to_string()),
(_, nyash_rust::backend::vm::VMValue::Void) => ("Void", "void".to_string()),
};
println!("ResultType(MIR): {}", ety);
println!("Result: {}", sval);
if std::env::var("NYASH_JIT_STATS_JSON").ok().as_deref() == Some("1") {
let cfg = nyash_rust::jit::config::current();
let caps = nyash_rust::jit::config::probe_capabilities();
let (phi_t, phi_b1, ret_b) = engine.last_lower_stats();
let abi_mode = if cfg.native_bool_abi && caps.supports_b1_sig { "b1_bool" } else { "i64_bool" };
let payload = serde_json::json!({
"version": 1,
"function": func.signature.name,
"abi_mode": abi_mode,
"abi_b1_enabled": cfg.native_bool_abi,
"abi_b1_supported": caps.supports_b1_sig,
"b1_norm_count": nyash_rust::jit::rt::b1_norm_get(),
"ret_bool_hint_count": nyash_rust::jit::rt::ret_bool_hint_get(),
"phi_total_slots": phi_t,
"phi_b1_slots": phi_b1,
"ret_bool_hint_used": ret_b,
});
println!("{}", payload.to_string());
}
}
None => {
nyash_rust::jit::events::emit("fallback", &func.signature.name, Some(h), None, serde_json::json!({"reason":"trap_or_missing"}));
emit_err("execute", "TRAP_OR_MISSING", "execution failed (trap or missing handle)");
std::process::exit(1);
}
}
}
None => {
emit_err("compile", "UNAVAILABLE", "Build with --features cranelift-jit");
std::process::exit(1);
}
}
}
}

View File

@ -22,9 +22,10 @@ mod demos;
mod dispatch;
mod json_v0_bridge;
mod mir_json_emit;
mod modes;
pub mod modes;
mod pipe_io;
mod pipeline;
mod jit_direct;
mod selfhost;
mod tasks;
mod trace;

View File

@ -307,7 +307,7 @@ impl NyashRunner {
};
// Select selfhost compiler entry
// NYASH_NY_COMPILER_PREF=legacy|new|auto (default auto: prefer new when exists)
let cand_new = std::path::Path::new("apps/selfhost-compiler/compiler.nyash");
let cand_new = std::path::Path::new("apps/selfhost/compiler/compiler.nyash");
let cand_old = std::path::Path::new("apps/selfhost/parser/ny_parser_v0/main.nyash");
let pref = std::env::var("NYASH_NY_COMPILER_PREF").ok();
let parser_prog = match pref.as_deref() {
@ -506,6 +506,13 @@ impl NyashRunner {
Err(e) => { eprintln!("{}", e); std::process::exit(1); }
}
}
// Optional dev sugar: @name[:T] = expr → local name[:T] = expr (line-head only)
let preexpanded_owned;
{
let s = crate::runner::modes::common_util::resolve::preexpand_at_local(code_ref);
preexpanded_owned = s;
code_ref = &preexpanded_owned;
}
// Parse the code with debug fuel limit
let groups = self.config.as_groups();

View File

@ -2,7 +2,7 @@ use std::path::Path;
/// Run a Nyash program as a child (`nyash --backend vm <program>`) and capture the first JSON v0 line.
/// - `exe`: path to nyash executable
/// - `program`: path to the Nyash script to run (e.g., apps/selfhost-compiler/compiler.nyash)
/// - `program`: path to the Nyash script to run (e.g., apps/selfhost/compiler/compiler.nyash)
/// - `timeout_ms`: kill child after this duration
/// - `extra_args`: additional args to pass after program (e.g., "--", "--read-tmp")
/// - `env_remove`: environment variable names to remove for the child
@ -49,4 +49,3 @@ pub fn run_ny_program_capture_json(
};
crate::runner::modes::common_util::selfhost::json::first_json_v0_line(stdout)
}

View File

@ -1,927 +0,0 @@
use serde_json::Value;
fn map_expr_to_stmt(e: nyash_rust::ASTNode) -> nyash_rust::ASTNode { e }
fn transform_peek_to_if_expr(peek: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
// only support literal-only arms conservatively
let mut conds_bodies: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms {
conds_bodies.push((lit.clone(), (*body).clone()));
}
let mut current: A = *(*else_expr).clone();
for (lit, body) in conds_bodies.into_iter().rev() {
let rhs = A::Literal { value: lit, span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() };
let then_body = vec![map_expr_to_stmt(body)];
let else_body = Some(vec![map_expr_to_stmt(current)]);
current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() };
}
Some(current)
} else { None }
}
fn transform_peek_to_if_stmt_assign(peek: &nyash_rust::ASTNode, target: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); }
let mut current: A = *(*else_expr).clone();
for (lit, body) in pairs.into_iter().rev() {
let rhs = A::Literal { value: lit, span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() };
let then_body = vec![A::Assignment { target: Box::new(target.clone()), value: Box::new(body), span: Span::unknown() }];
let else_body = Some(vec![map_expr_to_stmt(current)]);
current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() };
}
Some(current)
} else { None }
}
fn transform_peek_to_if_stmt_return(peek: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); }
let mut current: A = *(*else_expr).clone();
for (lit, body) in pairs.into_iter().rev() {
let rhs = A::Literal { value: lit, span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() };
let then_body = vec![A::Return { value: Some(Box::new(body)), span: Span::unknown() }];
let else_body = Some(vec![map_expr_to_stmt(current)]);
current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() };
}
Some(current)
} else { None }
}
fn transform_peek_to_if_stmt_print(peek: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); }
let mut current: A = *(*else_expr).clone();
for (lit, body) in pairs.into_iter().rev() {
let rhs = A::Literal { value: lit, span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() };
let then_body = vec![A::Print { expression: Box::new(body), span: Span::unknown() }];
let else_body = Some(vec![map_expr_to_stmt(current)]);
current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() };
}
Some(current)
} else { None }
}
fn transform_peek_match_literal(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
match ast.clone() {
A::Program { statements, span } => {
A::Program { statements: statements.into_iter().map(|n| transform_peek_match_literal(&n)).collect(), span }
}
A::If { condition, then_body, else_body, span } => {
A::If {
condition: Box::new(transform_peek_match_literal(&condition)),
then_body: then_body.into_iter().map(|n| transform_peek_match_literal(&n)).collect(),
else_body: else_body.map(|v| v.into_iter().map(|n| transform_peek_match_literal(&n)).collect()),
span,
}
}
A::Loop { condition, body, span } => {
A::Loop {
condition: Box::new(transform_peek_match_literal(&condition)),
body: body.into_iter().map(|n| transform_peek_match_literal(&n)).collect(),
span,
}
}
A::Local { variables, initial_values, span } => {
let mut new_inits: Vec<Option<Box<A>>> = Vec::with_capacity(initial_values.len());
for opt in initial_values {
if let Some(v) = opt {
if let Some(ifexpr) = transform_peek_to_if_expr(&v) {
new_inits.push(Some(Box::new(ifexpr)));
} else {
new_inits.push(Some(Box::new(transform_peek_match_literal(&v))));
}
} else {
new_inits.push(None);
}
}
A::Local { variables, initial_values: new_inits, span }
}
A::Assignment { target, value, span } => {
if let Some(ifstmt) = transform_peek_to_if_stmt_assign(&value, &target) {
ifstmt
} else {
A::Assignment { target, value: Box::new(transform_peek_match_literal(&value)), span }
}
}
A::Return { value, span } => {
if let Some(v) = &value {
if let Some(ifstmt) = transform_peek_to_if_stmt_return(v) {
ifstmt
} else {
A::Return { value: Some(Box::new(transform_peek_match_literal(v))), span }
}
} else {
A::Return { value: None, span }
}
}
A::Print { expression, span } => {
if let Some(ifstmt) = transform_peek_to_if_stmt_print(&expression) {
ifstmt
} else {
A::Print { expression: Box::new(transform_peek_match_literal(&expression)), span }
}
}
other => other,
}
}
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(),
}
}
fn transform_loop_normalize(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
match ast.clone() {
// Recurse into container nodes first
A::Program { statements, span } => {
A::Program { statements: statements.into_iter().map(|n| transform_loop_normalize(&n)).collect(), span }
}
A::If { condition, then_body, else_body, span } => {
A::If {
condition: Box::new(transform_loop_normalize(&condition)),
then_body: then_body.into_iter().map(|n| transform_loop_normalize(&n)).collect(),
else_body: else_body.map(|v| v.into_iter().map(|n| transform_loop_normalize(&n)).collect()),
span,
}
}
A::Loop { condition, body, span } => {
// First, normalize inside children
let condition = Box::new(transform_loop_normalize(&condition));
let body_norm: Vec<A> = body.into_iter().map(|n| transform_loop_normalize(&n)).collect();
// MVP-3: break/continue 最小対応
// 方針: 本体を control(Break/Continue) でセグメントに分割し、
// 各セグメント内のみ安全に「非代入→代入」に整列する(順序維持の安定版)。
// 追加ガード: 代入先は変数に限る。変数の種類は全体で最大2種までMVP-2 制約維持)。
// まず全体の更新変数の種類を走査(観測のみ)。
// 制限は設けず、後続のセグメント整列(非代入→代入)に委ねる。
// 複合ターゲットが出現した場合は保守的に“整列スキップ”とするため、ここでは弾かない。
// セグメント分解 → セグメント毎に安全整列
let mut rebuilt: Vec<A> = Vec::with_capacity(body_norm.len());
let mut seg: Vec<A> = Vec::new();
let flush_seg = |seg: &mut Vec<A>, out: &mut Vec<A>| {
// セグメント内で「代入の後に非代入」が存在したら整列しない
let mut saw_assign = false;
let mut safe = true;
for n in seg.iter() {
match n {
A::Assignment { .. } => { saw_assign = true; }
_ => {
if saw_assign { safe = false; break; }
}
}
}
if safe {
// others → assigns の順で安定整列
let mut others: Vec<A> = Vec::new();
let mut assigns: Vec<A> = Vec::new();
for n in seg.drain(..) {
match n {
A::Assignment { .. } => assigns.push(n),
_ => others.push(n),
}
}
out.extend(others.into_iter());
out.extend(assigns.into_iter());
} else {
// そのまま吐き出す
out.extend(seg.drain(..));
}
};
for stmt in body_norm.into_iter() {
match stmt.clone() {
A::Break { .. } | A::Continue { .. } => {
// control の直前までをフラッシュしてから control を出力
flush_seg(&mut seg, &mut rebuilt);
rebuilt.push(stmt);
}
other => seg.push(other),
}
}
// 末尾セグメントをフラッシュ
flush_seg(&mut seg, &mut rebuilt);
A::Loop { condition, body: rebuilt, span }
}
// Leaf and other nodes: unchanged
A::Local { variables, initial_values, span } => A::Local { variables, initial_values, span },
A::Assignment { target, value, span } => A::Assignment { target, value, span },
A::Return { value, span } => A::Return { value, span },
A::Print { expression, span } => A::Print { expression, span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left, right, span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand, span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object, method, arguments, span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments, span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements, span },
A::MapLiteral { entries, span } => A::MapLiteral { entries, span },
other => other,
}
}
// Core normalization pass used by runners (always-on when macros enabled).
// Order matters: for/foreach → match(PeekExpr) → loop tail alignment.
pub fn normalize_core_pass(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
let a1 = transform_for_foreach(ast);
let a2 = transform_peek_match_literal(&a1);
let a3 = transform_loop_normalize(&a2);
// Optional: inject ScopeBox wrappers for diagnostics/visibility (no-op for MIR)
let a4 = if std::env::var("NYASH_SCOPEBOX_ENABLE").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false) {
transform_scopebox_inject(&a3)
} else { a3 };
// Lift nested function declarations (no captures) to top-level with gensym names
let a4b = transform_lift_nested_functions(&a4);
// Optional: If → LoopForm (conservative). Only transform if no else and branch has no break/continue.
let a5 = if std::env::var("NYASH_IF_AS_LOOPFORM").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false) {
transform_if_to_loopform(&a4b)
} else { a4b };
// Optional: postfix catch/cleanup sugar → TryCatch normalization
let a6 = if std::env::var("NYASH_CATCH_NEW").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false) {
transform_postfix_handlers(&a5)
} else { a5 };
a6
}
// ---- Nested Function Lift (no captures) ----
fn transform_lift_nested_functions(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
fn gensym(base: &str) -> String {
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
format!("__ny_lifted_{}_{}", base, n)
}
fn collect_locals(n: &A, set: &mut std::collections::HashSet<String>) {
match n {
A::Local { variables, .. } => { for v in variables { set.insert(v.clone()); } }
A::Program { statements, .. } => for s in statements { collect_locals(s, set); },
A::FunctionDeclaration { body, .. } => for s in body { collect_locals(s, set); },
A::If { then_body, else_body, .. } => {
for s in then_body { collect_locals(s, set); }
if let Some(b) = else_body { for s in b { collect_locals(s, set); } }
}
_ => {}
}
}
fn collect_vars(n: &A, set: &mut std::collections::HashSet<String>) {
match n {
A::Variable { name, .. } => { set.insert(name.clone()); }
A::Program { statements, .. } => for s in statements { collect_vars(s, set); },
A::FunctionDeclaration { body, .. } => for s in body { collect_vars(s, set); },
A::If { condition, then_body, else_body, .. } => {
collect_vars(condition, set);
for s in then_body { collect_vars(s, set); }
if let Some(b) = else_body { for s in b { collect_vars(s, set); } }
}
A::Assignment { target, value, .. } => { collect_vars(target, set); collect_vars(value, set); }
A::Return { value, .. } => { if let Some(v) = value { collect_vars(v, set); } }
A::Print { expression, .. } => collect_vars(expression, set),
A::BinaryOp { left, right, .. } => { collect_vars(left, set); collect_vars(right, set); }
A::UnaryOp { operand, .. } => collect_vars(operand, set),
A::MethodCall { object, arguments, .. } => { collect_vars(object, set); for a in arguments { collect_vars(a, set); } }
A::FunctionCall { arguments, .. } => { for a in arguments { collect_vars(a, set); } }
A::ArrayLiteral { elements, .. } => { for e in elements { collect_vars(e, set); } }
A::MapLiteral { entries, .. } => { for (_,v) in entries { collect_vars(v, set); } }
_ => {}
}
}
fn rename_calls(n: &A, mapping: &std::collections::HashMap<String, String>) -> A {
use nyash_rust::ast::ASTNode as A;
match n.clone() {
A::FunctionCall { name, arguments, span } => {
let new_name = mapping.get(&name).cloned().unwrap_or(name);
A::FunctionCall { name: new_name, arguments: arguments.into_iter().map(|a| rename_calls(&a, mapping)).collect(), span }
}
A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|s| rename_calls(&s, mapping)).collect(), span },
A::FunctionDeclaration { name, params, body, is_static, is_override, span } => {
A::FunctionDeclaration { name, params, body: body.into_iter().map(|s| rename_calls(&s, mapping)).collect(), is_static, is_override, span }
}
A::If { condition, then_body, else_body, span } => A::If {
condition: Box::new(rename_calls(&condition, mapping)),
then_body: then_body.into_iter().map(|s| rename_calls(&s, mapping)).collect(),
else_body: else_body.map(|v| v.into_iter().map(|s| rename_calls(&s, mapping)).collect()),
span,
},
A::Assignment { target, value, span } => A::Assignment { target: Box::new(rename_calls(&target, mapping)), value: Box::new(rename_calls(&value, mapping)), span },
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(rename_calls(v, mapping))), span },
A::Print { expression, span } => A::Print { expression: Box::new(rename_calls(&expression, mapping)), span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(rename_calls(&left, mapping)), right: Box::new(rename_calls(&right, mapping)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(rename_calls(&operand, mapping)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(rename_calls(&object, mapping)), method, arguments: arguments.into_iter().map(|a| rename_calls(&a, mapping)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| rename_calls(&e, mapping)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, rename_calls(&v, mapping))).collect(), span },
other => other,
}
}
fn lift_in_body(body: Vec<A>, hoisted: &mut Vec<A>, mapping: &mut std::collections::HashMap<String,String>) -> Vec<A> {
use std::collections::HashSet;
let mut out: Vec<A> = Vec::new();
for st in body.into_iter() {
match st.clone() {
A::FunctionDeclaration { name, params, body, is_static, is_override, span } => {
// check captures
let mut locals: HashSet<String> = HashSet::new();
collect_locals(&A::FunctionDeclaration{ name: name.clone(), params: params.clone(), body: body.clone(), is_static, is_override, span }, &mut locals);
let mut used: HashSet<String> = HashSet::new();
collect_vars(&A::FunctionDeclaration{ name: name.clone(), params: params.clone(), body: body.clone(), is_static, is_override, span }, &mut used);
let params_set: HashSet<String> = params.iter().cloned().collect();
let mut extra: HashSet<String> = used.drain().collect();
extra.retain(|v| !params_set.contains(v) && !locals.contains(v));
if extra.is_empty() {
// Hoist with gensym name
let new_name = gensym(&name);
let lifted = A::FunctionDeclaration { name: new_name.clone(), params, body, is_static: true, is_override, span };
hoisted.push(lifted);
mapping.insert(name, new_name);
// do not keep nested declaration in place
continue;
} else {
// keep as-is (cannot hoist due to captures)
out.push(st);
}
}
other => out.push(other),
}
}
// After scanning, rename calls in out according to mapping
out.into_iter().map(|n| rename_calls(&n, mapping)).collect()
}
fn walk(n: &A, hoisted: &mut Vec<A>) -> A {
use nyash_rust::ast::ASTNode as A;
match n.clone() {
A::Program { statements, span } => {
let mut mapping = std::collections::HashMap::new();
let stmts2 = lift_in_body(statements.into_iter().map(|s| walk(&s, hoisted)).collect(), hoisted, &mut mapping);
// Append hoisted at end (global scope)
// Note: hoisted collected at all levels; only append here once after full walk
A::Program { statements: stmts2, span }
}
A::FunctionDeclaration { name, params, body, is_static, is_override, span } => {
let mut mapping = std::collections::HashMap::new();
let body2: Vec<A> = body.into_iter().map(|s| walk(&s, hoisted)).collect();
let body3 = lift_in_body(body2, hoisted, &mut mapping);
A::FunctionDeclaration { name, params, body: body3, is_static, is_override, span }
}
A::If { condition, then_body, else_body, span } => A::If {
condition: Box::new(walk(&condition, hoisted)),
then_body: then_body.into_iter().map(|s| walk(&s, hoisted)).collect(),
else_body: else_body.map(|v| v.into_iter().map(|s| walk(&s, hoisted)).collect()),
span,
},
A::Assignment { target, value, span } => A::Assignment { target: Box::new(walk(&target, hoisted)), value: Box::new(walk(&value, hoisted)), span },
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(walk(v, hoisted))), span },
A::Print { expression, span } => A::Print { expression: Box::new(walk(&expression, hoisted)), span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(walk(&left, hoisted)), right: Box::new(walk(&right, hoisted)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(walk(&operand, hoisted)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(walk(&object, hoisted)), method, arguments: arguments.into_iter().map(|a| walk(&a, hoisted)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|a| walk(&a, hoisted)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| walk(&e, hoisted)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, walk(&v, hoisted))).collect(), span },
other => other,
}
}
let mut hoisted: Vec<A> = Vec::new();
let mut out = walk(ast, &mut hoisted);
// Append hoisted functions at top-level if root is Program
if let A::Program { statements, span } = out.clone() {
let mut ss = statements;
ss.extend(hoisted.into_iter());
out = A::Program { statements: ss, span };
}
out
}
fn subst_var(node: &nyash_rust::ASTNode, name: &str, replacement: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
match node.clone() {
A::Variable { name: n, .. } if n == name => replacement.clone(),
A::Program { statements, span } => A::Program { statements: statements.iter().map(|s| subst_var(s, name, replacement)).collect(), span },
A::Print { expression, span } => A::Print { expression: Box::new(subst_var(&expression, name, replacement)), span },
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(subst_var(v, name, replacement))), span },
A::Assignment { target, value, span } => A::Assignment { target: Box::new(subst_var(&target, name, replacement)), value: Box::new(subst_var(&value, name, replacement)), span },
A::If { condition, then_body, else_body, span } => A::If {
condition: Box::new(subst_var(&condition, name, replacement)),
then_body: then_body.iter().map(|s| subst_var(s, name, replacement)).collect(),
else_body: else_body.map(|v| v.iter().map(|s| subst_var(s, name, replacement)).collect()),
span,
},
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(subst_var(&left, name, replacement)), right: Box::new(subst_var(&right, name, replacement)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(subst_var(&operand, name, replacement)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(subst_var(&object, name, replacement)), method, arguments: arguments.iter().map(|a| subst_var(a, name, replacement)).collect(), span },
A::FunctionCall { name: fn_name, arguments, span } => A::FunctionCall { name: fn_name, arguments: arguments.iter().map(|a| subst_var(a, name, replacement)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.iter().map(|e| subst_var(e, name, replacement)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.iter().map(|(k,v)| (k.clone(), subst_var(v, name, replacement))).collect(), span },
other => other,
}
}
fn transform_for_foreach(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, LiteralValue, Span};
fn rewrite_stmt_list(list: Vec<A>) -> Vec<A> {
let mut out: Vec<A> = Vec::new();
for st in list.into_iter() {
match st.clone() {
A::FunctionCall { name, arguments, .. } if (name == "ny_for" || name == "for") && arguments.len() == 4 => {
let init = arguments[0].clone();
let cond = arguments[1].clone();
let step = arguments[2].clone();
let body_lam = arguments[3].clone();
if let A::Lambda { params, body, .. } = body_lam {
if params.is_empty() {
// Accept init as Local/Assignment or Lambda(); step as Assignment or Lambda()
// Emit init statements (0..n)
match init.clone() {
A::Assignment { .. } | A::Local { .. } => out.push(init),
A::Lambda { params: p2, body: b2, .. } if p2.is_empty() => {
for s in b2 { out.push(transform_for_foreach(&s)); }
}
_ => {}
}
let mut loop_body: Vec<A> = body
.into_iter()
.map(|n| transform_for_foreach(&n))
.collect();
// Append step statements at tail
match step.clone() {
A::Assignment { .. } => loop_body.push(step),
A::Lambda { params: p3, body: b3, .. } if p3.is_empty() => {
for s in b3 { loop_body.push(transform_for_foreach(&s)); }
}
_ => {}
}
out.push(A::Loop { condition: Box::new(cond), body: loop_body, span: Span::unknown() });
continue;
}
}
// Fallback: keep as-is
out.push(A::FunctionCall { name, arguments, span: Span::unknown() });
}
A::FunctionCall { name, arguments, .. } if (name == "ny_foreach" || name == "foreach") && arguments.len() == 3 => {
let arr = arguments[0].clone();
let var_name_opt = match &arguments[1] { A::Literal { value: LiteralValue::String(s), .. } => Some(s.clone()), _ => None };
let lam = arguments[2].clone();
if let (Some(vn), A::Lambda { params, body, .. }) = (var_name_opt, lam) {
if params.is_empty() {
let idx_name = "__ny_i".to_string();
let idx_var = A::Variable { name: idx_name.clone(), span: Span::unknown() };
let init_idx = A::Local { variables: vec![idx_name.clone()], initial_values: vec![Some(Box::new(A::Literal { value: LiteralValue::Integer(0), span: Span::unknown() }))], span: Span::unknown() };
let size_call = A::MethodCall { object: Box::new(arr.clone()), method: "size".to_string(), arguments: vec![], span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Less, left: Box::new(idx_var.clone()), right: Box::new(size_call), span: Span::unknown() };
let elem = A::MethodCall { object: Box::new(arr.clone()), method: "get".to_string(), arguments: vec![idx_var.clone()], span: Span::unknown() };
let mut loop_body: Vec<A> = body.into_iter().map(|n| subst_var(&n, &vn, &elem)).map(|n| transform_for_foreach(&n)).collect();
let step = A::Assignment { target: Box::new(A::Variable { name: idx_name.clone(), span: Span::unknown() }), value: Box::new(A::BinaryOp { operator: BinaryOperator::Add, left: Box::new(A::Variable { name: idx_name.clone(), span: Span::unknown() }), right: Box::new(A::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() }), span: Span::unknown() };
loop_body.push(step);
out.push(init_idx);
out.push(A::Loop { condition: Box::new(cond), body: loop_body, span: Span::unknown() });
continue;
}
}
out.push(A::FunctionCall { name, arguments, span: Span::unknown() });
}
A::Local { variables, initial_values, .. } => {
let mut expanded_any = false;
for opt in &initial_values {
if let Some(v) = opt {
if let A::FunctionCall { name, arguments, .. } = v.as_ref() {
if ((name == "ny_for" || name == "for") && arguments.len() == 4)
|| ((name == "ny_foreach" || name == "foreach") && arguments.len() == 3)
{
expanded_any = true;
}
}
}
}
if expanded_any {
for opt in initial_values {
if let Some(v) = opt {
match v.as_ref() {
A::FunctionCall { name: _, arguments, .. } if (arguments.len() == 4) => {
// Reuse handling by fabricating a statement call
let fake = A::FunctionCall { name: "for".to_string(), arguments: arguments.clone(), span: Span::unknown() };
// Route into the top arm by re-matching
match fake.clone() {
A::FunctionCall { name: _, arguments, .. } => {
let init = arguments[0].clone();
let cond = arguments[1].clone();
let step = arguments[2].clone();
let body_lam = arguments[3].clone();
if let A::Lambda { params, body, .. } = body_lam {
if params.is_empty() {
match init.clone() {
A::Assignment { .. } | A::Local { .. } => out.push(init),
A::Lambda { params: p2, body: b2, .. } if p2.is_empty() => { for s in b2 { out.push(transform_for_foreach(&s)); } }
_ => {}
}
let mut loop_body: Vec<A> = body.into_iter().map(|n| transform_for_foreach(&n)).collect();
match step.clone() {
A::Assignment { .. } => loop_body.push(step),
A::Lambda { params: p3, body: b3, .. } if p3.is_empty() => { for s in b3 { loop_body.push(transform_for_foreach(&s)); } }
_ => {}
}
out.push(A::Loop { condition: Box::new(cond), body: loop_body, span: Span::unknown() });
}
}
}
_ => {}
}
}
A::FunctionCall { name: _, arguments, .. } if (arguments.len() == 3) => {
let arr = arguments[0].clone();
let var_name_opt = match &arguments[1] { A::Literal { value: LiteralValue::String(s), .. } => Some(s.clone()), _ => None };
let lam = arguments[2].clone();
if let (Some(vn), A::Lambda { params, body, .. }) = (var_name_opt, lam) {
if params.is_empty() {
let idx_name = "__ny_i".to_string();
let idx_var = A::Variable { name: idx_name.clone(), span: Span::unknown() };
let init_idx = A::Local { variables: vec![idx_name.clone()], initial_values: vec![Some(Box::new(A::Literal { value: LiteralValue::Integer(0), span: Span::unknown() }))], span: Span::unknown() };
let size_call = A::MethodCall { object: Box::new(arr.clone()), method: "size".to_string(), arguments: vec![], span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Less, left: Box::new(idx_var.clone()), right: Box::new(size_call), span: Span::unknown() };
let elem = A::MethodCall { object: Box::new(arr.clone()), method: "get".to_string(), arguments: vec![idx_var.clone()], span: Span::unknown() };
let mut loop_body: Vec<A> = body.into_iter().map(|n| subst_var(&n, &vn, &elem)).map(|n| transform_for_foreach(&n)).collect();
let step = A::Assignment { target: Box::new(A::Variable { name: idx_name.clone(), span: Span::unknown() }), value: Box::new(A::BinaryOp { operator: BinaryOperator::Add, left: Box::new(A::Variable { name: idx_name.clone(), span: Span::unknown() }), right: Box::new(A::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() }), span: Span::unknown() };
loop_body.push(step);
out.push(init_idx);
out.push(A::Loop { condition: Box::new(cond), body: loop_body, span: Span::unknown() });
}
}
}
_ => {}
}
}
}
// Drop original Local that carried macros
continue;
} else {
out.push(A::Local { variables, initial_values, span: Span::unknown() });
}
}
A::FunctionCall { name, arguments, .. } if name == "foreach_" && arguments.len() == 3 => {
let arr = arguments[0].clone();
let var_name_opt = match &arguments[1] { A::Literal { value: LiteralValue::String(s), .. } => Some(s.clone()), _ => None };
let lam = arguments[2].clone();
if let (Some(vn), A::Lambda { params, body, .. }) = (var_name_opt, lam) {
if params.is_empty() {
// __ny_i = 0; loop(__ny_i < arr.size()) { body[var=arr.get(__ny_i)]; __ny_i = __ny_i + 1 }
let idx_name = "__ny_i".to_string();
let idx_var = A::Variable { name: idx_name.clone(), span: Span::unknown() };
let init_idx = A::Local { variables: vec![idx_name.clone()], initial_values: vec![Some(Box::new(A::Literal { value: LiteralValue::Integer(0), span: Span::unknown() }))], span: Span::unknown() };
let size_call = A::MethodCall { object: Box::new(arr.clone()), method: "size".to_string(), arguments: vec![], span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Less, left: Box::new(idx_var.clone()), right: Box::new(size_call), span: Span::unknown() };
let elem = A::MethodCall { object: Box::new(arr.clone()), method: "get".to_string(), arguments: vec![idx_var.clone()], span: Span::unknown() };
let mut loop_body: Vec<A> = body.into_iter().map(|n| subst_var(&n, &vn, &elem)).map(|n| transform_for_foreach(&n)).collect();
let step = A::Assignment { target: Box::new(A::Variable { name: idx_name.clone(), span: Span::unknown() }), value: Box::new(A::BinaryOp { operator: BinaryOperator::Add, left: Box::new(A::Variable { name: idx_name.clone(), span: Span::unknown() }), right: Box::new(A::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() }), span: Span::unknown() };
loop_body.push(step);
out.push(init_idx);
out.push(A::Loop { condition: Box::new(cond), body: loop_body, span: Span::unknown() });
continue;
}
}
out.push(A::FunctionCall { name, arguments, span: Span::unknown() });
}
// Recurse into container nodes and preserve others
A::If { condition, then_body, else_body, span } => {
out.push(A::If {
condition: Box::new(transform_for_foreach(&condition)),
then_body: rewrite_stmt_list(then_body),
else_body: else_body.map(rewrite_stmt_list),
span,
});
}
A::Loop { condition, body, span } => {
out.push(A::Loop {
condition: Box::new(transform_for_foreach(&condition)),
body: rewrite_stmt_list(body),
span,
});
}
other => out.push(transform_for_foreach(&other)),
}
}
out
}
match ast.clone() {
A::Program { statements, span } => A::Program { statements: rewrite_stmt_list(statements), span },
A::If { condition, then_body, else_body, span } => A::If {
condition: Box::new(transform_for_foreach(&condition)),
then_body: rewrite_stmt_list(then_body),
else_body: else_body.map(rewrite_stmt_list),
span,
},
A::Loop { condition, body, span } => A::Loop { condition: Box::new(transform_for_foreach(&condition)), body: rewrite_stmt_list(body), span },
// Leaf and expression nodes: descend but no statement expansion
A::Print { expression, span } => A::Print { expression: Box::new(transform_for_foreach(&expression)), span },
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(transform_for_foreach(v))), span },
A::Assignment { target, value, span } => A::Assignment { target: Box::new(transform_for_foreach(&target)), value: Box::new(transform_for_foreach(&value)), span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_for_foreach(&left)), right: Box::new(transform_for_foreach(&right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_for_foreach(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_for_foreach(&object)), method, arguments: arguments.iter().map(|a| transform_for_foreach(a)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.iter().map(|a| transform_for_foreach(a)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.iter().map(|e| transform_for_foreach(e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.iter().map(|(k,v)| (k.clone(), transform_for_foreach(v))).collect(), span },
other => other,
}
}
fn transform_scopebox_inject(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
match ast.clone() {
A::Program { statements, span } => {
A::Program { statements: statements.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span }
}
A::If { condition, then_body, else_body, span } => {
let cond = Box::new(transform_scopebox_inject(&condition));
let then_wrapped = vec![A::ScopeBox { body: then_body.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span: nyash_rust::ast::Span::unknown() }];
let else_wrapped = else_body.map(|v| vec![A::ScopeBox { body: v.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span: nyash_rust::ast::Span::unknown() }]);
A::If { condition: cond, then_body: then_wrapped, else_body: else_wrapped, span }
}
A::Loop { condition, body, span } => {
let cond = Box::new(transform_scopebox_inject(&condition));
let body_wrapped = vec![A::ScopeBox { body: body.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span: nyash_rust::ast::Span::unknown() }];
A::Loop { condition: cond, body: body_wrapped, span }
}
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_scopebox_inject(&left)), right: Box::new(transform_scopebox_inject(&right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_scopebox_inject(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_scopebox_inject(&object)), method, arguments: arguments.into_iter().map(|a| transform_scopebox_inject(&a)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|a| transform_scopebox_inject(&a)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_scopebox_inject(&e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k, v)| (k, transform_scopebox_inject(&v))).collect(), span },
other => other,
}
}
fn transform_if_to_loopform(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::{ASTNode as A, Span};
// Conservative rewrite: if (cond) { then } with no else and no break/continue in then → loop(cond) { then }
// (unused helpers removed)
match ast.clone() {
A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|n| transform_if_to_loopform(&n)).collect(), span },
A::If { condition, then_body, else_body, span } => {
// Case A/B unified: wrap into single-iteration loop with explicit break (semantics-preserving)
// This avoids multi-iteration semantics and works for both then-only and else-present cases.
let cond_t = Box::new(transform_if_to_loopform(&condition));
let then_t = then_body.into_iter().map(|n| transform_if_to_loopform(&n)).collect();
let else_t = else_body.map(|v| v.into_iter().map(|n| transform_if_to_loopform(&n)).collect());
let inner_if = A::If { condition: cond_t, then_body: then_t, else_body: else_t, span: Span::unknown() };
let one = A::Literal { value: nyash_rust::ast::LiteralValue::Integer(1), span: Span::unknown() };
let loop_body = vec![inner_if, A::Break { span: Span::unknown() }];
A::Loop { condition: Box::new(one), body: loop_body, span }
}
A::Loop { condition, body, span } => A::Loop {
condition: Box::new(transform_if_to_loopform(&condition)),
body: body.into_iter().map(|n| transform_if_to_loopform(&n)).collect(),
span
},
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_if_to_loopform(&left)), right: Box::new(transform_if_to_loopform(&right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_if_to_loopform(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_if_to_loopform(&object)), method, arguments: arguments.into_iter().map(|a| transform_if_to_loopform(&a)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|a| transform_if_to_loopform(&a)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_if_to_loopform(&e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k, v)| (k, transform_if_to_loopform(&v))).collect(), span },
other => other,
}
}
// Phase 1 sugar: postfix_catch(expr, "Type"?, fn(e){...}) / with_cleanup(expr, fn(){...})
// → legacy TryCatch AST for existing lowering paths. This is a stopgap until parser accepts postfix forms.
fn transform_postfix_handlers(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::{ASTNode as A, CatchClause, Span};
fn map_vec(v: Vec<A>) -> Vec<A> { v.into_iter().map(|n| transform_postfix_handlers(&n)).collect() }
match ast.clone() {
A::Program { statements, span } => A::Program { statements: map_vec(statements), span },
A::If { condition, then_body, else_body, span } => A::If {
condition: Box::new(transform_postfix_handlers(&condition)),
then_body: map_vec(then_body),
else_body: else_body.map(map_vec),
span,
},
A::Loop { condition, body, span } => A::Loop {
condition: Box::new(transform_postfix_handlers(&condition)),
body: map_vec(body),
span,
},
A::BinaryOp { operator, left, right, span } => A::BinaryOp {
operator,
left: Box::new(transform_postfix_handlers(&left)),
right: Box::new(transform_postfix_handlers(&right)),
span,
},
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_postfix_handlers(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall {
object: Box::new(transform_postfix_handlers(&object)),
method,
arguments: arguments.into_iter().map(|a| transform_postfix_handlers(&a)).collect(),
span,
},
A::FunctionCall { name, arguments, span } => {
let name_l = name.to_ascii_lowercase();
if name_l == "postfix_catch" {
// Forms:
// - postfix_catch(expr, fn(e){...})
// - postfix_catch(expr, "Type", fn(e){...})
let mut args = arguments;
if args.len() >= 2 {
let expr = transform_postfix_handlers(&args.remove(0));
let (type_opt, handler) = if args.len() == 1 {
(None, args.remove(0))
} else if args.len() >= 2 {
let ty = match args.remove(0) {
A::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } => Some(s),
other => {
// keep robust: non-string type → debug print type name, treat as None
let _ = other; None
}
};
(ty, args.remove(0))
} else { (None, A::Literal { value: nyash_rust::ast::LiteralValue::Void, span: Span::unknown() }) };
if let A::Lambda { params, body, .. } = handler {
let var = params.get(0).cloned();
let cc = CatchClause { exception_type: type_opt, variable_name: var, body, span: Span::unknown() };
return A::TryCatch { try_body: vec![expr], catch_clauses: vec![cc], finally_body: None, span };
}
}
// Fallback: recurse into args
A::FunctionCall { name, arguments: args.into_iter().map(|a| transform_postfix_handlers(&a)).collect(), span }
} else if name_l == "with_cleanup" {
// Form: with_cleanup(expr, fn(){...})
let mut args = arguments;
if args.len() >= 2 {
let expr = transform_postfix_handlers(&args.remove(0));
let handler = args.remove(0);
if let A::Lambda { body, .. } = handler {
return A::TryCatch { try_body: vec![expr], catch_clauses: vec![], finally_body: Some(body), span };
}
}
A::FunctionCall { name, arguments: args.into_iter().map(|a| transform_postfix_handlers(&a)).collect(), span }
} else {
A::FunctionCall { name, arguments: arguments.into_iter().map(|a| transform_postfix_handlers(&a)).collect(), span }
}
}
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_postfix_handlers(&e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, transform_postfix_handlers(&v))).collect(), span },
other => other,
}
}
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 mut behavior = crate::r#macro::macro_box_ny::analyze_macro_file(macro_file);
if macro_file.contains("env_tag_string_macro") {
behavior = crate::r#macro::macro_box_ny::MacroBehavior::EnvTagString;
}
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),
crate::r#macro::macro_box_ny::MacroBehavior::LoopNormalize => {
transform_loop_normalize(&ast)
}
crate::r#macro::macro_box_ny::MacroBehavior::IfMatchNormalize => {
transform_peek_match_literal(&ast)
}
crate::r#macro::macro_box_ny::MacroBehavior::ForForeachNormalize => {
transform_for_foreach(&ast)
}
crate::r#macro::macro_box_ny::MacroBehavior::EnvTagString => {
fn tag(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
match ast.clone() {
A::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } => {
if s == "hello" { A::Literal { value: nyash_rust::ast::LiteralValue::String("hello [ENV]".to_string()), span: nyash_rust::ast::Span::unknown() } } else { ast.clone() }
}
A::Program { statements, span } => A::Program { statements: statements.iter().map(|n| tag(n)).collect(), span },
A::Print { expression, span } => A::Print { expression: Box::new(tag(&expression)), span },
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(tag(v))), span },
A::Assignment { target, value, span } => A::Assignment { target: Box::new(tag(&target)), value: Box::new(tag(&value)), span },
A::If { condition, then_body, else_body, span } => A::If { condition: Box::new(tag(&condition)), then_body: then_body.iter().map(|n| tag(n)).collect(), else_body: else_body.map(|v| v.iter().map(|n| tag(n)).collect()), span },
A::Loop { condition, body, span } => A::Loop { condition: Box::new(tag(&condition)), body: body.iter().map(|n| tag(n)).collect(), span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(tag(&left)), right: Box::new(tag(&right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(tag(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(tag(&object)), method, arguments: arguments.iter().map(|a| tag(a)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.iter().map(|a| tag(a)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.iter().map(|e| tag(e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.iter().map(|(k,v)| (k.clone(), tag(v))).collect(), span },
other => other,
}
}
// Prefer ctx JSON from env (NYASH_MACRO_CTX_JSON) if provided; fallback to simple flag
let mut env_on = std::env::var("NYASH_MACRO_CAP_ENV").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false);
if let Ok(ctxs) = std::env::var("NYASH_MACRO_CTX_JSON") {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ctxs) {
env_on = v.get("caps").and_then(|c| c.get("env")).and_then(|b| b.as_bool()).unwrap_or(env_on);
}
}
if env_on { tag(&ast) } else { ast.clone() }
}
};
let out_json = crate::r#macro::ast_json::ast_to_json(&out_ast);
println!("{}", out_json.to_string());
}

View File

@ -0,0 +1,70 @@
use super::transforms::*;
pub fn run_macro_child(macro_file: &str) {
// Read full AST JSON (v0) from stdin
use std::io::Read;
let mut input = String::new();
if let Err(_) = std::io::stdin().read_to_string(&mut input) {
eprintln!("[macro-child] failed to read AST JSON from stdin");
std::process::exit(3);
}
let value: serde_json::Value = match serde_json::from_str(&input) {
Ok(v) => v,
Err(_) => { eprintln!("[macro-child] invalid AST JSON v0"); std::process::exit(3); }
};
let ast: nyash_rust::ASTNode = match crate::r#macro::ast_json::json_to_ast(&value) {
Some(a) => a,
None => { eprintln!("[macro-child] unsupported AST JSON v0"); std::process::exit(4); }
};
// Analyze macro behavior (PoC)
let mut behavior = crate::r#macro::macro_box_ny::analyze_macro_file(macro_file);
if macro_file.contains("env_tag_string_macro") {
behavior = crate::r#macro::macro_box_ny::MacroBehavior::EnvTagString;
}
let out_ast = match behavior {
crate::r#macro::macro_box_ny::MacroBehavior::Identity => ast.clone(),
crate::r#macro::macro_box_ny::MacroBehavior::Uppercase => {
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),
crate::r#macro::macro_box_ny::MacroBehavior::LoopNormalize => transform_loop_normalize(&ast),
crate::r#macro::macro_box_ny::MacroBehavior::IfMatchNormalize => transform_peek_match_literal(&ast),
crate::r#macro::macro_box_ny::MacroBehavior::ForForeachNormalize => transform_for_foreach(&ast),
crate::r#macro::macro_box_ny::MacroBehavior::EnvTagString => {
fn tag(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
match ast.clone() {
A::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } => {
if s == "hello" { A::Literal { value: nyash_rust::ast::LiteralValue::String("hello [ENV]".to_string()), span: nyash_rust::ast::Span::unknown() } } else { ast.clone() }
}
A::Program { statements, span } => A::Program { statements: statements.iter().map(|n| tag(n)).collect(), span },
A::Print { expression, span } => A::Print { expression: Box::new(tag(&expression)), span },
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(tag(v))), span },
A::Assignment { target, value, span } => A::Assignment { target: Box::new(tag(&target)), value: Box::new(tag(&value)), span },
A::If { condition, then_body, else_body, span } => A::If { condition: Box::new(tag(&condition)), then_body: then_body.iter().map(|n| tag(n)).collect(), else_body: else_body.map(|v| v.iter().map(|n| tag(n)).collect()), span },
A::Loop { condition, body, span } => A::Loop { condition: Box::new(tag(&condition)), body: body.iter().map(|n| tag(n)).collect(), span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(tag(&left)), right: Box::new(tag(&right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(tag(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(tag(&object)), method, arguments: arguments.iter().map(|a| tag(a)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.iter().map(|a| tag(a)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.iter().map(|e| tag(e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.iter().map(|(k,v)| (k.clone(), tag(v))).collect(), span },
other => other,
}
}
let mut env_on = std::env::var("NYASH_MACRO_CAP_ENV").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false);
if let Ok(ctxs) = std::env::var("NYASH_MACRO_CTX_JSON") {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ctxs) {
env_on = v.get("caps").and_then(|c| c.get("env")).and_then(|b| b.as_bool()).unwrap_or(env_on);
}
}
if env_on { tag(&ast) } else { ast.clone() }
}
};
let out_json = crate::r#macro::ast_json::ast_to_json(&out_ast);
println!("{}", out_json.to_string());
}

View File

@ -0,0 +1,10 @@
/*!
* Macro child mode (split modules)
*/
mod transforms;
mod entry;
pub use entry::run_macro_child;
pub use transforms::normalize_core_pass;

View File

@ -0,0 +1,36 @@
pub(super) 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, .. } => {
let mut new_elems: Vec<A> = Vec::with_capacity(elements.len() + 1);
let already_zero = elements
.get(0)
.and_then(|n| match n { A::Literal { value: LiteralValue::Integer(0), .. } => Some(()), _ => 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(),
}
}

View File

@ -0,0 +1,98 @@
fn subst_var(node: &nyash_rust::ASTNode, name: &str, replacement: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
match node.clone() {
A::Variable { name: n, .. } if n == name => replacement.clone(),
A::Program { statements, span } => A::Program { statements: statements.iter().map(|s| subst_var(s, name, replacement)).collect(), span },
A::Print { expression, span } => A::Print { expression: Box::new(subst_var(&expression, name, replacement)), span },
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(subst_var(v, name, replacement))), span },
A::Assignment { target, value, span } => A::Assignment { target: Box::new(subst_var(&target, name, replacement)), value: Box::new(subst_var(&value, name, replacement)), span },
A::If { condition, then_body, else_body, span } => A::If {
condition: Box::new(subst_var(&condition, name, replacement)),
then_body: then_body.iter().map(|s| subst_var(s, name, replacement)).collect(),
else_body: else_body.map(|v| v.iter().map(|s| subst_var(s, name, replacement)).collect()),
span,
},
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(subst_var(&left, name, replacement)), right: Box::new(subst_var(&right, name, replacement)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(subst_var(&operand, name, replacement)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(subst_var(&object, name, replacement)), method, arguments: arguments.iter().map(|a| subst_var(a, name, replacement)).collect(), span },
A::FunctionCall { name: fn_name, arguments, span } => A::FunctionCall { name: fn_name, arguments: arguments.iter().map(|a| subst_var(a, name, replacement)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.iter().map(|e| subst_var(e, name, replacement)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.iter().map(|(k,v)| (k.clone(), subst_var(v, name, replacement))).collect(), span },
other => other,
}
}
pub(super) fn transform_for_foreach(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, LiteralValue, Span};
fn rewrite_stmt_list(list: Vec<A>) -> Vec<A> {
let mut out: Vec<A> = Vec::new();
for st in list.into_iter() {
match st.clone() {
A::FunctionCall { name, arguments, .. } if (name == "ny_for" || name == "for") && arguments.len() == 4 => {
let init = arguments[0].clone();
let cond = arguments[1].clone();
let step = arguments[2].clone();
let body_lam = arguments[3].clone();
if let A::Lambda { params, body, .. } = body_lam {
if params.is_empty() {
match init.clone() {
A::Assignment { .. } | A::Local { .. } => out.push(init),
A::Lambda { params: p2, body: b2, .. } if p2.is_empty() => { for s in b2 { out.push(transform_for_foreach(&s)); } }
_ => {}
}
let mut loop_body: Vec<A> = body.into_iter().map(|n| transform_for_foreach(&n)).collect();
match step.clone() {
A::Assignment { .. } => loop_body.push(step),
A::Lambda { params: p2, body: b2, .. } if p2.is_empty() => { for s in b2 { loop_body.push(transform_for_foreach(&s)); } }
_ => {}
}
out.push(A::Loop { condition: Box::new(transform_for_foreach(&cond)), body: loop_body, span: Span::unknown() });
continue;
}
}
out.push(st);
}
A::FunctionCall { name, arguments, .. } if (name == "ny_foreach" || name == "foreach") && arguments.len() == 3 => {
let array = arguments[0].clone();
let param_name = match &arguments[1] { A::Variable { name, .. } => name.clone(), _ => "it".to_string() };
let body_lam = arguments[2].clone();
if let A::Lambda { params, body, .. } = body_lam {
if params.is_empty() {
let iter = A::Variable { name: "__i".to_string(), span: Span::unknown() };
let zero = A::Literal { value: LiteralValue::Integer(0), span: Span::unknown() };
let one = A::Literal { value: LiteralValue::Integer(1), span: Span::unknown() };
let init = A::Local { variables: vec!["__i".to_string()], initial_values: vec![Some(Box::new(zero))], span: Span::unknown() };
let len_call = A::MethodCall { object: Box::new(transform_for_foreach(&array)), method: "len".to_string(), arguments: vec![], span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Less, left: Box::new(iter.clone()), right: Box::new(len_call), span: Span::unknown() };
let get_call = A::MethodCall { object: Box::new(transform_for_foreach(&array)), method: "get".to_string(), arguments: vec![iter.clone()], span: Span::unknown() };
let body_stmts: Vec<A> = body.into_iter().map(|s| subst_var(&s, &param_name, &get_call)).collect();
let step = A::Assignment { target: Box::new(iter.clone()), value: Box::new(A::BinaryOp { operator: BinaryOperator::Add, left: Box::new(iter), right: Box::new(one), span: Span::unknown() }), span: Span::unknown() };
out.push(init);
out.push(A::Loop { condition: Box::new(cond), body: { let mut b = Vec::new(); for s in body_stmts { b.push(transform_for_foreach(&s)); } b.push(step); b }, span: Span::unknown() });
continue;
}
}
out.push(st);
}
other => out.push(transform_for_foreach(&other)),
}
}
out
}
// `A` is already imported above
match ast.clone() {
A::Program { statements, span } => A::Program { statements: rewrite_stmt_list(statements), span },
A::If { condition, then_body, else_body, span } => A::If { condition: Box::new(transform_for_foreach(&condition)), then_body: rewrite_stmt_list(then_body), else_body: else_body.map(rewrite_stmt_list), span },
A::Loop { condition, body, span } => A::Loop { condition: Box::new(transform_for_foreach(&condition)), body: rewrite_stmt_list(body), span },
A::Print { expression, span } => A::Print { expression: Box::new(transform_for_foreach(&expression)), span },
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(transform_for_foreach(v))), span },
A::Assignment { target, value, span } => A::Assignment { target: Box::new(transform_for_foreach(&target)), value: Box::new(transform_for_foreach(&value)), span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_for_foreach(&left)), right: Box::new(transform_for_foreach(&right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_for_foreach(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_for_foreach(&object)), method, arguments: arguments.iter().map(|a| transform_for_foreach(a)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.iter().map(|a| transform_for_foreach(a)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.iter().map(|e| transform_for_foreach(e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.iter().map(|(k,v)| (k.clone(), transform_for_foreach(v))).collect(), span },
other => other,
}
}

View File

@ -0,0 +1,24 @@
pub(super) fn transform_if_to_loopform(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::{ASTNode as A, Span};
match ast.clone() {
A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|n| transform_if_to_loopform(&n)).collect(), span },
A::If { condition, then_body, else_body, span } => {
let cond_t = Box::new(transform_if_to_loopform(&condition));
let then_t = then_body.into_iter().map(|n| transform_if_to_loopform(&n)).collect();
let else_t = else_body.map(|v| v.into_iter().map(|n| transform_if_to_loopform(&n)).collect());
let inner_if = A::If { condition: cond_t, then_body: then_t, else_body: else_t, span: Span::unknown() };
let one = A::Literal { value: nyash_rust::ast::LiteralValue::Integer(1), span: Span::unknown() };
let loop_body = vec![inner_if, A::Break { span: Span::unknown() }];
A::Loop { condition: Box::new(one), body: loop_body, span }
}
A::Loop { condition, body, span } => A::Loop { condition: Box::new(transform_if_to_loopform(&condition)), body: body.into_iter().map(|n| transform_if_to_loopform(&n)).collect(), span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_if_to_loopform(&left)), right: Box::new(transform_if_to_loopform(&right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_if_to_loopform(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_if_to_loopform(&object)), method, arguments: arguments.into_iter().map(|a| transform_if_to_loopform(&a)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|a| transform_if_to_loopform(&a)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_if_to_loopform(&e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k, v)| (k, transform_if_to_loopform(&v))).collect(), span },
other => other,
}
}

View File

@ -0,0 +1,132 @@
pub(super) fn transform_lift_nested_functions(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
fn gensym(base: &str) -> String { let n = COUNTER.fetch_add(1, Ordering::Relaxed); format!("__ny_lifted_{}_{}", base, n) }
fn collect_locals(n: &A, set: &mut std::collections::HashSet<String>) {
match n {
A::Local { variables, .. } => { for v in variables { set.insert(v.clone()); } }
A::Program { statements, .. } => for s in statements { collect_locals(s, set); },
A::FunctionDeclaration { body, .. } => for s in body { collect_locals(s, set); },
A::If { then_body, else_body, .. } => { for s in then_body { collect_locals(s, set); } if let Some(b) = else_body { for s in b { collect_locals(s, set); } } }
_ => {}
}
}
fn collect_vars(n: &A, set: &mut std::collections::HashSet<String>) {
match n {
A::Variable { name, .. } => { set.insert(name.clone()); }
A::Program { statements, .. } => for s in statements { collect_vars(s, set); },
A::FunctionDeclaration { body, .. } => for s in body { collect_vars(s, set); },
A::If { condition, then_body, else_body, .. } => {
collect_vars(condition, set);
for s in then_body { collect_vars(s, set); }
if let Some(b) = else_body { for s in b { collect_vars(s, set); } }
}
A::Assignment { target, value, .. } => { collect_vars(target, set); collect_vars(value, set); }
A::Return { value, .. } => { if let Some(v) = value { collect_vars(v, set); } }
A::Print { expression, .. } => collect_vars(expression, set),
A::BinaryOp { left, right, .. } => { collect_vars(left, set); collect_vars(right, set); }
A::UnaryOp { operand, .. } => collect_vars(operand, set),
A::MethodCall { object, arguments, .. } => { collect_vars(object, set); for a in arguments { collect_vars(a, set); } }
A::FunctionCall { arguments, .. } => { for a in arguments { collect_vars(a, set); } }
A::ArrayLiteral { elements, .. } => { for e in elements { collect_vars(e, set); } }
A::MapLiteral { entries, .. } => { for (_,v) in entries { collect_vars(v, set); } }
_ => {}
}
}
fn rename_calls(n: &A, mapping: &std::collections::HashMap<String, String>) -> A {
use nyash_rust::ast::ASTNode as A;
match n.clone() {
A::FunctionCall { name, arguments, span } => {
let new_name = mapping.get(&name).cloned().unwrap_or(name);
A::FunctionCall { name: new_name, arguments: arguments.into_iter().map(|a| rename_calls(&a, mapping)).collect(), span }
}
A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|s| rename_calls(&s, mapping)).collect(), span },
A::FunctionDeclaration { name, params, body, is_static, is_override, span } => {
A::FunctionDeclaration { name, params, body: body.into_iter().map(|s| rename_calls(&s, mapping)).collect(), is_static, is_override, span }
}
A::If { condition, then_body, else_body, span } => A::If {
condition: Box::new(rename_calls(&condition, mapping)),
then_body: then_body.into_iter().map(|s| rename_calls(&s, mapping)).collect(),
else_body: else_body.map(|v| v.into_iter().map(|s| rename_calls(&s, mapping)).collect()),
span,
},
A::Assignment { target, value, span } => A::Assignment { target: Box::new(rename_calls(&target, mapping)), value: Box::new(rename_calls(&value, mapping)), span },
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(rename_calls(v, mapping))), span },
A::Print { expression, span } => A::Print { expression: Box::new(rename_calls(&expression, mapping)), span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(rename_calls(&left, mapping)), right: Box::new(rename_calls(&right, mapping)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(rename_calls(&operand, mapping)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(rename_calls(&object, mapping)), method, arguments: arguments.into_iter().map(|a| rename_calls(&a, mapping)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| rename_calls(&e, mapping)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, rename_calls(&v, mapping))).collect(), span },
other => other,
}
}
fn lift_in_body(body: Vec<A>, hoisted: &mut Vec<A>, mapping: &mut std::collections::HashMap<String,String>) -> Vec<A> {
use std::collections::HashSet;
let mut out: Vec<A> = Vec::new();
for st in body.into_iter() {
match st.clone() {
A::FunctionDeclaration { name, params, body, is_static, is_override, span } => {
let mut locals: HashSet<String> = HashSet::new();
collect_locals(&A::FunctionDeclaration{ name: name.clone(), params: params.clone(), body: body.clone(), is_static, is_override, span }, &mut locals);
let mut used: HashSet<String> = HashSet::new();
collect_vars(&A::FunctionDeclaration{ name: name.clone(), params: params.clone(), body: body.clone(), is_static, is_override, span }, &mut used);
let params_set: HashSet<String> = params.iter().cloned().collect();
let mut extra: HashSet<String> = used.drain().collect();
extra.retain(|v| !params_set.contains(v) && !locals.contains(v));
if extra.is_empty() {
let new_name = gensym(&name);
let lifted = A::FunctionDeclaration { name: new_name.clone(), params, body, is_static: true, is_override, span };
hoisted.push(lifted);
mapping.insert(name, new_name);
continue;
} else { out.push(st); }
}
other => out.push(other),
}
}
out.into_iter().map(|n| rename_calls(&n, mapping)).collect()
}
fn walk(n: &A, hoisted: &mut Vec<A>) -> A {
use nyash_rust::ast::ASTNode as A;
match n.clone() {
A::Program { statements, span } => {
let mut mapping = std::collections::HashMap::new();
let stmts2 = lift_in_body(statements.into_iter().map(|s| walk(&s, hoisted)).collect(), hoisted, &mut mapping);
A::Program { statements: stmts2, span }
}
A::FunctionDeclaration { name, params, body, is_static, is_override, span } => {
let mut mapping = std::collections::HashMap::new();
let body2: Vec<A> = body.into_iter().map(|s| walk(&s, hoisted)).collect();
let body3 = lift_in_body(body2, hoisted, &mut mapping);
A::FunctionDeclaration { name, params, body: body3, is_static, is_override, span }
}
A::If { condition, then_body, else_body, span } => A::If {
condition: Box::new(walk(&condition, hoisted)),
then_body: then_body.into_iter().map(|s| walk(&s, hoisted)).collect(),
else_body: else_body.map(|v| v.into_iter().map(|s| walk(&s, hoisted)).collect()),
span,
},
A::Assignment { target, value, span } => A::Assignment { target: Box::new(walk(&target, hoisted)), value: Box::new(walk(&value, hoisted)), span },
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(walk(v, hoisted))), span },
A::Print { expression, span } => A::Print { expression: Box::new(walk(&expression, hoisted)), span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(walk(&left, hoisted)), right: Box::new(walk(&right, hoisted)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(walk(&operand, hoisted)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(walk(&object, hoisted)), method, arguments: arguments.into_iter().map(|a| walk(&a, hoisted)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|a| walk(&a, hoisted)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| walk(&e, hoisted)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, walk(&v, hoisted))).collect(), span },
other => other,
}
}
let mut hoisted: Vec<A> = Vec::new();
let mut out = walk(ast, &mut hoisted);
if let A::Program { statements, span } = out.clone() {
let mut ss = statements;
ss.extend(hoisted.into_iter());
out = A::Program { statements: ss, span };
}
out
}

View File

@ -0,0 +1,25 @@
pub(super) fn transform_loop_normalize(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
match ast.clone() {
A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|n| transform_loop_normalize(&n)).collect(), span },
A::If { condition, then_body, else_body, span } => A::If {
condition: Box::new(transform_loop_normalize(&condition)),
then_body: then_body.into_iter().map(|n| transform_loop_normalize(&n)).collect(),
else_body: else_body.map(|v| v.into_iter().map(|n| transform_loop_normalize(&n)).collect()),
span,
},
A::Loop { condition, body, span } => A::Loop {
condition: Box::new(transform_loop_normalize(&condition)),
body: body.into_iter().map(|n| transform_loop_normalize(&n)).collect(),
span,
},
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_loop_normalize(&left)), right: Box::new(transform_loop_normalize(&right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_loop_normalize(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_loop_normalize(&object)), method, arguments: arguments.into_iter().map(|a| transform_loop_normalize(&a)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|a| transform_loop_normalize(&a)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_loop_normalize(&e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, transform_loop_normalize(&v))).collect(), span },
other => other,
}
}

View File

@ -0,0 +1,35 @@
pub(super) 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, .. } => {
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() },
other => other.clone(),
}
}

View File

@ -0,0 +1,61 @@
/*!
* Macro child transforms — split modules
*/
mod peek;
mod array;
mod map;
mod loops;
mod foreach;
mod scopebox;
mod lift;
mod if_to_loopform;
mod postfix;
// Re-exported via thin wrappers to keep names stable
pub(super) fn transform_peek_match_literal(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
peek::transform_peek_match_literal(ast)
}
pub(super) fn transform_array_prepend_zero(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
array::transform_array_prepend_zero(ast)
}
pub(super) fn transform_map_insert_tag(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
map::transform_map_insert_tag(ast)
}
pub(super) fn transform_loop_normalize(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
loops::transform_loop_normalize(ast)
}
pub(super) fn transform_for_foreach(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
foreach::transform_for_foreach(ast)
}
pub(super) fn transform_scopebox_inject(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
scopebox::transform_scopebox_inject(ast)
}
pub(super) fn transform_lift_nested_functions(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
lift::transform_lift_nested_functions(ast)
}
pub(super) fn transform_if_to_loopform(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
if_to_loopform::transform_if_to_loopform(ast)
}
pub(super) fn transform_postfix_handlers(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
postfix::transform_postfix_handlers(ast)
}
// Core normalization pass used by runners (always-on when macros enabled).
// Order matters: for/foreach → match(PeekExpr) → loop tail alignment.
pub fn normalize_core_pass(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
let a1 = transform_for_foreach(ast);
let a2 = transform_peek_match_literal(&a1);
let a3 = transform_loop_normalize(&a2);
let a4 = if std::env::var("NYASH_SCOPEBOX_ENABLE").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false) {
transform_scopebox_inject(&a3)
} else { a3 };
let a4b = transform_lift_nested_functions(&a4);
let a5 = if std::env::var("NYASH_IF_AS_LOOPFORM").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false) {
transform_if_to_loopform(&a4b)
} else { a4b };
let a6 = if std::env::var("NYASH_CATCH_NEW").ok().map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false) {
transform_postfix_handlers(&a5)
} else { a5 };
a6
}

View File

@ -0,0 +1,113 @@
fn map_expr_to_stmt(e: nyash_rust::ASTNode) -> nyash_rust::ASTNode { e }
fn transform_peek_to_if_expr(peek: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
let mut conds_bodies: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms { conds_bodies.push((lit.clone(), (*body).clone())); }
let mut current: A = *(*else_expr).clone();
for (lit, body) in conds_bodies.into_iter().rev() {
let rhs = A::Literal { value: lit, span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() };
let then_body = vec![map_expr_to_stmt(body)];
let else_body = Some(vec![map_expr_to_stmt(current)]);
current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() };
}
Some(current)
} else { None }
}
fn transform_peek_to_if_stmt_assign(peek: &nyash_rust::ASTNode, target: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); }
let mut current: A = *(*else_expr).clone();
for (lit, body) in pairs.into_iter().rev() {
let rhs = A::Literal { value: lit, span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() };
let then_body = vec![A::Assignment { target: Box::new(target.clone()), value: Box::new(body), span: Span::unknown() }];
let else_body = Some(vec![map_expr_to_stmt(current)]);
current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() };
}
Some(current)
} else { None }
}
fn transform_peek_to_if_stmt_return(peek: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); }
let mut current: A = *(*else_expr).clone();
for (lit, body) in pairs.into_iter().rev() {
let rhs = A::Literal { value: lit, span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() };
let then_body = vec![A::Return { value: Some(Box::new(body)), span: Span::unknown() }];
let else_body = Some(vec![map_expr_to_stmt(current)]);
current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() };
}
Some(current)
} else { None }
}
fn transform_peek_to_if_stmt_print(peek: &nyash_rust::ASTNode) -> Option<nyash_rust::ASTNode> {
use nyash_rust::ast::{ASTNode as A, BinaryOperator, Span};
if let A::PeekExpr { scrutinee, arms, else_expr, .. } = peek {
let mut pairs: Vec<(nyash_rust::ast::LiteralValue, A)> = Vec::new();
for (lit, body) in arms { pairs.push((lit.clone(), (*body).clone())); }
let mut current: A = *(*else_expr).clone();
for (lit, body) in pairs.into_iter().rev() {
let rhs = A::Literal { value: lit, span: Span::unknown() };
let cond = A::BinaryOp { operator: BinaryOperator::Equal, left: scrutinee.clone(), right: Box::new(rhs), span: Span::unknown() };
let then_body = vec![A::Print { expression: Box::new(body), span: Span::unknown() }];
let else_body = Some(vec![map_expr_to_stmt(current)]);
current = A::If { condition: Box::new(cond), then_body, else_body, span: Span::unknown() };
}
Some(current)
} else { None }
}
pub(super) fn transform_peek_match_literal(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
match ast.clone() {
A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|n| transform_peek_match_literal(&n)).collect(), span },
A::If { condition, then_body, else_body, span } => A::If {
condition: Box::new(transform_peek_match_literal(&condition)),
then_body: then_body.into_iter().map(|n| transform_peek_match_literal(&n)).collect(),
else_body: else_body.map(|v| v.into_iter().map(|n| transform_peek_match_literal(&n)).collect()),
span,
},
A::Loop { condition, body, span } => A::Loop {
condition: Box::new(transform_peek_match_literal(&condition)),
body: body.into_iter().map(|n| transform_peek_match_literal(&n)).collect(),
span,
},
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_peek_match_literal(&left)), right: Box::new(transform_peek_match_literal(&right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_peek_match_literal(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_peek_match_literal(&object)), method, arguments: arguments.into_iter().map(|a| transform_peek_match_literal(&a)).collect(), span },
A::FunctionCall { name, arguments, span } => {
if let Some(if_expr) = transform_peek_to_if_expr(&A::FunctionCall { name: name.clone(), arguments: arguments.clone(), span }) {
if_expr
} else { A::FunctionCall { name, arguments: arguments.into_iter().map(|a| transform_peek_match_literal(&a)).collect(), span } }
}
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_peek_match_literal(&e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k, v)| (k, transform_peek_match_literal(&v))).collect(), span },
A::Assignment { target, value, span } => {
if let Some(ifstmt) = transform_peek_to_if_stmt_assign(&value, &target) { ifstmt }
else { A::Assignment { target, value: Box::new(transform_peek_match_literal(&value)), span } }
}
A::Return { value, span } => {
if let Some(v) = &value {
if let Some(ifstmt) = transform_peek_to_if_stmt_return(v) { ifstmt }
else { A::Return { value: Some(Box::new(transform_peek_match_literal(v))), span } }
} else { A::Return { value: None, span } }
}
A::Print { expression, span } => {
if let Some(ifstmt) = transform_peek_to_if_stmt_print(&expression) { ifstmt }
else { A::Print { expression: Box::new(transform_peek_match_literal(&expression)), span } }
}
other => other,
}
}

View File

@ -0,0 +1,69 @@
pub(super) fn transform_postfix_handlers(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::{ASTNode as A, CatchClause, Span};
fn map_vec(v: Vec<A>) -> Vec<A> { v.into_iter().map(|n| transform_postfix_handlers(&n)).collect() }
match ast.clone() {
A::Program { statements, span } => A::Program { statements: map_vec(statements), span },
A::If { condition, then_body, else_body, span } => A::If { condition: Box::new(transform_postfix_handlers(&condition)), then_body: map_vec(then_body), else_body: else_body.map(map_vec), span },
A::Loop { condition, body, span } => A::Loop { condition: Box::new(transform_postfix_handlers(&condition)), body: map_vec(body), span },
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_postfix_handlers(&left)), right: Box::new(transform_postfix_handlers(&right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_postfix_handlers(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_postfix_handlers(&object)), method, arguments: arguments.into_iter().map(|a| transform_postfix_handlers(&a)).collect(), span },
A::FunctionCall { name, arguments, span } => {
let name_l = name.to_ascii_lowercase();
if name_l == "postfix_catch" {
let mut args = arguments;
if args.len() >= 2 {
let expr = transform_postfix_handlers(&args.remove(0));
let (type_opt, handler) = if args.len() == 1 {
(None, args.remove(0))
} else if args.len() >= 2 {
let ty = match args.remove(0) { A::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } => Some(s), _ => None };
(ty, args.remove(0))
} else {
(None, A::Literal { value: nyash_rust::ast::LiteralValue::Integer(0), span: Span::unknown() })
};
if let A::Lambda { params, body, .. } = handler {
if params.len() == 1 {
let cc = CatchClause {
exception_type: type_opt,
variable_name: Some(params[0].clone()),
body: body.into_iter().map(|n| transform_postfix_handlers(&n)).collect(),
span: Span::unknown(),
};
return A::TryCatch {
try_body: vec![expr],
catch_clauses: vec![cc],
finally_body: None,
span: Span::unknown(),
};
}
}
}
A::FunctionCall { name, arguments: args.into_iter().map(|n| transform_postfix_handlers(&n)).collect(), span }
} else if name_l == "with_cleanup" {
let mut args = arguments;
if args.len() >= 2 {
let expr = transform_postfix_handlers(&args.remove(0));
let cleanup = args.remove(0);
if let A::Lambda { params, body, .. } = cleanup {
if params.is_empty() {
return A::TryCatch {
try_body: vec![expr],
catch_clauses: vec![],
finally_body: Some(body.into_iter().map(|n| transform_postfix_handlers(&n)).collect()),
span: Span::unknown(),
};
}
}
}
A::FunctionCall { name, arguments: args.into_iter().map(|n| transform_postfix_handlers(&n)).collect(), span }
} else {
A::FunctionCall { name, arguments: arguments.into_iter().map(|n| transform_postfix_handlers(&n)).collect(), span }
}
}
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_postfix_handlers(&e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, transform_postfix_handlers(&v))).collect(), span },
other => other,
}
}

View File

@ -0,0 +1,25 @@
pub(super) fn transform_scopebox_inject(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
use nyash_rust::ast::ASTNode as A;
match ast.clone() {
A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span },
A::If { condition, then_body, else_body, span } => {
let cond = Box::new(transform_scopebox_inject(&condition));
let then_wrapped = vec![A::ScopeBox { body: then_body.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span: nyash_rust::ast::Span::unknown() }];
let else_wrapped = else_body.map(|v| vec![A::ScopeBox { body: v.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span: nyash_rust::ast::Span::unknown() }]);
A::If { condition: cond, then_body: then_wrapped, else_body: else_wrapped, span }
}
A::Loop { condition, body, span } => {
let cond = Box::new(transform_scopebox_inject(&condition));
let body_wrapped = vec![A::ScopeBox { body: body.into_iter().map(|n| transform_scopebox_inject(&n)).collect(), span: nyash_rust::ast::Span::unknown() }];
A::Loop { condition: cond, body: body_wrapped, span }
}
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(transform_scopebox_inject(&left)), right: Box::new(transform_scopebox_inject(&right)), span },
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(transform_scopebox_inject(&operand)), span },
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(transform_scopebox_inject(&object)), method, arguments: arguments.into_iter().map(|a| transform_scopebox_inject(&a)).collect(), span },
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|a| transform_scopebox_inject(&a)).collect(), span },
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|e| transform_scopebox_inject(&e)).collect(), span },
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k, v)| (k, transform_scopebox_inject(&v))).collect(), span },
other => other,
}
}

View File

@ -4,6 +4,7 @@ use std::{fs, process};
/// Execute using PyVM only (no Rust VM runtime). Emits MIR(JSON) and invokes tools/pyvm_runner.py.
pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) {
if std::env::var("NYASH_PYVM_TRACE").ok().as_deref() == Some("1") { eprintln!("[pyvm] entry"); }
// Read the file
let code = match fs::read_to_string(filename) {
Ok(content) => content,
@ -14,14 +15,63 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) {
};
// Optional using pre-processing (strip lines and register modules)
let code = if crate::config::env::enable_using() {
let mut code = if crate::config::env::enable_using() {
match crate::runner::modes::common_util::resolve::strip_using_and_register(runner, &code, filename) {
Ok(s) => s,
Err(e) => { eprintln!("{}", e); process::exit(1); }
}
} else { code };
// Dev sugar pre-expand: line-head @name[:T] = expr → local name[:T] = expr
code = crate::runner::modes::common_util::resolve::preexpand_at_local(&code);
// Normalize logical operators for Stage-2 parser: translate '||'/'&&' to 'or'/'and' outside strings/comments
fn normalize_logical_ops(src: &str) -> String {
let mut out = String::with_capacity(src.len());
let mut it = src.chars().peekable();
let mut in_str = false;
let mut in_line = false;
let mut in_block = false;
while let Some(c) = it.next() {
if in_line {
out.push(c);
if c == '\n' { in_line = false; }
continue;
}
if in_block {
out.push(c);
if c == '*' && matches!(it.peek(), Some('/')) { out.push('/'); it.next(); in_block = false; }
continue;
}
if in_str {
out.push(c);
if c == '\\' { if let Some(nc) = it.next() { out.push(nc); } continue; }
if c == '"' { in_str = false; }
continue;
}
match c {
'"' => { in_str = true; out.push(c); }
'/' => {
match it.peek() { Some('/') => { out.push('/'); out.push('/'); it.next(); in_line = true; }, Some('*') => { out.push('/'); out.push('*'); it.next(); in_block = true; }, _ => out.push('/') }
}
'#' => { in_line = true; out.push('#'); }
'|' => {
if matches!(it.peek(), Some('|')) { out.push_str(" or "); it.next(); } else if matches!(it.peek(), Some('>')) { out.push('|'); out.push('>'); it.next(); } else { out.push('|'); }
}
'&' => {
if matches!(it.peek(), Some('&')) { out.push_str(" and "); it.next(); } else { out.push('&'); }
}
_ => out.push(c),
}
}
out
}
code = normalize_logical_ops(&code);
// Parse to AST
if std::env::var("NYASH_PYVM_DUMP_CODE").ok().as_deref() == Some("1") {
eprintln!("[pyvm-code]\n{}", code);
}
let ast = match NyashParser::parse_from_string(&code) {
Ok(ast) => ast,
Err(e) => {

View File

@ -33,11 +33,17 @@ impl NyashRunner {
if let Ok(text) = std::fs::read_to_string("nyash.toml") {
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
if let Some(mods) = doc.get("modules").and_then(|v| v.as_table()) {
for (k, v) in mods.iter() {
if let Some(path) = v.as_str() {
pending_modules.push((k.to_string(), path.to_string()));
fn visit(prefix: &str, tbl: &toml::value::Table, out: &mut Vec<(String, String)>) {
for (k, v) in tbl.iter() {
let name = if prefix.is_empty() { k.to_string() } else { format!("{}.{}", prefix, k) };
if let Some(s) = v.as_str() {
out.push((name, s.to_string()));
} else if let Some(t) = v.as_table() {
visit(&name, t, out);
}
}
}
visit("", mods, &mut pending_modules);
}
if let Some(using_tbl) = doc.get("using").and_then(|v| v.as_table()) {
if let Some(paths_arr) = using_tbl.get("paths").and_then(|v| v.as_array()) {

View File

@ -103,13 +103,13 @@ impl NyashRunner {
}
}
}
// Preferred: run Ny selfhost compiler program (apps/selfhost-compiler/compiler.nyash)
// Preferred: run Ny selfhost compiler program (apps/selfhost/compiler/compiler.nyash)
// This avoids inline embedding pitfalls and supports Stage-3 gating via args.
{
use crate::runner::modes::common_util::selfhost::{child, json};
let exe = std::env::current_exe()
.unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash"));
let parser_prog = std::path::Path::new("apps/selfhost-compiler/compiler.nyash");
let parser_prog = std::path::Path::new("apps/selfhost/compiler/compiler.nyash");
if parser_prog.exists() {
// Build extra args forwarded to child program
let mut extra: Vec<&str> = Vec::new();
@ -296,7 +296,7 @@ impl NyashRunner {
}
let inline_path = std::path::Path::new("tmp").join("inline_selfhost_emit.nyash");
let inline_code = format!(
"include \"apps/selfhost-compiler/boxes/parser_box.nyash\"\ninclude \"apps/selfhost-compiler/boxes/emitter_box.nyash\"\nstatic box Main {{\n main(args) {{\n local s = \"{}\"\n local p = new ParserBox()\n p.stage3_enable(1)\n local json = p.parse_program2(s)\n local e = new EmitterBox()\n json = e.emit_program(json, \"[]\")\n print(json)\n return 0\n }}\n}}\n",
"include \"apps/selfhost/compiler/boxes/parser_box.nyash\"\ninclude \"apps/selfhost/compiler/boxes/emitter_box.nyash\"\nstatic box Main {{\n main(args) {{\n local s = \"{}\"\n local p = new ParserBox()\n p.stage3_enable(1)\n local json = p.parse_program2(s)\n local e = new EmitterBox()\n json = e.emit_program(json, \"[]\")\n print(json)\n return 0\n }}\n}}\n",
esc
);
if let Err(e) = std::fs::write(&inline_path, inline_code) {