feat(joinir): Phase 190-impl NumberAccumulation pattern implementation
Phase 190 implementation: Detect and emit number accumulation patterns
like `result = result * 10 + digit` in Pattern 2 loops.
## Changes
### Task 190-impl-1: UpdateRhs enum extension
- Added `NumberAccumulation { base, digit_var }` variant to UpdateRhs
- Implemented detection logic in `analyze_update_value()`:
- Detects pattern: `(carrier * base) + digit`
- Supports both Add and Subtract operations
- Base must be integer constant, digit must be variable
- Added 3 unit tests (base10, base2, wrong_lhs cases)
### Task 190-impl-2: Pattern2/4 whitelist update
- Updated `check_carrier_updates_allowed()` in common_init.rs
- NumberAccumulation now allowed in can_lower()
- Pattern 4 (continue) rejects with passthrough (not yet implemented)
### Task 190-impl-3: Carrier update emission
- Implemented NumberAccumulation emission in carrier_update_emitter.rs
- Emits 3 instructions:
1. Const(base)
2. BinOp(Mul, carrier, base) → tmp
3. BinOp(Add/Sub, tmp, digit) → result
- Added 2 unit tests (base10 emission, digit_not_found error)
- Both UpdateEnv and ConditionEnv versions supported
### Task 190-impl-4: E2E tests (in progress)
- Created phase190_atoi_impl.hako (Pattern 2 with break)
- Created phase190_parse_number_impl.hako (Pattern 2 with break)
- Tests compile and use Pattern 2 correctly
- Runtime execution validation pending
## Files Modified
- loop_update_analyzer.rs (+180 lines: enum, detection, 3 tests)
- carrier_update_emitter.rs (+182 lines: emission, 2 tests)
- common_init.rs (+4 lines: whitelist update)
- loop_with_continue_minimal.rs (+16 lines: Pattern 4 passthrough)
## Test Results
- ✅ All analyzer unit tests pass (4/4)
- ✅ All emitter unit tests pass (12/12)
- 🔄 E2E runtime validation in progress
## Architecture Notes
- **Box-first modular design**: Single responsibility per function
- **Fail-fast**: Complex patterns rejected early in can_lower()
- **Pattern 2 only**: Pattern 1/3 don't support carriers yet
- **Pattern 4 future**: Passthrough stub for continue support
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
22
apps/tests/phase190_atoi_impl.hako
Normal file
22
apps/tests/phase190_atoi_impl.hako
Normal file
@ -0,0 +1,22 @@
|
||||
// Phase 190: Number accumulation test - atoi implementation
|
||||
// Tests: result = result * 10 + digit pattern
|
||||
// Uses Pattern 2 (break) to enable carrier support
|
||||
|
||||
static box AtoiImpl {
|
||||
method main() {
|
||||
local result
|
||||
result = 0
|
||||
local i
|
||||
i = 0
|
||||
loop(i < 10) {
|
||||
if i >= 3 {
|
||||
break
|
||||
}
|
||||
local digit
|
||||
digit = i
|
||||
result = result * 10 + digit
|
||||
i = i + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
20
apps/tests/phase190_parse_number_impl.hako
Normal file
20
apps/tests/phase190_parse_number_impl.hako
Normal file
@ -0,0 +1,20 @@
|
||||
// Phase 190: Number accumulation test - parse number implementation
|
||||
// Tests: num = num * 10 + i pattern with different initial values
|
||||
// Uses Pattern 2 (break) to enable carrier support
|
||||
|
||||
static box ParseNumberImpl {
|
||||
method main() {
|
||||
local num
|
||||
num = 0
|
||||
local i
|
||||
i = 1
|
||||
loop(i < 10) {
|
||||
if i > 3 {
|
||||
break
|
||||
}
|
||||
num = num * 10 + i
|
||||
i = i + 1
|
||||
}
|
||||
print(num)
|
||||
}
|
||||
}
|
||||
@ -178,7 +178,8 @@ impl CommonPatternInitializer {
|
||||
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(body, &dummy_carriers);
|
||||
|
||||
// Phase 188: Check if any update is complex (reject only UpdateRhs::Other)
|
||||
// Allow: Const (numeric), Variable (numeric/string), StringLiteral
|
||||
// Phase 190: Allow NumberAccumulation pattern
|
||||
// Allow: Const (numeric), Variable (numeric/string), StringLiteral, NumberAccumulation
|
||||
// Reject: Other (method calls, nested BinOp)
|
||||
for update in updates.values() {
|
||||
if let UpdateExpr::BinOp { rhs, .. } = update {
|
||||
@ -193,6 +194,9 @@ impl CommonPatternInitializer {
|
||||
UpdateRhs::StringLiteral(_) => {
|
||||
// Phase 188: StringAppendLiteral: s = s + "x" (allowed)
|
||||
}
|
||||
UpdateRhs::NumberAccumulation { .. } => {
|
||||
// Phase 190: Number accumulation: result = result * 10 + digit (allowed)
|
||||
}
|
||||
UpdateRhs::Other => {
|
||||
// Phase 188: Complex update (method call, nested BinOp) - reject
|
||||
eprintln!("[common_init/check_carriers] Phase 188: Complex update detected (UpdateRhs::Other), rejecting pattern");
|
||||
|
||||
@ -134,6 +134,52 @@ pub fn emit_carrier_update_with_env(
|
||||
}));
|
||||
const_id
|
||||
}
|
||||
// Phase 190: Number accumulation pattern: result = result * base + digit
|
||||
// Emit as: tmp = carrier * base; result = tmp + digit
|
||||
UpdateRhs::NumberAccumulation { base, digit_var } => {
|
||||
// Step 1: Emit const for base
|
||||
let base_id = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: base_id,
|
||||
value: ConstValue::Integer(*base),
|
||||
}));
|
||||
|
||||
// Step 2: Emit multiplication: tmp = carrier * base
|
||||
let tmp_id = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: tmp_id,
|
||||
op: BinOpKind::Mul,
|
||||
lhs: carrier_param,
|
||||
rhs: base_id,
|
||||
}));
|
||||
|
||||
// Step 3: Resolve digit variable
|
||||
let digit_id = env.resolve(digit_var).ok_or_else(|| {
|
||||
format!(
|
||||
"Number accumulation digit variable '{}' not found in UpdateEnv",
|
||||
digit_var
|
||||
)
|
||||
})?;
|
||||
|
||||
// Step 4: Emit addition: result = tmp + digit
|
||||
// This will be handled by the outer BinOp emission
|
||||
// For now, return digit_id to be used as RHS
|
||||
// We need to handle this specially - return tmp_id instead
|
||||
// and adjust the outer BinOp to use correct values
|
||||
|
||||
// Actually, we need to emit both operations here
|
||||
// Final result = tmp + digit
|
||||
let result = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: result,
|
||||
op: *op, // Use the operation from outer UpdateExpr
|
||||
lhs: tmp_id,
|
||||
rhs: digit_id,
|
||||
}));
|
||||
|
||||
// Return result directly - we've already emitted everything
|
||||
return Ok(result);
|
||||
}
|
||||
// Phase 178/188: Complex updates (method calls) still rejected
|
||||
UpdateRhs::Other => {
|
||||
return Err(format!(
|
||||
@ -276,6 +322,45 @@ pub fn emit_carrier_update(
|
||||
}));
|
||||
const_id
|
||||
}
|
||||
// Phase 190: Number accumulation pattern: result = result * base + digit
|
||||
// Emit as: tmp = carrier * base; result = tmp + digit
|
||||
UpdateRhs::NumberAccumulation { base, digit_var } => {
|
||||
// Step 1: Emit const for base
|
||||
let base_id = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: base_id,
|
||||
value: ConstValue::Integer(*base),
|
||||
}));
|
||||
|
||||
// Step 2: Emit multiplication: tmp = carrier * base
|
||||
let tmp_id = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: tmp_id,
|
||||
op: BinOpKind::Mul,
|
||||
lhs: carrier_param,
|
||||
rhs: base_id,
|
||||
}));
|
||||
|
||||
// Step 3: Resolve digit variable
|
||||
let digit_id = env.get(digit_var).ok_or_else(|| {
|
||||
format!(
|
||||
"Number accumulation digit variable '{}' not found in ConditionEnv",
|
||||
digit_var
|
||||
)
|
||||
})?;
|
||||
|
||||
// Step 4: Emit addition: result = tmp + digit
|
||||
let result = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: result,
|
||||
op: *op, // Use the operation from outer UpdateExpr
|
||||
lhs: tmp_id,
|
||||
rhs: digit_id,
|
||||
}));
|
||||
|
||||
// Return result directly - we've already emitted everything
|
||||
return Ok(result);
|
||||
}
|
||||
// Phase 178/188: Complex updates (method calls) still rejected
|
||||
UpdateRhs::Other => {
|
||||
return Err(format!(
|
||||
@ -743,4 +828,124 @@ mod tests {
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(instructions.len(), 2); // Const + BinOp
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_number_accumulation_base10() {
|
||||
// Phase 190: Test number accumulation pattern: result = result * 10 + digit
|
||||
let mut cond_env = ConditionEnv::new();
|
||||
cond_env.insert("result".to_string(), ValueId(20)); // Carrier parameter
|
||||
cond_env.insert("digit".to_string(), ValueId(30)); // Digit variable
|
||||
|
||||
let body_env = LoopBodyLocalEnv::new();
|
||||
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||
|
||||
let carrier = test_carrier("result", 200);
|
||||
let update = UpdateExpr::BinOp {
|
||||
lhs: "result".to_string(),
|
||||
op: BinOpKind::Add,
|
||||
rhs: UpdateRhs::NumberAccumulation {
|
||||
base: 10,
|
||||
digit_var: "digit".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let mut value_counter = 150u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update_with_env(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&update_env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let result_id = result.unwrap();
|
||||
|
||||
// Should generate 3 instructions:
|
||||
// 1. Const(10) for base
|
||||
// 2. BinOp(Mul, result, base) for tmp
|
||||
// 3. BinOp(Add, tmp, digit) for final result
|
||||
assert_eq!(instructions.len(), 3);
|
||||
|
||||
// Instruction 1: Const(10)
|
||||
match &instructions[0] {
|
||||
JoinInst::Compute(MirLikeInst::Const { dst, value }) => {
|
||||
assert_eq!(*dst, ValueId(150)); // First allocated
|
||||
assert!(matches!(value, ConstValue::Integer(10)));
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
|
||||
// Instruction 2: BinOp(Mul, result, base)
|
||||
match &instructions[1] {
|
||||
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||
assert_eq!(*dst, ValueId(151)); // Second allocated (tmp)
|
||||
assert_eq!(*op, BinOpKind::Mul);
|
||||
assert_eq!(*lhs, ValueId(20)); // result from env
|
||||
assert_eq!(*rhs, ValueId(150)); // base const
|
||||
}
|
||||
_ => panic!("Expected BinOp(Mul) instruction"),
|
||||
}
|
||||
|
||||
// Instruction 3: BinOp(Add, tmp, digit)
|
||||
match &instructions[2] {
|
||||
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||
assert_eq!(*dst, ValueId(152)); // Third allocated (final result)
|
||||
assert_eq!(*op, BinOpKind::Add);
|
||||
assert_eq!(*lhs, ValueId(151)); // tmp from previous mul
|
||||
assert_eq!(*rhs, ValueId(30)); // digit from env
|
||||
}
|
||||
_ => panic!("Expected BinOp(Add) instruction"),
|
||||
}
|
||||
|
||||
assert_eq!(result_id, ValueId(152));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emit_number_accumulation_digit_not_found() {
|
||||
// Phase 190: Test error when digit variable not in env
|
||||
let mut cond_env = ConditionEnv::new();
|
||||
cond_env.insert("result".to_string(), ValueId(20));
|
||||
// Note: digit NOT in env
|
||||
|
||||
let body_env = LoopBodyLocalEnv::new();
|
||||
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||
|
||||
let carrier = test_carrier("result", 200);
|
||||
let update = UpdateExpr::BinOp {
|
||||
lhs: "result".to_string(),
|
||||
op: BinOpKind::Add,
|
||||
rhs: UpdateRhs::NumberAccumulation {
|
||||
base: 10,
|
||||
digit_var: "digit".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let mut value_counter = 160u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
let result = emit_carrier_update_with_env(
|
||||
&carrier,
|
||||
&update,
|
||||
&mut alloc_value,
|
||||
&update_env,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(err.contains("Number accumulation digit variable 'digit' not found"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ pub enum UpdateExpr {
|
||||
/// Right-hand side of update expression
|
||||
///
|
||||
/// Phase 178: Extended to detect string updates for multi-carrier loops.
|
||||
/// Phase 190: Extended to detect number accumulation pattern (result = result * base + digit).
|
||||
/// The goal is "carrier detection", not full semantic understanding.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UpdateRhs {
|
||||
@ -49,6 +50,14 @@ pub enum UpdateRhs {
|
||||
Variable(String),
|
||||
/// Phase 178: String literal: result + "x"
|
||||
StringLiteral(String),
|
||||
/// Phase 190: Number accumulation pattern: result = result * base + digit
|
||||
/// Represents expressions like: result * 10 + digit
|
||||
NumberAccumulation {
|
||||
/// Multiplication base (e.g., 10 for decimal, 2 for binary)
|
||||
base: i64,
|
||||
/// Variable name containing the digit to add
|
||||
digit_var: String,
|
||||
},
|
||||
/// Phase 178: Other expression (method call, complex expr)
|
||||
/// Used to detect "carrier has an update" without understanding semantics
|
||||
Other,
|
||||
@ -136,6 +145,7 @@ impl LoopUpdateAnalyzer {
|
||||
/// Recognizes patterns like:
|
||||
/// - `sum + i` → BinOp { lhs: "sum", op: Add, rhs: Variable("i") }
|
||||
/// - `count + 1` → BinOp { lhs: "count", op: Add, rhs: Const(1) }
|
||||
/// - Phase 190: `result * 10 + digit` → BinOp { lhs: "result", op: Add, rhs: NumberAccumulation {...} }
|
||||
fn analyze_update_value(carrier_name: &str, value: &ASTNode) -> Option<UpdateExpr> {
|
||||
match value {
|
||||
ASTNode::BinaryOp {
|
||||
@ -144,6 +154,43 @@ impl LoopUpdateAnalyzer {
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
// Phase 190: Check for number accumulation pattern first
|
||||
// Pattern: (carrier * base) + digit
|
||||
if matches!(operator, BinaryOperator::Add | BinaryOperator::Subtract) {
|
||||
if let ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Multiply,
|
||||
left: mul_left,
|
||||
right: mul_right,
|
||||
..
|
||||
} = left.as_ref()
|
||||
{
|
||||
// Check if multiplication is: carrier * base
|
||||
if let Some(mul_lhs_name) = Self::extract_variable_name(mul_left) {
|
||||
if mul_lhs_name == carrier_name {
|
||||
if let ASTNode::Literal {
|
||||
value: LiteralValue::Integer(base),
|
||||
..
|
||||
} = mul_right.as_ref()
|
||||
{
|
||||
// Check if RHS is a variable (digit)
|
||||
if let Some(digit_var) = Self::extract_variable_name(right) {
|
||||
// This is number accumulation pattern!
|
||||
let op = Self::convert_operator(operator)?;
|
||||
return Some(UpdateExpr::BinOp {
|
||||
lhs: carrier_name.to_string(),
|
||||
op,
|
||||
rhs: UpdateRhs::NumberAccumulation {
|
||||
base: *base,
|
||||
digit_var,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -169,6 +216,7 @@ impl LoopUpdateAnalyzer {
|
||||
/// Analyze right-hand side of update expression
|
||||
///
|
||||
/// Phase 178: Extended to detect string updates.
|
||||
/// Phase 190: Extended to detect number accumulation pattern.
|
||||
/// Goal: "carrier has update" detection, not semantic understanding.
|
||||
fn analyze_rhs(node: &ASTNode) -> Option<UpdateRhs> {
|
||||
match node {
|
||||
@ -189,11 +237,16 @@ impl LoopUpdateAnalyzer {
|
||||
Some(UpdateRhs::Variable(name.clone()))
|
||||
}
|
||||
|
||||
// Phase 190: Number accumulation pattern detection
|
||||
// This is called from analyze_update_value, so we're analyzing the full RHS of an assignment
|
||||
// Pattern not detected at this level - handled in analyze_update_value
|
||||
// BinaryOp nodes that aren't simple Add/Sub are treated as complex
|
||||
ASTNode::BinaryOp { .. } => Some(UpdateRhs::Other),
|
||||
|
||||
// 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),
|
||||
|
||||
_ => None,
|
||||
@ -264,4 +317,165 @@ mod tests {
|
||||
panic!("Expected BinOp, got {:?}", updates.get("count"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_analyze_number_accumulation_base10() {
|
||||
// Test case: result = result * 10 + digit
|
||||
use crate::ast::Span;
|
||||
|
||||
let body = vec![ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "result".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Multiply,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "result".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Variable {
|
||||
name: "digit".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
let carriers = vec![CarrierVar {
|
||||
name: "result".to_string(),
|
||||
host_id: crate::mir::ValueId(0),
|
||||
join_id: None,
|
||||
}];
|
||||
|
||||
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
|
||||
|
||||
assert_eq!(updates.len(), 1);
|
||||
assert!(updates.contains_key("result"));
|
||||
|
||||
if let Some(UpdateExpr::BinOp { lhs, op, rhs }) = updates.get("result") {
|
||||
assert_eq!(lhs, "result");
|
||||
assert_eq!(*op, BinOpKind::Add);
|
||||
if let UpdateRhs::NumberAccumulation { base, digit_var } = rhs {
|
||||
assert_eq!(*base, 10);
|
||||
assert_eq!(digit_var, "digit");
|
||||
} else {
|
||||
panic!("Expected NumberAccumulation, got {:?}", rhs);
|
||||
}
|
||||
} else {
|
||||
panic!("Expected BinOp, got {:?}", updates.get("result"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_analyze_number_accumulation_base2() {
|
||||
// Test case: result = result * 2 + bit
|
||||
use crate::ast::Span;
|
||||
|
||||
let body = vec![ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "result".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Multiply,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "result".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(2),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Variable {
|
||||
name: "bit".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
let carriers = vec![CarrierVar {
|
||||
name: "result".to_string(),
|
||||
host_id: crate::mir::ValueId(0),
|
||||
join_id: None,
|
||||
}];
|
||||
|
||||
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
|
||||
|
||||
assert_eq!(updates.len(), 1);
|
||||
assert!(updates.contains_key("result"));
|
||||
|
||||
if let Some(UpdateExpr::BinOp { lhs, op, rhs }) = updates.get("result") {
|
||||
assert_eq!(lhs, "result");
|
||||
assert_eq!(*op, BinOpKind::Add);
|
||||
if let UpdateRhs::NumberAccumulation { base, digit_var } = rhs {
|
||||
assert_eq!(*base, 2);
|
||||
assert_eq!(digit_var, "bit");
|
||||
} else {
|
||||
panic!("Expected NumberAccumulation, got {:?}", rhs);
|
||||
}
|
||||
} else {
|
||||
panic!("Expected BinOp, got {:?}", updates.get("result"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_analyze_number_accumulation_wrong_lhs() {
|
||||
// Test case: result = other * 10 + digit (should be Complex/Other)
|
||||
use crate::ast::Span;
|
||||
|
||||
let body = vec![ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "result".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Multiply,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "other".to_string(), // Wrong variable!
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Variable {
|
||||
name: "digit".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
let carriers = vec![CarrierVar {
|
||||
name: "result".to_string(),
|
||||
host_id: crate::mir::ValueId(0),
|
||||
join_id: None,
|
||||
}];
|
||||
|
||||
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(&body, &carriers);
|
||||
|
||||
// Should detect assignment but with Other (complex) RHS
|
||||
assert_eq!(updates.len(), 0); // Won't match because lhs != carrier
|
||||
}
|
||||
}
|
||||
|
||||
@ -387,6 +387,24 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
||||
const_1
|
||||
}
|
||||
}
|
||||
// Phase 190: Number accumulation not supported in Pattern 4 yet
|
||||
// Skip JoinIR update - use Select passthrough to keep carrier_merged defined
|
||||
UpdateRhs::NumberAccumulation { .. } => {
|
||||
eprintln!(
|
||||
"[loop_with_continue_minimal] Phase 190: Carrier '{}' has number accumulation - not supported in Pattern 4, using Select passthrough",
|
||||
carrier_name
|
||||
);
|
||||
// Emit Select with same values: carrier_merged = Select(_, carrier_param, carrier_param)
|
||||
// This is effectively a passthrough (no JoinIR update)
|
||||
loop_step_func.body.push(JoinInst::Select {
|
||||
dst: carrier_merged,
|
||||
cond: continue_cond, // Condition doesn't matter when both values are same
|
||||
then_val: carrier_param,
|
||||
else_val: carrier_param,
|
||||
type_hint: None,
|
||||
});
|
||||
continue; // Skip the BinOp and normal Select below
|
||||
}
|
||||
// Phase 178: String updates detected but not lowered to JoinIR yet
|
||||
// Skip JoinIR update - use Select passthrough to keep carrier_merged defined
|
||||
UpdateRhs::StringLiteral(_) | UpdateRhs::Other => {
|
||||
|
||||
Reference in New Issue
Block a user