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);
|
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(body, &dummy_carriers);
|
||||||
|
|
||||||
// Phase 188: Check if any update is complex (reject only UpdateRhs::Other)
|
// 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)
|
// Reject: Other (method calls, nested BinOp)
|
||||||
for update in updates.values() {
|
for update in updates.values() {
|
||||||
if let UpdateExpr::BinOp { rhs, .. } = update {
|
if let UpdateExpr::BinOp { rhs, .. } = update {
|
||||||
@ -193,6 +194,9 @@ impl CommonPatternInitializer {
|
|||||||
UpdateRhs::StringLiteral(_) => {
|
UpdateRhs::StringLiteral(_) => {
|
||||||
// Phase 188: StringAppendLiteral: s = s + "x" (allowed)
|
// Phase 188: StringAppendLiteral: s = s + "x" (allowed)
|
||||||
}
|
}
|
||||||
|
UpdateRhs::NumberAccumulation { .. } => {
|
||||||
|
// Phase 190: Number accumulation: result = result * 10 + digit (allowed)
|
||||||
|
}
|
||||||
UpdateRhs::Other => {
|
UpdateRhs::Other => {
|
||||||
// Phase 188: Complex update (method call, nested BinOp) - reject
|
// Phase 188: Complex update (method call, nested BinOp) - reject
|
||||||
eprintln!("[common_init/check_carriers] Phase 188: Complex update detected (UpdateRhs::Other), rejecting pattern");
|
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
|
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
|
// Phase 178/188: Complex updates (method calls) still rejected
|
||||||
UpdateRhs::Other => {
|
UpdateRhs::Other => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
@ -276,6 +322,45 @@ pub fn emit_carrier_update(
|
|||||||
}));
|
}));
|
||||||
const_id
|
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
|
// Phase 178/188: Complex updates (method calls) still rejected
|
||||||
UpdateRhs::Other => {
|
UpdateRhs::Other => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
@ -743,4 +828,124 @@ mod tests {
|
|||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(instructions.len(), 2); // Const + BinOp
|
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
|
/// Right-hand side of update expression
|
||||||
///
|
///
|
||||||
/// Phase 178: Extended to detect string updates for multi-carrier loops.
|
/// 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.
|
/// The goal is "carrier detection", not full semantic understanding.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum UpdateRhs {
|
pub enum UpdateRhs {
|
||||||
@ -49,6 +50,14 @@ pub enum UpdateRhs {
|
|||||||
Variable(String),
|
Variable(String),
|
||||||
/// Phase 178: String literal: result + "x"
|
/// Phase 178: String literal: result + "x"
|
||||||
StringLiteral(String),
|
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)
|
/// Phase 178: Other expression (method call, complex expr)
|
||||||
/// Used to detect "carrier has an update" without understanding semantics
|
/// Used to detect "carrier has an update" without understanding semantics
|
||||||
Other,
|
Other,
|
||||||
@ -136,6 +145,7 @@ impl LoopUpdateAnalyzer {
|
|||||||
/// Recognizes patterns like:
|
/// Recognizes patterns like:
|
||||||
/// - `sum + i` → BinOp { lhs: "sum", op: Add, rhs: Variable("i") }
|
/// - `sum + i` → BinOp { lhs: "sum", op: Add, rhs: Variable("i") }
|
||||||
/// - `count + 1` → BinOp { lhs: "count", op: Add, rhs: Const(1) }
|
/// - `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> {
|
fn analyze_update_value(carrier_name: &str, value: &ASTNode) -> Option<UpdateExpr> {
|
||||||
match value {
|
match value {
|
||||||
ASTNode::BinaryOp {
|
ASTNode::BinaryOp {
|
||||||
@ -144,6 +154,43 @@ impl LoopUpdateAnalyzer {
|
|||||||
right,
|
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")
|
// Check if LHS is the carrier itself (e.g., sum in "sum + i")
|
||||||
if let Some(lhs_name) = Self::extract_variable_name(left) {
|
if let Some(lhs_name) = Self::extract_variable_name(left) {
|
||||||
if lhs_name == carrier_name {
|
if lhs_name == carrier_name {
|
||||||
@ -169,6 +216,7 @@ impl LoopUpdateAnalyzer {
|
|||||||
/// Analyze right-hand side of update expression
|
/// Analyze right-hand side of update expression
|
||||||
///
|
///
|
||||||
/// Phase 178: Extended to detect string updates.
|
/// Phase 178: Extended to detect string updates.
|
||||||
|
/// Phase 190: Extended to detect number accumulation pattern.
|
||||||
/// Goal: "carrier has update" detection, not semantic understanding.
|
/// Goal: "carrier has update" detection, not semantic understanding.
|
||||||
fn analyze_rhs(node: &ASTNode) -> Option<UpdateRhs> {
|
fn analyze_rhs(node: &ASTNode) -> Option<UpdateRhs> {
|
||||||
match node {
|
match node {
|
||||||
@ -189,11 +237,16 @@ impl LoopUpdateAnalyzer {
|
|||||||
Some(UpdateRhs::Variable(name.clone()))
|
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
|
// Phase 178: Method call or other complex expression
|
||||||
// e.g., result + s.substring(pos, end)
|
// e.g., result + s.substring(pos, end)
|
||||||
ASTNode::Call { .. }
|
ASTNode::Call { .. }
|
||||||
| ASTNode::MethodCall { .. }
|
| ASTNode::MethodCall { .. }
|
||||||
| ASTNode::BinaryOp { .. }
|
|
||||||
| ASTNode::UnaryOp { .. } => Some(UpdateRhs::Other),
|
| ASTNode::UnaryOp { .. } => Some(UpdateRhs::Other),
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -264,4 +317,165 @@ mod tests {
|
|||||||
panic!("Expected BinOp, got {:?}", updates.get("count"));
|
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
|
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
|
// Phase 178: String updates detected but not lowered to JoinIR yet
|
||||||
// Skip JoinIR update - use Select passthrough to keep carrier_merged defined
|
// Skip JoinIR update - use Select passthrough to keep carrier_merged defined
|
||||||
UpdateRhs::StringLiteral(_) | UpdateRhs::Other => {
|
UpdateRhs::StringLiteral(_) | UpdateRhs::Other => {
|
||||||
|
|||||||
Reference in New Issue
Block a user