Skip to main content

tree_table/types/
val_cmp.rs

1use crate::Val;
2use crate::utils::utils_fns::{natural_text_segments, natural_text_segments_cs};
3use alloc::format;
4use alloc::string::{String, ToString};
5use alloc::vec::Vec;
6use bigdecimal::ToPrimitive;
7use bigdecimal::num_bigint::{BigInt, Sign};
8use deep_time::{Dt, Lang};
9
10// ─── OrderedF64 (clean f64 ordering for BTree/Ord) ───
11#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
12pub struct OrderedF64(pub f64);
13
14impl Eq for OrderedF64 {}
15impl Ord for OrderedF64 {
16    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
17        self.0.total_cmp(&other.0)
18    }
19}
20impl From<f64> for OrderedF64 {
21    fn from(f: f64) -> Self {
22        OrderedF64(f)
23    }
24}
25impl From<i64> for OrderedF64 {
26    fn from(i: i64) -> Self {
27        OrderedF64(i as f64)
28    }
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
32pub enum NaturalKey {
33    Blank,                                // 0
34    Empty,                                // 1
35    Bool(bool),                           // 2
36    HugeNegative(bigdecimal::BigDecimal), // 3: all huge negatives (more negative first)
37    Number(OrderedF64),                   // 4: everything that fits in f64 (including Val::Dec)
38    HugePositive(bigdecimal::BigDecimal), // 5: all huge positives
39    Date(Dt),                             // 6
40    Text(Vec<Vec<NaturalSegment>>), // 7: natural / version / fast / date text (with number extraction)
41    Lexical(String), // 8: Pure Lexical / Strict String Key (classic “dumb” computer sort)
42    Unknown(String), // 9
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
46pub enum XKey {
47    Numeric(OrderedF64), // 0 (numbers, dates, durations, huge values → ±inf)
48    Text(String),        // 1 (lowercased strings)
49    Bool(bool),          // 2
50    Nil,                 // 99 (null/undefined — always last)
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
54pub enum NaturalSegment {
55    Text(String),
56    Number(OrderedF64),
57}
58
59impl Val {
60    pub fn x_key(&self) -> XKey {
61        match self {
62            Val::Un8(_)
63            | Val::Un16(_)
64            | Val::Un32(_)
65            | Val::Un64(_)
66            | Val::Un128(_)
67            | Val::Sn8(_)
68            | Val::Sn16(_)
69            | Val::Sn32(_)
70            | Val::Sn64(_)
71            | Val::Sn128(_)
72            | Val::Fl64(_)
73            | Val::Dec(_)
74            | Val::Bigdec(_)
75            | Val::Bigint(_)
76            | Val::Dt(_)
77            | Val::Dur(_) => XKey::Numeric(self.x_numeric_value().unwrap_or(0.0).into()),
78            // Group 1 – text (comes after all numerics)
79            Val::Str(_) | Val::Chr(_) => XKey::Text(self.x_text_value()),
80            // Group 2 – bool
81            Val::Bl(b) => XKey::Bool(*b),
82            // Group 99 – nil/undefined
83            Val::Nil(_) | Val::Undf(_) => XKey::Nil,
84        }
85    }
86
87    pub fn cmp_x(&self, other: &Val) -> core::cmp::Ordering {
88        self.x_key().cmp(&other.x_key())
89    }
90
91    fn x_text_value(&self) -> String {
92        match self {
93            Val::Str(s) => s.to_lowercase(),
94            Val::Chr(c) => c.to_lowercase().collect(),
95            _ => String::new(),
96        }
97    }
98
99    /// Numeric representation for the numeric group.
100    pub fn x_numeric_value(&self) -> Option<f64> {
101        match self {
102            Val::Un8(v) => Some(*v as f64),
103            Val::Un16(v) => Some(*v as f64),
104            Val::Un32(v) => Some(*v as f64),
105            Val::Un64(v) => Some(*v as f64),
106            Val::Un128(v) => Some(*v as f64),
107            Val::Sn8(v) => Some(*v as f64),
108            Val::Sn16(v) => Some(*v as f64),
109            Val::Sn32(v) => Some(*v as f64),
110            Val::Sn64(v) => Some(*v as f64),
111            Val::Sn128(v) => Some(*v as f64),
112            Val::Fl64(v) => Some(*v),
113            Val::Dec(v) => v.to_f64(),
114            // Huge values stay in the numeric group for cmp_x
115            Val::Bigdec(v) => v.to_f64().or_else(|| {
116                Some(if v < &bigdecimal::BigDecimal::from(0) {
117                    f64::NEG_INFINITY
118                } else {
119                    f64::INFINITY
120                })
121            }),
122            Val::Bigint(v) => v.to_f64().or_else(|| {
123                Some(if v.sign() == Sign::Minus {
124                    f64::NEG_INFINITY
125                } else {
126                    f64::INFINITY
127                })
128            }),
129            Val::Dt(dt) => Some(dt.to_f64()),
130            Val::Dur(d) => Some(d.to_f64()),
131            _ => None,
132        }
133    }
134
135    fn make_key<F>(&self, string_to_key: F) -> NaturalKey
136    where
137        F: Fn(&str) -> NaturalKey,
138    {
139        match self {
140            Val::Nil(_) | Val::Undf(_) => NaturalKey::Blank,
141            Val::Bl(b) => NaturalKey::Bool(*b),
142            Val::Dt(dt) => NaturalKey::Date(*dt),
143            Val::Dur(d) => NaturalKey::Number(d.to_f64().into()),
144            Val::Bigint(v) => bigint_key(v),
145            Val::Bigdec(v) => bigdec_key(v),
146            other => {
147                if let Some(n) = other.x_numeric_value() {
148                    NaturalKey::Number(n.into())
149                } else {
150                    match other {
151                        Val::Str(s) => {
152                            let trimmed = s.trim();
153                            if trimmed.is_empty() {
154                                NaturalKey::Empty
155                            } else {
156                                string_to_key(trimmed)
157                            }
158                        }
159                        Val::Chr(c) if c.is_whitespace() => NaturalKey::Empty,
160                        Val::Chr(c) => string_to_key(&c.to_string()),
161                        _ => NaturalKey::Unknown(format!("{:?}", other)),
162                    }
163                }
164            }
165        }
166    }
167
168    #[inline(always)]
169    pub fn natural_key(&self) -> NaturalKey {
170        self.make_key(Self::natural_key_from_string)
171    }
172
173    #[inline(always)]
174    pub fn date_key(&self) -> NaturalKey {
175        self.make_key(Self::date_key_from_string)
176    }
177
178    #[inline(always)]
179    pub fn version_key(&self) -> NaturalKey {
180        self.make_key(Self::version_key_from_string)
181    }
182
183    #[inline(always)]
184    pub fn fast_key(&self) -> NaturalKey {
185        self.make_key(Self::fast_key_from_string)
186    }
187
188    #[inline(always)]
189    pub fn natural_key_cs(&self) -> NaturalKey {
190        self.make_key(Self::natural_key_cs_from_string)
191    }
192
193    #[inline(always)]
194    pub fn lexical_key(&self) -> NaturalKey {
195        NaturalKey::Lexical(self.to_string().to_lowercase())
196    }
197
198    #[inline(always)]
199    pub fn lexical_cs_key(&self) -> NaturalKey {
200        NaturalKey::Lexical(self.to_string())
201    }
202
203    //
204
205    #[inline(always)]
206    pub fn natural_cmp(&self, other: &Val) -> core::cmp::Ordering {
207        self.natural_key().cmp(&other.natural_key())
208    }
209
210    #[inline(always)]
211    pub fn date_cmp(&self, other: &Val) -> core::cmp::Ordering {
212        self.date_key().cmp(&other.date_key())
213    }
214
215    #[inline(always)]
216    pub fn version_cmp(&self, other: &Val) -> core::cmp::Ordering {
217        self.version_key().cmp(&other.version_key())
218    }
219
220    #[inline(always)]
221    pub fn fast_cmp(&self, other: &Val) -> core::cmp::Ordering {
222        self.fast_key().cmp(&other.fast_key())
223    }
224
225    #[inline(always)]
226    pub fn natural_cs_cmp(&self, other: &Val) -> core::cmp::Ordering {
227        self.natural_key_cs().cmp(&other.natural_key_cs())
228    }
229
230    #[inline(always)]
231    pub fn lexical_cmp(&self, other: &Val) -> core::cmp::Ordering {
232        self.lexical_key().cmp(&other.lexical_key())
233    }
234
235    #[inline(always)]
236    pub fn lexical_cs_cmp(&self, other: &Val) -> core::cmp::Ordering {
237        self.lexical_cs_key().cmp(&other.lexical_cs_key())
238    }
239
240    fn natural_key_from_string(trimmed: &str) -> NaturalKey {
241        if let Ok(f) = trimmed.parse::<f64>() {
242            if f.is_finite() {
243                return NaturalKey::Number(f.into());
244            }
245        }
246        if let Ok(dur) = Dt::from_str_duration(trimmed, Lang::En) {
247            return NaturalKey::Number(dur.to_f64().into());
248        }
249        if let Ok(ts) = Dt::from_str_parse(trimmed, &None) {
250            return NaturalKey::Date(ts);
251        }
252        NaturalKey::Text(natural_components(trimmed))
253    }
254
255    fn natural_key_cs_from_string(trimmed: &str) -> NaturalKey {
256        if let Ok(f) = trimmed.parse::<f64>() {
257            if f.is_finite() {
258                return NaturalKey::Number(f.into());
259            }
260        }
261        if let Ok(ts) = Dt::from_str_parse(trimmed, &None) {
262            return NaturalKey::Date(ts);
263        }
264        if let Ok(dur) = Dt::from_str_duration(trimmed, Lang::En) {
265            return NaturalKey::Number(dur.to_f64().into());
266        }
267        NaturalKey::Text(cs_natural_components(trimmed))
268    }
269
270    fn date_key_from_string(trimmed: &str) -> NaturalKey {
271        if let Ok(ts) = Dt::from_str_parse(trimmed, &None) {
272            return NaturalKey::Date(ts);
273        }
274        if let Ok(f) = trimmed.parse::<f64>() {
275            if f.is_finite() {
276                return NaturalKey::Number(f.into());
277            }
278        }
279        if let Ok(dur) = Dt::from_str_duration(trimmed, Lang::En) {
280            return NaturalKey::Number(dur.to_f64().into());
281        }
282        NaturalKey::Text(natural_components(trimmed))
283    }
284
285    fn version_key_from_string(trimmed: &str) -> NaturalKey {
286        NaturalKey::Text(version_components(trimmed))
287    }
288
289    fn fast_key_from_string(trimmed: &str) -> NaturalKey {
290        if let Ok(f) = trimmed.parse::<f64>() {
291            if f.is_finite() {
292                return NaturalKey::Number(f.into());
293            }
294        }
295        NaturalKey::Text(natural_components(trimmed))
296    }
297}
298
299fn bigint_key(v: &BigInt) -> NaturalKey {
300    if let Some(f) = v.to_f64() {
301        if f.is_finite() {
302            return NaturalKey::Number(f.into());
303        }
304    }
305    if v.sign() == Sign::Minus {
306        NaturalKey::HugeNegative(bigdecimal::BigDecimal::from(v.clone()))
307    } else {
308        NaturalKey::HugePositive(bigdecimal::BigDecimal::from(v.clone()))
309    }
310}
311
312fn bigdec_key(bd: &bigdecimal::BigDecimal) -> NaturalKey {
313    if let Some(f) = bd.to_f64() {
314        if f.is_finite() {
315            return NaturalKey::Number(f.into());
316        }
317    }
318    if bd < &bigdecimal::BigDecimal::from(0) {
319        NaturalKey::HugeNegative(bd.clone())
320    } else {
321        NaturalKey::HugePositive(bd.clone())
322    }
323}
324
325#[inline(always)]
326fn split_components(s: &str, separators: &[char]) -> Vec<Vec<NaturalSegment>> {
327    s.split(|c| separators.contains(&c))
328        .map(|comp| comp.trim())
329        .filter(|comp| !comp.is_empty())
330        .map(natural_text_segments)
331        .collect()
332}
333
334#[inline(always)]
335fn cs_split_components(s: &str, separators: &[char]) -> Vec<Vec<NaturalSegment>> {
336    s.split(|c| separators.contains(&c))
337        .map(|comp| comp.trim())
338        .filter(|comp| !comp.is_empty())
339        .map(natural_text_segments_cs)
340        .collect()
341}
342
343// Used by natural_key + date_key (conservative path splitting)
344#[inline(always)]
345fn natural_components(s: &str) -> Vec<Vec<NaturalSegment>> {
346    split_components(s, &['/', '\\', '-'])
347}
348
349#[inline(always)]
350fn cs_natural_components(s: &str) -> Vec<Vec<NaturalSegment>> {
351    cs_split_components(s, &['/', '\\', '-'])
352}
353
354// Used by version_key (aggressive version splitting)
355#[inline(always)]
356fn version_components(s: &str) -> Vec<Vec<NaturalSegment>> {
357    split_components(s, &['/', '\\', '.', '_', '-'])
358}