docs(phase29ab): closeout P1-P9

This commit is contained in:
2025-12-28 16:36:15 +09:00
parent c397016ac7
commit 209b04d808
12 changed files with 395 additions and 280 deletions

View File

@ -0,0 +1,43 @@
use crate::mir::join_ir::lowering::error_tags;
#[derive(Debug, Clone)]
pub(crate) struct ContractViolation {
pub(crate) msg: String,
pub(crate) hint: String,
}
impl ContractViolation {
pub(crate) fn new(msg: &str, hint: &str) -> Self {
Self {
msg: msg.to_string(),
hint: hint.to_string(),
}
}
}
pub(crate) fn freeze_contract(tag: &str, violation: &ContractViolation) -> String {
error_tags::freeze_with_hint(tag, &violation.msg, &violation.hint)
}
pub(crate) enum ExtractDecision<T> {
Match(T),
NotApplicable,
Contract(ContractViolation),
}
impl<T> ExtractDecision<T> {
pub(crate) fn contract(msg: &str, hint: &str) -> Self {
Self::Contract(ContractViolation::new(msg, hint))
}
}
pub(crate) fn finalize_extract<T>(
decision: ExtractDecision<T>,
tag: &str,
) -> Result<Option<T>, String> {
match decision {
ExtractDecision::Match(value) => Ok(Some(value)),
ExtractDecision::NotApplicable => Ok(None),
ExtractDecision::Contract(violation) => Err(freeze_contract(tag, &violation)),
}
}

View File

@ -5,8 +5,10 @@
mod ast_helpers;
mod carrier_binding_policy;
mod contract_error;
mod joinir_helpers; // Phase 256.8.5: JoinModule helpers
pub(crate) use ast_helpers::var;
pub(crate) use carrier_binding_policy::{decide_carrier_binding_policy, CarrierBindingPolicy};
pub(crate) use contract_error::{finalize_extract, ContractViolation, ExtractDecision};
pub(crate) use joinir_helpers::get_entry_function; // Phase 256.8.5

View File

@ -35,55 +35,14 @@
// Phase 255 P2: Use shared var() helper
use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::error_tags;
use crate::mir::builder::control_flow::joinir::patterns::common::{finalize_extract, ExtractDecision};
// Phase 273 P1: Import DomainPlan types (Plan renamed to DomainPlan)
use crate::mir::builder::control_flow::plan::{DomainPlan, ScanDirection as PlanScanDirection, ScanWithInitPlan};
// Phase 273 P0.1: Local ScanDirection and ScanParts removed (migrated to plan/mod.rs)
// Only keeping internal helper enum for legacy extract_scan_with_init_parts()
// Phase 273 P0.1: ScanDirection/ScanWithInitPlan is SSOT in plan/mod.rs
#[derive(Debug, Clone)]
struct ScanWithInitContractViolation {
msg: String,
hint: String,
}
#[derive(Debug, Clone)]
enum ScanWithInitExtractError {
Contract(ScanWithInitContractViolation),
}
impl ScanWithInitExtractError {
fn contract(msg: &str, hint: &str) -> Self {
Self::Contract(ScanWithInitContractViolation {
msg: msg.to_string(),
hint: hint.to_string(),
})
}
}
/// Phase 273 P0.1: Internal scan direction for legacy helper (temporary)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ScanDirection {
/// Forward scan: i < s.length(), i = i + 1
Forward,
/// Reverse scan: i >= 0, i = i - 1
Reverse,
}
/// Phase 273 P0.1: Internal structure for legacy helper (temporary)
#[derive(Debug, Clone)]
struct ScanParts {
loop_var: String,
haystack: String,
needle: String,
step_lit: i64,
early_return_expr: ASTNode,
not_found_return_lit: i64,
scan_direction: ScanDirection,
dynamic_needle: bool,
}
// Phase 273 P0.1: extract_scan_with_init_parts() returns ScanWithInitPlan directly
/// Phase 273 P1: Pure extractor that returns DomainPlan (SSOT)
///
@ -101,41 +60,21 @@ pub(crate) fn extract_scan_with_init_plan(
fn_body: Option<&[ASTNode]>,
) -> Result<Option<DomainPlan>, String> {
// Call internal extraction helper
let parts = match extract_scan_with_init_parts(condition, body, fn_body) {
Ok(parts) => parts,
Err(ScanWithInitExtractError::Contract(err)) => {
return Err(error_tags::freeze_with_hint(
"phase29ab/pattern6/contract",
&err.msg,
&err.hint,
));
}
};
let parts = finalize_extract(
extract_scan_with_init_parts(condition, body, fn_body),
"phase29ab/pattern6/contract",
)?;
// Phase 273 P1: Filter out patterns not supported by Plan-based normalizer
if let Some(ref p) = parts {
// P1 scope: Only forward scan (step=1) supported
if p.step_lit != 1 {
return Ok(None); // Let legacy path handle reverse scans
return Ok(None);
}
}
// Wrap in DomainPlan if extracted successfully
Ok(parts.map(|p| {
DomainPlan::ScanWithInit(ScanWithInitPlan {
loop_var: p.loop_var,
haystack: p.haystack,
needle: p.needle,
step_lit: p.step_lit,
early_return_expr: p.early_return_expr,
not_found_return_lit: p.not_found_return_lit,
scan_direction: match p.scan_direction {
ScanDirection::Forward => PlanScanDirection::Forward,
ScanDirection::Reverse => PlanScanDirection::Reverse,
},
dynamic_needle: p.dynamic_needle,
})
}))
Ok(parts.map(DomainPlan::ScanWithInit))
}
// Phase 273 P0.1: can_lower() removed (router now calls extract_scan_with_init_plan() directly)
@ -224,9 +163,9 @@ fn extract_substring_window(
///
/// # Returns
///
/// * `Ok(Some(ScanParts))` - Successfully extracted the pattern
/// * `Ok(None)` - Not a scan-with-init pattern (different pattern)
/// * `Err(String)` - Contract violation
/// * `ExtractDecision::Match(ScanWithInitPlan)` - Successfully extracted the pattern
/// * `ExtractDecision::NotApplicable` - Not a scan-with-init pattern (different pattern)
/// * `ExtractDecision::Contract` - Contract violation
///
/// # P0 Restrictions
///
@ -238,7 +177,7 @@ fn extract_scan_with_init_parts(
condition: &ASTNode,
body: &[ASTNode],
_fn_body: Option<&[ASTNode]>,
) -> Result<Option<ScanParts>, ScanWithInitExtractError> {
) -> ExtractDecision<ScanWithInitPlan> {
use crate::ast::{BinaryOperator, LiteralValue};
// 1. Check loop condition: i < s.length() (forward) or i >= 0 (reverse)
@ -253,7 +192,7 @@ fn extract_scan_with_init_parts(
} => {
let loop_var = match left.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
_ => return Ok(None),
_ => return ExtractDecision::NotApplicable,
};
let haystack = match right.as_ref() {
@ -261,12 +200,12 @@ fn extract_scan_with_init_parts(
object, method, ..
} if method == "length" => match object.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
_ => return Ok(None),
_ => return ExtractDecision::NotApplicable,
},
_ => return Ok(None),
_ => return ExtractDecision::NotApplicable,
};
(loop_var, Some(haystack), ScanDirection::Forward)
(loop_var, Some(haystack), PlanScanDirection::Forward)
}
// Forward (Dynamic): i <= s.length() - substr.length()
// Phase 258 P0: Accept dynamic needle form for index_of_string
@ -278,7 +217,7 @@ fn extract_scan_with_init_parts(
} => {
let loop_var = match left.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
_ => return Ok(None),
_ => return ExtractDecision::NotApplicable,
};
// Right side must be: s.length() - substr.length()
@ -295,9 +234,9 @@ fn extract_scan_with_init_parts(
object, method, ..
} if method == "length" => match object.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
_ => return Ok(None),
_ => return ExtractDecision::NotApplicable,
},
_ => return Ok(None),
_ => return ExtractDecision::NotApplicable,
};
// Right of subtraction: substr.length()
@ -306,13 +245,13 @@ fn extract_scan_with_init_parts(
// Valid: s.length() - substr.length()
haystack
}
_ => return Ok(None),
_ => return ExtractDecision::NotApplicable,
}
}
_ => return Ok(None),
_ => return ExtractDecision::NotApplicable,
};
(loop_var, Some(haystack), ScanDirection::Forward)
(loop_var, Some(haystack), PlanScanDirection::Forward)
}
// Reverse: i >= 0
ASTNode::BinaryOp {
@ -323,7 +262,7 @@ fn extract_scan_with_init_parts(
} => {
let loop_var = match left.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
_ => return Ok(None),
_ => return ExtractDecision::NotApplicable,
};
// Check right is Literal(0)
@ -332,13 +271,13 @@ fn extract_scan_with_init_parts(
value: LiteralValue::Integer(0),
..
} => {}
_ => return Ok(None),
_ => return ExtractDecision::NotApplicable,
}
// For reverse, haystack will be extracted from substring call in body
(loop_var, None, ScanDirection::Reverse)
(loop_var, None, PlanScanDirection::Reverse)
}
_ => return Ok(None),
_ => return ExtractDecision::NotApplicable,
};
// 2. Find if statement with substring == needle and return loop_var
@ -421,30 +360,36 @@ fn extract_scan_with_init_parts(
}
}
// Phase 273 P2: Return Ok(None) if pattern doesn't match (allow Pattern7 to try)
// Phase 273 P2: Return NotApplicable if pattern doesn't match (allow Pattern7 to try)
let needle = match needle_opt {
Some(n) => n,
None => return Ok(None), // Not Pattern6, try next pattern
None => return ExtractDecision::NotApplicable, // Not Pattern6, try next pattern
};
let early_return_expr = match early_return_expr_opt {
Some(e) => e,
None => return Ok(None), // Not Pattern6, try next pattern
None => return ExtractDecision::NotApplicable, // Not Pattern6, try next pattern
};
// Phase 257 P0: Determine haystack based on scan direction
let haystack = match scan_direction {
ScanDirection::Forward => haystack_opt.ok_or_else(|| {
ScanWithInitExtractError::contract(
"scan-with-init contract: forward scan missing haystack",
"use `i < s.length()` or `i <= s.length() - needle.length()` for forward scans",
)
})?,
ScanDirection::Reverse => haystack_from_substring_opt.ok_or_else(|| {
ScanWithInitExtractError::contract(
"scan-with-init contract: reverse scan missing haystack",
"use `s.substring(i, i + 1)` (or dynamic) in the match condition",
)
})?,
PlanScanDirection::Forward => match haystack_opt {
Some(value) => value,
None => {
return ExtractDecision::contract(
"scan-with-init contract: forward scan missing haystack",
"use `i < s.length()` or `i <= s.length() - needle.length()` for forward scans",
);
}
},
PlanScanDirection::Reverse => match haystack_from_substring_opt {
Some(value) => value,
None => {
return ExtractDecision::contract(
"scan-with-init contract: reverse scan missing haystack",
"use `s.substring(i, i + 1)` (or dynamic) in the match condition",
);
}
},
};
// 3. Check for step: i = i + 1 (forward) or i = i - 1 (reverse)
@ -486,29 +431,32 @@ fn extract_scan_with_init_parts(
}
}
let step_lit = step_lit_opt.ok_or_else(|| {
ScanWithInitExtractError::contract(
"scan-with-init contract: missing step update",
"add `i = i + 1` (forward) or `i = i - 1` (reverse) inside the loop",
)
})?;
let step_lit = match step_lit_opt {
Some(value) => value,
None => {
return ExtractDecision::contract(
"scan-with-init contract: missing step update",
"add `i = i + 1` (forward) or `i = i - 1` (reverse) inside the loop",
);
}
};
// Phase 257 P0: Verify step matches scan direction
match scan_direction {
ScanDirection::Forward => {
PlanScanDirection::Forward => {
if step_lit != 1 {
return Err(ScanWithInitExtractError::contract(
return ExtractDecision::contract(
"scan-with-init contract: forward step must be `i = i + 1`",
"change the step update to `i = i + 1`",
));
);
}
}
ScanDirection::Reverse => {
PlanScanDirection::Reverse => {
if step_lit != -1 {
return Err(ScanWithInitExtractError::contract(
return ExtractDecision::contract(
"scan-with-init contract: reverse step must be `i = i - 1`",
"change the step update to `i = i - 1`",
));
);
}
}
}
@ -519,7 +467,7 @@ fn extract_scan_with_init_parts(
// Phase 258 P0: Extract dynamic_needle (default to false for backward compat)
let dynamic_needle = dynamic_needle_opt.unwrap_or(false);
Ok(Some(ScanParts {
ExtractDecision::Match(ScanWithInitPlan {
loop_var,
haystack,
needle,
@ -528,7 +476,7 @@ fn extract_scan_with_init_parts(
not_found_return_lit,
scan_direction,
dynamic_needle,
}))
})
}
// Phase 273 P0.1: lower() removed (router now uses PlanLowerer::lower_scan_with_init())

View File

@ -28,32 +28,9 @@
//! - P0 restriction: 1-char separator only
use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::error_tags;
#[derive(Debug, Clone)]
struct SplitScanContractViolation {
msg: String,
hint: String,
}
#[derive(Debug, Clone)]
enum SplitScanExtractError {
NotApplicable(String),
Contract(SplitScanContractViolation),
}
impl SplitScanExtractError {
fn not_applicable(msg: &str) -> Self {
Self::NotApplicable(msg.to_string())
}
fn contract(msg: &str, hint: &str) -> Self {
Self::Contract(SplitScanContractViolation {
msg: msg.to_string(),
hint: hint.to_string(),
})
}
}
use crate::mir::builder::control_flow::joinir::patterns::common::{
finalize_extract, ContractViolation, ExtractDecision,
};
/// Phase 256 P0: Split/Scan pattern parts extractor
///
@ -89,24 +66,18 @@ pub(crate) fn extract_split_scan_plan(
use crate::mir::builder::control_flow::plan::{DomainPlan, SplitScanPlan};
// Try to extract using existing implementation
match extract_split_scan_parts(condition, body, &[]) {
Ok(parts) => {
let plan = SplitScanPlan {
s_var: parts.s_var,
sep_var: parts.sep_var,
result_var: parts.result_var,
i_var: parts.i_var,
start_var: parts.start_var,
};
Ok(Some(DomainPlan::SplitScan(plan)))
}
Err(SplitScanExtractError::NotApplicable(_)) => Ok(None), // Pattern doesn't match
Err(SplitScanExtractError::Contract(err)) => Err(error_tags::freeze_with_hint(
"phase29ab/pattern7/contract",
&err.msg,
&err.hint,
)),
}
let parts = finalize_extract(
extract_split_scan_parts(condition, body, &[]),
"phase29ab/pattern7/contract",
)?;
let plan = parts.map(|parts| SplitScanPlan {
s_var: parts.s_var,
sep_var: parts.sep_var,
result_var: parts.result_var,
i_var: parts.i_var,
start_var: parts.start_var,
});
Ok(plan.map(DomainPlan::SplitScan))
}
/// Phase 256 P0: Extract SplitScanParts from AST
@ -119,17 +90,19 @@ fn extract_split_scan_parts(
condition: &ASTNode,
body: &[ASTNode],
post_loop_code: &[ASTNode],
) -> Result<SplitScanParts, SplitScanExtractError> {
) -> ExtractDecision<SplitScanParts> {
// Step 1: Extract variables from loop condition
// Expected: i <= s.length() - separator.length()
let (i_var, s_var, sep_var) = extract_loop_condition_vars(condition)
.map_err(SplitScanExtractError::NotApplicable)?;
let (i_var, s_var, sep_var) = match extract_loop_condition_vars(condition) {
Ok(values) => values,
Err(_) => return ExtractDecision::NotApplicable,
};
// Step 2: Find the if statement in loop body
let if_stmt = body
.iter()
.find(|stmt| matches!(stmt, ASTNode::If { .. }))
.ok_or_else(|| SplitScanExtractError::not_applicable("extract_split_scan_parts: No if statement found in loop body"))?;
let if_stmt = match body.iter().find(|stmt| matches!(stmt, ASTNode::If { .. })) {
Some(stmt) => stmt,
None => return ExtractDecision::NotApplicable,
};
let (match_if_cond_ast, then_body, else_body) = match if_stmt {
ASTNode::If {
@ -138,57 +111,68 @@ fn extract_split_scan_parts(
else_body,
..
} => (condition.as_ref().clone(), then_body, else_body),
_ => return Err(SplitScanExtractError::not_applicable("extract_split_scan_parts: Invalid if statement")),
_ => return ExtractDecision::NotApplicable,
};
if let Err(err) = validate_separator_literal_len(&match_if_cond_ast) {
return ExtractDecision::Contract(err);
}
// Step 3: Extract push operation from then branch
let then_push_ast = then_body
let then_push_ast = match then_body
.iter()
.find(|stmt| matches!(stmt, ASTNode::MethodCall { method, .. } if method == "push"))
.ok_or_else(|| SplitScanExtractError::not_applicable("extract_split_scan_parts: No push() found in then branch"))?
.clone();
{
Some(stmt) => stmt.clone(),
None => return ExtractDecision::NotApplicable,
};
// Step 4: Extract start assignment (start = i + separator.length())
let then_start_next_ast = then_body
let then_start_next_ast = match then_body
.iter()
.find(|stmt| {
matches!(stmt, ASTNode::Assignment { target, .. } if {
matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == "start")
})
})
.ok_or_else(|| SplitScanExtractError::not_applicable("extract_split_scan_parts: No 'start = ...' assignment in then branch"))?
.clone();
}) {
Some(stmt) => stmt.clone(),
None => return ExtractDecision::NotApplicable,
};
// Step 5: Extract i assignment (i = start or i = variable)
let then_i_next_ast = then_body
.iter()
.find(|stmt| {
matches!(stmt, ASTNode::Assignment { target, value, .. } if {
matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == "i")
&& matches!(value.as_ref(), ASTNode::Variable { .. })
})
let then_i_next_ast = match then_body.iter().find(|stmt| {
matches!(stmt, ASTNode::Assignment { target, value, .. } if {
matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == "i")
&& matches!(value.as_ref(), ASTNode::Variable { .. })
})
.ok_or_else(|| SplitScanExtractError::not_applicable("extract_split_scan_parts: No 'i = variable' assignment in then branch"))?
.clone();
}) {
Some(stmt) => stmt.clone(),
None => return ExtractDecision::NotApplicable,
};
// Step 6: Extract else branch assignment (i = i + 1)
let else_i_next_ast = if let Some(else_statements) = else_body {
else_statements
.iter()
.find(|stmt| {
matches!(stmt, ASTNode::Assignment { target, .. } if {
matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == "i")
})
match else_statements.iter().find(|stmt| {
matches!(stmt, ASTNode::Assignment { target, .. } if {
matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == "i")
})
.ok_or_else(|| SplitScanExtractError::not_applicable("extract_split_scan_parts: No 'i = ...' assignment in else branch"))?
.clone()
}) {
Some(stmt) => stmt.clone(),
None => return ExtractDecision::NotApplicable,
}
} else {
return Err(SplitScanExtractError::not_applicable("extract_split_scan_parts: No else branch found"));
return ExtractDecision::NotApplicable;
};
validate_start_update(&then_start_next_ast, &i_var, &sep_var)?;
validate_i_set_to_start(&then_i_next_ast, &i_var)?;
validate_i_increment_by_one(&else_i_next_ast, &i_var)?;
if let Err(err) = validate_start_update(&then_start_next_ast, &i_var, &sep_var) {
return ExtractDecision::Contract(err);
}
if let Err(err) = validate_i_set_to_start(&then_i_next_ast, &i_var) {
return ExtractDecision::Contract(err);
}
if let Err(err) = validate_i_increment_by_one(&else_i_next_ast, &i_var) {
return ExtractDecision::Contract(err);
}
// Step 7: Extract post-loop push (result.push(...))
let post_push_ast = post_loop_code
@ -197,10 +181,12 @@ fn extract_split_scan_parts(
.cloned();
// Step 8: Extract result variable from push statements
let result_var = extract_result_var(&then_push_ast)
.map_err(SplitScanExtractError::NotApplicable)?;
let result_var = match extract_result_var(&then_push_ast) {
Ok(value) => value,
Err(_) => return ExtractDecision::NotApplicable,
};
Ok(SplitScanParts {
ExtractDecision::Match(SplitScanParts {
s_var,
sep_var,
result_var,
@ -292,14 +278,14 @@ fn validate_start_update(
assign: &ASTNode,
i_var: &str,
sep_var: &str,
) -> Result<(), SplitScanExtractError> {
) -> Result<(), ContractViolation> {
use crate::ast::BinaryOperator;
let hint = "use `start = i + separator.length()` in the then-branch";
match assign {
ASTNode::Assignment { target, value, .. } => {
if !matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == "start") {
return Err(SplitScanExtractError::contract(
return Err(ContractViolation::new(
"split scan contract: start target must be `start`",
hint,
));
@ -322,26 +308,26 @@ fn validate_start_update(
if left_ok && right_ok {
Ok(())
} else {
Err(SplitScanExtractError::contract(
Err(ContractViolation::new(
"split scan contract: start update must be `i + separator.length()`",
hint,
))
}
}
_ => Err(SplitScanExtractError::contract(
_ => Err(ContractViolation::new(
"split scan contract: start update must be `i + separator.length()`",
hint,
)),
}
}
_ => Err(SplitScanExtractError::contract(
_ => Err(ContractViolation::new(
"split scan contract: expected start assignment",
hint,
)),
}
}
fn validate_i_set_to_start(assign: &ASTNode, i_var: &str) -> Result<(), SplitScanExtractError> {
fn validate_i_set_to_start(assign: &ASTNode, i_var: &str) -> Result<(), ContractViolation> {
let hint = "use `i = start` in the then-branch";
match assign {
ASTNode::Assignment { target, value, .. } => {
@ -350,13 +336,13 @@ fn validate_i_set_to_start(assign: &ASTNode, i_var: &str) -> Result<(), SplitSca
if target_ok && value_ok {
Ok(())
} else {
Err(SplitScanExtractError::contract(
Err(ContractViolation::new(
"split scan contract: then i update must be `i = start`",
hint,
))
}
}
_ => Err(SplitScanExtractError::contract(
_ => Err(ContractViolation::new(
"split scan contract: expected then i assignment",
hint,
)),
@ -366,7 +352,7 @@ fn validate_i_set_to_start(assign: &ASTNode, i_var: &str) -> Result<(), SplitSca
fn validate_i_increment_by_one(
assign: &ASTNode,
i_var: &str,
) -> Result<(), SplitScanExtractError> {
) -> Result<(), ContractViolation> {
use crate::ast::{BinaryOperator, LiteralValue};
let hint = "use `i = i + 1` in the else-branch";
@ -385,19 +371,51 @@ fn validate_i_increment_by_one(
if target_ok && value_ok {
Ok(())
} else {
Err(SplitScanExtractError::contract(
Err(ContractViolation::new(
"split scan contract: else i update must be `i = i + 1`",
hint,
))
}
}
_ => Err(SplitScanExtractError::contract(
_ => Err(ContractViolation::new(
"split scan contract: expected else i assignment",
hint,
)),
}
}
fn validate_separator_literal_len(cond: &ASTNode) -> Result<(), ContractViolation> {
use crate::ast::{BinaryOperator, LiteralValue};
let hint = "use a 1-char separator (e.g. \",\") or avoid split-scan patterns";
let (left, right) = match cond {
ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left,
right,
..
} => (left.as_ref(), right.as_ref()),
_ => return Ok(()),
};
let literal = match (left, right) {
(ASTNode::Literal { value: LiteralValue::String(s), .. }, _) => Some(s),
(_, ASTNode::Literal { value: LiteralValue::String(s), .. }) => Some(s),
_ => None,
};
if let Some(value) = literal {
if value.chars().count() != 1 {
return Err(ContractViolation::new(
"split scan contract: separator must be 1 char (P0)",
hint,
));
}
}
Ok(())
}
/// Extract result variable name from push call
/// Expected: result.push(...)
fn extract_result_var(push_stmt: &ASTNode) -> Result<String, String> {

View File

@ -0,0 +1,62 @@
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{compose, EdgeStub, ExitKind, Frag};
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use std::collections::BTreeMap;
pub(super) fn empty_args() -> EdgeArgs {
EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
}
}
pub(super) fn normal_exit_frag(
entry: crate::mir::BasicBlockId,
args: &EdgeArgs,
) -> Frag {
let mut exits = BTreeMap::new();
exits.insert(
ExitKind::Normal,
vec![EdgeStub {
from: entry,
kind: ExitKind::Normal,
target: None,
args: args.clone(),
}],
);
Frag {
entry,
exits,
wires: vec![],
branches: vec![],
}
}
pub(super) fn build_body_if_frag(
body_bb: crate::mir::BasicBlockId,
cond: crate::mir::ValueId,
then_bb: crate::mir::BasicBlockId,
else_bb: crate::mir::BasicBlockId,
step_bb: crate::mir::BasicBlockId,
empty_args: &EdgeArgs,
) -> Frag {
let then_frag = normal_exit_frag(then_bb, empty_args);
let else_frag = normal_exit_frag(else_bb, empty_args);
let step_frag = Frag {
entry: step_bb,
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],
};
compose::if_(
body_bb,
cond,
then_frag,
empty_args.clone(),
else_frag,
empty_args.clone(),
step_frag,
)
}

View File

@ -21,6 +21,7 @@ mod pattern8_bool_predicate_scan;
mod pattern9_accum_const_loop;
mod pattern_scan_with_init;
mod pattern_split_scan;
mod common;
use super::{
CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, DomainPlan, Pattern1SimpleWhilePlan,

View File

@ -1,11 +1,10 @@
use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, SplitScanPlan};
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::{BinaryOp, CompareOp, ConstValue, Effect, EffectMask, MirType};
use std::collections::BTreeMap;
use super::common::{build_body_if_frag, empty_args};
impl super::PlanNormalizer {
/// SplitScan → CorePlan 変換
@ -21,8 +20,6 @@ impl super::PlanNormalizer {
ctx: &LoopPatternContext,
) -> Result<CorePlan, String> {
use crate::mir::builder::control_flow::joinir::trace;
use crate::mir::builder::control_flow::edgecfg::api::compose;
let trace_logger = trace::trace();
let debug = ctx.debug;
@ -279,61 +276,9 @@ impl super::PlanNormalizer {
];
// Step 9.5: Build Frags for compose::if_() (Phase 281 P0)
let empty_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
};
let empty_args = empty_args();
let mut then_exits = BTreeMap::new();
then_exits.insert(
ExitKind::Normal,
vec![EdgeStub {
from: then_bb,
kind: ExitKind::Normal,
target: None,
args: empty_args.clone(),
}],
);
let then_frag = Frag {
entry: then_bb,
exits: then_exits,
wires: vec![],
branches: vec![],
};
let mut else_exits = BTreeMap::new();
else_exits.insert(
ExitKind::Normal,
vec![EdgeStub {
from: else_bb,
kind: ExitKind::Normal,
target: None,
args: empty_args.clone(),
}],
);
let else_frag = Frag {
entry: else_bb,
exits: else_exits,
wires: vec![],
branches: vec![],
};
let step_frag = Frag {
entry: step_bb,
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],
};
let body_if_frag = compose::if_(
body_bb,
cond_match,
then_frag,
empty_args.clone(),
else_frag,
empty_args.clone(),
step_frag,
);
let body_if_frag = build_body_if_frag(body_bb, cond_match, then_bb, else_bb, step_bb, &empty_args);
// Step 10: Build block_effects
let block_effects = vec![