From 86a2c5e2ed86c2dc47b01168dd865b1147e2a64c Mon Sep 17 00:00:00 2001 From: bakaq Date: Wed, 14 Aug 2024 18:39:59 -0300 Subject: [PATCH 1/3] Add run_query_iter() --- src/machine/lib_machine.rs | 173 ++++++++++++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 1 deletion(-) diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index 8480950bc..f068c9083 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -8,7 +8,7 @@ use crate::machine::mock_wam::CompositeOpDir; use crate::machine::{BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS}; use crate::parser::ast::{Var, VarPtr}; use crate::parser::parser::{Parser, Tokens}; -use crate::read::write_term_to_heap; +use crate::read::{write_term_to_heap, TermWriteResult}; use indexmap::IndexMap; use super::{ @@ -16,6 +16,124 @@ use super::{ QueryResolution, QueryResolutionLine, QueryResult, Value, }; +pub struct QueryState<'a> { + machine: &'a mut Machine, + term: TermWriteResult, + stub_b: usize, + var_names: IndexMap, + called: bool, +} + +impl Drop for QueryState<'_> { + fn drop(&mut self) { + // This may be wrong if the iterator is not fully consumend, but from testing it seems + // fine. + self.machine.trust_me(); + } +} + +impl Iterator for QueryState<'_> { + type Item = Result; + + fn next(&mut self) -> Option { + let var_names = &self.var_names; + let term_write_result = &self.term; + let machine = &mut self.machine; + + // No more choicepoints, end iteration + if self.called && machine.machine_st.b <= self.stub_b { + return None; + } + + machine.dispatch_loop(); + + self.called = true; + + if !machine.machine_st.ball.stub.is_empty() { + // NOTE: this means an exception was thrown, at which + // point we backtracked to the stub choice point. + // this should halt the search for solutions as it + // does in the Scryer top-level. the exception term is + // contained in self.machine_st.ball. + let error_string = self + .machine + .machine_st + .ball + .stub + .iter() + .filter(|h| { + matches!( + h.get_tag(), + HeapCellValueTag::Atom | HeapCellValueTag::Fixnum + ) + }) + .map(|h| match h.get_tag() { + HeapCellValueTag::Atom => { + let (name, _) = cell_as_atom_cell!(h).get_name_and_arity(); + name.as_str().to_string() + } + HeapCellValueTag::Fixnum => h.get_value().clone().to_string(), + _ => unreachable!(), + }) + .collect::>() + .join(" "); + + return Some(Err(error_string)); + } + + if machine.machine_st.p == LIB_QUERY_SUCCESS { + if term_write_result.var_dict.is_empty() { + self.machine.machine_st.backtrack(); + return Some(Ok(QueryResolutionLine::True)); + } + } else if machine.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC { + return Some(Ok(QueryResolutionLine::False)); + } + + let mut bindings: BTreeMap = BTreeMap::new(); + + for (var_key, term_to_be_printed) in &term_write_result.var_dict { + if var_key.to_string().starts_with('_') { + continue; + } + let mut printer = HCPrinter::new( + &mut machine.machine_st.heap, + Arc::clone(&machine.machine_st.atom_tbl), + &mut machine.machine_st.stack, + &machine.indices.op_dir, + PrinterOutputter::new(), + *term_to_be_printed, + ); + + printer.ignore_ops = false; + printer.numbervars = true; + printer.quoted = true; + printer.max_depth = 1000; // NOTE: set this to 0 for unbounded depth + printer.double_quotes = true; + printer.var_names.clone_from(var_names); + + let outputter = printer.print(); + + let output: String = outputter.result(); + + if var_key.to_string() != output { + bindings.insert( + var_key.to_string(), + Value::try_from(output).expect("Couldn't convert Houtput to Value"), + ); + } + } + + // NOTE: there are outstanding choicepoints, backtrack + // through them for further solutions. if + // self.machine_st.b == stub_b we've backtracked to the stub + // choice point, so we should break. + self.machine.machine_st.backtrack(); + + Some(Ok(QueryResolutionLine::Match(bindings))) + } +} + impl Machine { pub fn new_lib() -> Self { Machine::new(MachineConfig::in_memory()) @@ -228,6 +346,59 @@ impl Machine { Ok(QueryResolution::from(matches)) } + + pub fn run_query_iter(&mut self, query: String) -> QueryState { + let mut parser = Parser::new( + Stream::from_owned_string(query, &mut self.machine_st.arena), + &mut self.machine_st, + ); + let op_dir = CompositeOpDir::new(&self.indices.op_dir, None); + let term = parser + .read_term(&op_dir, Tokens::Default) + .expect("Failed to parse query"); + + self.allocate_stub_choice_point(); + + // Write parsed term to heap + let term_write_result = + write_term_to_heap(&term, &mut self.machine_st.heap, &self.machine_st.atom_tbl) + .expect("couldn't write term to heap"); + + let var_names: IndexMap<_, _> = term_write_result + .var_dict + .iter() + .map(|(var_key, cell)| match var_key { + // NOTE: not the intention behind Var::InSitu here but + // we can hijack it to store anonymous variables + // without creating problems. + VarKey::AnonVar(h) => (*cell, VarPtr::from(Var::InSitu(*h))), + VarKey::VarPtr(var_ptr) => (*cell, var_ptr.clone()), + }) + .collect(); + + // Write term to heap + self.machine_st.registers[1] = self.machine_st.heap[term_write_result.heap_loc]; + + self.machine_st.cp = LIB_QUERY_SUCCESS; // BREAK_FROM_DISPATCH_LOOP_LOC; + let call_index_p = self + .indices + .code_dir + .get(&(atom!("call"), 1)) + .expect("couldn't get code index") + .local() + .unwrap(); + + self.machine_st.execute_at_index(1, call_index_p); + + let stub_b = self.machine_st.b; + QueryState { + machine: self, + term: term_write_result, + stub_b, + var_names, + called: false, + } + } } #[cfg(test)] From bc262db6d730dadbeb5ce6995a86640b5f342e97 Mon Sep 17 00:00:00 2001 From: bakaq Date: Wed, 14 Aug 2024 18:44:10 -0300 Subject: [PATCH 2/3] Implement run_query() in terms of run_query_iter() --- src/machine/lib_machine.rs | 168 +--------------------------------- src/machine/parsed_results.rs | 9 ++ 2 files changed, 11 insertions(+), 166 deletions(-) diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index f068c9083..a420d79e4 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -13,7 +13,7 @@ use indexmap::IndexMap; use super::{ streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine, MachineConfig, - QueryResolution, QueryResolutionLine, QueryResult, Value, + QueryResolutionLine, QueryResult, Value, }; pub struct QueryState<'a> { @@ -180,171 +180,7 @@ impl Machine { } pub fn run_query(&mut self, query: String) -> QueryResult { - // println!("Query: {}", query); - // Parse the query so we can analyze and then call the term - let mut parser = Parser::new( - Stream::from_owned_string(query, &mut self.machine_st.arena), - &mut self.machine_st, - ); - let op_dir = CompositeOpDir::new(&self.indices.op_dir, None); - let term = parser - .read_term(&op_dir, Tokens::Default) - .expect("Failed to parse query"); - - self.allocate_stub_choice_point(); - - // Write parsed term to heap - let term_write_result = - write_term_to_heap(&term, &mut self.machine_st.heap, &self.machine_st.atom_tbl) - .expect("couldn't write term to heap"); - - let var_names: IndexMap<_, _> = term_write_result - .var_dict - .iter() - .map(|(var_key, cell)| match var_key { - // NOTE: not the intention behind Var::InSitu here but - // we can hijack it to store anonymous variables - // without creating problems. - VarKey::AnonVar(h) => (*cell, VarPtr::from(Var::InSitu(*h))), - VarKey::VarPtr(var_ptr) => (*cell, var_ptr.clone()), - }) - .collect(); - - // Write term to heap - self.machine_st.registers[1] = self.machine_st.heap[term_write_result.heap_loc]; - - self.machine_st.cp = LIB_QUERY_SUCCESS; // BREAK_FROM_DISPATCH_LOOP_LOC; - let call_index_p = self - .indices - .code_dir - .get(&(atom!("call"), 1)) - .expect("couldn't get code index") - .local() - .unwrap(); - - self.machine_st.execute_at_index(1, call_index_p); - - let stub_b = self.machine_st.b; - - let mut matches: Vec = Vec::new(); - // Call the term - loop { - self.dispatch_loop(); - - //println!("b: {}", self.machine_st.b); - //println!("stub_b: {}", stub_b); - //println!("fail: {}", self.machine_st.fail); - - if !self.machine_st.ball.stub.is_empty() { - // NOTE: this means an exception was thrown, at which - // point we backtracked to the stub choice point. - // this should halt the search for solutions as it - // does in the Scryer top-level. the exception term is - // contained in self.machine_st.ball. - let error_string = self - .machine_st - .ball - .stub - .iter() - .filter(|h| { - matches!( - h.get_tag(), - HeapCellValueTag::Atom | HeapCellValueTag::Fixnum - ) - }) - .map(|h| match h.get_tag() { - HeapCellValueTag::Atom => { - let (name, _) = cell_as_atom_cell!(h).get_name_and_arity(); - name.as_str().to_string() - } - HeapCellValueTag::Fixnum => h.get_value().clone().to_string(), - _ => unreachable!(), - }) - .collect::>() - .join(" "); - - return Err(error_string); - } - - /* - if self.machine_st.fail { - // NOTE: only print results on success - self.machine_st.fail = false; - println!("fail!"); - matches.push(QueryResolutionLine::False); - break; - }; - */ - - if self.machine_st.p == LIB_QUERY_SUCCESS { - if term_write_result.var_dict.is_empty() { - matches.push(QueryResolutionLine::True); - break; - } - } else if self.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC { - // NOTE: only print results on success - // self.machine_st.fail = false; - // println!("b == stub_b"); - matches.push(QueryResolutionLine::False); - break; - } - - let mut bindings: BTreeMap = BTreeMap::new(); - - for (var_key, term_to_be_printed) in &term_write_result.var_dict { - if var_key.to_string().starts_with('_') { - continue; - } - let mut printer = HCPrinter::new( - &mut self.machine_st.heap, - Arc::clone(&self.machine_st.atom_tbl), - &mut self.machine_st.stack, - &self.indices.op_dir, - PrinterOutputter::new(), - *term_to_be_printed, - ); - - printer.ignore_ops = false; - printer.numbervars = true; - printer.quoted = true; - printer.max_depth = 1000; // NOTE: set this to 0 for unbounded depth - printer.double_quotes = true; - printer.var_names.clone_from(&var_names); - - let outputter = printer.print(); - - let output: String = outputter.result(); - // println!("Result: {} = {}", var_key.to_string(), output); - - if var_key.to_string() != output { - bindings.insert( - var_key.to_string(), - Value::try_from(output).expect("Couldn't convert Houtput to Value"), - ); - } - } - - matches.push(QueryResolutionLine::Match(bindings)); - - // NOTE: there are outstanding choicepoints, backtrack - // through them for further solutions. if - // self.machine_st.b == stub_b we've backtracked to the stub - // choice point, so we should break. - self.machine_st.backtrack(); - - if self.machine_st.b <= stub_b { - // NOTE: out of choicepoints to backtrack through, no - // more solutions to gather. - break; - } - } - - // NOTE: deallocate stub choice point - if self.machine_st.b == stub_b { - self.trust_me(); - } - - Ok(QueryResolution::from(matches)) + self.run_query_iter(query).collect() } pub fn run_query_iter(&mut self, query: String) -> QueryState { diff --git a/src/machine/parsed_results.rs b/src/machine/parsed_results.rs index 8ca6f8e82..0dd8236f8 100644 --- a/src/machine/parsed_results.rs +++ b/src/machine/parsed_results.rs @@ -5,6 +5,7 @@ use std::collections::BTreeMap; use std::collections::HashMap; use std::fmt::Display; use std::fmt::Write; +use std::iter::FromIterator; pub type QueryResult = Result; @@ -198,6 +199,14 @@ impl From> for QueryResolution { } } +impl FromIterator for QueryResolution { + fn from_iter>(iter: I) -> Self { + // TODO: Probably a good idea to implement From> based on this + // instead. + iter.into_iter().collect::>().into() + } +} + fn split_response_string(input: &str) -> Vec { let mut level_bracket = 0; let mut level_parenthesis = 0; From ed1b25cb307d465ebdaab79f94b93a031bc7e6a3 Mon Sep 17 00:00:00 2001 From: bakaq Date: Wed, 14 Aug 2024 18:30:56 -0300 Subject: [PATCH 3/3] Tests for run_query_iter() --- src/machine/lib_machine.rs | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index a420d79e4..dba599c88 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -637,4 +637,45 @@ mod tests { assert_eq!(output, Ok(QueryResolution::False)); } + + #[test] + #[cfg_attr(miri, ignore)] + fn query_iterator_determinism() { + let mut machine = Machine::new_lib(); + + { + let mut iterator = machine.run_query_iter("X = 1.".into()); + + iterator.next(); + assert_eq!(iterator.next(), None); + } + + { + let mut iterator = machine.run_query_iter("X = 1 ; false.".into()); + + iterator.next(); + + assert_eq!(iterator.next(), Some(Ok(QueryResolutionLine::False))); + assert_eq!(iterator.next(), None); + } + + { + let mut iterator = machine.run_query_iter("false.".into()); + + assert_eq!(iterator.next(), Some(Ok(QueryResolutionLine::False))); + assert_eq!(iterator.next(), None); + } + } + + #[test] + #[cfg_attr(miri, ignore)] + fn query_iterator_backtracking_when_no_variables() { + let mut machine = Machine::new_lib(); + + let mut iterator = machine.run_query_iter("true;false.".into()); + + assert_eq!(iterator.next(), Some(Ok(QueryResolutionLine::True))); + assert_eq!(iterator.next(), Some(Ok(QueryResolutionLine::False))); + assert_eq!(iterator.next(), None); + } }