json_eval_rs/utils/
mod.rs1use 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#[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
31thread_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#[inline]
39pub fn is_timing_enabled() -> bool {
40 TIMING_ENABLED.with(|enabled| *enabled.borrow())
41}
42
43pub fn enable_timing() {
45 TIMING_ENABLED.with(|enabled| {
46 *enabled.borrow_mut() = true;
47 });
48}
49
50pub fn disable_timing() {
52 TIMING_ENABLED.with(|enabled| {
53 *enabled.borrow_mut() = false;
54 });
55}
56
57#[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
67pub 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
94pub fn clear_timing_data() {
96 TIMING_DATA.with(|data| {
97 data.borrow_mut().clear();
98 });
99}
100
101#[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
118pub 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}