oxidd_parser/
load_file.rs1use std::fmt;
6use std::path::Path;
7
8use codespan_reporting::diagnostic::{Diagnostic, Label};
9use codespan_reporting::files::SimpleFile;
10use codespan_reporting::term::termcolor::ColorChoice;
11use codespan_reporting::term::termcolor::{StandardStream, WriteColor};
12use codespan_reporting::term::{emit, Config};
13use nom::error::{ContextError, ErrorKind, FromExternalError, ParseError};
14use nom::Offset;
15
16use crate::ParseOptions;
17use crate::Problem;
18use crate::{aiger, dimacs, nnf};
19
20#[non_exhaustive]
25#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
26pub enum FileType {
27 DIMACS,
31 NNF,
35 AIGER,
39}
40
41impl FileType {
42 pub fn from_path(path: &Path) -> Option<Self> {
44 let ext = path.extension()?;
45 match ext.as_encoded_bytes() {
46 b"cnf" | b"sat" | b"dimacs" => Some(FileType::DIMACS),
47 b"nnf" => Some(FileType::NNF),
48 b"aag" | b"aig" => Some(FileType::AIGER),
49 _ => None,
50 }
51 }
52}
53
54struct ParserReport<I>(Vec<(I, ParserError)>);
55
56enum ParserError {
57 Nom(ErrorKind),
58 Char(char),
59 Context(&'static str),
60 External(String),
61}
62
63impl<I> ParseError<I> for ParserReport<I> {
64 fn from_error_kind(input: I, kind: ErrorKind) -> Self {
65 ParserReport(vec![(input, ParserError::Nom(kind))])
66 }
67
68 fn append(input: I, kind: ErrorKind, mut other: Self) -> Self {
69 other.0.push((input, ParserError::Nom(kind)));
70 other
71 }
72
73 fn from_char(input: I, c: char) -> Self {
74 ParserReport(vec![(input, ParserError::Char(c))])
75 }
76}
77
78impl<I> ContextError<I> for ParserReport<I> {
79 fn add_context(input: I, ctx: &'static str, mut other: Self) -> Self {
80 match other.0[0].1 {
81 ParserError::Context(_) => {}
82 _ => other.0.clear(),
84 }
85 other.0.push((input, ParserError::Context(ctx)));
86 other
87 }
88}
89
90impl<I, S: ToString> FromExternalError<I, S> for ParserReport<I> {
91 fn from_external_error(input: I, _kind: ErrorKind, e: S) -> Self {
92 Self(vec![(input, ParserError::External(e.to_string()))])
93 }
94}
95
96pub fn parse<S: AsRef<str> + Clone + fmt::Display>(
105 input: &[u8],
106 file_type: FileType,
107 parse_options: &ParseOptions,
108 file_id: S,
109 writer: &mut dyn WriteColor,
110 config: &Config,
111) -> Option<Problem> {
112 let parse_result = match file_type {
113 FileType::DIMACS => dimacs::parse::<ParserReport<_>>(parse_options)(input),
114 FileType::NNF => nnf::parse::<ParserReport<_>>(parse_options)(input),
115 FileType::AIGER => aiger::parse::<ParserReport<_>>(parse_options)(input),
116 };
117 let errors = match parse_result {
118 Ok((rest, problem)) => {
119 debug_assert!(rest.is_empty());
120 return Some(problem);
121 }
122 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => e.0,
123 Err(nom::Err::Incomplete(_)) => unreachable!("only using complete parsers"),
124 };
125
126 let range = move |span: &[u8]| {
127 let offset = input.offset(span);
128 let end = offset + span.len();
129 if end >= input.len() {
130 offset..offset
131 } else {
132 offset..end
133 }
134 };
135
136 let mut labels = Vec::with_capacity(errors.len());
137 let mut errors = errors.into_iter().map(|(span, err)| {
138 (
139 span,
140 match err {
141 ParserError::Nom(e) => format!("Expected {}", e.description()),
142 ParserError::Char(c) => format!("Expected '{c}'"),
143 ParserError::Context(msg) => msg.to_string(),
144 ParserError::External(s) => s,
145 },
146 )
147 });
148 let (span, error) = errors.next().unwrap();
149 labels.push(Label::primary((), range(span)).with_message(error.to_string()));
150 for (span, error) in errors {
151 labels.push(Label::secondary((), range(span)).with_message(error.to_string()));
152 }
153
154 let diagnostic = Diagnostic::error()
155 .with_message("parsing failed")
156 .with_labels(labels);
157
158 let file = SimpleFile::new(file_id, String::from_utf8_lossy(input));
159 emit(writer, config, &file, &diagnostic).ok();
160
161 None
162}
163
164pub fn load_file(path: impl AsRef<Path>, options: &ParseOptions) -> Option<Problem> {
168 let path = path.as_ref();
169 let file_type = match FileType::from_path(path) {
170 Some(t) => t,
171 None => {
172 eprintln!(
173 "error: could not determine file type of '{}'",
174 path.display()
175 );
176 return None;
177 }
178 };
179
180 let src = match std::fs::read(path) {
181 Ok(src) => src,
182 Err(err) => {
183 eprintln!("error: could not read '{}' ({err})", path.display());
184 return None;
185 }
186 };
187
188 let config = codespan_reporting::term::Config::default();
189 let writer = StandardStream::stderr(ColorChoice::Auto);
190 let mut write_lock = writer.lock();
191
192 parse(
193 &src,
194 file_type,
195 options,
196 path.to_string_lossy(),
197 &mut write_lock,
198 &config,
199 )
200}