provable_contracts/discharge/
refinement.rs1use std::collections::{BTreeMap, BTreeSet};
14use std::path::Path;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
18pub enum Kind {
19 Extraction,
21 Simulation,
23 TestWitnessed,
25}
26
27impl Kind {
28 #[must_use]
29 pub fn parse(s: &str) -> Option<Self> {
30 match s {
31 "extraction" => Some(Self::Extraction),
32 "simulation" => Some(Self::Simulation),
33 "test_witnessed" => Some(Self::TestWitnessed),
34 _ => None,
35 }
36 }
37
38 #[must_use]
40 pub fn level(self) -> u8 {
41 match self {
42 Self::Extraction | Self::Simulation => 4,
43 Self::TestWitnessed => 3,
44 }
45 }
46
47 #[must_use]
48 pub fn as_str(self) -> &'static str {
49 match self {
50 Self::Extraction => "extraction",
51 Self::Simulation => "simulation",
52 Self::TestWitnessed => "test_witnessed",
53 }
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct Model {
60 pub module: String,
61 pub model_of: String,
62 pub kind: Kind,
63 pub evidence: String,
64}
65
66#[derive(Debug, Clone, Default, PartialEq, Eq)]
68pub struct Record {
69 pub models: Vec<Model>,
70 pub malformed: Vec<String>,
71 pub unrefined_baseline: Option<usize>,
72}
73
74pub fn load(lean_dir: &Path) -> Result<Record, String> {
76 let p = lean_dir.join("formalization.yaml");
77 let text = std::fs::read_to_string(&p).map_err(|e| format!("{}: {e}", p.display()))?;
78 let doc: serde_yaml::Value =
79 serde_yaml::from_str(&text).map_err(|e| format!("{}: {e}", p.display()))?;
80 Ok(parse(&doc))
81}
82
83fn parse(doc: &serde_yaml::Value) -> Record {
84 let mut rec = Record {
85 unrefined_baseline: doc
86 .get("unrefined_baseline")
87 .and_then(serde_yaml::Value::as_u64)
88 .and_then(|n| usize::try_from(n).ok()),
89 ..Record::default()
90 };
91 let entries = doc
92 .get("models")
93 .and_then(serde_yaml::Value::as_sequence)
94 .cloned()
95 .unwrap_or_default();
96 let mut seen = BTreeSet::new();
97 for (i, e) in entries.iter().enumerate() {
98 let s = |k: &str| e.get(k).and_then(serde_yaml::Value::as_str).map(str::trim);
99 let rel = |k: &str| {
100 e.get("relation")
101 .and_then(|r| r.get(k))
102 .and_then(serde_yaml::Value::as_str)
103 .map(str::trim)
104 };
105 let (Some(module), Some(model_of)) = (s("module"), s("model_of")) else {
106 rec.malformed
107 .push(format!("models[{i}] needs `module` and `model_of`"));
108 continue;
109 };
110 let Some(kind) = rel("kind").and_then(Kind::parse) else {
111 rec.malformed.push(format!(
112 "models[{i}] ({module}): relation.kind {:?} is not extraction, simulation or test_witnessed",
113 rel("kind")
114 ));
115 continue;
116 };
117 let evidence = rel("evidence").unwrap_or_default();
118 if module.is_empty() || !model_of.contains("::") || evidence.is_empty() {
119 rec.malformed.push(format!(
120 "models[{i}] ({module}): needs a module, a `crate::path::item` model_of and relation.evidence"
121 ));
122 continue;
123 }
124 if !seen.insert(module.to_string()) {
125 rec.malformed
126 .push(format!("models[{i}]: {module} is modelled twice"));
127 continue;
128 }
129 rec.models.push(Model {
130 module: module.into(),
131 model_of: model_of.into(),
132 kind,
133 evidence: evidence.into(),
134 });
135 }
136 rec
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct Judged {
142 pub model: Model,
143 pub resolved: Result<(), String>,
145 pub in_tree: bool,
147}
148
149impl Judged {
150 #[must_use]
152 pub fn level(&self) -> Option<u8> {
153 (self.resolved.is_ok() && self.in_tree).then(|| self.model.kind.level())
154 }
155}
156
157pub fn judge(
159 models: &[Model],
160 tree_modules: &BTreeSet<String>,
161 mut resolve: impl FnMut(&str) -> Result<(), String>,
162) -> Vec<Judged> {
163 models
164 .iter()
165 .map(|m| Judged {
166 resolved: resolve(&m.model_of),
167 in_tree: tree_modules.contains(&m.module),
168 model: m.clone(),
169 })
170 .collect()
171}
172
173#[must_use]
175pub fn l4_modules(judged: &[Judged]) -> BTreeSet<String> {
176 judged
177 .iter()
178 .filter(|j| j.level() == Some(4))
179 .map(|j| j.model.module.clone())
180 .collect()
181}
182
183#[must_use]
185pub fn unrefined<'a>(
186 theorem_modules: &'a BTreeMap<String, Vec<String>>,
187 l4: &BTreeSet<String>,
188) -> Vec<&'a str> {
189 theorem_modules
190 .iter()
191 .filter(|(m, ts)| !ts.is_empty() && !l4.contains(*m))
192 .map(|(m, _)| m.as_str())
193 .collect()
194}
195
196pub fn workspace_resolver(root: &Path) -> impl FnMut(&str) -> Result<(), String> {
198 let ws = crate::ontology::extract::code::Workspace::scan(root);
199 let mut cache: BTreeMap<String, Result<(), String>> = BTreeMap::new();
200 move |path: &str| {
201 cache
202 .entry(path.to_string())
203 .or_insert_with(|| {
204 let Some((module, item)) = path.rsplit_once("::") else {
205 return Err(format!("`{path}` is not a crate::path::item"));
206 };
207 let mut r = crate::ontology::extract::code::Resolver::new(&ws);
208 r.resolve(module, item).map(|_| ()).map_err(|u| u.reason)
209 })
210 .clone()
211 }
212}
213
214#[cfg(test)]
215#[path = "refinement_tests.rs"]
216mod tests;