use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Kind {
Extraction,
Simulation,
TestWitnessed,
}
impl Kind {
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s {
"extraction" => Some(Self::Extraction),
"simulation" => Some(Self::Simulation),
"test_witnessed" => Some(Self::TestWitnessed),
_ => None,
}
}
#[must_use]
pub fn level(self) -> u8 {
match self {
Self::Extraction | Self::Simulation => 4,
Self::TestWitnessed => 3,
}
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Extraction => "extraction",
Self::Simulation => "simulation",
Self::TestWitnessed => "test_witnessed",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Model {
pub module: String,
pub model_of: String,
pub kind: Kind,
pub evidence: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Record {
pub models: Vec<Model>,
pub malformed: Vec<String>,
pub unrefined_baseline: Option<usize>,
}
pub fn load(lean_dir: &Path) -> Result<Record, String> {
let p = lean_dir.join("formalization.yaml");
let text = std::fs::read_to_string(&p).map_err(|e| format!("{}: {e}", p.display()))?;
let doc: serde_yaml::Value =
serde_yaml::from_str(&text).map_err(|e| format!("{}: {e}", p.display()))?;
Ok(parse(&doc))
}
fn parse(doc: &serde_yaml::Value) -> Record {
let mut rec = Record {
unrefined_baseline: doc
.get("unrefined_baseline")
.and_then(serde_yaml::Value::as_u64)
.and_then(|n| usize::try_from(n).ok()),
..Record::default()
};
let entries = doc
.get("models")
.and_then(serde_yaml::Value::as_sequence)
.cloned()
.unwrap_or_default();
let mut seen = BTreeSet::new();
for (i, e) in entries.iter().enumerate() {
let s = |k: &str| e.get(k).and_then(serde_yaml::Value::as_str).map(str::trim);
let rel = |k: &str| {
e.get("relation")
.and_then(|r| r.get(k))
.and_then(serde_yaml::Value::as_str)
.map(str::trim)
};
let (Some(module), Some(model_of)) = (s("module"), s("model_of")) else {
rec.malformed
.push(format!("models[{i}] needs `module` and `model_of`"));
continue;
};
let Some(kind) = rel("kind").and_then(Kind::parse) else {
rec.malformed.push(format!(
"models[{i}] ({module}): relation.kind {:?} is not extraction, simulation or test_witnessed",
rel("kind")
));
continue;
};
let evidence = rel("evidence").unwrap_or_default();
if module.is_empty() || !model_of.contains("::") || evidence.is_empty() {
rec.malformed.push(format!(
"models[{i}] ({module}): needs a module, a `crate::path::item` model_of and relation.evidence"
));
continue;
}
if !seen.insert(module.to_string()) {
rec.malformed
.push(format!("models[{i}]: {module} is modelled twice"));
continue;
}
rec.models.push(Model {
module: module.into(),
model_of: model_of.into(),
kind,
evidence: evidence.into(),
});
}
rec
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Judged {
pub model: Model,
pub resolved: Result<(), String>,
pub in_tree: bool,
}
impl Judged {
#[must_use]
pub fn level(&self) -> Option<u8> {
(self.resolved.is_ok() && self.in_tree).then(|| self.model.kind.level())
}
}
pub fn judge(
models: &[Model],
tree_modules: &BTreeSet<String>,
mut resolve: impl FnMut(&str) -> Result<(), String>,
) -> Vec<Judged> {
models
.iter()
.map(|m| Judged {
resolved: resolve(&m.model_of),
in_tree: tree_modules.contains(&m.module),
model: m.clone(),
})
.collect()
}
#[must_use]
pub fn l4_modules(judged: &[Judged]) -> BTreeSet<String> {
judged
.iter()
.filter(|j| j.level() == Some(4))
.map(|j| j.model.module.clone())
.collect()
}
#[must_use]
pub fn unrefined<'a>(
theorem_modules: &'a BTreeMap<String, Vec<String>>,
l4: &BTreeSet<String>,
) -> Vec<&'a str> {
theorem_modules
.iter()
.filter(|(m, ts)| !ts.is_empty() && !l4.contains(*m))
.map(|(m, _)| m.as_str())
.collect()
}
pub fn workspace_resolver(root: &Path) -> impl FnMut(&str) -> Result<(), String> {
let ws = crate::ontology::extract::code::Workspace::scan(root);
let mut cache: BTreeMap<String, Result<(), String>> = BTreeMap::new();
move |path: &str| {
cache
.entry(path.to_string())
.or_insert_with(|| {
let Some((module, item)) = path.rsplit_once("::") else {
return Err(format!("`{path}` is not a crate::path::item"));
};
let mut r = crate::ontology::extract::code::Resolver::new(&ws);
r.resolve(module, item).map(|_| ()).map_err(|u| u.reason)
})
.clone()
}
}
#[cfg(test)]
#[path = "refinement_tests.rs"]
mod tests;