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 {
154 let idx = m.heap.len();
155 m.heap.push(make_ref(idx));
156 }
157 let constructed = make(TAG_STR, base as u64);
158 unify(m, term, constructed) as i32
159}
160
161#[unsafe(no_mangle)]
163pub extern "C" fn plg_rt_b_arg_3(
164 m: *mut Machine,
165 n: u64,
166 term: u64,
167 result: u64,
168 site_id: u32,
169) -> i32 {
170 let _site = crate::machine::ErrorSiteGuard::enter(m, site_id);
171 let m = mref(m);
172 let wn = m.deref(n);
173 let n_val = match tag_of(wn) {
174 TAG_INT => int_value(wn),
175 TAG_BIG => m.heap[payload(wn) as usize] as i64,
176 _ => {
177 crate::errors::type_error(m, "integer", wn, "arg/3: first argument must be integer");
178 return 0;
179 }
180 };
181 let wt = m.deref(term);
182 match tag_of(wt) {
183 TAG_STR => {
184 let idx = payload(wt) as usize;
185 let (_, arity) = unpack_functor(m.heap[idx]);
186 if n_val >= 1 && (n_val as u64) <= arity as u64 {
187 let arg = m.heap[idx + n_val as usize];
188 unify(m, result, arg) as i32
189 } else {
190 0 }
192 }
193 TAG_LST => {
194 let idx = payload(wt) as usize;
195 match n_val {
196 1 => unify(m, result, m.heap[idx]) as i32,
197 2 => unify(m, result, m.heap[idx + 1]) as i32,
198 _ => 0,
199 }
200 }
201 _ => {
202 crate::errors::type_error(m, "compound", wt, "arg/3: second argument must be compound");
203 0
204 }
205 }
206}
207
208#[unsafe(no_mangle)]
210pub extern "C" fn plg_rt_b_univ_2(m: *mut Machine, term: u64, list: u64, site_id: u32) -> i32 {
211 let _site = crate::machine::ErrorSiteGuard::enter(m, site_id);
212 let m = mref(m);
213 let w = m.deref(term);
214 match tag_of(w) {
215 TAG_REF => univ_construct(m, term, list),
216 TAG_ATOM | TAG_INT | TAG_BIG | TAG_FLT => {
217 let lst = build_list(m, &[w]);
218 unify(m, list, lst) as i32
219 }
220 TAG_STR => {
221 let idx = payload(w) as usize;
222 let (f, n) = unpack_functor(m.heap[idx]);
223 let mut elems = Vec::with_capacity(n as usize + 1);
224 elems.push(make_atom(f));
225 for i in 0..n as usize {
226 elems.push(m.heap[idx + 1 + i]);
227 }
228 let lst = build_list(m, &elems);
229 unify(m, list, lst) as i32
230 }
231 TAG_LST => {
232 let idx = payload(w) as usize;
233 let head = m.heap[idx];
234 let tail = m.heap[idx + 1];
235 let lst = build_list(m, &[make_atom(ATOM_DOT), head, tail]);
236 unify(m, list, lst) as i32
237 }
238 _ => 0,
239 }
240}
241
242fn univ_construct(m: &mut Machine, term: u64, list: u64) -> i32 {
244 let Some(elems) = collect_list(m, list) else {
245 let culprit = m.deref(list);
246 crate::errors::type_error(m, "list", culprit, "=../2: second argument must be a list");
247 return 0;
248 };
249 if elems.is_empty() {
250 let culprit = m.deref(list);
251 crate::errors::domain_error(
252 m,
253 "non_empty_list",
254 culprit,
255 "=../2: list must not be empty",
256 );
257 return 0;
258 }
259 let head = m.deref(elems[0]);
260 if elems.len() == 1 {
261 if tag_of(head) == TAG_REF {
263 crate::errors::instantiation(m, "=../2: instantiation error - element must be bound");
264 return 0;
265 }
266 return unify(m, term, head) as i32;
267 }
268 if tag_of(head) != TAG_ATOM {
270 crate::errors::type_error(
271 m,
272 "atom",
273 head,
274 "=../2: functor must be an atom when arity > 0",
275 );
276 return 0;
277 }
278 let f = atom_id(head);
279 let n = (elems.len() - 1) as u32;
280 let base = m.heap.len();
281 m.heap.push(pack_functor(f, n));
282 for &e in &elems[1..] {
283 m.heap.push(e);
284 }
285 let constructed = make(TAG_STR, base as u64);
286 unify(m, term, constructed) as i32
287}
288
289#[unsafe(no_mangle)]
291pub extern "C" fn plg_rt_b_copy_term_2(m: *mut Machine, orig: u64, copy: u64) -> i32 {
292 let m = mref(m);
293 let buf = crate::copyterm::copy_to_buf(m, orig);
294 let fresh = crate::copyterm::restore_from_buf(m, &buf);
295 unify(m, copy, fresh) as i32
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use crate::machine::NO_SITE;
302 use plg_shared::StringInterner;
303
304 fn machine() -> Box<Machine> {
305 Machine::new(StringInterner::new(), Vec::new())
306 }
307
308 fn functor3(m: *mut Machine, t: u64, n: u64, a: u64) -> i32 {
311 plg_rt_b_functor_3(m, t, n, a, NO_SITE)
312 }
313 fn arg3(m: *mut Machine, n: u64, t: u64, r: u64) -> i32 {
314 plg_rt_b_arg_3(m, n, t, r, NO_SITE)
315 }
316 fn univ2(m: *mut Machine, t: u64, l: u64) -> i32 {
317 plg_rt_b_univ_2(m, t, l, NO_SITE)
318 }
319
320 fn str_term(m: &mut Machine, name: &str, args: &[Word]) -> Word {
321 let f = m.atoms.intern(name);
322 let idx = m.heap.len();
323 m.heap.push(pack_functor(f, args.len() as u32));
324 m.heap.extend_from_slice(args);
325 make(TAG_STR, idx as u64)
326 }
327
328 fn lst(m: &mut Machine, head: Word, tail: Word) -> Word {
329 let idx = m.heap.len();
330 m.heap.push(head);
331 m.heap.push(tail);
332 make(TAG_LST, idx as u64)
333 }
334
335 fn msg(m: &Machine) -> &str {
336 m.error.as_ref().unwrap().message.as_str()
337 }
338
339 #[test]
340 fn functor_decompose() {
341 let mut m = machine();
342 let s = str_term(&mut m, "foo", &[make_int(1), make_int(2)]);
343 let name = m.new_var();
344 let ar = m.new_var();
345 let mp = &mut *m as *mut Machine;
346 assert_eq!(functor3(mp, s, name, ar), 1);
347 let foo = m.atoms.lookup("foo").unwrap();
348 assert_eq!(m.deref(name), make_atom(foo));
349 assert_eq!(int_value(m.deref(ar)), 2);
350
351 let a = make_atom(m.atoms.intern("a"));
353 let name = m.new_var();
354 let ar = m.new_var();
355 let mp = &mut *m as *mut Machine;
356 assert_eq!(functor3(mp, a, name, ar), 1);
357 assert_eq!(int_value(m.deref(ar)), 0);
358 }
359
360 #[test]
361 fn functor_list_is_dot_2() {
362 let mut m = machine();
363 let nil = make_atom(plg_shared::atom::ATOM_NIL);
364 let l = lst(&mut m, make_int(1), nil);
365 let name = m.new_var();
366 let ar = m.new_var();
367 let mp = &mut *m as *mut Machine;
368 assert_eq!(functor3(mp, l, name, ar), 1);
369 assert_eq!(m.deref(name), make_atom(ATOM_DOT));
370 assert_eq!(int_value(m.deref(ar)), 2);
371 }
372
373 #[test]
374 fn functor_construct_builds_str_even_for_dot() {
375 let mut m = machine();
377 let t = m.new_var();
378 let dot = make_atom(ATOM_DOT);
379 let mp = &mut *m as *mut Machine;
380 assert_eq!(functor3(mp, t, dot, make_int(2)), 1);
381 let w = m.deref(t);
382 assert_eq!(tag_of(w), TAG_STR);
383 let (f, n) = unpack_functor(m.heap[payload(w) as usize]);
384 assert_eq!((f, n), (ATOM_DOT, 2));
385
386 let t = m.new_var();
388 let foo = make_atom(m.atoms.intern("foo"));
389 let mp = &mut *m as *mut Machine;
390 assert_eq!(functor3(mp, t, foo, make_int(2)), 1);
391 assert_eq!(tag_of(m.deref(t)), TAG_STR);
392 }
393
394 #[test]
395 fn functor_construct_uses_distinct_fresh_vars() {
396 let mut m = machine();
400 let t = m.new_var();
401 let point = make_atom(m.atoms.intern("point"));
402 let mp = &mut *m as *mut Machine;
403 assert_eq!(functor3(mp, t, point, make_int(2)), 1);
404
405 let w = m.deref(t);
406 assert_eq!(tag_of(w), TAG_STR);
407 let base = payload(w) as usize;
408 let a0 = m.deref(m.heap[base + 1]);
410 let a1 = m.deref(m.heap[base + 2]);
411 assert_eq!(tag_of(a0), TAG_REF);
412 assert_eq!(tag_of(a1), TAG_REF);
413 assert_ne!(a0, a1, "argument slots must be distinct variables");
414
415 let concrete = str_term(&mut m, "point", &[make_int(3), make_int(4)]);
417 assert!(unify(&mut m, t, concrete));
418 let w = m.deref(t);
419 let base = payload(w) as usize;
420 assert_eq!(int_value(m.deref(m.heap[base + 1])), 3);
421 assert_eq!(int_value(m.deref(m.heap[base + 2])), 4);
422 }
423
424 #[test]
425 fn functor_errors() {
426 let mut m = machine();
428 let t = m.new_var();
429 let n = m.new_var();
430 let a = m.new_var();
431 let mp = &mut *m as *mut Machine;
432 assert_eq!(functor3(mp, t, n, a), 0);
433 assert_eq!(
434 msg(&m),
435 "error(instantiation_error, functor/3: insufficient arguments)"
436 );
437
438 let mut m = machine();
440 let t = m.new_var();
441 let foo = make_atom(m.atoms.intern("foo"));
442 let mp = &mut *m as *mut Machine;
443 assert_eq!(functor3(mp, t, foo, make_int(-1)), 0);
444 assert_eq!(
445 msg(&m),
446 "error(domain_error(not_less_than_zero, -1), functor/3: arity must be non-negative)"
447 );
448 }
449
450 #[test]
451 fn arg_in_and_out_of_range() {
452 let mut m = machine();
453 let s = str_term(&mut m, "foo", &[make_atom(7), make_atom(8)]);
454 let x = m.new_var();
455 let mp = &mut *m as *mut Machine;
456 assert_eq!(arg3(mp, make_int(1), s, x), 1);
457 assert_eq!(m.deref(x), make_atom(7));
458 let y = m.new_var();
460 let mp = &mut *m as *mut Machine;
461 assert_eq!(arg3(mp, make_int(3), s, y), 0);
462 assert!(m.error.is_none());
463 let mp = &mut *m as *mut Machine;
465 assert_eq!(arg3(mp, make_int(0), s, y), 0);
466 assert!(m.error.is_none());
467 }
468
469 #[test]
470 fn arg_non_compound_errors() {
471 let mut m = machine();
472 let x = m.new_var();
473 let a = make_atom(m.atoms.intern("a"));
474 let mp = &mut *m as *mut Machine;
475 assert_eq!(arg3(mp, make_int(1), a, x), 0);
476 assert_eq!(
477 msg(&m),
478 "error(type_error(compound, a), arg/3: second argument must be compound)"
479 );
480 }
481
482 #[test]
483 fn univ_decompose_and_construct() {
484 let mut m = machine();
485 let s = str_term(&mut m, "foo", &[make_atom(7), make_int(2)]);
486 let l = m.new_var();
487 let mp = &mut *m as *mut Machine;
488 assert_eq!(univ2(mp, s, l), 1);
489 let elems = collect_list(&m, l).unwrap();
490 let foo = m.atoms.lookup("foo").unwrap();
491 assert_eq!(m.deref(elems[0]), make_atom(foo));
492 assert_eq!(elems.len(), 3);
493
494 let a = make_atom(m.atoms.intern("a"));
496 let b = make_atom(m.atoms.intern("b"));
497 let dot = make_atom(ATOM_DOT);
498 let inlist = build_list(&mut m, &[dot, a, b]);
499 let t = m.new_var();
500 let mp = &mut *m as *mut Machine;
501 assert_eq!(univ2(mp, t, inlist), 1);
502 let w = m.deref(t);
503 assert_eq!(tag_of(w), TAG_STR);
504 let (f, n) = unpack_functor(m.heap[payload(w) as usize]);
505 assert_eq!((f, n), (ATOM_DOT, 2));
506 }
507
508 #[test]
509 fn univ_single_element_and_errors() {
510 let mut m = machine();
512 let inlist = build_list(&mut m, &[make_int(42)]);
513 let t = m.new_var();
514 let mp = &mut *m as *mut Machine;
515 assert_eq!(univ2(mp, t, inlist), 1);
516 assert_eq!(int_value(m.deref(t)), 42);
517
518 let mut m = machine();
520 let nil = make_atom(plg_shared::atom::ATOM_NIL);
521 let t = m.new_var();
522 let mp = &mut *m as *mut Machine;
523 assert_eq!(univ2(mp, t, nil), 0);
524 assert_eq!(
525 msg(&m),
526 "error(domain_error(non_empty_list, []), =../2: list must not be empty)"
527 );
528 }
529
530 #[test]
531 fn copy_term_renames_and_shares() {
532 let mut m = machine();
533 let x = m.new_var();
534 let s = str_term(&mut m, "f", &[x, x]);
536 let c = m.new_var();
537 let mp = &mut *m as *mut Machine;
538 assert_eq!(plg_rt_b_copy_term_2(mp, s, c), 1);
539 let cw = m.deref(c);
540 assert_eq!(tag_of(cw), TAG_STR);
541 let idx = payload(cw) as usize;
542 let a0 = m.deref(m.heap[idx + 1]);
543 let a1 = m.deref(m.heap[idx + 2]);
544 assert_eq!(a0, a1, "shared var preserved");
545 assert_ne!(a0, m.deref(x), "fresh var distinct from original");
546 }
547}