Skip to content

Commit 1ef4c61

Browse files
committed
rust: use file format traits
1 parent ac7066b commit 1ef4c61

File tree

3 files changed

+96
-43
lines changed

3 files changed

+96
-43
lines changed

rust/bear/src/modes/semantic.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// SPDX-License-Identifier: GPL-3.0-or-later
22

33
use crate::intercept::Event;
4-
use crate::output::IteratorWriter;
54
use crate::semantic::interpreters;
65
use crate::semantic::transformation::FilterAndFormat;
76
use crate::semantic::{transformation, Recognition};

rust/bear/src/output/clang/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use crate::output::json;
1515
use serde::Serialize;
1616
use serde_json::Error;
17-
1817
mod tests;
1918
mod type_de;
2019

rust/bear/src/output/mod.rs

Lines changed: 96 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ mod json;
1919

2020
use super::{args, config, intercept, semantic};
2121
use anyhow::Context;
22-
use serde::ser::SerializeSeq;
23-
use serde::Serializer;
22+
use serde_json::de::IoRead;
23+
use serde_json::StreamDeserializer;
2424
use std::{fs, io, path};
2525
use thiserror::Error;
2626

@@ -29,8 +29,21 @@ use thiserror::Error;
2929
/// The file format in this project is usually a sequence of values. This trait
3030
/// provides a type-independent abstraction over the file format.
3131
pub trait FileFormat<T> {
32-
fn write(self, _: impl Iterator<Item = T>) -> Result<(), Error>;
33-
fn read(self) -> impl Iterator<Item = Result<T, Error>>;
32+
fn write(_: impl io::Write, _: impl Iterator<Item = T>) -> Result<(), Error>;
33+
34+
fn read(_: impl io::Read) -> impl Iterator<Item = Result<T, Error>>;
35+
36+
/// Reads the entries from the file and ignores any errors.
37+
/// This is not always feasible, when the file format is strict.
38+
fn read_and_ignore(reader: impl io::Read, source: path::PathBuf) -> impl Iterator<Item = T> {
39+
Self::read(reader).filter_map(move |result| match result {
40+
Ok(value) => Some(value),
41+
Err(error) => {
42+
log::warn!("Failed to read entry: {:?} from {:?}", error, source);
43+
None
44+
}
45+
})
46+
}
3447
}
3548

3649
#[derive(Debug, Error)]
@@ -48,21 +61,47 @@ pub enum Error {
4861
/// The format is a JSON array format, which is a sequence of JSON objects
4962
/// enclosed in square brackets. Each object represents a compilation
5063
/// command.
51-
///
64+
///
5265
/// # Note
5366
/// The format itself is defined in the LLVM project documentation.
5467
/// https://clang.llvm.org/docs/JSONCompilationDatabase.html
55-
pub trait JsonCompilationDatabase: FileFormat<clang::Entry> {}
68+
pub struct JsonCompilationDatabase;
69+
70+
impl FileFormat<clang::Entry> for JsonCompilationDatabase {
71+
fn write(
72+
writer: impl io::Write,
73+
entries: impl Iterator<Item = clang::Entry>,
74+
) -> Result<(), Error> {
75+
json::write_array(writer, entries).map_err(Error::Json)
76+
}
77+
78+
fn read(reader: impl io::Read) -> impl Iterator<Item = Result<clang::Entry, Error>> {
79+
json::read_array(reader).map(|value| value.map_err(Error::Json))
80+
}
81+
}
5682

5783
/// The trait represents a JSON semantic database format.
58-
///
84+
///
5985
/// The format is a JSON array format, which is a sequence of JSON objects
6086
/// enclosed in square brackets. Each object represents a semantic analysis
6187
/// result.
6288
///
6389
/// # Note
6490
/// The output format is not stable and may change in future versions.
65-
pub trait JsonSemanticDatabase: FileFormat<semantic::CompilerCall> {}
91+
pub struct JsonSemanticDatabase;
92+
93+
impl FileFormat<semantic::CompilerCall> for JsonSemanticDatabase {
94+
fn write(
95+
writer: impl io::Write,
96+
entries: impl Iterator<Item = semantic::CompilerCall>,
97+
) -> Result<(), Error> {
98+
json::write_array(writer, entries).map_err(Error::Json)
99+
}
100+
fn read(_: impl io::Read) -> impl Iterator<Item = Result<semantic::CompilerCall, Error>> {
101+
// Not implemented! (No reader for the semantic output in this project.)
102+
std::iter::empty()
103+
}
104+
}
66105

67106
/// The trait represents a database format for execution events.
68107
///
@@ -71,7 +110,26 @@ pub trait JsonSemanticDatabase: FileFormat<semantic::CompilerCall> {}
71110
///
72111
/// # Note
73112
/// The output format is not stable and may change in future versions.
74-
pub trait ExecutionEventDatabase: FileFormat<intercept::Event> {}
113+
pub struct ExecutionEventDatabase;
114+
115+
impl FileFormat<intercept::Event> for ExecutionEventDatabase {
116+
fn write(
117+
writer: impl io::Write,
118+
entries: impl Iterator<Item = intercept::Event>,
119+
) -> Result<(), Error> {
120+
let mut writer = writer;
121+
for entry in entries {
122+
serde_json::to_writer(&mut writer, &entry).map_err(Error::Json)?;
123+
writer.write_all(b"\n").map_err(Error::IO)?;
124+
}
125+
Ok(())
126+
}
127+
128+
fn read(reader: impl io::Read) -> impl Iterator<Item = Result<intercept::Event, Error>> {
129+
let stream = StreamDeserializer::new(IoRead::new(reader));
130+
stream.map(|value| value.map_err(Error::Json))
131+
}
132+
}
75133

76134
/// The trait represents a writer for iterator type `T`.
77135
///
@@ -81,16 +139,16 @@ pub trait ExecutionEventDatabase: FileFormat<intercept::Event> {}
81139
pub trait IteratorWriter<T> {
82140
/// Writes the iterator as a sequence of elements.
83141
/// It consumes the iterator and returns either a nothing or an error.
84-
fn write(self, _: impl Iterator<Item = T>) -> anyhow::Result<()>;
142+
fn write_array(self, _: impl Iterator<Item = T>) -> anyhow::Result<()>;
85143
}
86144

87145
/// Represents the output writer, which can handle different types of outputs.
88146
///
89147
/// This enum provides two variants:
90-
/// - `Clang`: Handles output for Clang compilation databases.
91-
/// - `Semantic`: Handles output for semantic analysis results.
148+
/// - `Clang`: Writes output as a JSON compilation database.
149+
/// - `Semantic`: Writes output as a JSON semantic analysis result.
92150
///
93-
/// The specific behavior of each variant is implemented in their respective types.
151+
/// The variants are selected at runtime based on the configuration provided.
94152
pub enum OutputWriter {
95153
#[allow(private_interfaces)]
96154
Clang(FormattedClangOutputWriter),
@@ -117,11 +175,14 @@ impl TryFrom<(&args::BuildSemantic, &config::Output)> for OutputWriter {
117175
}
118176
}
119177

120-
impl IteratorWriter<semantic::CompilerCall> for OutputWriter {
121-
fn write(self, semantics: impl Iterator<Item = semantic::CompilerCall>) -> anyhow::Result<()> {
178+
impl OutputWriter {
179+
pub(crate) fn write(
180+
self,
181+
semantics: impl Iterator<Item = semantic::CompilerCall>,
182+
) -> anyhow::Result<()> {
122183
match self {
123-
Self::Clang(writer) => writer.write(semantics),
124-
Self::Semantic(writer) => writer.write(semantics),
184+
Self::Clang(writer) => writer.write_array(semantics),
185+
Self::Semantic(writer) => writer.write_array(semantics),
125186
}
126187
}
127188
}
@@ -148,14 +209,11 @@ impl TryFrom<&path::Path> for SemanticOutputWriter {
148209
}
149210

150211
impl IteratorWriter<semantic::CompilerCall> for SemanticOutputWriter {
151-
// FIXME: this is the same method as `clang::write` for entries. Should be generalized?
152-
fn write(self, entries: impl Iterator<Item = semantic::CompilerCall>) -> anyhow::Result<()> {
153-
let mut ser = serde_json::Serializer::pretty(self.output);
154-
let mut seq = ser.serialize_seq(None)?;
155-
for entry in entries {
156-
seq.serialize_element(&entry)?;
157-
}
158-
seq.end()?;
212+
fn write_array(
213+
self,
214+
semantics: impl Iterator<Item = semantic::CompilerCall>,
215+
) -> anyhow::Result<()> {
216+
JsonSemanticDatabase::write(self.output, semantics)?;
159217

160218
Ok(())
161219
}
@@ -183,9 +241,12 @@ impl TryFrom<(&args::BuildSemantic, &config::DuplicateFilter)> for FormattedClan
183241
}
184242

185243
impl IteratorWriter<semantic::CompilerCall> for FormattedClangOutputWriter {
186-
fn write(self, semantics: impl Iterator<Item = semantic::CompilerCall>) -> anyhow::Result<()> {
244+
fn write_array(
245+
self,
246+
semantics: impl Iterator<Item = semantic::CompilerCall>,
247+
) -> anyhow::Result<()> {
187248
let entries = semantics.flat_map(|semantic| self.formatter.apply(semantic));
188-
self.writer.write(entries)
249+
self.writer.write_array(entries)
189250
}
190251
}
191252

@@ -225,13 +286,13 @@ impl TryFrom<(&args::BuildSemantic, &config::DuplicateFilter)> for AppendClangOu
225286
}
226287

227288
impl IteratorWriter<clang::Entry> for AppendClangOutputWriter {
228-
fn write(self, entries: impl Iterator<Item = clang::Entry>) -> anyhow::Result<()> {
289+
fn write_array(self, entries: impl Iterator<Item = clang::Entry>) -> anyhow::Result<()> {
229290
if let Some(path) = self.path {
230291
let entries_from_db = Self::read_from_compilation_db(&path)?;
231292
let final_entries = entries_from_db.chain(entries);
232-
self.writer.write(final_entries)
293+
self.writer.write_array(final_entries)
233294
} else {
234-
self.writer.write(entries)
295+
self.writer.write_array(entries)
235296
}
236297
}
237298
}
@@ -250,13 +311,7 @@ impl AppendClangOutputWriter {
250311
.map(io::BufReader::new)
251312
.with_context(|| format!("Failed to open file: {:?}", source))?;
252313

253-
let entries = clang::read(file).filter_map(move |candidate| match candidate {
254-
Ok(entry) => Some(entry),
255-
Err(error) => {
256-
log::error!("Failed to read file: {:?}, reason: {}", source_copy, error);
257-
None
258-
}
259-
});
314+
let entries = JsonCompilationDatabase::read_and_ignore(file, source_copy);
260315
Ok(entries)
261316
}
262317
}
@@ -289,11 +344,11 @@ impl TryFrom<(&path::Path, &config::DuplicateFilter)> for AtomicClangOutputWrite
289344
}
290345

291346
impl IteratorWriter<clang::Entry> for AtomicClangOutputWriter {
292-
fn write(self, entries: impl Iterator<Item = clang::Entry>) -> anyhow::Result<()> {
347+
fn write_array(self, entries: impl Iterator<Item = clang::Entry>) -> anyhow::Result<()> {
293348
let temp_file_name = self.temp_file_name.clone();
294349
let final_file_name = self.final_file_name.clone();
295350

296-
self.writer.write(entries)?;
351+
self.writer.write_array(entries)?;
297352

298353
fs::rename(&temp_file_name, &final_file_name).with_context(|| {
299354
format!(
@@ -333,10 +388,10 @@ impl TryFrom<(&path::Path, &config::DuplicateFilter)> for ClangOutputWriter {
333388
}
334389

335390
impl IteratorWriter<clang::Entry> for ClangOutputWriter {
336-
fn write(self, entries: impl Iterator<Item = clang::Entry>) -> anyhow::Result<()> {
391+
fn write_array(self, entries: impl Iterator<Item = clang::Entry>) -> anyhow::Result<()> {
337392
let mut filter = self.filter.clone();
338393
let filtered_entries = entries.filter(move |entry| filter.unique(entry));
339-
clang::write(self.output, filtered_entries)?;
394+
JsonCompilationDatabase::write(self.output, filtered_entries)?;
340395
Ok(())
341396
}
342397
}

0 commit comments

Comments
 (0)