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