Skip to main content

custom_sink/
custom_sink.rs

1//! Receives diagnostics through a custom [`DiagnosticSink`], here one that
2//! renders them as JSON lines for an IDE or a build system.
3//!
4//! ```sh
5//! cargo run --example custom_sink
6//! ```
7//!
8//! The link below has an undefined symbol, so it fails with
9//! [`qld::Error::Reported`] after reporting the error to the sink. Stages
10//! report from many threads at once, so a sink must be `Send + Sync`; to
11//! print in a deterministic order, collect first and sort by
12//! [`Diagnostic::order`], as this sink does.
13
14#[path = "support/objects.rs"]
15mod objects;
16
17use std::sync::Mutex;
18use std::sync::atomic::{AtomicUsize, Ordering};
19
20use qld::args::{InputAttrs, InputKind, LinkOptions, OutputBuffer, OutputKind};
21use qld::diag::{Diagnostic, DiagnosticSink, Severity};
22
23/// Collects diagnostics and renders them as JSON lines.
24#[derive(Default)]
25struct JsonLines {
26    diagnostics: Mutex<Vec<Diagnostic>>,
27    errors: AtomicUsize,
28}
29
30impl DiagnosticSink for JsonLines {
31    fn emit(&self, diagnostic: Diagnostic) {
32        if diagnostic.severity == Severity::Error {
33            self.errors.fetch_add(1, Ordering::Relaxed);
34        }
35        if let Ok(mut diagnostics) = self.diagnostics.lock() {
36            diagnostics.push(diagnostic);
37        }
38    }
39
40    fn error_count(&self) -> usize {
41        self.errors.load(Ordering::Relaxed)
42    }
43}
44
45impl JsonLines {
46    /// The diagnostics so far, one JSON object per line, in a deterministic
47    /// order.
48    fn render(&self) -> String {
49        let mut diagnostics = match self.diagnostics.lock() {
50            Ok(mut diagnostics) => std::mem::take(&mut *diagnostics),
51            Err(_) => return String::new(),
52        };
53        diagnostics.sort_by(|a, b| a.order.cmp(&b.order).then(a.message.cmp(&b.message)));
54        let mut out = String::new();
55        for diagnostic in diagnostics {
56            let locations: Vec<String> = diagnostic
57                .locations
58                .iter()
59                .map(|location| quote(&location.to_string()))
60                .collect();
61            let details: Vec<String> = diagnostic.details.iter().map(|d| quote(d)).collect();
62            out.push_str(&format!(
63                "{{\"severity\":{},\"message\":{},\"locations\":[{}],\"details\":[{}]}}\n",
64                quote(&diagnostic.severity.to_string()),
65                quote(&diagnostic.message),
66                locations.join(","),
67                details.join(","),
68            ));
69        }
70        out
71    }
72}
73
74/// A JSON string literal.
75fn quote(text: &str) -> String {
76    let mut out = String::from('"');
77    for c in text.chars() {
78        match c {
79            '"' => out.push_str("\\\""),
80            '\\' => out.push_str("\\\\"),
81            c if u32::from(c) < 0x20 => out.push_str(&format!("\\u{:04x}", u32::from(c))),
82            c => out.push(c),
83        }
84    }
85    out.push('"');
86    out
87}
88
89fn main() {
90    // `main.o` calls `answer`, which nothing defines.
91    let mut options = LinkOptions::new();
92    options.kind = OutputKind::StaticExecutable;
93    options.output_buffer = Some(OutputBuffer::new());
94    options.push_input(
95        InputKind::bytes("main.o", objects::main_object()),
96        InputAttrs::default(),
97    );
98
99    let sink = JsonLines::default();
100    let result = qld::link(&options, &sink);
101    print!("{}", sink.render());
102    match result {
103        Ok(()) => println!("linked"),
104        Err(qld::Error::Reported { errors }) => {
105            println!("the link failed with {errors} error(s) reported above");
106        }
107        Err(error) => println!("the link failed: {error}"),
108    }
109}