1use crate::builtins::pred;
12use crate::cell::*;
13use crate::machine::{ContFn, Machine, NO_SITE};
14use crate::solve::call_goal;
15use crate::unify::unify;
16
17fn invoke_k(m: &mut Machine) -> i32 {
19 let k = m.k_fn;
20 let e = m.k_env;
21 unsafe { k(m as *mut Machine, e) }
22}
23
24fn det(m: &mut Machine, ok: bool) -> i32 {
25 if ok { invoke_k(m) } else { 0 }
26}
27
28pub fn try_builtin(m: &mut Machine, name: &str, args_idx: usize, arity: u32) -> Option<i32> {
31 let mp = m as *mut Machine;
32 let a: Vec<Word> = (0..arity as usize).map(|i| m.heap[args_idx + i]).collect();
35 let arg = |i: usize| -> Word { a[i] };
36 let r = match (name, arity) {
37 (",", 2) => conjunction(m, arg(0), arg(1)),
38 (";", 2) => {
39 let lhs = m.deref(arg(0));
41 if tag_of(lhs) == TAG_STR {
42 let idx = payload(lhs) as usize;
43 let (f, n) = unpack_functor(m.heap[idx]);
44 if n == 2 && m.atoms.resolve(f) == "->" {
45 let (c, t) = (m.heap[idx + 1], m.heap[idx + 2]);
46 return Some(if_then_else(m, c, t, Some(arg(1))));
47 }
48 }
49 disjunction(m, arg(0), arg(1))
50 }
51 ("->", 2) => if_then_else(m, arg(0), arg(1), None),
52 ("\\+", 1) => naf(m, arg(0)),
53 ("once", 1) => once(m, arg(0)),
54 ("catch", 3) => catch_impl(m, arg(0), arg(1), arg(2)),
55 ("throw", 1) => {
56 crate::errors::throw_term(m, arg(0));
57 0
58 }
59 ("findall", 3) => findall_impl(m, arg(0), arg(1), arg(2)),
60 ("call", n) if n >= 1 => metacall_extend(m, arg(0), &a[1..]),
61 ("between", 3) => between_impl(m, arg(0), arg(1), arg(2)),
62 ("atom_concat", 3) => atom_concat_impl(m, arg(0), arg(1), arg(2), NO_SITE),
63 ("=", 2) => {
64 let ok = unify(m, arg(0), arg(1));
65 det(m, ok)
66 }
67 ("\\=", 2) => {
68 let ok = pred::plg_rt_b_neq(mp, arg(0), arg(1)) != 0;
69 det(m, ok)
70 }
71 ("is", 2) => {
72 let ok = pred::plg_rt_b_is(mp, arg(0), arg(1), crate::machine::NO_SITE) != 0;
74 det(m, ok)
75 }
76 ("compare", 3) => {
77 let ok = pred::plg_rt_b_compare(mp, arg(0), arg(1), arg(2)) != 0;
78 det(m, ok)
79 }
80 (op, 2) if arith_op(op).is_some() => {
81 let ok = pred::plg_rt_b_arith_cmp(
82 mp,
83 arith_op(op).unwrap(),
84 arg(0),
85 arg(1),
86 crate::machine::NO_SITE,
87 ) != 0;
88 det(m, ok)
89 }
90 (op, 2) if order_op(op).is_some() => {
91 let ok = pred::plg_rt_b_term_cmp(mp, order_op(op).unwrap(), arg(0), arg(1)) != 0;
92 det(m, ok)
93 }
94 _ => {
95 let ok = det_builtin(mp, name, arity, &a)?;
96 det(m, ok)
97 }
98 };
99 Some(r)
100}
101
102fn det_builtin(mp: *mut Machine, name: &str, arity: u32, a: &[Word]) -> Option<bool> {
106 use crate::builtins::{atomops, miscops, sortops, termops, typecheck};
107 let r = match (name, arity) {
108 ("var", 1) => typecheck::plg_rt_b_var_1(mp, a[0]),
109 ("nonvar", 1) => typecheck::plg_rt_b_nonvar_1(mp, a[0]),
110 ("atom", 1) => typecheck::plg_rt_b_atom_1(mp, a[0]),
111 ("number", 1) => typecheck::plg_rt_b_number_1(mp, a[0]),
112 ("integer", 1) => typecheck::plg_rt_b_integer_1(mp, a[0]),
113 ("float", 1) => typecheck::plg_rt_b_float_1(mp, a[0]),
114 ("compound", 1) => typecheck::plg_rt_b_compound_1(mp, a[0]),
115 ("is_list", 1) => typecheck::plg_rt_b_is_list_1(mp, a[0]),
116 ("functor", 3) => termops::plg_rt_b_functor_3(mp, a[0], a[1], a[2], NO_SITE),
118 ("arg", 3) => termops::plg_rt_b_arg_3(mp, a[0], a[1], a[2], NO_SITE),
119 ("=..", 2) => termops::plg_rt_b_univ_2(mp, a[0], a[1], NO_SITE),
120 ("copy_term", 2) => termops::plg_rt_b_copy_term_2(mp, a[0], a[1]),
121 ("atom_length", 2) => atomops::plg_rt_b_atom_length_2(mp, a[0], a[1], NO_SITE),
122 ("atom_chars", 2) => atomops::plg_rt_b_atom_chars_2(mp, a[0], a[1], NO_SITE),
123 ("number_chars", 2) => atomops::plg_rt_b_number_chars_2(mp, a[0], a[1], NO_SITE),
124 ("number_codes", 2) => atomops::plg_rt_b_number_codes_2(mp, a[0], a[1], NO_SITE),
125 ("msort", 2) => sortops::plg_rt_b_msort_2(mp, a[0], a[1], NO_SITE),
126 ("sort", 2) => sortops::plg_rt_b_sort_2(mp, a[0], a[1], NO_SITE),
127 ("succ", 2) => miscops::plg_rt_b_succ_2(mp, a[0], a[1], NO_SITE),
128 ("plus", 3) => miscops::plg_rt_b_plus_3(mp, a[0], a[1], a[2], NO_SITE),
129 ("unify_with_occurs_check", 2) => {
130 miscops::plg_rt_b_unify_with_occurs_check_2(mp, a[0], a[1])
131 }
132 ("write", 1) => miscops::plg_rt_b_write_1(mp, a[0]),
133 ("writeq", 1) => miscops::plg_rt_b_writeq_1(mp, a[0]),
134 ("writeln", 1) => miscops::plg_rt_b_writeln_1(mp, a[0]),
135 _ => return None,
136 };
137 Some(r != 0)
138}
139
140pub fn try_atom_builtin(m: &mut Machine, name: &str) -> Option<i32> {
142 match name {
143 "true" => Some(invoke_k(m)),
144 "nl" => {
145 let ok = crate::builtins::miscops::plg_rt_b_nl_0(m as *mut Machine) != 0;
146 Some(det(m, ok))
147 }
148 "fail" | "false" => Some(0),
149 "!" => {
150 let h = m.qbarrier;
154 m.cut_to(h);
155 Some(invoke_k(m))
156 }
157 _ => None,
158 }
159}
160
161fn arith_op(name: &str) -> Option<i32> {
163 Some(match name {
164 "<" => 0,
165 ">" => 1,
166 "=<" => 2,
167 ">=" => 3,
168 "=:=" => 4,
169 "=\\=" => 5,
170 _ => return None,
171 })
172}
173
174fn order_op(name: &str) -> Option<i32> {
175 Some(match name {
176 "==" => 0,
177 "\\==" => 1,
178 "@<" => 2,
179 "@>" => 3,
180 "@=<" => 4,
181 "@>=" => 5,
182 _ => return None,
183 })
184}
185
186fn save_k(m: &mut Machine, frame: usize, at: usize) {
189 m.heap[frame + at] = m.k_fn as usize as u64;
190 m.heap[frame + at + 1] = m.k_env;
191 m.heap[frame + at + 2] = m.qbarrier as u64;
192}
193
194fn load_k(m: &mut Machine, frame: usize, at: usize) -> (ContFn, u64) {
196 let k: ContFn = unsafe { std::mem::transmute(m.heap[frame + at] as usize) };
197 m.qbarrier = m.heap[frame + at + 2] as usize;
198 (k, m.heap[frame + at + 1])
199}
200
201fn conjunction(m: &mut Machine, a: Word, b: Word) -> i32 {
203 let frame = m.frame_alloc(4);
204 m.heap[frame] = b;
205 save_k(m, frame, 1);
206 m.k_fn = conj_k;
207 m.k_env = frame as u64;
208 call_goal(m, a)
209}
210
211unsafe extern "C" fn conj_k(m: *mut Machine, env: u64) -> i32 {
212 let m = unsafe { &mut *m };
213 let frame = env as usize;
214 let b = m.heap[frame];
215 let (kf, ke) = load_k(m, frame, 1);
216 m.k_fn = kf;
217 m.k_env = ke;
218 call_goal(m, b)
219}
220
221fn disjunction(m: &mut Machine, a: Word, b: Word) -> i32 {
223 let frame = m.frame_alloc(4);
224 m.heap[frame] = b;
225 save_k(m, frame, 1);
226 m.push_cp(disj_retry, frame as u64);
227 call_goal(m, a)
228}
229
230unsafe extern "C" fn disj_retry(m: *mut Machine, env: u64) -> i32 {
231 let m = unsafe { &mut *m };
232 let frame = env as usize;
233 let b = m.heap[frame];
234 let (kf, ke) = load_k(m, frame, 1);
235 m.k_fn = kf;
236 m.k_env = ke;
237 call_goal(m, b)
238}
239
240fn if_then_else(m: &mut Machine, c: Word, t: Word, e: Option<Word>) -> i32 {
242 let h = m.cps.len() as u64; if let Some(e) = e {
244 let ef = m.frame_alloc(4);
245 m.heap[ef] = e;
246 save_k(m, ef, 1);
247 m.push_cp(disj_retry, ef as u64);
248 }
249 let tf = m.frame_alloc(5);
250 m.heap[tf] = t;
251 save_k(m, tf, 1);
252 m.heap[tf + 4] = h;
253 m.k_fn = ite_then;
254 m.k_env = tf as u64;
255 m.qbarrier = m.cps.len();
258 call_goal(m, c)
259}
260
261unsafe extern "C" fn ite_then(m: *mut Machine, env: u64) -> i32 {
262 let m = unsafe { &mut *m };
263 let frame = env as usize;
264 let h = m.heap[frame + 4] as usize;
265 m.cps.truncate(h); let t = m.heap[frame];
267 let (kf, ke) = load_k(m, frame, 1); m.k_fn = kf;
269 m.k_env = ke;
270 call_goal(m, t)
271}
272
273fn once(m: &mut Machine, g: Word) -> i32 {
275 let h = m.cps.len() as u64;
276 let frame = m.frame_alloc(4);
277 save_k(m, frame, 0);
278 m.heap[frame + 3] = h;
279 m.k_fn = once_then;
280 m.k_env = frame as u64;
281 m.qbarrier = m.cps.len(); call_goal(m, g)
283}
284
285unsafe extern "C" fn once_then(m: *mut Machine, env: u64) -> i32 {
286 let m = unsafe { &mut *m };
287 let frame = env as usize;
288 let h = m.heap[frame + 3] as usize;
289 m.cps.truncate(h);
290 let (kf, ke) = load_k(m, frame, 0);
291 m.k_fn = kf;
292 m.k_env = ke;
293 invoke_k(m)
294}
295
296fn naf(m: &mut Machine, g: Word) -> i32 {
299 let h = m.cps.len() as u64;
300 let cf = m.frame_alloc(3);
301 save_k(m, cf, 0);
302 m.push_cp(naf_continue, cf as u64);
303 let ff = m.frame_alloc(1);
304 m.heap[ff] = h;
305 m.k_fn = naf_found;
306 m.k_env = ff as u64;
307 m.qbarrier = m.cps.len();
309 call_goal(m, g)
310}
311
312unsafe extern "C" fn naf_continue(m: *mut Machine, env: u64) -> i32 {
313 let m = unsafe { &mut *m };
314 let frame = env as usize;
315 let (kf, ke) = load_k(m, frame, 0);
316 m.k_fn = kf;
317 m.k_env = ke;
318 invoke_k(m)
319}
320
321unsafe extern "C" fn naf_found(m: *mut Machine, env: u64) -> i32 {
322 let m = unsafe { &mut *m };
323 let h = m.heap[env as usize] as usize;
324 m.cps.truncate(h); 0
326}
327
328fn catch_impl(m: &mut Machine, goal: Word, catcher: Word, recovery: Word) -> i32 {
332 let frame = m.frame_alloc(5);
333 m.heap[frame] = catcher;
334 m.heap[frame + 1] = recovery;
335 save_k(m, frame, 2);
336 m.push_catch_cp(catch_retry, frame as u64);
337 call_goal(m, goal)
338}
339
340unsafe extern "C" fn catch_retry(_m: *mut Machine, _env: u64) -> i32 {
343 0
344}
345
346fn findall_impl(m: &mut Machine, template: Word, goal: Word, bag: Word) -> i32 {
351 let floor = m.cps.len();
352 let tmark = m.trail.len();
353 let hmark = m.heap.len();
354 let saved_k = (m.k_fn, m.k_env, m.qbarrier);
355 m.findall_stack.push(Vec::new());
356 let cf = m.frame_alloc(1);
357 m.heap[cf] = template;
358 m.k_fn = findall_collect;
359 m.k_env = cf as u64;
360 m.qbarrier = m.cps.len(); let r = call_goal(m, goal);
362 crate::solve::drive(m, floor, r); let results = m.findall_stack.pop().unwrap();
364 m.k_fn = saved_k.0;
365 m.k_env = saved_k.1;
366 m.qbarrier = saved_k.2;
367 if m.error.is_some() {
368 return 0;
369 }
370 m.rewind_to(tmark, hmark);
372 let mut w = make_atom(plg_shared::atom::ATOM_NIL);
373 for buf in results.iter().rev() {
374 let e = crate::copyterm::restore_from_buf(m, buf);
375 let idx = m.heap.len();
376 m.heap.push(e);
377 m.heap.push(w);
378 w = make(TAG_LST, idx as u64);
379 }
380 let ok = unify(m, bag, w);
381 det(m, ok)
382}
383
384unsafe extern "C" fn findall_collect(m: *mut Machine, env: u64) -> i32 {
385 let m = unsafe { &mut *m };
386 let template = m.heap[env as usize];
387 let buf = crate::copyterm::copy_to_buf(m, template);
388 m.findall_stack
389 .last_mut()
390 .expect("collector outside findall")
391 .push(buf);
392 0 }
394
395fn metacall_extend(m: &mut Machine, goal: Word, extras: &[Word]) -> i32 {
398 let goal = m.deref(goal);
399 m.qbarrier = m.cps.len();
401 if extras.is_empty() {
402 return call_goal(m, goal);
403 }
404 let extended = match tag_of(goal) {
405 TAG_ATOM => {
406 let f = atom_id(goal);
407 let idx = m.heap.len();
408 m.heap.push(pack_functor(f, extras.len() as u32));
409 m.heap.extend_from_slice(extras);
410 make(TAG_STR, idx as u64)
411 }
412 TAG_STR => {
413 let sidx = payload(goal) as usize;
414 let (f, n) = unpack_functor(m.heap[sidx]);
415 let idx = m.heap.len();
416 m.heap.push(pack_functor(f, n + extras.len() as u32));
417 for i in 0..n as usize {
418 let w = m.heap[sidx + 1 + i];
419 m.heap.push(w);
420 }
421 m.heap.extend_from_slice(extras);
422 make(TAG_STR, idx as u64)
423 }
424 TAG_REF => {
425 crate::errors::instantiation(m, "call/N requires a bound goal");
426 return 0;
427 }
428 _ => {
429 crate::errors::type_error(m, "callable", goal, "Goal is not callable");
430 return 0;
431 }
432 };
433 call_goal(m, extended)
434}
435
436fn between_impl(m: &mut Machine, lo_w: Word, hi_w: Word, x_w: Word) -> i32 {
439 let Some(lo) = int_arg(m, lo_w, "between/3") else {
440 return 0;
441 };
442 let Some(hi) = int_arg(m, hi_w, "between/3") else {
443 return 0;
444 };
445 let x = m.deref(x_w);
446 match tag_of(x) {
447 TAG_INT | TAG_BIG => {
448 let xv = if tag_of(x) == TAG_INT {
449 int_value(x)
450 } else {
451 m.heap[payload(x) as usize] as i64
452 };
453 det(m, lo <= xv && xv <= hi)
454 }
455 TAG_REF => {
456 if lo > hi {
457 return 0;
458 }
459 let frame = m.frame_alloc(6);
463 m.heap[frame] = x;
464 m.heap[frame + 1] = lo as u64;
465 m.heap[frame + 2] = hi as u64;
466 save_k(m, frame, 3);
467 if lo < hi {
468 m.push_cp(between_retry, frame as u64);
469 }
470 let w = int_to_word(m, lo);
471 let ok = unify(m, x, w);
472 debug_assert!(ok, "binding a fresh var cannot fail");
473 invoke_k(m)
474 }
475 _ => {
476 crate::errors::type_error(m, "integer", x, "between/3 requires an integer");
477 0
478 }
479 }
480}
481
482unsafe extern "C" fn between_retry(m: *mut Machine, env: u64) -> i32 {
483 let m = unsafe { &mut *m };
484 let frame = env as usize;
485 let x = m.heap[frame];
486 let cur = m.heap[frame + 1] as i64 + 1;
487 let hi = m.heap[frame + 2] as i64;
488 m.heap[frame + 1] = cur as u64;
489 if cur < hi {
490 m.push_cp(between_retry, frame as u64);
491 }
492 let (kf, ke) = load_k(m, frame, 3);
493 m.k_fn = kf;
494 m.k_env = ke;
495 let w = int_to_word(m, cur);
496 let ok = unify(m, x, w);
497 debug_assert!(ok, "x rewound to unbound before retry");
498 invoke_k(m)
499}
500
501fn int_to_word(m: &mut Machine, n: i64) -> Word {
503 if (INT_MIN..=INT_MAX).contains(&n) {
504 make_int(n)
505 } else {
506 let idx = m.heap.len();
507 m.heap.push(n as u64);
508 make(TAG_BIG, idx as u64)
509 }
510}
511
512fn int_arg(m: &mut Machine, w: Word, who: &str) -> Option<i64> {
515 let w = m.deref(w);
516 match tag_of(w) {
517 TAG_INT => Some(int_value(w)),
518 TAG_BIG => Some(m.heap[payload(w) as usize] as i64),
519 TAG_REF => {
520 let ctx = format!("{who} requires bound integer bounds");
521 crate::errors::instantiation(m, &ctx);
522 None
523 }
524 _ => {
525 let ctx = format!("{who} requires an integer");
526 crate::errors::type_error(m, "integer", w, &ctx);
527 None
528 }
529 }
530}
531
532#[unsafe(no_mangle)]
537pub unsafe extern "C" fn plg_rt_pred_between_3(m: *mut Machine, _env: u64) -> i32 {
538 let m = unsafe { &mut *m };
539 let (lo, hi, x) = (m.areg[0], m.areg[1], m.areg[2]);
540 between_impl(m, lo, hi, x)
541}
542
543fn atom_str_opt(m: &Machine, w: Word) -> Option<String> {
546 (tag_of(w) == TAG_ATOM).then(|| m.atoms.resolve(atom_id(w)).to_string())
547}
548
549fn bind_split(m: &mut Machine, a_w: Word, b_w: Word, sc: &str, i: usize) -> bool {
555 let byte = sc.char_indices().nth(i).map_or(sc.len(), |(b, _)| b);
556 let (pre, suf) = sc.split_at(byte);
557 let pid = m.atoms.intern(pre);
558 let sid = m.atoms.intern(suf);
559 unify(m, a_w, make_atom(pid)) && unify(m, b_w, make_atom(sid))
560}
561
562fn atom_concat_impl(m: &mut Machine, a_w: Word, b_w: Word, c_w: Word, site: u32) -> i32 {
570 m.error_site = site;
571 let wa = m.deref(a_w);
572 let wb = m.deref(b_w);
573 let a_atom = atom_str_opt(m, wa);
574 let b_atom = atom_str_opt(m, wb);
575 if a_atom.is_none() && tag_of(wa) != TAG_REF {
578 crate::errors::type_error(m, "atom", wa, "atom_concat/3: arguments must be atoms");
579 return 0;
580 }
581 if b_atom.is_none() && tag_of(wb) != TAG_REF {
582 crate::errors::type_error(m, "atom", wb, "atom_concat/3: arguments must be atoms");
583 return 0;
584 }
585 if let (Some(sa), Some(sb)) = (&a_atom, &b_atom) {
587 let id = m.atoms.intern(&format!("{sa}{sb}"));
588 m.error_site = NO_SITE;
589 let ok = unify(m, c_w, make_atom(id));
590 return det(m, ok);
591 }
592 let wc = m.deref(c_w);
594 let Some(sc) = atom_str_opt(m, wc) else {
595 if tag_of(wc) == TAG_REF {
596 crate::errors::instantiation(
598 m,
599 "atom_concat/3: arguments not sufficiently instantiated",
600 );
601 } else {
602 crate::errors::type_error(m, "atom", wc, "atom_concat/3: arguments must be atoms");
603 }
604 return 0;
605 };
606 m.error_site = NO_SITE; if let Some(sa) = &a_atom {
609 return match sc.strip_prefix(sa.as_str()) {
610 Some(rest) => {
611 let id = m.atoms.intern(rest);
612 let ok = unify(m, b_w, make_atom(id));
613 det(m, ok)
614 }
615 None => 0,
616 };
617 }
618 if let Some(sb) = &b_atom {
620 return match sc.strip_suffix(sb.as_str()) {
621 Some(pre) => {
622 let id = m.atoms.intern(pre);
623 let ok = unify(m, a_w, make_atom(id));
624 det(m, ok)
625 }
626 None => 0,
627 };
628 }
629 let nchars = sc.chars().count();
632 let frame = m.frame_alloc(8);
633 m.heap[frame] = a_w;
634 m.heap[frame + 1] = b_w;
635 m.heap[frame + 2] = atom_id(wc) as u64;
636 m.heap[frame + 3] = 0;
637 m.heap[frame + 4] = nchars as u64;
638 save_k(m, frame, 5);
639 if nchars >= 1 {
640 m.push_cp(atom_concat_retry, frame as u64);
641 }
642 if bind_split(m, a_w, b_w, &sc, 0) {
643 invoke_k(m)
644 } else {
645 0 }
647}
648
649unsafe extern "C" fn atom_concat_retry(m: *mut Machine, env: u64) -> i32 {
650 let m = unsafe { &mut *m };
651 let frame = env as usize;
652 let cur = m.heap[frame + 3] as usize + 1;
653 let nchars = m.heap[frame + 4] as usize;
654 m.heap[frame + 3] = cur as u64;
655 if cur < nchars {
656 m.push_cp(atom_concat_retry, frame as u64);
657 }
658 let (kf, ke) = load_k(m, frame, 5);
659 m.k_fn = kf;
660 m.k_env = ke;
661 let (a_w, b_w) = (m.heap[frame], m.heap[frame + 1]);
662 let sc = m.atoms.resolve(m.heap[frame + 2] as u32).to_string();
663 if bind_split(m, a_w, b_w, &sc, cur) {
664 invoke_k(m)
665 } else {
666 0
667 }
668}
669
670#[unsafe(no_mangle)]
676pub unsafe extern "C" fn plg_rt_pred_atom_concat_3(m: *mut Machine, env: u64) -> i32 {
677 let m = unsafe { &mut *m };
678 let (a, b, c) = (m.areg[0], m.areg[1], m.areg[2]);
679 atom_concat_impl(m, a, b, c, env as u32)
680}
681
682#[unsafe(no_mangle)]
686pub unsafe extern "C" fn plg_rt_metacall(m: *mut Machine, goal: u64) -> i32 {
687 let m = unsafe { &mut *m };
688 m.qbarrier = m.cps.len();
691 call_goal(m, goal)
692}
693
694#[unsafe(no_mangle)]
711pub unsafe extern "C" fn plg_rt_metacall_resolve(m: *mut Machine, goal: u64) -> u64 {
712 let m = unsafe { &mut *m };
713 m.qbarrier = m.cps.len();
714 match resolve_goal_ptr(m, goal) {
715 Some(f) => f as usize as u64,
716 None => 0,
717 }
718}
719
720fn resolve_goal_ptr(m: &mut Machine, mut goal: Word) -> Option<ContFn> {
721 loop {
722 goal = m.deref(goal);
723 match tag_of(goal) {
724 TAG_ATOM => return crate::solve::resolve_simple(m, atom_id(goal), 0, 0),
725 TAG_STR => {
726 let idx = payload(goal) as usize;
727 let (f, n) = unpack_functor(m.heap[idx]);
728 if n == 1 && m.atoms.resolve(f) == "call" {
734 goal = m.heap[idx + 1];
735 continue;
736 }
737 return crate::solve::resolve_simple(m, f, n, idx + 1);
738 }
739 _ => return None,
740 }
741 }
742}
743
744#[unsafe(no_mangle)]
747pub unsafe extern "C" fn plg_rt_b_catch_3(m: *mut Machine, g: u64, c: u64, r: u64) -> i32 {
748 catch_impl(unsafe { &mut *m }, g, c, r)
749}
750
751#[unsafe(no_mangle)]
754pub unsafe extern "C" fn plg_rt_b_throw_1(m: *mut Machine, ball: u64) -> i32 {
755 crate::errors::throw_term(unsafe { &mut *m }, ball);
756 0
757}
758
759#[unsafe(no_mangle)]
762pub unsafe extern "C" fn plg_rt_b_findall_3(m: *mut Machine, t: u64, g: u64, b: u64) -> i32 {
763 findall_impl(unsafe { &mut *m }, t, g, b)
764}
765
766#[cfg(test)]
767mod tests {
768 use crate::machine::Machine;
769 use crate::query::parse_query;
770 use crate::solve::{Outcome, solve};
771 use plg_shared::StringInterner;
772
773 fn run(query: &str) -> (Vec<String>, Option<String>) {
774 let mut m = Machine::new(StringInterner::new(), Vec::new());
775 let goal = parse_query(&mut m, query).unwrap();
776 let outcome = solve(&mut m, goal);
777 let err = match outcome {
778 Outcome::Error => Some(m.error.take().unwrap().message),
779 Outcome::Done => None,
780 };
781 let sols = m
782 .solutions
783 .iter()
784 .map(|s| {
785 s.bindings
786 .iter()
787 .map(|b| format!("{}={}", b.name, b.text))
788 .collect::<Vec<_>>()
789 .join(",")
790 })
791 .collect();
792 (sols, err)
793 }
794
795 #[test]
796 fn top_level_is_and_comparison() {
797 assert_eq!(run("X is 2 + 3 * 4").0, vec!["X=14"]);
798 assert_eq!(run("1 < 2").0, vec![""]);
799 assert_eq!(run("2 < 1").0, Vec::<String>::new());
800 }
801
802 #[test]
803 fn top_level_disjunction_enumerates() {
804 assert_eq!(run("(X = 1 ; X = 2)").0, vec!["X=1", "X=2"]);
805 }
806
807 #[test]
808 fn top_level_ite_and_naf() {
809 assert_eq!(run("(1 < 2 -> X = yes ; X = no)").0, vec!["X=yes"]);
810 assert_eq!(run("(2 < 1 -> X = yes ; X = no)").0, vec!["X=no"]);
811 assert_eq!(run("\\+ 2 < 1").0, vec![""]);
812 assert_eq!(run("\\+ 1 < 2").0, Vec::<String>::new());
813 assert_eq!(run("\\+ (X = 1, 2 < 1), X = ok").0, vec!["X=ok"]);
815 }
816
817 #[test]
818 fn top_level_once_commits() {
819 assert_eq!(run("once((X = 1 ; X = 2))").0, vec!["X=1"]);
820 }
821
822 #[test]
823 fn errors_propagate() {
824 let (_, err) = run("X is 1 // 0");
825 assert!(err.unwrap().contains("zero_divisor"));
826 }
827}
828
829#[cfg(test)]
830mod m4_tests {
831 use crate::machine::Machine;
832 use crate::query::parse_query;
833 use crate::solve::{Outcome, solve};
834 use plg_shared::StringInterner;
835
836 fn run(query: &str) -> (Vec<String>, Option<String>) {
837 let mut m = Machine::new(StringInterner::new(), Vec::new());
838 let goal = parse_query(&mut m, query).unwrap();
839 let outcome = solve(&mut m, goal);
840 let err = match outcome {
841 Outcome::Error => Some(m.error.take().unwrap().message),
842 Outcome::Done => None,
843 };
844 let sols = m
845 .solutions
846 .iter()
847 .map(|s| {
848 s.bindings
849 .iter()
850 .map(|b| format!("{}={}", b.name, b.text))
851 .collect::<Vec<_>>()
852 .join(",")
853 })
854 .collect();
855 (sols, err)
856 }
857
858 #[test]
859 fn throw_uncaught_propagates_with_rendered_ball() {
860 let (sols, err) = run("throw(my_ball)");
861 assert!(sols.is_empty());
862 assert_eq!(err.unwrap(), "my_ball");
863 }
864
865 #[test]
866 fn catch_catches_matching_ball_and_runs_recovery() {
867 let (sols, err) = run("catch(throw(oops(1)), oops(N), X = caught(N))");
868 assert!(err.is_none());
869 assert_eq!(sols, vec!["N=1,X=caught(1)"]);
870 }
871
872 #[test]
873 fn catch_passes_nonmatching_ball_outward() {
874 let (sols, err) = run("catch(throw(other), oops(_), X = no)");
875 assert!(sols.is_empty());
876 assert_eq!(err.unwrap(), "other");
877 }
878
879 #[test]
880 fn nested_catch_inner_first() {
881 let (sols, err) = run("catch(catch(throw(b), a, X = inner_a), b, X = outer_b)");
882 assert!(err.is_none());
883 assert_eq!(sols, vec!["X=outer_b"]);
884 }
885
886 #[test]
887 fn catch_traps_builtin_errors() {
888 let (sols, err) = run("catch(X is 1 // 0, error(evaluation_error(E), _), Y = E)");
889 assert!(err.is_none());
890 assert_eq!(sols, vec!["E=zero_divisor,X=_0,Y=zero_divisor"]);
891 }
892
893 #[test]
894 fn catch_is_transparent_to_normal_backtracking() {
895 let (sols, err) = run("catch((X = 1 ; X = 2), _, fail)");
896 assert!(err.is_none());
897 assert_eq!(sols, vec!["X=1", "X=2"]);
898 }
899
900 #[test]
901 fn step_limit_is_not_catchable() {
902 let mut m = Machine::new(StringInterner::new(), Vec::new());
903 m.step_limit = 1;
904 m.steps = 1; assert!(!m.step());
906 let goal = parse_query(&mut m, "catch(true, _, true)").unwrap();
907 assert!(matches!(solve(&mut m, goal), Outcome::Error));
909 assert!(m.error.as_ref().unwrap().uncatchable);
910 }
911
912 #[test]
913 fn findall_collects_and_rewinds() {
914 let (sols, err) = run("findall(X, (X = 1 ; X = 2 ; X = 3), L)");
915 assert!(err.is_none());
916 assert_eq!(sols, vec!["L=[1, 2, 3],X=_0"]);
917 }
918
919 #[test]
920 fn findall_empty_on_failure() {
921 let (sols, err) = run("findall(X, fail, L)");
922 assert!(err.is_none());
923 assert_eq!(sols, vec!["L=[],X=_0"]);
924 }
925
926 #[test]
927 fn findall_propagates_errors() {
928 let (sols, err) = run("findall(X, throw(bad), L)");
929 assert!(sols.is_empty());
930 assert_eq!(err.unwrap(), "bad");
931 }
932
933 #[test]
934 fn nested_findall() {
935 let (sols, err) = run("findall(L1, (Y = 2, findall(X, (X = 1 ; X = Y), L1)), L)");
936 assert!(err.is_none());
937 assert_eq!(sols.len(), 1);
938 assert!(sols[0].contains("L=[[1, 2]]"), "{sols:?}");
939 }
940
941 #[test]
942 fn call_n_extends_goals() {
943 let (sols, err) = run("call(=, X, 7)");
944 assert!(err.is_none());
945 assert_eq!(sols, vec!["X=7"]);
946 let (sols, _) = run("G = (X = 1 ; X = 2), call(G)");
947 assert_eq!(sols.len(), 2);
948 }
949
950 #[test]
951 fn call_unbound_is_instantiation_error() {
952 let (sols, err) = run("call(X)");
953 assert!(sols.is_empty());
954 assert!(err.unwrap().contains("instantiation_error"));
955 }
956
957 #[test]
958 fn between_enumerates_and_checks() {
959 let (sols, err) = run("between(1, 4, X)");
960 assert!(err.is_none());
961 assert_eq!(sols, vec!["X=1", "X=2", "X=3", "X=4"]);
962 let (sols, _) = run("between(1, 4, 3)");
963 assert_eq!(sols.len(), 1);
964 let (sols, _) = run("between(1, 4, 9)");
965 assert!(sols.is_empty());
966 let (sols, _) = run("between(3, 1, X)");
967 assert!(sols.is_empty());
968 let (sols, _) = run("between(2, 2, X)");
970 assert_eq!(sols, vec!["X=2"]);
971 }
972
973 #[test]
974 fn findall_with_between() {
975 let (sols, err) = run("findall(X, between(1, 5, X), L)");
976 assert!(err.is_none());
977 assert_eq!(sols, vec!["L=[1, 2, 3, 4, 5],X=_0"]);
978 }
979}
980
981#[cfg(test)]
982mod vocab_invariant {
983 use plg_shared::{BUILTINS, builtins::BuiltinKind};
991 use std::collections::BTreeSet;
992
993 #[rustfmt::skip]
996 const DET_DISPATCH: &[(&str, u32)] = &[
997 ("var", 1), ("nonvar", 1), ("atom", 1), ("number", 1), ("integer", 1),
998 ("float", 1), ("compound", 1), ("is_list", 1), ("functor", 3), ("arg", 3),
999 ("=..", 2), ("copy_term", 2), ("atom_length", 2),
1000 ("atom_chars", 2), ("number_chars", 2), ("number_codes", 2), ("msort", 2),
1001 ("sort", 2), ("succ", 2), ("plus", 3), ("unify_with_occurs_check", 2),
1002 ("write", 1), ("writeq", 1), ("writeln", 1),
1003 ];
1004
1005 #[test]
1006 fn det_dispatch_equals_shared_det_subset() {
1007 let dispatch: BTreeSet<(&str, u32)> = DET_DISPATCH.iter().copied().collect();
1008 let shared_det: BTreeSet<(&str, u32)> = BUILTINS
1009 .iter()
1010 .filter(|s| s.kind == BuiltinKind::Det && s.arity > 0)
1011 .map(|s| (s.name, s.arity))
1012 .collect();
1013 assert_eq!(
1014 dispatch, shared_det,
1015 "runtime det dispatch diverges from BUILTINS Det subset \
1016 (left = control.rs, right = shared table)"
1017 );
1018 }
1019}