use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use crate::util::params::{ParamValue, Params};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParamKind {
Bool,
Uint,
Double,
String,
Symbol,
}
impl ParamKind {
pub fn accepts(&self, value: &ParamValue) -> bool {
matches!(
(self, value),
(ParamKind::Bool, ParamValue::Bool(_))
| (ParamKind::Uint, ParamValue::UInt(_))
| (ParamKind::Double, ParamValue::Double(_))
| (ParamKind::String, ParamValue::Str(_))
| (ParamKind::Symbol, ParamValue::Str(_))
)
}
pub fn name(&self) -> &'static str {
match self {
ParamKind::Bool => "bool",
ParamKind::Uint => "unsigned int",
ParamKind::Double => "double",
ParamKind::String => "string",
ParamKind::Symbol => "symbol",
}
}
}
#[derive(Clone, Debug)]
pub struct ParamDescr {
pub kind: ParamKind,
pub default: ParamValue,
pub doc: String,
}
#[derive(Clone, Debug, Default)]
pub struct ParamDescrs {
entries: BTreeMap<String, ParamDescr>,
}
impl ParamDescrs {
pub fn new() -> ParamDescrs {
ParamDescrs {
entries: BTreeMap::new(),
}
}
pub fn insert(&mut self, name: &str, kind: ParamKind, default: ParamValue, doc: &str) {
debug_assert!(
kind.accepts(&default),
"default for `{name}` does not match its kind {kind:?}"
);
self.entries.insert(
name.to_string(),
ParamDescr {
kind,
default,
doc: doc.to_string(),
},
);
}
pub fn with(mut self, name: &str, kind: ParamKind, default: ParamValue, doc: &str) -> Self {
self.insert(name, kind, default, doc);
self
}
pub fn get(&self, name: &str) -> Option<&ParamDescr> {
self.entries.get(name)
}
pub fn contains(&self, name: &str) -> bool {
self.entries.contains_key(name)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn names(&self) -> Vec<&str> {
self.entries.keys().map(String::as_str).collect()
}
pub fn extend(&mut self, other: &ParamDescrs) {
for (k, v) in &other.entries {
self.entries.insert(k.clone(), v.clone());
}
}
pub fn validate(&self, params: &Params) -> Result<(), String> {
for name in params.keys() {
match self.entries.get(name) {
None => return Err(alloc::format!("unknown parameter: {name}")),
Some(descr) => {
let value = params.get(name).expect("key just iterated");
if !descr.kind.accepts(value) {
return Err(alloc::format!(
"parameter {name} expects {}, got a different kind",
descr.kind.name()
));
}
}
}
}
Ok(())
}
pub fn effective(&self, name: &str, params: &Params) -> Option<ParamValue> {
let descr = self.entries.get(name)?;
match params.get(name) {
Some(v) if descr.kind.accepts(v) => Some(v.clone()),
_ => Some(descr.default.clone()),
}
}
}
pub fn global_descrs() -> ParamDescrs {
ParamDescrs::new()
.with(
"timeout",
ParamKind::Uint,
ParamValue::UInt(u32::MAX as u64),
"timeout in milliseconds (per check-sat)",
)
.with(
"rlimit",
ParamKind::Uint,
ParamValue::UInt(0),
"resource limit (0 means no limit)",
)
.with(
"model",
ParamKind::Bool,
ParamValue::Bool(true),
"produce models for satisfiable queries",
)
.with(
"unsat_core",
ParamKind::Bool,
ParamValue::Bool(false),
"produce unsat cores",
)
.with(
"produce_proofs",
ParamKind::Bool,
ParamValue::Bool(false),
"produce proofs for unsatisfiable queries",
)
.with(
"random_seed",
ParamKind::Uint,
ParamValue::UInt(0),
"random seed for the search",
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::params::{ParamValue, Params};
#[test]
fn kind_matching() {
assert!(ParamKind::Bool.accepts(&ParamValue::Bool(true)));
assert!(!ParamKind::Bool.accepts(&ParamValue::UInt(1)));
assert!(ParamKind::Uint.accepts(&ParamValue::UInt(7)));
assert!(ParamKind::Symbol.accepts(&ParamValue::Str("x".into())));
assert!(ParamKind::String.accepts(&ParamValue::Str("x".into())));
}
#[test]
fn validate_accepts_known_and_rejects_unknown() {
let d = global_descrs();
assert!(d.contains("timeout") && d.contains("model"));
let mut p = Params::new();
p.set("timeout", ParamValue::UInt(5000));
p.set("model", ParamValue::Bool(false));
assert!(d.validate(&p).is_ok());
let mut bad_key = Params::new();
bad_key.set("no_such_param", ParamValue::Bool(true));
assert!(d.validate(&bad_key).is_err());
let mut bad_kind = Params::new();
bad_kind.set("timeout", ParamValue::Bool(true)); assert!(d.validate(&bad_kind).is_err());
}
#[test]
fn effective_falls_back_to_default() {
let d = global_descrs();
let empty = Params::new();
assert_eq!(d.effective("model", &empty), Some(ParamValue::Bool(true)));
let mut p = Params::new();
p.set("model", ParamValue::Bool(false));
assert_eq!(d.effective("model", &p), Some(ParamValue::Bool(false)));
assert_eq!(d.effective("undeclared", &p), None);
}
#[test]
fn extend_merges_tables() {
let mut a = ParamDescrs::new();
a.insert("x", ParamKind::Bool, ParamValue::Bool(false), "x flag");
let mut b = ParamDescrs::new();
b.insert("y", ParamKind::Uint, ParamValue::UInt(3), "y count");
a.extend(&b);
assert_eq!(a.len(), 2);
assert!(a.contains("x") && a.contains("y"));
}
}