1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::fmt;
4
5#[derive(Debug, Clone, Default)]
38pub enum Value {
39 #[default]
41 Uninitialized,
42 Number(f64),
44 String(String),
46 NumericString(String, f64),
49}
50
51impl Value {
52 #[inline]
54 pub fn from_string(s: String) -> Self {
55 if let Some(num) = parse_numeric_string(&s) {
56 Value::NumericString(s, num)
57 } else {
58 Value::String(s)
59 }
60 }
61
62 #[inline]
64 pub fn from_number(n: f64) -> Self {
65 Value::Number(n)
66 }
67
68 #[inline]
74 pub fn is_truthy(&self) -> bool {
75 match self {
76 Value::Uninitialized => false,
77 Value::Number(n) => *n != 0.0,
78 Value::String(s) => !s.is_empty(),
79 Value::NumericString(s, _) => !s.is_empty(),
80 }
81 }
82
83 #[inline]
85 pub fn to_number(&self) -> f64 {
86 match self {
87 Value::Uninitialized => 0.0,
88 Value::Number(n) => *n,
89 Value::String(s) => parse_leading_number(s),
90 Value::NumericString(_, n) => *n,
91 }
92 }
93
94 #[inline]
96 pub fn to_string_val(&self) -> String {
97 self.to_string_with_format("%.6g")
98 }
99
100 #[inline]
102 pub fn as_str(&self) -> Cow<'_, str> {
103 match self {
104 Value::Uninitialized => Cow::Borrowed(""),
105 Value::Number(n) => Cow::Owned(format_number(*n, "%.6g")),
106 Value::String(s) => Cow::Borrowed(s),
107 Value::NumericString(s, _) => Cow::Borrowed(s),
108 }
109 }
110
111 pub fn to_string_with_format(&self, format: &str) -> String {
113 match self {
114 Value::Uninitialized => String::new(),
115 Value::Number(n) => format_number(*n, format),
116 Value::String(s) => s.clone(),
117 Value::NumericString(s, _) => s.clone(),
118 }
119 }
120
121 #[inline]
123 pub fn is_numeric(&self) -> bool {
124 matches!(self, Value::Number(_))
125 }
126
127 #[inline]
129 pub fn is_numeric_string(&self) -> bool {
130 matches!(self, Value::NumericString(_, _))
131 }
132
133 #[inline]
135 pub fn compares_as_number(&self) -> bool {
136 matches!(
137 self,
138 Value::Number(_) | Value::NumericString(_, _) | Value::Uninitialized
139 )
140 }
141}
142
143impl fmt::Display for Value {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Value::Uninitialized => Ok(()),
147 Value::Number(n) => write!(f, "{}", format_number(*n, "%.6g")),
148 Value::String(s) => write!(f, "{}", s),
149 Value::NumericString(s, _) => write!(f, "{}", s),
150 }
151 }
152}
153
154#[inline]
156pub fn compare_values(left: &Value, right: &Value) -> Ordering {
157 compare_values_with_format(left, right, "%.6g")
158}
159
160#[inline]
163pub fn compare_values_with_format(left: &Value, right: &Value, convfmt: &str) -> Ordering {
164 if left.compares_as_number() && right.compares_as_number() {
166 let l = left.to_number();
167 let r = right.to_number();
168 l.partial_cmp(&r).unwrap_or(Ordering::Equal)
169 } else {
170 match (left, right) {
172 (Value::Number(_), _) | (_, Value::Number(_)) => left
174 .to_string_with_format(convfmt)
175 .cmp(&right.to_string_with_format(convfmt)),
176 _ => left.as_str().cmp(&right.as_str()),
178 }
179 }
180}
181
182#[inline]
187pub fn parse_leading_number(s: &str) -> f64 {
188 let bytes = s.as_bytes();
189 let mut i = 0;
190
191 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
193 i += 1;
194 }
195
196 if i >= bytes.len() {
197 return 0.0;
198 }
199
200 let start = i;
201
202 if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
204 i += 1;
205 }
206
207 let mut has_digits = false;
208
209 while i < bytes.len() && bytes[i].is_ascii_digit() {
211 i += 1;
212 has_digits = true;
213 }
214
215 if i < bytes.len() && bytes[i] == b'.' {
217 i += 1;
218 while i < bytes.len() && bytes[i].is_ascii_digit() {
219 i += 1;
220 has_digits = true;
221 }
222 }
223
224 if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
226 let exp_start = i;
227 i += 1;
228 if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
229 i += 1;
230 }
231 if i < bytes.len() && bytes[i].is_ascii_digit() {
232 while i < bytes.len() && bytes[i].is_ascii_digit() {
233 i += 1;
234 }
235 } else {
236 i = exp_start;
238 }
239 }
240
241 if !has_digits {
242 return 0.0;
243 }
244
245 let num_str = &s[start..i];
247 let is_integer_like =
248 !num_str.contains('.') && !num_str.contains('e') && !num_str.contains('E');
249 match num_str.parse::<i64>() {
250 Ok(n) if is_integer_like => return n as f64,
251 _ => {}
252 }
253
254 num_str.parse().unwrap_or(0.0)
255}
256
257#[inline]
259fn parse_numeric_string(s: &str) -> Option<f64> {
260 let trimmed = s.trim();
261 if trimmed.is_empty() {
262 return None;
263 }
264
265 if trimmed.bytes().all(|b| b.is_ascii_digit()) {
267 return trimmed.parse().ok();
268 }
269
270 let check = if trimmed.starts_with('-') || trimmed.starts_with('+') {
272 &trimmed[1..]
273 } else {
274 trimmed
275 };
276
277 let mut has_dot = false;
279 let mut has_e = false;
280 for (i, b) in check.bytes().enumerate() {
281 match b {
282 b'0'..=b'9' => continue,
283 b'.' if !has_dot && !has_e => has_dot = true,
284 b'e' | b'E' if !has_e && i > 0 => {
285 has_e = true;
286 if i + 1 < check.len() {
288 let next = check.as_bytes()[i + 1];
289 if next == b'+' || next == b'-' {
290 continue;
291 }
292 }
293 }
294 b'+' | b'-' if has_e => continue,
295 _ => return None,
296 }
297 }
298
299 trimmed.parse().ok()
300}
301
302pub fn format_number(n: f64, format: &str) -> String {
304 if n.is_nan() {
305 return "nan".to_string();
306 }
307 if n.is_infinite() {
308 return if n > 0.0 { "inf" } else { "-inf" }.to_string();
309 }
310
311 if n.fract() == 0.0 {
314 if n.abs() < 1e15 {
315 return itoa_fast(n as i64);
316 }
317 return format!("{:.0}", n);
318 }
319
320 crate::fmt::sprintf(format, &[Value::Number(n)])
321}
322
323#[inline]
325fn itoa_fast(n: i64) -> String {
326 if n == 0 {
327 return "0".to_string();
328 }
329
330 let mut result = String::with_capacity(20);
331 let mut num = n;
332 let negative = num < 0;
333 if negative {
334 num = -num;
335 }
336
337 while num > 0 {
338 result.push((b'0' + (num % 10) as u8) as char);
339 num /= 10;
340 }
341
342 if negative {
343 result.push('-');
344 }
345
346 result.chars().rev().collect()
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn test_uninitialized() {
355 let v = Value::Uninitialized;
356 assert_eq!(v.to_number(), 0.0);
357 assert_eq!(v.to_string_val(), "");
358 assert!(!v.is_truthy());
359 }
360
361 #[test]
362 fn test_number() {
363 let v = Value::Number(42.0);
364 assert_eq!(v.to_number(), 42.0);
365 assert_eq!(v.to_string_val(), "42");
366 assert!(v.is_truthy());
367
368 let zero = Value::Number(0.0);
369 assert!(!zero.is_truthy());
370 }
371
372 #[test]
373 fn test_string() {
374 let v = Value::from_string("hello".to_string());
375 assert_eq!(v.to_number(), 0.0);
376 assert_eq!(v.to_string_val(), "hello");
377 assert!(v.is_truthy());
378
379 let empty = Value::from_string("".to_string());
380 assert!(!empty.is_truthy());
381 }
382
383 #[test]
384 fn test_numeric_string() {
385 let v = Value::from_string("42".to_string());
386 assert!(v.is_numeric_string());
387 assert_eq!(v.to_number(), 42.0);
388 assert_eq!(v.to_string_val(), "42");
389 }
390
391 #[test]
392 fn test_leading_number() {
393 assert_eq!(parse_leading_number("42abc"), 42.0);
394 assert_eq!(parse_leading_number(" 2.75 "), 2.75);
395 assert_eq!(parse_leading_number("abc"), 0.0);
396 assert_eq!(parse_leading_number("-5.5"), -5.5);
397 assert_eq!(parse_leading_number("1e10"), 1e10);
398 }
399
400 #[test]
401 fn test_comparison() {
402 let n1 = Value::Number(10.0);
403 let n2 = Value::Number(2.0);
404 assert_eq!(compare_values(&n1, &n2), Ordering::Greater);
405
406 let s1 = Value::from_string("10".to_string());
407 let s2 = Value::from_string("2".to_string());
408 assert_eq!(compare_values(&s1, &s2), Ordering::Greater);
410
411 let s3 = Value::from_string("abc".to_string());
412 let s4 = Value::from_string("def".to_string());
413 assert_eq!(compare_values(&s3, &s4), Ordering::Less);
415 }
416
417 #[test]
418 fn test_itoa_fast() {
419 assert_eq!(itoa_fast(0), "0");
420 assert_eq!(itoa_fast(42), "42");
421 assert_eq!(itoa_fast(-123), "-123");
422 assert_eq!(itoa_fast(1000000), "1000000");
423 }
424
425 #[test]
426 fn test_format_number_nan() {
427 assert_eq!(format_number(f64::NAN, "%.6g"), "nan");
428 }
429
430 #[test]
431 fn test_format_number_inf() {
432 assert_eq!(format_number(f64::INFINITY, "%.6g"), "inf");
433 assert_eq!(format_number(f64::NEG_INFINITY, "%.6g"), "-inf");
434 }
435
436 #[test]
437 fn test_format_number_integer() {
438 assert_eq!(format_number(42.0, "%.6g"), "42");
439 assert_eq!(format_number(-100.0, "%.6g"), "-100");
440 }
441
442 #[test]
443 fn test_format_number_float() {
444 assert_eq!(format_number(2.75, "%.6g"), "2.75");
445 }
446
447 #[test]
448 fn test_from_number() {
449 let v = Value::from_number(2.75);
450 assert_eq!(v.to_number(), 2.75);
451 }
452
453 #[test]
454 fn test_is_truthy_numeric_string() {
455 let v = Value::NumericString("42".to_string(), 42.0);
456 assert!(v.is_truthy());
457
458 let empty = Value::NumericString("".to_string(), 0.0);
459 assert!(!empty.is_truthy());
460 }
461
462 #[test]
463 fn test_comparison_number_vs_string() {
464 let n = Value::Number(10.0);
465 let s = Value::from_string("hello".to_string());
466 assert!(compare_values(&n, &s) != Ordering::Equal);
468 }
469
470 #[test]
471 fn test_comparison_uninitialized() {
472 let u = Value::Uninitialized;
473 let n = Value::Number(1.0);
474 assert_eq!(compare_values(&u, &n), Ordering::Less);
476 }
477
478 #[test]
479 fn test_parse_leading_with_sign() {
480 assert_eq!(parse_leading_number("+42"), 42.0);
481 assert_eq!(parse_leading_number(" +2.75"), 2.75);
482 }
483
484 #[test]
485 fn test_parse_leading_exponent() {
486 assert_eq!(parse_leading_number("1e-5"), 1e-5);
487 assert_eq!(parse_leading_number("2E+3"), 2000.0);
488 }
489
490 #[test]
491 fn test_numeric_string_with_exponent() {
492 let v = Value::from_string("1e5".to_string());
493 assert!(v.is_numeric_string());
494 assert_eq!(v.to_number(), 1e5);
495 }
496
497 #[test]
498 fn test_numeric_string_with_sign() {
499 let v = Value::from_string("-42.5".to_string());
500 assert!(v.is_numeric_string());
501 assert_eq!(v.to_number(), -42.5);
502 }
503
504 #[test]
505 fn test_numeric_string_whitespace() {
506 let v = Value::from_string(" 123 ".to_string());
507 assert!(v.is_numeric_string());
508 assert_eq!(v.to_number(), 123.0);
509 }
510
511 #[test]
512 fn test_to_string_val_uninitialized() {
513 let v = Value::Uninitialized;
514 assert_eq!(v.to_string_val(), "");
515 }
516
517 #[test]
518 fn test_to_number_uninitialized() {
519 let v = Value::Uninitialized;
520 assert_eq!(v.to_number(), 0.0);
521 }
522}