1use crate::proof::{Proof, ProofNode, ProofNodeId};
7use rustc_hash::FxHashMap;
8use std::fs::{File, OpenOptions};
9use std::io::{self, Read, Seek, SeekFrom, Write};
10use std::path::{Path, PathBuf};
11
12#[derive(Debug, Clone)]
14pub struct MmapConfig {
15 pub file_path: PathBuf,
17 pub read_only: bool,
19 pub sync_on_write: bool,
21 pub cache_size: usize,
23}
24
25impl MmapConfig {
26 pub fn new<P: AsRef<Path>>(path: P) -> Self {
28 Self {
29 file_path: path.as_ref().to_path_buf(),
30 read_only: false,
31 sync_on_write: true,
32 cache_size: 10000,
33 }
34 }
35
36 pub fn with_read_only(mut self, read_only: bool) -> Self {
38 self.read_only = read_only;
39 self
40 }
41
42 pub fn with_sync_on_write(mut self, sync: bool) -> Self {
44 self.sync_on_write = sync;
45 self
46 }
47
48 pub fn with_cache_size(mut self, size: usize) -> Self {
50 self.cache_size = size;
51 self
52 }
53}
54
55pub struct MmapProofStorage {
57 config: MmapConfig,
58 file: Option<File>,
59 index: FxHashMap<ProofNodeId, u64>, cache: FxHashMap<ProofNodeId, ProofNode>,
61 cache_order: Vec<ProofNodeId>,
62}
63
64impl MmapProofStorage {
65 pub fn new(config: MmapConfig) -> io::Result<Self> {
67 let file = if config.read_only {
68 Some(File::open(&config.file_path)?)
69 } else {
70 Some(
71 OpenOptions::new()
72 .read(true)
73 .write(true)
74 .create(true)
75 .truncate(false)
76 .open(&config.file_path)?,
77 )
78 };
79
80 Ok(Self {
81 config,
82 file,
83 index: FxHashMap::default(),
84 cache: FxHashMap::default(),
85 cache_order: Vec::new(),
86 })
87 }
88
89 pub fn store_proof(&mut self, proof: &Proof) -> io::Result<()> {
91 if self.config.read_only {
92 return Err(io::Error::new(
93 io::ErrorKind::PermissionDenied,
94 "Storage is read-only",
95 ));
96 }
97
98 {
99 let file = self
100 .file
101 .as_mut()
102 .ok_or_else(|| io::Error::other("File not opened"))?;
103
104 for node in proof.nodes() {
106 let offset = file.seek(SeekFrom::End(0))?;
107 let node_id = node.id;
108 Self::write_node_to_file(file, node)?;
109 self.index.insert(node_id, offset);
110 }
111
112 if self.config.sync_on_write {
113 file.sync_all()?;
114 }
115 }
116
117 Ok(())
118 }
119
120 pub fn load_node(&mut self, id: ProofNodeId) -> io::Result<Option<ProofNode>> {
122 if let Some(node) = self.cache.get(&id) {
124 return Ok(Some(node.clone()));
125 }
126
127 let offset = match self.index.get(&id) {
129 Some(&off) => off,
130 None => return Ok(None),
131 };
132
133 let node = {
135 let file = self
136 .file
137 .as_mut()
138 .ok_or_else(|| io::Error::other("File not opened"))?;
139
140 file.seek(SeekFrom::Start(offset))?;
141 Self::read_node_from_file(file)?
142 };
143
144 self.add_to_cache(id, node.clone());
146
147 Ok(Some(node))
148 }
149
150 pub fn load_proof(&mut self) -> io::Result<Proof> {
152 let mut proof = Proof::new();
153
154 {
155 let file = self
156 .file
157 .as_mut()
158 .ok_or_else(|| io::Error::other("File not opened"))?;
159 file.seek(SeekFrom::Start(0))?;
160
161 while let Ok(node) = Self::read_node_from_file(file) {
163 match &node.step {
165 crate::proof::ProofStep::Axiom { conclusion } => {
166 proof.add_axiom(conclusion);
167 }
168 crate::proof::ProofStep::Inference {
169 rule,
170 premises,
171 conclusion,
172 ..
173 } => {
174 proof.add_inference(rule, premises.to_vec(), conclusion);
175 }
176 }
177 }
178 }
179
180 Ok(proof)
181 }
182
183 pub fn clear_cache(&mut self) {
185 self.cache.clear();
186 self.cache_order.clear();
187 }
188
189 pub fn cache_stats(&self) -> (usize, usize) {
191 (self.cache.len(), self.config.cache_size)
192 }
193
194 pub fn close(&mut self) -> io::Result<()> {
196 if let Some(file) = self.file.take() {
197 file.sync_all()?;
198 }
199 Ok(())
200 }
201
202 fn write_node_to_file(file: &mut File, node: &ProofNode) -> io::Result<()> {
204 let serialized = format!("{:?}\n", node); file.write_all(serialized.as_bytes())?;
207 Ok(())
208 }
209
210 fn read_node_from_file(file: &mut File) -> io::Result<ProofNode> {
212 let mut buffer = String::new();
213 let mut temp_buf = [0u8; 1];
214
215 loop {
217 let n = file.read(&mut temp_buf)?;
218 if n == 0 || temp_buf[0] == b'\n' {
219 break;
220 }
221 buffer.push(temp_buf[0] as char);
222 }
223
224 if buffer.is_empty() {
225 return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "EOF"));
226 }
227
228 Err(io::Error::new(
231 io::ErrorKind::InvalidData,
232 "Deserialization not implemented",
233 ))
234 }
235
236 fn add_to_cache(&mut self, id: ProofNodeId, node: ProofNode) {
238 if self.cache.len() >= self.config.cache_size {
239 if let Some(oldest_id) = self.cache_order.first().copied() {
241 self.cache.remove(&oldest_id);
242 self.cache_order.remove(0);
243 }
244 }
245
246 self.cache.insert(id, node);
247 self.cache_order.push(id);
248 }
249}
250
251pub struct MmapProof {
253 storage: MmapProofStorage,
254 metadata: ProofMetadata,
255}
256
257#[derive(Debug, Clone, Default)]
259pub struct ProofMetadata {
260 pub num_nodes: usize,
262 pub num_axioms: usize,
264 pub max_depth: usize,
266}
267
268impl MmapProof {
269 pub fn new(config: MmapConfig) -> io::Result<Self> {
271 let storage = MmapProofStorage::new(config)?;
272 Ok(Self {
273 storage,
274 metadata: ProofMetadata::default(),
275 })
276 }
277
278 pub fn store(&mut self, proof: &Proof) -> io::Result<()> {
280 self.metadata.num_nodes = proof.len();
281 self.metadata.num_axioms = proof
282 .nodes()
283 .iter()
284 .filter(|n| matches!(n.step, crate::proof::ProofStep::Axiom { .. }))
285 .count();
286 self.metadata.max_depth = proof
287 .nodes()
288 .iter()
289 .map(|n| n.depth as usize)
290 .max()
291 .unwrap_or(0);
292
293 self.storage.store_proof(proof)
294 }
295
296 pub fn get_node(&mut self, id: ProofNodeId) -> io::Result<Option<ProofNode>> {
298 self.storage.load_node(id)
299 }
300
301 pub fn load_all(&mut self) -> io::Result<Proof> {
303 self.storage.load_proof()
304 }
305
306 pub fn metadata(&self) -> &ProofMetadata {
308 &self.metadata
309 }
310
311 pub fn clear_cache(&mut self) {
313 self.storage.clear_cache();
314 }
315
316 pub fn close(mut self) -> io::Result<()> {
318 self.storage.close()
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325 use std::env;
326
327 fn temp_path() -> PathBuf {
328 let mut path = env::temp_dir();
329 path.push(format!("test_proof_{}.mmap", std::process::id()));
330 path
331 }
332
333 #[test]
334 fn test_mmap_config_new() {
335 let path = temp_path();
336 let config = MmapConfig::new(&path);
337 assert_eq!(config.file_path, path);
338 assert!(!config.read_only);
339 assert!(config.sync_on_write);
340 assert_eq!(config.cache_size, 10000);
341 }
342
343 #[test]
344 fn test_mmap_config_with_settings() {
345 let path = temp_path();
346 let config = MmapConfig::new(&path)
347 .with_read_only(true)
348 .with_sync_on_write(false)
349 .with_cache_size(5000);
350 assert!(config.read_only);
351 assert!(!config.sync_on_write);
352 assert_eq!(config.cache_size, 5000);
353 }
354
355 #[test]
356 fn test_mmap_proof_new() {
357 let path = temp_path();
358 let config = MmapConfig::new(&path);
359 let result = MmapProof::new(config);
360 if let Ok(mmap_proof) = result {
362 assert_eq!(mmap_proof.metadata.num_nodes, 0);
363 }
364 }
365
366 #[test]
367 fn test_proof_metadata_default() {
368 let metadata = ProofMetadata::default();
369 assert_eq!(metadata.num_nodes, 0);
370 assert_eq!(metadata.num_axioms, 0);
371 assert_eq!(metadata.max_depth, 0);
372 }
373
374 #[test]
375 fn test_cache_stats() {
376 let path = temp_path();
377 let config = MmapConfig::new(&path).with_cache_size(100);
378 if let Ok(storage) = MmapProofStorage::new(config) {
379 let (used, total) = storage.cache_stats();
380 assert_eq!(used, 0);
381 assert_eq!(total, 100);
382 }
383 }
384
385 #[test]
386 fn test_clear_cache() {
387 let path = temp_path();
388 let config = MmapConfig::new(&path);
389 if let Ok(mut storage) = MmapProofStorage::new(config) {
390 storage.clear_cache();
391 let (used, _) = storage.cache_stats();
392 assert_eq!(used, 0);
393 }
394 }
395}