feat(normalization): Phase 252 P1 - DebugOutputBox unification + this.methodcall tests
**P1-1: Local Refactor**: - loop_with_if_phi_if_sum.rs: Replace 8 eprintln! with DebugOutputBox - Default output: 0 lines (clean) - With NYASH_JOINIR_DEBUG=1: Rich trace output **P1-2: Unit Tests** (3/3 PASS): - test_this_methodcall_in_condition: BoxCall generation - test_this_methodcall_requires_context: Static box requirement - test_this_methodcall_disallowed_method: Method whitelist enforcement **P1-3: v2 Smoke Fixture**: - phase252_p0_this_methodcall_break_cond_min.hako - StringUtils.count_leading_digits with this.is_digit break condition - VM + LLVM integration test scripts **P1-4: Test Fixes**: - condition_lowering_box.rs: current_static_box_name: None - loop_with_break_minimal/tests.rs: current_static_box_name: None 🧪 Tests: - cargo test condition_lowerer: 3 new tests PASS - cargo check --lib: PASS (0 errors) 📊 Quick Profile Status: - json_pp_vm: ✅ PASS - json_lint_vm: ❌ FAIL (deferred to Phase 253) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
28
apps/tests/phase252_p0_this_methodcall_break_cond_min.hako
Normal file
28
apps/tests/phase252_p0_this_methodcall_break_cond_min.hako
Normal file
@ -0,0 +1,28 @@
|
||||
// Phase 252 P0: this.methodcall in break condition
|
||||
// Minimal test fixture - simplified to avoid '-' operator (Phase 253 issue)
|
||||
|
||||
static box StringUtils {
|
||||
is_digit(ch) {
|
||||
return ch >= "0"
|
||||
}
|
||||
|
||||
count_leading_digits(s) {
|
||||
local i = 0
|
||||
local len = s.length()
|
||||
loop(i < len) {
|
||||
local ch = s.substring(i, i + 1)
|
||||
if not this.is_digit(ch) {
|
||||
break
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
local result = StringUtils.count_leading_digits("123abc")
|
||||
return result
|
||||
}
|
||||
}
|
||||
@ -50,6 +50,12 @@ use super::method_call_lowerer::MethodCallLowerer;
|
||||
/// 1. ConditionEnv (loop parameters, captured variables)
|
||||
/// 2. LoopBodyLocalEnv (body-local variables like `ch`)
|
||||
///
|
||||
/// # Phase 252: This-Method Support
|
||||
///
|
||||
/// When lowering conditions in static box methods (e.g., `StringUtils.trim_end/1`),
|
||||
/// the `current_static_box_name` parameter enables `this.method(...)` calls to be
|
||||
/// resolved to the appropriate static box method.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
@ -73,6 +79,7 @@ use super::method_call_lowerer::MethodCallLowerer;
|
||||
/// &mut alloc_value,
|
||||
/// &env,
|
||||
/// Some(&body_env), // Phase 92 P2-2: Body-local support
|
||||
/// Some("StringUtils"), // Phase 252: Static box name for this.method
|
||||
/// )?;
|
||||
/// ```
|
||||
pub fn lower_condition_to_joinir(
|
||||
@ -80,19 +87,27 @@ pub fn lower_condition_to_joinir(
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
|
||||
current_static_box_name: Option<&str>, // Phase 252
|
||||
) -> Result<(ValueId, Vec<JoinInst>), String> {
|
||||
let mut instructions = Vec::new();
|
||||
let result_value = lower_condition_recursive(cond_ast, alloc_value, env, body_local_env, &mut instructions)?;
|
||||
let result_value = lower_condition_recursive(
|
||||
cond_ast,
|
||||
alloc_value,
|
||||
env,
|
||||
body_local_env,
|
||||
current_static_box_name,
|
||||
&mut instructions,
|
||||
)?;
|
||||
Ok((result_value, instructions))
|
||||
}
|
||||
|
||||
/// Convenience wrapper: lower a condition without body-local support.
|
||||
/// Convenience wrapper: lower a condition without body-local or static box support.
|
||||
pub fn lower_condition_to_joinir_no_body_locals(
|
||||
cond_ast: &ASTNode,
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
) -> Result<(ValueId, Vec<JoinInst>), String> {
|
||||
lower_condition_to_joinir(cond_ast, alloc_value, env, None)
|
||||
lower_condition_to_joinir(cond_ast, alloc_value, env, None, None)
|
||||
}
|
||||
|
||||
/// Recursive helper for condition lowering
|
||||
@ -102,11 +117,17 @@ pub fn lower_condition_to_joinir_no_body_locals(
|
||||
/// # Phase 92 P2-2
|
||||
///
|
||||
/// Added `body_local_env` parameter to support body-local variable resolution.
|
||||
///
|
||||
/// # Phase 252
|
||||
///
|
||||
/// Added `current_static_box_name` parameter to support `this.method(...)` calls
|
||||
/// in static box method conditions.
|
||||
fn lower_condition_recursive(
|
||||
cond_ast: &ASTNode,
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
|
||||
current_static_box_name: Option<&str>, // Phase 252
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
match cond_ast {
|
||||
@ -123,10 +144,10 @@ fn lower_condition_recursive(
|
||||
| BinaryOperator::LessEqual
|
||||
| BinaryOperator::GreaterEqual
|
||||
| BinaryOperator::Greater => {
|
||||
lower_comparison(operator, left, right, alloc_value, env, body_local_env, instructions)
|
||||
lower_comparison(operator, left, right, alloc_value, env, body_local_env, current_static_box_name, instructions)
|
||||
}
|
||||
BinaryOperator::And => lower_logical_and(left, right, alloc_value, env, body_local_env, instructions),
|
||||
BinaryOperator::Or => lower_logical_or(left, right, alloc_value, env, body_local_env, instructions),
|
||||
BinaryOperator::And => lower_logical_and(left, right, alloc_value, env, body_local_env, current_static_box_name, instructions),
|
||||
BinaryOperator::Or => lower_logical_or(left, right, alloc_value, env, body_local_env, current_static_box_name, instructions),
|
||||
_ => Err(format!(
|
||||
"Unsupported binary operator in condition: {:?}",
|
||||
operator
|
||||
@ -138,7 +159,7 @@ fn lower_condition_recursive(
|
||||
operator: UnaryOperator::Not,
|
||||
operand,
|
||||
..
|
||||
} => lower_not_operator(operand, alloc_value, env, body_local_env, instructions),
|
||||
} => lower_not_operator(operand, alloc_value, env, body_local_env, current_static_box_name, instructions),
|
||||
|
||||
// Phase 92 P2-2: Variables - resolve from ConditionEnv or LoopBodyLocalEnv
|
||||
ASTNode::Variable { name, .. } => {
|
||||
@ -158,6 +179,69 @@ fn lower_condition_recursive(
|
||||
// Literals - emit as constants
|
||||
ASTNode::Literal { value, .. } => lower_literal(value, alloc_value, instructions),
|
||||
|
||||
// Phase 252: MethodCall support (this.method or builtin methods)
|
||||
ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} => {
|
||||
// Check if this is a this.method(...) call
|
||||
match object.as_ref() {
|
||||
ASTNode::Me { .. } => {
|
||||
// this.method(...) - requires current_static_box_name
|
||||
let box_name = current_static_box_name.ok_or_else(|| {
|
||||
format!(
|
||||
"this.{}(...) requires current_static_box_name (not in static box context)",
|
||||
method
|
||||
)
|
||||
})?;
|
||||
|
||||
// Check if method is allowed in condition context via UserMethodPolicy
|
||||
if !super::user_method_policy::UserMethodPolicy::allowed_in_condition(box_name, method) {
|
||||
return Err(format!(
|
||||
"User-defined method not allowed in loop condition: {}.{}() (not whitelisted)",
|
||||
box_name, method
|
||||
));
|
||||
}
|
||||
|
||||
// Lower arguments using lower_for_init whitelist
|
||||
// (Arguments are value expressions, not conditions, so we use init whitelist)
|
||||
let mut arg_vals = Vec::new();
|
||||
for arg_ast in arguments {
|
||||
let arg_val = lower_value_expression(
|
||||
arg_ast,
|
||||
alloc_value,
|
||||
env,
|
||||
body_local_env,
|
||||
current_static_box_name,
|
||||
instructions,
|
||||
)?;
|
||||
arg_vals.push(arg_val);
|
||||
}
|
||||
|
||||
// Emit BoxCall instruction
|
||||
let dst = alloc_value();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(dst),
|
||||
box_name: box_name.to_string(),
|
||||
method: method.clone(),
|
||||
args: arg_vals,
|
||||
}));
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
_ => {
|
||||
// Not this.method - treat as value expression (builtin methods via CoreMethodId)
|
||||
lower_value_expression(object, alloc_value, env, body_local_env, current_static_box_name, instructions)?;
|
||||
Err(format!(
|
||||
"MethodCall on non-this object not yet supported in condition: {:?}",
|
||||
cond_ast
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => Err(format!("Unsupported AST node in condition: {:?}", cond_ast)),
|
||||
}
|
||||
}
|
||||
@ -170,11 +254,12 @@ fn lower_comparison(
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
|
||||
current_static_box_name: Option<&str>, // Phase 252
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Lower left and right sides
|
||||
let lhs = lower_value_expression(left, alloc_value, env, body_local_env, instructions)?;
|
||||
let rhs = lower_value_expression(right, alloc_value, env, body_local_env, instructions)?;
|
||||
let lhs = lower_value_expression(left, alloc_value, env, body_local_env, current_static_box_name, instructions)?;
|
||||
let rhs = lower_value_expression(right, alloc_value, env, body_local_env, current_static_box_name, instructions)?;
|
||||
let dst = alloc_value();
|
||||
|
||||
let cmp_op = match operator {
|
||||
@ -205,11 +290,12 @@ fn lower_logical_and(
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
|
||||
current_static_box_name: Option<&str>, // Phase 252
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Logical AND: evaluate both sides and combine
|
||||
let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, instructions)?;
|
||||
let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, instructions)?;
|
||||
let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, current_static_box_name, instructions)?;
|
||||
let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, current_static_box_name, instructions)?;
|
||||
let dst = alloc_value();
|
||||
|
||||
// Emit BinOp And instruction
|
||||
@ -230,11 +316,12 @@ fn lower_logical_or(
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
|
||||
current_static_box_name: Option<&str>, // Phase 252
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Logical OR: evaluate both sides and combine
|
||||
let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, instructions)?;
|
||||
let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, instructions)?;
|
||||
let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, current_static_box_name, instructions)?;
|
||||
let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, current_static_box_name, instructions)?;
|
||||
let dst = alloc_value();
|
||||
|
||||
// Emit BinOp Or instruction
|
||||
@ -254,9 +341,10 @@ fn lower_not_operator(
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
|
||||
current_static_box_name: Option<&str>, // Phase 252
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
let operand_val = lower_condition_recursive(operand, alloc_value, env, body_local_env, instructions)?;
|
||||
let operand_val = lower_condition_recursive(operand, alloc_value, env, body_local_env, current_static_box_name, instructions)?;
|
||||
let dst = alloc_value();
|
||||
|
||||
// Emit UnaryOp Not instruction
|
||||
@ -308,11 +396,17 @@ fn lower_literal(
|
||||
///
|
||||
/// Added `body_local_env` parameter to support body-local variable resolution
|
||||
/// (e.g., `ch` in `ch == '\\'`).
|
||||
///
|
||||
/// # Phase 252
|
||||
///
|
||||
/// Added `current_static_box_name` parameter to support `this.method(...)` calls
|
||||
/// in argument expressions.
|
||||
pub fn lower_value_expression(
|
||||
expr: &ASTNode,
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
|
||||
current_static_box_name: Option<&str>, // Phase 252
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
match expr {
|
||||
@ -340,7 +434,7 @@ pub fn lower_value_expression(
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => lower_arithmetic_binop(operator, left, right, alloc_value, env, body_local_env, instructions),
|
||||
} => lower_arithmetic_binop(operator, left, right, alloc_value, env, body_local_env, current_static_box_name, instructions),
|
||||
|
||||
// Phase 224-C: MethodCall support with arguments (e.g., s.length(), s.indexOf(ch))
|
||||
ASTNode::MethodCall {
|
||||
@ -350,7 +444,7 @@ pub fn lower_value_expression(
|
||||
..
|
||||
} => {
|
||||
// 1. Lower receiver (object) to ValueId
|
||||
let recv_val = lower_value_expression(object, alloc_value, env, body_local_env, instructions)?;
|
||||
let recv_val = lower_value_expression(object, alloc_value, env, body_local_env, current_static_box_name, instructions)?;
|
||||
|
||||
// 2. Lower method call using MethodCallLowerer (will lower arguments internally)
|
||||
MethodCallLowerer::lower_for_condition(
|
||||
@ -378,10 +472,11 @@ fn lower_arithmetic_binop(
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &ConditionEnv,
|
||||
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
|
||||
current_static_box_name: Option<&str>, // Phase 252
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
let lhs = lower_value_expression(left, alloc_value, env, body_local_env, instructions)?;
|
||||
let rhs = lower_value_expression(right, alloc_value, env, body_local_env, instructions)?;
|
||||
let lhs = lower_value_expression(left, alloc_value, env, body_local_env, current_static_box_name, instructions)?;
|
||||
let rhs = lower_value_expression(right, alloc_value, env, body_local_env, current_static_box_name, instructions)?;
|
||||
let dst = alloc_value();
|
||||
|
||||
let bin_op = match operator {
|
||||
@ -616,7 +711,7 @@ mod tests {
|
||||
};
|
||||
|
||||
// Phase 92 P2-2: Use lower_condition_to_joinir with body_local_env
|
||||
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env));
|
||||
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env), None);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Body-local variable resolution should succeed"
|
||||
@ -678,7 +773,7 @@ mod tests {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env));
|
||||
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env), None);
|
||||
assert!(result.is_ok(), "Variable resolution should succeed");
|
||||
|
||||
let (_cond_value, instructions) = result.unwrap();
|
||||
@ -724,7 +819,7 @@ mod tests {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env));
|
||||
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env), None);
|
||||
assert!(result.is_err(), "Undefined variable should fail");
|
||||
|
||||
let err = result.unwrap_err();
|
||||
@ -737,4 +832,145 @@ mod tests {
|
||||
"Error message should indicate variable was not found"
|
||||
);
|
||||
}
|
||||
|
||||
/// Phase 252 P1: Test this.methodcall(...) in conditions
|
||||
///
|
||||
/// Verifies that user-defined static box method calls work in conditions
|
||||
#[test]
|
||||
fn test_this_methodcall_in_condition() {
|
||||
let env = create_test_env();
|
||||
let mut value_counter = 2u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
// AST: not this.is_whitespace(ch)
|
||||
// Simulates StringUtils.trim_end break condition
|
||||
let method_call = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Me {
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "is_whitespace".to_string(),
|
||||
arguments: vec![ASTNode::Variable {
|
||||
name: "ch".to_string(),
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let ast = ASTNode::UnaryOp {
|
||||
operator: crate::ast::UnaryOperator::Not,
|
||||
operand: Box::new(method_call),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
// Register 'ch' variable for the test
|
||||
let mut env = env;
|
||||
env.insert("ch".to_string(), ValueId(100));
|
||||
|
||||
let result = lower_condition_to_joinir(
|
||||
&ast,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
None,
|
||||
Some("StringUtils"), // Phase 252: static box context
|
||||
);
|
||||
|
||||
assert!(result.is_ok(), "this.methodcall should succeed: {:?}", result);
|
||||
|
||||
let (_cond_value, instructions) = result.unwrap();
|
||||
|
||||
// Should have: BoxCall for is_whitespace, UnaryOp(Not)
|
||||
assert!(
|
||||
instructions.len() >= 2,
|
||||
"Should generate BoxCall + Not instructions"
|
||||
);
|
||||
|
||||
// Verify BoxCall instruction exists
|
||||
let has_box_call = instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::BoxCall { method, .. }) if method == "is_whitespace"
|
||||
));
|
||||
assert!(
|
||||
has_box_call,
|
||||
"Should generate BoxCall for is_whitespace"
|
||||
);
|
||||
}
|
||||
|
||||
/// Phase 252 P1: Test this.methodcall fails without static box context
|
||||
#[test]
|
||||
fn test_this_methodcall_requires_context() {
|
||||
let env = create_test_env();
|
||||
let mut value_counter = 2u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
// AST: this.is_whitespace(ch)
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Me {
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "is_whitespace".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let result = lower_condition_to_joinir(
|
||||
&ast,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
None,
|
||||
None, // No static box context
|
||||
);
|
||||
|
||||
assert!(result.is_err(), "this.methodcall should fail without context");
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.contains("current_static_box_name"),
|
||||
"Error should mention missing static box context"
|
||||
);
|
||||
}
|
||||
|
||||
/// Phase 252 P1: Test disallowed method fails
|
||||
#[test]
|
||||
fn test_this_methodcall_disallowed_method() {
|
||||
let env = create_test_env();
|
||||
let mut value_counter = 2u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
// AST: this.trim("test") - trim is NOT allowed in conditions
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Me {
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "trim".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let result = lower_condition_to_joinir(
|
||||
&ast,
|
||||
&mut alloc_value,
|
||||
&env,
|
||||
None,
|
||||
Some("StringUtils"),
|
||||
);
|
||||
|
||||
assert!(result.is_err(), "Disallowed method should fail");
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.contains("not allowed") || err.contains("not whitelisted"),
|
||||
"Error should indicate method is not allowed: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ use crate::mir::ValueId;
|
||||
/// * `loop_var_id` - ValueId of the loop variable in JoinIR space
|
||||
/// * `scope` - Reference to ScopeManager for variable lookup
|
||||
/// * `alloc_value` - ValueId allocator function
|
||||
/// * `current_static_box_name` - Phase 252: Name of the static box being lowered (for this.method)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -39,6 +40,7 @@ use crate::mir::ValueId;
|
||||
/// loop_var_id: ValueId(1),
|
||||
/// scope: &scope_manager,
|
||||
/// alloc_value: &mut alloc_fn,
|
||||
/// current_static_box_name: Some("StringUtils".to_string()), // Phase 252
|
||||
/// };
|
||||
///
|
||||
/// let value_id = lowerer.lower_condition(&ast, &context)?;
|
||||
@ -55,6 +57,24 @@ pub struct ConditionContext<'a, S: ScopeManager> {
|
||||
|
||||
/// ValueId allocator function
|
||||
pub alloc_value: &'a mut dyn FnMut() -> ValueId,
|
||||
|
||||
/// Phase 252: Name of the static box being lowered (for this.method(...) support)
|
||||
///
|
||||
/// When lowering a static box method (e.g., `StringUtils.trim_end/1`),
|
||||
/// this field contains the box name ("StringUtils"). This allows
|
||||
/// `this.is_whitespace(...)` to be resolved to `StringUtils.is_whitespace(...)`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // Function: StringUtils.trim_end/1
|
||||
/// // Condition: not this.is_whitespace(s.substring(i, i + 1))
|
||||
/// // current_static_box_name: Some("StringUtils")
|
||||
/// // → Resolves to: StringUtils.is_whitespace(...)
|
||||
/// ```
|
||||
///
|
||||
/// Set to `None` for non-static-box contexts (e.g., `Main.main()` loops).
|
||||
pub current_static_box_name: Option<String>,
|
||||
}
|
||||
|
||||
/// Phase 244: Unified condition lowering interface
|
||||
@ -230,6 +250,7 @@ mod tests {
|
||||
loop_var_id: ValueId(1),
|
||||
scope: &scope,
|
||||
alloc_value: &mut alloc_fn,
|
||||
current_static_box_name: None, // Phase 252: No static box in test
|
||||
};
|
||||
|
||||
// AST: i < 10
|
||||
@ -285,6 +306,7 @@ mod tests {
|
||||
loop_var_id: ValueId(1),
|
||||
scope: &scope,
|
||||
alloc_value: &mut alloc_fn,
|
||||
current_static_box_name: None, // Phase 252: No static box in test
|
||||
};
|
||||
|
||||
assert_eq!(context.loop_var_name, "i");
|
||||
|
||||
@ -149,6 +149,7 @@ fn test_pattern2_header_condition_via_exprlowerer() {
|
||||
condition_only_recipe: None, // Phase 93 P0: None for normal loops
|
||||
body_local_derived_recipe: None, // Phase 94: None for normal loops
|
||||
balanced_depth_scan_recipe: None, // Phase 107: None for normal loops
|
||||
current_static_box_name: None, // Phase 252: No static box context in test
|
||||
});
|
||||
|
||||
assert!(result.is_ok(), "ExprLowerer header path should succeed");
|
||||
|
||||
@ -32,6 +32,7 @@ use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::condition_lowerer::lower_value_expression;
|
||||
use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; // Phase 252 P1
|
||||
use crate::mir::join_ir::lowering::exit_meta_builder::IfSumExitMetaBuilderBox;
|
||||
#[cfg(debug_assertions)]
|
||||
use crate::mir::join_ir::lowering::condition_pattern::{
|
||||
@ -65,7 +66,9 @@ pub fn lower_if_sum_pattern(
|
||||
cond_env: &ConditionEnv,
|
||||
join_value_space: &mut JoinValueSpace,
|
||||
) -> Result<(JoinModule, JoinFragmentMeta), String> {
|
||||
eprintln!("[joinir/pattern3/if-sum] Starting AST-based if-sum lowering");
|
||||
// Phase 252 P1: Use DebugOutputBox for unified trace output
|
||||
let trace = DebugOutputBox::new_dev("joinir/pattern3/if-sum");
|
||||
trace.log("start", "Starting AST-based if-sum lowering");
|
||||
|
||||
// Allocator for extracting condition values
|
||||
let mut alloc_value = || join_value_space.alloc_local();
|
||||
@ -86,9 +89,9 @@ pub fn lower_if_sum_pattern(
|
||||
// Uses cond_env for variable resolution (e.g., `len` in `i < len`)
|
||||
let (loop_var, loop_op, loop_lhs_val, loop_limit_val, loop_limit_insts) =
|
||||
extract_loop_condition(loop_condition, &mut alloc_value, cond_env)?;
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] Loop condition: {} {:?} ValueId({})",
|
||||
loop_var, loop_op, loop_limit_val.0
|
||||
trace.log(
|
||||
"loop-cond",
|
||||
&format!("{} {:?} ValueId({})", loop_var, loop_op, loop_limit_val.0)
|
||||
);
|
||||
|
||||
// Step 2: Extract if condition info (e.g., i > 0 → var="i", op=Gt, value=ValueId)
|
||||
@ -96,23 +99,23 @@ pub fn lower_if_sum_pattern(
|
||||
// Phase 242-EX-A: Now supports complex LHS
|
||||
let (if_var, if_op, if_lhs_val, if_value_val, if_value_insts) =
|
||||
extract_if_condition(if_stmt, &mut alloc_value, cond_env)?;
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] If condition: {} {:?} ValueId({})",
|
||||
if_var, if_op, if_value_val.0
|
||||
trace.log(
|
||||
"if-cond",
|
||||
&format!("{} {:?} ValueId({})", if_var, if_op, if_value_val.0)
|
||||
);
|
||||
|
||||
// Step 3: Extract then-branch update (e.g., sum = sum + 1 → var="sum", addend=1)
|
||||
let (update_var, update_addend) = extract_then_update(if_stmt)?;
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] Then update: {} += {}",
|
||||
update_var, update_addend
|
||||
trace.log(
|
||||
"then-update",
|
||||
&format!("{} += {}", update_var, update_addend)
|
||||
);
|
||||
|
||||
// Step 4: Extract counter update (e.g., i = i + 1 → var="i", step=1)
|
||||
let (counter_var, counter_step) = extract_counter_update(body, &loop_var)?;
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] Counter update: {} += {}",
|
||||
counter_var, counter_step
|
||||
trace.log(
|
||||
"counter-update",
|
||||
&format!("{} += {}", counter_var, counter_step)
|
||||
);
|
||||
|
||||
// Step 5: Generate JoinIR
|
||||
@ -331,18 +334,18 @@ pub fn lower_if_sum_pattern(
|
||||
// sum_final is the k_exit return value - this is what `return sum` should use
|
||||
let fragment_meta = JoinFragmentMeta::with_expr_result(sum_final, exit_meta);
|
||||
|
||||
eprintln!("[joinir/pattern3/if-sum] Generated AST-based JoinIR");
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] Loop: {} {:?} ValueId({})",
|
||||
loop_var, loop_op, loop_limit_val.0
|
||||
trace.log("complete", "Generated AST-based JoinIR");
|
||||
trace.log(
|
||||
"summary-loop",
|
||||
&format!("{} {:?} ValueId({})", loop_var, loop_op, loop_limit_val.0)
|
||||
);
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] If: {} {:?} ValueId({})",
|
||||
if_var, if_op, if_value_val.0
|
||||
trace.log(
|
||||
"summary-if",
|
||||
&format!("{} {:?} ValueId({})", if_var, if_op, if_value_val.0)
|
||||
);
|
||||
eprintln!(
|
||||
"[joinir/pattern3/if-sum] Phase 215-2: expr_result={:?}",
|
||||
sum_final
|
||||
trace.log(
|
||||
"summary-result",
|
||||
&format!("expr_result={:?}", sum_final)
|
||||
);
|
||||
|
||||
Ok((join_module, fragment_meta))
|
||||
@ -411,7 +414,7 @@ where
|
||||
column: 1,
|
||||
},
|
||||
};
|
||||
lower_value_expression(&var_node, alloc_value, cond_env, None, &mut limit_instructions)? // Phase 92 P2-2
|
||||
lower_value_expression(&var_node, alloc_value, cond_env, None, None, &mut limit_instructions)? // Phase 92 P2-2 + Phase 252
|
||||
}
|
||||
};
|
||||
|
||||
@ -447,10 +450,10 @@ where
|
||||
|
||||
// Lower left-hand side (complex expression)
|
||||
let mut instructions = Vec::new();
|
||||
let lhs_val = lower_value_expression(left, alloc_value, cond_env, None, &mut instructions)?; // Phase 92 P2-2
|
||||
let lhs_val = lower_value_expression(left, alloc_value, cond_env, None, None, &mut instructions)?; // Phase 92 P2-2 + Phase 252
|
||||
|
||||
// Lower right-hand side
|
||||
let rhs_val = lower_value_expression(right, alloc_value, cond_env, None, &mut instructions)?; // Phase 92 P2-2
|
||||
let rhs_val = lower_value_expression(right, alloc_value, cond_env, None, None, &mut instructions)?; // Phase 92 P2-2 + Phase 252
|
||||
|
||||
// Extract base variable name from LHS if possible
|
||||
let var_name = extract_base_variable(left);
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# Phase 252 P0: this.methodcall in break condition (LLVM)
|
||||
set -euo pipefail
|
||||
|
||||
HAKO_PATH="apps/tests/phase252_p0_this_methodcall_break_cond_min.hako"
|
||||
|
||||
# Phase 252: Test StringUtils.count_leading_digits with this.is_digit break condition
|
||||
EXPECTED_EXIT=3 # "123abc" has 3 leading digits
|
||||
|
||||
NYASH_LLVM_USE_HARNESS=1 $HAKORUNE_BIN --backend llvm "$HAKO_PATH"
|
||||
actual_exit=$?
|
||||
|
||||
if [[ $actual_exit -eq $EXPECTED_EXIT ]]; then
|
||||
echo "✅ phase252_p0_this_methodcall_break_cond_llvm_exe: PASS (exit=$actual_exit)"
|
||||
exit 0
|
||||
else
|
||||
echo "❌ phase252_p0_this_methodcall_break_cond_llvm_exe: FAIL (expected=$EXPECTED_EXIT, got=$actual_exit)"
|
||||
exit 1
|
||||
fi
|
||||
@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# Phase 252 P0: this.methodcall in break condition (VM)
|
||||
set -euo pipefail
|
||||
|
||||
HAKO_PATH="apps/tests/phase252_p0_this_methodcall_break_cond_min.hako"
|
||||
|
||||
# Phase 252: Test StringUtils.count_leading_digits with this.is_digit break condition
|
||||
EXPECTED_EXIT=3 # "123abc" has 3 leading digits
|
||||
|
||||
$HAKORUNE_BIN --backend vm "$HAKO_PATH"
|
||||
actual_exit=$?
|
||||
|
||||
if [[ $actual_exit -eq $EXPECTED_EXIT ]]; then
|
||||
echo "✅ phase252_p0_this_methodcall_break_cond_vm: PASS (exit=$actual_exit)"
|
||||
exit 0
|
||||
else
|
||||
echo "❌ phase252_p0_this_methodcall_break_cond_vm: FAIL (expected=$EXPECTED_EXIT, got=$actual_exit)"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user