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