/home/noah/src/ruchy/src/bin/handlers/handlers_modules/prove_helpers.rs
Line | Count | Source |
1 | | //! Helper functions for prove command |
2 | | //! Extracted to maintain ≤10 complexity per function |
3 | | |
4 | | use anyhow::{Context, Result}; |
5 | | use ruchy::proving::{InteractiveProver, ProverSession, SmtBackend}; |
6 | | use std::fs; |
7 | | |
8 | | /// Parse SMT backend from string |
9 | 0 | pub fn parse_smt_backend(backend: &str, verbose: bool) -> SmtBackend { |
10 | 0 | match backend.to_lowercase().as_str() { |
11 | 0 | "z3" => SmtBackend::Z3, |
12 | 0 | "cvc5" => SmtBackend::CVC5, |
13 | 0 | "yices2" => SmtBackend::Yices2, |
14 | | _ => { |
15 | 0 | if verbose { |
16 | 0 | eprintln!("Warning: Unknown backend '{}', defaulting to Z3", backend); |
17 | 0 | } |
18 | 0 | SmtBackend::Z3 |
19 | | } |
20 | | } |
21 | 0 | } |
22 | | |
23 | | /// Configure prover with settings |
24 | 0 | pub fn configure_prover( |
25 | 0 | prover: &mut InteractiveProver, |
26 | 0 | timeout: u64, |
27 | 0 | ml_suggestions: bool, |
28 | 0 | verbose: bool, |
29 | 0 | ) { |
30 | 0 | prover.set_timeout(timeout); |
31 | 0 | prover.set_ml_suggestions(ml_suggestions); |
32 | | |
33 | 0 | if verbose { |
34 | 0 | println!("⚙️ Configuration:"); |
35 | 0 | println!(" Timeout: {}ms", timeout); |
36 | 0 | println!(" ML Suggestions: {}", ml_suggestions); |
37 | 0 | } |
38 | 0 | } |
39 | | |
40 | | /// Load and parse file for proof checking |
41 | 0 | pub fn load_proof_file(file_path: &std::path::Path, verbose: bool) -> Result<ruchy::frontend::ast::Expr> { |
42 | | use ruchy::Parser as RuchyParser; |
43 | | |
44 | 0 | if verbose { |
45 | 0 | println!("📂 Loading file: {}", file_path.display()); |
46 | 0 | } |
47 | | |
48 | 0 | let source = fs::read_to_string(file_path) |
49 | 0 | .with_context(|| format!("Failed to read file: {}", file_path.display()))?; |
50 | | |
51 | 0 | let mut parser = RuchyParser::new(&source); |
52 | 0 | let ast = parser.parse() |
53 | 0 | .with_context(|| format!("Failed to parse file: {}", file_path.display()))?; |
54 | | |
55 | 0 | if verbose { |
56 | 0 | println!("📋 Extracted proof goals from source"); |
57 | 0 | } |
58 | | |
59 | 0 | Ok(ast) |
60 | 0 | } |
61 | | |
62 | | /// Load proof script from file |
63 | 0 | pub fn load_proof_script( |
64 | 0 | prover: &mut InteractiveProver, |
65 | 0 | script_path: &std::path::Path, |
66 | 0 | verbose: bool, |
67 | 0 | ) -> Result<()> { |
68 | 0 | if verbose { |
69 | 0 | println!("📜 Loading proof script: {}", script_path.display()); |
70 | 0 | } |
71 | | |
72 | 0 | let script_content = fs::read_to_string(script_path) |
73 | 0 | .with_context(|| format!("Failed to read script: {}", script_path.display()))?; |
74 | | |
75 | 0 | prover.load_script(&script_content)?; |
76 | 0 | Ok(()) |
77 | 0 | } |
78 | | |
79 | | /// Print interactive prover help |
80 | 0 | pub fn print_prover_help() { |
81 | 0 | println!("\nInteractive Prover Commands:"); |
82 | 0 | println!(" help - Show this help message"); |
83 | 0 | println!(" quit/exit - Exit the prover"); |
84 | 0 | println!(" goals - Show current proof goals"); |
85 | 0 | println!(" tactics - List available tactics"); |
86 | 0 | println!(" goal <stmt> - Add a new proof goal"); |
87 | 0 | println!(" apply <tactic> - Apply a tactic to current goal"); |
88 | 0 | println!("\nTactics:"); |
89 | 0 | println!(" intro - Introduce hypothesis from implication"); |
90 | 0 | println!(" split - Split conjunction into subgoals"); |
91 | 0 | println!(" induction - Proof by induction"); |
92 | 0 | println!(" contradiction - Proof by contradiction"); |
93 | 0 | println!(" reflexivity - Prove equality by reflexivity"); |
94 | 0 | println!(" simplify - Simplify expression"); |
95 | 0 | println!(" assumption - Prove using an assumption"); |
96 | 0 | println!("\nExamples:"); |
97 | 0 | println!(" goal x > 0 -> x + 1 > 1"); |
98 | 0 | println!(" apply intro"); |
99 | 0 | println!(" apply simplify\n"); |
100 | 0 | } |
101 | | |
102 | | /// Handle single prover command |
103 | 0 | pub fn handle_prover_command( |
104 | 0 | input: &str, |
105 | 0 | prover: &mut InteractiveProver, |
106 | 0 | session: &mut ProverSession, |
107 | 0 | verbose: bool, |
108 | 0 | ) -> Result<bool> { |
109 | 0 | match input { |
110 | 0 | "quit" | "exit" => { |
111 | 0 | println!("Goodbye!"); |
112 | 0 | Ok(true) // Signal to exit |
113 | | } |
114 | 0 | "help" => { |
115 | 0 | print_prover_help(); |
116 | 0 | Ok(false) |
117 | | } |
118 | 0 | "goals" => { |
119 | 0 | print_current_goals(session); |
120 | 0 | Ok(false) |
121 | | } |
122 | 0 | "tactics" => { |
123 | 0 | print_available_tactics(prover); |
124 | 0 | Ok(false) |
125 | | } |
126 | 0 | cmd if cmd.starts_with("apply ") => { |
127 | 0 | apply_tactic(cmd, prover, session)?; |
128 | 0 | Ok(false) |
129 | | } |
130 | 0 | cmd if cmd.starts_with("goal ") => { |
131 | 0 | add_goal(cmd, session); |
132 | 0 | Ok(false) |
133 | | } |
134 | | _ => { |
135 | 0 | process_general_input(input, prover, session, verbose)?; |
136 | 0 | Ok(false) |
137 | | } |
138 | | } |
139 | 0 | } |
140 | | |
141 | | /// Print current proof goals |
142 | 0 | fn print_current_goals(session: &ProverSession) { |
143 | 0 | let goals = session.get_goals(); |
144 | 0 | if goals.is_empty() { |
145 | 0 | println!("No active goals"); |
146 | 0 | } else { |
147 | 0 | for (i, goal) in goals.iter().enumerate() { |
148 | 0 | println!("Goal {}: {}", i + 1, goal.statement); |
149 | 0 | } |
150 | | } |
151 | 0 | } |
152 | | |
153 | | /// Print available tactics |
154 | 0 | fn print_available_tactics(prover: &InteractiveProver) { |
155 | 0 | let tactics = prover.get_available_tactics(); |
156 | 0 | println!("Available tactics:"); |
157 | 0 | for tactic in tactics { |
158 | 0 | println!(" {} - {}", tactic.name(), tactic.description()); |
159 | 0 | } |
160 | 0 | } |
161 | | |
162 | | /// Apply a tactic to current goal |
163 | 0 | fn apply_tactic( |
164 | 0 | cmd: &str, |
165 | 0 | prover: &mut InteractiveProver, |
166 | 0 | session: &mut ProverSession, |
167 | 0 | ) -> Result<()> { |
168 | 0 | let tactic_name = &cmd[6..]; |
169 | 0 | match prover.apply_tactic(session, tactic_name, &[]) { |
170 | 0 | Ok(result) => { |
171 | 0 | println!("Result: {:?}", result); |
172 | 0 | Ok(()) |
173 | | } |
174 | 0 | Err(e) => { |
175 | 0 | eprintln!("Error: {}", e); |
176 | 0 | Err(e) |
177 | | } |
178 | | } |
179 | 0 | } |
180 | | |
181 | | /// Add a new goal to session |
182 | 0 | fn add_goal(cmd: &str, session: &mut ProverSession) { |
183 | 0 | let goal_stmt = &cmd[5..]; |
184 | 0 | session.add_goal(goal_stmt.to_string()); |
185 | 0 | println!("Added goal: {}", goal_stmt); |
186 | 0 | } |
187 | | |
188 | | /// Process general input |
189 | 0 | fn process_general_input( |
190 | 0 | input: &str, |
191 | 0 | prover: &mut InteractiveProver, |
192 | 0 | session: &mut ProverSession, |
193 | 0 | verbose: bool, |
194 | 0 | ) -> Result<()> { |
195 | 0 | match prover.process_input(session, input) { |
196 | 0 | Ok(result) => { |
197 | 0 | if verbose { |
198 | 0 | println!("Processed: {:?}", result); |
199 | 0 | } |
200 | 0 | Ok(()) |
201 | | } |
202 | 0 | Err(e) => { |
203 | 0 | eprintln!("Error: {}", e); |
204 | 0 | Err(e) |
205 | | } |
206 | | } |
207 | 0 | } |
208 | | |
209 | | /// Show current prover state with ML suggestions |
210 | 0 | pub fn show_prover_state( |
211 | 0 | session: &ProverSession, |
212 | 0 | prover: &mut InteractiveProver, |
213 | 0 | ml_suggestions: bool, |
214 | 0 | ) { |
215 | 0 | if session.is_complete() { |
216 | 0 | println!("✅ All goals proved!"); |
217 | 0 | } else if let Some(current_goal) = session.current_goal() { |
218 | 0 | println!("\nCurrent goal: {}", current_goal.statement); |
219 | | |
220 | 0 | if ml_suggestions { |
221 | 0 | show_ml_suggestions(prover, current_goal); |
222 | 0 | } |
223 | 0 | } |
224 | 0 | } |
225 | | |
226 | | /// Show ML-powered tactic suggestions |
227 | 0 | fn show_ml_suggestions(prover: &mut InteractiveProver, goal: &ruchy::proving::ProofGoal) { |
228 | 0 | if let Ok(suggestions) = prover.suggest_tactics(goal) { |
229 | 0 | if !suggestions.is_empty() { |
230 | 0 | println!("\nSuggested tactics:"); |
231 | 0 | for (i, sugg) in suggestions.iter().take(3).enumerate() { |
232 | 0 | println!(" {}. {} (confidence: {:.2})", |
233 | 0 | i + 1, sugg.tactic_name, sugg.confidence); |
234 | 0 | } |
235 | 0 | } |
236 | 0 | } |
237 | 0 | } |
238 | | |
239 | | /// Export proof to file |
240 | 0 | pub fn export_proof( |
241 | 0 | session: &ProverSession, |
242 | 0 | export_path: &std::path::Path, |
243 | 0 | format: &str, |
244 | 0 | verbose: bool, |
245 | 0 | ) -> Result<()> { |
246 | 0 | if verbose { |
247 | 0 | println!("📝 Exporting proof to: {}", export_path.display()); |
248 | 0 | } |
249 | | |
250 | 0 | let proof_content = match format { |
251 | 0 | "json" => serde_json::to_string_pretty(session)?, |
252 | 0 | "coq" => session.to_coq_proof(), |
253 | 0 | "lean" => session.to_lean_proof(), |
254 | 0 | _ => session.to_text_proof(), |
255 | | }; |
256 | | |
257 | 0 | fs::write(export_path, proof_content) |
258 | 0 | .with_context(|| format!("Failed to write proof: {}", export_path.display()))?; |
259 | | |
260 | 0 | println!("✅ Proof exported successfully"); |
261 | 0 | Ok(()) |
262 | 0 | } |
263 | | |
264 | | /// Verify proofs extracted from AST |
265 | 0 | pub fn verify_proofs_from_ast( |
266 | 0 | ast: &ruchy::frontend::ast::Expr, |
267 | 0 | file_path: &std::path::Path, |
268 | 0 | format: &str, |
269 | 0 | counterexample: bool, |
270 | 0 | verbose: bool |
271 | 0 | ) -> Result<()> { |
272 | | use ruchy::proving::{extract_assertions_from_ast, verify_assertions_batch}; |
273 | | |
274 | 0 | let assertions = extract_assertions_from_ast(ast); |
275 | | |
276 | 0 | if assertions.is_empty() { |
277 | 0 | handle_no_assertions(file_path, format, verbose)?; |
278 | 0 | return Ok(()); |
279 | 0 | } |
280 | | |
281 | 0 | if verbose { |
282 | 0 | print_assertions(&assertions); |
283 | 0 | } |
284 | | |
285 | 0 | let results = verify_assertions_batch(&assertions, counterexample); |
286 | | |
287 | 0 | output_verification_results(&results, file_path, format, verbose)?; |
288 | | |
289 | 0 | check_verification_failures(&results); |
290 | 0 | Ok(()) |
291 | 0 | } |
292 | | |
293 | | /// Handle case when no assertions found |
294 | 0 | fn handle_no_assertions( |
295 | 0 | file_path: &std::path::Path, |
296 | 0 | format: &str, |
297 | 0 | verbose: bool, |
298 | 0 | ) -> Result<()> { |
299 | 0 | if verbose { |
300 | 0 | println!("No assertions found in {}", file_path.display()); |
301 | 0 | } |
302 | | |
303 | 0 | if format == "json" { |
304 | 0 | let json_result = serde_json::json!({ |
305 | 0 | "file": file_path.display().to_string(), |
306 | 0 | "status": "no_proofs", |
307 | 0 | "total": 0, |
308 | 0 | "passed": 0, |
309 | 0 | "failed": 0, |
310 | 0 | "proofs": [] |
311 | | }); |
312 | 0 | println!("{}", serde_json::to_string_pretty(&json_result)?); |
313 | 0 | } else { |
314 | 0 | println!("✅ No proofs found (file valid)"); |
315 | 0 | } |
316 | | |
317 | 0 | Ok(()) |
318 | 0 | } |
319 | | |
320 | | /// Print discovered assertions |
321 | 0 | fn print_assertions(assertions: &[String]) { |
322 | 0 | println!("Found {} assertions to verify", assertions.len()); |
323 | 0 | for (i, assertion) in assertions.iter().enumerate() { |
324 | 0 | println!(" {}: {}", i + 1, assertion); |
325 | 0 | } |
326 | 0 | } |
327 | | |
328 | | /// Output verification results in requested format |
329 | 0 | fn output_verification_results( |
330 | 0 | results: &[ruchy::proving::ProofVerificationResult], |
331 | 0 | file_path: &std::path::Path, |
332 | 0 | format: &str, |
333 | 0 | verbose: bool, |
334 | 0 | ) -> Result<()> { |
335 | 0 | let total = results.len(); |
336 | 0 | let passed = results.iter().filter(|r| r.is_verified).count(); |
337 | 0 | let failed = total - passed; |
338 | | |
339 | 0 | if format == "json" { |
340 | 0 | output_json_results(results, file_path, total, passed, failed)?; |
341 | 0 | } else { |
342 | 0 | output_text_results(results, total, passed, failed, verbose); |
343 | 0 | } |
344 | | |
345 | 0 | Ok(()) |
346 | 0 | } |
347 | | |
348 | | /// Output results in JSON format |
349 | 0 | fn output_json_results( |
350 | 0 | results: &[ruchy::proving::ProofVerificationResult], |
351 | 0 | file_path: &std::path::Path, |
352 | 0 | total: usize, |
353 | 0 | passed: usize, |
354 | 0 | failed: usize, |
355 | 0 | ) -> Result<()> { |
356 | 0 | let json_result = serde_json::json!({ |
357 | 0 | "file": file_path.display().to_string(), |
358 | 0 | "status": if failed == 0 { "verified" } else { "failed" }, |
359 | 0 | "total": total, |
360 | 0 | "passed": passed, |
361 | 0 | "failed": failed, |
362 | 0 | "proofs": results |
363 | | }); |
364 | 0 | println!("{}", serde_json::to_string_pretty(&json_result)?); |
365 | 0 | Ok(()) |
366 | 0 | } |
367 | | |
368 | | /// Output results in text format |
369 | 0 | fn output_text_results( |
370 | 0 | results: &[ruchy::proving::ProofVerificationResult], |
371 | 0 | total: usize, |
372 | 0 | _passed: usize, |
373 | 0 | failed: usize, |
374 | 0 | verbose: bool, |
375 | 0 | ) { |
376 | 0 | if failed == 0 { |
377 | 0 | println!("✅ All {} proofs verified successfully", total); |
378 | 0 | if verbose { |
379 | 0 | for (i, result) in results.iter().enumerate() { |
380 | 0 | println!(" ✅ Proof {}: {} ({:.1}ms)", |
381 | 0 | i + 1, result.assertion, result.verification_time_ms); |
382 | 0 | } |
383 | 0 | } |
384 | | } else { |
385 | 0 | print_failed_proofs(results, total, failed); |
386 | 0 | if verbose { |
387 | 0 | print_passed_proofs(results); |
388 | 0 | } |
389 | | } |
390 | 0 | } |
391 | | |
392 | | /// Print failed proofs |
393 | 0 | fn print_failed_proofs( |
394 | 0 | results: &[ruchy::proving::ProofVerificationResult], |
395 | 0 | total: usize, |
396 | 0 | failed: usize, |
397 | 0 | ) { |
398 | 0 | println!("❌ {} of {} proofs failed verification", failed, total); |
399 | 0 | for (i, result) in results.iter().enumerate() { |
400 | 0 | if !result.is_verified { |
401 | 0 | println!(" ❌ Proof {}: {}", i + 1, result.assertion); |
402 | 0 | if let Some(ref counterex) = result.counterexample { |
403 | 0 | println!(" Counterexample: {}", counterex); |
404 | 0 | } |
405 | 0 | if let Some(ref error) = result.error { |
406 | 0 | println!(" Error: {}", error); |
407 | 0 | } |
408 | 0 | } |
409 | | } |
410 | 0 | } |
411 | | |
412 | | /// Print passed proofs |
413 | 0 | fn print_passed_proofs(results: &[ruchy::proving::ProofVerificationResult]) { |
414 | 0 | println!("\nPassed proofs:"); |
415 | 0 | for (i, result) in results.iter().enumerate() { |
416 | 0 | if result.is_verified { |
417 | 0 | println!(" ✅ Proof {}: {} ({:.1}ms)", |
418 | 0 | i + 1, result.assertion, result.verification_time_ms); |
419 | 0 | } |
420 | | } |
421 | 0 | } |
422 | | |
423 | | /// Check if any verifications failed and exit accordingly |
424 | 0 | fn check_verification_failures(results: &[ruchy::proving::ProofVerificationResult]) { |
425 | 0 | let failed = results.iter().filter(|r| !r.is_verified).count(); |
426 | 0 | if failed > 0 { |
427 | 0 | std::process::exit(1); |
428 | 0 | } |
429 | 0 | } |