Skip to main content

hax_rust_engine/printer/pretty_ast/
debug_json.rs

1use std::fmt::{Debug, Display};
2
3use crate::printer::pretty_ast::ToDocument;
4
5/// This type is primarily useful inside printer implementations when you want a
6/// low-friction way to inspect an AST fragment.
7///
8/// # What it does
9/// - Appends a JSON representation of the wrapped value to
10///   `"/tmp/hax-ast-debug.json"` (one JSON document per line).
11/// - Implements [`std::fmt::Display`] to print a `just` invocation you can paste in a shell
12///   to re-open that same JSON by line number:
13///   `just debug-json <line-id>`
14///
15/// # Example
16/// ```rust
17/// # use hax_rust_engine::printer::pretty_ast::DebugJSON;
18/// # #[derive(serde::Serialize)]
19/// # struct Small { x: u32 }
20/// let s = Small { x: 42 };
21/// // Prints something like: `just debug-json 17`.
22/// println!("{}", DebugJSON(&s));
23/// // Running `just debug-json 17` will print `{"x":42}`
24/// ```
25///
26/// # Notes
27/// - This is a **debugging convenience** and intentionally has a side-effect (file write).
28///   Avoid keeping it in user-facing output paths.
29/// - The file grows over time; occasionally delete it if you no longer need historical entries.
30pub struct DebugJSON<T: serde::Serialize>(pub T);
31
32impl<T: serde::Serialize> Display for DebugJSON<T> {
33    #[cfg(not(unix))]
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        write!(f, "<unknown, DebugJSON supported on unix plateforms only>")
36    }
37    #[cfg(unix)]
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        const PATH: &str = "/tmp/hax-ast-debug.json";
40        /// Write a new JSON as a line at the end of `PATH`
41        fn append_line_json(value: &serde_json::Value) -> std::io::Result<usize> {
42            use std::io::{BufRead, BufReader, Write};
43            cleanup();
44            let file = std::fs::OpenOptions::new()
45                .read(true)
46                .append(true)
47                .create(true)
48                .open(PATH)?;
49            let count = BufReader::new(&file).lines().count();
50            writeln!(&file, "{value}")?;
51            Ok(count)
52        }
53
54        /// Drop the file at `PATH` when we first write
55        fn cleanup() {
56            static DID_RUN: AtomicBool = AtomicBool::new(false);
57            use std::sync::atomic::{AtomicBool, Ordering};
58            if DID_RUN
59                .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
60                .is_ok()
61            {
62                let _ignored = std::fs::remove_file(PATH);
63            }
64        }
65
66        if let Ok(id) = append_line_json(&serde_json::to_value(&self.0).unwrap()) {
67            write!(f, "`just debug-json {id}`")
68        } else {
69            write!(f, "<DebugJSON failed>")
70        }
71    }
72}
73
74impl<A: 'static + Clone, P, T: serde::Serialize + Debug> ToDocument<P, A> for DebugJSON<T> {
75    fn to_document(&self, _: &P) -> super::DocBuilder<A> {
76        pretty::DocAllocator::as_string(
77            &pretty::BoxAllocator,
78            serde_json::to_string_pretty(&self.0).unwrap_or_else(|_| format!("{:#?}", &self.0)),
79        )
80    }
81}