#![cfg_attr(coverage_nightly, coverage(off))]
use super::types::Breakpoint;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone)]
struct BreakpointMetadata {
breakpoint: Breakpoint,
hit_count: u64,
}
#[derive(Debug)]
pub struct BreakpointManager {
breakpoints: HashMap<String, HashMap<i64, BreakpointMetadata>>,
}
impl BreakpointManager {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self {
breakpoints: HashMap::new(),
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn set_breakpoint(&mut self, breakpoint: Breakpoint) -> Result<(), String> {
let source = breakpoint.source.clone();
let line = breakpoint.line;
let file_breakpoints = self.breakpoints.entry(source).or_default();
let metadata = BreakpointMetadata {
breakpoint,
hit_count: 0,
};
file_breakpoints.insert(line, metadata);
Ok(())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn remove_breakpoint(&mut self, source: &str, line: i64) -> Result<(), String> {
if let Some(file_breakpoints) = self.breakpoints.get_mut(source) {
file_breakpoints.remove(&line);
if file_breakpoints.is_empty() {
self.breakpoints.remove(source);
}
Ok(())
} else {
Err(format!("No breakpoints in file: {}", source))
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn has_breakpoint(&self, source: &str, line: i64) -> bool {
self.breakpoints
.get(source)
.map(|file_bp| file_bp.contains_key(&line))
.unwrap_or(false)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_breakpoint(&self, source: &str, line: i64) -> Option<Breakpoint> {
self.breakpoints
.get(source)
.and_then(|file_bp| file_bp.get(&line))
.map(|metadata| metadata.breakpoint.clone())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn count(&self) -> usize {
self.breakpoints.values().map(|file_bp| file_bp.len()).sum()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn breakpoints_in_file(&self, source: &str) -> Vec<Breakpoint> {
self.breakpoints
.get(source)
.map(|file_bp| {
file_bp
.values()
.map(|metadata| metadata.breakpoint.clone())
.collect()
})
.unwrap_or_default()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn clear_all(&mut self) {
self.breakpoints.clear();
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn clear_file(&mut self, source: &str) {
self.breakpoints.remove(source);
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn all_breakpoints(&self) -> Vec<Breakpoint> {
self.breakpoints
.values()
.flat_map(|file_bp| file_bp.values().map(|metadata| metadata.breakpoint.clone()))
.collect()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn record_hit(&mut self, source: &str, line: i64) {
if let Some(file_breakpoints) = self.breakpoints.get_mut(source) {
if let Some(metadata) = file_breakpoints.get_mut(&line) {
metadata.hit_count += 1;
}
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_hit_count(&self, source: &str, line: i64) -> u64 {
self.breakpoints
.get(source)
.and_then(|file_bp| file_bp.get(&line))
.map(|metadata| metadata.hit_count)
.unwrap_or(0)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn should_break(&self, source: &str, line: i64, variables: Option<&Value>) -> bool {
if let Some(breakpoint) = self.get_breakpoint(source, line) {
if let Some(condition) = &breakpoint.condition {
if let Some(vars) = variables {
return self.evaluate_condition(condition, vars);
}
return false;
}
return true;
}
false
}
fn evaluate_condition(&self, condition: &str, variables: &Value) -> bool {
let condition = condition.trim();
if let Some((var, value)) = parse_equality(condition) {
if let Some(var_value) = variables.get(var.trim()) {
return check_equality(var_value, value.trim());
}
return false;
}
if let Some((var, value)) = parse_comparison(condition, ">") {
if let Some(var_value) = variables.get(var.trim()) {
return check_greater_than(var_value, value.trim());
}
return false;
}
if let Some((var, value)) = parse_comparison(condition, "<") {
if let Some(var_value) = variables.get(var.trim()) {
return check_less_than(var_value, value.trim());
}
return false;
}
if let Some((var, value)) = parse_comparison(condition, ">=") {
if let Some(var_value) = variables.get(var.trim()) {
return check_greater_than_or_equal(var_value, value.trim());
}
return false;
}
if let Some((var, value)) = parse_comparison(condition, "<=") {
if let Some(var_value) = variables.get(var.trim()) {
return check_less_than_or_equal(var_value, value.trim());
}
return false;
}
if let Some((var, value)) = parse_inequality(condition) {
if let Some(var_value) = variables.get(var.trim()) {
return !check_equality(var_value, value.trim());
}
return false;
}
false
}
}
impl Default for BreakpointManager {
fn default() -> Self {
Self::new()
}
}
fn parse_equality(condition: &str) -> Option<(&str, &str)> {
if let Some(pos) = condition.find("==") {
let var = &condition[..pos];
let value = &condition[pos + 2..];
return Some((var, value));
}
None
}
fn parse_inequality(condition: &str) -> Option<(&str, &str)> {
if let Some(pos) = condition.find("!=") {
let var = &condition[..pos];
let value = &condition[pos + 2..];
return Some((var, value));
}
None
}
fn parse_comparison<'a>(condition: &'a str, op: &str) -> Option<(&'a str, &'a str)> {
if let Some(pos) = condition.find(op) {
let var = &condition[..pos];
let value = &condition[pos + op.len()..];
return Some((var, value));
}
None
}
fn check_equality(var_value: &Value, expected: &str) -> bool {
match var_value {
Value::Number(n) => {
if let Ok(expected_num) = expected.parse::<i64>() {
return n.as_i64() == Some(expected_num);
}
if let Ok(expected_num) = expected.parse::<f64>() {
return n.as_f64() == Some(expected_num);
}
}
Value::String(s) => {
let expected_clean = expected.trim_matches(|c| c == '\'' || c == '"');
return s == expected_clean;
}
Value::Bool(b) => {
if let Ok(expected_bool) = expected.parse::<bool>() {
return *b == expected_bool;
}
}
_ => {}
}
false
}
fn check_greater_than(var_value: &Value, expected: &str) -> bool {
if let Value::Number(n) = var_value {
if let Ok(expected_num) = expected.parse::<i64>() {
if let Some(actual) = n.as_i64() {
return actual > expected_num;
}
}
if let Ok(expected_num) = expected.parse::<f64>() {
if let Some(actual) = n.as_f64() {
return actual > expected_num;
}
}
}
false
}
fn check_less_than(var_value: &Value, expected: &str) -> bool {
if let Value::Number(n) = var_value {
if let Ok(expected_num) = expected.parse::<i64>() {
if let Some(actual) = n.as_i64() {
return actual < expected_num;
}
}
if let Ok(expected_num) = expected.parse::<f64>() {
if let Some(actual) = n.as_f64() {
return actual < expected_num;
}
}
}
false
}
fn check_greater_than_or_equal(var_value: &Value, expected: &str) -> bool {
if let Value::Number(n) = var_value {
if let Ok(expected_num) = expected.parse::<i64>() {
if let Some(actual) = n.as_i64() {
return actual >= expected_num;
}
}
if let Ok(expected_num) = expected.parse::<f64>() {
if let Some(actual) = n.as_f64() {
return actual >= expected_num;
}
}
}
false
}
fn check_less_than_or_equal(var_value: &Value, expected: &str) -> bool {
if let Value::Number(n) = var_value {
if let Ok(expected_num) = expected.parse::<i64>() {
if let Some(actual) = n.as_i64() {
return actual <= expected_num;
}
}
if let Ok(expected_num) = expected.parse::<f64>() {
if let Some(actual) = n.as_f64() {
return actual <= expected_num;
}
}
}
false
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_manager_creation() {
let mgr = BreakpointManager::new();
assert_eq!(mgr.count(), 0);
}
#[test]
fn test_parse_equality() {
let (var, value) = parse_equality("x == 5").unwrap();
assert_eq!(var, "x ");
assert_eq!(value, " 5");
}
#[test]
fn test_parse_comparison() {
let (var, value) = parse_comparison("x > 5", ">").unwrap();
assert_eq!(var, "x ");
assert_eq!(value, " 5");
}
#[test]
fn test_check_equality_number() {
let var_value = json!(5);
assert!(check_equality(&var_value, "5"));
assert!(!check_equality(&var_value, "3"));
}
#[test]
fn test_check_greater_than() {
let var_value = json!(10);
assert!(check_greater_than(&var_value, "5"));
assert!(!check_greater_than(&var_value, "15"));
}
}