Skip to main content

plg_runtime/builtins/
typecheck.rs

1//! Type-checking builtins: `var/1`, `nonvar/1`, `atom/1`, `number/1`,
2//! `integer/1`, `float/1`, `compound/1`, `is_list/1`.
3//!
4//! Ported byte-for-byte from patch-prolog v1 (`builtins.rs` type-check
5//! arms). Each deref-tests a single tag. Notable v1 decisions, verified
6//! against the oracle:
7//! - `integer/1` accepts both the immediate `TAG_INT` and the boxed
8//!   `TAG_BIG` (v1's `Term::Integer` covers the full i64 range).
9//! - `compound/1` is TRUE for lists: v1 matches `Compound { .. } |
10//!   List { .. }`, so a `TAG_LST` counts as compound
11//!   (`compound([1])` succeeds).
12//! - `is_list/1` walks the tail iteratively and is true only for a
13//!   proper, nil-terminated list (`is_list([a|b])` fails).
14
15use crate::cell::*;
16use crate::machine::Machine;
17use plg_shared::atom::ATOM_NIL;
18
19#[inline]
20fn mref<'a>(m: *mut Machine) -> &'a mut Machine {
21    unsafe { &mut *m }
22}
23
24/// `var/1`: succeeds iff the dereferenced argument is an unbound variable.
25#[unsafe(no_mangle)]
26pub extern "C" fn plg_rt_b_var_1(m: *mut Machine, a: u64) -> i32 {
27    let m = mref(m);
28    (tag_of(m.deref(a)) == TAG_REF) as i32
29}
30
31/// `nonvar/1`: the negation of `var/1`.
32#[unsafe(no_mangle)]
33pub extern "C" fn plg_rt_b_nonvar_1(m: *mut Machine, a: u64) -> i32 {
34    let m = mref(m);
35    (tag_of(m.deref(a)) != TAG_REF) as i32
36}
37
38/// `atom/1`: succeeds iff the argument is an atom.
39#[unsafe(no_mangle)]
40pub extern "C" fn plg_rt_b_atom_1(m: *mut Machine, a: u64) -> i32 {
41    let m = mref(m);
42    (tag_of(m.deref(a)) == TAG_ATOM) as i32
43}
44
45/// `number/1`: any numeric tag (immediate int, boxed big, or float).
46#[unsafe(no_mangle)]
47pub extern "C" fn plg_rt_b_number_1(m: *mut Machine, a: u64) -> i32 {
48    let m = mref(m);
49    matches!(tag_of(m.deref(a)), TAG_INT | TAG_BIG | TAG_FLT) as i32
50}
51
52/// `integer/1`: immediate `TAG_INT` or boxed `TAG_BIG`.
53#[unsafe(no_mangle)]
54pub extern "C" fn plg_rt_b_integer_1(m: *mut Machine, a: u64) -> i32 {
55    let m = mref(m);
56    matches!(tag_of(m.deref(a)), TAG_INT | TAG_BIG) as i32
57}
58
59/// `float/1`: a boxed float.
60#[unsafe(no_mangle)]
61pub extern "C" fn plg_rt_b_float_1(m: *mut Machine, a: u64) -> i32 {
62    let m = mref(m);
63    (tag_of(m.deref(a)) == TAG_FLT) as i32
64}
65
66/// `compound/1`: a structure OR a list cell (v1 counts lists as compound).
67#[unsafe(no_mangle)]
68pub extern "C" fn plg_rt_b_compound_1(m: *mut Machine, a: u64) -> i32 {
69    let m = mref(m);
70    matches!(tag_of(m.deref(a)), TAG_STR | TAG_LST) as i32
71}
72
73/// `is_list/1`: a proper (nil-terminated) list. Iterative tail walk so a
74/// long list can't blow the C stack; a partial or improper list fails.
75#[unsafe(no_mangle)]
76pub extern "C" fn plg_rt_b_is_list_1(m: *mut Machine, a: u64) -> i32 {
77    let m = mref(m);
78    let mut cur = m.deref(a);
79    loop {
80        match tag_of(cur) {
81            TAG_ATOM if atom_id(cur) == ATOM_NIL => return 1,
82            TAG_LST => {
83                let idx = payload(cur) as usize;
84                cur = m.deref(m.heap[idx + 1]); // follow the tail
85            }
86            _ => return 0, // var tail, improper tail, or non-list
87        }
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use plg_shared::StringInterner;
95
96    fn machine() -> Box<Machine> {
97        Machine::new(StringInterner::new(), Vec::new())
98    }
99
100    fn big(m: &mut Machine, n: i64) -> Word {
101        let idx = m.heap.len();
102        m.heap.push(n as u64);
103        make(TAG_BIG, idx as u64)
104    }
105
106    fn flt(m: &mut Machine, f: f64) -> Word {
107        let idx = m.heap.len();
108        m.heap.push(f.to_bits());
109        make(TAG_FLT, idx as u64)
110    }
111
112    fn str_term(m: &mut Machine, name: &str, args: &[Word]) -> Word {
113        let f = m.atoms.intern(name);
114        let idx = m.heap.len();
115        m.heap.push(pack_functor(f, args.len() as u32));
116        m.heap.extend_from_slice(args);
117        make(TAG_STR, idx as u64)
118    }
119
120    fn list(m: &mut Machine, head: Word, tail: Word) -> Word {
121        let idx = m.heap.len();
122        m.heap.push(head);
123        m.heap.push(tail);
124        make(TAG_LST, idx as u64)
125    }
126
127    #[test]
128    fn var_nonvar() {
129        let mut m = machine();
130        let v = m.new_var();
131        let mp = &mut *m as *mut Machine;
132        assert_eq!(plg_rt_b_var_1(mp, v), 1);
133        assert_eq!(plg_rt_b_nonvar_1(mp, v), 0);
134        assert_eq!(plg_rt_b_var_1(mp, make_int(3)), 0);
135        assert_eq!(plg_rt_b_nonvar_1(mp, make_int(3)), 1);
136        // bound var derefs to its value (nonvar)
137        m.bind(payload(v) as usize, make_atom(7));
138        let mp = &mut *m as *mut Machine;
139        assert_eq!(plg_rt_b_var_1(mp, v), 0);
140        assert_eq!(plg_rt_b_nonvar_1(mp, v), 1);
141    }
142
143    #[test]
144    fn atom_number_integer_float() {
145        let mut m = machine();
146        let a = make_atom(m.atoms.intern("a"));
147        let b = big(&mut m, 1 << 62);
148        let f = flt(&mut m, 1.5);
149        let mp = &mut *m as *mut Machine;
150        assert_eq!(plg_rt_b_atom_1(mp, a), 1);
151        assert_eq!(plg_rt_b_atom_1(mp, make_int(1)), 0);
152
153        assert_eq!(plg_rt_b_number_1(mp, make_int(1)), 1);
154        assert_eq!(plg_rt_b_number_1(mp, b), 1);
155        assert_eq!(plg_rt_b_number_1(mp, f), 1);
156        assert_eq!(plg_rt_b_number_1(mp, a), 0);
157
158        assert_eq!(plg_rt_b_integer_1(mp, make_int(1)), 1);
159        assert_eq!(plg_rt_b_integer_1(mp, b), 1); // TAG_BIG counts
160        assert_eq!(plg_rt_b_integer_1(mp, f), 0);
161
162        assert_eq!(plg_rt_b_float_1(mp, f), 1);
163        assert_eq!(plg_rt_b_float_1(mp, make_int(1)), 0);
164        assert_eq!(plg_rt_b_float_1(mp, b), 0);
165    }
166
167    #[test]
168    fn compound_includes_lists() {
169        let mut m = machine();
170        let nil = make_atom(ATOM_NIL);
171        let s = str_term(&mut m, "foo", &[make_int(1)]);
172        let l = list(&mut m, make_int(1), nil);
173        let a = make_atom(m.atoms.intern("a"));
174        let mp = &mut *m as *mut Machine;
175        assert_eq!(plg_rt_b_compound_1(mp, s), 1); // foo(1)
176        assert_eq!(plg_rt_b_compound_1(mp, l), 1); // [1] — v1: list IS compound
177        assert_eq!(plg_rt_b_compound_1(mp, a), 0);
178        assert_eq!(plg_rt_b_compound_1(mp, make_int(1)), 0);
179    }
180
181    #[test]
182    fn is_list_proper_partial_improper() {
183        let mut m = machine();
184        let nil = make_atom(ATOM_NIL);
185        // [a, b]
186        let proper = {
187            let inner = list(&mut m, make_atom(2), nil);
188            list(&mut m, make_atom(1), inner)
189        };
190        // [a|b] improper
191        let improper = list(&mut m, make_atom(1), make_atom(2));
192        // [a|V] partial
193        let v = m.new_var();
194        let partial = list(&mut m, make_atom(1), v);
195        let mp = &mut *m as *mut Machine;
196        assert_eq!(plg_rt_b_is_list_1(mp, proper), 1);
197        assert_eq!(plg_rt_b_is_list_1(mp, nil), 1); // [] is a proper list
198        assert_eq!(plg_rt_b_is_list_1(mp, improper), 0);
199        assert_eq!(plg_rt_b_is_list_1(mp, partial), 0);
200        assert_eq!(plg_rt_b_is_list_1(mp, make_int(1)), 0);
201    }
202}