#![cfg_attr(coverage_nightly, coverage(off))]
use crate::models::complexity_bound::{
ComplexityBound, ComplexityFlags, RecurrenceRelation, RecursiveCall,
};
use crate::models::unified_ast::{AstKind, ExprKind, StmtKind, UnifiedAstNode};
use std::collections::HashMap;
use tracing::debug;
pub struct ComplexityPatternMatcher {
patterns: HashMap<String, ComplexityPattern>,
}
#[derive(Debug, Clone)]
pub struct ComplexityPattern {
pub name: String,
pub description: String,
pub complexity: ComplexityBound,
pub pattern_type: PatternType,
}
#[derive(Debug, Clone)]
pub enum PatternType {
LinearIteration,
NestedLoops { depth: u32 },
BinarySearch,
DivideAndConquer { divisions: u32 },
DynamicProgramming,
Recursive(RecurrenceRelation),
HashTableOp,
TreeTraversal,
GraphAlgorithm { vertices: bool, edges: bool },
}
impl ComplexityPatternMatcher {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
let mut patterns = HashMap::new();
patterns.insert(
"linear_iteration".to_string(),
ComplexityPattern {
name: "Linear Iteration".to_string(),
description: "Single loop over collection".to_string(),
complexity: ComplexityBound::linear()
.with_confidence(90)
.with_flags(ComplexityFlags::WORST_CASE),
pattern_type: PatternType::LinearIteration,
},
);
patterns.insert(
"nested_loops_2".to_string(),
ComplexityPattern {
name: "Nested Loops (Depth 2)".to_string(),
description: "Two nested loops over same collection".to_string(),
complexity: ComplexityBound::quadratic()
.with_confidence(85)
.with_flags(ComplexityFlags::WORST_CASE),
pattern_type: PatternType::NestedLoops { depth: 2 },
},
);
patterns.insert(
"binary_search".to_string(),
ComplexityPattern {
name: "Binary Search".to_string(),
description: "Divide search space by 2 each iteration".to_string(),
complexity: ComplexityBound::logarithmic()
.with_confidence(95)
.with_flags(ComplexityFlags::WORST_CASE | ComplexityFlags::PROVEN),
pattern_type: PatternType::BinarySearch,
},
);
patterns.insert(
"merge_sort".to_string(),
ComplexityPattern {
name: "Merge Sort".to_string(),
description: "Divide and conquer with linear merge".to_string(),
complexity: ComplexityBound::linearithmic()
.with_confidence(95)
.with_flags(ComplexityFlags::WORST_CASE | ComplexityFlags::PROVEN),
pattern_type: PatternType::DivideAndConquer { divisions: 2 },
},
);
patterns.insert(
"hash_lookup".to_string(),
ComplexityPattern {
name: "Hash Table Lookup".to_string(),
description: "Average case constant time lookup".to_string(),
complexity: ComplexityBound::constant()
.with_confidence(80)
.with_flags(ComplexityFlags::AVERAGE_CASE | ComplexityFlags::AMORTIZED),
pattern_type: PatternType::HashTableOp,
},
);
Self { patterns }
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn match_pattern(&self, ast: &UnifiedAstNode) -> Option<&ComplexityPattern> {
for pattern in self.patterns.values() {
if self.matches_pattern(ast, pattern) {
debug!("Matched pattern: {} for AST node", pattern.name);
return Some(pattern);
}
}
None
}
fn matches_pattern(&self, ast: &UnifiedAstNode, pattern: &ComplexityPattern) -> bool {
match &pattern.pattern_type {
PatternType::LinearIteration => self.is_linear_iteration(ast),
PatternType::NestedLoops { depth } => self.is_nested_loops(ast, *depth),
PatternType::BinarySearch => self.is_binary_search(ast),
PatternType::DivideAndConquer { divisions } => {
self.is_divide_and_conquer(ast, *divisions)
}
PatternType::HashTableOp => self.is_hash_operation(ast),
_ => false, }
}
fn is_linear_iteration(&self, ast: &UnifiedAstNode) -> bool {
match &ast.kind {
AstKind::Statement(stmt) => {
matches!(
stmt,
StmtKind::For | StmtKind::While | StmtKind::DoWhile | StmtKind::ForEach
)
}
AstKind::Expression(expr) => {
matches!(
expr,
ExprKind::Call )
}
_ => false,
}
}
fn is_nested_loops(&self, ast: &UnifiedAstNode, target_depth: u32) -> bool {
self.count_loop_depth(ast) >= target_depth
}
fn count_loop_depth(&self, ast: &UnifiedAstNode) -> u32 {
let is_loop = self.is_linear_iteration(ast);
let mut max_child_depth = 0;
if ast.first_child != 0 {
max_child_depth = 1;
}
if is_loop {
1 + max_child_depth
} else {
max_child_depth
}
}
fn is_binary_search(&self, ast: &UnifiedAstNode) -> bool {
if let AstKind::Function(_) = &ast.kind {
if let Some(name) = self.get_function_name(ast) {
let lower_name = name.to_lowercase();
return lower_name.contains("binary") && lower_name.contains("search");
}
}
false
}
fn is_divide_and_conquer(&self, ast: &UnifiedAstNode, _expected_divisions: u32) -> bool {
if let AstKind::Function(_) = &ast.kind {
if let Some(name) = self.get_function_name(ast) {
let lower_name = name.to_lowercase();
return (lower_name.contains("merge") && lower_name.contains("sort"))
|| (lower_name.contains("quick") && lower_name.contains("sort"));
}
}
false
}
fn is_hash_operation(&self, ast: &UnifiedAstNode) -> bool {
match &ast.kind {
AstKind::Expression(expr) => {
matches!(expr, ExprKind::Member | ExprKind::Call)
}
_ => false,
}
}
fn get_function_name(&self, ast: &UnifiedAstNode) -> Option<String> {
match &ast.kind {
AstKind::Function(_) => {
None
}
_ => None,
}
}
#[inline]
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn analyze_recursion(&self, ast: &UnifiedAstNode) -> Option<RecurrenceRelation> {
if !self.is_recursive_function(ast) {
return None;
}
let recursive_calls = self.find_recursive_calls(ast);
let work_complexity = self.estimate_non_recursive_work(ast);
if recursive_calls.is_empty() {
return None;
}
Some(RecurrenceRelation {
recursive_calls,
work_per_call: work_complexity,
base_case_size: 1, })
}
fn is_recursive_function(&self, ast: &UnifiedAstNode) -> bool {
if let AstKind::Function(_) = &ast.kind {
return false; }
false
}
fn find_recursive_calls(&self, _ast: &UnifiedAstNode) -> Vec<RecursiveCall> {
Vec::new() }
fn estimate_non_recursive_work(&self, _ast: &UnifiedAstNode) -> ComplexityBound {
ComplexityBound::linear() }
#[inline]
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn analyze_loop_complexity(&self, ast: &UnifiedAstNode) -> ComplexityBound {
let loop_depth = self.count_loop_depth(ast);
match loop_depth {
0 => ComplexityBound::constant(),
1 => ComplexityBound::linear(),
2 => ComplexityBound::quadratic(),
3 => ComplexityBound::polynomial(3, 1),
_ => ComplexityBound::polynomial(loop_depth, 1),
}
.with_confidence(80 - (loop_depth * 10).min(50) as u8)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn add_pattern(&mut self, id: String, pattern: ComplexityPattern) {
self.patterns.insert(id, pattern);
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn patterns(&self) -> &HashMap<String, ComplexityPattern> {
&self.patterns
}
}
impl Default for ComplexityPatternMatcher {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct ComplexityAnalysisResult {
pub time_complexity: ComplexityBound,
pub space_complexity: ComplexityBound,
pub matched_patterns: Vec<String>,
pub confidence: u8,
pub notes: Vec<String>,
}
impl ComplexityAnalysisResult {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new(time: ComplexityBound, space: ComplexityBound) -> Self {
Self {
time_complexity: time,
space_complexity: space,
matched_patterns: Vec::new(),
confidence: (time.confidence + space.confidence) / 2,
notes: Vec::new(),
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn with_pattern(mut self, pattern_name: String) -> Self {
self.matched_patterns.push(pattern_name);
self
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn with_note(mut self, note: String) -> Self {
self.notes.push(note);
self
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use crate::models::complexity_bound::BigOClass;
#[test]
fn test_pattern_matcher_creation() {
let matcher = ComplexityPatternMatcher::new();
assert!(matcher.patterns.contains_key("linear_iteration"));
assert!(matcher.patterns.contains_key("binary_search"));
assert!(matcher.patterns.contains_key("merge_sort"));
}
#[test]
fn test_complexity_bound_properties() {
let pattern = &ComplexityPatternMatcher::new().patterns["binary_search"];
assert_eq!(pattern.complexity.class, BigOClass::Logarithmic);
assert!(pattern.complexity.confidence >= 90);
assert!(pattern.complexity.flags.is_worst_case());
}
#[test]
fn test_loop_complexity_analysis() {
let matcher = ComplexityPatternMatcher::new();
let ast = UnifiedAstNode {
kind: AstKind::Statement(StmtKind::For),
lang: crate::models::unified_ast::Language::JavaScript,
first_child: 0,
next_sibling: 0,
semantic_hash: 0,
structural_hash: 0,
name_vector: 0,
parent: 0,
source_range: 0..10,
flags: Default::default(),
metadata: Default::default(),
proof_annotations: None,
};
let result = matcher.analyze_loop_complexity(&ast);
assert_eq!(result.class, BigOClass::Linear);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}