1use crate::cell::*;
15use crate::machine::Machine;
16use crate::unify::unify;
17use plg_shared::atom::ATOM_DOT;
18
19#[inline]
20fn mref<'a>(m: *mut Machine) -> &'a mut Machine {
21 unsafe { &mut *m }
22}
23
24fn build_list(m: &mut Machine, elems: &[Word]) -> Word {
26 let mut tail = make_atom(plg_shared::atom::ATOM_NIL);
27 for &e in elems.iter().rev() {
28 let idx = m.heap.len();
29 m.heap.push(e);
30 m.heap.push(tail);
31 tail = make(TAG_LST, idx as u64);
32 }
33 tail
34}
35
36fn collect_list(m: &Machine, w: Word) -> Option<Vec<Word>> {
38 let mut out = Vec::new();
39 let mut cur = m.deref(w);
40 loop {
41 match tag_of(cur) {
42 TAG_ATOM if atom_id(cur) == plg_shared::atom::ATOM_NIL => return Some(out),
43 TAG_LST => {
44 let idx = payload(cur) as usize;
45 out.push(m.heap[idx]);
46 cur = m.deref(m.heap[idx + 1]);
47 }
48 _ => return None,
49 }
50 }
51}
52
53#[unsafe(no_mangle)]
55pub extern "C" fn plg_rt_b_functor_3(
56 m: *mut Machine,
57 term: u64,
58 name: u64,
59 arity: u64,
60 site_id: u32,
61) -> i32 {
62 let _site = crate::machine::ErrorSiteGuard::enter(m, site_id);
63 let m = mref(m);
64 let w = m.deref(term);
65 match tag_of(w) {
66 TAG_ATOM => {
67 let ok = unify(m, name, w) && unify(m, arity, make_int(0));
68 ok as i32
69 }
70 TAG_INT | TAG_BIG | TAG_FLT => {
71 let ok = unify(m, name, w) && unify(m, arity, make_int(0));
72 ok as i32
73 }
74 TAG_STR => {
75 let idx = payload(w) as usize;
76 let (f, n) = unpack_functor(m.heap[idx]);
77 let ok = unify(m, name, make_atom(f)) && unify(m, arity, make_int(n as i64));
78 ok as i32
79 }
80 TAG_LST => {
81 let ok = unify(m, name, make_atom(ATOM_DOT)) && unify(m, arity, make_int(2));
83 ok as i32
84 }
85 TAG_REF => functor_construct(m, term, name, arity),
86 _ => 0,
87 }
88}
89
90fn functor_construct(m: &mut Machine, term: u64, name: u64, arity: u64) -> i32 {
93 let wname = m.deref(name);
94 let warity = m.deref(arity);
95 let arity_val = match tag_of(warity) {
97 TAG_INT => int_value(warity),
98 TAG_BIG => m.heap[payload(warity) as usize] as i64,
99 _ => {
100 crate::errors::instantiation(m, "functor/3: insufficient arguments");
101 return 0;
102 }
103 };
104 if arity_val == 0 {
106 match tag_of(wname) {
107 TAG_ATOM | TAG_INT | TAG_BIG | TAG_FLT => return unify(m, term, wname) as i32,
108 _ => {
109 crate::errors::instantiation(m, "functor/3: insufficient arguments");
110 return 0;
111 }
112 }
113 }
114 if arity_val < 0 {
115 crate::errors::domain_error(
117 m,
118 "not_less_than_zero",
119 warity,
120 "functor/3: arity must be non-negative",
121 );
122 return 0;
123 }
124 if arity_val > 1024 {
125 let re = m.atoms.intern("representation_error");
127 let flag = make_atom(m.atoms.intern("max_arity"));
128 let idx = m.heap.len();
129 m.heap.push(pack_functor(re, 1));
130 m.heap.push(flag);
131 crate::errors::set_formal(
132 m,
133 make(TAG_STR, idx as u64),
134 "functor/3: arity too large (max 1024)",
135 false,
136 );
137 return 0;
138 }
139 if tag_of(wname) != TAG_ATOM {
141 crate::errors::instantiation(m, "functor/3: insufficient arguments");
142 return 0;
143 }
144 let f = atom_id(wname);
145 let n = arity_val as u32;
146 let base = m.heap.len();
147 m.heap.push(pack_functor(f, n));
148 for _ in 0..n {
149 let v = m.new_var();
150 m.heap.push(v);
151 }
152 let constructed = make(TAG_STR, base as u64);
153 unify(m, term, constructed) as i32
154}
155
156#[unsafe(no_mangle)]
158pub extern "C" fn plg_rt_b_arg_3(
159 m: *mut Machine,
160 n: u64,
161 term: u64,
162 result: u64,
163 site_id: u32,
164) -> i32 {
165 let _site = crate::machine::ErrorSiteGuard::enter(m, site_id);
166 let m = mref(m);
167 let wn = m.deref(n);
168 let n_val = match tag_of(wn) {
169 TAG_INT => int_value(wn),
170 TAG_BIG => m.heap[payload(wn) as usize] as i64,
171 _ => {
172 crate::errors::type_error(m, "integer", wn, "arg/3: first argument must be integer");
173 return 0;
174 }
175 };
176 let wt = m.deref(term);
177 match tag_of(wt) {
178 TAG_STR => {
179 let idx = payload(wt) as usize;
180 let (_, arity) = unpack_functor(m.heap[idx]);
181 if n_val >= 1 && (n_val as u64) <= arity as u64 {
182 let arg = m.heap[idx + n_val as usize];
183 unify(m, result, arg) as i32
184 } else {
185 0 }
187 }
188 TAG_LST => {
189 let idx = payload(wt) as usize;
190 match n_val {
191 1 => unify(m, result, m.heap[idx]) as i32,
192 2 => unify(m, result, m.heap[idx + 1]) as i32,
193 _ => 0,
194 }
195 }
196 _ => {
197 crate::errors::type_error(m, "compound", wt, "arg/3: second argument must be compound");
198 0
199 }
200 }
201}
202
203#[unsafe(no_mangle)]
205pub extern "C" fn plg_rt_b_univ_2(m: *mut Machine, term: u64, list: u64, site_id: u32) -> i32 {
206 let _site = crate::machine::ErrorSiteGuard::enter(m, site_id);
207 let m = mref(m);
208 let w = m.deref(term);
209 match tag_of(w) {
210 TAG_REF => univ_construct(m, term, list),
211 TAG_ATOM | TAG_INT | TAG_BIG | TAG_FLT => {
212 let lst = build_list(m, &[w]);
213 unify(m, list, lst) as i32
214 }
215 TAG_STR => {
216 let idx = payload(w) as usize;
217 let (f, n) = unpack_functor(m.heap[idx]);
218 let mut elems = Vec::with_capacity(n as usize + 1);
219 elems.push(make_atom(f));
220 for i in 0..n as usize {
221 elems.push(m.heap[idx + 1 + i]);
222 }
223 let lst = build_list(m, &elems);
224 unify(m, list, lst) as i32
225 }
226 TAG_LST => {
227 let idx = payload(w) as usize;
228 let head = m.heap[idx];
229 let tail = m.heap[idx + 1];
230 let lst = build_list(m, &[make_atom(ATOM_DOT), head, tail]);
231 unify(m, list, lst) as i32
232 }
233 _ => 0,
234 }
235}
236
237fn univ_construct(m: &mut Machine, term: u64, list: u64) -> i32 {
239 let Some(elems) = collect_list(m, list) else {
240 let culprit = m.deref(list);
241 crate::errors::type_error(m, "list", culprit, "=../2: second argument must be a list");
242 return 0;
243 };
244 if elems.is_empty() {
245 let culprit = m.deref(list);
246 crate::errors::domain_error(
247 m,
248 "non_empty_list",
249 culprit,
250 "=../2: list must not be empty",
251 );
252 return 0;
253 }
254 let head = m.deref(elems[0]);
255 if elems.len() == 1 {
256 if tag_of(head) == TAG_REF {
258 crate::errors::instantiation(m, "=../2: instantiation error - element must be bound");
259 return 0;
260 }
261 return unify(m, term, head) as i32;
262 }
263 if tag_of(head) != TAG_ATOM {
265 crate::errors::type_error(
266 m,
267 "atom",
268 head,
269 "=../2: functor must be an atom when arity > 0",
270 );
271 return 0;
272 }
273 let f = atom_id(head);
274 let n = (elems.len() - 1) as u32;
275 let base = m.heap.len();
276 m.heap.push(pack_functor(f, n));
277 for &e in &elems[1..] {
278 m.heap.push(e);
279 }
280 let constructed = make(TAG_STR, base as u64);
281 unify(m, term, constructed) as i32
282}
283
284#[unsafe(no_mangle)]
286pub extern "C" fn plg_rt_b_copy_term_2(m: *mut Machine, orig: u64, copy: u64) -> i32 {
287 let m = mref(m);
288 let buf = crate::copyterm::copy_to_buf(m, orig);
289 let fresh = crate::copyterm::restore_from_buf(m, &buf);
290 unify(m, copy, fresh) as i32
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::machine::NO_SITE;
297 use plg_shared::StringInterner;
298
299 fn machine() -> Box<Machine> {
300 Machine::new(StringInterner::new(), Vec::new())
301 }
302
303 fn functor3(m: *mut Machine, t: u64, n: u64, a: u64) -> i32 {
306 plg_rt_b_functor_3(m, t, n, a, NO_SITE)
307 }
308 fn arg3(m: *mut Machine, n: u64, t: u64, r: u64) -> i32 {
309 plg_rt_b_arg_3(m, n, t, r, NO_SITE)
310 }
311 fn univ2(m: *mut Machine, t: u64, l: u64) -> i32 {
312 plg_rt_b_univ_2(m, t, l, NO_SITE)
313 }
314
315 fn str_term(m: &mut Machine, name: &str, args: &[Word]) -> Word {
316 let f = m.atoms.intern(name);
317 let idx = m.heap.len();
318 m.heap.push(pack_functor(f, args.len() as u32));
319 m.heap.extend_from_slice(args);
320 make(TAG_STR, idx as u64)
321 }
322
323 fn lst(m: &mut Machine, head: Word, tail: Word) -> Word {
324 let idx = m.heap.len();
325 m.heap.push(head);
326 m.heap.push(tail);
327 make(TAG_LST, idx as u64)
328 }
329
330 fn msg(m: &Machine) -> &str {
331 m.error.as_ref().unwrap().message.as_str()
332 }
333
334 #[test]
335 fn functor_decompose() {
336 let mut m = machine();
337 let s = str_term(&mut m, "foo", &[make_int(1), make_int(2)]);
338 let name = m.new_var();
339 let ar = m.new_var();
340 let mp = &mut *m as *mut Machine;
341 assert_eq!(functor3(mp, s, name, ar), 1);
342 let foo = m.atoms.lookup("foo").unwrap();
343 assert_eq!(m.deref(name), make_atom(foo));
344 assert_eq!(int_value(m.deref(ar)), 2);
345
346 let a = make_atom(m.atoms.intern("a"));
348 let name = m.new_var();
349 let ar = m.new_var();
350 let mp = &mut *m as *mut Machine;
351 assert_eq!(functor3(mp, a, name, ar), 1);
352 assert_eq!(int_value(m.deref(ar)), 0);
353 }
354
355 #[test]
356 fn functor_list_is_dot_2() {
357 let mut m = machine();
358 let nil = make_atom(plg_shared::atom::ATOM_NIL);
359 let l = lst(&mut m, make_int(1), nil);
360 let name = m.new_var();
361 let ar = m.new_var();
362 let mp = &mut *m as *mut Machine;
363 assert_eq!(functor3(mp, l, name, ar), 1);
364 assert_eq!(m.deref(name), make_atom(ATOM_DOT));
365 assert_eq!(int_value(m.deref(ar)), 2);
366 }
367
368 #[test]
369 fn functor_construct_builds_str_even_for_dot() {
370 let mut m = machine();
372 let t = m.new_var();
373 let dot = make_atom(ATOM_DOT);
374 let mp = &mut *m as *mut Machine;
375 assert_eq!(functor3(mp, t, dot, make_int(2)), 1);
376 let w = m.deref(t);
377 assert_eq!(tag_of(w), TAG_STR);
378 let (f, n) = unpack_functor(m.heap[payload(w) as usize]);
379 assert_eq!((f, n), (ATOM_DOT, 2));
380
381 let t = m.new_var();
383 let foo = make_atom(m.atoms.intern("foo"));
384 let mp = &mut *m as *mut Machine;
385 assert_eq!(functor3(mp, t, foo, make_int(2)), 1);
386 assert_eq!(tag_of(m.deref(t)), TAG_STR);
387 }
388
389 #[test]
390 fn functor_errors() {
391 let mut m = machine();
393 let t = m.new_var();
394 let n = m.new_var();
395 let a = m.new_var();
396 let mp = &mut *m as *mut Machine;
397 assert_eq!(functor3(mp, t, n, a), 0);
398 assert_eq!(
399 msg(&m),
400 "error(instantiation_error, functor/3: insufficient arguments)"
401 );
402
403 let mut m = machine();
405 let t = m.new_var();
406 let foo = make_atom(m.atoms.intern("foo"));
407 let mp = &mut *m as *mut Machine;
408 assert_eq!(functor3(mp, t, foo, make_int(-1)), 0);
409 assert_eq!(
410 msg(&m),
411 "error(domain_error(not_less_than_zero, -1), functor/3: arity must be non-negative)"
412 );
413 }
414
415 #[test]
416 fn arg_in_and_out_of_range() {
417 let mut m = machine();
418 let s = str_term(&mut m, "foo", &[make_atom(7), make_atom(8)]);
419 let x = m.new_var();
420 let mp = &mut *m as *mut Machine;
421 assert_eq!(arg3(mp, make_int(1), s, x), 1);
422 assert_eq!(m.deref(x), make_atom(7));
423 let y = m.new_var();
425 let mp = &mut *m as *mut Machine;
426 assert_eq!(arg3(mp, make_int(3), s, y), 0);
427 assert!(m.error.is_none());
428 let mp = &mut *m as *mut Machine;
430 assert_eq!(arg3(mp, make_int(0), s, y), 0);
431 assert!(m.error.is_none());
432 }
433
434 #[test]
435 fn arg_non_compound_errors() {
436 let mut m = machine();
437 let x = m.new_var();
438 let a = make_atom(m.atoms.intern("a"));
439 let mp = &mut *m as *mut Machine;
440 assert_eq!(arg3(mp, make_int(1), a, x), 0);
441 assert_eq!(
442 msg(&m),
443 "error(type_error(compound, a), arg/3: second argument must be compound)"
444 );
445 }
446
447 #[test]
448 fn univ_decompose_and_construct() {
449 let mut m = machine();
450 let s = str_term(&mut m, "foo", &[make_atom(7), make_int(2)]);
451 let l = m.new_var();
452 let mp = &mut *m as *mut Machine;
453 assert_eq!(univ2(mp, s, l), 1);
454 let elems = collect_list(&m, l).unwrap();
455 let foo = m.atoms.lookup("foo").unwrap();
456 assert_eq!(m.deref(elems[0]), make_atom(foo));
457 assert_eq!(elems.len(), 3);
458
459 let a = make_atom(m.atoms.intern("a"));
461 let b = make_atom(m.atoms.intern("b"));
462 let dot = make_atom(ATOM_DOT);
463 let inlist = build_list(&mut m, &[dot, a, b]);
464 let t = m.new_var();
465 let mp = &mut *m as *mut Machine;
466 assert_eq!(univ2(mp, t, inlist), 1);
467 let w = m.deref(t);
468 assert_eq!(tag_of(w), TAG_STR);
469 let (f, n) = unpack_functor(m.heap[payload(w) as usize]);
470 assert_eq!((f, n), (ATOM_DOT, 2));
471 }
472
473 #[test]
474 fn univ_single_element_and_errors() {
475 let mut m = machine();
477 let inlist = build_list(&mut m, &[make_int(42)]);
478 let t = m.new_var();
479 let mp = &mut *m as *mut Machine;
480 assert_eq!(univ2(mp, t, inlist), 1);
481 assert_eq!(int_value(m.deref(t)), 42);
482
483 let mut m = machine();
485 let nil = make_atom(plg_shared::atom::ATOM_NIL);
486 let t = m.new_var();
487 let mp = &mut *m as *mut Machine;
488 assert_eq!(univ2(mp, t, nil), 0);
489 assert_eq!(
490 msg(&m),
491 "error(domain_error(non_empty_list, []), =../2: list must not be empty)"
492 );
493 }
494
495 #[test]
496 fn copy_term_renames_and_shares() {
497 let mut m = machine();
498 let x = m.new_var();
499 let s = str_term(&mut m, "f", &[x, x]);
501 let c = m.new_var();
502 let mp = &mut *m as *mut Machine;
503 assert_eq!(plg_rt_b_copy_term_2(mp, s, c), 1);
504 let cw = m.deref(c);
505 assert_eq!(tag_of(cw), TAG_STR);
506 let idx = payload(cw) as usize;
507 let a0 = m.deref(m.heap[idx + 1]);
508 let a1 = m.deref(m.heap[idx + 2]);
509 assert_eq!(a0, a1, "shared var preserved");
510 assert_ne!(a0, m.deref(x), "fresh var distinct from original");
511 }
512}