Skip to main content

json_eval_rs/utils/
mod.rs

1use serde_json::Value;
2use std::cell::RefCell;
3
4#[cfg(feature = "wasm")]
5use wasm_bindgen::prelude::*;
6
7#[cfg(feature = "wasm")]
8#[wasm_bindgen]
9extern "C" {
10    #[wasm_bindgen(js_namespace = console)]
11    pub fn log(s: &str);
12}
13
14/// Cross-platform debug logging macro.
15/// Prints natively to console.log in WASM environments, and falls back to println! elsewhere.
16#[macro_export]
17macro_rules! debug_log {
18    ($($t:tt)*) => {
19        let msg = format!($($t)*);
20        #[cfg(feature = "wasm")]
21        {
22            $crate::utils::log(&format!("[WASM DEBUG] {}", msg));
23        }
24        #[cfg(not(feature = "wasm"))]
25        {
26            println!("[WASM DEBUG] {}", msg);
27        }
28    }
29}
30
31// Timing and Debug infrastructure
32thread_local! {
33    static TIMING_ENABLED: RefCell<bool> = RefCell::new(std::env::var("JSONEVAL_TIMING").is_ok());
34    static DEBUG_CACHE_ENABLED: RefCell<bool> = RefCell::new(std::env::var("JSONEVAL_DEBUG_CACHE").is_ok());
35    static TIMING_DATA: RefCell<Vec<(String, std::time::Duration)>> = RefCell::new(Vec::new());
36}
37
38/// Check if timing is enabled
39#[inline]
40pub fn is_timing_enabled() -> bool {
41    TIMING_ENABLED.with(|enabled| *enabled.borrow())
42}
43
44/// Check if cache debugging is enabled
45#[inline]
46pub fn is_debug_cache_enabled() -> bool {
47    DEBUG_CACHE_ENABLED.with(|enabled| *enabled.borrow())
48}
49
50/// Enable timing programmatically (in addition to JSONEVAL_TIMING environment variable)
51pub fn enable_timing() {
52    TIMING_ENABLED.with(|enabled| {
53        *enabled.borrow_mut() = true;
54    });
55}
56
57/// Disable timing
58pub fn disable_timing() {
59    TIMING_ENABLED.with(|enabled| {
60        *enabled.borrow_mut() = false;
61    });
62}
63
64/// Record timing data
65#[inline]
66pub fn record_timing(label: &str, duration: std::time::Duration) {
67    if is_timing_enabled() {
68        TIMING_DATA.with(|data| {
69            data.borrow_mut().push((label.to_string(), duration));
70        });
71    }
72}
73
74/// Print timing summary
75pub fn print_timing_summary() {
76    if !is_timing_enabled() {
77        return;
78    }
79
80    TIMING_DATA.with(|data| {
81        let timings = data.borrow();
82        if timings.is_empty() {
83            return;
84        }
85
86        eprintln!("\nšŸ“Š Timing Summary (JSONEVAL_TIMING enabled)");
87        eprintln!("{}", "=".repeat(60));
88
89        let mut total = std::time::Duration::ZERO;
90        for (label, duration) in timings.iter() {
91            eprintln!("{:40} {:>12?}", label, duration);
92            total += *duration;
93        }
94
95        eprintln!("{}", "=".repeat(60));
96        eprintln!("{:40} {:>12?}", "TOTAL", total);
97        eprintln!();
98    });
99}
100
101/// Clear timing data
102pub fn clear_timing_data() {
103    TIMING_DATA.with(|data| {
104        data.borrow_mut().clear();
105    });
106}
107
108/// Macro for timing a block of code
109#[macro_export]
110macro_rules! time_block {
111    ($label:expr, $block:block) => {{
112        let _start = if $crate::utils::is_timing_enabled() {
113            Some(std::time::Instant::now())
114        } else {
115            None
116        };
117        let result = $block;
118        if let Some(start) = _start {
119            $crate::utils::record_timing($label, start.elapsed());
120        }
121        result
122    }};
123}
124
125/// Clean floating point noise from JSON values
126/// Converts values very close to zero (< 1e-10) to exactly 0
127pub fn clean_float_noise(value: Value) -> Value {
128    const EPSILON: f64 = 1e-10;
129
130    match value {
131        Value::Number(n) => {
132            if let Some(f) = n.as_f64() {
133                if f.abs() < EPSILON {
134                    Value::Number(serde_json::Number::from(0))
135                } else if f.fract().abs() < EPSILON {
136                    Value::Number(serde_json::Number::from(f.round() as i64))
137                } else {
138                    Value::Number(n)
139                }
140            } else {
141                Value::Number(n)
142            }
143        }
144        Value::Array(arr) => Value::Array(arr.into_iter().map(clean_float_noise).collect()),
145        Value::Object(obj) => Value::Object(
146            obj.into_iter()
147                .map(|(k, v)| (k, clean_float_noise(v)))
148                .collect(),
149        ),
150        _ => value,
151    }
152}
153
154#[inline(always)]
155pub fn clean_float_noise_scalar(value: Value) -> Value {
156    const EPSILON: f64 = 1e-10;
157
158    match value {
159        Value::Number(ref n) => {
160            if let Some(f) = n.as_f64() {
161                if f.abs() < EPSILON {
162                    Value::Number(serde_json::Number::from(0))
163                } else if f.fract().abs() < EPSILON {
164                    Value::Number(serde_json::Number::from(f.round() as i64))
165                } else {
166                    value
167                }
168            } else {
169                value
170            }
171        }
172        Value::Array(_) | Value::Object(_) => clean_float_noise(value),
173        _ => value,
174    }
175}