plg_runtime/builtins/
sortops.rs1use crate::builtins::order::compare_terms;
11use crate::cell::*;
12use crate::machine::Machine;
13use crate::unify::unify;
14use plg_shared::atom::ATOM_NIL;
15use std::cmp::Ordering;
16
17#[inline]
18fn mref<'a>(m: *mut Machine) -> &'a mut Machine {
19 unsafe { &mut *m }
20}
21
22fn collect_list(m: &Machine, w: Word) -> Option<Vec<Word>> {
24 let mut out = Vec::new();
25 let mut cur = m.deref(w);
26 loop {
27 match tag_of(cur) {
28 TAG_ATOM if atom_id(cur) == ATOM_NIL => return Some(out),
29 TAG_LST => {
30 let idx = payload(cur) as usize;
31 out.push(m.heap[idx]);
32 cur = m.deref(m.heap[idx + 1]);
33 }
34 _ => return None,
35 }
36 }
37}
38
39fn build_list(m: &mut Machine, elems: &[Word]) -> Word {
41 let mut tail = make_atom(ATOM_NIL);
42 for &e in elems.iter().rev() {
43 let idx = m.heap.len();
44 m.heap.push(e);
45 m.heap.push(tail);
46 tail = make(TAG_LST, idx as u64);
47 }
48 tail
49}
50
51#[unsafe(no_mangle)]
53pub extern "C" fn plg_rt_b_msort_2(m: *mut Machine, list: u64, sorted: u64, site_id: u32) -> i32 {
54 let _site = crate::machine::ErrorSiteGuard::enter(m, site_id);
55 let m = mref(m);
56 let Some(mut elems) = collect_list(m, list) else {
57 let culprit = m.deref(list);
58 crate::errors::type_error(m, "list", culprit, "msort/2: first argument must be a list");
59 return 0;
60 };
61 elems.sort_by(|&a, &b| compare_terms(m, a, b));
62 let lst = build_list(m, &elems);
63 unify(m, sorted, lst) as i32
64}
65
66#[unsafe(no_mangle)]
68pub extern "C" fn plg_rt_b_sort_2(m: *mut Machine, list: u64, sorted: u64, site_id: u32) -> i32 {
69 let _site = crate::machine::ErrorSiteGuard::enter(m, site_id);
70 let m = mref(m);
71 let Some(mut elems) = collect_list(m, list) else {
72 let culprit = m.deref(list);
73 crate::errors::type_error(m, "list", culprit, "sort/2: first argument must be a list");
74 return 0;
75 };
76 elems.sort_by(|&a, &b| compare_terms(m, a, b));
77 elems.dedup_by(|&mut a, &mut b| compare_terms(m, a, b) == Ordering::Equal);
78 let lst = build_list(m, &elems);
79 unify(m, sorted, lst) as i32
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::machine::NO_SITE;
86 use plg_shared::StringInterner;
87
88 fn machine() -> Box<Machine> {
89 Machine::new(StringInterner::new(), Vec::new())
90 }
91
92 fn msort(m: *mut Machine, l: u64, s: u64) -> i32 {
94 plg_rt_b_msort_2(m, l, s, NO_SITE)
95 }
96 fn sort(m: *mut Machine, l: u64, s: u64) -> i32 {
97 plg_rt_b_sort_2(m, l, s, NO_SITE)
98 }
99
100 fn msg(m: &Machine) -> &str {
101 m.error.as_ref().unwrap().message.as_str()
102 }
103
104 fn ints(m: &mut Machine, vals: &[i64]) -> Word {
105 let ws: Vec<Word> = vals.iter().map(|&v| make_int(v)).collect();
106 build_list(m, &ws)
107 }
108
109 #[test]
110 fn msort_keeps_duplicates() {
111 let mut m = machine();
112 let l = ints(&mut m, &[3, 1, 2, 1]);
113 let out = m.new_var();
114 let mp = &mut *m as *mut Machine;
115 assert_eq!(msort(mp, l, out), 1);
116 let got: Vec<i64> = collect_list(&m, out)
117 .unwrap()
118 .iter()
119 .map(|&w| int_value(m.deref(w)))
120 .collect();
121 assert_eq!(got, vec![1, 1, 2, 3]);
122 }
123
124 #[test]
125 fn sort_dedups_adjacent_equal() {
126 let mut m = machine();
127 let l = ints(&mut m, &[3, 1, 2, 1]);
128 let out = m.new_var();
129 let mp = &mut *m as *mut Machine;
130 assert_eq!(sort(mp, l, out), 1);
131 let got: Vec<i64> = collect_list(&m, out)
132 .unwrap()
133 .iter()
134 .map(|&w| int_value(m.deref(w)))
135 .collect();
136 assert_eq!(got, vec![1, 2, 3]);
137 }
138
139 #[test]
140 fn sort_non_list_errors() {
141 let mut m = machine();
142 let foo = make_atom(m.atoms.intern("foo"));
143 let out = m.new_var();
144 let mp = &mut *m as *mut Machine;
145 assert_eq!(sort(mp, foo, out), 0);
146 assert_eq!(
147 msg(&m),
148 "error(type_error(list, foo), sort/2: first argument must be a list)"
149 );
150
151 let mut m = machine();
152 let foo = make_atom(m.atoms.intern("foo"));
153 let out = m.new_var();
154 let mp = &mut *m as *mut Machine;
155 assert_eq!(msort(mp, foo, out), 0);
156 assert_eq!(
157 msg(&m),
158 "error(type_error(list, foo), msort/2: first argument must be a list)"
159 );
160 }
161}