docs(phase29ab): closeout P1-P9
This commit is contained in:
@ -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)),
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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> {
|
||||
|
||||
62
src/mir/builder/control_flow/plan/normalizer/common.rs
Normal file
62
src/mir/builder/control_flow/plan/normalizer/common.rs
Normal 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,
|
||||
)
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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![
|
||||
|
||||
Reference in New Issue
Block a user