visi_core/core/engine/
result_data.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
17pub enum ResultData {
18 None,
20 Boolean(bool),
22 Integer(i64),
24 Float(f64),
27 String(String),
29 List(Vec<ResultData>),
32 Dict(Vec<(ResultData, ResultData)>),
34 Error(String),
36}
37
38impl std::fmt::Display for ResultData {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 ResultData::None => write!(f, ""),
42 ResultData::Boolean(b) => write!(f, "{}", if *b { "TRUE" } else { "FALSE" }),
43 ResultData::Integer(i) => write!(f, "{}", i),
44 ResultData::Float(fl) => write!(f, "{}", format_excel_number(*fl)),
45 ResultData::String(s) => write!(f, "{}", s),
46 ResultData::List(l) => {
47 let items: Vec<String> = l.iter().map(|i| i.to_string()).collect();
48 write!(f, "[{}]", items.join(", "))
49 }
50 ResultData::Dict(d) => {
51 let items: Vec<String> = d.iter().map(|(k, v)| format!("{}: {}", k, v)).collect();
52 write!(f, "{{ {} }}", items.join(", "))
53 }
54 ResultData::Error(e) => write!(f, "Error: {}", e),
55 }
56 }
57}
58
59fn round_digits_half_up(digits: &str, exp: i32, keep: usize) -> (String, i32) {
60 if digits.len() <= keep {
61 return (digits.to_string(), exp);
62 }
63 let mut kept: Vec<u8> = digits.as_bytes()[..keep].to_vec();
64 let round_up = digits.as_bytes()[keep] >= b'5';
65 let mut exp = exp;
66 if round_up {
67 let mut i = keep;
68 loop {
69 if i == 0 {
70 kept.insert(0, b'1');
71 kept.pop();
72 exp += 1;
73 break;
74 }
75 i -= 1;
76 if kept[i] == b'9' {
77 kept[i] = b'0';
78 } else {
79 kept[i] += 1;
80 break;
81 }
82 }
83 }
84 let mut out = String::from_utf8(kept).expect("ascii digits");
85 while out.len() > 1 && out.ends_with('0') {
86 out.pop();
87 }
88 (out, exp)
89}
90
91pub(crate) const EXCEL_ERROR_CODES: &[&str] = &[
92 "#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A", "#CALC!", "#SPILL!",
93];
94
95pub(crate) fn is_excel_error_code(src: &str) -> bool {
96 EXCEL_ERROR_CODES
97 .iter()
98 .any(|e| src.eq_ignore_ascii_case(e))
99}
100
101pub(crate) fn format_excel_number(f: f64) -> String {
102 if f == 0.0 {
103 return "0".to_string();
104 }
105 if f.is_nan() || f.is_infinite() {
106 return "#NUM!".to_string();
107 }
108
109 let sci = format!("{:.14e}", f);
110 let (mantissa, exp_str) = sci.split_once('e').expect("{:e} always emits an exponent");
111 let exp: i32 = exp_str.parse().expect("{:e} emits an integer exponent");
112 let sign = if f < 0.0 { "-" } else { "" };
113 let digits: String = mantissa.chars().filter(|c| c.is_ascii_digit()).collect();
114 let digits = digits.trim_end_matches('0');
115 let digits = if digits.is_empty() { "0" } else { digits };
116
117 let decimal_len = if exp >= 0 {
118 let int_digits = (exp + 1) as usize;
119 let frac_digits = digits.len().saturating_sub(int_digits);
120 int_digits + usize::from(frac_digits > 0) + frac_digits
121 } else {
122 2 + (-exp - 1) as usize + digits.len()
123 };
124
125 if decimal_len <= 20 {
126 if exp >= 0 {
127 let int_digits = (exp + 1) as usize;
128 let mut out = String::from(sign);
129 if digits.len() <= int_digits {
130 out.push_str(digits);
131 out.push_str(&"0".repeat(int_digits - digits.len()));
132 } else {
133 out.push_str(&digits[..int_digits]);
134 out.push('.');
135 out.push_str(&digits[int_digits..]);
136 }
137 out
138 } else {
139 format!("{}0.{}{}", sign, "0".repeat((-exp - 1) as usize), digits)
140 }
141 } else {
142 let suffix_budget_len = if exp.abs() >= 99 {
143 5
144 } else {
145 format!("E{:+03}", exp).len()
146 };
147 let frac_digits = 18usize.saturating_sub(suffix_budget_len).min(14);
148
149 let (rounded_digits, exp) = round_digits_half_up(digits, exp, frac_digits + 1);
150 let mut mantissa = String::from(sign);
151 mantissa.push_str(&rounded_digits[..1]);
152 if rounded_digits.len() > 1 {
153 mantissa.push('.');
154 mantissa.push_str(&rounded_digits[1..]);
155 }
156 format!("{}E{:+03}", mantissa, exp)
157 }
158}