erlang_term/term/
ord.rs

1use crate::raw_term::{RawTermGeneralType, RawTermType};
2use crate::RawTerm;
3use crate::Term;
4
5use std::cmp::{Ord, Ordering};
6
7use num_bigint::BigInt;
8
9impl Eq for Term {}
10
11impl Ord for Term {
12    fn cmp(&self, other: &Self) -> Ordering {
13        self.partial_cmp(other).expect("total order not found")
14    }
15}
16
17impl PartialOrd for Term {
18    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
19        if self == other {
20            return Some(Ordering::Equal);
21        }
22
23        let left_type = self.as_general_type();
24        let right_type = other.as_general_type();
25
26        if left_type != right_type {
27            return left_type.partial_cmp(&right_type);
28        }
29
30        RawTerm::from(self.clone()).partial_cmp(&RawTerm::from(other.clone()))
31    }
32}
33
34impl RawTermType {
35    ///
36    /// number < atom < reference < fun < port < pid < tuple < map < nil < list < bit string
37    ///
38    pub fn type_cmp(&self, other: &Self) -> Option<Ordering> {
39        if self == other {
40            return Some(Ordering::Equal);
41        }
42
43        let left_type = RawTermGeneralType::from(self);
44        let right_type = RawTermGeneralType::from(other);
45
46        left_type.partial_cmp(&right_type)
47    }
48}
49
50impl Eq for RawTerm {}
51
52impl Ord for RawTerm {
53    fn cmp(&self, other: &Self) -> Ordering {
54        self.partial_cmp(other).expect("total order not found")
55    }
56}
57
58impl PartialOrd for RawTerm {
59    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
60        if self == other {
61            return Some(Ordering::Equal);
62        }
63
64        let left_type = self.as_general_type();
65        let right_type = other.as_general_type();
66
67        if left_type != right_type {
68            return left_type.partial_cmp(&right_type);
69        }
70
71        match left_type {
72            RawTermGeneralType::Atom => {
73                atom_cmp(self, other).or_else(|| atom_cmp(other, self).map(Ordering::reverse))
74            }
75            RawTermGeneralType::Number => {
76                number_cmp(self, other).or_else(|| number_cmp(other, self).map(Ordering::reverse))
77            }
78            RawTermGeneralType::Reference => ref_cmp(self, other),
79            RawTermGeneralType::Pid => pid_cmp(self, other),
80            RawTermGeneralType::Port => port_cmp(self, other),
81            RawTermGeneralType::BitString => bitstring_cmp(self, other),
82            RawTermGeneralType::List => list_cmp(self, other),
83            RawTermGeneralType::Tuple => tuple_cmp(self, other),
84            RawTermGeneralType::Map => map_cmp(self, other),
85            RawTermGeneralType::Fun => func_cmp(self, other),
86            RawTermGeneralType::Nil => Some(Ordering::Equal),
87            _ => panic!(),
88        }
89    }
90}
91
92fn atom_cmp(left: &RawTerm, right: &RawTerm) -> Option<Ordering> {
93    use RawTerm::*;
94
95    match (left, right) {
96        (Atom(a), Atom(b)) => a.partial_cmp(b),
97        (Atom(a), SmallAtom(b)) => a.partial_cmp(b),
98        (Atom(a), AtomDeprecated(b)) => a.partial_cmp(b),
99        (Atom(a), SmallAtomDeprecated(b)) => a.partial_cmp(b),
100        (SmallAtom(a), SmallAtom(b)) => a.partial_cmp(b),
101        (SmallAtom(a), AtomDeprecated(b)) => a.partial_cmp(b),
102        (SmallAtom(a), SmallAtomDeprecated(b)) => a.partial_cmp(b),
103        (AtomDeprecated(a), AtomDeprecated(b)) => a.partial_cmp(b),
104        (AtomDeprecated(a), SmallAtomDeprecated(b)) => a.partial_cmp(b),
105        (SmallAtomDeprecated(a), SmallAtomDeprecated(b)) => a.partial_cmp(b),
106        _ => None,
107    }
108}
109
110fn number_cmp(left: &RawTerm, right: &RawTerm) -> Option<Ordering> {
111    use std::convert::TryInto;
112    use RawTerm::*;
113
114    match (left, right) {
115        (SmallInt(a), SmallInt(b)) => a.partial_cmp(b),
116
117        (SmallInt(_a), Int(b)) if b > &(u8::MAX as i32) => Some(Ordering::Less),
118        (SmallInt(_a), Int(b)) if b < &(u8::MIN as i32) => Some(Ordering::Greater),
119        (SmallInt(a), Int(b)) => a.partial_cmp(&(*b as u8)),
120
121        (SmallInt(_a), SmallBigInt(b)) if b > &BigInt::from(u8::MAX) => Some(Ordering::Less),
122        (SmallInt(_a), SmallBigInt(b)) if b < &BigInt::from(u8::MIN) => Some(Ordering::Greater),
123        (SmallInt(a), SmallBigInt(b)) => a.partial_cmp(&(b.clone().try_into().unwrap())),
124
125        (SmallInt(_a), LargeBigInt(b)) if b > &BigInt::from(u8::MAX) => Some(Ordering::Less),
126        (SmallInt(_a), LargeBigInt(b)) if b < &BigInt::from(u8::MIN) => Some(Ordering::Greater),
127        (SmallInt(a), LargeBigInt(b)) => a.partial_cmp(&(b.clone().try_into().unwrap())),
128
129        (Int(a), Int(b)) => a.partial_cmp(b),
130
131        (Int(_a), SmallBigInt(b)) if b > &BigInt::from(i32::MAX) => Some(Ordering::Less),
132        (Int(_a), SmallBigInt(b)) if b < &BigInt::from(i32::MIN) => Some(Ordering::Greater),
133        (Int(a), SmallBigInt(b)) => a.partial_cmp(&(b.clone().try_into().unwrap())),
134
135        (Int(_a), LargeBigInt(b)) if b > &BigInt::from(i32::MAX) => Some(Ordering::Less),
136        (Int(_a), LargeBigInt(b)) if b < &BigInt::from(i32::MIN) => Some(Ordering::Greater),
137        (Int(a), LargeBigInt(b)) => a.partial_cmp(&(b.clone().try_into().unwrap())),
138
139        (SmallBigInt(a), SmallBigInt(b)) => a.partial_cmp(b),
140        (SmallBigInt(a), LargeBigInt(b)) => a.partial_cmp(b),
141        (LargeBigInt(a), LargeBigInt(b)) => a.partial_cmp(b),
142
143        (Float(a), Float(b)) => a.partial_cmp(b),
144        _ => None,
145    }
146}
147
148fn tuple_cmp(left: &RawTerm, right: &RawTerm) -> Option<Ordering> {
149    use RawTerm::*;
150
151    match (left, right) {
152        (SmallTuple(a), SmallTuple(b)) => a.partial_cmp(b),
153        (LargeTuple(a), SmallTuple(b)) => a.partial_cmp(b),
154        (SmallTuple(a), LargeTuple(b)) => a.partial_cmp(b),
155        (LargeTuple(a), LargeTuple(b)) => a.partial_cmp(b),
156        _ => None,
157    }
158}
159
160fn ref_cmp(left: &RawTerm, right: &RawTerm) -> Option<Ordering> {
161    use RawTerm::*;
162
163    match (left, right) {
164        (
165            Ref {
166                creation: creation_a,
167                id: id_a,
168                ..
169            },
170            Ref {
171                creation: creation_b,
172                id: id_b,
173                ..
174            },
175        ) => (creation_a, id_a).partial_cmp(&(creation_b, id_b)),
176        (
177            NewerRef {
178                creation: creation_a,
179                id: id_a,
180                ..
181            },
182            NewerRef {
183                creation: creation_b,
184                id: id_b,
185                ..
186            },
187        ) => (creation_a, id_a).partial_cmp(&(creation_b, id_b)),
188        (
189            NewerRef {
190                creation: creation_a,
191                id: id_a,
192                ..
193            },
194            Ref {
195                creation: creation_b,
196                id: id_b,
197                ..
198            },
199        ) => (creation_a, id_a).partial_cmp(&(&(*creation_b as u32), id_b)),
200        (
201            Ref {
202                creation: creation_a,
203                id: id_a,
204                ..
205            },
206            NewerRef {
207                creation: creation_b,
208                id: id_b,
209                ..
210            },
211        ) => (&(*creation_a as u32), id_a).partial_cmp(&(creation_b, id_b)),
212        _ => None,
213    }
214}
215
216fn pid_cmp(left: &RawTerm, right: &RawTerm) -> Option<Ordering> {
217    use RawTerm::*;
218
219    match (left, right) {
220        (
221            NewPid {
222                id: id_a,
223                serial: serial_a,
224                creation: creation_a,
225                ..
226            },
227            NewPid {
228                id: id_b,
229                serial: serial_b,
230                creation: creation_b,
231                ..
232            },
233        ) => (creation_a, id_a, serial_a).partial_cmp(&(creation_b, id_b, serial_b)),
234        (
235            Pid {
236                id: id_a,
237                serial: serial_a,
238                creation: creation_a,
239                ..
240            },
241            Pid {
242                id: id_b,
243                serial: serial_b,
244                creation: creation_b,
245                ..
246            },
247        ) => (creation_a, id_a, serial_a).partial_cmp(&(creation_b, id_b, serial_b)),
248        (
249            NewPid {
250                id: id_a,
251                serial: serial_a,
252                creation: creation_a,
253                ..
254            },
255            Pid {
256                id: id_b,
257                serial: serial_b,
258                creation: creation_b,
259                ..
260            },
261        ) => (creation_a, id_a, serial_a).partial_cmp(&(&(*creation_b as u32), id_b, serial_b)),
262        (
263            Pid {
264                id: id_a,
265                serial: serial_a,
266                creation: creation_a,
267                ..
268            },
269            NewPid {
270                id: id_b,
271                serial: serial_b,
272                creation: creation_b,
273                ..
274            },
275        ) => (&(*creation_a as u32), id_a, serial_a).partial_cmp(&(creation_b, id_b, serial_b)),
276        _ => None,
277    }
278}
279
280fn port_cmp(left: &RawTerm, right: &RawTerm) -> Option<Ordering> {
281    use RawTerm::*;
282
283    match (left, right) {
284        (
285            NewPort {
286                id: id_a,
287                creation: creation_a,
288                ..
289            },
290            NewPort {
291                id: id_b,
292                creation: creation_b,
293                ..
294            },
295        ) => (creation_a, id_a).partial_cmp(&(creation_b, id_b)),
296        (
297            Port {
298                id: id_a,
299                creation: creation_a,
300                ..
301            },
302            Port {
303                id: id_b,
304                creation: creation_b,
305                ..
306            },
307        ) => (creation_a, id_a).partial_cmp(&(creation_b, id_b)),
308        (
309            NewPort {
310                id: id_a,
311                creation: creation_a,
312                ..
313            },
314            Port {
315                id: id_b,
316                creation: creation_b,
317                ..
318            },
319        ) => (creation_a, id_a).partial_cmp(&(&(*creation_b as u32), id_b)),
320        (
321            Port {
322                id: id_a,
323                creation: creation_a,
324                ..
325            },
326            NewPort {
327                id: id_b,
328                creation: creation_b,
329                ..
330            },
331        ) => (&(*creation_a as u32), id_a).partial_cmp(&(creation_b, id_b)),
332        _ => None,
333    }
334}
335
336fn bitstring_cmp(left: &RawTerm, right: &RawTerm) -> Option<Ordering> {
337    use RawTerm::*;
338
339    match (left, right) {
340        (String(a), String(b)) => a.partial_cmp(b),
341        (Binary(a), String(b)) => a.partial_cmp(b),
342        (Binary(a), Binary(b)) => a.partial_cmp(b),
343        (String(a), Binary(b)) => a.partial_cmp(b),
344        _ => None,
345    }
346}
347
348fn list_cmp(left: &RawTerm, right: &RawTerm) -> Option<Ordering> {
349    use RawTerm::*;
350
351    match (left, right) {
352        (List(a), List(b)) => a.partial_cmp(b),
353        _ => None,
354    }
355}
356
357fn map_cmp(left: &RawTerm, right: &RawTerm) -> Option<Ordering> {
358    use RawTerm::*;
359
360    match (left, right) {
361        (Map(a), Map(b)) => a.partial_cmp(b),
362        _ => None,
363    }
364}
365
366fn func_cmp(left: &RawTerm, right: &RawTerm) -> Option<Ordering> {
367    // note that this is not the way erlang does it, but it is something
368    // If you know how erlang compares functions let me know.
369    match (left, right) {
370        (
371            RawTerm::Function {
372                size: a1,
373                arity: b1,
374                uniq: c1,
375                index: d1,
376                module: e1,
377                old_index: f1,
378                old_uniq: g1,
379                pid: h1,
380                free_var: i1,
381            },
382            RawTerm::Function {
383                size: a2,
384                arity: b2,
385                uniq: c2,
386                index: d2,
387                module: e2,
388                old_index: f2,
389                old_uniq: g2,
390                pid: h2,
391                free_var: i2,
392            },
393        ) => {
394            (a1, b1, c1, d1, e1, f1, g1, h1, i1).partial_cmp(&(a2, b2, c2, d2, e2, f2, g2, h2, i2))
395        }
396
397        _ => None,
398    }
399}
400
401#[test]
402fn compare_pids() {
403    let a = RawTerm::NewPid {
404        creation: 1,
405        id: 2,
406        serial: 3,
407        node: Box::new(RawTerm::AtomDeprecated("test".to_string())),
408    };
409    let b = RawTerm::Pid {
410        creation: 1,
411        id: 2,
412        serial: 4,
413        node: Box::new(RawTerm::AtomDeprecated("test".to_string())),
414    };
415    let c = RawTerm::NewPid {
416        creation: 1,
417        id: 3,
418        serial: 3,
419        node: Box::new(RawTerm::AtomDeprecated("test".to_string())),
420    };
421    let d = RawTerm::Pid {
422        creation: 4,
423        id: 2,
424        serial: 4,
425        node: Box::new(RawTerm::AtomDeprecated("test".to_string())),
426    };
427
428    assert_eq!(Some(Ordering::Less), a.partial_cmp(&b));
429    assert_eq!(Some(Ordering::Greater), c.partial_cmp(&a));
430    assert_eq!(Some(Ordering::Greater), d.partial_cmp(&b));
431}
432
433#[test]
434fn ordering_test() {
435    let mut map = std::collections::BTreeMap::new();
436
437    map.insert(
438        RawTerm::Int(2),
439        RawTerm::List(vec![RawTerm::Atom(String::from("test"))]),
440    );
441    map.insert(
442        RawTerm::Int(4),
443        RawTerm::List(vec![RawTerm::Atom(String::from("testing"))]),
444    );
445    map.insert(
446        RawTerm::Binary(vec![123, 50, 90]),
447        RawTerm::List(vec![RawTerm::Atom(String::from("ordering"))]),
448    );
449    map.insert(
450        RawTerm::NewPort {
451            creation: 123,
452            id: 1,
453            node: Box::new(RawTerm::AtomDeprecated(String::from("localhost"))),
454        },
455        RawTerm::List(vec![RawTerm::Atom(String::from("ordering"))]),
456    );
457
458    assert_eq!(
459        &RawTerm::List(vec![RawTerm::Atom(String::from("ordering"))]),
460        map.get(&RawTerm::Binary(vec![123, 50, 90])).unwrap()
461    )
462}