Coverage Report

Created: 2025-09-05 15:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/proving/prover.rs
Line
Count
Source
1
//! Simplified interactive theorem prover
2
3
use anyhow::Result;
4
use serde::{Deserialize, Serialize};
5
use std::collections::HashMap;
6
7
use super::smt::SmtBackend;
8
use super::tactics::TacticLibrary;
9
10
/// Interactive prover
11
pub struct InteractiveProver {
12
    _backend: SmtBackend,
13
    tactics: TacticLibrary,
14
    timeout: u64,
15
    ml_suggestions: bool,
16
}
17
18
impl InteractiveProver {
19
    /// Create new prover
20
7
    pub fn new(backend: SmtBackend) -> Self {
21
7
        Self {
22
7
            _backend: backend,
23
7
            tactics: TacticLibrary::default(),
24
7
            timeout: 5000,
25
7
            ml_suggestions: false,
26
7
        }
27
7
    }
28
    
29
    /// Set timeout
30
1
    pub fn set_timeout(&mut self, timeout: u64) {
31
1
        self.timeout = timeout;
32
1
    }
33
    
34
    /// Enable ML suggestions
35
2
    pub fn set_ml_suggestions(&mut self, enabled: bool) {
36
2
        self.ml_suggestions = enabled;
37
2
    }
38
    
39
    /// Load proof script
40
1
    pub fn load_script(&mut self, _script: &str) -> Result<()> {
41
        // Simplified: just return ok
42
1
        Ok(())
43
1
    }
44
    
45
    /// Get available tactics
46
0
    pub fn get_available_tactics(&self) -> Vec<&dyn super::tactics::Tactic> {
47
0
        self.tactics.all_tactics()
48
0
    }
49
    
50
    /// Apply tactic
51
1
    pub fn apply_tactic(&mut self, session: &mut ProverSession, tactic_name: &str, args: &[&str]) -> Result<ProofResult> {
52
1
        let 
tactic0
= self.tactics.get_tactic(tactic_name)?;
53
        
54
0
        if let Some(goal) = session.current_goal() {
55
0
            let result = tactic.apply(goal, args, &session.context)?;
56
0
            match result {
57
                StepResult::Solved => {
58
0
                    session.complete_goal();
59
0
                    Ok(ProofResult::Solved)
60
                }
61
0
                StepResult::Simplified(new_goal) => {
62
0
                    session.update_goal(new_goal);
63
0
                    Ok(ProofResult::Progress)
64
                }
65
0
                StepResult::Subgoals(subgoals) => {
66
0
                    session.replace_with_subgoals(subgoals);
67
0
                    Ok(ProofResult::Progress)
68
                }
69
0
                StepResult::Failed(msg) => {
70
0
                    Ok(ProofResult::Failed(msg))
71
                }
72
            }
73
        } else {
74
0
            Ok(ProofResult::Failed("No active goal".to_string()))
75
        }
76
1
    }
77
    
78
    /// Process input
79
3
    pub fn process_input(&mut self, session: &mut ProverSession, input: &str) -> Result<ProofResult> {
80
        // Try to parse as goal
81
3
        if let Some(
goal1
) = input.strip_prefix("prove ") {
82
1
            session.add_goal(goal.to_string());
83
1
            return Ok(ProofResult::Progress);
84
2
        }
85
        
86
        // Try as tactic
87
2
        let parts: Vec<&str> = input.split_whitespace().collect();
88
2
        if !parts.is_empty() {
89
1
            let tactic_name = parts[0];
90
1
            let args = &parts[1..];
91
1
            return self.apply_tactic(session, tactic_name, args);
92
1
        }
93
        
94
1
        Ok(ProofResult::Failed("Unknown command".to_string()))
95
3
    }
96
    
97
    /// Suggest tactics
98
0
    pub fn suggest_tactics(&self, goal: &ProofGoal) -> Result<Vec<super::tactics::TacticSuggestion>> {
99
0
        self.tactics.suggest_tactics(goal, &ProofContext::new())
100
0
    }
101
}
102
103
/// Prover session
104
#[derive(Debug, Clone, Serialize, Deserialize)]
105
pub struct ProverSession {
106
    goals: Vec<ProofGoal>,
107
    context: ProofContext,
108
    history: Vec<String>,
109
}
110
111
impl ProverSession {
112
    /// Create new session
113
23
    pub fn new() -> Self {
114
23
        Self {
115
23
            goals: Vec::new(),
116
23
            context: ProofContext::new(),
117
23
            history: Vec::new(),
118
23
        }
119
23
    }
120
    
121
    /// Add goal
122
13
    pub fn add_goal(&mut self, statement: String) {
123
13
        self.goals.push(ProofGoal { statement });
124
13
    }
125
    
126
    /// Get current goal
127
6
    pub fn current_goal(&self) -> Option<&ProofGoal> {
128
6
        self.goals.first()
129
6
    }
130
    
131
    /// Update current goal
132
3
    pub fn update_goal(&mut self, statement: String) {
133
3
        if !self.goals.is_empty() {
134
2
            self.goals[0].statement = statement;
135
2
        
}1
136
3
    }
137
    
138
    /// Complete current goal
139
6
    pub fn complete_goal(&mut self) {
140
6
        if !self.goals.is_empty() {
141
5
            self.goals.remove(0);
142
5
        
}1
143
6
    }
144
    
145
    /// Replace with subgoals
146
3
    pub fn replace_with_subgoals(&mut self, subgoals: Vec<String>) {
147
3
        if !self.goals.is_empty() {
148
2
            self.goals.remove(0);
149
5
            for subgoal in 
subgoals2
.
into_iter2
().
rev2
() {
150
5
                self.goals.insert(0, ProofGoal { statement: subgoal });
151
5
            }
152
1
        }
153
3
    }
154
    
155
    /// Get all goals
156
1
    pub fn get_goals(&self) -> &[ProofGoal] {
157
1
        &self.goals
158
1
    }
159
    
160
    /// Check if complete
161
17
    pub fn is_complete(&self) -> bool {
162
17
        self.goals.is_empty()
163
17
    }
164
    
165
    /// Export to text
166
5
    pub fn to_text_proof(&self) -> String {
167
5
        let mut proof = String::new();
168
5
        proof.push_str("Proof:\n");
169
8
        for 
line3
in &self.history {
170
            use std::fmt::Write;
171
3
            let _ = writeln!(proof, "  {line}");
172
        }
173
5
        if self.is_complete() {
174
4
            proof.push_str("Qed.\n");
175
4
        
}1
176
5
        proof
177
5
    }
178
    
179
    /// Export to Coq
180
1
    pub fn to_coq_proof(&self) -> String {
181
1
        self.to_text_proof() // Simplified
182
1
    }
183
    
184
    /// Export to Lean
185
1
    pub fn to_lean_proof(&self) -> String {
186
1
        self.to_text_proof() // Simplified
187
1
    }
188
}
189
190
impl Default for ProverSession {
191
1
    fn default() -> Self {
192
1
        Self::new()
193
1
    }
194
}
195
196
/// Proof goal
197
#[derive(Debug, Clone, Serialize, Deserialize)]
198
pub struct ProofGoal {
199
    pub statement: String,
200
}
201
202
/// Proof context
203
#[derive(Debug, Clone, Serialize, Deserialize)]
204
pub struct ProofContext {
205
    pub assumptions: Vec<String>,
206
    pub definitions: HashMap<String, String>,
207
}
208
209
impl ProofContext {
210
    /// Create new context
211
25
    pub fn new() -> Self {
212
25
        Self {
213
25
            assumptions: Vec::new(),
214
25
            definitions: HashMap::new(),
215
25
        }
216
25
    }
217
}
218
219
impl Default for ProofContext {
220
1
    fn default() -> Self {
221
1
        Self::new()
222
1
    }
223
}
224
225
/// Proof result
226
#[derive(Debug)]
227
pub enum ProofResult {
228
    Solved,
229
    Progress,
230
    Failed(String),
231
}
232
233
/// Step result from tactic application
234
#[derive(Debug, Clone)]
235
pub enum StepResult {
236
    /// Goal was solved
237
    Solved,
238
    
239
    /// Goal was simplified to new goal
240
    Simplified(String),
241
    
242
    /// Goal was split into subgoals
243
    Subgoals(Vec<String>),
244
    
245
    /// Tactic failed
246
    Failed(String),
247
}
248
249
#[cfg(test)]
250
mod tests {
251
    use super::*;
252
    use crate::proving::smt::SmtBackend;
253
254
7
    fn create_test_prover() -> InteractiveProver {
255
7
        let backend = SmtBackend::Z3;
256
7
        InteractiveProver::new(backend)
257
7
    }
258
259
22
    fn create_test_session() -> ProverSession {
260
22
        ProverSession::new()
261
22
    }
262
263
    // Test 1: Prover Creation and Basic Configuration
264
    #[test]
265
1
    fn test_prover_creation() {
266
1
        let prover = create_test_prover();
267
1
        assert_eq!(prover.timeout, 5000);
268
1
        assert!(!prover.ml_suggestions);
269
1
    }
270
271
    #[test]
272
1
    fn test_prover_timeout_setting() {
273
1
        let mut prover = create_test_prover();
274
1
        prover.set_timeout(10000);
275
1
        assert_eq!(prover.timeout, 10000);
276
1
    }
277
278
    #[test]
279
1
    fn test_prover_ml_suggestions_setting() {
280
1
        let mut prover = create_test_prover();
281
1
        prover.set_ml_suggestions(true);
282
1
        assert!(prover.ml_suggestions);
283
        
284
1
        prover.set_ml_suggestions(false);
285
1
        assert!(!prover.ml_suggestions);
286
1
    }
287
288
    #[test]
289
1
    fn test_prover_load_script() {
290
1
        let mut prover = create_test_prover();
291
1
        let result = prover.load_script("example script");
292
1
        assert!(result.is_ok());
293
1
    }
294
295
    // Test 2: Session Management
296
    #[test]
297
1
    fn test_session_creation() {
298
1
        let session = create_test_session();
299
1
        assert!(session.goals.is_empty());
300
1
        assert!(session.history.is_empty());
301
1
        assert!(session.is_complete());
302
1
    }
303
304
    #[test]
305
1
    fn test_session_default() {
306
1
        let session = ProverSession::default();
307
1
        assert!(session.goals.is_empty());
308
1
        assert!(session.is_complete());
309
1
    }
310
311
    #[test]
312
1
    fn test_session_add_goal() {
313
1
        let mut session = create_test_session();
314
1
        session.add_goal("forall x, x = x".to_string());
315
        
316
1
        assert_eq!(session.goals.len(), 1);
317
1
        assert!(!session.is_complete());
318
        
319
1
        let current_goal = session.current_goal().unwrap();
320
1
        assert_eq!(current_goal.statement, "forall x, x = x");
321
1
    }
322
323
    #[test]
324
1
    fn test_session_multiple_goals() {
325
1
        let mut session = create_test_session();
326
1
        session.add_goal("goal1".to_string());
327
1
        session.add_goal("goal2".to_string());
328
        
329
1
        assert_eq!(session.goals.len(), 2);
330
1
        let current = session.current_goal().unwrap();
331
1
        assert_eq!(current.statement, "goal1");
332
1
    }
333
334
    #[test]
335
1
    fn test_session_complete_goal() {
336
1
        let mut session = create_test_session();
337
1
        session.add_goal("test goal".to_string());
338
        
339
1
        assert!(!session.is_complete());
340
1
        session.complete_goal();
341
1
        assert!(session.is_complete());
342
1
    }
343
344
    #[test]
345
1
    fn test_session_update_goal() {
346
1
        let mut session = create_test_session();
347
1
        session.add_goal("original goal".to_string());
348
        
349
1
        session.update_goal("updated goal".to_string());
350
1
        let current = session.current_goal().unwrap();
351
1
        assert_eq!(current.statement, "updated goal");
352
1
    }
353
354
    #[test]
355
1
    fn test_session_replace_with_subgoals() {
356
1
        let mut session = create_test_session();
357
1
        session.add_goal("main goal".to_string());
358
        
359
1
        let subgoals = vec!["subgoal1".to_string(), "subgoal2".to_string()];
360
1
        session.replace_with_subgoals(subgoals);
361
        
362
1
        assert_eq!(session.goals.len(), 2);
363
        // Due to .rev() then inserting at position 0, order is preserved
364
1
        assert_eq!(session.goals[0].statement, "subgoal1"); 
365
1
        assert_eq!(session.goals[1].statement, "subgoal2");
366
1
    }
367
368
    // Test 3: Goal Operations
369
    #[test]
370
1
    fn test_get_goals() {
371
1
        let mut session = create_test_session();
372
1
        session.add_goal("goal1".to_string());
373
1
        session.add_goal("goal2".to_string());
374
        
375
1
        let goals = session.get_goals();
376
1
        assert_eq!(goals.len(), 2);
377
1
        assert_eq!(goals[0].statement, "goal1");
378
1
        assert_eq!(goals[1].statement, "goal2");
379
1
    }
380
381
    #[test]
382
1
    fn test_session_completion_status() {
383
1
        let mut session = create_test_session();
384
1
        assert!(session.is_complete());
385
        
386
1
        session.add_goal("test".to_string());
387
1
        assert!(!session.is_complete());
388
        
389
1
        session.complete_goal();
390
1
        assert!(session.is_complete());
391
1
    }
392
393
    // Test 4: Text Export Features
394
    #[test]
395
1
    fn test_text_proof_export_empty() {
396
1
        let session = create_test_session();
397
1
        let text_proof = session.to_text_proof();
398
        
399
1
        assert!(text_proof.contains("Proof:"));
400
1
        assert!(text_proof.contains("Qed."));
401
1
    }
402
403
    #[test]
404
1
    fn test_text_proof_export_with_history() {
405
1
        let mut session = create_test_session();
406
1
        session.history.push("apply reflexivity".to_string());
407
1
        session.history.push("exact H".to_string());
408
        
409
1
        let text_proof = session.to_text_proof();
410
1
        assert!(text_proof.contains("apply reflexivity"));
411
1
        assert!(text_proof.contains("exact H"));
412
1
        assert!(text_proof.contains("Qed."));
413
1
    }
414
415
    #[test]
416
1
    fn test_text_proof_export_incomplete() {
417
1
        let mut session = create_test_session();
418
1
        session.add_goal("incomplete goal".to_string());
419
1
        session.history.push("started proof".to_string());
420
        
421
1
        let text_proof = session.to_text_proof();
422
1
        assert!(text_proof.contains("started proof"));
423
1
        assert!(!text_proof.contains("Qed.")); // Should not have Qed for incomplete proof
424
1
    }
425
426
    #[test]
427
1
    fn test_coq_proof_export() {
428
1
        let session = create_test_session();
429
1
        let coq_proof = session.to_coq_proof();
430
        
431
        // Currently simplified to text proof
432
1
        assert!(coq_proof.contains("Proof:"));
433
1
        assert!(coq_proof.contains("Qed."));
434
1
    }
435
436
    #[test]
437
1
    fn test_lean_proof_export() {
438
1
        let session = create_test_session();
439
1
        let lean_proof = session.to_lean_proof();
440
        
441
        // Currently simplified to text proof
442
1
        assert!(lean_proof.contains("Proof:"));
443
1
        assert!(lean_proof.contains("Qed."));
444
1
    }
445
446
    // Test 5: Input Processing
447
    #[test]
448
1
    fn test_process_prove_command() {
449
1
        let mut prover = create_test_prover();
450
1
        let mut session = create_test_session();
451
        
452
1
        let result = prover.process_input(&mut session, "prove forall x, x = x").unwrap();
453
        
454
1
        match result {
455
            ProofResult::Progress => {
456
1
                assert_eq!(session.goals.len(), 1);
457
1
                assert_eq!(session.current_goal().unwrap().statement, "forall x, x = x");
458
            }
459
0
            _ => panic!("Expected Progress result"),
460
        }
461
1
    }
462
463
    #[test]
464
1
    fn test_process_empty_input() {
465
1
        let mut prover = create_test_prover();
466
1
        let mut session = create_test_session();
467
        
468
1
        let result = prover.process_input(&mut session, "").unwrap();
469
        
470
1
        match result {
471
1
            ProofResult::Failed(msg) => {
472
1
                assert!(msg.contains("Unknown command"));
473
            }
474
0
            _ => panic!("Expected Failed result"),
475
        }
476
1
    }
477
478
    #[test]
479
1
    fn test_process_unknown_command() {
480
1
        let mut prover = create_test_prover();
481
1
        let mut session = create_test_session();
482
        
483
1
        let result = prover.process_input(&mut session, "unknown_command arg1 arg2");
484
        
485
        // Should try to apply as tactic and fail with error
486
0
        match result {
487
1
            Err(e) => {
488
1
                assert!(e.to_string().contains("Unknown tactic"));
489
            }
490
0
            Ok(ProofResult::Failed(_)) => {
491
0
                // This is also acceptable
492
0
            }
493
0
            _ => panic!("Expected error or Failed result"),
494
        }
495
1
    }
496
497
    // Test 6: Proof Context
498
    #[test]
499
1
    fn test_proof_context_creation() {
500
1
        let context = ProofContext::new();
501
1
        assert!(context.assumptions.is_empty());
502
1
        assert!(context.definitions.is_empty());
503
1
    }
504
505
    #[test]
506
1
    fn test_proof_context_default() {
507
1
        let context = ProofContext::default();
508
1
        assert!(context.assumptions.is_empty());
509
1
        assert!(context.definitions.is_empty());
510
1
    }
511
512
    // Test 7: Proof Goal Structure
513
    #[test]
514
1
    fn test_proof_goal_creation() {
515
1
        let goal = ProofGoal {
516
1
            statement: "test statement".to_string(),
517
1
        };
518
1
        assert_eq!(goal.statement, "test statement");
519
1
    }
520
521
    // Test 8: Edge Cases
522
    #[test]
523
1
    fn test_complete_goal_empty_session() {
524
1
        let mut session = create_test_session();
525
1
        session.complete_goal(); // Should not panic
526
1
        assert!(session.is_complete());
527
1
    }
528
529
    #[test]
530
1
    fn test_update_goal_empty_session() {
531
1
        let mut session = create_test_session();
532
1
        session.update_goal("new goal".to_string()); // Should not panic
533
1
        assert!(session.is_complete());
534
1
    }
535
536
    #[test]
537
1
    fn test_replace_subgoals_empty_session() {
538
1
        let mut session = create_test_session();
539
1
        session.replace_with_subgoals(vec!["goal1".to_string()]); // Should not panic
540
1
        assert!(session.is_complete());
541
1
    }
542
543
    #[test]
544
1
    fn test_current_goal_empty_session() {
545
1
        let session = create_test_session();
546
1
        assert!(session.current_goal().is_none());
547
1
    }
548
549
    // Test 9: Serialization (Basic Structure Test)
550
    #[test]
551
1
    fn test_session_serialization_structure() {
552
1
        let mut session = create_test_session();
553
1
        session.add_goal("test goal".to_string());
554
1
        session.context.assumptions.push("assumption1".to_string());
555
1
        session.history.push("step1".to_string());
556
        
557
        // Test that all fields are accessible for serialization
558
1
        assert_eq!(session.goals.len(), 1);
559
1
        assert_eq!(session.context.assumptions.len(), 1);
560
1
        assert_eq!(session.history.len(), 1);
561
1
    }
562
563
    // Test 10: Multiple Session Operations
564
    #[test]
565
1
    fn test_complex_session_workflow() {
566
1
        let mut session = create_test_session();
567
        
568
        // Add initial goal
569
1
        session.add_goal("main theorem".to_string());
570
1
        assert_eq!(session.goals.len(), 1);
571
        
572
        // Replace with subgoals
573
1
        session.replace_with_subgoals(vec![
574
1
            "subgoal 1".to_string(),
575
1
            "subgoal 2".to_string(),
576
1
            "subgoal 3".to_string(),
577
        ]);
578
1
        assert_eq!(session.goals.len(), 3);
579
        
580
        // Complete first subgoal
581
1
        session.complete_goal();
582
1
        assert_eq!(session.goals.len(), 2);
583
        
584
        // Update current goal
585
1
        session.update_goal("modified subgoal".to_string());
586
1
        assert_eq!(session.current_goal().unwrap().statement, "modified subgoal");
587
        
588
        // Complete remaining goals
589
1
        session.complete_goal();
590
1
        session.complete_goal();
591
1
        assert!(session.is_complete());
592
1
    }
593
}