logicaffeine_proof/
development.rs1use crate::formula::{parse_formula, FormulaError};
27use crate::verify::{prove_library_with_axioms, LibraryResult, LibraryTheorem};
28use crate::ProofExpr;
29
30#[derive(Debug, Clone, PartialEq)]
32pub struct Development {
33 pub axioms: Vec<(String, ProofExpr)>,
35 pub theorems: Vec<LibraryTheorem>,
37 pub simp_tagged: Vec<String>,
40}
41
42impl Development {
43 pub fn axiom_exprs(&self) -> Vec<ProofExpr> {
45 self.axioms.iter().map(|(_, e)| e.clone()).collect()
46 }
47
48 pub fn simp_lemmas(&self) -> &[String] {
50 &self.simp_tagged
51 }
52}
53
54pub fn parse_development(body: &str) -> Result<Development, FormulaError> {
56 let mut axioms = Vec::new();
57 let mut theorems = Vec::new();
58
59 let mut simp_tagged = Vec::new();
60
61 for stmt in split_on_top_level(body, '.') {
62 let stmt = stmt.trim();
63 if stmt.is_empty() {
64 continue;
65 }
66 let words: Vec<&str> = stmt.split_whitespace().collect();
67 match words.first().map(|w| w.to_lowercase()).as_deref() {
68 Some("axiom") => {
69 let (decl, simp) = parse_axiom_decl(stmt)?;
70 if simp {
71 simp_tagged.push(decl.0.clone());
72 }
73 axioms.push(decl);
74 }
75 Some("theorem") => {
76 let (thm, simp) = parse_theorem_decl(stmt)?;
77 if simp {
78 simp_tagged.push(thm.name.clone());
79 }
80 theorems.push(thm);
81 }
82 other => {
83 return Err(FormulaError::new(format!(
84 "expected a declaration starting with 'Axiom' or 'Theorem', found {other:?}"
85 )))
86 }
87 }
88 }
89
90 Ok(Development { axioms, theorems, simp_tagged })
91}
92
93pub fn prove_development(body: &str) -> Result<Vec<(String, LibraryResult)>, FormulaError> {
97 let dev = parse_development(body)?;
98 let axioms = dev.axiom_exprs();
99 let results = prove_library_with_axioms(&axioms, &dev.theorems);
100 Ok(dev
101 .theorems
102 .iter()
103 .map(|t| t.name.clone())
104 .zip(results)
105 .collect())
106}
107
108fn strip_simp_attr<'a>(words: &[&'a str]) -> (Vec<&'a str>, bool) {
116 let mut simp = false;
117 let kept = words
118 .iter()
119 .filter(|w| {
120 if w.eq_ignore_ascii_case("[simp]") {
121 simp = true;
122 false
123 } else {
124 true
125 }
126 })
127 .copied()
128 .collect();
129 (kept, simp)
130}
131
132fn parse_axiom_decl(stmt: &str) -> Result<((String, ProofExpr), bool), FormulaError> {
134 let (header, formula_src) = split_once_top_level(stmt, ':').ok_or_else(|| {
135 FormulaError::new("an 'Axiom' declaration needs a ':' before its formula")
136 })?;
137 let hwords: Vec<&str> = header.split_whitespace().collect();
138 let (hwords, simp) = strip_simp_attr(&hwords);
139 let name = hwords
140 .get(1)
141 .ok_or_else(|| FormulaError::new("'Axiom' declaration is missing a name"))?
142 .to_string();
143 let formula = parse_formula(&formula_src)?;
144 Ok(((name, formula), simp))
145}
146
147fn parse_theorem_decl(stmt: &str) -> Result<(LibraryTheorem, bool), FormulaError> {
149 let (header, body) = split_once_top_level(stmt, ':').ok_or_else(|| {
150 FormulaError::new("a 'Theorem' declaration needs a ':' before its clauses")
151 })?;
152
153 let hwords: Vec<&str> = header.split_whitespace().collect();
155 let (hwords, simp) = strip_simp_attr(&hwords);
156 let name = hwords
157 .get(1)
158 .ok_or_else(|| FormulaError::new("'Theorem' declaration is missing a name"))?
159 .to_string();
160 let mut cites = Vec::new();
161 if let Some(kw) = hwords.get(2) {
162 if matches!(kw.to_lowercase().as_str(), "cites" | "using" | "from") {
163 for w in &hwords[3..] {
164 for part in w.split(',') {
165 let part = part.trim();
166 if !part.is_empty() {
167 cites.push(part.to_string());
168 }
169 }
170 }
171 }
172 }
173
174 let is_clause_kw = |w: &str| matches!(w, "given" | "assume" | "prove" | "show");
180 let words: Vec<&str> = body.split_whitespace().collect();
181 let mut premises = Vec::new();
182 let mut goal: Option<ProofExpr> = None;
183 let mut i = 0;
184 while i < words.len() {
185 let kw = words[i].to_lowercase();
186 if !is_clause_kw(&kw) {
187 i += 1;
189 continue;
190 }
191 i += 1;
192 let start = i;
193 while i < words.len() && !is_clause_kw(&words[i].to_lowercase()) {
194 i += 1;
195 }
196 let formula_src = words[start..i].join(" ");
197 match kw.as_str() {
198 "given" | "assume" => premises.push(parse_formula(&formula_src)?),
199 _ => {
200 if goal.is_some() {
201 return Err(FormulaError::new(format!(
202 "theorem '{name}' has more than one 'prove' clause"
203 )));
204 }
205 goal = Some(parse_formula(&formula_src)?);
206 }
207 }
208 }
209
210 let goal = goal.ok_or_else(|| {
211 FormulaError::new(format!("theorem '{name}' has no 'prove' clause"))
212 })?;
213 Ok((LibraryTheorem { name, premises, goal, cites }, simp))
214}
215
216fn split_on_top_level(s: &str, delim: char) -> Vec<String> {
222 let mut out = Vec::new();
223 let mut depth = 0i32;
224 let mut cur = String::new();
225 for c in s.chars() {
226 match c {
227 '(' => {
228 depth += 1;
229 cur.push(c);
230 }
231 ')' => {
232 depth -= 1;
233 cur.push(c);
234 }
235 c if c == delim && depth == 0 => {
236 out.push(std::mem::take(&mut cur));
237 }
238 c => cur.push(c),
239 }
240 }
241 out.push(cur);
242 out
243}
244
245fn split_once_top_level(s: &str, delim: char) -> Option<(String, String)> {
247 let mut depth = 0i32;
248 for (i, c) in s.char_indices() {
249 match c {
250 '(' => depth += 1,
251 ')' => depth -= 1,
252 c if c == delim && depth == 0 => {
253 return Some((s[..i].to_string(), s[i + c.len_utf8()..].to_string()));
254 }
255 _ => {}
256 }
257 }
258 None
259}