1use crate::cell::*;
17use crate::machine::Machine;
18use plg_shared::atom::ATOM_DOT;
19use std::cmp::Ordering;
20
21fn type_rank(w: Word) -> u8 {
23 match tag_of(w) {
24 TAG_REF => 0,
25 TAG_INT | TAG_FLT | TAG_BIG => 1,
26 TAG_ATOM => 2,
27 TAG_STR | TAG_LST => 3,
28 _ => unreachable!("bad tag in term order"),
29 }
30}
31
32enum Num {
35 I(i64),
36 F(f64),
37}
38
39fn num_of(m: &Machine, w: Word) -> Num {
40 match tag_of(w) {
41 TAG_INT => Num::I(int_value(w)),
42 TAG_BIG => Num::I(m.heap[payload(w) as usize] as i64),
43 TAG_FLT => Num::F(f64::from_bits(m.heap[payload(w) as usize])),
44 _ => unreachable!(),
45 }
46}
47
48fn compare_numbers(m: &Machine, a: Word, b: Word) -> Ordering {
52 match (num_of(m, a), num_of(m, b)) {
53 (Num::I(ia), Num::I(ib)) => ia.cmp(&ib),
54 (Num::F(fa), Num::F(fb)) => {
55 fa.partial_cmp(&fb)
56 .unwrap_or_else(|| match (fa.is_nan(), fb.is_nan()) {
57 (true, true) => Ordering::Equal,
58 (true, false) => Ordering::Greater,
59 (false, true) => Ordering::Less,
60 (false, false) => unreachable!(),
61 })
62 }
63 (Num::I(ia), Num::F(fb)) => {
64 if fb.is_nan() {
65 return Ordering::Less; }
67 match (ia as f64).partial_cmp(&fb).unwrap_or(Ordering::Less) {
68 Ordering::Equal => Ordering::Greater, other => other,
70 }
71 }
72 (Num::F(fa), Num::I(ib)) => {
73 if fa.is_nan() {
74 return Ordering::Greater;
75 }
76 match fa.partial_cmp(&(ib as f64)).unwrap_or(Ordering::Greater) {
77 Ordering::Equal => Ordering::Less, other => other,
79 }
80 }
81 }
82}
83
84fn compound_view(m: &Machine, w: Word) -> (u32, u32, ArgsKind) {
87 match tag_of(w) {
88 TAG_STR => {
89 let idx = payload(w) as usize;
90 let (f, n) = unpack_functor(m.heap[idx]);
91 (f, n, ArgsKind::Str(idx))
92 }
93 TAG_LST => (ATOM_DOT, 2, ArgsKind::Lst(payload(w) as usize)),
94 _ => unreachable!(),
95 }
96}
97
98enum ArgsKind {
99 Str(usize), Lst(usize), }
102
103impl ArgsKind {
104 fn arg(&self, m: &Machine, i: usize) -> Word {
106 match self {
107 ArgsKind::Str(idx) => m.heap[idx + 1 + i],
108 ArgsKind::Lst(idx) => m.heap[idx + i],
109 }
110 }
111}
112
113pub fn compare_terms(m: &Machine, a: Word, b: Word) -> Ordering {
115 let mut work = vec![(a, b)];
119 while let Some((a, b)) = work.pop() {
120 let a = m.deref(a);
121 let b = m.deref(b);
122 if a == b {
123 continue; }
125 let ra = type_rank(a);
126 let rb = type_rank(b);
127 if ra != rb {
128 return ra.cmp(&rb);
129 }
130 let c = match ra {
131 0 => {
132 payload(a).cmp(&payload(b))
134 }
135 1 => compare_numbers(m, a, b),
136 2 => m.atoms.resolve(atom_id(a)).cmp(m.atoms.resolve(atom_id(b))),
137 3 => {
138 let (fa, na, ka) = compound_view(m, a);
139 let (fb, nb, kb) = compound_view(m, b);
140 let head = (na as usize)
141 .cmp(&(nb as usize))
142 .then_with(|| m.atoms.resolve(fa).cmp(m.atoms.resolve(fb)));
143 if head != Ordering::Equal {
144 return head;
145 }
146 for i in (0..na as usize).rev() {
149 work.push((ka.arg(m, i), kb.arg(m, i)));
150 }
151 Ordering::Equal
152 }
153 _ => unreachable!(),
154 };
155 if c != Ordering::Equal {
156 return c;
157 }
158 }
159 Ordering::Equal
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use plg_shared::StringInterner;
166
167 fn machine() -> Box<Machine> {
168 let mut atoms = StringInterner::new();
169 atoms.intern("a");
171 atoms.intern("b");
172 atoms.intern("f");
173 atoms.intern("g");
174 Machine::new(atoms, Vec::new())
175 }
176
177 fn atom(m: &Machine, name: &str) -> Word {
178 make_atom(m.atoms.lookup(name).unwrap())
179 }
180
181 fn flt(m: &mut Machine, f: f64) -> Word {
182 let idx = m.heap.len();
183 m.heap.push(f.to_bits());
184 make(TAG_FLT, idx as u64)
185 }
186
187 fn str_term(m: &mut Machine, name: &str, args: &[Word]) -> Word {
188 let f = m.atoms.intern(name);
189 let idx = m.heap.len();
190 m.heap.push(pack_functor(f, args.len() as u32));
191 m.heap.extend_from_slice(args);
192 make(TAG_STR, idx as u64)
193 }
194
195 fn list(m: &mut Machine, head: Word, tail: Word) -> Word {
196 let idx = m.heap.len();
197 m.heap.push(head);
198 m.heap.push(tail);
199 make(TAG_LST, idx as u64)
200 }
201
202 #[test]
203 fn rank_var_num_atom_compound() {
204 let mut m = machine();
205 let v = m.new_var();
206 let n = make_int(1);
207 let at = atom(&m, "a");
208 let c = str_term(&mut m, "f", &[make_int(1)]);
209 assert_eq!(compare_terms(&m, v, n), Ordering::Less);
210 assert_eq!(compare_terms(&m, n, at), Ordering::Less);
211 assert_eq!(compare_terms(&m, at, c), Ordering::Less);
212 assert_eq!(compare_terms(&m, c, v), Ordering::Greater);
213 }
214
215 #[test]
216 fn float_less_than_int_at_equality() {
217 let mut m = machine();
218 let f = flt(&mut m, 1.0);
219 let i = make_int(1);
220 assert_eq!(compare_terms(&m, f, i), Ordering::Less);
221 assert_eq!(compare_terms(&m, i, f), Ordering::Greater);
222 let f2 = flt(&mut m, 0.5);
224 assert_eq!(compare_terms(&m, f2, i), Ordering::Less);
225 }
226
227 #[test]
228 fn nan_sorts_after_floats() {
229 let mut m = machine();
230 let nan = flt(&mut m, f64::NAN);
231 let big = flt(&mut m, 1.0e9);
232 assert_eq!(compare_terms(&m, nan, big), Ordering::Greater);
233 assert_eq!(compare_terms(&m, big, nan), Ordering::Less);
234 }
235
236 #[test]
237 fn atoms_alphabetical() {
238 let m = machine();
239 let a = atom(&m, "a");
240 let b = atom(&m, "b");
241 assert_eq!(compare_terms(&m, a, b), Ordering::Less);
242 assert_eq!(compare_terms(&m, b, a), Ordering::Greater);
243 assert_eq!(compare_terms(&m, a, a), Ordering::Equal);
244 }
245
246 #[test]
247 fn compounds_arity_then_name_then_args() {
248 let mut m = machine();
249 let f1 = str_term(&mut m, "f", &[make_int(1)]);
251 let g2 = str_term(&mut m, "g", &[make_int(1), make_int(2)]);
252 assert_eq!(compare_terms(&m, f1, g2), Ordering::Less);
253 let g1 = str_term(&mut m, "g", &[make_int(1)]);
255 let f1b = str_term(&mut m, "f", &[make_int(1)]);
256 assert_eq!(compare_terms(&m, f1b, g1), Ordering::Less);
257 let fa = str_term(&mut m, "f", &[make_int(1)]);
259 let fb = str_term(&mut m, "f", &[make_int(2)]);
260 assert_eq!(compare_terms(&m, fa, fb), Ordering::Less);
261 }
262
263 #[test]
264 fn list_equals_dot_struct() {
265 let mut m = machine();
267 let nil = make_atom(plg_shared::atom::ATOM_NIL);
268 let lst = {
269 let inner = list(&mut m, make_int(2), nil);
270 list(&mut m, make_int(1), inner)
271 };
272 let dotstruct = {
273 let inner = str_term(&mut m, ".", &[make_int(2), nil]);
274 str_term(&mut m, ".", &[make_int(1), inner])
275 };
276 assert_eq!(compare_terms(&m, lst, dotstruct), Ordering::Equal);
277 assert_eq!(compare_terms(&m, dotstruct, lst), Ordering::Equal);
278 }
279
280 #[test]
281 fn list_vs_improper_dot() {
282 let mut m = machine();
284 let nil = make_atom(plg_shared::atom::ATOM_NIL);
285 let lst = {
286 let inner = list(&mut m, make_int(2), nil);
287 list(&mut m, make_int(1), inner)
288 };
289 let improper = str_term(&mut m, ".", &[make_int(1), make_int(2)]);
290 assert_eq!(compare_terms(&m, lst, improper), Ordering::Greater);
291 assert_eq!(compare_terms(&m, improper, lst), Ordering::Less);
292 }
293
294 #[test]
295 fn vars_ordered_by_index() {
296 let mut m = machine();
297 let v0 = m.new_var();
298 let v1 = m.new_var();
299 assert_eq!(compare_terms(&m, v0, v1), Ordering::Less);
300 assert_eq!(compare_terms(&m, v1, v0), Ordering::Greater);
301 }
302}