Skip to main content

patch_prolog_core/
solver.rs

1use crate::builtins::{
2    build_list, collect_list, exec_builtin, is_builtin, term_compare, BuiltinResult,
3};
4use crate::database::CompiledDatabase;
5use crate::term::{Clause, Term, VarId};
6use crate::unify::Substitution;
7use fnv::FnvHashMap;
8use serde::{Deserialize, Serialize};
9use std::collections::VecDeque;
10use std::io::Write as IoWrite;
11
12/// Format a float for number_chars/number_codes, ensuring ".0" suffix for whole numbers.
13fn format_float(f: f64) -> String {
14    if f.is_nan() || f.is_infinite() {
15        return format!("{}", f);
16    }
17    let s = format!("{}", f);
18    if s.contains('.') || s.contains('e') || s.contains('E') {
19        s
20    } else {
21        format!("{}.0", s)
22    }
23}
24
25/// A solution: variable name -> resolved term.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Solution {
28    pub bindings: Vec<(String, Term)>,
29}
30
31/// Result of solving a query.
32#[derive(Debug)]
33pub enum SolveResult {
34    /// Query succeeded with a solution.
35    Success(Solution),
36    /// No (more) solutions.
37    Failure,
38    /// A runtime error occurred.
39    Error(String),
40}
41
42/// Choice point for backtracking.
43struct ChoicePoint {
44    /// Goal list at the time this choice was created.
45    goals: VecDeque<Term>,
46    /// Remaining untried clause indices.
47    untried: Vec<usize>,
48    /// Trail mark for undoing substitution bindings.
49    trail_mark: usize,
50    /// Variable counter at the time this choice was created.
51    var_counter: VarId,
52    /// Cut barrier: if true, backtracking past this point is blocked.
53    cut_barrier: bool,
54    /// Whether this is a disjunction choice point (alternative branch).
55    disjunction: bool,
56}
57
58pub struct Solver<'a> {
59    db: &'a CompiledDatabase,
60    subst: Substitution,
61    var_counter: VarId,
62    query_vars: FnvHashMap<String, VarId>,
63    choice_stack: Vec<ChoicePoint>,
64    /// Limit on number of solutions to return.
65    limit: Option<usize>,
66    solutions_found: usize,
67    /// Current number of goal resolution steps.
68    steps: usize,
69    /// Maximum allowed steps before returning an error.
70    max_depth: usize,
71    /// Mutable interner for runtime atom creation (atom_concat, atom_chars, etc.).
72    interner: crate::term::StringInterner,
73    /// Flag: cut was triggered inside try_solve_once, prevents clause alternatives.
74    cut_in_try_solve: bool,
75    /// Flag: step limit was exceeded inside try_solve_once/try_solve_collecting.
76    steps_exceeded: bool,
77}
78
79/// Find the maximum variable ID in a term.
80fn max_var_in_term(term: &Term) -> Option<VarId> {
81    match term {
82        Term::Var(id) => Some(*id),
83        Term::Atom(_) | Term::Integer(_) | Term::Float(_) => None,
84        Term::Compound { args, .. } => args.iter().filter_map(max_var_in_term).max(),
85        Term::List { head, tail } => {
86            let h = max_var_in_term(head);
87            let t = max_var_in_term(tail);
88            h.max(t)
89        }
90    }
91}
92
93impl<'a> Solver<'a> {
94    /// Create a new solver for a query against a compiled database.
95    pub fn new(
96        db: &'a CompiledDatabase,
97        goals: Vec<Term>,
98        query_vars: FnvHashMap<String, VarId>,
99    ) -> Self {
100        // Start var counter above all variable IDs in query (including anonymous _)
101        let max_from_vars = query_vars.values().copied().max().unwrap_or(0);
102        let max_from_goals = goals.iter().filter_map(max_var_in_term).max().unwrap_or(0);
103        let initial_var_counter = max_from_vars.max(max_from_goals) + 1;
104        let mut solver = Solver {
105            interner: db.interner.clone(),
106            db,
107            subst: Substitution::new(),
108            var_counter: initial_var_counter,
109            query_vars,
110            choice_stack: Vec::new(),
111            limit: None,
112            solutions_found: 0,
113            steps: 0,
114            max_depth: 10_000,
115            cut_in_try_solve: false,
116            steps_exceeded: false,
117        };
118        // Push the initial goal list as a choice point with no alternatives
119        // (this is just to set up the initial state; the real solving starts in next())
120        solver.choice_stack.push(ChoicePoint {
121            goals: VecDeque::from(goals),
122            untried: vec![],
123            trail_mark: 0,
124            var_counter: initial_var_counter,
125            cut_barrier: false,
126            disjunction: false,
127        });
128        solver
129    }
130
131    pub fn with_limit(mut self, limit: usize) -> Self {
132        self.limit = Some(limit);
133        self
134    }
135
136    pub fn with_max_depth(mut self, max_depth: usize) -> Self {
137        self.max_depth = max_depth;
138        self
139    }
140
141    /// Get the solver's interner (includes any runtime-created atoms).
142    pub fn interner(&self) -> &crate::term::StringInterner {
143        &self.interner
144    }
145
146    /// Get the next solution (or failure/error).
147    pub fn next(&mut self) -> SolveResult {
148        if let Some(limit) = self.limit {
149            if self.solutions_found >= limit {
150                return SolveResult::Failure;
151            }
152        }
153
154        // If this is the first call, start from the initial choice point's goals
155        if self.solutions_found == 0 && !self.choice_stack.is_empty() {
156            let initial = self.choice_stack.pop().unwrap();
157            self.var_counter = initial.var_counter;
158            return self.solve(initial.goals);
159        }
160
161        // Otherwise, backtrack to find the next solution
162        self.backtrack()
163    }
164
165    /// Enumerate all solutions.
166    pub fn all_solutions(self) -> Result<Vec<Solution>, String> {
167        self.all_solutions_with_interner().map(|(sols, _)| sols)
168    }
169
170    /// Enumerate all solutions, returning the interner for term display.
171    pub fn all_solutions_with_interner(
172        mut self,
173    ) -> Result<(Vec<Solution>, crate::term::StringInterner), String> {
174        let mut solutions = Vec::new();
175        loop {
176            match self.next() {
177                SolveResult::Success(sol) => solutions.push(sol),
178                SolveResult::Failure => return Ok((solutions, self.interner)),
179                SolveResult::Error(e) => return Err(e),
180            }
181        }
182    }
183
184    /// Core solve loop: process goals one at a time.
185    fn solve(&mut self, mut goals: VecDeque<Term>) -> SolveResult {
186        loop {
187            if goals.is_empty() {
188                // Success! Extract the solution.
189                self.solutions_found += 1;
190                return SolveResult::Success(self.extract_solution());
191            }
192
193            self.steps += 1;
194            if self.steps > self.max_depth {
195                return SolveResult::Error(format!(
196                    "Maximum step limit exceeded ({})",
197                    self.max_depth
198                ));
199            }
200
201            let goal = goals.pop_front().unwrap();
202            let walked_goal = self.subst.walk(&goal);
203
204            if is_builtin(&walked_goal, &self.interner) {
205                match exec_builtin(&walked_goal, &mut self.subst, &self.interner) {
206                    Ok(BuiltinResult::Success) => {
207                        // Continue with remaining goals
208                        continue;
209                    }
210                    Ok(BuiltinResult::Failure) => {
211                        return self.backtrack();
212                    }
213                    Ok(BuiltinResult::Cut) => {
214                        // Remove all choice points up to and including the nearest cut barrier
215                        while let Some(cp) = self.choice_stack.pop() {
216                            if cp.cut_barrier {
217                                break;
218                            }
219                        }
220                        // Continue with remaining goals
221                        continue;
222                    }
223                    Ok(BuiltinResult::NegationAsFailure(inner_goal)) => {
224                        // Try to solve the inner goal
225                        let mark = self.subst.trail_mark();
226                        let saved_counter = self.var_counter;
227                        let saved_stack_len = self.choice_stack.len();
228
229                        // Create a mini-solver for the inner goal
230                        let inner_result = self.try_solve_once(vec![inner_goal]);
231
232                        // Restore state regardless of outcome
233                        self.subst.undo_to(mark);
234                        self.var_counter = saved_counter;
235                        self.choice_stack.truncate(saved_stack_len);
236                        self.cut_in_try_solve = false;
237
238                        // Check if step limit was hit (don't treat timeout as failure)
239                        if self.steps_exceeded {
240                            self.steps_exceeded = false;
241                            return SolveResult::Error(format!(
242                                "Maximum step limit exceeded ({}) inside negation",
243                                self.max_depth
244                            ));
245                        }
246
247                        if inner_result {
248                            // Inner goal succeeded, so \+ fails
249                            return self.backtrack();
250                        } else {
251                            // Inner goal failed, so \+ succeeds
252                            continue;
253                        }
254                    }
255                    Ok(BuiltinResult::Disjunction(left, right)) => {
256                        // Try left first; push choice point for right
257                        let mark = self.subst.trail_mark();
258                        let saved_counter = self.var_counter;
259
260                        // Build the alternative goal list: right + remaining goals
261                        let mut alt_goals = VecDeque::from(vec![right]);
262                        alt_goals.extend(goals.iter().cloned());
263
264                        // Push a disjunction choice point
265                        self.choice_stack.push(ChoicePoint {
266                            goals: alt_goals,
267                            untried: vec![],
268                            trail_mark: mark,
269                            var_counter: saved_counter,
270                            cut_barrier: false,
271                            disjunction: true,
272                        });
273
274                        // Continue with left branch + remaining goals
275                        goals.push_front(left);
276                        continue;
277                    }
278                    Ok(BuiltinResult::IfThenElse(cond, then, else_branch)) => {
279                        // Try cond; if succeeds, commit to then (cut alternatives)
280                        // If cond fails, try else_branch
281                        let mark = self.subst.trail_mark();
282                        let saved_counter = self.var_counter;
283                        let saved_stack_len = self.choice_stack.len();
284
285                        let cond_result = self.try_solve_once(vec![cond]);
286                        self.cut_in_try_solve = false;
287
288                        if self.steps_exceeded {
289                            self.steps_exceeded = false;
290                            return SolveResult::Error(format!(
291                                "Maximum step limit exceeded ({})",
292                                self.max_depth
293                            ));
294                        }
295
296                        if cond_result {
297                            // Cond succeeded — keep bindings, truncate only choice stack
298                            self.choice_stack.truncate(saved_stack_len);
299                            goals.push_front(then);
300                            continue;
301                        } else {
302                            // Cond failed — restore and try else
303                            self.subst.undo_to(mark);
304                            self.var_counter = saved_counter;
305                            self.choice_stack.truncate(saved_stack_len);
306
307                            goals.push_front(else_branch);
308                            continue;
309                        }
310                    }
311                    Ok(BuiltinResult::IfThen(cond, then)) => {
312                        // Like if-then-else but no else (fails if cond fails)
313                        let mark = self.subst.trail_mark();
314                        let saved_counter = self.var_counter;
315                        let saved_stack_len = self.choice_stack.len();
316
317                        let cond_result = self.try_solve_once(vec![cond]);
318                        self.cut_in_try_solve = false;
319
320                        if self.steps_exceeded {
321                            self.steps_exceeded = false;
322                            return SolveResult::Error(format!(
323                                "Maximum step limit exceeded ({})",
324                                self.max_depth
325                            ));
326                        }
327
328                        if cond_result {
329                            // Cond succeeded — keep bindings, truncate only choice stack
330                            self.choice_stack.truncate(saved_stack_len);
331                            goals.push_front(then);
332                            continue;
333                        } else {
334                            self.subst.undo_to(mark);
335                            self.var_counter = saved_counter;
336                            self.choice_stack.truncate(saved_stack_len);
337                            return self.backtrack();
338                        }
339                    }
340                    Ok(BuiltinResult::Conjunction(a, b)) => {
341                        // Flatten conjunction into goal list
342                        goals.push_front(b);
343                        goals.push_front(a);
344                        continue;
345                    }
346                    Ok(BuiltinResult::FindAll(template, goal, result_var)) => {
347                        match self.exec_findall(template, goal) {
348                            Ok(result_list) => {
349                                if self.subst.unify(&result_var, &result_list) {
350                                    continue;
351                                } else {
352                                    return self.backtrack();
353                                }
354                            }
355                            Err(e) => return SolveResult::Error(e),
356                        }
357                    }
358                    Ok(BuiltinResult::Once(inner_goal)) => {
359                        // once/1: solve inner goal, keep bindings from first success,
360                        // remove any choice points created by the inner goal.
361                        let saved_stack_len = self.choice_stack.len();
362
363                        let once_result = self.try_solve_once(vec![inner_goal]);
364                        self.cut_in_try_solve = false;
365
366                        if self.steps_exceeded {
367                            self.steps_exceeded = false;
368                            self.choice_stack.truncate(saved_stack_len);
369                            return SolveResult::Error(format!(
370                                "Maximum step limit exceeded ({})",
371                                self.max_depth
372                            ));
373                        }
374
375                        if once_result {
376                            self.choice_stack.truncate(saved_stack_len);
377                            continue;
378                        } else {
379                            self.choice_stack.truncate(saved_stack_len);
380                            return self.backtrack();
381                        }
382                    }
383                    Ok(BuiltinResult::Call(inner_goal)) => {
384                        // call/1: just execute the term as a goal
385                        let walked = self.subst.walk(&inner_goal);
386                        goals.push_front(walked);
387                        continue;
388                    }
389                    Ok(BuiltinResult::AtomLength(atom_arg, len_arg)) => {
390                        let walked = self.subst.walk(&atom_arg);
391                        if let Term::Atom(id) = walked {
392                            let name_str = self.interner.resolve(id);
393                            let len = name_str.chars().count() as i64;
394                            if self.subst.unify(&len_arg, &Term::Integer(len)) {
395                                continue;
396                            } else {
397                                return self.backtrack();
398                            }
399                        } else {
400                            return SolveResult::Error(
401                                "atom_length/2: first argument must be an atom".to_string(),
402                            );
403                        }
404                    }
405                    Ok(BuiltinResult::AtomConcat(a_arg, b_arg, result_arg)) => {
406                        let a = self.subst.walk(&a_arg);
407                        let b = self.subst.walk(&b_arg);
408                        if let (Term::Atom(id_a), Term::Atom(id_b)) = (&a, &b) {
409                            let s = format!(
410                                "{}{}",
411                                self.interner.resolve(*id_a),
412                                self.interner.resolve(*id_b)
413                            );
414                            let result_id = self.interner.intern(&s);
415                            if self.subst.unify(&result_arg, &Term::Atom(result_id)) {
416                                continue;
417                            } else {
418                                return self.backtrack();
419                            }
420                        } else {
421                            return SolveResult::Error(
422                                "atom_concat/3: first two arguments must be atoms".to_string(),
423                            );
424                        }
425                    }
426                    Ok(BuiltinResult::AtomChars(atom_arg, list_arg)) => {
427                        let walked = self.subst.walk(&atom_arg);
428                        if let Term::Atom(id) = walked {
429                            // Forward: atom -> char list
430                            let name_str = self.interner.resolve(id).to_string();
431                            let nil_id = self.interner.lookup("[]").expect("[] must be interned");
432                            let mut list = Term::Atom(nil_id);
433                            for ch in name_str.chars().rev() {
434                                let ch_id = self.interner.intern(&ch.to_string());
435                                list = Term::List {
436                                    head: Box::new(Term::Atom(ch_id)),
437                                    tail: Box::new(list),
438                                };
439                            }
440                            if self.subst.unify(&list_arg, &list) {
441                                continue;
442                            } else {
443                                return self.backtrack();
444                            }
445                        } else if let Term::Var(_) = walked {
446                            // Reverse: char list -> atom
447                            let wlist = self.subst.apply(&list_arg);
448                            if let Some(elems) = collect_list(&wlist, &self.interner) {
449                                let s: Option<String> = elems
450                                    .iter()
451                                    .map(|e| {
452                                        if let Term::Atom(id) = e {
453                                            let ch = self.interner.resolve(*id);
454                                            if ch.chars().count() == 1 {
455                                                Some(ch.to_string())
456                                            } else {
457                                                None
458                                            }
459                                        } else {
460                                            None
461                                        }
462                                    })
463                                    .collect();
464                                if let Some(s) = s {
465                                    let atom_id = self.interner.intern(&s);
466                                    if self.subst.unify(&atom_arg, &Term::Atom(atom_id)) {
467                                        continue;
468                                    }
469                                }
470                                return self.backtrack();
471                            }
472                            return SolveResult::Error(
473                                "atom_chars/2: second argument must be a character list"
474                                    .to_string(),
475                            );
476                        } else {
477                            return SolveResult::Error(
478                                "atom_chars/2: first argument must be an atom or variable"
479                                    .to_string(),
480                            );
481                        }
482                    }
483                    Ok(BuiltinResult::Write(term)) => {
484                        let resolved = self.subst.apply(&term);
485                        let s = term_to_string(&resolved, &self.interner);
486                        print!("{}", s);
487                        let _ = std::io::stdout().flush();
488                        continue;
489                    }
490                    Ok(BuiltinResult::Writeln(term)) => {
491                        let resolved = self.subst.apply(&term);
492                        let s = term_to_string(&resolved, &self.interner);
493                        println!("{}", s);
494                        continue;
495                    }
496                    Ok(BuiltinResult::Nl) => {
497                        println!();
498                        continue;
499                    }
500                    Ok(BuiltinResult::Compare(order_arg, t1, t2)) => {
501                        let w1 = self.subst.apply(&t1);
502                        let w2 = self.subst.apply(&t2);
503                        let cmp = term_compare(&w1, &w2, &self.interner);
504                        let order_name = match cmp {
505                            std::cmp::Ordering::Less => "<",
506                            std::cmp::Ordering::Equal => "=",
507                            std::cmp::Ordering::Greater => ">",
508                        };
509                        let order_id = self.interner.intern(order_name);
510                        if self.subst.unify(&order_arg, &Term::Atom(order_id)) {
511                            continue;
512                        } else {
513                            return self.backtrack();
514                        }
515                    }
516                    Ok(BuiltinResult::Functor(term_arg, name_arg, arity_arg)) => {
517                        let walked = self.subst.walk(&term_arg);
518                        match &walked {
519                            Term::Atom(id) => {
520                                // functor(atom, atom, 0)
521                                if self.subst.unify(&name_arg, &Term::Atom(*id))
522                                    && self.subst.unify(&arity_arg, &Term::Integer(0))
523                                {
524                                    continue;
525                                }
526                                return self.backtrack();
527                            }
528                            Term::Integer(_) | Term::Float(_) => {
529                                if self.subst.unify(&name_arg, &walked)
530                                    && self.subst.unify(&arity_arg, &Term::Integer(0))
531                                {
532                                    continue;
533                                }
534                                return self.backtrack();
535                            }
536                            Term::Compound { functor, args } => {
537                                if self.subst.unify(&name_arg, &Term::Atom(*functor))
538                                    && self
539                                        .subst
540                                        .unify(&arity_arg, &Term::Integer(args.len() as i64))
541                                {
542                                    continue;
543                                }
544                                return self.backtrack();
545                            }
546                            Term::List { .. } => {
547                                // Lists are ./2
548                                let dot_id = self.interner.intern(".");
549                                if self.subst.unify(&name_arg, &Term::Atom(dot_id))
550                                    && self.subst.unify(&arity_arg, &Term::Integer(2))
551                                {
552                                    continue;
553                                }
554                                return self.backtrack();
555                            }
556                            Term::Var(_) => {
557                                // First arg is a variable — try to construct from name+arity
558                                let wname = self.subst.walk(&name_arg);
559                                let warity = self.subst.walk(&arity_arg);
560                                if let (Term::Atom(name_id), Term::Integer(0)) = (&wname, &warity) {
561                                    if self.subst.unify(&term_arg, &Term::Atom(*name_id)) {
562                                        continue;
563                                    }
564                                    return self.backtrack();
565                                }
566                                if let (Term::Integer(_), Term::Integer(0)) = (&wname, &warity) {
567                                    if self.subst.unify(&term_arg, &wname) {
568                                        continue;
569                                    }
570                                    return self.backtrack();
571                                }
572                                if let (Term::Float(_), Term::Integer(0)) = (&wname, &warity) {
573                                    if self.subst.unify(&term_arg, &wname) {
574                                        continue;
575                                    }
576                                    return self.backtrack();
577                                }
578                                if let (Term::Atom(name_id), Term::Integer(arity)) =
579                                    (&wname, &warity)
580                                {
581                                    if *arity < 0 {
582                                        return SolveResult::Error(
583                                            "functor/3: arity must be non-negative".to_string(),
584                                        );
585                                    }
586                                    if *arity > 1024 {
587                                        return SolveResult::Error(
588                                            "functor/3: arity too large (max 1024)".to_string(),
589                                        );
590                                    }
591                                    if *arity > 0 {
592                                        let args: Vec<Term> = (0..*arity as u32)
593                                            .map(|_| {
594                                                let v = self.var_counter;
595                                                self.var_counter += 1;
596                                                Term::Var(v)
597                                            })
598                                            .collect();
599                                        let constructed = Term::Compound {
600                                            functor: *name_id,
601                                            args,
602                                        };
603                                        if self.subst.unify(&term_arg, &constructed) {
604                                            continue;
605                                        }
606                                        return self.backtrack();
607                                    }
608                                }
609                                return SolveResult::Error(
610                                    "functor/3: insufficient arguments".to_string(),
611                                );
612                            }
613                        }
614                    }
615                    Ok(BuiltinResult::Arg(n_arg, term_arg, result_arg)) => {
616                        let wn = self.subst.walk(&n_arg);
617                        let wterm = self.subst.walk(&term_arg);
618                        if let Term::Integer(n) = wn {
619                            let args_list = match &wterm {
620                                Term::Compound { args, .. } => Some(args.as_slice()),
621                                Term::List { .. } => {
622                                    // Treat as .(H,T) with 2 args — handled below
623                                    None
624                                }
625                                _ => {
626                                    return SolveResult::Error(
627                                        "arg/3: second argument must be compound".to_string(),
628                                    );
629                                }
630                            };
631                            if let Some(args) = args_list {
632                                if n >= 1 && (n as usize) <= args.len() {
633                                    let arg = args[(n - 1) as usize].clone();
634                                    if self.subst.unify(&result_arg, &arg) {
635                                        continue;
636                                    }
637                                    return self.backtrack();
638                                }
639                                return self.backtrack();
640                            }
641                            // Handle list case: .(H,T)
642                            if let Term::List { head, tail } = &wterm {
643                                match n {
644                                    1 => {
645                                        if self.subst.unify(&result_arg, head) {
646                                            continue;
647                                        }
648                                        return self.backtrack();
649                                    }
650                                    2 => {
651                                        if self.subst.unify(&result_arg, tail) {
652                                            continue;
653                                        }
654                                        return self.backtrack();
655                                    }
656                                    _ => return self.backtrack(),
657                                }
658                            }
659                            return self.backtrack();
660                        }
661                        return SolveResult::Error(
662                            "arg/3: first argument must be integer".to_string(),
663                        );
664                    }
665                    Ok(BuiltinResult::Univ(term_arg, list_arg)) => {
666                        let walked = self.subst.walk(&term_arg);
667                        match &walked {
668                            Term::Var(_) => {
669                                // Construct term from list (apply to deeply resolve variables)
670                                let wlist = self.subst.apply(&list_arg);
671                                if let Some(elems) = collect_list(&wlist, &self.interner) {
672                                    if elems.is_empty() {
673                                        return SolveResult::Error(
674                                            "=../2: list must not be empty".to_string(),
675                                        );
676                                    }
677                                    if let Term::Atom(functor_id) = &elems[0] {
678                                        if elems.len() == 1 {
679                                            if self.subst.unify(&term_arg, &Term::Atom(*functor_id))
680                                            {
681                                                continue;
682                                            }
683                                        } else {
684                                            let constructed = Term::Compound {
685                                                functor: *functor_id,
686                                                args: elems[1..].to_vec(),
687                                            };
688                                            if self.subst.unify(&term_arg, &constructed) {
689                                                continue;
690                                            }
691                                        }
692                                    } else if elems.len() == 1 {
693                                        // number =.. [number] or atom =.. [atom]
694                                        if matches!(&elems[0], Term::Var(_)) {
695                                            return SolveResult::Error(
696                                                "=../2: instantiation error - element must be bound".to_string(),
697                                            );
698                                        }
699                                        if self.subst.unify(&term_arg, &elems[0]) {
700                                            continue;
701                                        }
702                                    } else {
703                                        return SolveResult::Error(
704                                            "=../2: functor must be an atom when arity > 0"
705                                                .to_string(),
706                                        );
707                                    }
708                                    return self.backtrack();
709                                }
710                                return SolveResult::Error(
711                                    "=../2: second argument must be a list".to_string(),
712                                );
713                            }
714                            Term::Atom(id) => {
715                                let elems = vec![Term::Atom(*id)];
716                                let list = build_list(elems, &self.interner);
717                                if self.subst.unify(&list_arg, &list) {
718                                    continue;
719                                }
720                                return self.backtrack();
721                            }
722                            Term::Integer(_) | Term::Float(_) => {
723                                let elems = vec![walked.clone()];
724                                let list = build_list(elems, &self.interner);
725                                if self.subst.unify(&list_arg, &list) {
726                                    continue;
727                                }
728                                return self.backtrack();
729                            }
730                            Term::Compound { functor, args } => {
731                                let mut elems = vec![Term::Atom(*functor)];
732                                elems.extend(args.clone());
733                                let list = build_list(elems, &self.interner);
734                                if self.subst.unify(&list_arg, &list) {
735                                    continue;
736                                }
737                                return self.backtrack();
738                            }
739                            Term::List { head, tail } => {
740                                let dot_id = self.interner.intern(".");
741                                let elems = vec![Term::Atom(dot_id), *head.clone(), *tail.clone()];
742                                let list = build_list(elems, &self.interner);
743                                if self.subst.unify(&list_arg, &list) {
744                                    continue;
745                                }
746                                return self.backtrack();
747                            }
748                        }
749                    }
750                    Ok(BuiltinResult::Between(low_arg, high_arg, x_arg)) => {
751                        let wlow = self.subst.walk(&low_arg);
752                        let whigh = self.subst.walk(&high_arg);
753                        if let (Term::Integer(low), Term::Integer(high)) = (&wlow, &whigh) {
754                            if low > high {
755                                return self.backtrack();
756                            }
757                            // O(1) fast path: if X is already bound, just check range
758                            let wx = self.subst.walk(&x_arg);
759                            if let Term::Integer(x_val) = &wx {
760                                if *x_val >= *low && *x_val <= *high {
761                                    continue;
762                                }
763                                return self.backtrack();
764                            }
765                            let mark = self.subst.trail_mark();
766                            let saved_counter = self.var_counter;
767                            // Push choice points for low+1..=high
768                            if *low < *high {
769                                let new_low = Term::Integer(match low.checked_add(1) {
770                                    Some(v) => v,
771                                    None => {
772                                        return SolveResult::Error(
773                                            "between/3: integer overflow".to_string(),
774                                        )
775                                    }
776                                });
777                                let between_functor = self.interner.intern("between");
778                                let alt_goal = Term::Compound {
779                                    functor: between_functor,
780                                    args: vec![new_low, whigh.clone(), x_arg.clone()],
781                                };
782                                let mut alt_goals = VecDeque::from(vec![alt_goal]);
783                                alt_goals.extend(goals.iter().cloned());
784                                self.choice_stack.push(ChoicePoint {
785                                    goals: alt_goals,
786                                    untried: vec![],
787                                    trail_mark: mark,
788                                    var_counter: saved_counter,
789                                    cut_barrier: false,
790                                    disjunction: true,
791                                });
792                            }
793                            if self.subst.unify(&x_arg, &Term::Integer(*low)) {
794                                continue;
795                            }
796                            return self.backtrack();
797                        }
798                        return SolveResult::Error(
799                            "between/3: first two arguments must be integers".to_string(),
800                        );
801                    }
802                    Ok(BuiltinResult::CopyTerm(original, copy)) => {
803                        let walked = self.subst.walk(&original);
804                        let copied = self.copy_term_fresh(&walked);
805                        if self.subst.unify(&copy, &copied) {
806                            continue;
807                        }
808                        return self.backtrack();
809                    }
810                    Ok(BuiltinResult::Succ(x_arg, s_arg)) => {
811                        let wx = self.subst.walk(&x_arg);
812                        let ws = self.subst.walk(&s_arg);
813                        match (&wx, &ws) {
814                            (Term::Integer(x), _) if *x >= 0 => match x.checked_add(1) {
815                                Some(result) => {
816                                    if self.subst.unify(&s_arg, &Term::Integer(result)) {
817                                        continue;
818                                    }
819                                    return self.backtrack();
820                                }
821                                None => {
822                                    return SolveResult::Error(
823                                        "succ/2: integer overflow".to_string(),
824                                    )
825                                }
826                            },
827                            (_, Term::Integer(s)) if *s > 0 => {
828                                if self.subst.unify(&x_arg, &Term::Integer(s - 1)) {
829                                    continue;
830                                }
831                                return self.backtrack();
832                            }
833                            (_, Term::Integer(s)) if *s == 0 => {
834                                // succ(X, 0): no non-negative predecessor of 0, fail
835                                return self.backtrack();
836                            }
837                            (Term::Integer(_), _) => {
838                                return SolveResult::Error(
839                                    "succ/2: argument must be non-negative".to_string(),
840                                );
841                            }
842                            (_, Term::Integer(_)) => {
843                                // s < 0: succ is only defined for natural numbers
844                                return SolveResult::Error(
845                                    "succ/2: successor must be non-negative".to_string(),
846                                );
847                            }
848                            _ => {
849                                return SolveResult::Error(
850                                    "succ/2: at least one argument must be an integer".to_string(),
851                                );
852                            }
853                        }
854                    }
855                    Ok(BuiltinResult::Plus(x_arg, y_arg, z_arg)) => {
856                        let wx = self.subst.walk(&x_arg);
857                        let wy = self.subst.walk(&y_arg);
858                        let wz = self.subst.walk(&z_arg);
859                        match (&wx, &wy, &wz) {
860                            (Term::Integer(x), Term::Integer(y), _) => match x.checked_add(*y) {
861                                Some(result) => {
862                                    if self.subst.unify(&z_arg, &Term::Integer(result)) {
863                                        continue;
864                                    }
865                                    return self.backtrack();
866                                }
867                                None => {
868                                    return SolveResult::Error(
869                                        "plus/3: integer overflow".to_string(),
870                                    )
871                                }
872                            },
873                            (Term::Integer(x), _, Term::Integer(z)) => match z.checked_sub(*x) {
874                                Some(result) => {
875                                    if self.subst.unify(&y_arg, &Term::Integer(result)) {
876                                        continue;
877                                    }
878                                    return self.backtrack();
879                                }
880                                None => {
881                                    return SolveResult::Error(
882                                        "plus/3: integer overflow".to_string(),
883                                    )
884                                }
885                            },
886                            (_, Term::Integer(y), Term::Integer(z)) => match z.checked_sub(*y) {
887                                Some(result) => {
888                                    if self.subst.unify(&x_arg, &Term::Integer(result)) {
889                                        continue;
890                                    }
891                                    return self.backtrack();
892                                }
893                                None => {
894                                    return SolveResult::Error(
895                                        "plus/3: integer overflow".to_string(),
896                                    )
897                                }
898                            },
899                            _ => {
900                                return SolveResult::Error(
901                                    "plus/3: at least two arguments must be integers".to_string(),
902                                );
903                            }
904                        }
905                    }
906                    Ok(BuiltinResult::MSort(list_arg, sorted_arg)) => {
907                        let wlist = self.subst.apply(&list_arg);
908                        if let Some(mut elems) = collect_list(&wlist, &self.interner) {
909                            elems.sort_by(|a, b| term_compare(a, b, &self.interner));
910                            let sorted = build_list(elems, &self.interner);
911                            if self.subst.unify(&sorted_arg, &sorted) {
912                                continue;
913                            }
914                            return self.backtrack();
915                        }
916                        return SolveResult::Error(
917                            "msort/2: first argument must be a list".to_string(),
918                        );
919                    }
920                    Ok(BuiltinResult::Sort(list_arg, sorted_arg)) => {
921                        let wlist = self.subst.apply(&list_arg);
922                        if let Some(mut elems) = collect_list(&wlist, &self.interner) {
923                            elems.sort_by(|a, b| term_compare(a, b, &self.interner));
924                            elems.dedup_by(|a, b| {
925                                term_compare(a, b, &self.interner) == std::cmp::Ordering::Equal
926                            });
927                            let sorted = build_list(elems, &self.interner);
928                            if self.subst.unify(&sorted_arg, &sorted) {
929                                continue;
930                            }
931                            return self.backtrack();
932                        }
933                        return SolveResult::Error(
934                            "sort/2: first argument must be a list".to_string(),
935                        );
936                    }
937                    Ok(BuiltinResult::NumberChars(num_arg, chars_arg)) => {
938                        let wnum = self.subst.walk(&num_arg);
939                        match &wnum {
940                            Term::Integer(n) => {
941                                let s = n.to_string();
942                                let nil_id =
943                                    self.interner.lookup("[]").expect("[] must be interned");
944                                let mut list = Term::Atom(nil_id);
945                                for ch in s.chars().rev() {
946                                    let ch_id = self.interner.intern(&ch.to_string());
947                                    list = Term::List {
948                                        head: Box::new(Term::Atom(ch_id)),
949                                        tail: Box::new(list),
950                                    };
951                                }
952                                if self.subst.unify(&chars_arg, &list) {
953                                    continue;
954                                }
955                                return self.backtrack();
956                            }
957                            Term::Float(f) => {
958                                let s = format_float(*f);
959                                let nil_id =
960                                    self.interner.lookup("[]").expect("[] must be interned");
961                                let mut list = Term::Atom(nil_id);
962                                for ch in s.chars().rev() {
963                                    let ch_id = self.interner.intern(&ch.to_string());
964                                    list = Term::List {
965                                        head: Box::new(Term::Atom(ch_id)),
966                                        tail: Box::new(list),
967                                    };
968                                }
969                                if self.subst.unify(&chars_arg, &list) {
970                                    continue;
971                                }
972                                return self.backtrack();
973                            }
974                            Term::Var(_) => {
975                                // Try to parse chars back to number
976                                let wchars = self.subst.apply(&chars_arg);
977                                if let Some(elems) = collect_list(&wchars, &self.interner) {
978                                    let s: Option<String> = elems
979                                        .iter()
980                                        .map(|e| match e {
981                                            Term::Atom(id) => {
982                                                let ch = self.interner.resolve(*id);
983                                                if ch.chars().count() == 1 {
984                                                    Some(ch.to_string())
985                                                } else {
986                                                    None
987                                                }
988                                            }
989                                            _ => None,
990                                        })
991                                        .collect();
992                                    match s {
993                                        Some(s) => {
994                                            if let Ok(n) = s.parse::<i64>() {
995                                                if self.subst.unify(&num_arg, &Term::Integer(n)) {
996                                                    continue;
997                                                }
998                                                return self.backtrack();
999                                            } else if let Ok(f) = s.parse::<f64>() {
1000                                                if f.is_nan() || f.is_infinite() {
1001                                                    return SolveResult::Error(
1002                                                        "number_chars/2: invalid number syntax"
1003                                                            .to_string(),
1004                                                    );
1005                                                }
1006                                                if self.subst.unify(&num_arg, &Term::Float(f)) {
1007                                                    continue;
1008                                                }
1009                                                return self.backtrack();
1010                                            }
1011                                            return SolveResult::Error(
1012                                                "number_chars/2: invalid number syntax".to_string(),
1013                                            );
1014                                        }
1015                                        None => {
1016                                            return SolveResult::Error(
1017                                                "number_chars/2: list elements must be single-character atoms".to_string(),
1018                                            );
1019                                        }
1020                                    }
1021                                }
1022                                return SolveResult::Error(
1023                                    "number_chars/2: at least one argument must be bound"
1024                                        .to_string(),
1025                                );
1026                            }
1027                            _ => {
1028                                return SolveResult::Error(
1029                                    "number_chars/2: first argument must be a number".to_string(),
1030                                );
1031                            }
1032                        }
1033                    }
1034                    Ok(BuiltinResult::NumberCodes(num_arg, codes_arg)) => {
1035                        let wnum = self.subst.walk(&num_arg);
1036                        match &wnum {
1037                            Term::Integer(n) => {
1038                                let s = n.to_string();
1039                                let nil_id =
1040                                    self.interner.lookup("[]").expect("[] must be interned");
1041                                let mut list = Term::Atom(nil_id);
1042                                for ch in s.chars().rev() {
1043                                    list = Term::List {
1044                                        head: Box::new(Term::Integer(ch as i64)),
1045                                        tail: Box::new(list),
1046                                    };
1047                                }
1048                                if self.subst.unify(&codes_arg, &list) {
1049                                    continue;
1050                                }
1051                                return self.backtrack();
1052                            }
1053                            Term::Float(f) => {
1054                                let s = format_float(*f);
1055                                let nil_id =
1056                                    self.interner.lookup("[]").expect("[] must be interned");
1057                                let mut list = Term::Atom(nil_id);
1058                                for ch in s.chars().rev() {
1059                                    list = Term::List {
1060                                        head: Box::new(Term::Integer(ch as i64)),
1061                                        tail: Box::new(list),
1062                                    };
1063                                }
1064                                if self.subst.unify(&codes_arg, &list) {
1065                                    continue;
1066                                }
1067                                return self.backtrack();
1068                            }
1069                            Term::Var(_) => {
1070                                // Try to parse codes back to number
1071                                let wcodes = self.subst.apply(&codes_arg);
1072                                if let Some(elems) = collect_list(&wcodes, &self.interner) {
1073                                    let s: Option<String> = elems
1074                                        .iter()
1075                                        .map(|e| {
1076                                            if let Term::Integer(code) = e {
1077                                                if *code >= 0 && *code <= 0x10FFFF {
1078                                                    char::from_u32(*code as u32)
1079                                                        .map(|c| c.to_string())
1080                                                } else {
1081                                                    None
1082                                                }
1083                                            } else {
1084                                                None
1085                                            }
1086                                        })
1087                                        .collect();
1088                                    match s {
1089                                        Some(s) => {
1090                                            if let Ok(n) = s.parse::<i64>() {
1091                                                if self.subst.unify(&num_arg, &Term::Integer(n)) {
1092                                                    continue;
1093                                                }
1094                                                return self.backtrack();
1095                                            } else if let Ok(f) = s.parse::<f64>() {
1096                                                if f.is_nan() || f.is_infinite() {
1097                                                    return SolveResult::Error(
1098                                                        "number_codes/2: invalid number syntax"
1099                                                            .to_string(),
1100                                                    );
1101                                                }
1102                                                if self.subst.unify(&num_arg, &Term::Float(f)) {
1103                                                    continue;
1104                                                }
1105                                                return self.backtrack();
1106                                            }
1107                                            return SolveResult::Error(
1108                                                "number_codes/2: invalid number syntax".to_string(),
1109                                            );
1110                                        }
1111                                        None => {
1112                                            return SolveResult::Error(
1113                                                "number_codes/2: list elements must be valid character codes".to_string(),
1114                                            );
1115                                        }
1116                                    }
1117                                }
1118                                return SolveResult::Error(
1119                                    "number_codes/2: at least one argument must be bound"
1120                                        .to_string(),
1121                                );
1122                            }
1123                            _ => {
1124                                return SolveResult::Error(
1125                                    "number_codes/2: first argument must be a number".to_string(),
1126                                );
1127                            }
1128                        }
1129                    }
1130                    Err(e) => return SolveResult::Error(e),
1131                }
1132            } else {
1133                // User-defined predicate: look up candidate clauses
1134                let candidates = self.db.lookup(&walked_goal);
1135                if candidates.is_empty() {
1136                    return self.backtrack();
1137                }
1138
1139                match self.try_clauses(walked_goal, goals, candidates) {
1140                    Some(new_goals) => {
1141                        goals = new_goals;
1142                        continue;
1143                    }
1144                    None => return self.backtrack(),
1145                }
1146            }
1147        }
1148    }
1149
1150    /// Try candidate clauses for a goal. Returns new goal list if one succeeds.
1151    fn try_clauses(
1152        &mut self,
1153        goal: Term,
1154        rest_goals: VecDeque<Term>,
1155        candidates: Vec<usize>,
1156    ) -> Option<VecDeque<Term>> {
1157        for (i, &clause_idx) in candidates.iter().enumerate() {
1158            let mark = self.subst.trail_mark();
1159            let saved_counter = self.var_counter;
1160
1161            let clause = &self.db.clauses[clause_idx];
1162            let renamed = self.rename_clause(clause);
1163
1164            if self.subst.unify(&goal, &renamed.head) {
1165                // Build new goals: body of matched clause + remaining goals
1166                let mut new_goals: VecDeque<Term> = VecDeque::from(renamed.body);
1167                new_goals.extend(rest_goals.iter().cloned());
1168
1169                // If there are more candidates, push a choice point
1170                if i + 1 < candidates.len() {
1171                    self.choice_stack.push(ChoicePoint {
1172                        goals: {
1173                            let mut g = VecDeque::from(vec![goal.clone()]);
1174                            g.extend(rest_goals);
1175                            g
1176                        },
1177                        untried: candidates[i + 1..].to_vec(),
1178                        trail_mark: mark,
1179                        var_counter: saved_counter,
1180                        cut_barrier: true,
1181                        disjunction: false,
1182                    });
1183                }
1184
1185                return Some(new_goals);
1186            } else {
1187                // Unification failed; undo and try next clause
1188                self.subst.undo_to(mark);
1189                self.var_counter = saved_counter;
1190            }
1191        }
1192        None
1193    }
1194
1195    /// Backtrack: pop choice points until we find one with alternatives.
1196    fn backtrack(&mut self) -> SolveResult {
1197        while let Some(cp) = self.choice_stack.pop() {
1198            // Restore state to this choice point
1199            self.subst.undo_to(cp.trail_mark);
1200            self.var_counter = cp.var_counter;
1201
1202            if cp.disjunction {
1203                // Disjunction choice point: try the alternative branch directly
1204                return self.solve(cp.goals);
1205            }
1206
1207            if cp.untried.is_empty() {
1208                // No alternatives at this level — keep backtracking
1209                continue;
1210            }
1211
1212            // The first goal in cp.goals is the one we need to retry
1213            let mut cp_goals = cp.goals;
1214            let goal = cp_goals.pop_front().unwrap();
1215            let rest_goals = cp_goals;
1216            let candidates = cp.untried;
1217
1218            match self.try_clauses(goal, rest_goals, candidates) {
1219                Some(new_goals) => {
1220                    return self.solve(new_goals);
1221                }
1222                None => {
1223                    // All remaining candidates failed — keep backtracking
1224                    continue;
1225                }
1226            }
1227        }
1228        SolveResult::Failure
1229    }
1230
1231    /// Try to solve goals (used for negation-as-failure check).
1232    /// Returns true if the goals succeed at least once.
1233    fn try_solve_once(&mut self, goals: Vec<Term>) -> bool {
1234        let mut goal_list = VecDeque::from(goals);
1235        loop {
1236            if goal_list.is_empty() {
1237                return true;
1238            }
1239
1240            self.steps += 1;
1241            if self.steps > self.max_depth {
1242                self.steps_exceeded = true;
1243                return false;
1244            }
1245
1246            let goal = goal_list.pop_front().unwrap();
1247            let walked_goal = self.subst.walk(&goal);
1248
1249            if is_builtin(&walked_goal, &self.interner) {
1250                match exec_builtin(&walked_goal, &mut self.subst, &self.interner) {
1251                    Ok(BuiltinResult::Success) => continue,
1252                    Ok(BuiltinResult::Failure) => return false,
1253                    Ok(BuiltinResult::Cut) => {
1254                        self.cut_in_try_solve = true;
1255                        continue;
1256                    }
1257                    Ok(BuiltinResult::NegationAsFailure(inner)) => {
1258                        let mark = self.subst.trail_mark();
1259                        let saved_counter = self.var_counter;
1260                        let inner_result = self.try_solve_once(vec![inner]);
1261                        self.subst.undo_to(mark);
1262                        self.var_counter = saved_counter;
1263                        // Step limit inside NAF: treat as failure of the whole context
1264                        if self.steps_exceeded {
1265                            return false;
1266                        }
1267                        if inner_result {
1268                            return false;
1269                        }
1270                        continue;
1271                    }
1272                    Ok(BuiltinResult::Conjunction(a, b)) => {
1273                        goal_list.push_front(b);
1274                        goal_list.push_front(a);
1275                        continue;
1276                    }
1277                    Ok(BuiltinResult::Disjunction(left, right)) => {
1278                        // Try left first
1279                        let mark = self.subst.trail_mark();
1280                        let saved_counter = self.var_counter;
1281                        let mut left_goals = vec![left];
1282                        left_goals.extend(goal_list.iter().cloned());
1283                        if self.try_solve_once(left_goals) {
1284                            return true;
1285                        }
1286                        self.subst.undo_to(mark);
1287                        self.var_counter = saved_counter;
1288                        // Try right
1289                        goal_list.push_front(right);
1290                        continue;
1291                    }
1292                    Ok(BuiltinResult::IfThenElse(cond, then, else_branch)) => {
1293                        let mark = self.subst.trail_mark();
1294                        let saved_counter = self.var_counter;
1295                        if self.try_solve_once(vec![cond]) {
1296                            // Keep bindings from cond
1297                            goal_list.push_front(then);
1298                            continue;
1299                        } else {
1300                            self.subst.undo_to(mark);
1301                            self.var_counter = saved_counter;
1302                            goal_list.push_front(else_branch);
1303                            continue;
1304                        }
1305                    }
1306                    Ok(BuiltinResult::IfThen(cond, then)) => {
1307                        let mark = self.subst.trail_mark();
1308                        let saved_counter = self.var_counter;
1309                        if self.try_solve_once(vec![cond]) {
1310                            // Keep bindings from cond
1311                            goal_list.push_front(then);
1312                            continue;
1313                        } else {
1314                            self.subst.undo_to(mark);
1315                            self.var_counter = saved_counter;
1316                            return false;
1317                        }
1318                    }
1319                    Ok(BuiltinResult::FindAll(template, goal, result_var)) => {
1320                        match self.exec_findall(template, goal) {
1321                            Ok(result_list) => {
1322                                if self.subst.unify(&result_var, &result_list) {
1323                                    continue;
1324                                } else {
1325                                    return false;
1326                                }
1327                            }
1328                            Err(_) => return false,
1329                        }
1330                    }
1331                    Ok(BuiltinResult::Once(inner_goal)) => {
1332                        // once/1 in try_solve_once: just solve the inner goal once
1333                        let walked = self.subst.walk(&inner_goal);
1334                        goal_list.push_front(walked);
1335                        continue;
1336                    }
1337                    Ok(BuiltinResult::Call(inner_goal)) => {
1338                        let walked = self.subst.walk(&inner_goal);
1339                        goal_list.push_front(walked);
1340                        continue;
1341                    }
1342                    Ok(BuiltinResult::AtomLength(atom_arg, len_arg)) => {
1343                        let walked = self.subst.walk(&atom_arg);
1344                        if let Term::Atom(id) = walked {
1345                            let len = self.interner.resolve(id).chars().count() as i64;
1346                            if self.subst.unify(&len_arg, &Term::Integer(len)) {
1347                                continue;
1348                            }
1349                        }
1350                        return false;
1351                    }
1352                    Ok(BuiltinResult::AtomConcat(a_arg, b_arg, result_arg)) => {
1353                        let a = self.subst.walk(&a_arg);
1354                        let b = self.subst.walk(&b_arg);
1355                        if let (Term::Atom(id_a), Term::Atom(id_b)) = (&a, &b) {
1356                            let s = format!(
1357                                "{}{}",
1358                                self.interner.resolve(*id_a),
1359                                self.interner.resolve(*id_b)
1360                            );
1361                            let result_id = self.interner.intern(&s);
1362                            if self.subst.unify(&result_arg, &Term::Atom(result_id)) {
1363                                continue;
1364                            }
1365                        }
1366                        return false;
1367                    }
1368                    Ok(BuiltinResult::AtomChars(atom_arg, list_arg)) => {
1369                        let walked = self.subst.walk(&atom_arg);
1370                        if let Term::Atom(id) = walked {
1371                            let name_str = self.interner.resolve(id).to_string();
1372                            let nil_id = self.interner.lookup("[]").expect("[] must be interned");
1373                            let mut list = Term::Atom(nil_id);
1374                            for ch in name_str.chars().rev() {
1375                                let ch_id = self.interner.intern(&ch.to_string());
1376                                list = Term::List {
1377                                    head: Box::new(Term::Atom(ch_id)),
1378                                    tail: Box::new(list),
1379                                };
1380                            }
1381                            if self.subst.unify(&list_arg, &list) {
1382                                continue;
1383                            }
1384                        } else if let Term::Var(_) = walked {
1385                            let wlist = self.subst.apply(&list_arg);
1386                            if let Some(elems) = collect_list(&wlist, &self.interner) {
1387                                let s: Option<String> = elems
1388                                    .iter()
1389                                    .map(|e| {
1390                                        if let Term::Atom(id) = e {
1391                                            let ch = self.interner.resolve(*id);
1392                                            if ch.chars().count() == 1 {
1393                                                Some(ch.to_string())
1394                                            } else {
1395                                                None
1396                                            }
1397                                        } else {
1398                                            None
1399                                        }
1400                                    })
1401                                    .collect();
1402                                if let Some(s) = s {
1403                                    let atom_id = self.interner.intern(&s);
1404                                    if self.subst.unify(&atom_arg, &Term::Atom(atom_id)) {
1405                                        continue;
1406                                    }
1407                                }
1408                            }
1409                        }
1410                        return false;
1411                    }
1412                    Ok(BuiltinResult::Between(low_arg, high_arg, x_arg)) => {
1413                        // between/3 needs explicit handling to backtrack through values
1414                        // in conjunctions like \+ (between(1,5,X), X > 3)
1415                        let wlow = self.subst.walk(&low_arg);
1416                        let whigh = self.subst.walk(&high_arg);
1417                        if let (Term::Integer(low), Term::Integer(high)) = (&wlow, &whigh) {
1418                            // If X is already bound, just check low <= X <= high (O(1))
1419                            let wx = self.subst.walk(&x_arg);
1420                            if let Term::Integer(x_val) = &wx {
1421                                if *x_val >= *low && *x_val <= *high {
1422                                    let remaining: Vec<Term> = goal_list.iter().cloned().collect();
1423                                    return self.try_solve_once(remaining);
1424                                }
1425                                return false;
1426                            }
1427                            // X is unbound: iterate with per-iteration step counting
1428                            let remaining: Vec<Term> = goal_list.iter().cloned().collect();
1429                            for val in *low..=*high {
1430                                self.steps += 1;
1431                                if self.steps > self.max_depth {
1432                                    self.steps_exceeded = true;
1433                                    return false;
1434                                }
1435                                let mark = self.subst.trail_mark();
1436                                let saved_counter = self.var_counter;
1437                                if self.subst.unify(&x_arg, &Term::Integer(val)) {
1438                                    if self.try_solve_once(remaining.clone()) {
1439                                        return true;
1440                                    }
1441                                }
1442                                self.subst.undo_to(mark);
1443                                self.var_counter = saved_counter;
1444                            }
1445                            return false;
1446                        }
1447                        return false;
1448                    }
1449                    Ok(other) => {
1450                        // Handle remaining builtins via try_exec_misc
1451                        if let Some(success) = self.try_exec_misc(other, &mut goal_list) {
1452                            if success {
1453                                continue;
1454                            } else {
1455                                return false;
1456                            }
1457                        }
1458                        return false;
1459                    }
1460                    Err(_) => return false,
1461                }
1462            }
1463
1464            let candidates = self.db.lookup(&walked_goal);
1465            for &clause_idx in &candidates {
1466                let mark = self.subst.trail_mark();
1467                let saved_counter = self.var_counter;
1468
1469                let clause = &self.db.clauses[clause_idx];
1470                let renamed = self.rename_clause(clause);
1471
1472                if self.subst.unify(&walked_goal, &renamed.head) {
1473                    let mut new_goals = renamed.body;
1474                    new_goals.extend(goal_list.clone());
1475                    let saved_cut = self.cut_in_try_solve;
1476                    self.cut_in_try_solve = false;
1477                    if self.try_solve_once(new_goals) {
1478                        self.cut_in_try_solve = saved_cut;
1479                        return true;
1480                    }
1481                    if self.cut_in_try_solve {
1482                        // Cut was triggered — don't try more clauses
1483                        self.cut_in_try_solve = saved_cut;
1484                        self.subst.undo_to(mark);
1485                        self.var_counter = saved_counter;
1486                        return false;
1487                    }
1488                    self.cut_in_try_solve = saved_cut;
1489                }
1490                self.subst.undo_to(mark);
1491                self.var_counter = saved_counter;
1492            }
1493            return false;
1494        }
1495    }
1496
1497    /// Execute findall/3: collect all instances of Template for which Goal succeeds.
1498    fn exec_findall(&mut self, template: Term, goal: Term) -> Result<Term, String> {
1499        let mark = self.subst.trail_mark();
1500        let saved_counter = self.var_counter;
1501        let saved_stack_len = self.choice_stack.len();
1502
1503        let mut collected = Vec::new();
1504        self.try_solve_collecting(VecDeque::from(vec![goal]), &template, &mut collected);
1505
1506        // Check if collection was truncated by step limit
1507        let truncated = self.steps > self.max_depth || self.steps_exceeded;
1508
1509        // Restore state (steps accumulate globally — no save/restore)
1510        self.subst.undo_to(mark);
1511        self.var_counter = saved_counter;
1512        self.choice_stack.truncate(saved_stack_len);
1513
1514        if truncated {
1515            self.steps_exceeded = false;
1516            return Err(format!(
1517                "findall: step limit ({}) exceeded during collection",
1518                self.max_depth
1519            ));
1520        }
1521
1522        // Build the result list from collected terms
1523        let nil_id = self.interner.lookup("[]").expect("[] must be interned");
1524        let mut result = Term::Atom(nil_id);
1525        for term in collected.into_iter().rev() {
1526            result = Term::List {
1527                head: Box::new(term),
1528                tail: Box::new(result),
1529            };
1530        }
1531
1532        Ok(result)
1533    }
1534
1535    /// Try to solve a list of goals, collecting template instances for each success.
1536    fn try_solve_collecting(
1537        &mut self,
1538        goals: VecDeque<Term>,
1539        template: &Term,
1540        results: &mut Vec<Term>,
1541    ) -> bool {
1542        if goals.is_empty() {
1543            results.push(self.subst.apply(template));
1544            return true;
1545        }
1546
1547        self.steps += 1;
1548        if self.steps > self.max_depth {
1549            self.steps_exceeded = true;
1550            return false;
1551        }
1552
1553        let mut goal_list = goals;
1554        let goal = goal_list.pop_front().unwrap();
1555        let walked_goal = self.subst.walk(&goal);
1556
1557        if is_builtin(&walked_goal, &self.interner) {
1558            match exec_builtin(&walked_goal, &mut self.subst, &self.interner) {
1559                Ok(BuiltinResult::Success) => {
1560                    return self.try_solve_collecting(goal_list, template, results);
1561                }
1562                Ok(BuiltinResult::Cut) => {
1563                    self.cut_in_try_solve = true;
1564                    return self.try_solve_collecting(goal_list, template, results);
1565                }
1566                Ok(BuiltinResult::Conjunction(a, b)) => {
1567                    goal_list.push_front(b);
1568                    goal_list.push_front(a);
1569                    return self.try_solve_collecting(goal_list, template, results);
1570                }
1571                Ok(BuiltinResult::NegationAsFailure(inner)) => {
1572                    let mark = self.subst.trail_mark();
1573                    let saved_counter = self.var_counter;
1574                    let inner_result = self.try_solve_once(vec![inner]);
1575                    self.subst.undo_to(mark);
1576                    self.var_counter = saved_counter;
1577                    // Step limit inside NAF: treat as failure of the whole context
1578                    if self.steps_exceeded {
1579                        return false;
1580                    }
1581                    if inner_result {
1582                        return false;
1583                    }
1584                    return self.try_solve_collecting(goal_list, template, results);
1585                }
1586                Ok(BuiltinResult::Disjunction(left, right)) => {
1587                    // Try left branch
1588                    let mark = self.subst.trail_mark();
1589                    let saved_counter = self.var_counter;
1590                    let mut left_goals = VecDeque::from(vec![left]);
1591                    left_goals.extend(goal_list.iter().cloned());
1592                    let found_left = self.try_solve_collecting(left_goals, template, results);
1593                    self.subst.undo_to(mark);
1594                    self.var_counter = saved_counter;
1595                    // Try right branch
1596                    let mut right_goals = VecDeque::from(vec![right]);
1597                    right_goals.extend(goal_list);
1598                    let found_right = self.try_solve_collecting(right_goals, template, results);
1599                    return found_left || found_right;
1600                }
1601                Ok(BuiltinResult::IfThenElse(cond, then, else_branch)) => {
1602                    let mark = self.subst.trail_mark();
1603                    let saved_counter = self.var_counter;
1604                    if self.try_solve_once(vec![cond]) {
1605                        // Keep bindings from cond
1606                        goal_list.push_front(then);
1607                        return self.try_solve_collecting(goal_list, template, results);
1608                    } else {
1609                        self.subst.undo_to(mark);
1610                        self.var_counter = saved_counter;
1611                        goal_list.push_front(else_branch);
1612                        return self.try_solve_collecting(goal_list, template, results);
1613                    }
1614                }
1615                Ok(BuiltinResult::IfThen(cond, then)) => {
1616                    let mark = self.subst.trail_mark();
1617                    let saved_counter = self.var_counter;
1618                    if self.try_solve_once(vec![cond]) {
1619                        // Keep bindings from cond
1620                        goal_list.push_front(then);
1621                        return self.try_solve_collecting(goal_list, template, results);
1622                    } else {
1623                        self.subst.undo_to(mark);
1624                        self.var_counter = saved_counter;
1625                        return false;
1626                    }
1627                }
1628                Ok(BuiltinResult::FindAll(tmpl, inner_goal, result_var)) => {
1629                    match self.exec_findall(tmpl, inner_goal) {
1630                        Ok(result_list) => {
1631                            if self.subst.unify(&result_var, &result_list) {
1632                                return self.try_solve_collecting(goal_list, template, results);
1633                            }
1634                            return false;
1635                        }
1636                        Err(_) => return false,
1637                    }
1638                }
1639                Ok(BuiltinResult::Once(inner_goal)) => {
1640                    let walked = self.subst.walk(&inner_goal);
1641                    if self.try_solve_once(vec![walked]) {
1642                        return self.try_solve_collecting(goal_list, template, results);
1643                    }
1644                    return false;
1645                }
1646                Ok(BuiltinResult::Call(inner_goal)) => {
1647                    let walked = self.subst.walk(&inner_goal);
1648                    goal_list.push_front(walked);
1649                    return self.try_solve_collecting(goal_list, template, results);
1650                }
1651                Ok(BuiltinResult::AtomLength(atom_arg, len_arg)) => {
1652                    let walked = self.subst.walk(&atom_arg);
1653                    if let Term::Atom(id) = walked {
1654                        let len = self.interner.resolve(id).chars().count() as i64;
1655                        if self.subst.unify(&len_arg, &Term::Integer(len)) {
1656                            return self.try_solve_collecting(goal_list, template, results);
1657                        }
1658                    }
1659                    return false;
1660                }
1661                Ok(BuiltinResult::AtomConcat(a_arg, b_arg, result_arg)) => {
1662                    let a = self.subst.walk(&a_arg);
1663                    let b = self.subst.walk(&b_arg);
1664                    if let (Term::Atom(id_a), Term::Atom(id_b)) = (&a, &b) {
1665                        let s = format!(
1666                            "{}{}",
1667                            self.interner.resolve(*id_a),
1668                            self.interner.resolve(*id_b)
1669                        );
1670                        let result_id = self.interner.intern(&s);
1671                        if self.subst.unify(&result_arg, &Term::Atom(result_id)) {
1672                            return self.try_solve_collecting(goal_list, template, results);
1673                        }
1674                    }
1675                    return false;
1676                }
1677                Ok(BuiltinResult::AtomChars(atom_arg, list_arg)) => {
1678                    let walked = self.subst.walk(&atom_arg);
1679                    if let Term::Atom(id) = walked {
1680                        let name_str = self.interner.resolve(id).to_string();
1681                        let nil_id = self.interner.lookup("[]").expect("[] must be interned");
1682                        let mut list = Term::Atom(nil_id);
1683                        for ch in name_str.chars().rev() {
1684                            let ch_id = self.interner.intern(&ch.to_string());
1685                            list = Term::List {
1686                                head: Box::new(Term::Atom(ch_id)),
1687                                tail: Box::new(list),
1688                            };
1689                        }
1690                        if self.subst.unify(&list_arg, &list) {
1691                            return self.try_solve_collecting(goal_list, template, results);
1692                        }
1693                    } else if let Term::Var(_) = walked {
1694                        let wlist = self.subst.apply(&list_arg);
1695                        if let Some(elems) = collect_list(&wlist, &self.interner) {
1696                            let s: Option<String> = elems
1697                                .iter()
1698                                .map(|e| {
1699                                    if let Term::Atom(id) = e {
1700                                        let ch = self.interner.resolve(*id);
1701                                        if ch.chars().count() == 1 {
1702                                            Some(ch.to_string())
1703                                        } else {
1704                                            None
1705                                        }
1706                                    } else {
1707                                        None
1708                                    }
1709                                })
1710                                .collect();
1711                            if let Some(s) = s {
1712                                let atom_id = self.interner.intern(&s);
1713                                if self.subst.unify(&atom_arg, &Term::Atom(atom_id)) {
1714                                    return self.try_solve_collecting(goal_list, template, results);
1715                                }
1716                            }
1717                        }
1718                    }
1719                    return false;
1720                }
1721                Ok(BuiltinResult::Failure) => return false,
1722                Ok(BuiltinResult::Between(low_arg, high_arg, x_arg)) => {
1723                    // between/3 needs special handling to enumerate all values
1724                    let wlow = self.subst.walk(&low_arg);
1725                    let whigh = self.subst.walk(&high_arg);
1726                    if let (Term::Integer(low), Term::Integer(high)) = (&wlow, &whigh) {
1727                        // If X is already bound, just check low <= X <= high (O(1))
1728                        let wx = self.subst.walk(&x_arg);
1729                        if let Term::Integer(x_val) = &wx {
1730                            if *x_val >= *low && *x_val <= *high {
1731                                let remaining = goal_list.clone();
1732                                return self.try_solve_collecting(remaining, template, results);
1733                            }
1734                            return false;
1735                        }
1736                        // X is unbound: iterate with per-iteration step counting
1737                        let mut found_any = false;
1738                        let remaining = goal_list.clone();
1739                        for val in *low..=*high {
1740                            self.steps += 1;
1741                            if self.steps > self.max_depth {
1742                                self.steps_exceeded = true;
1743                                return found_any;
1744                            }
1745                            let mark = self.subst.trail_mark();
1746                            let saved_counter = self.var_counter;
1747                            if self.subst.unify(&x_arg, &Term::Integer(val)) {
1748                                if self.try_solve_collecting(remaining.clone(), template, results) {
1749                                    found_any = true;
1750                                }
1751                            }
1752                            self.subst.undo_to(mark);
1753                            self.var_counter = saved_counter;
1754                        }
1755                        return found_any;
1756                    }
1757                    return false;
1758                }
1759                Ok(other) => {
1760                    // Handle remaining builtins via try_exec_misc
1761                    if let Some(success) = self.try_exec_misc(other, &mut goal_list) {
1762                        if success {
1763                            return self.try_solve_collecting(goal_list, template, results);
1764                        }
1765                    }
1766                    return false;
1767                }
1768                Err(_) => return false,
1769            }
1770        }
1771
1772        let candidates = self.db.lookup(&walked_goal);
1773        let mut found_any = false;
1774        for &clause_idx in &candidates {
1775            let mark = self.subst.trail_mark();
1776            let saved_counter = self.var_counter;
1777
1778            let clause = &self.db.clauses[clause_idx];
1779            let renamed = self.rename_clause(clause);
1780
1781            let saved_cut = self.cut_in_try_solve;
1782            self.cut_in_try_solve = false;
1783            if self.subst.unify(&walked_goal, &renamed.head) {
1784                let mut new_goals: VecDeque<Term> = VecDeque::from(renamed.body);
1785                new_goals.extend(goal_list.iter().cloned());
1786                if self.try_solve_collecting(new_goals, template, results) {
1787                    found_any = true;
1788                }
1789            }
1790            // If cut fired inside, stop iterating clauses
1791            if self.cut_in_try_solve {
1792                self.cut_in_try_solve = saved_cut;
1793                self.subst.undo_to(mark);
1794                self.var_counter = saved_counter;
1795                return found_any;
1796            }
1797            self.cut_in_try_solve = saved_cut;
1798            self.subst.undo_to(mark);
1799            self.var_counter = saved_counter;
1800        }
1801        found_any
1802    }
1803
1804    /// Rename all variables in a clause to fresh IDs to avoid collisions.
1805    fn rename_clause(&mut self, clause: &Clause) -> Clause {
1806        let mut var_map: FnvHashMap<VarId, VarId> = FnvHashMap::default();
1807        Clause {
1808            head: self.rename_term(&clause.head, &mut var_map),
1809            body: clause
1810                .body
1811                .iter()
1812                .map(|t| self.rename_term(t, &mut var_map))
1813                .collect(),
1814        }
1815    }
1816
1817    fn rename_term(&mut self, term: &Term, var_map: &mut FnvHashMap<VarId, VarId>) -> Term {
1818        match term {
1819            Term::Var(id) => {
1820                let new_id = *var_map.entry(*id).or_insert_with(|| {
1821                    let fresh = self.var_counter;
1822                    self.var_counter += 1;
1823                    fresh
1824                });
1825                Term::Var(new_id)
1826            }
1827            Term::Compound { functor, args } => Term::Compound {
1828                functor: *functor,
1829                args: args.iter().map(|a| self.rename_term(a, var_map)).collect(),
1830            },
1831            Term::List { head, tail } => Term::List {
1832                head: Box::new(self.rename_term(head, var_map)),
1833                tail: Box::new(self.rename_term(tail, var_map)),
1834            },
1835            _ => term.clone(),
1836        }
1837    }
1838
1839    /// Handle miscellaneous builtins shared between try_solve_once and try_solve_collecting.
1840    /// Returns Some(true) on success, Some(false) on failure, None on error (treat as failure).
1841    fn try_exec_misc(
1842        &mut self,
1843        result: BuiltinResult,
1844        _goal_list: &mut VecDeque<Term>,
1845    ) -> Option<bool> {
1846        match result {
1847            BuiltinResult::Write(term) => {
1848                let resolved = self.subst.apply(&term);
1849                print!("{}", term_to_string(&resolved, &self.interner));
1850                let _ = std::io::stdout().flush();
1851                Some(true)
1852            }
1853            BuiltinResult::Writeln(term) => {
1854                let resolved = self.subst.apply(&term);
1855                println!("{}", term_to_string(&resolved, &self.interner));
1856                Some(true)
1857            }
1858            BuiltinResult::Nl => {
1859                println!();
1860                Some(true)
1861            }
1862            BuiltinResult::Compare(order_arg, t1, t2) => {
1863                let w1 = self.subst.apply(&t1);
1864                let w2 = self.subst.apply(&t2);
1865                let cmp = term_compare(&w1, &w2, &self.interner);
1866                let order_name = match cmp {
1867                    std::cmp::Ordering::Less => "<",
1868                    std::cmp::Ordering::Equal => "=",
1869                    std::cmp::Ordering::Greater => ">",
1870                };
1871                let order_id = self.interner.intern(order_name);
1872                Some(self.subst.unify(&order_arg, &Term::Atom(order_id)))
1873            }
1874            BuiltinResult::Functor(term_arg, name_arg, arity_arg) => {
1875                let walked = self.subst.walk(&term_arg);
1876                match &walked {
1877                    Term::Atom(id) => Some(
1878                        self.subst.unify(&name_arg, &Term::Atom(*id))
1879                            && self.subst.unify(&arity_arg, &Term::Integer(0)),
1880                    ),
1881                    Term::Integer(_) | Term::Float(_) => Some(
1882                        self.subst.unify(&name_arg, &walked)
1883                            && self.subst.unify(&arity_arg, &Term::Integer(0)),
1884                    ),
1885                    Term::Compound { functor, args } => Some(
1886                        self.subst.unify(&name_arg, &Term::Atom(*functor))
1887                            && self
1888                                .subst
1889                                .unify(&arity_arg, &Term::Integer(args.len() as i64)),
1890                    ),
1891                    Term::List { .. } => {
1892                        let dot_id = self.interner.intern(".");
1893                        Some(
1894                            self.subst.unify(&name_arg, &Term::Atom(dot_id))
1895                                && self.subst.unify(&arity_arg, &Term::Integer(2)),
1896                        )
1897                    }
1898                    Term::Var(_) => {
1899                        let wname = self.subst.walk(&name_arg);
1900                        let warity = self.subst.walk(&arity_arg);
1901                        match (&wname, &warity) {
1902                            (Term::Atom(name_id), Term::Integer(0)) => {
1903                                Some(self.subst.unify(&term_arg, &Term::Atom(*name_id)))
1904                            }
1905                            (Term::Integer(_) | Term::Float(_), Term::Integer(0)) => {
1906                                Some(self.subst.unify(&term_arg, &wname))
1907                            }
1908                            (Term::Atom(name_id), Term::Integer(arity))
1909                                if *arity > 0 && *arity <= 1024 =>
1910                            {
1911                                let args: Vec<Term> = (0..*arity as u32)
1912                                    .map(|_| {
1913                                        let v = self.var_counter;
1914                                        self.var_counter += 1;
1915                                        Term::Var(v)
1916                                    })
1917                                    .collect();
1918                                let constructed = Term::Compound {
1919                                    functor: *name_id,
1920                                    args,
1921                                };
1922                                Some(self.subst.unify(&term_arg, &constructed))
1923                            }
1924                            _ => Some(false),
1925                        }
1926                    }
1927                }
1928            }
1929            BuiltinResult::Arg(n_arg, term_arg, result_arg) => {
1930                let wn = self.subst.walk(&n_arg);
1931                let wterm = self.subst.walk(&term_arg);
1932                if let Term::Integer(n) = wn {
1933                    if let Term::Compound { args, .. } = &wterm {
1934                        if n >= 1 && (n as usize) <= args.len() {
1935                            return Some(self.subst.unify(&result_arg, &args[(n - 1) as usize]));
1936                        }
1937                    }
1938                    if let Term::List { head, tail } = &wterm {
1939                        return match n {
1940                            1 => Some(self.subst.unify(&result_arg, head)),
1941                            2 => Some(self.subst.unify(&result_arg, tail)),
1942                            _ => Some(false),
1943                        };
1944                    }
1945                }
1946                Some(false)
1947            }
1948            BuiltinResult::Univ(term_arg, list_arg) => {
1949                let walked = self.subst.walk(&term_arg);
1950                match &walked {
1951                    Term::Atom(id) => {
1952                        let list = build_list(vec![Term::Atom(*id)], &self.interner);
1953                        Some(self.subst.unify(&list_arg, &list))
1954                    }
1955                    Term::Integer(_) | Term::Float(_) => {
1956                        let list = build_list(vec![walked.clone()], &self.interner);
1957                        Some(self.subst.unify(&list_arg, &list))
1958                    }
1959                    Term::Compound { functor, args } => {
1960                        let mut elems = vec![Term::Atom(*functor)];
1961                        elems.extend(args.clone());
1962                        let list = build_list(elems, &self.interner);
1963                        Some(self.subst.unify(&list_arg, &list))
1964                    }
1965                    Term::List { head, tail } => {
1966                        let dot_id = self.interner.intern(".");
1967                        let elems = vec![Term::Atom(dot_id), *head.clone(), *tail.clone()];
1968                        let list = build_list(elems, &self.interner);
1969                        Some(self.subst.unify(&list_arg, &list))
1970                    }
1971                    Term::Var(_) => {
1972                        let wlist = self.subst.apply(&list_arg);
1973                        if let Some(elems) = collect_list(&wlist, &self.interner) {
1974                            if !elems.is_empty() {
1975                                if let Term::Atom(fid) = &elems[0] {
1976                                    if elems.len() == 1 {
1977                                        return Some(
1978                                            self.subst.unify(&term_arg, &Term::Atom(*fid)),
1979                                        );
1980                                    }
1981                                    let constructed = Term::Compound {
1982                                        functor: *fid,
1983                                        args: elems[1..].to_vec(),
1984                                    };
1985                                    return Some(self.subst.unify(&term_arg, &constructed));
1986                                } else if elems.len() == 1 {
1987                                    // Instantiation error if element is unbound
1988                                    if matches!(&elems[0], Term::Var(_)) {
1989                                        return Some(false);
1990                                    }
1991                                    return Some(self.subst.unify(&term_arg, &elems[0]));
1992                                }
1993                            }
1994                        }
1995                        Some(false)
1996                    }
1997                }
1998            }
1999            BuiltinResult::CopyTerm(original, copy) => {
2000                let walked = self.subst.walk(&original);
2001                let copied = self.copy_term_fresh(&walked);
2002                Some(self.subst.unify(&copy, &copied))
2003            }
2004            BuiltinResult::Succ(x_arg, s_arg) => {
2005                let wx = self.subst.walk(&x_arg);
2006                let ws = self.subst.walk(&s_arg);
2007                match (&wx, &ws) {
2008                    (Term::Integer(x), _) if *x >= 0 => match x.checked_add(1) {
2009                        Some(result) => Some(self.subst.unify(&s_arg, &Term::Integer(result))),
2010                        None => Some(false),
2011                    },
2012                    (_, Term::Integer(s)) if *s > 0 => {
2013                        Some(self.subst.unify(&x_arg, &Term::Integer(s - 1)))
2014                    }
2015                    _ => Some(false),
2016                }
2017            }
2018            BuiltinResult::Plus(x_arg, y_arg, z_arg) => {
2019                let wx = self.subst.walk(&x_arg);
2020                let wy = self.subst.walk(&y_arg);
2021                let wz = self.subst.walk(&z_arg);
2022                match (&wx, &wy, &wz) {
2023                    (Term::Integer(x), Term::Integer(y), _) => match x.checked_add(*y) {
2024                        Some(result) => Some(self.subst.unify(&z_arg, &Term::Integer(result))),
2025                        None => Some(false),
2026                    },
2027                    (Term::Integer(x), _, Term::Integer(z)) => match z.checked_sub(*x) {
2028                        Some(result) => Some(self.subst.unify(&y_arg, &Term::Integer(result))),
2029                        None => Some(false),
2030                    },
2031                    (_, Term::Integer(y), Term::Integer(z)) => match z.checked_sub(*y) {
2032                        Some(result) => Some(self.subst.unify(&x_arg, &Term::Integer(result))),
2033                        None => Some(false),
2034                    },
2035                    _ => Some(false),
2036                }
2037            }
2038            BuiltinResult::MSort(list_arg, sorted_arg) => {
2039                let wlist = self.subst.apply(&list_arg);
2040                if let Some(mut elems) = collect_list(&wlist, &self.interner) {
2041                    elems.sort_by(|a, b| term_compare(a, b, &self.interner));
2042                    let sorted = build_list(elems, &self.interner);
2043                    return Some(self.subst.unify(&sorted_arg, &sorted));
2044                }
2045                Some(false)
2046            }
2047            BuiltinResult::Sort(list_arg, sorted_arg) => {
2048                let wlist = self.subst.apply(&list_arg);
2049                if let Some(mut elems) = collect_list(&wlist, &self.interner) {
2050                    elems.sort_by(|a, b| term_compare(a, b, &self.interner));
2051                    elems.dedup_by(|a, b| {
2052                        term_compare(a, b, &self.interner) == std::cmp::Ordering::Equal
2053                    });
2054                    let sorted = build_list(elems, &self.interner);
2055                    return Some(self.subst.unify(&sorted_arg, &sorted));
2056                }
2057                Some(false)
2058            }
2059            BuiltinResult::NumberChars(num_arg, chars_arg) => {
2060                let wnum = self.subst.walk(&num_arg);
2061                match &wnum {
2062                    Term::Integer(n) => {
2063                        let s = n.to_string();
2064                        let nil_id = self.interner.lookup("[]").expect("[] must be interned");
2065                        let mut list = Term::Atom(nil_id);
2066                        for ch in s.chars().rev() {
2067                            let ch_id = self.interner.intern(&ch.to_string());
2068                            list = Term::List {
2069                                head: Box::new(Term::Atom(ch_id)),
2070                                tail: Box::new(list),
2071                            };
2072                        }
2073                        Some(self.subst.unify(&chars_arg, &list))
2074                    }
2075                    Term::Float(f) => {
2076                        let s = format_float(*f);
2077                        let nil_id = self.interner.lookup("[]").expect("[] must be interned");
2078                        let mut list = Term::Atom(nil_id);
2079                        for ch in s.chars().rev() {
2080                            let ch_id = self.interner.intern(&ch.to_string());
2081                            list = Term::List {
2082                                head: Box::new(Term::Atom(ch_id)),
2083                                tail: Box::new(list),
2084                            };
2085                        }
2086                        Some(self.subst.unify(&chars_arg, &list))
2087                    }
2088                    Term::Var(_) => {
2089                        // Reverse: char list -> number
2090                        let wchars = self.subst.apply(&chars_arg);
2091                        if let Some(elems) = collect_list(&wchars, &self.interner) {
2092                            let s: Option<String> = elems
2093                                .iter()
2094                                .map(|e| match e {
2095                                    Term::Atom(id) => {
2096                                        let ch = self.interner.resolve(*id);
2097                                        if ch.chars().count() == 1 {
2098                                            Some(ch.to_string())
2099                                        } else {
2100                                            None
2101                                        }
2102                                    }
2103                                    _ => None,
2104                                })
2105                                .collect();
2106                            if let Some(s) = s {
2107                                if let Ok(n) = s.parse::<i64>() {
2108                                    return Some(self.subst.unify(&num_arg, &Term::Integer(n)));
2109                                } else if let Ok(f) = s.parse::<f64>() {
2110                                    if !f.is_nan() && !f.is_infinite() {
2111                                        return Some(self.subst.unify(&num_arg, &Term::Float(f)));
2112                                    }
2113                                }
2114                            }
2115                        }
2116                        Some(false)
2117                    }
2118                    _ => Some(false),
2119                }
2120            }
2121            BuiltinResult::NumberCodes(num_arg, codes_arg) => {
2122                let wnum = self.subst.walk(&num_arg);
2123                match &wnum {
2124                    Term::Integer(n) => {
2125                        let s = n.to_string();
2126                        let nil_id = self.interner.lookup("[]").expect("[] must be interned");
2127                        let mut list = Term::Atom(nil_id);
2128                        for ch in s.chars().rev() {
2129                            list = Term::List {
2130                                head: Box::new(Term::Integer(ch as i64)),
2131                                tail: Box::new(list),
2132                            };
2133                        }
2134                        Some(self.subst.unify(&codes_arg, &list))
2135                    }
2136                    Term::Float(f) => {
2137                        let s = format_float(*f);
2138                        let nil_id = self.interner.lookup("[]").expect("[] must be interned");
2139                        let mut list = Term::Atom(nil_id);
2140                        for ch in s.chars().rev() {
2141                            list = Term::List {
2142                                head: Box::new(Term::Integer(ch as i64)),
2143                                tail: Box::new(list),
2144                            };
2145                        }
2146                        Some(self.subst.unify(&codes_arg, &list))
2147                    }
2148                    Term::Var(_) => {
2149                        // Reverse: code list -> number
2150                        let wcodes = self.subst.apply(&codes_arg);
2151                        if let Some(elems) = collect_list(&wcodes, &self.interner) {
2152                            let s: Option<String> = elems
2153                                .iter()
2154                                .map(|e| {
2155                                    if let Term::Integer(code) = e {
2156                                        if *code >= 0 && *code <= 0x10FFFF {
2157                                            char::from_u32(*code as u32).map(|c| c.to_string())
2158                                        } else {
2159                                            None
2160                                        }
2161                                    } else {
2162                                        None
2163                                    }
2164                                })
2165                                .collect();
2166                            if let Some(s) = s {
2167                                if let Ok(n) = s.parse::<i64>() {
2168                                    return Some(self.subst.unify(&num_arg, &Term::Integer(n)));
2169                                } else if let Ok(f) = s.parse::<f64>() {
2170                                    if !f.is_nan() && !f.is_infinite() {
2171                                        return Some(self.subst.unify(&num_arg, &Term::Float(f)));
2172                                    }
2173                                }
2174                            }
2175                        }
2176                        Some(false)
2177                    }
2178                    _ => Some(false),
2179                }
2180            }
2181            _ => None, // Unhandled variants
2182        }
2183    }
2184
2185    /// Copy a term with all variables replaced by fresh ones (for copy_term/2).
2186    fn copy_term_fresh(&mut self, term: &Term) -> Term {
2187        let mut var_map: FnvHashMap<VarId, VarId> = FnvHashMap::default();
2188        self.copy_term_impl(term, &mut var_map)
2189    }
2190
2191    fn copy_term_impl(&mut self, term: &Term, var_map: &mut FnvHashMap<VarId, VarId>) -> Term {
2192        let walked = self.subst.walk(term);
2193        match &walked {
2194            Term::Var(id) => {
2195                let new_id = *var_map.entry(*id).or_insert_with(|| {
2196                    let fresh = self.var_counter;
2197                    self.var_counter += 1;
2198                    fresh
2199                });
2200                Term::Var(new_id)
2201            }
2202            Term::Atom(_) | Term::Integer(_) | Term::Float(_) => walked.clone(),
2203            Term::Compound { functor, args } => Term::Compound {
2204                functor: *functor,
2205                args: args
2206                    .iter()
2207                    .map(|a| self.copy_term_impl(a, var_map))
2208                    .collect(),
2209            },
2210            Term::List { .. } => {
2211                // Iterative list spine traversal to avoid stack overflow on long lists.
2212                // We use an owned current value so variable-threaded spines are walked.
2213                let mut heads = Vec::new();
2214                let mut current_owned = walked;
2215                loop {
2216                    match current_owned {
2217                        Term::List { head, tail } => {
2218                            heads.push(self.copy_term_impl(&head, var_map));
2219                            let walked_tail = self.subst.walk(&tail);
2220                            match walked_tail {
2221                                Term::List { .. } => {
2222                                    // Continue iterating with the walked tail
2223                                    current_owned = walked_tail;
2224                                }
2225                                _ => {
2226                                    // Terminal element: copy it and build list
2227                                    let final_tail = self.copy_term_impl(&tail, var_map);
2228                                    let mut result = final_tail;
2229                                    for h in heads.into_iter().rev() {
2230                                        result = Term::List {
2231                                            head: Box::new(h),
2232                                            tail: Box::new(result),
2233                                        };
2234                                    }
2235                                    return result;
2236                                }
2237                            }
2238                        }
2239                        _ => {
2240                            // Reached a non-list (var, atom, etc.)
2241                            let final_tail = self.copy_term_impl(&current_owned, var_map);
2242                            let mut result = final_tail;
2243                            for h in heads.into_iter().rev() {
2244                                result = Term::List {
2245                                    head: Box::new(h),
2246                                    tail: Box::new(result),
2247                                };
2248                            }
2249                            return result;
2250                        }
2251                    }
2252                }
2253            }
2254        }
2255    }
2256
2257    /// Extract the current solution based on query variable bindings.
2258    fn extract_solution(&self) -> Solution {
2259        let mut bindings = Vec::new();
2260        let mut vars: Vec<_> = self.query_vars.iter().collect();
2261        vars.sort_by_key(|(name, _)| name.to_string());
2262        for (name, &var_id) in vars {
2263            if name == "_" {
2264                continue;
2265            }
2266            let resolved = self.subst.apply(&Term::Var(var_id));
2267            bindings.push((name.clone(), resolved));
2268        }
2269        Solution { bindings }
2270    }
2271}
2272
2273/// Format a term as a human-readable string.
2274pub fn term_to_string(term: &Term, interner: &crate::term::StringInterner) -> String {
2275    match term {
2276        Term::Atom(id) => interner.resolve(*id).to_string(),
2277        Term::Var(id) => format!("_{}", id),
2278        Term::Integer(n) => n.to_string(),
2279        Term::Float(f) => format!("{}", f),
2280        Term::Compound { functor, args } => {
2281            let name = interner.resolve(*functor);
2282            if args.len() == 2 {
2283                // Check if it's an infix operator
2284                match name {
2285                    "+" | "-" | "*" | "/" | "mod" | "is" | "=" | "\\=" | "<" | ">" | "=<"
2286                    | ">=" | "=:=" | "=\\=" => {
2287                        return format!(
2288                            "{} {} {}",
2289                            term_to_string(&args[0], interner),
2290                            name,
2291                            term_to_string(&args[1], interner)
2292                        );
2293                    }
2294                    _ => {}
2295                }
2296            }
2297            format!(
2298                "{}({})",
2299                name,
2300                args.iter()
2301                    .map(|a| term_to_string(a, interner))
2302                    .collect::<Vec<_>>()
2303                    .join(", ")
2304            )
2305        }
2306        Term::List { head, tail } => {
2307            let mut elements = vec![term_to_string(head, interner)];
2308            let mut current = tail.as_ref();
2309            loop {
2310                match current {
2311                    Term::List { head, tail } => {
2312                        elements.push(term_to_string(head, interner));
2313                        current = tail;
2314                    }
2315                    Term::Atom(id) if interner.resolve(*id) == "[]" => {
2316                        return format!("[{}]", elements.join(", "));
2317                    }
2318                    _ => {
2319                        return format!(
2320                            "[{}|{}]",
2321                            elements.join(", "),
2322                            term_to_string(current, interner)
2323                        );
2324                    }
2325                }
2326            }
2327        }
2328    }
2329}
2330
2331#[cfg(test)]
2332mod tests {
2333    use super::*;
2334    use crate::database::CompiledDatabase;
2335    use crate::parser::Parser;
2336
2337    fn query(source: &str, query_str: &str) -> Vec<Solution> {
2338        let mut interner = crate::term::StringInterner::new();
2339        let clauses = Parser::parse_program(source, &mut interner).unwrap();
2340        let (goals, vars) = Parser::parse_query_with_vars(query_str, &mut interner).unwrap();
2341        let db = CompiledDatabase::new(interner, clauses);
2342        let solver = Solver::new(&db, goals, vars);
2343        solver.all_solutions().unwrap()
2344    }
2345
2346    fn query_first_binding(source: &str, query_str: &str, var_name: &str) -> Option<String> {
2347        let mut interner = crate::term::StringInterner::new();
2348        let clauses = Parser::parse_program(source, &mut interner).unwrap();
2349        let (goals, vars) = Parser::parse_query_with_vars(query_str, &mut interner).unwrap();
2350        let db = CompiledDatabase::new(interner, clauses);
2351        let solver = Solver::new(&db, goals, vars);
2352        let (solutions, solver_interner) = solver.all_solutions_with_interner().unwrap();
2353        solutions.first().and_then(|sol| {
2354            sol.bindings
2355                .iter()
2356                .find(|(name, _)| name == var_name)
2357                .map(|(_, term)| term_to_string(term, &solver_interner))
2358        })
2359    }
2360
2361    #[test]
2362    fn test_simple_fact() {
2363        let solutions = query("likes(mary, food).", "likes(mary, food)");
2364        assert_eq!(solutions.len(), 1);
2365        assert!(solutions[0].bindings.is_empty()); // no variables
2366    }
2367
2368    #[test]
2369    fn test_fact_negative() {
2370        let solutions = query("likes(mary, food).", "likes(mary, beer)");
2371        assert_eq!(solutions.len(), 0);
2372    }
2373
2374    #[test]
2375    fn test_variable_binding() {
2376        let result = query_first_binding("likes(mary, food).", "likes(mary, X)", "X");
2377        assert_eq!(result, Some("food".to_string()));
2378    }
2379
2380    #[test]
2381    fn test_simple_rule() {
2382        let source = "likes(mary, food). happy(X) :- likes(X, food).";
2383        let solutions = query(source, "happy(mary)");
2384        assert_eq!(solutions.len(), 1);
2385    }
2386
2387    #[test]
2388    fn test_multiple_solutions() {
2389        let source = "parent(tom, mary). parent(tom, james). parent(tom, ann).";
2390        let solutions = query(source, "parent(tom, X)");
2391        assert_eq!(solutions.len(), 3);
2392    }
2393
2394    #[test]
2395    fn test_recursive_rule() {
2396        let source = r#"
2397            parent(tom, mary).
2398            parent(mary, ann).
2399            ancestor(X, Y) :- parent(X, Y).
2400            ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
2401        "#;
2402        let solutions = query(source, "ancestor(tom, ann)");
2403        assert!(solutions.len() >= 1);
2404    }
2405
2406    #[test]
2407    fn test_grandparent() {
2408        let source = r#"
2409            parent(tom, mary).
2410            parent(mary, ann).
2411            grandparent(X, Z) :- parent(X, Y), parent(Y, Z).
2412        "#;
2413        let result = query_first_binding(source, "grandparent(tom, X)", "X");
2414        assert_eq!(result, Some("ann".to_string()));
2415    }
2416
2417    #[test]
2418    fn test_arithmetic_is() {
2419        let source = "add(X, Y, Z) :- Z is X + Y.";
2420        let result = query_first_binding(source, "add(3, 4, X)", "X");
2421        assert_eq!(result, Some("7".to_string()));
2422    }
2423
2424    #[test]
2425    fn test_arithmetic_multiply() {
2426        let source = "double(X, Y) :- Y is X * 2.";
2427        let result = query_first_binding(source, "double(5, X)", "X");
2428        assert_eq!(result, Some("10".to_string()));
2429    }
2430
2431    #[test]
2432    fn test_comparison_positive() {
2433        let source = "big(X) :- X > 100.";
2434        let solutions = query(source, "big(200)");
2435        assert_eq!(solutions.len(), 1);
2436    }
2437
2438    #[test]
2439    fn test_comparison_negative() {
2440        let source = "big(X) :- X > 100.";
2441        let solutions = query(source, "big(50)");
2442        assert_eq!(solutions.len(), 0);
2443    }
2444
2445    #[test]
2446    fn test_operator_precedence() {
2447        let source = "result(X) :- X is 2 + 3 * 4.";
2448        let result = query_first_binding(source, "result(X)", "X");
2449        assert_eq!(result, Some("14".to_string()));
2450    }
2451
2452    #[test]
2453    fn test_mod_operator() {
2454        let source = "remainder(X, Y, Z) :- Z is X mod Y.";
2455        let result = query_first_binding(source, "remainder(10, 3, X)", "X");
2456        assert_eq!(result, Some("1".to_string()));
2457    }
2458
2459    #[test]
2460    fn test_nested_compound() {
2461        let source = "outer(inner(deep(hello))).";
2462        let solutions = query(source, "outer(inner(deep(hello)))");
2463        assert_eq!(solutions.len(), 1);
2464    }
2465
2466    #[test]
2467    fn test_nested_compound_with_var() {
2468        let source = "outer(inner(deep(hello))).";
2469        let result = query_first_binding(source, "outer(inner(deep(X)))", "X");
2470        assert_eq!(result, Some("hello".to_string()));
2471    }
2472
2473    #[test]
2474    fn test_negation_as_failure() {
2475        let source = r#"
2476            likes(mary, food).
2477            likes(mary, wine).
2478            dislikes(X, Y) :- \+ likes(X, Y).
2479        "#;
2480        let solutions = query(source, "dislikes(mary, beer)");
2481        assert_eq!(solutions.len(), 1);
2482
2483        let solutions = query(source, "dislikes(mary, food)");
2484        assert_eq!(solutions.len(), 0);
2485    }
2486
2487    #[test]
2488    fn test_cut() {
2489        let source = r#"
2490            max(X, Y, X) :- X >= Y, !.
2491            max(X, Y, Y).
2492        "#;
2493        // max(3, 5, Z) should give Z = 5 (first clause fails, second succeeds)
2494        let result = query_first_binding(source, "max(3, 5, Z)", "Z");
2495        assert_eq!(result, Some("5".to_string()));
2496
2497        // max(5, 3, Z) should give Z = 5 (first clause succeeds + cut)
2498        let result = query_first_binding(source, "max(5, 3, Z)", "Z");
2499        assert_eq!(result, Some("5".to_string()));
2500    }
2501
2502    #[test]
2503    fn test_index_multi_predicate() {
2504        let source = "color(red). color(blue). color(green). shape(circle). shape(square).";
2505        let solutions = query(source, "shape(circle)");
2506        assert_eq!(solutions.len(), 1);
2507
2508        let solutions = query(source, "color(circle)");
2509        assert_eq!(solutions.len(), 0);
2510    }
2511
2512    #[test]
2513    fn test_index_first_arg() {
2514        let source = r#"
2515            component(engine, piston).
2516            component(engine, crankshaft).
2517            component(engine, valve).
2518            component(brake, pad).
2519            component(brake, rotor).
2520            component(wheel, tire).
2521            component(wheel, rim).
2522        "#;
2523        let result = query_first_binding(source, "component(brake, X)", "X");
2524        assert_eq!(result, Some("pad".to_string()));
2525
2526        let solutions = query(source, "component(engine, X)");
2527        assert_eq!(solutions.len(), 3);
2528
2529        let solutions = query(source, "component(transmission, X)");
2530        assert_eq!(solutions.len(), 0);
2531    }
2532
2533    #[test]
2534    fn test_index_variable_fallback() {
2535        let source = r#"
2536            component(engine, piston).
2537            component(brake, pad).
2538            component(wheel, tire).
2539        "#;
2540        let solutions = query(source, "component(X, tire)");
2541        assert!(solutions.len() >= 1);
2542        let result = query_first_binding(source, "component(X, tire)", "X");
2543        assert_eq!(result, Some("wheel".to_string()));
2544    }
2545
2546    #[test]
2547    fn test_mixed_ground_var_clauses() {
2548        let source = r#"
2549            lookup(a, 1).
2550            lookup(b, 2).
2551            lookup(c, 3).
2552            lookup(X, 0) :- X = default.
2553        "#;
2554        let result = query_first_binding(source, "lookup(b, X)", "X");
2555        assert_eq!(result, Some("2".to_string()));
2556
2557        let result = query_first_binding(source, "lookup(default, X)", "X");
2558        assert_eq!(result, Some("0".to_string()));
2559    }
2560
2561    #[test]
2562    fn test_disjunction() {
2563        let source = "color(red). color(blue).";
2564        let solutions = query(source, "( color(red) ; color(green) )");
2565        assert_eq!(solutions.len(), 1);
2566    }
2567
2568    #[test]
2569    fn test_disjunction_both_branches() {
2570        let source = "color(red). color(blue).";
2571        let solutions = query(source, "( color(red) ; color(blue) )");
2572        assert_eq!(solutions.len(), 2);
2573    }
2574
2575    #[test]
2576    fn test_if_then_else_true() {
2577        // If 1 < 2, then X = yes, else X = no
2578        let source = "";
2579        let result = query_first_binding(source, "(1 < 2 -> X = yes ; X = no)", "X");
2580        assert_eq!(result, Some("yes".to_string()));
2581    }
2582
2583    #[test]
2584    fn test_if_then_else_false() {
2585        let source = "";
2586        let result = query_first_binding(source, "(2 < 1 -> X = yes ; X = no)", "X");
2587        assert_eq!(result, Some("no".to_string()));
2588    }
2589
2590    #[test]
2591    fn test_findall_basic() {
2592        let source = "color(red). color(green). color(blue).";
2593        let result = query_first_binding(source, "findall(X, color(X), L)", "L");
2594        assert_eq!(result, Some("[red, green, blue]".to_string()));
2595    }
2596
2597    #[test]
2598    fn test_findall_empty() {
2599        let source = "color(red).";
2600        let result = query_first_binding(source, "findall(X, shape(X), L)", "L");
2601        assert_eq!(result, Some("[]".to_string()));
2602    }
2603
2604    #[test]
2605    fn test_findall_with_rule() {
2606        let source = r#"
2607            parent(tom, mary). parent(tom, james). parent(tom, ann).
2608        "#;
2609        let result = query_first_binding(source, "findall(C, parent(tom, C), Kids)", "Kids");
2610        assert_eq!(result, Some("[mary, james, ann]".to_string()));
2611    }
2612
2613    #[test]
2614    fn test_if_then_no_else() {
2615        let source = "";
2616        let solutions = query(source, "(1 < 2 -> true)");
2617        assert_eq!(solutions.len(), 1);
2618
2619        let solutions = query(source, "(2 < 1 -> true)");
2620        assert_eq!(solutions.len(), 0);
2621    }
2622
2623    #[test]
2624    fn test_solution_limit() {
2625        let source = "n(1). n(2). n(3). n(4). n(5).";
2626        let mut interner = crate::term::StringInterner::new();
2627        let clauses = Parser::parse_program(source, &mut interner).unwrap();
2628        let (goals, vars) = Parser::parse_query_with_vars("n(X)", &mut interner).unwrap();
2629        let db = CompiledDatabase::new(interner, clauses);
2630        let solver = Solver::new(&db, goals, vars).with_limit(3);
2631        let solutions = solver.all_solutions().unwrap();
2632        assert_eq!(solutions.len(), 3);
2633    }
2634
2635    #[test]
2636    fn test_depth_limit() {
2637        // Create an infinite loop: loop :- loop.
2638        let source = "loop :- loop.";
2639        let mut interner = crate::term::StringInterner::new();
2640        let clauses = Parser::parse_program(source, &mut interner).unwrap();
2641        let (goals, vars) = Parser::parse_query_with_vars("loop", &mut interner).unwrap();
2642        let db = CompiledDatabase::new(interner, clauses);
2643        let mut solver = Solver::new(&db, goals, vars).with_max_depth(100);
2644        let result = solver.next();
2645        assert!(matches!(result, SolveResult::Error(ref e) if e.contains("step limit")));
2646    }
2647
2648    // Phase 3 tests: once/1, call/1, atom predicates, arithmetic functions
2649
2650    #[test]
2651    fn test_once_basic() {
2652        let source = "color(red). color(green). color(blue).";
2653        // once/1 should return only the first solution
2654        let solutions = query(source, "once(color(X))");
2655        assert_eq!(solutions.len(), 1);
2656    }
2657
2658    #[test]
2659    fn test_once_prevents_backtracking() {
2660        let source = "n(1). n(2). n(3).";
2661        let result = query_first_binding(source, "once(n(X))", "X");
2662        assert_eq!(result, Some("1".to_string()));
2663    }
2664
2665    #[test]
2666    fn test_once_fails_if_goal_fails() {
2667        let source = "color(red).";
2668        let solutions = query(source, "once(shape(X))");
2669        assert_eq!(solutions.len(), 0);
2670    }
2671
2672    #[test]
2673    fn test_call_basic() {
2674        let source = "color(red). color(blue).";
2675        let solutions = query(source, "call(color(red))");
2676        assert_eq!(solutions.len(), 1);
2677    }
2678
2679    #[test]
2680    fn test_call_with_variable() {
2681        let source = "color(red). color(blue). color(green).";
2682        let solutions = query(source, "call(color(X))");
2683        assert_eq!(solutions.len(), 3);
2684    }
2685
2686    #[test]
2687    fn test_call_fails() {
2688        let source = "color(red).";
2689        let solutions = query(source, "call(shape(X))");
2690        assert_eq!(solutions.len(), 0);
2691    }
2692
2693    #[test]
2694    fn test_atom_length() {
2695        let source = "";
2696        let result = query_first_binding(source, "atom_length(hello, N)", "N");
2697        assert_eq!(result, Some("5".to_string()));
2698    }
2699
2700    #[test]
2701    fn test_atom_length_empty() {
2702        let source = "";
2703        // Parse an empty atom using quotes
2704        let result = query_first_binding(source, "atom_length('', N)", "N");
2705        assert_eq!(result, Some("0".to_string()));
2706    }
2707
2708    #[test]
2709    fn test_atom_concat() {
2710        let source = "";
2711        let result = query_first_binding(source, "atom_concat(hello, world, X)", "X");
2712        assert_eq!(result, Some("helloworld".to_string()));
2713    }
2714
2715    #[test]
2716    fn test_atom_chars() {
2717        let source = "";
2718        let result = query_first_binding(source, "atom_chars(abc, X)", "X");
2719        assert_eq!(result, Some("[a, b, c]".to_string()));
2720    }
2721
2722    #[test]
2723    fn test_atom_chars_single() {
2724        let source = "";
2725        let result = query_first_binding(source, "atom_chars(x, X)", "X");
2726        assert_eq!(result, Some("[x]".to_string()));
2727    }
2728
2729    #[test]
2730    fn test_arith_abs_in_rule() {
2731        let source = "dist(X, Y, D) :- D is abs(X - Y).";
2732        let result = query_first_binding(source, "dist(3, 7, D)", "D");
2733        assert_eq!(result, Some("4".to_string()));
2734    }
2735
2736    #[test]
2737    fn test_arith_max_in_rule() {
2738        let source = "bigger(X, Y, Z) :- Z is max(X, Y).";
2739        let result = query_first_binding(source, "bigger(3, 7, Z)", "Z");
2740        assert_eq!(result, Some("7".to_string()));
2741    }
2742
2743    #[test]
2744    fn test_arith_min_in_rule() {
2745        let source = "smaller(X, Y, Z) :- Z is min(X, Y).";
2746        let result = query_first_binding(source, "smaller(3, 7, Z)", "Z");
2747        assert_eq!(result, Some("3".to_string()));
2748    }
2749
2750    #[test]
2751    fn test_arith_sign_in_rule() {
2752        let source = "direction(X, S) :- S is sign(X).";
2753        let result = query_first_binding(source, "direction(-42, S)", "S");
2754        assert_eq!(result, Some("-1".to_string()));
2755    }
2756}