use crate::cell::*;
use crate::machine::Machine;
use plg_shared::atom::ATOM_DOT;
use std::cmp::Ordering;
fn type_rank(w: Word) -> u8 {
match tag_of(w) {
TAG_REF => 0,
TAG_INT | TAG_FLT | TAG_BIG => 1,
TAG_ATOM => 2,
TAG_STR | TAG_LST => 3,
_ => unreachable!("bad tag in term order"),
}
}
enum Num {
I(i64),
F(f64),
}
fn num_of(m: &Machine, w: Word) -> Num {
match tag_of(w) {
TAG_INT => Num::I(int_value(w)),
TAG_BIG => Num::I(m.heap[payload(w) as usize] as i64),
TAG_FLT => Num::F(f64::from_bits(m.heap[payload(w) as usize])),
_ => unreachable!(),
}
}
fn compare_numbers(m: &Machine, a: Word, b: Word) -> Ordering {
match (num_of(m, a), num_of(m, b)) {
(Num::I(ia), Num::I(ib)) => ia.cmp(&ib),
(Num::F(fa), Num::F(fb)) => {
fa.partial_cmp(&fb)
.unwrap_or_else(|| match (fa.is_nan(), fb.is_nan()) {
(true, true) => Ordering::Equal,
(true, false) => Ordering::Greater,
(false, true) => Ordering::Less,
(false, false) => unreachable!(),
})
}
(Num::I(ia), Num::F(fb)) => {
if fb.is_nan() {
return Ordering::Less; }
match (ia as f64).partial_cmp(&fb).unwrap_or(Ordering::Less) {
Ordering::Equal => Ordering::Greater, other => other,
}
}
(Num::F(fa), Num::I(ib)) => {
if fa.is_nan() {
return Ordering::Greater;
}
match fa.partial_cmp(&(ib as f64)).unwrap_or(Ordering::Greater) {
Ordering::Equal => Ordering::Less, other => other,
}
}
}
}
fn compound_view(m: &Machine, w: Word) -> (u32, u32, ArgsKind) {
match tag_of(w) {
TAG_STR => {
let idx = payload(w) as usize;
let (f, n) = unpack_functor(m.heap[idx]);
(f, n, ArgsKind::Str(idx))
}
TAG_LST => (ATOM_DOT, 2, ArgsKind::Lst(payload(w) as usize)),
_ => unreachable!(),
}
}
enum ArgsKind {
Str(usize), Lst(usize), }
impl ArgsKind {
fn arg(&self, m: &Machine, i: usize) -> Word {
match self {
ArgsKind::Str(idx) => m.heap[idx + 1 + i],
ArgsKind::Lst(idx) => m.heap[idx + i],
}
}
}
pub fn compare_terms(m: &Machine, a: Word, b: Word) -> Ordering {
let mut work = vec![(a, b)];
while let Some((a, b)) = work.pop() {
let a = m.deref(a);
let b = m.deref(b);
if a == b {
continue; }
let ra = type_rank(a);
let rb = type_rank(b);
if ra != rb {
return ra.cmp(&rb);
}
let c = match ra {
0 => {
payload(a).cmp(&payload(b))
}
1 => compare_numbers(m, a, b),
2 => m.atoms.resolve(atom_id(a)).cmp(m.atoms.resolve(atom_id(b))),
3 => {
let (fa, na, ka) = compound_view(m, a);
let (fb, nb, kb) = compound_view(m, b);
let head = (na as usize)
.cmp(&(nb as usize))
.then_with(|| m.atoms.resolve(fa).cmp(m.atoms.resolve(fb)));
if head != Ordering::Equal {
return head;
}
for i in (0..na as usize).rev() {
work.push((ka.arg(m, i), kb.arg(m, i)));
}
Ordering::Equal
}
_ => unreachable!(),
};
if c != Ordering::Equal {
return c;
}
}
Ordering::Equal
}
#[cfg(test)]
mod tests {
use super::*;
use plg_shared::StringInterner;
fn machine() -> Box<Machine> {
let mut atoms = StringInterner::new();
atoms.intern("a");
atoms.intern("b");
atoms.intern("f");
atoms.intern("g");
Machine::new(atoms, Vec::new())
}
fn atom(m: &Machine, name: &str) -> Word {
make_atom(m.atoms.lookup(name).unwrap())
}
fn flt(m: &mut Machine, f: f64) -> Word {
let idx = m.heap.len();
m.heap.push(f.to_bits());
make(TAG_FLT, idx as u64)
}
fn str_term(m: &mut Machine, name: &str, args: &[Word]) -> Word {
let f = m.atoms.intern(name);
let idx = m.heap.len();
m.heap.push(pack_functor(f, args.len() as u32));
m.heap.extend_from_slice(args);
make(TAG_STR, idx as u64)
}
fn list(m: &mut Machine, head: Word, tail: Word) -> Word {
let idx = m.heap.len();
m.heap.push(head);
m.heap.push(tail);
make(TAG_LST, idx as u64)
}
#[test]
fn rank_var_num_atom_compound() {
let mut m = machine();
let v = m.new_var();
let n = make_int(1);
let at = atom(&m, "a");
let c = str_term(&mut m, "f", &[make_int(1)]);
assert_eq!(compare_terms(&m, v, n), Ordering::Less);
assert_eq!(compare_terms(&m, n, at), Ordering::Less);
assert_eq!(compare_terms(&m, at, c), Ordering::Less);
assert_eq!(compare_terms(&m, c, v), Ordering::Greater);
}
#[test]
fn float_less_than_int_at_equality() {
let mut m = machine();
let f = flt(&mut m, 1.0);
let i = make_int(1);
assert_eq!(compare_terms(&m, f, i), Ordering::Less);
assert_eq!(compare_terms(&m, i, f), Ordering::Greater);
let f2 = flt(&mut m, 0.5);
assert_eq!(compare_terms(&m, f2, i), Ordering::Less);
}
#[test]
fn nan_sorts_after_floats() {
let mut m = machine();
let nan = flt(&mut m, f64::NAN);
let big = flt(&mut m, 1.0e9);
assert_eq!(compare_terms(&m, nan, big), Ordering::Greater);
assert_eq!(compare_terms(&m, big, nan), Ordering::Less);
}
#[test]
fn atoms_alphabetical() {
let m = machine();
let a = atom(&m, "a");
let b = atom(&m, "b");
assert_eq!(compare_terms(&m, a, b), Ordering::Less);
assert_eq!(compare_terms(&m, b, a), Ordering::Greater);
assert_eq!(compare_terms(&m, a, a), Ordering::Equal);
}
#[test]
fn compounds_arity_then_name_then_args() {
let mut m = machine();
let f1 = str_term(&mut m, "f", &[make_int(1)]);
let g2 = str_term(&mut m, "g", &[make_int(1), make_int(2)]);
assert_eq!(compare_terms(&m, f1, g2), Ordering::Less);
let g1 = str_term(&mut m, "g", &[make_int(1)]);
let f1b = str_term(&mut m, "f", &[make_int(1)]);
assert_eq!(compare_terms(&m, f1b, g1), Ordering::Less);
let fa = str_term(&mut m, "f", &[make_int(1)]);
let fb = str_term(&mut m, "f", &[make_int(2)]);
assert_eq!(compare_terms(&m, fa, fb), Ordering::Less);
}
#[test]
fn list_equals_dot_struct() {
let mut m = machine();
let nil = make_atom(plg_shared::atom::ATOM_NIL);
let lst = {
let inner = list(&mut m, make_int(2), nil);
list(&mut m, make_int(1), inner)
};
let dotstruct = {
let inner = str_term(&mut m, ".", &[make_int(2), nil]);
str_term(&mut m, ".", &[make_int(1), inner])
};
assert_eq!(compare_terms(&m, lst, dotstruct), Ordering::Equal);
assert_eq!(compare_terms(&m, dotstruct, lst), Ordering::Equal);
}
#[test]
fn list_vs_improper_dot() {
let mut m = machine();
let nil = make_atom(plg_shared::atom::ATOM_NIL);
let lst = {
let inner = list(&mut m, make_int(2), nil);
list(&mut m, make_int(1), inner)
};
let improper = str_term(&mut m, ".", &[make_int(1), make_int(2)]);
assert_eq!(compare_terms(&m, lst, improper), Ordering::Greater);
assert_eq!(compare_terms(&m, improper, lst), Ordering::Less);
}
#[test]
fn vars_ordered_by_index() {
let mut m = machine();
let v0 = m.new_var();
let v1 = m.new_var();
assert_eq!(compare_terms(&m, v0, v1), Ordering::Less);
assert_eq!(compare_terms(&m, v1, v0), Ordering::Greater);
}
}