core: for/foreach -> Loop normalization (always-on); LoopForm MVP-3 per-segment reorder; smokes stabilized (VM + LLVM PHI); docs updated (macro-system, loopform); quiet macro load logs

This commit is contained in:
Selfhosting Dev
2025-09-20 08:39:40 +09:00
parent f50f79994f
commit 8a84339ac2
20 changed files with 1216 additions and 32 deletions

View File

@ -31,6 +31,7 @@ impl NyashRunner {
};
// Macro expansion (env-gated)
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
let ast = crate::runner::modes::macro_child::normalize_core_pass(&ast);
// Compile to MIR
let mut mir_compiler = MirCompiler::new();

View File

@ -206,6 +206,354 @@ fn transform_map_insert_tag(ast: &nyash_rust::ASTNode) -> nyash_rust::ASTNode {
}
}
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 制約維持)。
// まず全体の更新変数の種類数を計測上限2
let mut uniq_targets_overall: Vec<String> = Vec::new();
for stmt in &body_norm {
if let A::Assignment { target, .. } = stmt {
if let A::Variable { name, .. } = target.as_ref() {
if !uniq_targets_overall.iter().any(|s| s == name) {
uniq_targets_overall.push(name.clone());
if uniq_targets_overall.len() > 2 { // 超過したら全体の並べ替えは不許可
return A::Loop { condition, body: body_norm, span };
}
}
} else {
// 複合ターゲットを含む場合は保守的にスキップ
return A::Loop { condition, body: body_norm, span };
}
}
}
// セグメント分解 → セグメント毎に安全整列
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);
a3
}
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,
}
}
pub fn run_macro_child(macro_file: &str) {
// Read stdin all
use std::io::Read;
@ -234,12 +582,14 @@ pub fn run_macro_child(macro_file: &str) {
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 => {
// MVP: identity (future: normalize Loop into carrier-based form)
ast.clone()
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)
}
};
let out_json = crate::r#macro::ast_json::ast_to_json(&out_ast);
println!("{}", out_json.to_string());

View File

@ -22,6 +22,7 @@ pub fn execute_pyvm_only(_runner: &NyashRunner, filename: &str) {
}
};
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
let ast = crate::runner::modes::macro_child::normalize_core_pass(&ast);
// Compile to MIR (respect default optimizer setting)
let mut mir_compiler = MirCompiler::with_options(true);