#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
use crate::sat::clause_storage::LiteralStorage;
use crate::sat::cnf::Cnf;
use crate::sat::literal::Literal;
use itertools::Itertools; use std::io::{self, BufRead};
use std::path::PathBuf;
pub fn parse_dimacs_text<L: Literal, S: LiteralStorage<L>>(
dimacs_text: &str,
) -> Result<Cnf<L, S>, String> {
let reader = io::Cursor::new(dimacs_text);
parse_dimacs(reader)
}
pub fn parse_dimacs<R: BufRead, L: Literal, S: LiteralStorage<L>>(
reader: R,
) -> Result<Cnf<L, S>, String> {
let mut lines = reader
.lines()
.map_while(Result::ok)
.filter(|line| !line.is_empty());
let mut clauses_dimacs: Vec<Vec<i32>> = Vec::new();
for line_str in &mut lines {
let mut parts = line_str.split_whitespace().peekable();
match parts.peek() {
Some(&"%") => break,
None | Some(&"c" | &"p") => {}
Some(_) => {
let clause_literals: Vec<i32> = parts
.map(|s| {
s.parse::<i32>()
.unwrap_or_else(|e| panic!("Failed to parse literal '{s}' as i32: {e}"))
})
.filter(|&p| p != 0)
.collect_vec();
if !clause_literals.is_empty() {
clauses_dimacs.push(clause_literals);
}
}
}
}
Ok(Cnf::new(clauses_dimacs))
}
pub fn parse_file<T: Literal, S: LiteralStorage<T>>(file_path: &PathBuf) -> io::Result<Cnf<T, S>> {
let file = std::fs::File::open(file_path)?;
let reader = io::BufReader::new(file);
let cnf: Cnf<T, S> = parse_dimacs(reader).map_err(|s| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Failed to parse DIMACS file: {}, {s}", file_path.display()),
)
})?;
if cnf.clauses.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("No clauses found in DIMACS file: {}", file_path.display()),
));
}
Ok(cnf)
}
#[allow(dead_code)]
pub fn get_all_files(dir_path: &str) -> io::Result<Vec<String>> {
let mut files_found = Vec::new();
for entry_result in std::fs::read_dir(dir_path)? {
let entry = entry_result?;
let path = entry.path();
if path.is_file() {
let path_str = path
.to_str()
.unwrap_or_else(|| panic!("Path contains non-UTF8 characters: {}", path.display()))
.to_string();
files_found.push(path_str);
} else if path.is_dir() {
let sub_dir_path_str = path.to_str().unwrap_or_else(|| {
panic!(
"Subdirectory path contains non-UTF8 characters: {}",
path.display()
)
});
let mut sub_files = get_all_files(sub_dir_path_str)?;
files_found.append(&mut sub_files);
}
}
Ok(files_found)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sat::literal::{DoubleLiteral, PackedLiteral};
use std::io::Cursor;
type TestCnf = Cnf<PackedLiteral, smallvec::SmallVec<[PackedLiteral; 8]>>;
#[test]
fn test_parse_simple_dimacs() {
let dimacs_content = "c This is a comment\n\
p cnf 3 2\n\
1 -2 0\n\
2 3 0\n";
let reader = Cursor::new(dimacs_content);
let result = parse_dimacs(reader);
assert!(result.is_ok(), "Parsing should succeed");
let cnf: TestCnf = result.unwrap();
assert_eq!(cnf.clauses.len(), 2, "Should parse 2 clauses");
assert_eq!(cnf.num_vars, 3 + 1, "Number of variables mismatch");
assert_eq!(cnf.clauses[0].len(), 2);
let c1_lits: Vec<i32> = cnf.clauses[0]
.iter()
.map(Literal::to_i32)
.sorted()
.collect();
assert_eq!(c1_lits, vec![-2, 1]);
assert_eq!(cnf.clauses[1].len(), 2);
let c2_lits: Vec<i32> = cnf.clauses[1]
.iter()
.map(Literal::to_i32)
.sorted()
.collect();
assert_eq!(c2_lits, vec![2, 3]);
}
#[test]
fn test_parse_dimacs_with_empty_lines_and_end_marker() {
let dimacs_content = "p cnf 2 2\n\
\n\
1 0\n\
\n\
-2 0\n\
%\n\
c this should be ignored";
let reader = Cursor::new(dimacs_content);
let result = parse_dimacs(reader);
assert!(result.is_ok(), "Parsing should succeed");
let cnf: TestCnf = result.unwrap();
assert_eq!(cnf.clauses.len(), 2);
assert_eq!(cnf.num_vars, 2 + 1);
assert_eq!(cnf.clauses[0].iter().next().unwrap().to_i32(), 1);
assert_eq!(cnf.clauses[1].iter().next().unwrap().to_i32(), -2);
}
#[test]
fn test_parse_dimacs_empty_clause() {
let dimacs_content = "p cnf 1 1\n0\n";
let reader = Cursor::new(dimacs_content);
let result = parse_dimacs(reader);
assert!(result.is_ok(), "Parsing should succeed");
let cnf: TestCnf = result.unwrap();
assert_eq!(cnf.clauses.len(), 0, "Should parse 0 clauses");
}
#[test]
fn test_parse_dimacs_malformed_literal() {
let dimacs_content = "1 abc 0\n";
let reader = Cursor::new(dimacs_content);
let result: Result<Cnf<DoubleLiteral, Vec<DoubleLiteral>>, String> = parse_dimacs(reader);
assert!(
result.is_err(),
"Parsing should fail due to malformed literal"
);
}
#[test]
fn test_parse_dimacs_no_clauses() {
let dimacs_content = "p cnf 0 0\n";
let reader = Cursor::new(dimacs_content);
let result = parse_dimacs(reader);
assert!(
result.is_ok(),
"Parsing should succeed even with no clauses"
);
let cnf: TestCnf = result.unwrap();
assert!(cnf.clauses.is_empty());
assert_eq!(cnf.num_vars, 1);
}
}