selfhost(pyvm): MiniVmPrints – prefer JSON route early-return (ok==1) to avoid fallback loops; keep default behavior unchanged elsewhere
This commit is contained in:
152
src/runner/jit_direct.rs
Normal file
152
src/runner/jit_direct.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
70
src/runner/modes/macro_child/entry.rs
Normal file
70
src/runner/modes/macro_child/entry.rs
Normal 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());
|
||||
}
|
||||
10
src/runner/modes/macro_child/mod.rs
Normal file
10
src/runner/modes/macro_child/mod.rs
Normal 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;
|
||||
|
||||
36
src/runner/modes/macro_child/transforms/array.rs
Normal file
36
src/runner/modes/macro_child/transforms/array.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
|
||||
98
src/runner/modes/macro_child/transforms/foreach.rs
Normal file
98
src/runner/modes/macro_child/transforms/foreach.rs
Normal 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, ¶m_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,
|
||||
}
|
||||
}
|
||||
24
src/runner/modes/macro_child/transforms/if_to_loopform.rs
Normal file
24
src/runner/modes/macro_child/transforms/if_to_loopform.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
|
||||
132
src/runner/modes/macro_child/transforms/lift.rs
Normal file
132
src/runner/modes/macro_child/transforms/lift.rs
Normal 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
|
||||
}
|
||||
|
||||
25
src/runner/modes/macro_child/transforms/loops.rs
Normal file
25
src/runner/modes/macro_child/transforms/loops.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
|
||||
35
src/runner/modes/macro_child/transforms/map.rs
Normal file
35
src/runner/modes/macro_child/transforms/map.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
|
||||
61
src/runner/modes/macro_child/transforms/mod.rs
Normal file
61
src/runner/modes/macro_child/transforms/mod.rs
Normal 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
|
||||
}
|
||||
113
src/runner/modes/macro_child/transforms/peek.rs
Normal file
113
src/runner/modes/macro_child/transforms/peek.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
|
||||
69
src/runner/modes/macro_child/transforms/postfix.rs
Normal file
69
src/runner/modes/macro_child/transforms/postfix.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
|
||||
25
src/runner/modes/macro_child/transforms/scopebox.rs
Normal file
25
src/runner/modes/macro_child/transforms/scopebox.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user