feat(joinir): Phase 259 P0 - Pattern8 BoolPredicateScan + Copy binding fix

Pattern8 (Boolean Predicate Scan) implementation for is_integer/1:
- New pattern detection for `loop + if not predicate() { return false }`
- JoinIR lowerer with main/loop_step/k_exit structure
- Me receiver passed as param (by-name 禁止)

Key fixes:
1. expr_result = Some(join_exit_value) (Pattern7 style)
2. Tail-call: dst: None (no extra Ret instruction)
3. instruction_rewriter: Add `&& is_loop_header_with_phi` check
   - Pattern8 has no carriers → no PHIs → MUST generate Copy bindings
   - Without this, ValueId(103/104/105) were undefined

Status: Copy instructions now generated correctly, but exit block
creation issue remains (next step: Step A-C in指示書).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-21 02:40:07 +09:00
parent e4f57ea83d
commit a767f0f3a9
10 changed files with 913 additions and 5 deletions

View File

@ -0,0 +1,35 @@
static box StringUtils {
is_digit(ch) {
return ch == "0" or ch == "1" or ch == "2" or ch == "3" or ch == "4"
or ch == "5" or ch == "6" or ch == "7" or ch == "8" or ch == "9"
}
is_integer(s) {
if s.length() == 0 {
return false
}
local start = 0
if s.substring(0, 1) == "-" {
if s.length() == 1 {
return false
}
start = 1
}
local i = start
loop(i < s.length()) {
if not this.is_digit(s.substring(i, i + 1)) {
return false
}
i = i + 1
}
return true
}
}
static box Main {
main() {
return StringUtils.is_integer("123") ? 7 : 1
}
}

View File

@ -22,8 +22,42 @@ Related:
## Proposed Approach (P0)
方針: preludenested-ifは既存 lowering のまま、loop 部分だけを scan パターンへ寄せる(構造で解決)。
**P0 Design Decision: Pattern8新規採用**
P0 の狙い:
- loop の core は “scan until mismatch” で、Pattern6scanと近い
- ただし return 値が `i/-1` ではなく `true/false` なので、scan パターンの “return payload” を一般化する必要がある可能性がある
### Why Pattern8?
Pattern6index_of系は "見つける" scan返り値: 整数 i or -1で、is_integer は "全部検証する" predicate scan返り値: 真偽値 true/false。役割が異なるため、Pattern8 として分離した。
### Pattern8 vs Pattern6
| | Pattern6 (index_of系) | Pattern8 (is_integer系) |
|---|---|---|
| 役割 | "見つける" scan | "全部検証する" predicate scan |
| Match形 | `substring(...) == needle` | `not predicate(ch)` → early exit |
| 返り値 | Integer (i or -1) | Boolean (true/false) |
| Exit PHI | `i`(ループ状態変数) | `ret_bool`(検証結果) |
| Carriers | [i] (LoopState) | [] (empty, expr_result のみ) |
### JoinIR Contract
- **jump_args_layout**: ExprResultPlusCarrierscarriers=0
- **expr_result**: Some(join_exit_value) - ret_bool from k_exit (pipeline handling)
- **exit_bindings**: Emptycarriers なし)
- **SSOT**: `join_inputs = entry_func.params.clone()`
- **Me receiver**: Passed as param [i, me, s] (by-name 禁止)
### 受理形P0固定
```nyash
loop(i < s.length()) {
if not this.is_digit(s.substring(i, i + 1)) {
return false
}
i = i + 1
}
return true
```
- prelude の start 計算は許可(ただし i_init = start で渡す)
- predicate は Me method callthis.is_digitのみ
- step は 1 固定

View File

@ -645,7 +645,7 @@ pub(super) fn merge_and_rewrite(
"[cf_loop/joinir] Phase 33-21: Skip param bindings in header block (PHIs define carriers)"
);
}
} else if is_recursive_call || is_target_loop_entry {
} else if (is_recursive_call || is_target_loop_entry) && is_loop_header_with_phi {
// Phase 256 P1.10: Skip param bindings for:
// - Recursive call (loop_step → loop_step): latch edge
// - Entry call (main → loop_step): entry edge
@ -653,6 +653,9 @@ pub(super) fn merge_and_rewrite(
// Header PHIs receive values from these edges via separate mechanism.
// Generating Copies here would cause multiple definitions.
//
// Phase 259 P0 FIX: Only skip if loop header HAS PHIs!
// Pattern8 has no carriers → no PHIs → MUST generate Copy bindings.
//
// Update remapper mappings for any further instructions.
for (i, arg_val_remapped) in args.iter().enumerate() {
if i < target_params.len() {

View File

@ -80,6 +80,7 @@ pub(in crate::mir::builder) mod pattern4_with_continue;
pub(in crate::mir::builder) mod pattern5_infinite_early_exit; // Phase 131-11
pub(in crate::mir::builder) mod pattern6_scan_with_init; // Phase 254 P0: index_of/find/contains pattern
pub(in crate::mir::builder) mod pattern7_split_scan; // Phase 256 P0: split/tokenization with variable step
pub(in crate::mir::builder) mod pattern8_scan_bool_predicate; // Phase 259 P0: boolean predicate scan (is_integer/is_valid)
pub(in crate::mir::builder) mod pattern_pipeline;
pub(in crate::mir::builder) mod router;
pub(in crate::mir::builder) mod trim_loop_lowering; // Phase 180: Dedicated Trim/P5 lowering module

View File

@ -0,0 +1,549 @@
//! Pattern 8: Boolean Predicate Scan (is_integer/is_valid form)
//!
//! Phase 259 P0: Dedicated pattern for boolean predicate validation loops
//!
//! ## Pattern Structure
//!
//! ```nyash
//! is_integer(s) {
//! local i = start // Computed in prelude
//! loop(i < s.length()) {
//! if not this.is_digit(s.substring(i, i + 1)) {
//! return false
//! }
//! i = i + 1
//! }
//! return true
//! }
//! ```
//!
//! ## Detection Criteria (P0: Fixed Form Only)
//!
//! 1. Loop condition: `i < s.length()`
//! 2. Loop body has if statement with:
//! - Condition: `not this.method(...)` (UnaryOp::Not + MethodCall)
//! - Then branch: `return false` (early exit)
//! 3. Loop body has step: `i = i + 1`
//! 4. Post-loop: `return true`
//!
//! ## vs Pattern 6
//!
//! - Pattern 6: Match scan (substring == needle → return i)
//! - Pattern 8: Predicate scan (not is_digit → return false, else true)
//! - Pattern 6: Returns integer (index or -1)
//! - Pattern 8: Returns boolean (true/false)
use super::super::trace;
use super::common::var;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator};
use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;
/// Phase 259 P0: Extracted structure for boolean predicate scan
#[derive(Debug, Clone)]
struct BoolPredicateScanParts {
/// Loop variable name (e.g., "i")
loop_var: String,
/// Haystack variable name (e.g., "s")
haystack: String,
/// Predicate method receiver (e.g., "this")
predicate_receiver: String,
/// Predicate method name (e.g., "is_digit")
predicate_method: String,
/// Step literal (P0: must be 1)
step_lit: i64,
}
/// Phase 259 P0: Detection for Pattern 8 (BoolPredicateScan)
pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool {
eprintln!("[pattern8/can_lower] Called for function: {}", ctx.func_name);
match extract_bool_predicate_scan_parts(ctx.condition, ctx.body) {
Ok(Some(_)) => {
if ctx.debug {
trace::trace().debug(
"pattern8/can_lower",
"accept: boolean predicate scan pattern extractable",
);
}
true
}
Ok(None) => {
if ctx.debug {
trace::trace().debug(
"pattern8/can_lower",
"reject: not a boolean predicate scan pattern",
);
}
false
}
Err(e) => {
if ctx.debug {
trace::trace().debug(
"pattern8/can_lower",
&format!("reject: extraction error: {}", e),
);
}
false
}
}
}
/// Phase 259 P0: Extract boolean predicate scan pattern parts
///
/// # P0 Restrictions (Fail-Fast)
///
/// - Loop condition: `i < s.length()` (forward only)
/// - If condition: `not this.method(s.substring(i, i + 1))` (UnaryOp::Not)
/// - Then branch: `return false` (Literal::Bool(false))
/// - Step: `i = i + 1` (step_lit == 1)
/// - Post-loop: `return true` (enforced by caller)
fn extract_bool_predicate_scan_parts(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<BoolPredicateScanParts>, String> {
eprintln!("[pattern8/extract] Starting extraction");
eprintln!("[pattern8/extract] Body statements: {}", body.len());
// 1. Check loop condition: i < s.length()
let (loop_var, haystack) = match condition {
ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left,
right,
..
} => {
let loop_var = match left.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
_ => {
eprintln!("[pattern8/extract] REJECT: loop condition left is not Variable");
return Ok(None);
}
};
let haystack = match right.as_ref() {
ASTNode::MethodCall {
object, method, ..
} if method == "length" => match object.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
_ => {
eprintln!("[pattern8/extract] REJECT: length() object is not Variable");
return Ok(None);
}
},
_ => {
eprintln!("[pattern8/extract] REJECT: loop condition right is not .length()");
return Ok(None);
}
};
(loop_var, haystack)
}
_ => {
eprintln!("[pattern8/extract] REJECT: loop condition is not BinaryOp::Less");
return Ok(None);
}
};
eprintln!("[pattern8/extract] ✅ Loop condition OK: {} < {}.length()", loop_var, haystack);
// 2. Find if statement with predicate check and return false
let mut predicate_receiver_opt = None;
let mut predicate_method_opt = None;
eprintln!("[pattern8/extract] Step 2: Searching for predicate pattern in {} statements", body.len());
for (i, stmt) in body.iter().enumerate() {
eprintln!("[pattern8/extract] Statement {}: {:?}", i, stmt);
if let ASTNode::If {
condition: if_cond,
then_body,
..
} = stmt
{
// Check if condition is: not this.method(...)
if let ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand,
..
} = if_cond.as_ref()
{
// Operand must be MethodCall
if let ASTNode::MethodCall {
object,
method,
arguments,
..
} = operand.as_ref()
{
// Extract receiver (e.g., "me")
// Phase 259 P0: Support both Variable and Me node
// IMPORTANT: Me is registered as "me" in variable_map (not "this")
let receiver = match object.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
ASTNode::Me { .. } => "me".to_string(), // Me is registered as "me" in MirBuilder
_ => {
eprintln!("[pattern8/extract] Receiver is not Variable or Me: {:?}", object);
continue;
}
};
// P0: Expect 1 argument: s.substring(i, i + 1)
if arguments.len() != 1 {
continue;
}
// Validate argument is substring call
if let ASTNode::MethodCall {
object: substr_obj,
method: substr_method,
arguments: substr_args,
..
} = &arguments[0]
{
if substr_method != "substring" {
continue;
}
// Object must be haystack
if let ASTNode::Variable { name, .. } = substr_obj.as_ref() {
if name != &haystack {
continue;
}
} else {
continue;
}
// Args: (i, i + 1)
if substr_args.len() != 2 {
continue;
}
// Arg 0: loop_var
match &substr_args[0] {
ASTNode::Variable { name, .. } if name == &loop_var => {}
_ => continue,
}
// Arg 1: loop_var + 1
match &substr_args[1] {
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left,
right,
..
} => {
// Left: loop_var
match left.as_ref() {
ASTNode::Variable { name, .. } if name == &loop_var => {}
_ => continue,
}
// Right: Literal(1)
match right.as_ref() {
ASTNode::Literal {
value: LiteralValue::Integer(1),
..
} => {}
_ => continue,
}
}
_ => continue,
}
} else {
continue;
}
// Check then_body contains: return false
if then_body.len() == 1 {
if let ASTNode::Return { value, .. } = &then_body[0] {
if let Some(ret_val) = value {
if let ASTNode::Literal {
value: LiteralValue::Bool(false),
..
} = ret_val.as_ref()
{
eprintln!("[pattern8/extract] ✅ Found predicate pattern: {}.{}", receiver, method);
predicate_receiver_opt = Some(receiver);
predicate_method_opt = Some(method.clone());
}
}
}
}
}
}
}
}
if predicate_receiver_opt.is_none() {
eprintln!("[pattern8/extract] REJECT: No predicate pattern found");
}
let predicate_receiver = predicate_receiver_opt.ok_or_else(|| "No predicate pattern found")?;
let predicate_method = predicate_method_opt.ok_or_else(|| "No predicate method found")?;
// 3. Check for step: i = i + 1
let mut step_lit_opt = None;
eprintln!("[pattern8/extract] Step 3: Searching for step pattern ({} = {} + 1)", loop_var, loop_var);
for stmt in body {
if let ASTNode::Assignment { target, value, .. } = stmt {
if let ASTNode::Variable { name: target_name, .. } = target.as_ref() {
if target_name == &loop_var {
if let ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left,
right,
..
} = value.as_ref()
{
if let ASTNode::Variable { name: left_name, .. } = left.as_ref() {
if left_name == &loop_var {
if let ASTNode::Literal {
value: LiteralValue::Integer(lit),
..
} = right.as_ref()
{
eprintln!("[pattern8/extract] ✅ Found step pattern: {} = {} + {}", loop_var, loop_var, lit);
step_lit_opt = Some(*lit);
}
}
}
}
}
}
}
}
if step_lit_opt.is_none() {
eprintln!("[pattern8/extract] REJECT: No step pattern found");
}
let step_lit = step_lit_opt.ok_or_else(|| "No step pattern found")?;
// P0: Step must be 1
if step_lit != 1 {
eprintln!("[pattern8/extract] REJECT: Step is {}, expected 1", step_lit);
return Ok(None);
}
eprintln!("[pattern8/extract] ✅✅✅ ACCEPT: Pattern8 extraction successful!");
eprintln!("[pattern8/extract] Parts: loop_var={}, haystack={}, predicate={}.{}, step={}",
loop_var, haystack, predicate_receiver, predicate_method, step_lit);
Ok(Some(BoolPredicateScanParts {
loop_var,
haystack,
predicate_receiver,
predicate_method,
step_lit,
}))
}
/// Phase 259 P0: Lowering function for Pattern 8
pub(crate) fn lower(
builder: &mut MirBuilder,
ctx: &super::router::LoopPatternContext,
) -> Result<Option<ValueId>, String> {
builder.cf_loop_pattern8_bool_predicate_impl(
ctx.condition,
ctx.body,
ctx.func_name,
ctx.debug,
)
}
impl MirBuilder {
/// Phase 259 P0: Pattern 8 (BoolPredicateScan) implementation
pub(crate) fn cf_loop_pattern8_bool_predicate_impl(
&mut self,
condition: &ASTNode,
body: &[ASTNode],
func_name: &str,
debug: bool,
) -> Result<Option<ValueId>, String> {
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
let trace = trace::trace();
if debug {
trace.debug(
"pattern8/lower",
&format!("Phase 259 P0: BoolPredicateScan lowering for {}", func_name),
);
}
// Step 1: Extract pattern parts
let parts = extract_bool_predicate_scan_parts(condition, body)?
.ok_or_else(|| format!("[pattern8] Not a boolean predicate scan pattern in {}", func_name))?;
if debug {
trace.debug(
"pattern8/lower",
&format!(
"Extracted: loop_var={}, haystack={}, predicate={}.{}, step={}",
parts.loop_var, parts.haystack, parts.predicate_receiver, parts.predicate_method, parts.step_lit
),
);
}
// Step 2: Get host ValueIds for variables
eprintln!("[pattern8/lower] variable_map contents:");
for (name, vid) in &self.variable_ctx.variable_map {
eprintln!("[pattern8/lower] {} -> {:?}", name, vid);
}
eprintln!("[pattern8/lower] Looking for receiver: {}", parts.predicate_receiver);
let s_host = self
.variable_ctx
.variable_map
.get(&parts.haystack)
.copied()
.ok_or_else(|| format!("[pattern8] Variable {} not found", parts.haystack))?;
let i_host = self
.variable_ctx
.variable_map
.get(&parts.loop_var)
.copied()
.ok_or_else(|| format!("[pattern8] Variable {} not found", parts.loop_var))?;
if debug {
trace.debug(
"pattern8/lower",
&format!("Host ValueIds: s={:?}, i={:?}", s_host, i_host),
);
}
// Step 3: Create JoinModule
let mut join_value_space = JoinValueSpace::new();
use crate::mir::join_ir::lowering::scan_bool_predicate_minimal::lower_scan_bool_predicate_minimal;
let join_module = lower_scan_bool_predicate_minimal(
&mut join_value_space,
&parts.predicate_receiver,
&parts.predicate_method,
);
// Step 4: Build boundary (SSOT - use entry.params)
use super::common::get_entry_function;
let main_func = get_entry_function(&join_module, "pattern8")?;
// SSOT: Use actual params from JoinModule entry
let join_inputs = main_func.params.clone();
// host_inputs in same order: [i, me, s] (alphabetical)
// Phase 259 P0: me_host = receiver ValueId
// IMPORTANT: Me receiver might not be in variable_map yet, so we use build_me_expression()
let me_host = if parts.predicate_receiver == "me" {
self.build_me_expression()?
} else {
self
.variable_ctx
.variable_map
.get(&parts.predicate_receiver)
.copied()
.ok_or_else(|| format!("[pattern8] Receiver {} not found", parts.predicate_receiver))?
};
let host_inputs = vec![i_host, me_host, s_host];
if debug {
trace.debug(
"pattern8/lower",
&format!(
"Boundary inputs: join_inputs={:?}, host_inputs={:?}",
join_inputs, host_inputs
),
);
}
// Verify count consistency
if join_inputs.len() != host_inputs.len() {
return Err(format!(
"[pattern8] Params count mismatch: join_inputs={}, host_inputs={}",
join_inputs.len(), host_inputs.len()
));
}
// Step 5: Build exit_bindings (no carriers, only expr_result)
// Pattern8 returns ret_bool directly (not loop variable)
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding;
let k_exit_func = join_module.require_function(
crate::mir::join_ir::lowering::canonical_names::K_EXIT,
"Pattern 8",
);
let join_exit_value = k_exit_func
.params
.first()
.copied()
.expect("k_exit must have parameter for exit value");
// Phase 259 P0: Allocate host ValueId for return value
let result_host = self.next_value_id();
self.type_ctx.value_types.insert(result_host, crate::mir::MirType::Bool);
// Phase 259 P0: exit_bindings contains ret_bool binding (Pattern7 style)
// This allows remapper to map join_exit_value → result_host
let result_exit_binding = LoopExitBinding {
carrier_name: "ret_bool".to_string(), // Logical name for return value
join_exit_value,
host_slot: result_host,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
};
let exit_bindings = vec![result_exit_binding];
// Step 6: Build boundary with expr_result
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
let carrier_info = CarrierInfo {
loop_var_name: parts.loop_var.clone(),
loop_var_id: i_host,
carriers: vec![], // No carriers - Pattern8 uses expr_result only
trim_helper: None,
promoted_loopbodylocals: Vec::new(),
#[cfg(feature = "normalized_dev")]
promoted_bindings: std::collections::BTreeMap::new(),
};
// Phase 259 P0: expr_result = join_exit_value (Pattern7 style)
// Pattern8 returns boolean from k_exit, not loop variable
let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(join_inputs, host_inputs)
.with_exit_bindings(exit_bindings)
.with_carrier_info(carrier_info)
.with_expr_result(Some(join_exit_value)) // ✅ CRITICAL: Set expr_result to k_exit param
.build();
if debug {
trace.debug(
"pattern8/lower",
"Built boundary with expr_result (ret_bool from k_exit)",
);
}
// Step 7: Execute JoinIRConversionPipeline
use super::conversion_pipeline::JoinIRConversionPipeline;
let result = JoinIRConversionPipeline::execute(
self,
join_module,
Some(&boundary),
"pattern8",
debug,
)?;
eprintln!("[pattern8/lower] Pipeline execution complete, result: {:?}", result);
if debug {
trace.debug(
"pattern8/lower",
&format!("Pattern 8 complete, result: {:?}", result),
);
}
// Pattern8 returns ret_bool (expr_result), not Void
eprintln!("[pattern8/lower] Returning result: {:?}", result);
Ok(result)
}
}

View File

@ -203,6 +203,11 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
detect: super::pattern7_split_scan::can_lower,
lower: super::pattern7_split_scan::lower,
},
LoopPatternEntry {
name: "Pattern8_BoolPredicateScan", // Phase 259 P0: boolean predicate scan (is_integer/is_valid)
detect: super::pattern8_scan_bool_predicate::can_lower,
lower: super::pattern8_scan_bool_predicate::lower,
},
LoopPatternEntry {
name: "Pattern3_WithIfPhi",
detect: super::pattern3_with_if_phi::can_lower,

View File

@ -79,6 +79,7 @@ pub mod simple_while_minimal; // Phase 188-Impl-1: Pattern 1 minimal lowerer
pub mod scan_with_init_minimal; // Phase 254 P1: Pattern 6 minimal lowerer (index_of/find/contains)
pub mod scan_with_init_reverse; // Phase 257 P0: Pattern 6 reverse scan lowerer (last_index_of)
pub mod split_scan_minimal; // Phase 256 P0: Pattern 7 minimal lowerer (split/tokenization with variable step)
pub mod scan_bool_predicate_minimal; // Phase 259 P0: Pattern 8 minimal lowerer (is_integer/is_valid boolean predicate scan)
pub mod skip_ws;
pub mod stage1_using_resolver;
pub mod stageb_body;

View File

@ -0,0 +1,252 @@
//! Phase 259 P0: Pattern 8 (BoolPredicateScan) JoinIR Lowerer
//!
//! Target: apps/tests/phase259_p0_is_integer_min.hako
//!
//! Expected JoinIR:
//! ```text
//! fn main(i, me, s):
//! result = loop_step(i, me, s)
//! ret result
//!
//! fn loop_step(i, me, s):
//! // 1. Check exit condition: i >= s.length()
//! len = StringBox.length(s)
//! exit_cond = (i >= len)
//! const_true = true
//! Jump(k_exit, [const_true], cond=exit_cond) // All passed
//!
//! // 2. Extract character
//! const_1 = 1
//! i_plus_1 = i + 1
//! ch = StringBox.substring(s, i, i_plus_1)
//!
//! // 3. Call predicate (Me method → MethodCall with me receiver)
//! ok = MethodCall(me, predicate_method, [ch])
//!
//! // 4. Check predicate result (inverted - if !ok, return false)
//! const_false = false
//! not_ok = !ok
//! Jump(k_exit, [const_false], cond=not_ok) // Predicate failed
//!
//! // 5. Tail recursion
//! tail_result = Call(loop_step, [i_plus_1, me, s])
//! Ret(tail_result)
//!
//! fn k_exit(ret_bool):
//! ret ret_bool
//! ```
use crate::mir::join_ir::lowering::canonical_names as cn;
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::{
CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, MirType,
UnaryOp,
};
/// Lower Pattern 8 (BoolPredicateScan) to JoinIR
///
/// # Arguments
///
/// * `join_value_space` - Unified ValueId allocator
/// * `predicate_receiver` - Receiver name (e.g., "this")
/// * `predicate_method` - Method name (e.g., "is_digit")
///
/// # Returns
///
/// * `JoinModule` - Successfully lowered to JoinIR
pub(crate) fn lower_scan_bool_predicate_minimal(
join_value_space: &mut JoinValueSpace,
predicate_receiver: &str,
predicate_method: &str,
) -> JoinModule {
let mut join_module = JoinModule::new();
// Function IDs
let main_id = JoinFuncId::new(0);
let loop_step_id = JoinFuncId::new(1);
let k_exit_id = JoinFuncId::new(2);
// main() params/locals
let i_main_param = join_value_space.alloc_param();
let me_main_param = join_value_space.alloc_param(); // Phase 259 P0: Me receiver param
let s_main_param = join_value_space.alloc_param();
let loop_result = join_value_space.alloc_local();
// loop_step params/locals
let i_step_param = join_value_space.alloc_param();
let me_step_param = join_value_space.alloc_param(); // Phase 259 P0: Me receiver param
let s_step_param = join_value_space.alloc_param();
let len = join_value_space.alloc_local();
let exit_cond = join_value_space.alloc_local();
let const_true = join_value_space.alloc_local();
let const_1 = join_value_space.alloc_local();
let i_plus_1 = join_value_space.alloc_local();
let ch = join_value_space.alloc_local();
let ok = join_value_space.alloc_local();
let const_false = join_value_space.alloc_local();
let not_ok = join_value_space.alloc_local();
// k_exit params
let ret_bool_param = join_value_space.alloc_param();
// main() function
let mut main_func = JoinFunction::new(
main_id,
"main".to_string(),
vec![i_main_param, me_main_param, s_main_param], // Phase 259 P0: +me_param
);
main_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![i_main_param, me_main_param, s_main_param], // Phase 259 P0: +me_param
k_next: None,
dst: Some(loop_result),
});
main_func.body.push(JoinInst::Ret {
value: Some(loop_result),
});
join_module.add_function(main_func);
// loop_step(i, me, s) function
let mut loop_step_func = JoinFunction::new(
loop_step_id,
cn::LOOP_STEP.to_string(),
vec![i_step_param, me_step_param, s_step_param], // Phase 259 P0: +me_param
);
// 1. len = s.length()
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(len),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![s_step_param],
}));
// 2. exit_cond = (i >= len)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: exit_cond,
op: CompareOp::Ge,
lhs: i_step_param,
rhs: len,
}));
// 3. const_true = true
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_true,
value: ConstValue::Bool(true),
}));
// 4. Jump(k_exit, [true], cond=exit_cond) - all passed
loop_step_func.body.push(JoinInst::Jump {
cont: k_exit_id.as_cont(),
args: vec![const_true],
cond: Some(exit_cond),
});
// 5. const_1 = 1
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
// 6. i_plus_1 = i + 1
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_plus_1,
op: crate::mir::join_ir::BinOpKind::Add,
lhs: i_step_param,
rhs: const_1,
}));
// 7. ch = s.substring(i, i_plus_1)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ch),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![s_step_param, i_step_param, i_plus_1],
}));
// 8. ok = me.predicate_method(ch)
// P0: predicate_receiver is "this" → use MethodCall with me_step_param
// Phase 259 P0: Me receiver passed as param (by-name 禁止)
loop_step_func.body.push(JoinInst::MethodCall {
dst: ok,
receiver: me_step_param, // Me receiver as param
method: predicate_method.to_string(),
args: vec![ch],
type_hint: Some(MirType::Bool),
});
// 9. const_false = false
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_false,
value: ConstValue::Bool(false),
}));
// 10. not_ok = !ok (invert predicate for jump condition)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: not_ok,
op: UnaryOp::Not,
operand: ok,
}));
// 11. Jump(k_exit, [false], cond=not_ok) - predicate failed
loop_step_func.body.push(JoinInst::Jump {
cont: k_exit_id.as_cont(),
args: vec![const_false],
cond: Some(not_ok),
});
// 12. Call(loop_step, [i_plus_1, me, s]) - tail recursion
// Phase 259 P0: dst: None for tail-call (bridge handles optimization)
loop_step_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![i_plus_1, me_step_param, s_step_param], // Phase 259 P0: +me_param
k_next: None,
dst: None, // Tail-call: bridge が適切に処理
});
join_module.add_function(loop_step_func);
// k_exit(ret_bool) function
let mut k_exit_func = JoinFunction::new(
k_exit_id,
cn::K_EXIT.to_string(),
vec![ret_bool_param],
);
k_exit_func.body.push(JoinInst::Ret {
value: Some(ret_bool_param),
});
join_module.add_function(k_exit_func);
// Set entry point
join_module.entry = Some(main_id);
eprintln!("[joinir/pattern8] Generated JoinIR for BoolPredicateScan Pattern");
eprintln!("[joinir/pattern8] Functions: main, loop_step, k_exit");
eprintln!(
"[joinir/pattern8] Predicate: {}.{}",
predicate_receiver, predicate_method
);
join_module
}

View File

@ -0,0 +1,14 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/../../.."
HAKORUNE_BIN="${HAKORUNE_BIN:-./target/release/hakorune}"
NYASH_LLVM_USE_HARNESS=1 $HAKORUNE_BIN --backend llvm apps/tests/phase259_p0_is_integer_min.hako > /tmp/phase259_llvm.txt 2>&1
EXIT_CODE=$?
if [ $EXIT_CODE -eq 7 ]; then
echo "[PASS] phase259_p0_is_integer_llvm_exe"
exit 0
else
echo "[FAIL] phase259_p0_is_integer_llvm_exe: expected exit 7, got $EXIT_CODE"
cat /tmp/phase259_llvm.txt
exit 1
fi

View File

@ -0,0 +1,14 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/../../.."
HAKORUNE_BIN="${HAKORUNE_BIN:-./target/release/hakorune}"
$HAKORUNE_BIN apps/tests/phase259_p0_is_integer_min.hako > /tmp/phase259_out.txt 2>&1
EXIT_CODE=$?
if [ $EXIT_CODE -eq 7 ]; then
echo "[PASS] phase259_p0_is_integer_vm"
exit 0
else
echo "[FAIL] phase259_p0_is_integer_vm: expected exit 7, got $EXIT_CODE"
cat /tmp/phase259_out.txt
exit 1
fi