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 infrastructure
32thread_local! {
33    static TIMING_ENABLED: RefCell<bool> = RefCell::new(std::env::var("JSONEVAL_TIMING").is_ok());
34    static TIMING_DATA: RefCell<Vec<(String, std::time::Duration)>> = RefCell::new(Vec::new());
35}
36
37/// Check if timing is enabled
38#[inline]
39pub fn is_timing_enabled() -> bool {
40    TIMING_ENABLED.with(|enabled| *enabled.borrow())
41}
42
43/// Enable timing programmatically (in addition to JSONEVAL_TIMING environment variable)
44pub fn enable_timing() {
45    TIMING_ENABLED.with(|enabled| {
46        *enabled.borrow_mut() = true;
47    });
48}
49
50/// Disable timing
51pub fn disable_timing() {
52    TIMING_ENABLED.with(|enabled| {
53        *enabled.borrow_mut() = false;
54    });
55}
56
57/// Record timing data
58#[inline]
59pub fn record_timing(label: &str, duration: std::time::Duration) {
60    if is_timing_enabled() {
61        TIMING_DATA.with(|data| {
62            data.borrow_mut().push((label.to_string(), duration));
63        });
64    }
65}
66
67/// Print timing summary
68pub fn print_timing_summary() {
69    if !is_timing_enabled() {
70        return;
71    }
72
73    TIMING_DATA.with(|data| {
74        let timings = data.borrow();
75        if timings.is_empty() {
76            return;
77        }
78
79        eprintln!("\nšŸ“Š Timing Summary (JSONEVAL_TIMING enabled)");
80        eprintln!("{}", "=".repeat(60));
81
82        let mut total = std::time::Duration::ZERO;
83        for (label, duration) in timings.iter() {
84            eprintln!("{:40} {:>12?}", label, duration);
85            total += *duration;
86        }
87
88        eprintln!("{}", "=".repeat(60));
89        eprintln!("{:40} {:>12?}", "TOTAL", total);
90        eprintln!();
91    });
92}
93
94/// Clear timing data
95pub fn clear_timing_data() {
96    TIMING_DATA.with(|data| {
97        data.borrow_mut().clear();
98    });
99}
100
101/// Macro for timing a block of code
102#[macro_export]
103macro_rules! time_block {
104    ($label:expr, $block:block) => {{
105        let _start = if $crate::utils::is_timing_enabled() {
106            Some(std::time::Instant::now())
107        } else {
108            None
109        };
110        let result = $block;
111        if let Some(start) = _start {
112            $crate::utils::record_timing($label, start.elapsed());
113        }
114        result
115    }};
116}
117
118/// Clean floating point noise from JSON values
119/// Converts values very close to zero (< 1e-10) to exactly 0
120pub fn clean_float_noise(value: Value) -> Value {
121    const EPSILON: f64 = 1e-10;
122
123    match value {
124        Value::Number(n) => {
125            if let Some(f) = n.as_f64() {
126                if f.abs() < EPSILON {
127                    Value::Number(serde_json::Number::from(0))
128                } else if f.fract().abs() < EPSILON {
129                    Value::Number(serde_json::Number::from(f.round() as i64))
130                } else {
131                    Value::Number(n)
132                }
133            } else {
134                Value::Number(n)
135            }
136        }
137        Value::Array(arr) => Value::Array(arr.into_iter().map(clean_float_noise).collect()),
138        Value::Object(obj) => Value::Object(
139            obj.into_iter()
140                .map(|(k, v)| (k, clean_float_noise(v)))
141                .collect(),
142        ),
143        _ => value,
144    }
145}
146
147#[inline(always)]
148pub fn clean_float_noise_scalar(value: Value) -> Value {
149    const EPSILON: f64 = 1e-10;
150
151    match value {
152        Value::Number(ref n) => {
153            if let Some(f) = n.as_f64() {
154                if f.abs() < EPSILON {
155                    Value::Number(serde_json::Number::from(0))
156                } else if f.fract().abs() < EPSILON {
157                    Value::Number(serde_json::Number::from(f.round() as i64))
158                } else {
159                    value
160                }
161            } else {
162                value
163            }
164        }
165        Value::Array(_) | Value::Object(_) => clean_float_noise(value),
166        _ => value,
167    }
168}