phase29ao(p32): strict adopt pattern2 realworld from facts

This commit is contained in:
2025-12-30 15:28:40 +09:00
parent 99c3a935bb
commit dd7f923b88
9 changed files with 587 additions and 17 deletions

View File

@ -1,10 +1,11 @@
//! Phase 29ai P11: Pattern2BreakFacts (Facts SSOT)
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::mir::builder::control_flow::plan::planner::Freeze;
use crate::mir::builder::control_flow::plan::extractors::common_helpers::{
count_control_flow, has_continue_statement as common_has_continue,
has_return_statement as common_has_return, ControlFlowDetector, extract_loop_increment_plan,
is_true_literal,
};
#[derive(Debug, Clone)]
@ -22,6 +23,10 @@ pub(in crate::mir::builder) fn try_extract_pattern2_break_facts(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<Pattern2BreakFacts>, Freeze> {
if let Some(realworld) = try_extract_pattern2_break_realworld_subset(condition, body) {
return Ok(Some(realworld));
}
let Some(loop_var) = extract_loop_var_for_plan_subset(condition) else {
return Ok(None);
};
@ -74,6 +79,79 @@ pub(in crate::mir::builder) fn try_extract_pattern2_break_facts(
}))
}
fn try_extract_pattern2_break_realworld_subset(
condition: &ASTNode,
body: &[ASTNode],
) -> Option<Pattern2BreakFacts> {
if !is_true_literal(condition) {
return None;
}
let counts = count_control_flow(body, ControlFlowDetector::default());
if counts.break_count != 1 || counts.continue_count > 0 || counts.return_count > 0 {
return None;
}
if body.len() != 5 {
return None;
}
let (j_var, haystack_var, sep_lit, loop_var) =
match_indexof_local(&body[0])?;
let seg_var = match_local_empty_string(&body[1])?;
if !match_seg_if_else(
&body[2],
&j_var,
&seg_var,
&haystack_var,
&loop_var,
)? {
return None;
}
if !match_break_if(&body[3], &seg_var)? {
return None;
}
let sep_len = sep_lit.len() as i64;
if !match_loop_increment(&body[4], &loop_var, &j_var, sep_len)? {
return None;
}
let loop_condition = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(var(&loop_var)),
right: Box::new(length_call(&haystack_var)),
span: Span::unknown(),
};
let index_expr = index_of_call(&haystack_var, &sep_lit, &loop_var);
let break_condition = ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(substring_call(&haystack_var, var(&loop_var), index_expr.clone())),
right: Box::new(lit_str("")),
span: Span::unknown(),
};
let loop_increment = ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(index_expr),
right: Box::new(lit_int(sep_len)),
span: Span::unknown(),
};
Some(Pattern2BreakFacts {
loop_var: loop_var.clone(),
carrier_var: loop_var,
loop_condition,
break_condition,
carrier_update_in_break: None,
carrier_update_in_body: loop_increment.clone(),
loop_increment,
})
}
fn extract_loop_var_for_plan_subset(condition: &ASTNode) -> Option<String> {
let ASTNode::BinaryOp {
operator: BinaryOperator::Less,
@ -136,6 +214,338 @@ fn extract_break_if_parts(stmt: &ASTNode) -> Option<(ASTNode, Option<ASTNode>)>
Some((condition.as_ref().clone(), carrier_update_in_break))
}
fn match_indexof_local(stmt: &ASTNode) -> Option<(String, String, String, String)> {
let ASTNode::Local {
variables,
initial_values,
..
} = stmt
else {
return None;
};
if variables.len() != 1 || initial_values.len() != 1 {
return None;
}
let j_var = variables[0].clone();
let Some(expr) = initial_values[0].as_ref() else {
return None;
};
let ASTNode::MethodCall {
object,
method,
arguments,
..
} = expr.as_ref()
else {
return None;
};
if method != "indexOf" || arguments.len() != 2 {
return None;
}
let ASTNode::Variable { name: haystack_var, .. } = object.as_ref() else {
return None;
};
let ASTNode::Literal {
value: LiteralValue::String(sep_lit),
..
} = &arguments[0]
else {
return None;
};
let ASTNode::Variable { name: loop_var, .. } = &arguments[1] else {
return None;
};
Some((
j_var,
haystack_var.clone(),
sep_lit.clone(),
loop_var.clone(),
))
}
fn match_local_empty_string(stmt: &ASTNode) -> Option<String> {
let ASTNode::Local {
variables,
initial_values,
..
} = stmt
else {
return None;
};
if variables.len() != 1 || initial_values.len() != 1 {
return None;
}
let seg_var = variables[0].clone();
let Some(expr) = initial_values[0].as_ref() else {
return None;
};
let ASTNode::Literal {
value: LiteralValue::String(value),
..
} = expr.as_ref()
else {
return None;
};
if value != "" {
return None;
}
Some(seg_var)
}
fn match_seg_if_else(
stmt: &ASTNode,
j_var: &str,
seg_var: &str,
haystack_var: &str,
loop_var: &str,
) -> Option<bool> {
let ASTNode::If {
condition,
then_body,
else_body,
..
} = stmt
else {
return None;
};
let else_body = else_body.as_ref()?;
if then_body.len() != 1 || else_body.len() != 1 {
return None;
}
if !matches_ge_zero(condition.as_ref(), j_var) {
return None;
}
let then_expr = extract_substring_assignment(&then_body[0], seg_var, haystack_var)?;
let else_expr = extract_substring_assignment(&else_body[0], seg_var, haystack_var)?;
if !matches_substring_args(&then_expr, loop_var, Some(j_var), None) {
return None;
}
if !matches_substring_args(&else_expr, loop_var, None, Some(haystack_var)) {
return None;
}
Some(true)
}
fn extract_substring_assignment(
stmt: &ASTNode,
seg_var: &str,
haystack_var: &str,
) -> Option<ASTNode> {
let ASTNode::Assignment { target, value, .. } = stmt else {
return None;
};
let ASTNode::Variable { name, .. } = target.as_ref() else {
return None;
};
if name != seg_var {
return None;
}
let ASTNode::MethodCall {
object,
method,
arguments,
..
} = value.as_ref()
else {
return None;
};
if method != "substring" || arguments.len() != 2 {
return None;
}
let ASTNode::Variable { name: obj_name, .. } = object.as_ref() else {
return None;
};
if obj_name != haystack_var {
return None;
}
Some(value.as_ref().clone())
}
fn matches_substring_args(
expr: &ASTNode,
loop_var: &str,
end_var: Option<&str>,
end_length_of: Option<&str>,
) -> bool {
let ASTNode::MethodCall { arguments, .. } = expr else {
return false;
};
if arguments.len() != 2 {
return false;
}
let ASTNode::Variable { name: start_var, .. } = &arguments[0] else {
return false;
};
if start_var != loop_var {
return false;
}
match (&arguments[1], end_var, end_length_of) {
(ASTNode::Variable { name, .. }, Some(var), None) => name == var,
(ASTNode::MethodCall { object, method, arguments, .. }, None, Some(owner)) => {
if method != "length" || !arguments.is_empty() {
return false;
}
matches!(object.as_ref(), ASTNode::Variable { name, .. } if name == owner)
}
_ => false,
}
}
fn match_break_if(stmt: &ASTNode, seg_var: &str) -> Option<bool> {
let ASTNode::If {
condition,
then_body,
else_body,
..
} = stmt
else {
return None;
};
if else_body.is_some() {
return None;
}
if then_body.len() != 1 || !matches!(then_body[0], ASTNode::Break { .. }) {
return None;
}
if !matches_eq_empty_string(condition.as_ref(), seg_var) {
return None;
}
Some(true)
}
fn match_loop_increment(
stmt: &ASTNode,
loop_var: &str,
j_var: &str,
sep_len: i64,
) -> Option<bool> {
let ASTNode::Assignment { target, value, .. } = stmt else {
return None;
};
let ASTNode::Variable { name, .. } = target.as_ref() else {
return None;
};
if name != loop_var {
return None;
}
let ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left,
right,
..
} = value.as_ref()
else {
return None;
};
if !matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == j_var) {
return None;
}
if !matches!(right.as_ref(), ASTNode::Literal { value: LiteralValue::Integer(v), .. } if *v == sep_len) {
return None;
}
Some(true)
}
fn matches_eq_empty_string(node: &ASTNode, var_name: &str) -> bool {
let ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left,
right,
..
} = node
else {
return false;
};
matches_eq_empty_string_sides(left.as_ref(), right.as_ref(), var_name)
|| matches_eq_empty_string_sides(right.as_ref(), left.as_ref(), var_name)
}
fn matches_eq_empty_string_sides(var_node: &ASTNode, lit_node: &ASTNode, var_name: &str) -> bool {
if !matches!(var_node, ASTNode::Variable { name, .. } if name == var_name) {
return false;
}
matches!(
lit_node,
ASTNode::Literal {
value: LiteralValue::String(value),
..
} if value.is_empty()
)
}
fn matches_ge_zero(node: &ASTNode, var_name: &str) -> bool {
let ASTNode::BinaryOp {
operator: BinaryOperator::GreaterEqual,
left,
right,
..
} = node
else {
return false;
};
matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == var_name)
&& matches!(
right.as_ref(),
ASTNode::Literal {
value: LiteralValue::Integer(0),
..
}
)
}
fn var(name: &str) -> ASTNode {
ASTNode::Variable {
name: name.to_string(),
span: Span::unknown(),
}
}
fn lit_int(value: i64) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Integer(value),
span: Span::unknown(),
}
}
fn lit_str(value: &str) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::String(value.to_string()),
span: Span::unknown(),
}
}
fn length_call(obj: &str) -> ASTNode {
ASTNode::MethodCall {
object: Box::new(var(obj)),
method: "length".to_string(),
arguments: vec![],
span: Span::unknown(),
}
}
fn index_of_call(haystack: &str, sep: &str, loop_var: &str) -> ASTNode {
ASTNode::MethodCall {
object: Box::new(var(haystack)),
method: "indexOf".to_string(),
arguments: vec![lit_str(sep), var(loop_var)],
span: Span::unknown(),
}
}
fn substring_call(haystack: &str, start: ASTNode, end: ASTNode) -> ASTNode {
ASTNode::MethodCall {
object: Box::new(var(haystack)),
method: "substring".to_string(),
arguments: vec![start, end],
span: Span::unknown(),
}
}
fn has_continue_statement(body: &[ASTNode]) -> bool {
common_has_continue(body)
}
@ -143,3 +553,151 @@ fn has_continue_statement(body: &[ASTNode]) -> bool {
fn has_return_statement(body: &[ASTNode]) -> bool {
common_has_return(body)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Span;
fn v(name: &str) -> ASTNode {
ASTNode::Variable {
name: name.to_string(),
span: Span::unknown(),
}
}
fn lit_int(value: i64) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Integer(value),
span: Span::unknown(),
}
}
fn lit_bool(value: bool) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Bool(value),
span: Span::unknown(),
}
}
fn lit_str(value: &str) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::String(value.to_string()),
span: Span::unknown(),
}
}
fn local(name: &str, value: ASTNode) -> ASTNode {
ASTNode::Local {
variables: vec![name.to_string()],
initial_values: vec![Some(Box::new(value))],
span: Span::unknown(),
}
}
fn assign(name: &str, value: ASTNode) -> ASTNode {
ASTNode::Assignment {
target: Box::new(v(name)),
value: Box::new(value),
span: Span::unknown(),
}
}
fn method_call(obj: &str, method: &str, args: Vec<ASTNode>) -> ASTNode {
ASTNode::MethodCall {
object: Box::new(v(obj)),
method: method.to_string(),
arguments: args,
span: Span::unknown(),
}
}
fn binop(operator: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator,
left: Box::new(left),
right: Box::new(right),
span: Span::unknown(),
}
}
#[test]
fn extract_pattern2_break_realworld_subset() {
let condition = lit_bool(true);
let body = vec![
local(
"j",
method_call("table", "indexOf", vec![lit_str("|||"), v("i")]),
),
local("seg", lit_str("")),
ASTNode::If {
condition: Box::new(binop(
BinaryOperator::GreaterEqual,
v("j"),
lit_int(0),
)),
then_body: vec![assign(
"seg",
method_call("table", "substring", vec![v("i"), v("j")]),
)],
else_body: Some(vec![assign(
"seg",
method_call("table", "substring", vec![v("i"), method_call("table", "length", vec![])]),
)]),
span: Span::unknown(),
},
ASTNode::If {
condition: Box::new(binop(BinaryOperator::Equal, v("seg"), lit_str(""))),
then_body: vec![ASTNode::Break { span: Span::unknown() }],
else_body: None,
span: Span::unknown(),
},
assign("i", binop(BinaryOperator::Add, v("j"), lit_int(3))),
];
let facts = try_extract_pattern2_break_facts(&condition, &body)
.expect("Ok")
.expect("Some facts");
assert_eq!(facts.loop_var, "i");
assert_eq!(facts.carrier_var, "i");
match facts.loop_condition {
ASTNode::BinaryOp { operator: BinaryOperator::Less, left, right, .. } => {
assert!(matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == "i"));
assert!(matches!(
right.as_ref(),
ASTNode::MethodCall { method, .. } if method == "length"
));
}
other => panic!("unexpected loop_condition: {:?}", other),
}
match facts.break_condition {
ASTNode::BinaryOp { operator: BinaryOperator::Equal, left, right, .. } => {
assert!(matches!(
right.as_ref(),
ASTNode::Literal { value: LiteralValue::String(value), .. } if value.is_empty()
));
assert!(matches!(
left.as_ref(),
ASTNode::MethodCall { method, .. } if method == "substring"
));
}
other => panic!("unexpected break_condition: {:?}", other),
}
match facts.loop_increment {
ASTNode::BinaryOp { operator: BinaryOperator::Add, left, right, .. } => {
assert!(matches!(
left.as_ref(),
ASTNode::MethodCall { method, .. } if method == "indexOf"
));
assert!(matches!(
right.as_ref(),
ASTNode::Literal { value: LiteralValue::Integer(3), .. }
));
}
other => panic!("unexpected loop_increment: {:?}", other),
}
}
}