phase29ao(p32): strict adopt pattern2 realworld from facts
This commit is contained in:
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user