scryer_prolog/machine/lib_machine/
mod.rs

1use std::cmp::Ordering;
2use std::collections::BTreeMap;
3use std::rc::Rc;
4
5use crate::atom_table;
6use crate::heap_iter::{stackful_post_order_iter, NonListElider};
7use crate::machine::machine_indices::VarKey;
8use crate::machine::mock_wam::CompositeOpDir;
9use crate::machine::{
10    ArenaHeaderTag, Fixnum, Number, BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS,
11};
12use crate::offset_table::*;
13use crate::parser::ast::{Var, VarPtr};
14use crate::parser::parser::{Parser, Tokens};
15use crate::read::{write_term_to_heap, TermWriteResult};
16use crate::types::UntypedArenaPtr;
17
18use dashu::{Integer, Rational};
19use indexmap::IndexMap;
20
21use super::{streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine};
22
23#[cfg(test)]
24mod tests;
25
26/// Represents a leaf answer from a query.
27#[derive(Debug, Clone, PartialEq)]
28pub enum LeafAnswer {
29    /// A `true` leaf answer.
30    True,
31    /// A `false` leaf answer.
32    ///
33    /// This means that there are no more answers for the query.
34    False,
35    /// An exception leaf answer.
36    Exception(Term),
37    /// A leaf answer with bindings.
38    #[non_exhaustive]
39    LeafAnswer {
40        /// The bindings of variables in the query.
41        bindings: BTreeMap<String, Term>,
42        //residual_goals: Vec<Term>,
43    },
44}
45
46impl LeafAnswer {
47    /// Creates a leaf answer with no residual goals.
48    pub fn from_bindings<S: Into<String>>(bindings: impl IntoIterator<Item = (S, Term)>) -> Self {
49        LeafAnswer::LeafAnswer {
50            bindings: bindings.into_iter().map(|(k, v)| (k.into(), v)).collect(),
51        }
52    }
53}
54
55/// Represents a Prolog term.
56#[non_exhaustive]
57#[derive(Debug, Clone, PartialEq)]
58pub enum Term {
59    /// An arbitrary precision integer.
60    Integer(Integer),
61    /// An arbitrary precision rational.
62    Rational(Rational),
63    /// A float.
64    Float(f64),
65    /// A Prolog atom.
66    Atom(String),
67    /// A Prolog string.
68    ///
69    /// In particular, this represents Prolog lists of characters.
70    String(String),
71    /// A Prolog list.
72    List(Vec<Term>),
73    /// A Prolog compound term.
74    Compound(String, Vec<Term>),
75    /// A Prolog variable.
76    Var(String),
77}
78
79impl Term {
80    /// Creates an integer term.
81    pub fn integer(value: impl Into<Integer>) -> Self {
82        Term::Integer(value.into())
83    }
84
85    /// Creates a rational term.
86    pub fn rational(value: impl Into<Rational>) -> Self {
87        Term::Rational(value.into())
88    }
89
90    /// Creates a float term.
91    pub fn float(value: impl Into<f64>) -> Self {
92        Term::Float(value.into())
93    }
94
95    /// Creates an atom term.
96    pub fn atom(value: impl Into<String>) -> Self {
97        Term::Atom(value.into())
98    }
99
100    /// Creates a string term.
101    ///
102    /// In specific, this represents a list of chars in Prolog.
103    pub fn string(value: impl Into<String>) -> Self {
104        Term::String(value.into())
105    }
106
107    /// Creates a list term.
108    pub fn list(value: impl IntoIterator<Item = Term>) -> Self {
109        Term::List(value.into_iter().collect())
110    }
111
112    /// Creates a compound term.
113    pub fn compound(functor: impl Into<String>, args: impl IntoIterator<Item = Term>) -> Self {
114        Term::Compound(functor.into(), args.into_iter().collect())
115    }
116
117    /// Creates a variable.
118    pub fn variable(value: impl Into<String>) -> Self {
119        Term::Var(value.into())
120    }
121
122    /// Creates a conjunction, giving the atom `true` if empty.
123    pub fn conjunction(value: impl IntoIterator<Item = Term>) -> Self {
124        Term::try_conjunction(value).unwrap_or(Term::atom("true"))
125    }
126
127    /// Creates a conjunction, giving `None` if empty.
128    pub fn try_conjunction(value: impl IntoIterator<Item = Term>) -> Option<Self> {
129        let mut iter = value.into_iter();
130        iter.next().map(|first| {
131            Term::try_conjunction(iter)
132                .map(|rest| Term::compound(",", [first.clone(), rest]))
133                .unwrap_or(first)
134        })
135    }
136
137    /// Creates a disjunction, giving the atom `false` if empty.
138    pub fn disjunction(value: impl IntoIterator<Item = Term>) -> Self {
139        Term::try_disjunction(value).unwrap_or(Term::atom("false"))
140    }
141
142    /// Creates a disjunction, giving `None` if empty.
143    pub fn try_disjunction(value: impl IntoIterator<Item = Term>) -> Option<Self> {
144        let mut iter = value.into_iter();
145        iter.next().map(|first| {
146            Term::try_disjunction(iter)
147                .map(|rest| Term::compound(";", [first.clone(), rest]))
148                .unwrap_or(first)
149        })
150    }
151}
152
153/// This is an auxiliary function to turn a count into names of anonymous variables like _A, _B,
154/// _AB, etc...
155fn count_to_letter_code(mut count: usize) -> String {
156    let mut letters = Vec::new();
157
158    loop {
159        let letter_idx = (count % 26) as u32;
160        letters.push(char::from_u32('A' as u32 + letter_idx).unwrap());
161        count /= 26;
162
163        if count == 0 {
164            break;
165        }
166    }
167
168    letters.into_iter().chain("_".chars()).rev().collect()
169}
170
171impl Term {
172    pub(crate) fn from_heapcell(
173        machine: &mut Machine,
174        heap_cell: HeapCellValue,
175        var_names: &mut IndexMap<HeapCellValue, VarPtr>,
176    ) -> Self {
177        // Adapted from MachineState::read_term_from_heap
178        let mut term_stack = vec![];
179
180        machine.machine_st.heap[0] = heap_cell;
181
182        let mut iter = stackful_post_order_iter::<NonListElider>(
183            &mut machine.machine_st.heap,
184            &mut machine.machine_st.stack,
185            0,
186        );
187
188        let mut anon_count: usize = 0;
189        let var_ptr_cmp = |a, b| match a {
190            Var::Named(name_a) => match b {
191                Var::Named(name_b) => name_a.cmp(&name_b),
192                _ => Ordering::Less,
193            },
194            _ => match b {
195                Var::Named(_) => Ordering::Greater,
196                _ => Ordering::Equal,
197            },
198        };
199
200        while let Some(addr) = iter.next() {
201            let addr = unmark_cell_bits!(addr);
202
203            read_heap_cell!(addr,
204                (HeapCellValueTag::Lis) => {
205                    let tail = term_stack.pop().unwrap();
206                    let head = term_stack.pop().unwrap();
207
208                    let list = match tail {
209                        Term::Atom(atom) if atom == "[]" => match head {
210                            Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
211                                // Handle lists of char as strings
212                                Term::String(a.to_string())
213                            }
214                            _ => Term::List(vec![head]),
215                        },
216                        Term::List(elems) if elems.is_empty() => match head {
217                            Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
218                                // Handle lists of char as strings
219                                Term::String(a.to_string())
220                            },
221                            _ => Term::List(vec![head]),
222                        },
223                        Term::List(mut elems) => {
224                            elems.insert(0, head);
225                            Term::List(elems)
226                        },
227                        Term::String(mut elems) => match head {
228                            Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
229                                // Handle lists of char as strings
230                                elems.insert(0, a.chars().next().unwrap());
231                                Term::String(elems)
232                            },
233                            _ => {
234                                let mut elems: Vec<Term> = elems
235                                    .chars()
236                                    .map(|x| Term::Atom(x.into()))
237                                    .collect();
238                                elems.insert(0, head);
239                                Term::List(elems)
240                            }
241                        },
242                        _ => {
243                            Term::Compound(".".into(), vec![head, tail])
244                        }
245                    };
246                    term_stack.push(list);
247                }
248                (HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar) => {
249                    let var = var_names.get(&addr).map(|x| x.borrow().clone());
250                    match var {
251                        Some(Var::Named(name)) => term_stack.push(Term::Var(name.as_ref().to_owned())),
252                        _ => {
253                            let anon_name = loop {
254                                // Generate a name for the anonymous variable
255                                let anon_name = Rc::new(count_to_letter_code(anon_count));
256
257                                // Find if this name is already being used
258                                var_names.sort_by(|_, a, _, b| {
259                                    var_ptr_cmp(a.borrow().clone(), b.borrow().clone())
260                                });
261                                let binary_result = var_names.binary_search_by(|_,a| {
262                                    let var_ptr = Var::Named(anon_name.clone());
263                                    var_ptr_cmp(a.borrow().clone(), var_ptr.clone())
264                                });
265
266                                match binary_result {
267                                    Ok(_) => anon_count += 1, // Name already used
268                                    Err(_) => {
269                                        // Name not used, assign it to this variable
270                                        let var_ptr = VarPtr::from(Var::Named(anon_name.clone()));
271                                        var_names.insert(addr, var_ptr);
272                                        break anon_name;
273                                    },
274                                }
275                            };
276                            term_stack.push(Term::Var(anon_name.as_ref().to_owned()));
277                        },
278                    }
279                }
280                (HeapCellValueTag::F64Offset, offset) => {
281                    let f = machine.machine_st.arena.f64_tbl.get_entry(offset);
282                    term_stack.push(Term::Float(f.into()));
283                }
284                (HeapCellValueTag::Fixnum, n) => {
285                    term_stack.push(Term::Integer(n.into()));
286                }
287                (HeapCellValueTag::Cons, ptr) => {
288                    if let Ok(n) = Number::try_from((addr, &machine.machine_st.arena.f64_tbl)) {
289                        match n {
290                            Number::Integer(i) => term_stack.push(Term::Integer((*i).clone())),
291                            Number::Rational(r) => term_stack.push(Term::Rational((*r).clone())),
292                            _ => { unreachable!() },
293                        }
294                    } else {
295                        match_untyped_arena_ptr!(ptr,
296                            (ArenaHeaderTag::Stream, stream) => {
297                                let stream_term = if let Some(alias) = stream.options().get_alias() {
298                                    Term::atom(alias.as_str().to_string())
299                                } else {
300                                    Term::compound("$stream", [
301                                        Term::integer(stream.as_ptr().addr())
302                                    ])
303                                };
304                                term_stack.push(stream_term);
305                            }
306                            (ArenaHeaderTag::Dropped, _stream) => {
307                                term_stack.push(Term::atom("$dropped_value"));
308                            }
309                            _ => {
310                                unreachable!();
311                            }
312                        );
313                    }
314                }
315                (HeapCellValueTag::Atom, (name, arity)) => {
316                    if arity == 0 {
317                        let atom_name = name.as_str().to_string();
318                        if atom_name == "[]" {
319                            term_stack.push(Term::List(vec![]));
320                        } else {
321                            term_stack.push(Term::Atom(atom_name));
322                        }
323                    } else {
324                        let subterms = term_stack
325                            .drain(term_stack.len() - arity ..)
326                            .collect();
327
328                        term_stack.push(Term::Compound(name.as_str().to_string(), subterms));
329                    }
330                }
331                (HeapCellValueTag::PStrLoc, pstr_loc) => {
332                    let tail = term_stack.pop().unwrap();
333                    let char_iter = iter.base_iter.heap.char_iter(pstr_loc);
334
335                    match tail {
336                        Term::Atom(atom) => {
337                            if atom == "[]" {
338                                term_stack.push(Term::String(atom.as_str().to_string()));
339                            }
340                        },
341                        Term::List(l) if l.is_empty() => {
342                            term_stack.push(Term::String(char_iter.collect()));
343                        }
344                        Term::List(l) => {
345                            let mut list: Vec<Term> = char_iter
346                                .map(|x| Term::Atom(x.to_string()))
347                                .collect();
348                            list.extend(l.into_iter());
349                            term_stack.push(Term::List(list));
350                        },
351                        _ => {
352                            let mut list: Vec<Term> = char_iter
353                                .map(|x| Term::Atom(x.to_string()))
354                                .collect();
355
356                            let mut partial_list = Term::Compound(
357                                ".".into(),
358                                vec![
359                                    list.pop().unwrap(),
360                                    tail,
361                                ],
362                            );
363
364                            while let Some(last) = list.pop() {
365                                partial_list = Term::Compound(
366                                    ".".into(),
367                                    vec![
368                                        last,
369                                        partial_list,
370                                    ],
371                                );
372                            }
373
374                            term_stack.push(partial_list);
375                        }
376                    }
377                }
378                _ => {
379                    unreachable!();
380                }
381            );
382        }
383
384        debug_assert_eq!(term_stack.len(), 1);
385        term_stack.pop().unwrap()
386    }
387}
388
389/// An iterator though the leaf answers of a query.
390pub struct QueryState<'a> {
391    machine: &'a mut Machine,
392    term: TermWriteResult,
393    stub_b: usize,
394    var_names: IndexMap<HeapCellValue, VarPtr>,
395    called: bool,
396}
397
398impl Drop for QueryState<'_> {
399    fn drop(&mut self) {
400        // FIXME: This may be wrong if the iterator is not fully consumend, but from testing it
401        // seems fine. Is this really ok?
402        self.machine.trust_me();
403    }
404}
405
406impl Iterator for QueryState<'_> {
407    type Item = Result<LeafAnswer, Term>;
408
409    fn next(&mut self) -> Option<Self::Item> {
410        let var_names = &mut self.var_names;
411        let term_write_result = &self.term;
412        let machine = &mut self.machine;
413
414        // No more choicepoints, end iteration
415        if self.called && machine.machine_st.b <= self.stub_b {
416            return None;
417        }
418
419        machine.dispatch_loop();
420
421        self.called = true;
422
423        if !machine.machine_st.ball.stub.is_empty() {
424            // NOTE: this means an exception was thrown, at which
425            // point we backtracked to the stub choice point.
426            // this should halt the search for solutions as it
427            // does in the Scryer top-level. the exception term is
428            // contained in self.machine_st.ball.
429            let h = machine.machine_st.heap.cell_len();
430
431            if let Err(resource_err_loc) = machine
432                .machine_st
433                .heap
434                .append(&machine.machine_st.ball.stub)
435            {
436                return Some(Err(Term::from_heapcell(
437                    machine,
438                    machine.machine_st.heap[resource_err_loc],
439                    &mut IndexMap::new(),
440                )));
441            }
442
443            let exception_term =
444                Term::from_heapcell(machine, machine.machine_st.heap[h], &mut var_names.clone());
445
446            if let Term::Compound(functor, args) = &exception_term {
447                if functor == "error" && args.len() == 2 {
448                    // We have an error
449                    return Some(Err(exception_term));
450                }
451            }
452
453            // We have an exception that is not an error
454            return Some(Ok(LeafAnswer::Exception(exception_term)));
455        }
456
457        if machine.machine_st.p == LIB_QUERY_SUCCESS {
458            if term_write_result.var_dict.is_empty() {
459                self.machine.machine_st.backtrack();
460                return Some(Ok(LeafAnswer::True));
461            }
462        } else if machine.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC {
463            return Some(Ok(LeafAnswer::False));
464        }
465
466        let mut bindings: BTreeMap<String, Term> = BTreeMap::new();
467
468        let var_dict = &term_write_result.var_dict;
469
470        for (var_key, term_to_be_printed) in var_dict.iter() {
471            let mut var_name = var_key.to_string();
472            if var_name.starts_with('_') {
473                let should_print = var_names.values().any(|x| match x.borrow().clone() {
474                    Var::Named(v) => *v == *var_name,
475                    _ => false,
476                });
477                if !should_print {
478                    continue;
479                }
480            }
481
482            let mut term =
483                Term::from_heapcell(machine, *term_to_be_printed, &mut var_names.clone());
484
485            if let Term::Var(ref term_str) = term {
486                if *term_str == var_name {
487                    continue;
488                }
489
490                // Var dict is in the order things appear in the query. If var_name appears
491                // after term in the query, switch their places.
492                let var_name_idx = var_dict
493                    .get_index_of(&VarKey::VarPtr(Var::from(var_name.clone()).into()))
494                    .unwrap();
495                let term_idx =
496                    var_dict.get_index_of(&VarKey::VarPtr(Var::from(term_str.clone()).into()));
497                if let Some(idx) = term_idx {
498                    if idx < var_name_idx {
499                        let new_term = Term::Var(var_name);
500                        let new_var_name = term_str.into();
501                        term = new_term;
502                        var_name = new_var_name;
503                    }
504                }
505            }
506
507            bindings.insert(var_name, term);
508        }
509
510        // NOTE: there are outstanding choicepoints, backtrack
511        // through them for further solutions. if
512        // self.machine_st.b == stub_b we've backtracked to the stub
513        // choice point, so we should break.
514        self.machine.machine_st.backtrack();
515
516        Some(Ok(LeafAnswer::LeafAnswer { bindings }))
517    }
518}
519
520impl Machine {
521    /// Loads a module into the [`Machine`] from a string.
522    pub fn load_module_string(&mut self, module_name: &str, program: impl Into<String>) {
523        let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena);
524        self.load_file(module_name, stream);
525    }
526
527    /// Consults a module into the [`Machine`] from a string.
528    pub fn consult_module_string(&mut self, module_name: &str, program: impl Into<String>) {
529        let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena);
530        self.machine_st.registers[1] = stream_as_cell!(stream);
531        self.machine_st.registers[2] = atom_as_cell!(&atom_table::AtomTable::build_with(
532            &self.machine_st.atom_tbl,
533            module_name
534        ));
535
536        self.run_module_predicate(atom!("loader"), (atom!("consult_stream"), 2));
537    }
538
539    pub(crate) fn allocate_stub_choice_point(&mut self) {
540        // NOTE: create a choice point to terminate the dispatch_loop
541        // if an exception is thrown.
542
543        let stub_b = self.machine_st.stack.allocate_or_frame(0);
544        let or_frame = self.machine_st.stack.index_or_frame_mut(stub_b);
545
546        or_frame.prelude.num_cells = 0;
547        or_frame.prelude.e = 0;
548        or_frame.prelude.cp = 0;
549        or_frame.prelude.b = 0;
550        or_frame.prelude.bp = BREAK_FROM_DISPATCH_LOOP_LOC;
551        or_frame.prelude.boip = 0;
552        or_frame.prelude.biip = 0;
553        or_frame.prelude.tr = 0;
554        or_frame.prelude.h = 0;
555        or_frame.prelude.b0 = 0;
556        or_frame.prelude.attr_var_queue_len = 0;
557
558        self.machine_st.b = stub_b;
559        self.machine_st.hb = self.machine_st.heap.cell_len();
560        self.machine_st.block = stub_b;
561    }
562
563    /// Runs a query.
564    pub fn run_query(&mut self, query: impl Into<String>) -> QueryState<'_> {
565        let mut parser = Parser::new(
566            Stream::from_owned_string(query.into(), &mut self.machine_st.arena),
567            &mut self.machine_st,
568        );
569        let op_dir = CompositeOpDir::new(&self.indices.op_dir, None);
570        let term = parser
571            .read_term(&op_dir, Tokens::Default)
572            .expect("Failed to parse query");
573
574        self.allocate_stub_choice_point();
575
576        // Write parsed term to heap
577        let term_write_result = write_term_to_heap(&term, &mut self.machine_st.heap)
578            .expect("couldn't write term to heap");
579
580        let var_names: IndexMap<_, _> = term_write_result
581            .var_dict
582            .iter()
583            .map(|(var_key, cell)| match var_key {
584                // NOTE: not the intention behind Var::InSitu here but
585                // we can hijack it to store anonymous variables
586                // without creating problems.
587                VarKey::AnonVar(h) => (*cell, VarPtr::from(Var::InSitu(*h))),
588                VarKey::VarPtr(var_ptr) => (*cell, var_ptr.clone()),
589            })
590            .collect();
591
592        // Write term to heap
593        self.machine_st.registers[1] = self.machine_st.heap[term_write_result.heap_loc];
594
595        self.machine_st.cp = LIB_QUERY_SUCCESS; // BREAK_FROM_DISPATCH_LOOP_LOC;
596        let call_index_p = self
597            .indices
598            .code_dir
599            .get(&(atom!("call"), 1))
600            .cloned()
601            .map(|offset| {
602                self.machine_st
603                    .arena
604                    .code_index_tbl
605                    .get_entry(offset.into())
606                    .p() as usize
607            })
608            .expect("couldn't get code index");
609
610        self.machine_st.execute_at_index(1, call_index_p);
611
612        let stub_b = self.machine_st.b;
613        QueryState {
614            machine: self,
615            term: term_write_result,
616            stub_b,
617            var_names,
618            called: false,
619        }
620    }
621}