2025-12-06 14:46:33 +09:00
|
|
|
//! Loop Update Expression Analyzer
|
|
|
|
|
//!
|
|
|
|
|
//! Phase 197: Extracts update expressions from loop body to generate
|
|
|
|
|
//! correct carrier update semantics.
|
|
|
|
|
//!
|
|
|
|
|
//! # Purpose
|
|
|
|
|
//!
|
|
|
|
|
//! The Pattern 4 lowerer needs to know how each carrier variable is updated
|
|
|
|
|
//! in the loop body. Instead of hardcoding "count uses +1, sum uses +i",
|
|
|
|
|
//! we extract the actual update expressions from the AST.
|
|
|
|
|
//!
|
|
|
|
|
//! # Example
|
|
|
|
|
//!
|
|
|
|
|
//! ```nyash
|
|
|
|
|
//! loop(i < 10) {
|
|
|
|
|
//! i = i + 1 // UpdateExpr::BinOp { lhs: "i", op: Add, rhs: Const(1) }
|
|
|
|
|
//! sum = sum + i // UpdateExpr::BinOp { lhs: "sum", op: Add, rhs: "i" }
|
|
|
|
|
//! count = count + 1 // UpdateExpr::BinOp { lhs: "count", op: Add, rhs: Const(1) }
|
|
|
|
|
//! }
|
|
|
|
|
//! ```
|
|
|
|
|
|
|
|
|
|
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
|
|
|
|
|
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
|
|
|
|
|
use crate::mir::join_ir::BinOpKind;
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
/// Update expression for a carrier variable
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum UpdateExpr {
|
|
|
|
|
/// Constant increment: carrier = carrier + N
|
|
|
|
|
Const(i64),
|
|
|
|
|
/// Binary operation: carrier = carrier op rhs
|
|
|
|
|
BinOp {
|
|
|
|
|
lhs: String,
|
|
|
|
|
op: BinOpKind,
|
|
|
|
|
rhs: UpdateRhs,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Right-hand side of update expression
|
2025-12-08 18:36:13 +09:00
|
|
|
///
|
|
|
|
|
/// Phase 178: Extended to detect string updates for multi-carrier loops.
|
|
|
|
|
/// The goal is "carrier detection", not full semantic understanding.
|
2025-12-06 14:46:33 +09:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum UpdateRhs {
|
2025-12-08 18:36:13 +09:00
|
|
|
/// Numeric constant: count + 1
|
2025-12-06 14:46:33 +09:00
|
|
|
Const(i64),
|
2025-12-08 18:36:13 +09:00
|
|
|
/// Variable reference: sum + i
|
2025-12-06 14:46:33 +09:00
|
|
|
Variable(String),
|
2025-12-08 18:36:13 +09:00
|
|
|
/// Phase 178: String literal: result + "x"
|
|
|
|
|
StringLiteral(String),
|
|
|
|
|
/// Phase 178: Other expression (method call, complex expr)
|
|
|
|
|
/// Used to detect "carrier has an update" without understanding semantics
|
|
|
|
|
Other,
|
2025-12-06 14:46:33 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct LoopUpdateAnalyzer;
|
|
|
|
|
|
|
|
|
|
impl LoopUpdateAnalyzer {
|
|
|
|
|
/// Analyze carrier update expressions from loop body
|
|
|
|
|
///
|
|
|
|
|
/// Extracts update patterns like:
|
|
|
|
|
/// - `sum = sum + i` → BinOp { lhs: "sum", op: Add, rhs: Variable("i") }
|
|
|
|
|
/// - `count = count + 1` → BinOp { lhs: "count", op: Add, rhs: Const(1) }
|
|
|
|
|
///
|
|
|
|
|
/// # Parameters
|
|
|
|
|
/// - `body`: Loop body AST nodes
|
|
|
|
|
/// - `carriers`: Carrier variables to analyze
|
|
|
|
|
///
|
|
|
|
|
/// # Returns
|
|
|
|
|
/// Map from carrier name to UpdateExpr
|
|
|
|
|
pub fn analyze_carrier_updates(
|
|
|
|
|
body: &[ASTNode],
|
|
|
|
|
carriers: &[CarrierVar],
|
|
|
|
|
) -> HashMap<String, UpdateExpr> {
|
|
|
|
|
let mut updates = HashMap::new();
|
|
|
|
|
|
|
|
|
|
// Extract carrier names for quick lookup
|
|
|
|
|
let carrier_names: Vec<&str> = carriers.iter().map(|c| c.name.as_str()).collect();
|
|
|
|
|
|
2025-12-07 19:00:12 +09:00
|
|
|
// Recursively scan all statements in the loop body
|
|
|
|
|
Self::scan_nodes(body, &carrier_names, &mut updates);
|
|
|
|
|
|
|
|
|
|
updates
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Recursively scan AST nodes for carrier updates
|
|
|
|
|
///
|
|
|
|
|
/// Phase 33-19: Extended to scan into if-else branches to handle
|
|
|
|
|
/// Pattern B (else-continue) after normalization.
|
|
|
|
|
fn scan_nodes(
|
|
|
|
|
nodes: &[ASTNode],
|
|
|
|
|
carrier_names: &[&str],
|
|
|
|
|
updates: &mut HashMap<String, UpdateExpr>,
|
|
|
|
|
) {
|
|
|
|
|
for node in nodes {
|
|
|
|
|
match node {
|
|
|
|
|
ASTNode::Assignment { target, value, .. } => {
|
|
|
|
|
// Check if this is a carrier update (e.g., sum = sum + i)
|
|
|
|
|
if let Some(target_name) = Self::extract_variable_name(target) {
|
|
|
|
|
if carrier_names.contains(&target_name.as_str()) {
|
|
|
|
|
// This is a carrier update, analyze the RHS
|
|
|
|
|
if let Some(update_expr) = Self::analyze_update_value(&target_name, value) {
|
|
|
|
|
updates.insert(target_name, update_expr);
|
|
|
|
|
}
|
2025-12-06 14:46:33 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-07 19:00:12 +09:00
|
|
|
// Phase 33-19: Recursively scan if-else branches
|
|
|
|
|
ASTNode::If {
|
|
|
|
|
then_body,
|
|
|
|
|
else_body,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
Self::scan_nodes(then_body, carrier_names, updates);
|
|
|
|
|
if let Some(else_stmts) = else_body {
|
|
|
|
|
Self::scan_nodes(else_stmts, carrier_names, updates);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Add more recursive cases as needed (loops, etc.)
|
|
|
|
|
_ => {}
|
2025-12-06 14:46:33 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Extract variable name from AST node (for assignment target)
|
|
|
|
|
fn extract_variable_name(node: &ASTNode) -> Option<String> {
|
|
|
|
|
match node {
|
|
|
|
|
ASTNode::Variable { name, .. } => Some(name.clone()),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Analyze update value expression
|
|
|
|
|
///
|
|
|
|
|
/// Recognizes patterns like:
|
|
|
|
|
/// - `sum + i` → BinOp { lhs: "sum", op: Add, rhs: Variable("i") }
|
|
|
|
|
/// - `count + 1` → BinOp { lhs: "count", op: Add, rhs: Const(1) }
|
|
|
|
|
fn analyze_update_value(carrier_name: &str, value: &ASTNode) -> Option<UpdateExpr> {
|
|
|
|
|
match value {
|
|
|
|
|
ASTNode::BinaryOp {
|
|
|
|
|
operator,
|
|
|
|
|
left,
|
|
|
|
|
right,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
// Check if LHS is the carrier itself (e.g., sum in "sum + i")
|
|
|
|
|
if let Some(lhs_name) = Self::extract_variable_name(left) {
|
|
|
|
|
if lhs_name == carrier_name {
|
|
|
|
|
// Convert operator
|
|
|
|
|
let op = Self::convert_operator(operator)?;
|
|
|
|
|
|
|
|
|
|
// Analyze RHS
|
|
|
|
|
let rhs = Self::analyze_rhs(right)?;
|
|
|
|
|
|
|
|
|
|
return Some(UpdateExpr::BinOp {
|
|
|
|
|
lhs: lhs_name,
|
|
|
|
|
op,
|
|
|
|
|
rhs,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Analyze right-hand side of update expression
|
2025-12-08 18:36:13 +09:00
|
|
|
///
|
|
|
|
|
/// Phase 178: Extended to detect string updates.
|
|
|
|
|
/// Goal: "carrier has update" detection, not semantic understanding.
|
2025-12-06 14:46:33 +09:00
|
|
|
fn analyze_rhs(node: &ASTNode) -> Option<UpdateRhs> {
|
|
|
|
|
match node {
|
|
|
|
|
// Constant: count + 1
|
|
|
|
|
ASTNode::Literal {
|
|
|
|
|
value: LiteralValue::Integer(n),
|
|
|
|
|
..
|
|
|
|
|
} => Some(UpdateRhs::Const(*n)),
|
|
|
|
|
|
2025-12-08 18:36:13 +09:00
|
|
|
// Phase 178: String literal: result + "x"
|
|
|
|
|
ASTNode::Literal {
|
|
|
|
|
value: LiteralValue::String(s),
|
|
|
|
|
..
|
|
|
|
|
} => Some(UpdateRhs::StringLiteral(s.clone())),
|
|
|
|
|
|
|
|
|
|
// Variable: sum + i (also handles: result + ch)
|
2025-12-06 14:46:33 +09:00
|
|
|
ASTNode::Variable { name, .. } => {
|
|
|
|
|
Some(UpdateRhs::Variable(name.clone()))
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 18:36:13 +09:00
|
|
|
// Phase 178: Method call or other complex expression
|
|
|
|
|
// e.g., result + s.substring(pos, end)
|
|
|
|
|
ASTNode::Call { .. }
|
|
|
|
|
| ASTNode::MethodCall { .. }
|
|
|
|
|
| ASTNode::BinaryOp { .. }
|
|
|
|
|
| ASTNode::UnaryOp { .. } => Some(UpdateRhs::Other),
|
|
|
|
|
|
2025-12-06 14:46:33 +09:00
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convert AST operator to MIR BinOpKind
|
|
|
|
|
fn convert_operator(op: &BinaryOperator) -> Option<BinOpKind> {
|
|
|
|
|
match op {
|
|
|
|
|
BinaryOperator::Add => Some(BinOpKind::Add),
|
|
|
|
|
BinaryOperator::Subtract => Some(BinOpKind::Sub),
|
|
|
|
|
BinaryOperator::Multiply => Some(BinOpKind::Mul),
|
|
|
|
|
BinaryOperator::Divide => Some(BinOpKind::Div),
|
|
|
|
|
_ => None, // Only support arithmetic operators for now
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_analyze_simple_increment() {
|
|
|
|
|
// Test case: count = count + 1
|
|
|
|
|
use crate::ast::Span;
|
|
|
|
|
|
|
|
|
|
let body = vec![ASTNode::Assignment {
|
|
|
|
|
target: Box::new(ASTNode::Variable {
|
|
|
|
|
name: "count".to_string(),
|
2025-12-07 23:45:55 +09:00
|
|
|
span: Span::unknown(),
|
2025-12-06 14:46:33 +09:00
|
|
|
}),
|
|
|
|
|
value: Box::new(ASTNode::BinaryOp {
|
|
|
|
|
operator: BinaryOperator::Add,
|
|
|
|
|
left: Box::new(ASTNode::Variable {
|
|
|
|
|
name: "count".to_string(),
|
2025-12-07 23:45:55 +09:00
|
|
|
span: Span::unknown(),
|
2025-12-06 14:46:33 +09:00
|
|
|
}),
|
|
|
|
|
right: Box::new(ASTNode::Literal {
|
|
|
|
|
value: LiteralValue::Integer(1),
|
2025-12-07 23:45:55 +09:00
|
|
|
span: Span::unknown(),
|
2025-12-06 14:46:33 +09:00
|
|
|
}),
|
2025-12-07 23:45:55 +09:00
|
|
|
span: Span::unknown(),
|
2025-12-06 14:46:33 +09:00
|
|
|
}),
|
2025-12-07 23:45:55 +09:00
|
|
|
span: Span::unknown(),
|
2025-12-06 14:46:33 +09:00
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
let carriers = vec![CarrierVar {
|
|
|
|
|
name: "count".to_string(),
|
|
|
|
|
host_id: crate::mir::ValueId(0),
|
2025-12-08 18:36:13 +09:00
|
|
|
join_id: None, // Phase 177-STRUCT-1
|
2025-12-06 14:46:33 +09:00
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
|
|
|
|
|
|
|
|
|
|
assert_eq!(updates.len(), 1);
|
|
|
|
|
assert!(updates.contains_key("count"));
|
|
|
|
|
|
|
|
|
|
if let Some(UpdateExpr::BinOp { lhs, op, rhs }) = updates.get("count") {
|
|
|
|
|
assert_eq!(lhs, "count");
|
|
|
|
|
assert_eq!(*op, BinOpKind::Add);
|
|
|
|
|
if let UpdateRhs::Const(n) = rhs {
|
|
|
|
|
assert_eq!(*n, 1);
|
|
|
|
|
} else {
|
|
|
|
|
panic!("Expected Const(1), got {:?}", rhs);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
panic!("Expected BinOp, got {:?}", updates.get("count"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|