mirsa-domains 0.2.0

Abstract interpretation domains for mirsa
use std::collections::HashMap;
use std::fs;
use std::path::Path;

#[derive(Clone, Copy, Debug)]
pub struct EngineConfig {
    pub max_paths: usize,
    pub max_iterations: Option<u32>,
}

fn load_key_value_config(path: &Path) -> HashMap<String, String> {
    let mut map = HashMap::new();
    let Ok(content) = fs::read_to_string(path) else {
        return map;
    };

    for raw_line in content.lines() {
        let line = raw_line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        let Some((key, value)) = line.split_once('=') else {
            continue;
        };
        map.insert(key.trim().to_string(), value.trim().to_string());
    }
    map
}

fn parse_u32_config_value(key: &str, value: &str) -> Option<u32> {
    match value.trim().parse::<u32>() {
        Ok(v) => Some(v),
        Err(_) => {
            println!(
                "Warning: invalid config value for `{}`: `{}`; using default.",
                key,
                value.trim()
            );
            None
        }
    }
}

fn parse_optional_u32_config_value(key: &str, value: &str) -> Option<Option<u32>> {
    let trimmed = value.trim();
    if trimmed.eq_ignore_ascii_case("none") {
        return Some(None);
    }
    parse_u32_config_value(key, trimmed).map(Some)
}

fn parse_bool_config_value(key: &str, value: &str) -> Option<bool> {
    match value.trim().to_ascii_lowercase().as_str() {
        "true" => Some(true),
        "false" => Some(false),
        _ => {
            println!(
                "Warning: invalid config value for `{}`: `{}`; using default.",
                key,
                value.trim()
            );
            None
        }
    }
}

pub fn load_bool_config(path: &Path, key: &str, default: bool) -> bool {
    let map = load_key_value_config(path);
    map.get(key)
        .and_then(|value| parse_bool_config_value(key, value))
        .unwrap_or(default)
}

pub fn load_engine_config(path: &Path) -> EngineConfig {
    let mut cfg = EngineConfig {
        max_paths: 10,
        max_iterations: None,
    };

    let map = load_key_value_config(path);
    if let Some(v) = map
        .get("max_paths")
        .and_then(|x| parse_u32_config_value("max_paths", x))
    {
        cfg.max_paths = usize::try_from(v).unwrap_or(cfg.max_paths);
    }
    if let Some(v) = map
        .get("max_iterations")
        .and_then(|x| parse_optional_u32_config_value("max_iterations", x))
    {
        cfg.max_iterations = v;
    }

    cfg
}