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