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