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