From 8e7dc9dba8e72a9758a5eac43acbd3b7722ea26f Mon Sep 17 00:00:00 2001 From: bakaq Date: Fri, 6 Sep 2024 23:06:20 -0300 Subject: [PATCH 01/28] Rename PrologTerm --- src/machine/lib_machine.rs | 88 ++++++++++----------- src/machine/parsed_results.rs | 142 +++++++++++++++++----------------- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index e44e54e4e..a4233696a 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -11,7 +11,7 @@ use indexmap::IndexMap; use super::{ streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine, MachineConfig, - QueryResolutionLine, QueryResult, Value, + QueryResolutionLine, QueryResult, PrologTerm, }; pub struct QueryState<'a> { @@ -88,7 +88,7 @@ impl Iterator for QueryState<'_> { return Some(Ok(QueryResolutionLine::False)); } - let mut bindings: BTreeMap = BTreeMap::new(); + let mut bindings: BTreeMap = BTreeMap::new(); let var_dict = &term_write_result.var_dict; @@ -105,9 +105,9 @@ impl Iterator for QueryState<'_> { } let mut term = - Value::from_heapcell(machine, *term_to_be_printed, &mut var_names.clone()); + PrologTerm::from_heapcell(machine, *term_to_be_printed, &mut var_names.clone()); - if let Value::Var(ref term_str) = term { + if let PrologTerm::Var(ref term_str) = term { if *term_str == var_name { continue; } @@ -121,7 +121,7 @@ impl Iterator for QueryState<'_> { var_dict.get_index_of(&VarKey::VarPtr(Var::Named(term_str.clone()).into())); if let Some(idx) = term_idx { if idx < var_name_idx { - let new_term = Value::Var(var_name); + let new_term = PrologTerm::Var(var_name); let new_var_name = term_str.into(); term = new_term; var_name = new_var_name; @@ -248,7 +248,7 @@ impl Machine { #[cfg(test)] mod tests { use super::*; - use crate::machine::{QueryMatch, QueryResolution, Value}; + use crate::machine::{QueryMatch, QueryResolution, Term}; #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] @@ -271,10 +271,10 @@ mod tests { output, Ok(QueryResolution::Matches(vec![ QueryMatch::from(btreemap! { - "P" => Value::from("p1"), + "P" => Term::from("p1"), }), QueryMatch::from(btreemap! { - "P" => Value::from("p2"), + "P" => Term::from("p2"), }), ])) ); @@ -328,8 +328,8 @@ mod tests { result, Ok(QueryResolution::Matches(vec![QueryMatch::from( btreemap! { - "C" => Value::Atom("c".into()), - "Actions" => Value::Atom("[{action: \"addLink\", source: \"this\", predicate: \"todo://state\", target: \"todo://ready\"}]".into()), + "C" => Term::Atom("c".into()), + "Actions" => Term::Atom("[{action: \"addLink\", source: \"this\", predicate: \"todo://state\", target: \"todo://ready\"}]".into()), } ),])) ); @@ -341,8 +341,8 @@ mod tests { result, Ok(QueryResolution::Matches(vec![QueryMatch::from( btreemap! { - "C" => Value::Atom("xyz".into()), - "Actions" => Value::Atom("[{action: \"addLink\", source: \"this\", predicate: \"recipe://title\", target: \"literal://string:Meta%20Muffins\"}]".into()), + "C" => Term::Atom("xyz".into()), + "Actions" => Term::Atom("[{action: \"addLink\", source: \"this\", predicate: \"recipe://title\", target: \"literal://string:Meta%20Muffins\"}]".into()), } ),])) ); @@ -352,10 +352,10 @@ mod tests { result, Ok(QueryResolution::Matches(vec![ QueryMatch::from(btreemap! { - "Class" => Value::String("Todo".into()) + "Class" => Term::String("Todo".into()) }), QueryMatch::from(btreemap! { - "Class" => Value::String("Recipe".into()) + "Class" => Term::String("Recipe".into()) }), ])) ); @@ -394,10 +394,10 @@ mod tests { result, Ok(QueryResolution::Matches(vec![QueryMatch::from( btreemap! { - "X" => Value::List(vec![ - Value::Integer(1.into()), - Value::Integer(2.into()), - Value::Integer(3.into()), + "X" => Term::List(vec![ + Term::Integer(1.into()), + Term::Integer(2.into()), + Term::Integer(3.into()), ]), } ),])) @@ -425,10 +425,10 @@ mod tests { output, Ok(QueryResolution::Matches(vec![ QueryMatch::from(btreemap! { - "P" => Value::from("p1"), + "P" => Term::from("p1"), }), QueryMatch::from(btreemap! { - "P" => Value::from("p2"), + "P" => Term::from("p2"), }), ])) ); @@ -529,10 +529,10 @@ mod tests { output, Ok(QueryResolution::Matches(vec![QueryMatch::from( btreemap! { - "Result" => Value::List( + "Result" => Term::List( Vec::from([ - Value::List([Value::from("p1"), Value::from("b")].into()), - Value::List([Value::from("p2"), Value::from("b")].into()), + Term::List([Term::from("p1"), Term::from("b")].into()), + Term::List([Term::from("p2"), Term::from("b")].into()), ]) ), } @@ -634,7 +634,7 @@ mod tests { result, Ok(QueryResolution::Matches(vec![QueryMatch::from( btreemap! { - "X" => Value::Atom(".".into()), + "X" => Term::Atom(".".into()), } )])) ); @@ -654,7 +654,7 @@ mod tests { result, Ok(QueryResolution::Matches(vec![QueryMatch::from( btreemap! { - "X" => Value::Rational(RBig::from_parts(1.into(), 2u32.into())), + "X" => Term::Rational(RBig::from_parts(1.into(), 2u32.into())), } )])) ); @@ -674,7 +674,7 @@ mod tests { result, Ok(QueryResolution::Matches(vec![QueryMatch::from( btreemap! { - "X" => Value::Integer(IBig::from(10).pow(100)), + "X" => Term::Integer(IBig::from(10).pow(100)), } )])) ); @@ -689,31 +689,31 @@ mod tests { let result = machine.run_query(query); - let expected = Value::Structure( + let expected = Term::Structure( // Composite term "a".into(), vec![ - Value::String("asdf".into()), // String - Value::List(vec![ - Value::Integer(42.into()), // Fixnum - Value::Float(2.54.into()), // Float - Value::Atom("asdf".into()), // Atom - Value::Atom("a".into()), // Char - Value::Structure( + Term::String("asdf".into()), // String + Term::List(vec![ + Term::Integer(42.into()), // Fixnum + Term::Float(2.54.into()), // Float + Term::Atom("asdf".into()), // Atom + Term::Atom("a".into()), // Char + Term::Structure( // Partial string ".".into(), vec![ - Value::Atom("a".into()), - Value::Structure( + Term::Atom("a".into()), + Term::Structure( ".".into(), vec![ - Value::Atom("b".into()), - Value::Var("_A".into()), // Anonymous variable + Term::Atom("b".into()), + Term::Var("_A".into()), // Anonymous variable ], ), ], ), - Value::Var("Z".into()), // Named variable + Term::Var("Z".into()), // Named variable ]), ], ); @@ -802,11 +802,11 @@ mod tests { result, Ok(QueryResolution::Matches(vec![ QueryMatch::from(btreemap! { - "A" => Value::List(vec![Value::Var("_A".into()), Value::Var("_C".into())]), - "_B" => Value::Integer(1.into()), + "A" => Term::List(vec![Term::Var("_A".into()), Term::Var("_C".into())]), + "_B" => Term::Integer(1.into()), }), QueryMatch::from(btreemap! { - "B" => Value::List(vec![Value::Var("_A".into()), Value::Var("_C".into())]), + "B" => Term::List(vec![Term::Var("_A".into()), Term::Var("_C".into())]), }), ])) ); @@ -823,8 +823,8 @@ mod tests { result, Ok(QueryResolution::Matches(vec![QueryMatch::from( btreemap! { - "X" => Value::Var("Y".into()), - "Z" => Value::Var("W".into()), + "X" => Term::Var("Y".into()), + "Z" => Term::Var("W".into()), } ),])) ); diff --git a/src/machine/parsed_results.rs b/src/machine/parsed_results.rs index b76239edd..b1641ef40 100644 --- a/src/machine/parsed_results.rs +++ b/src/machine/parsed_results.rs @@ -26,14 +26,14 @@ pub enum QueryResolution { fn write_prolog_value_as_json( writer: &mut W, - value: &Value, + value: &PrologTerm, ) -> Result<(), std::fmt::Error> { match value { - Value::Integer(i) => write!(writer, "{}", i), - Value::Float(f) => write!(writer, "{}", f), - Value::Rational(r) => write!(writer, "{}", r), - Value::Atom(a) => writer.write_str(a.as_str()), - Value::String(s) => { + PrologTerm::Integer(i) => write!(writer, "{}", i), + PrologTerm::Float(f) => write!(writer, "{}", f), + PrologTerm::Rational(r) => write!(writer, "{}", r), + PrologTerm::Atom(a) => writer.write_str(a.as_str()), + PrologTerm::String(s) => { if let Err(_e) = serde_json::from_str::(s.as_str()) { //treat as string literal //escape double quotes @@ -50,7 +50,7 @@ fn write_prolog_value_as_json( writer.write_str(s) } } - Value::List(l) => { + PrologTerm::List(l) => { writer.write_char('[')?; if let Some((first, rest)) = l.split_first() { write_prolog_value_as_json(writer, first)?; @@ -62,7 +62,7 @@ fn write_prolog_value_as_json( } writer.write_char(']') } - Value::Structure(s, l) => { + PrologTerm::Structure(s, l) => { write!(writer, "\"{}\":[", s.as_str())?; if let Some((first, rest)) = l.split_first() { @@ -119,25 +119,25 @@ impl Display for QueryResolution { #[derive(Debug, Clone, PartialEq, Eq)] pub struct QueryMatch { - pub bindings: BTreeMap, + pub bindings: BTreeMap, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum QueryResolutionLine { True, False, - Match(BTreeMap), + Match(BTreeMap), } #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Value { +pub enum PrologTerm { Integer(Integer), Rational(Rational), Float(OrderedFloat), Atom(String), String(String), - List(Vec), - Structure(String, Vec), + List(Vec), + Structure(String, Vec), Var(String), } @@ -159,7 +159,7 @@ fn count_to_letter_code(mut count: usize) -> String { letters.into_iter().chain("_".chars()).rev().collect() } -impl Value { +impl PrologTerm { pub(crate) fn from_heapcell( machine: &mut Machine, heap_cell: HeapCellValue, @@ -194,41 +194,41 @@ impl Value { let head = term_stack.pop().unwrap(); let list = match tail { - Value::Atom(atom) if atom == "[]" => match head { - Value::Atom(ref a) if a.chars().collect::>().len() == 1 => { + PrologTerm::Atom(atom) if atom == "[]" => match head { + PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { // Handle lists of char as strings - Value::String(a.to_string()) + PrologTerm::String(a.to_string()) } - _ => Value::List(vec![head]), + _ => PrologTerm::List(vec![head]), }, - Value::List(elems) if elems.is_empty() => match head { - Value::Atom(ref a) if a.chars().collect::>().len() == 1 => { + PrologTerm::List(elems) if elems.is_empty() => match head { + PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { // Handle lists of char as strings - Value::String(a.to_string()) + PrologTerm::String(a.to_string()) }, - _ => Value::List(vec![head]), + _ => PrologTerm::List(vec![head]), }, - Value::List(mut elems) => { + PrologTerm::List(mut elems) => { elems.insert(0, head); - Value::List(elems) + PrologTerm::List(elems) }, - Value::String(mut elems) => match head { - Value::Atom(ref a) if a.chars().collect::>().len() == 1 => { + PrologTerm::String(mut elems) => match head { + PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { // Handle lists of char as strings elems.insert(0, a.chars().next().unwrap()); - Value::String(elems) + PrologTerm::String(elems) }, _ => { - let mut elems: Vec = elems + let mut elems: Vec = elems .chars() - .map(|x| Value::Atom(x.into())) + .map(|x| PrologTerm::Atom(x.into())) .collect(); elems.insert(0, head); - Value::List(elems) + PrologTerm::List(elems) } }, _ => { - Value::Structure(".".into(), vec![head, tail]) + PrologTerm::Structure(".".into(), vec![head, tail]) } }; term_stack.push(list); @@ -236,7 +236,7 @@ impl Value { (HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar) => { let var = var_names.get(&addr).map(|x| x.borrow().clone()); match var { - Some(Var::Named(name)) => term_stack.push(Value::Var(name)), + Some(Var::Named(name)) => term_stack.push(PrologTerm::Var(name)), _ => { let anon_name = loop { // Generate a name for the anonymous variable @@ -261,28 +261,28 @@ impl Value { }, } }; - term_stack.push(Value::Var(anon_name)); + term_stack.push(PrologTerm::Var(anon_name)); }, } } (HeapCellValueTag::F64, f) => { - term_stack.push(Value::Float(*f)); + term_stack.push(PrologTerm::Float(*f)); } (HeapCellValueTag::Char, c) => { - term_stack.push(Value::Atom(c.into())); + term_stack.push(PrologTerm::Atom(c.into())); } (HeapCellValueTag::Fixnum, n) => { - term_stack.push(Value::Integer(n.into())); + term_stack.push(PrologTerm::Integer(n.into())); } (HeapCellValueTag::Cons) => { match Number::try_from(addr) { - Ok(Number::Integer(i)) => term_stack.push(Value::Integer((*i).clone())), - Ok(Number::Rational(r)) => term_stack.push(Value::Rational((*r).clone())), + Ok(Number::Integer(i)) => term_stack.push(PrologTerm::Integer((*i).clone())), + Ok(Number::Rational(r)) => term_stack.push(PrologTerm::Rational((*r).clone())), _ => {} } } (HeapCellValueTag::CStr, s) => { - term_stack.push(Value::String(s.as_str().to_string())); + term_stack.push(PrologTerm::String(s.as_str().to_string())); } (HeapCellValueTag::Atom, (name, arity)) => { //let h = iter.focus().value() as usize; @@ -313,46 +313,46 @@ impl Value { if arity == 0 { let atom_name = name.as_str().to_string(); if atom_name == "[]" { - term_stack.push(Value::List(vec![])); + term_stack.push(PrologTerm::List(vec![])); } else { - term_stack.push(Value::Atom(atom_name)); + term_stack.push(PrologTerm::Atom(atom_name)); } } else { let subterms = term_stack .drain(term_stack.len() - arity ..) .collect(); - term_stack.push(Value::Structure(name.as_str().to_string(), subterms)); + term_stack.push(PrologTerm::Structure(name.as_str().to_string(), subterms)); } } (HeapCellValueTag::PStr, atom) => { let tail = term_stack.pop().unwrap(); match tail { - Value::Atom(atom) => { + PrologTerm::Atom(atom) => { if atom == "[]" { - term_stack.push(Value::String(atom.as_str().to_string())); + term_stack.push(PrologTerm::String(atom.as_str().to_string())); } }, - Value::List(l) => { - let mut list: Vec = atom + PrologTerm::List(l) => { + let mut list: Vec = atom .as_str() .to_string() .chars() - .map(|x| Value::Atom(x.to_string())) + .map(|x| PrologTerm::Atom(x.to_string())) .collect(); list.extend(l.into_iter()); - term_stack.push(Value::List(list)); + term_stack.push(PrologTerm::List(list)); }, _ => { - let mut list: Vec = atom + let mut list: Vec = atom .as_str() .to_string() .chars() - .map(|x| Value::Atom(x.to_string())) + .map(|x| PrologTerm::Atom(x.to_string())) .collect(); - let mut partial_list = Value::Structure( + let mut partial_list = PrologTerm::Structure( ".".into(), vec![ list.pop().unwrap(), @@ -361,7 +361,7 @@ impl Value { ); while let Some(last) = list.pop() { - partial_list = Value::Structure( + partial_list = PrologTerm::Structure( ".".into(), vec![ last, @@ -397,8 +397,8 @@ impl Value { } } -impl From> for QueryMatch { - fn from(bindings: BTreeMap<&str, Value>) -> Self { +impl From> for QueryMatch { + fn from(bindings: BTreeMap<&str, PrologTerm>) -> Self { QueryMatch { bindings: bindings .into_iter() @@ -408,8 +408,8 @@ impl From> for QueryMatch { } } -impl From> for QueryMatch { - fn from(bindings: BTreeMap) -> Self { +impl From> for QueryMatch { + fn from(bindings: BTreeMap) -> Self { QueryMatch { bindings } } } @@ -545,10 +545,10 @@ impl TryFrom for QueryResolutionLine { _ => Ok(QueryResolutionLine::Match( parse_prolog_response(&string) .iter() - .map(|(k, v)| -> Result<(String, Value), ()> { + .map(|(k, v)| -> Result<(String, PrologTerm), ()> { let key = k.to_string(); let value = v.to_string(); - Ok((key, Value::try_from(value)?)) + Ok((key, PrologTerm::try_from(value)?)) }) .filter_map(Result::ok) .collect::>(), @@ -578,28 +578,28 @@ fn split_nested_list(input: &str) -> Vec { result } -impl TryFrom for Value { +impl TryFrom for PrologTerm { type Error = (); fn try_from(string: String) -> Result { let trimmed = string.trim(); if let Ok(float_value) = string.parse::() { - Ok(Value::Float(OrderedFloat(float_value))) + Ok(PrologTerm::Float(OrderedFloat(float_value))) } else if let Ok(int_value) = string.parse::() { - Ok(Value::Integer(int_value.into())) + Ok(PrologTerm::Integer(int_value.into())) } else if trimmed.starts_with('\'') && trimmed.ends_with('\'') || trimmed.starts_with('"') && trimmed.ends_with('"') { - Ok(Value::String(trimmed[1..trimmed.len() - 1].into())) + Ok(PrologTerm::String(trimmed[1..trimmed.len() - 1].into())) } else if trimmed.starts_with('[') && trimmed.ends_with(']') { let split = split_nested_list(&trimmed[1..trimmed.len() - 1]); let values = split .into_iter() - .map(Value::try_from) + .map(PrologTerm::try_from) .collect::, _>>()?; - Ok(Value::List(values)) + Ok(PrologTerm::List(values)) } else if trimmed.starts_with('{') && trimmed.ends_with('}') { let iter = trimmed[1..trimmed.len() - 1].split(','); let mut values = vec![]; @@ -609,11 +609,11 @@ impl TryFrom for Value { if items.len() == 2 { let _key = items[0].to_string(); let value = items[1].to_string(); - values.push(Value::try_from(value)?); + values.push(PrologTerm::try_from(value)?); } } - Ok(Value::Structure("{}".into(), values)) + Ok(PrologTerm::Structure("{}".into(), values)) } else if trimmed.starts_with("<<") && trimmed.ends_with(">>") { let iter = trimmed[2..trimmed.len() - 2].split(','); let mut values = vec![]; @@ -623,21 +623,21 @@ impl TryFrom for Value { if items.len() == 2 { let _key = items[0].to_string(); let value = items[1].to_string(); - values.push(Value::try_from(value)?); + values.push(PrologTerm::try_from(value)?); } } - Ok(Value::Structure("<<>>".into(), values)) + Ok(PrologTerm::Structure("<<>>".into(), values)) } else if !trimmed.contains(',') && !trimmed.contains('\'') && !trimmed.contains('"') { - Ok(Value::String(trimmed.into())) + Ok(PrologTerm::String(trimmed.into())) } else { Err(()) } } } -impl From<&str> for Value { +impl From<&str> for PrologTerm { fn from(str: &str) -> Self { - Value::String(str.to_string()) + PrologTerm::String(str.to_string()) } } From 29fc55cb28f877c0dbd67947ff893c2d7c9180a7 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sat, 7 Sep 2024 01:15:05 -0300 Subject: [PATCH 02/28] Rename LeafAnswer --- src/machine/lib_machine.rs | 24 +-- src/machine/parsed_results.rs | 364 +--------------------------------- 2 files changed, 16 insertions(+), 372 deletions(-) diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index a4233696a..52fb3e020 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -11,7 +11,7 @@ use indexmap::IndexMap; use super::{ streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine, MachineConfig, - QueryResolutionLine, QueryResult, PrologTerm, + LeafAnswer, PrologTerm, }; pub struct QueryState<'a> { @@ -31,7 +31,7 @@ impl Drop for QueryState<'_> { } impl Iterator for QueryState<'_> { - type Item = Result; + type Item = Result; fn next(&mut self) -> Option { let var_names = &mut self.var_names; @@ -82,10 +82,10 @@ impl Iterator for QueryState<'_> { 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)); + return Some(Ok(LeafAnswer::True)); } } else if machine.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC { - return Some(Ok(QueryResolutionLine::False)); + return Some(Ok(LeafAnswer::False)); } let mut bindings: BTreeMap = BTreeMap::new(); @@ -138,7 +138,7 @@ impl Iterator for QueryState<'_> { // choice point, so we should break. self.machine.machine_st.backtrack(); - Some(Ok(QueryResolutionLine::Match(bindings))) + Some(Ok(LeafAnswer::LeafAnswer { bindings: bindings, residual_goals: vec![] })) } } @@ -187,11 +187,7 @@ impl Machine { self.machine_st.block = stub_b; } - pub fn run_query(&mut self, query: String) -> QueryResult { - self.run_query_iter(query).collect() - } - - pub fn run_query_iter(&mut self, query: String) -> QueryState { + pub fn run_query(&mut self, query: String) -> QueryState { let mut parser = Parser::new( Stream::from_owned_string(query, &mut self.machine_st.arena), &mut self.machine_st, @@ -767,14 +763,14 @@ mod tests { iterator.next(); - assert_eq!(iterator.next(), Some(Ok(QueryResolutionLine::False))); + assert_eq!(iterator.next(), Some(Ok(LeafAnswer::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(), Some(Ok(LeafAnswer::False))); assert_eq!(iterator.next(), None); } } @@ -786,8 +782,8 @@ mod tests { 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(), Some(Ok(LeafAnswer::True))); + assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False))); assert_eq!(iterator.next(), None); } diff --git a/src/machine/parsed_results.rs b/src/machine/parsed_results.rs index b1641ef40..4ab1f91a2 100644 --- a/src/machine/parsed_results.rs +++ b/src/machine/parsed_results.rs @@ -7,126 +7,19 @@ use indexmap::IndexMap; use ordered_float::OrderedFloat; use std::cmp::Ordering; use std::collections::BTreeMap; -use std::collections::HashMap; -use std::fmt::Display; -use std::fmt::Write; -use std::iter::FromIterator; use super::Machine; use super::{HeapCellValue, Number}; -pub type QueryResult = Result; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum QueryResolution { - True, - False, - Matches(Vec), -} - -fn write_prolog_value_as_json( - writer: &mut W, - value: &PrologTerm, -) -> Result<(), std::fmt::Error> { - match value { - PrologTerm::Integer(i) => write!(writer, "{}", i), - PrologTerm::Float(f) => write!(writer, "{}", f), - PrologTerm::Rational(r) => write!(writer, "{}", r), - PrologTerm::Atom(a) => writer.write_str(a.as_str()), - PrologTerm::String(s) => { - if let Err(_e) = serde_json::from_str::(s.as_str()) { - //treat as string literal - //escape double quotes - write!( - writer, - "\"{}\"", - s.replace('\"', "\\\"") - .replace('\n', "\\n") - .replace('\t', "\\t") - .replace('\r', "\\r") - ) - } else { - //return valid json string - writer.write_str(s) - } - } - PrologTerm::List(l) => { - writer.write_char('[')?; - if let Some((first, rest)) = l.split_first() { - write_prolog_value_as_json(writer, first)?; - - for other in rest { - writer.write_char(',')?; - write_prolog_value_as_json(writer, other)?; - } - } - writer.write_char(']') - } - PrologTerm::Structure(s, l) => { - write!(writer, "\"{}\":[", s.as_str())?; - - if let Some((first, rest)) = l.split_first() { - write_prolog_value_as_json(writer, first)?; - for other in rest { - writer.write_char(',')?; - write_prolog_value_as_json(writer, other)?; - } - } - writer.write_char(']') - } - _ => writer.write_str("null"), - } -} - -fn write_prolog_match_as_json( - writer: &mut W, - query_match: &QueryMatch, -) -> Result<(), std::fmt::Error> { - writer.write_char('{')?; - let mut iter = query_match.bindings.iter(); - - if let Some((k, v)) = iter.next() { - write!(writer, "\"{k}\":")?; - write_prolog_value_as_json(writer, v)?; - - for (k, v) in iter { - write!(writer, ",\"{k}\":")?; - write_prolog_value_as_json(writer, v)?; - } - } - writer.write_char('}') -} - -impl Display for QueryResolution { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - QueryResolution::True => f.write_str("true"), - QueryResolution::False => f.write_str("false"), - QueryResolution::Matches(matches) => { - f.write_char('[')?; - if let Some((first, rest)) = matches.split_first() { - write_prolog_match_as_json(f, first)?; - for other in rest { - f.write_char(',')?; - write_prolog_match_as_json(f, other)?; - } - } - f.write_char(']') - } - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct QueryMatch { - pub bindings: BTreeMap, -} - #[derive(Debug, Clone, PartialEq, Eq)] -pub enum QueryResolutionLine { +pub enum LeafAnswer { True, False, - Match(BTreeMap), + Exception(PrologTerm), + LeafAnswer { + bindings: BTreeMap, + residual_goals: Vec, + }, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -396,248 +289,3 @@ impl PrologTerm { term_stack.pop().unwrap() } } - -impl From> for QueryMatch { - fn from(bindings: BTreeMap<&str, PrologTerm>) -> Self { - QueryMatch { - bindings: bindings - .into_iter() - .map(|(k, v)| (k.to_string(), v)) - .collect::>(), - } - } -} - -impl From> for QueryMatch { - fn from(bindings: BTreeMap) -> Self { - QueryMatch { bindings } - } -} - -impl From> for QueryResolution { - fn from(query_result_lines: Vec) -> Self { - // If there is only one line, and it is true or false, return that. - if query_result_lines.len() == 1 { - match query_result_lines[0].clone() { - QueryResolutionLine::True => return QueryResolution::True, - QueryResolutionLine::False => return QueryResolution::False, - _ => {} - } - } - - // If there is only one line, and it is an empty match, return false. - if query_result_lines.len() == 1 { - if let QueryResolutionLine::Match(m) = query_result_lines[0].clone() { - if m.is_empty() { - return QueryResolution::False; - } - } - } - - // If there is at least one line with true and no matches, return true. - if query_result_lines - .iter() - .any(|l| l == &QueryResolutionLine::True) - && !query_result_lines - .iter() - .any(|l| matches!(l, QueryResolutionLine::Match(_))) - { - return QueryResolution::True; - } - - // If there is at least one match, return all matches. - let all_matches = query_result_lines - .into_iter() - .filter(|l| matches!(l, QueryResolutionLine::Match(_))) - .map(|l| match l { - QueryResolutionLine::Match(m) => QueryMatch::from(m), - _ => unreachable!(), - }) - .collect::>(); - - if !all_matches.is_empty() { - return QueryResolution::Matches(all_matches); - } - - QueryResolution::False - } -} - -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; - let mut in_double_quotes = false; - let mut in_single_quotes = false; - let mut start = 0; - let mut result = Vec::new(); - - for (i, c) in input.chars().enumerate() { - match c { - '[' => level_bracket += 1, - ']' => level_bracket -= 1, - '(' => level_parenthesis += 1, - ')' => level_parenthesis -= 1, - '"' => in_double_quotes = !in_double_quotes, - '\'' => in_single_quotes = !in_single_quotes, - ',' if level_bracket == 0 - && level_parenthesis == 0 - && !in_double_quotes - && !in_single_quotes => - { - result.push(input[start..i].trim().to_string()); - start = i + 1; - } - _ => {} - } - } - - result.push(input[start..].trim().to_string()); - result -} - -fn split_key_value_pairs(input: &str) -> Vec<(String, String)> { - let items = split_response_string(input); - let mut result = Vec::new(); - - for item in items { - let parts: Vec<&str> = item.splitn(2, '=').collect(); - if parts.len() == 2 { - let key = parts[0].trim().to_string(); - let value = parts[1].trim().to_string(); - result.push((key, value)); - } - } - - result -} - -fn parse_prolog_response(input: &str) -> HashMap { - let mut map: HashMap = HashMap::new(); - // Use regex to match strings including commas inside them - for result in split_key_value_pairs(input) { - let key = result.0; - let value = result.1; - // cut off at given characters/strings: - let value = value.split('\n').next().unwrap().to_string(); - let value = value.split(' ').next().unwrap().to_string(); - let value = value.split('\t').next().unwrap().to_string(); - let value = value.split("error").next().unwrap().to_string(); - map.insert(key, value); - } - - map -} - -impl TryFrom for QueryResolutionLine { - type Error = (); - fn try_from(string: String) -> Result { - match string.as_str() { - "true" => Ok(QueryResolutionLine::True), - "false" => Ok(QueryResolutionLine::False), - _ => Ok(QueryResolutionLine::Match( - parse_prolog_response(&string) - .iter() - .map(|(k, v)| -> Result<(String, PrologTerm), ()> { - let key = k.to_string(); - let value = v.to_string(); - Ok((key, PrologTerm::try_from(value)?)) - }) - .filter_map(Result::ok) - .collect::>(), - )), - } - } -} - -fn split_nested_list(input: &str) -> Vec { - let mut level = 0; - let mut start = 0; - let mut result = Vec::new(); - - for (i, c) in input.chars().enumerate() { - match c { - '[' => level += 1, - ']' => level -= 1, - ',' if level == 0 => { - result.push(input[start..i].trim().to_string()); - start = i + 1; - } - _ => {} - } - } - - result.push(input[start..].trim().to_string()); - result -} - -impl TryFrom for PrologTerm { - type Error = (); - fn try_from(string: String) -> Result { - let trimmed = string.trim(); - - if let Ok(float_value) = string.parse::() { - Ok(PrologTerm::Float(OrderedFloat(float_value))) - } else if let Ok(int_value) = string.parse::() { - Ok(PrologTerm::Integer(int_value.into())) - } else if trimmed.starts_with('\'') && trimmed.ends_with('\'') - || trimmed.starts_with('"') && trimmed.ends_with('"') - { - Ok(PrologTerm::String(trimmed[1..trimmed.len() - 1].into())) - } else if trimmed.starts_with('[') && trimmed.ends_with(']') { - let split = split_nested_list(&trimmed[1..trimmed.len() - 1]); - - let values = split - .into_iter() - .map(PrologTerm::try_from) - .collect::, _>>()?; - - Ok(PrologTerm::List(values)) - } else if trimmed.starts_with('{') && trimmed.ends_with('}') { - let iter = trimmed[1..trimmed.len() - 1].split(','); - let mut values = vec![]; - - for value in iter { - let items: Vec<_> = value.split(':').collect(); - if items.len() == 2 { - let _key = items[0].to_string(); - let value = items[1].to_string(); - values.push(PrologTerm::try_from(value)?); - } - } - - Ok(PrologTerm::Structure("{}".into(), values)) - } else if trimmed.starts_with("<<") && trimmed.ends_with(">>") { - let iter = trimmed[2..trimmed.len() - 2].split(','); - let mut values = vec![]; - - for value in iter { - let items: Vec<_> = value.split(':').collect(); - if items.len() == 2 { - let _key = items[0].to_string(); - let value = items[1].to_string(); - values.push(PrologTerm::try_from(value)?); - } - } - - Ok(PrologTerm::Structure("<<>>".into(), values)) - } else if !trimmed.contains(',') && !trimmed.contains('\'') && !trimmed.contains('"') { - Ok(PrologTerm::String(trimmed.into())) - } else { - Err(()) - } - } -} - -impl From<&str> for PrologTerm { - fn from(str: &str) -> Self { - PrologTerm::String(str.to_string()) - } -} From 658e39aae6996ff6f8f1b6b1e2a58abea846b80f Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 21:30:27 -0300 Subject: [PATCH 03/28] Machine and stream config rework --- src/machine/config.rs | 64 +++++++++++++++++++++++++++++++------- src/machine/lib_machine.rs | 4 +-- src/machine/mock_wam.rs | 3 +- src/machine/mod.rs | 6 ++-- 4 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/machine/config.rs b/src/machine/config.rs index 6268c8d81..79752f317 100644 --- a/src/machine/config.rs +++ b/src/machine/config.rs @@ -1,32 +1,74 @@ -pub struct MachineConfig { - pub streams: StreamConfig, - pub toplevel: &'static str, +/// Describes how the streams of a `crate::Machine` will be handled. +/// +/// Defaults to using standard IO. +pub struct StreamConfig { + pub(crate) inner: StreamConfigInner, +} + +impl Default for StreamConfig { + fn default() -> Self { + Self::stdio() + } +} + +impl StreamConfig { + /// Binds the input, output and error streams to stdin, stdout and stderr. + pub fn stdio() -> Self { + StreamConfig { + inner: StreamConfigInner::Stdio, + } + } + + /// Binds the output stream to a memory buffer, and the error stream to stderr. + /// + /// The input stream is ignored. + pub fn in_memory() -> Self { + StreamConfig { + inner: StreamConfigInner::Memory, + } + } } -pub enum StreamConfig { +pub(crate) enum StreamConfigInner { Stdio, Memory, } +/// Describes how a `crate::Machine` will be configured. +pub struct MachineConfig { + pub(crate) streams: StreamConfig, + pub(crate) toplevel: &'static str, +} + impl Default for MachineConfig { fn default() -> Self { MachineConfig { - streams: StreamConfig::Stdio, - toplevel: include_str!("../toplevel.pl"), + streams: Default::default(), + toplevel: default_toplevel(), } } } impl MachineConfig { - pub fn in_memory() -> Self { - MachineConfig { - streams: StreamConfig::Memory, - ..Default::default() - } + /// Creates a default configuration. + pub fn new() -> Self { + Default::default() + } + + /// Uses the given `crate::StreamConfig` in this configuration. + pub fn with_streams(mut self, streams: StreamConfig) -> Self { + self.streams = streams; + self } + /// Uses the given toplevel in this configuration. pub fn with_toplevel(mut self, toplevel: &'static str) -> Self { self.toplevel = toplevel; self } } + +/// Returns a static string slice to the default toplevel +pub fn default_toplevel() -> &'static str { + include_str!("../toplevel.pl") +} diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index 52fb3e020..abfe87bf1 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use crate::atom_table; +use crate::{atom_table, StreamConfig}; use crate::machine::machine_indices::VarKey; use crate::machine::mock_wam::CompositeOpDir; use crate::machine::{BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS}; @@ -144,7 +144,7 @@ impl Iterator for QueryState<'_> { impl Machine { pub fn new_lib() -> Self { - Machine::new(MachineConfig::in_memory()) + Machine::new(MachineConfig::default().with_streams(StreamConfig::in_memory())) } pub fn load_module_string(&mut self, module_name: &str, program: String) { diff --git a/src/machine/mock_wam.rs b/src/machine/mock_wam.rs index 90aefa0e7..2c7e37d6b 100644 --- a/src/machine/mock_wam.rs +++ b/src/machine/mock_wam.rs @@ -6,6 +6,7 @@ pub use crate::machine::*; pub use crate::parser::ast::*; use crate::read::*; pub use crate::types::*; +use crate::StreamConfig; use std::sync::Arc; @@ -232,7 +233,7 @@ pub(crate) fn parse_and_write_parsed_term_to_heap( impl Machine { pub fn with_test_streams() -> Self { - Machine::new(MachineConfig::in_memory()) + Machine::new(MachineConfig::default().with_streams(StreamConfig::in_memory())) } pub fn test_load_file(&mut self, file: &str) -> Vec { diff --git a/src/machine/mod.rs b/src/machine/mod.rs index f35288926..94f2842f8 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -485,13 +485,13 @@ impl Machine { let args = MachineArgs::new(); let mut machine_st = MachineState::new(); - let (user_input, user_output, user_error) = match config.streams { - config::StreamConfig::Stdio => ( + let (user_input, user_output, user_error) = match config.streams.inner { + config::StreamConfigInner::Stdio => ( Stream::stdin(&mut machine_st.arena, args.add_history), Stream::stdout(&mut machine_st.arena), Stream::stderr(&mut machine_st.arena), ), - config::StreamConfig::Memory => ( + config::StreamConfigInner::Memory => ( Stream::Null(StreamOptions::default()), Stream::from_owned_string("".to_owned(), &mut machine_st.arena), Stream::stderr(&mut machine_st.arena), From 71dec62ce2475ff4eb0c307f42dedb15de192bb6 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 22:12:29 -0300 Subject: [PATCH 04/28] Basic docs and non_exhaustive for PrologTerm --- src/machine/parsed_results.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/machine/parsed_results.rs b/src/machine/parsed_results.rs index 4ab1f91a2..561311cef 100644 --- a/src/machine/parsed_results.rs +++ b/src/machine/parsed_results.rs @@ -11,6 +11,7 @@ use std::collections::BTreeMap; use super::Machine; use super::{HeapCellValue, Number}; +/// Represents a leaf answer from a query. #[derive(Debug, Clone, PartialEq, Eq)] pub enum LeafAnswer { True, @@ -22,6 +23,8 @@ pub enum LeafAnswer { }, } +/// Represents a Prolog term. +#[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq)] pub enum PrologTerm { Integer(Integer), From 4480e7c0667ca813cf2d77f83741025573831d30 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 22:30:24 -0300 Subject: [PATCH 05/28] Associated functions for creating PrologTerm --- src/machine/parsed_results.rs | 57 ++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/machine/parsed_results.rs b/src/machine/parsed_results.rs index 561311cef..6b7e3fc7f 100644 --- a/src/machine/parsed_results.rs +++ b/src/machine/parsed_results.rs @@ -33,10 +33,57 @@ pub enum PrologTerm { Atom(String), String(String), List(Vec), - Structure(String, Vec), + Compound(String, Vec), Var(String), } +impl PrologTerm { + /// Creates an integer term. + pub fn integer(value: impl Into) -> Self { + PrologTerm::Integer(value.into()) + } + + /// Creates a rational term. + pub fn rational(value: impl Into) -> Self { + PrologTerm::Rational(value.into()) + } + + /// Creates a float term. + pub fn float(value: impl Into>) -> Self { + PrologTerm::Float(value.into()) + } + + /// Creates an atom term. + pub fn atom(value: impl Into) -> Self { + PrologTerm::Atom(value.into()) + } + + /// Creates a string term. + /// + /// In specific, this represents a list of chars in Prolog. + pub fn string(value: impl Into) -> Self { + PrologTerm::String(value.into()) + } + + /// Creates a list term. + pub fn list(value: impl IntoIterator) -> Self { + PrologTerm::List(value.into_iter().collect()) + } + + /// Creates a compound term. + pub fn compound( + functor: impl Into, + args: impl IntoIterator, + ) -> Self { + PrologTerm::Compound(functor.into(), args.into_iter().collect()) + } + + /// Creates a variable. + pub fn variable(value: impl Into) -> Self { + PrologTerm::Var(value.into()) + } +} + /// This is an auxiliary function to turn a count into names of anonymous variables like _A, _B, /// _AB, etc... fn count_to_letter_code(mut count: usize) -> String { @@ -124,7 +171,7 @@ impl PrologTerm { } }, _ => { - PrologTerm::Structure(".".into(), vec![head, tail]) + PrologTerm::Compound(".".into(), vec![head, tail]) } }; term_stack.push(list); @@ -218,7 +265,7 @@ impl PrologTerm { .drain(term_stack.len() - arity ..) .collect(); - term_stack.push(PrologTerm::Structure(name.as_str().to_string(), subterms)); + term_stack.push(PrologTerm::Compound(name.as_str().to_string(), subterms)); } } (HeapCellValueTag::PStr, atom) => { @@ -248,7 +295,7 @@ impl PrologTerm { .map(|x| PrologTerm::Atom(x.to_string())) .collect(); - let mut partial_list = PrologTerm::Structure( + let mut partial_list = PrologTerm::Compound( ".".into(), vec![ list.pop().unwrap(), @@ -257,7 +304,7 @@ impl PrologTerm { ); while let Some(last) = list.pop() { - partial_list = PrologTerm::Structure( + partial_list = PrologTerm::Compound( ".".into(), vec![ last, From e74fd11b0dc7c8e099b95552ed9b70ccec3453f3 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 23:06:43 -0300 Subject: [PATCH 06/28] Conjunctions, disjunction, and LeafAnswer to PrologTerm --- src/machine/parsed_results.rs | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/machine/parsed_results.rs b/src/machine/parsed_results.rs index 6b7e3fc7f..93d15ee1f 100644 --- a/src/machine/parsed_results.rs +++ b/src/machine/parsed_results.rs @@ -82,6 +82,54 @@ impl PrologTerm { pub fn variable(value: impl Into) -> Self { PrologTerm::Var(value.into()) } + + /// Creates a conjunction, giving the atom `true` if empty. + pub fn conjunction(value: impl IntoIterator) -> Self { + PrologTerm::try_conjunction(value).unwrap_or(PrologTerm::atom("true")) + } + + /// Creates a conjunction, giving `None` if empty. + pub fn try_conjunction(value: impl IntoIterator) -> Option { + let mut iter = value.into_iter(); + iter.next() + .map(|first| { + PrologTerm::try_conjunction(iter) + .map(|rest| PrologTerm::compound(",", [first.clone(), rest])) + .unwrap_or(first) + }) + } + + /// Creates a disjunction, giving the atom `false` if empty. + pub fn disjunction(value: impl IntoIterator) -> Self { + PrologTerm::try_disjunction(value).unwrap_or(PrologTerm::atom("false")) + } + + /// Creates a disjunction, giving `None` if empty. + pub fn try_disjunction(value: impl IntoIterator) -> Option { + let mut iter = value.into_iter(); + iter.next() + .map(|first| { + PrologTerm::try_disjunction(iter) + .map(|rest| PrologTerm::compound(";", [first.clone(), rest])) + .unwrap_or(first) + }) + } +} + +impl From for PrologTerm { + fn from(value: LeafAnswer) -> Self { + match value { + LeafAnswer::True => PrologTerm::atom("true"), + LeafAnswer::False => PrologTerm::atom("false"), + LeafAnswer::Exception(inner) => match inner.clone() { + PrologTerm::Compound(functor, args) if functor == "error" && args.len() == 2 => inner, + _ => PrologTerm::compound("throw", [inner]), + }, + LeafAnswer::LeafAnswer { bindings: _, residual_goals: _ } => { + todo!() + }, + } + } } /// This is an auxiliary function to turn a count into names of anonymous variables like _A, _B, From 0433706db68c54604f175a671ba203231f10140e Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 23:09:47 -0300 Subject: [PATCH 07/28] More PrologTerm documentation --- src/machine/config.rs | 2 +- src/machine/lib_machine.rs | 11 +++++---- src/machine/parsed_results.rs | 43 +++++++++++++++++++++++------------ 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/machine/config.rs b/src/machine/config.rs index 79752f317..b968e6249 100644 --- a/src/machine/config.rs +++ b/src/machine/config.rs @@ -55,7 +55,7 @@ impl MachineConfig { Default::default() } - /// Uses the given `crate::StreamConfig` in this configuration. + /// Uses the given `crate::StreamConfig` in this configuration. pub fn with_streams(mut self, streams: StreamConfig) -> Self { self.streams = streams; self diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index abfe87bf1..4137a9855 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -1,17 +1,17 @@ use std::collections::BTreeMap; -use crate::{atom_table, StreamConfig}; use crate::machine::machine_indices::VarKey; 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, TermWriteResult}; +use crate::{atom_table, StreamConfig}; use indexmap::IndexMap; use super::{ - streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine, MachineConfig, - LeafAnswer, PrologTerm, + streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, LeafAnswer, Machine, + MachineConfig, PrologTerm, }; pub struct QueryState<'a> { @@ -138,7 +138,10 @@ impl Iterator for QueryState<'_> { // choice point, so we should break. self.machine.machine_st.backtrack(); - Some(Ok(LeafAnswer::LeafAnswer { bindings: bindings, residual_goals: vec![] })) + Some(Ok(LeafAnswer::LeafAnswer { + bindings: bindings, + residual_goals: vec![], + })) } } diff --git a/src/machine/parsed_results.rs b/src/machine/parsed_results.rs index 93d15ee1f..3688bc97e 100644 --- a/src/machine/parsed_results.rs +++ b/src/machine/parsed_results.rs @@ -27,13 +27,23 @@ pub enum LeafAnswer { #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq)] pub enum PrologTerm { + /// An arbitrary precision integer. Integer(Integer), + /// An arbitrary precision rational. Rational(Rational), + /// A float. Float(OrderedFloat), + /// A Prolog atom. Atom(String), + /// A Prolog string. + /// + /// In particular, this represents Prolog lists of characters. String(String), + /// A Prolog list. List(Vec), + /// A Prolog compound term. Compound(String, Vec), + /// A Prolog variable. Var(String), } @@ -91,12 +101,11 @@ impl PrologTerm { /// Creates a conjunction, giving `None` if empty. pub fn try_conjunction(value: impl IntoIterator) -> Option { let mut iter = value.into_iter(); - iter.next() - .map(|first| { - PrologTerm::try_conjunction(iter) - .map(|rest| PrologTerm::compound(",", [first.clone(), rest])) - .unwrap_or(first) - }) + iter.next().map(|first| { + PrologTerm::try_conjunction(iter) + .map(|rest| PrologTerm::compound(",", [first.clone(), rest])) + .unwrap_or(first) + }) } /// Creates a disjunction, giving the atom `false` if empty. @@ -107,12 +116,11 @@ impl PrologTerm { /// Creates a disjunction, giving `None` if empty. pub fn try_disjunction(value: impl IntoIterator) -> Option { let mut iter = value.into_iter(); - iter.next() - .map(|first| { - PrologTerm::try_disjunction(iter) - .map(|rest| PrologTerm::compound(";", [first.clone(), rest])) - .unwrap_or(first) - }) + iter.next().map(|first| { + PrologTerm::try_disjunction(iter) + .map(|rest| PrologTerm::compound(";", [first.clone(), rest])) + .unwrap_or(first) + }) } } @@ -122,12 +130,17 @@ impl From for PrologTerm { LeafAnswer::True => PrologTerm::atom("true"), LeafAnswer::False => PrologTerm::atom("false"), LeafAnswer::Exception(inner) => match inner.clone() { - PrologTerm::Compound(functor, args) if functor == "error" && args.len() == 2 => inner, + PrologTerm::Compound(functor, args) if functor == "error" && args.len() == 2 => { + inner + } _ => PrologTerm::compound("throw", [inner]), }, - LeafAnswer::LeafAnswer { bindings: _, residual_goals: _ } => { + LeafAnswer::LeafAnswer { + bindings: _, + residual_goals: _, + } => { todo!() - }, + } } } } From 1375f448a04adbfd2c41db113e8acf0ff429f2ef Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 23:21:18 -0300 Subject: [PATCH 08/28] LeafAnswer docs and success checking methods --- src/machine/parsed_results.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/machine/parsed_results.rs b/src/machine/parsed_results.rs index 3688bc97e..ec3d46936 100644 --- a/src/machine/parsed_results.rs +++ b/src/machine/parsed_results.rs @@ -14,15 +14,47 @@ use super::{HeapCellValue, Number}; /// Represents a leaf answer from a query. #[derive(Debug, Clone, PartialEq, Eq)] pub enum LeafAnswer { + /// A `true` leaf answer. True, + /// A `false` leaf answer. + /// + /// This means that there are no more answers for the query. False, + /// An exception leaf answer. Exception(PrologTerm), + /// A leaf answer with bindings and residual goals. + /// + /// Both bindings and residual goals can be empty. LeafAnswer { bindings: BTreeMap, residual_goals: Vec, }, } +impl LeafAnswer { + /// True if leaf answer failed. + /// + /// This gives [`false`] for exceptions. + pub fn failed(&self) -> bool { + match self { + LeafAnswer::False => true, + _ => false, + } + } + + /// True if leaf answer may have succeeded. + /// + /// When a leaf answer has residual goals the success is conditional on the satisfiability of + /// the contraints they represent. This gives [`false`] for exceptions. + pub fn maybe_succeeded(&self) -> bool { + match self { + LeafAnswer::True => true, + LeafAnswer::LeafAnswer { .. } => true, + _ => false, + } + } +} + /// Represents a Prolog term. #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq)] From c13fc2d1b6f84addf517cc7bdc3af0aef8373370 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 23:22:51 -0300 Subject: [PATCH 09/28] Docs for run_binary() --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 2a736a1ea..61f98dd9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,7 @@ pub fn eval_code(s: &str) -> String { String::from_utf8_lossy(&bytes).to_string() } +/// The entry point for the Scryer Prolog CLI. pub fn run_binary() -> std::process::ExitCode { use crate::atom_table::Atom; use crate::machine::{Machine, INTERRUPT}; From cb040dafc15626842a2fb8bade94954dd587df82 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 23:30:57 -0300 Subject: [PATCH 10/28] Docs for Machine and QueryState --- src/machine/lib_machine.rs | 17 +++++++++++------ src/machine/mod.rs | 3 +++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index 4137a9855..5f0f42135 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -14,6 +14,7 @@ use super::{ MachineConfig, PrologTerm, }; +/// An iterator though the leaf answers of a query. pub struct QueryState<'a> { machine: &'a mut Machine, term: TermWriteResult, @@ -146,17 +147,20 @@ impl Iterator for QueryState<'_> { } impl Machine { + /// Creates a new [`Machine`] configured for use as a library. pub fn new_lib() -> Self { Machine::new(MachineConfig::default().with_streams(StreamConfig::in_memory())) } - pub fn load_module_string(&mut self, module_name: &str, program: String) { - let stream = Stream::from_owned_string(program, &mut self.machine_st.arena); + /// Loads a module into the [`Machine`] from a string. + pub fn load_module_string(&mut self, module_name: &str, program: impl Into) { + let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena); self.load_file(module_name, stream); } - pub fn consult_module_string(&mut self, module_name: &str, program: String) { - let stream = Stream::from_owned_string(program, &mut self.machine_st.arena); + /// Consults a module into the [`Machine`] from a string. + pub fn consult_module_string(&mut self, module_name: &str, program: impl Into) { + let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena); self.machine_st.registers[1] = stream_as_cell!(stream); self.machine_st.registers[2] = atom_as_cell!(&atom_table::AtomTable::build_with( &self.machine_st.atom_tbl, @@ -190,9 +194,10 @@ impl Machine { self.machine_st.block = stub_b; } - pub fn run_query(&mut self, query: String) -> QueryState { + /// Runs a query. + pub fn run_query(&mut self, query: impl Into) -> QueryState { let mut parser = Parser::new( - Stream::from_owned_string(query, &mut self.machine_st.arena), + Stream::from_owned_string(query.into(), &mut self.machine_st.arena), &mut self.machine_st, ); let op_dir = CompositeOpDir::new(&self.indices.op_dir, None); diff --git a/src/machine/mod.rs b/src/machine/mod.rs index 94f2842f8..448734e40 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -70,6 +70,7 @@ lazy_static! { pub static ref INTERRUPT: AtomicBool = AtomicBool::new(false); } +/// An instance of Scryer Prolog. #[derive(Debug)] pub struct Machine { pub(super) machine_st: MachineState, @@ -262,6 +263,7 @@ impl Machine { ) } + /// Gets the current inference count. pub fn get_inference_count(&mut self) -> u64 { self.machine_st .cwil @@ -480,6 +482,7 @@ impl Machine { } } + /// Creates a new [`Machine`] from a [`MachineConfig`]. #[allow(clippy::new_without_default)] pub fn new(config: MachineConfig) -> Self { let args = MachineArgs::new(); From dc8348b843e0263347ca13d8675f309b127a5b93 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 23:42:11 -0300 Subject: [PATCH 11/28] Add interfaces for QueryState methods --- src/machine/lib_machine.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index 5f0f42135..04cfb55c2 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -23,6 +23,25 @@ pub struct QueryState<'a> { called: bool, } +impl QueryState<'_> { + /// True if the query fails. + /// + /// Consumes the query. Gives [`false`] if an exception occurs. + pub fn fails(&mut self) -> bool { + todo!() + } + + /// True if the query maybe succeeded. + /// + /// If a leaf answer has residual goals, it's only successful if the constraints they represent + /// are satisfiable. + /// + /// Consumes the query. Gives [`false`] if an exception occurs. + pub fn maybe_suceeded() -> bool { + todo!() + } +} + impl Drop for QueryState<'_> { fn drop(&mut self) { // This may be wrong if the iterator is not fully consumend, but from testing it seems From e6cc408ff20df2079f13808c66e2a45e15aaa577 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 23:45:40 -0300 Subject: [PATCH 12/28] Document test methods --- src/machine/mock_wam.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/machine/mock_wam.rs b/src/machine/mock_wam.rs index 2c7e37d6b..a4aba1128 100644 --- a/src/machine/mock_wam.rs +++ b/src/machine/mock_wam.rs @@ -232,10 +232,12 @@ pub(crate) fn parse_and_write_parsed_term_to_heap( } impl Machine { + /// For use in tests. pub fn with_test_streams() -> Self { Machine::new(MachineConfig::default().with_streams(StreamConfig::in_memory())) } + /// For use in tests. pub fn test_load_file(&mut self, file: &str) -> Vec { let stream = Stream::from_owned_string( std::fs::read_to_string(AsRef::::as_ref(file)).unwrap(), @@ -246,6 +248,7 @@ impl Machine { self.user_output.bytes().map(|b| b.unwrap()).collect() } + /// For use in tests. pub fn test_load_string(&mut self, code: &str) -> Vec { let stream = Stream::from_owned_string(code.to_owned(), &mut self.machine_st.arena); From cab61738adb92ff2d157acb84e78ddced4f7f319 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 23:50:15 -0300 Subject: [PATCH 13/28] #[deny(missing_docs)] --- src/lib.rs | 2 ++ src/machine/parsed_results.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 61f98dd9d..dc8693ecc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ +//! A free software ISO Prolog system. #![recursion_limit = "4112"] +#![deny(missing_docs)] #[macro_use] extern crate static_assertions; diff --git a/src/machine/parsed_results.rs b/src/machine/parsed_results.rs index ec3d46936..4791c7772 100644 --- a/src/machine/parsed_results.rs +++ b/src/machine/parsed_results.rs @@ -23,10 +23,14 @@ pub enum LeafAnswer { /// An exception leaf answer. Exception(PrologTerm), /// A leaf answer with bindings and residual goals. - /// - /// Both bindings and residual goals can be empty. LeafAnswer { + /// The bindings of variables in the query. + /// + /// Can be empty. bindings: BTreeMap, + /// Residual goals. + /// + /// Can be empty. residual_goals: Vec, }, } From 79fbd9e90f531a7e73c1cf4fc5f85e945638373c Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 29 Sep 2024 23:53:37 -0300 Subject: [PATCH 14/28] Fix Machine links --- src/machine/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/machine/config.rs b/src/machine/config.rs index b968e6249..ec930464f 100644 --- a/src/machine/config.rs +++ b/src/machine/config.rs @@ -1,4 +1,4 @@ -/// Describes how the streams of a `crate::Machine` will be handled. +/// Describes how the streams of a [`Machine`](crate::Machine) will be handled. /// /// Defaults to using standard IO. pub struct StreamConfig { @@ -34,7 +34,7 @@ pub(crate) enum StreamConfigInner { Memory, } -/// Describes how a `crate::Machine` will be configured. +/// Describes how a [`Machine`](crate::Machine) will be configured. pub struct MachineConfig { pub(crate) streams: StreamConfig, pub(crate) toplevel: &'static str, From d336cbc86a17105f1cfb7b21ec42605fbcb11ec9 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sat, 12 Oct 2024 16:07:13 -0300 Subject: [PATCH 15/28] MachineBuilder --- src/lib.rs | 6 +- src/machine/config.rs | 135 ++++++++++++++++++++++++++++++++++--- src/machine/lib_machine.rs | 9 +-- src/machine/mock_wam.rs | 6 -- src/machine/mod.rs | 113 +------------------------------ 5 files changed, 134 insertions(+), 135 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dc8693ecc..6148ef5c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ pub fn eval_code(s: &str) -> String { /// The entry point for the Scryer Prolog CLI. pub fn run_binary() -> std::process::ExitCode { use crate::atom_table::Atom; - use crate::machine::{Machine, INTERRUPT}; + use crate::machine::INTERRUPT; #[cfg(feature = "repl")] ctrlc::set_handler(move || { @@ -87,7 +87,9 @@ pub fn run_binary() -> std::process::ExitCode { .unwrap(); runtime.block_on(async move { - let mut wam = Machine::new(Default::default()); + let mut wam = MachineBuilder::default() + .with_streams(StreamConfig::stdio()) + .build(); wam.run_module_predicate(atom!("$toplevel"), (atom!("$repl"), 0)) }) } diff --git a/src/machine/config.rs b/src/machine/config.rs index ec930464f..039cf2f45 100644 --- a/src/machine/config.rs +++ b/src/machine/config.rs @@ -1,13 +1,21 @@ +use rand::{rngs::StdRng, SeedableRng}; + +use crate::Machine; + +use super::{ + bootstrapping_compile, current_dir, import_builtin_impls, libraries, load_module, Atom, + CompilationTarget, IndexStore, ListingSource, MachineArgs, MachineState, Stream, StreamOptions, +}; + /// Describes how the streams of a [`Machine`](crate::Machine) will be handled. -/// -/// Defaults to using standard IO. pub struct StreamConfig { - pub(crate) inner: StreamConfigInner, + inner: StreamConfigInner, } impl Default for StreamConfig { + /// Defaults to using in-memory streams. fn default() -> Self { - Self::stdio() + Self::in_memory() } } @@ -29,27 +37,28 @@ impl StreamConfig { } } -pub(crate) enum StreamConfigInner { +enum StreamConfigInner { Stdio, Memory, } /// Describes how a [`Machine`](crate::Machine) will be configured. -pub struct MachineConfig { +pub struct MachineBuilder { pub(crate) streams: StreamConfig, pub(crate) toplevel: &'static str, } -impl Default for MachineConfig { +impl Default for MachineBuilder { + /// Defaults to using in-memory streams. fn default() -> Self { - MachineConfig { + MachineBuilder { streams: Default::default(), toplevel: default_toplevel(), } } } -impl MachineConfig { +impl MachineBuilder { /// Creates a default configuration. pub fn new() -> Self { Default::default() @@ -66,6 +75,114 @@ impl MachineConfig { self.toplevel = toplevel; self } + + /// Builds the [`Machine`](crate::Machine) from this configuration. + pub fn build(self) -> Machine { + let args = MachineArgs::new(); + let mut machine_st = MachineState::new(); + + let (user_input, user_output, user_error) = match self.streams.inner { + StreamConfigInner::Stdio => ( + Stream::stdin(&mut machine_st.arena, args.add_history), + Stream::stdout(&mut machine_st.arena), + Stream::stderr(&mut machine_st.arena), + ), + StreamConfigInner::Memory => ( + Stream::Null(StreamOptions::default()), + Stream::from_owned_string("".to_owned(), &mut machine_st.arena), + Stream::stderr(&mut machine_st.arena), + ), + }; + + let mut wam = Machine { + machine_st, + indices: IndexStore::new(), + code: vec![], + user_input, + user_output, + user_error, + load_contexts: vec![], + #[cfg(feature = "ffi")] + foreign_function_table: Default::default(), + rng: StdRng::from_entropy(), + }; + + let mut lib_path = current_dir(); + + lib_path.pop(); + lib_path.push("lib"); + + wam.add_impls_to_indices(); + + bootstrapping_compile( + Stream::from_static_string( + libraries::get("ops_and_meta_predicates") + .expect("library ops_and_meta_predicates should exist"), + &mut wam.machine_st.arena, + ), + &mut wam, + ListingSource::from_file_and_path( + atom!("ops_and_meta_predicates.pl"), + lib_path.clone(), + ), + ) + .unwrap(); + + bootstrapping_compile( + Stream::from_static_string( + libraries::get("builtins").expect("library builtins should exist"), + &mut wam.machine_st.arena, + ), + &mut wam, + ListingSource::from_file_and_path(atom!("builtins.pl"), lib_path.clone()), + ) + .unwrap(); + + if let Some(builtins) = wam.indices.modules.get_mut(&atom!("builtins")) { + load_module( + &mut wam.machine_st, + &mut wam.indices.code_dir, + &mut wam.indices.op_dir, + &mut wam.indices.meta_predicates, + &CompilationTarget::User, + builtins, + ); + + import_builtin_impls(&wam.indices.code_dir, builtins); + } else { + unreachable!() + } + + lib_path.pop(); // remove the "lib" at the end + + bootstrapping_compile( + Stream::from_static_string(include_str!("../loader.pl"), &mut wam.machine_st.arena), + &mut wam, + ListingSource::from_file_and_path(atom!("loader.pl"), lib_path.clone()), + ) + .unwrap(); + + wam.configure_modules(); + + if let Some(loader) = wam.indices.modules.get(&atom!("loader")) { + load_module( + &mut wam.machine_st, + &mut wam.indices.code_dir, + &mut wam.indices.op_dir, + &mut wam.indices.meta_predicates, + &CompilationTarget::User, + loader, + ); + } else { + unreachable!() + } + + wam.load_special_forms(); + wam.load_top_level(self.toplevel); + wam.configure_streams(); + + wam + } } /// Returns a static string slice to the default toplevel diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index 04cfb55c2..c4f168b6e 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -1,17 +1,17 @@ use std::collections::BTreeMap; +use crate::atom_table; use crate::machine::machine_indices::VarKey; 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, TermWriteResult}; -use crate::{atom_table, StreamConfig}; use indexmap::IndexMap; use super::{ streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, LeafAnswer, Machine, - MachineConfig, PrologTerm, + PrologTerm, }; /// An iterator though the leaf answers of a query. @@ -166,11 +166,6 @@ impl Iterator for QueryState<'_> { } impl Machine { - /// Creates a new [`Machine`] configured for use as a library. - pub fn new_lib() -> Self { - Machine::new(MachineConfig::default().with_streams(StreamConfig::in_memory())) - } - /// Loads a module into the [`Machine`] from a string. pub fn load_module_string(&mut self, module_name: &str, program: impl Into) { let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena); diff --git a/src/machine/mock_wam.rs b/src/machine/mock_wam.rs index a4aba1128..457afacfd 100644 --- a/src/machine/mock_wam.rs +++ b/src/machine/mock_wam.rs @@ -6,7 +6,6 @@ pub use crate::machine::*; pub use crate::parser::ast::*; use crate::read::*; pub use crate::types::*; -use crate::StreamConfig; use std::sync::Arc; @@ -232,11 +231,6 @@ pub(crate) fn parse_and_write_parsed_term_to_heap( } impl Machine { - /// For use in tests. - pub fn with_test_streams() -> Self { - Machine::new(MachineConfig::default().with_streams(StreamConfig::in_memory())) - } - /// For use in tests. pub fn test_load_file(&mut self, file: &str) -> Vec { let stream = Stream::from_owned_string( diff --git a/src/machine/mod.rs b/src/machine/mod.rs index 448734e40..7da191467 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -55,7 +55,6 @@ use lazy_static::lazy_static; use ordered_float::OrderedFloat; use rand::rngs::StdRng; -use rand::SeedableRng; use std::cmp::Ordering; use std::env; use std::io::Read; @@ -63,7 +62,6 @@ use std::path::PathBuf; use std::sync::atomic::AtomicBool; use std::sync::OnceLock; -use self::config::MachineConfig; use self::parsed_results::*; lazy_static! { @@ -71,6 +69,8 @@ lazy_static! { } /// An instance of Scryer Prolog. +/// +/// Created with [`MachineBuilder::build`](crate::machine::config::MachineBuilder::build). #[derive(Debug)] pub struct Machine { pub(super) machine_st: MachineState, @@ -482,115 +482,6 @@ impl Machine { } } - /// Creates a new [`Machine`] from a [`MachineConfig`]. - #[allow(clippy::new_without_default)] - pub fn new(config: MachineConfig) -> Self { - let args = MachineArgs::new(); - let mut machine_st = MachineState::new(); - - let (user_input, user_output, user_error) = match config.streams.inner { - config::StreamConfigInner::Stdio => ( - Stream::stdin(&mut machine_st.arena, args.add_history), - Stream::stdout(&mut machine_st.arena), - Stream::stderr(&mut machine_st.arena), - ), - config::StreamConfigInner::Memory => ( - Stream::Null(StreamOptions::default()), - Stream::from_owned_string("".to_owned(), &mut machine_st.arena), - Stream::stderr(&mut machine_st.arena), - ), - }; - - let mut wam = Machine { - machine_st, - indices: IndexStore::new(), - code: vec![], - user_input, - user_output, - user_error, - load_contexts: vec![], - #[cfg(feature = "ffi")] - foreign_function_table: Default::default(), - rng: StdRng::from_entropy(), - }; - - let mut lib_path = current_dir(); - - lib_path.pop(); - lib_path.push("lib"); - - wam.add_impls_to_indices(); - - bootstrapping_compile( - Stream::from_static_string( - libraries::get("ops_and_meta_predicates") - .expect("library ops_and_meta_predicates should exist"), - &mut wam.machine_st.arena, - ), - &mut wam, - ListingSource::from_file_and_path( - atom!("ops_and_meta_predicates.pl"), - lib_path.clone(), - ), - ) - .unwrap(); - - bootstrapping_compile( - Stream::from_static_string( - libraries::get("builtins").expect("library builtins should exist"), - &mut wam.machine_st.arena, - ), - &mut wam, - ListingSource::from_file_and_path(atom!("builtins.pl"), lib_path.clone()), - ) - .unwrap(); - - if let Some(builtins) = wam.indices.modules.get_mut(&atom!("builtins")) { - load_module( - &mut wam.machine_st, - &mut wam.indices.code_dir, - &mut wam.indices.op_dir, - &mut wam.indices.meta_predicates, - &CompilationTarget::User, - builtins, - ); - - import_builtin_impls(&wam.indices.code_dir, builtins); - } else { - unreachable!() - } - - lib_path.pop(); // remove the "lib" at the end - - bootstrapping_compile( - Stream::from_static_string(include_str!("../loader.pl"), &mut wam.machine_st.arena), - &mut wam, - ListingSource::from_file_and_path(atom!("loader.pl"), lib_path.clone()), - ) - .unwrap(); - - wam.configure_modules(); - - if let Some(loader) = wam.indices.modules.get(&atom!("loader")) { - load_module( - &mut wam.machine_st, - &mut wam.indices.code_dir, - &mut wam.indices.op_dir, - &mut wam.indices.meta_predicates, - &CompilationTarget::User, - loader, - ); - } else { - unreachable!() - } - - wam.load_special_forms(); - wam.load_top_level(config.toplevel); - wam.configure_streams(); - - wam - } - pub(crate) fn configure_streams(&mut self) { self.user_input .options_mut() From 33d8abfed868f31e0b6d475c98f48d6c1bec318f Mon Sep 17 00:00:00 2001 From: bakaq Date: Sat, 12 Oct 2024 16:42:56 -0300 Subject: [PATCH 16/28] Remove parsed_results.rs --- src/lib.rs | 1 - src/machine/lib_machine.rs | 451 ++++++++++++++++++++++++++++++++-- src/machine/mod.rs | 3 - src/machine/parsed_results.rs | 438 --------------------------------- 4 files changed, 427 insertions(+), 466 deletions(-) delete mode 100644 src/machine/parsed_results.rs diff --git a/src/lib.rs b/src/lib.rs index 6148ef5c7..9a7a797e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,6 @@ use wasm_bindgen::prelude::*; // Re-exports pub use machine::config::*; pub use machine::lib_machine::*; -pub use machine::parsed_results::*; pub use machine::Machine; #[cfg(target_arch = "wasm32")] diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index c4f168b6e..ba1be14cc 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -1,45 +1,448 @@ +use std::cmp::Ordering; use std::collections::BTreeMap; use crate::atom_table; +use crate::heap_iter::{stackful_post_order_iter, NonListElider}; use crate::machine::machine_indices::VarKey; use crate::machine::mock_wam::CompositeOpDir; -use crate::machine::{BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS}; +use crate::machine::{ + F64Offset, F64Ptr, Fixnum, Number, 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, TermWriteResult}; + +use dashu::{Integer, Rational}; use indexmap::IndexMap; -use super::{ - streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, LeafAnswer, Machine, - PrologTerm, -}; +use super::{streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine}; -/// An iterator though the leaf answers of a query. -pub struct QueryState<'a> { - machine: &'a mut Machine, - term: TermWriteResult, - stub_b: usize, - var_names: IndexMap, - called: bool, +/// Represents a leaf answer from a query. +#[derive(Debug, Clone, PartialEq)] +pub enum LeafAnswer { + /// A `true` leaf answer. + True, + /// A `false` leaf answer. + /// + /// This means that there are no more answers for the query. + False, + /// An exception leaf answer. + Exception(PrologTerm), + /// A leaf answer with bindings and residual goals. + LeafAnswer { + /// The bindings of variables in the query. + /// + /// Can be empty. + bindings: BTreeMap, + /// Residual goals. + /// + /// Can be empty. + residual_goals: Vec, + }, } -impl QueryState<'_> { - /// True if the query fails. +impl LeafAnswer { + /// True if leaf answer failed. /// - /// Consumes the query. Gives [`false`] if an exception occurs. - pub fn fails(&mut self) -> bool { - todo!() + /// This gives [`false`] for exceptions. + pub fn failed(&self) -> bool { + matches!(self, LeafAnswer::False) } - /// True if the query maybe succeeded. + /// True if leaf answer may have succeeded. /// - /// If a leaf answer has residual goals, it's only successful if the constraints they represent - /// are satisfiable. + /// When a leaf answer has residual goals the success is conditional on the satisfiability of + /// the contraints they represent. This gives [`false`] for exceptions. + pub fn maybe_succeeded(&self) -> bool { + matches!(self, LeafAnswer::True | LeafAnswer::LeafAnswer { .. }) + } +} + +/// Represents a Prolog term. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq)] +pub enum PrologTerm { + /// An arbitrary precision integer. + Integer(Integer), + /// An arbitrary precision rational. + Rational(Rational), + /// A float. + Float(f64), + /// A Prolog atom. + Atom(String), + /// A Prolog string. + /// + /// In particular, this represents Prolog lists of characters. + String(String), + /// A Prolog list. + List(Vec), + /// A Prolog compound term. + Compound(String, Vec), + /// A Prolog variable. + Var(String), +} + +impl PrologTerm { + /// Creates an integer term. + pub fn integer(value: impl Into) -> Self { + PrologTerm::Integer(value.into()) + } + + /// Creates a rational term. + pub fn rational(value: impl Into) -> Self { + PrologTerm::Rational(value.into()) + } + + /// Creates a float term. + pub fn float(value: impl Into) -> Self { + PrologTerm::Float(value.into()) + } + + /// Creates an atom term. + pub fn atom(value: impl Into) -> Self { + PrologTerm::Atom(value.into()) + } + + /// Creates a string term. /// - /// Consumes the query. Gives [`false`] if an exception occurs. - pub fn maybe_suceeded() -> bool { - todo!() + /// In specific, this represents a list of chars in Prolog. + pub fn string(value: impl Into) -> Self { + PrologTerm::String(value.into()) + } + + /// Creates a list term. + pub fn list(value: impl IntoIterator) -> Self { + PrologTerm::List(value.into_iter().collect()) + } + + /// Creates a compound term. + pub fn compound( + functor: impl Into, + args: impl IntoIterator, + ) -> Self { + PrologTerm::Compound(functor.into(), args.into_iter().collect()) + } + + /// Creates a variable. + pub fn variable(value: impl Into) -> Self { + PrologTerm::Var(value.into()) + } + + /// Creates a conjunction, giving the atom `true` if empty. + pub fn conjunction(value: impl IntoIterator) -> Self { + PrologTerm::try_conjunction(value).unwrap_or(PrologTerm::atom("true")) } + + /// Creates a conjunction, giving `None` if empty. + pub fn try_conjunction(value: impl IntoIterator) -> Option { + let mut iter = value.into_iter(); + iter.next().map(|first| { + PrologTerm::try_conjunction(iter) + .map(|rest| PrologTerm::compound(",", [first.clone(), rest])) + .unwrap_or(first) + }) + } + + /// Creates a disjunction, giving the atom `false` if empty. + pub fn disjunction(value: impl IntoIterator) -> Self { + PrologTerm::try_disjunction(value).unwrap_or(PrologTerm::atom("false")) + } + + /// Creates a disjunction, giving `None` if empty. + pub fn try_disjunction(value: impl IntoIterator) -> Option { + let mut iter = value.into_iter(); + iter.next().map(|first| { + PrologTerm::try_disjunction(iter) + .map(|rest| PrologTerm::compound(";", [first.clone(), rest])) + .unwrap_or(first) + }) + } +} + +impl From for PrologTerm { + fn from(value: LeafAnswer) -> Self { + match value { + LeafAnswer::True => PrologTerm::atom("true"), + LeafAnswer::False => PrologTerm::atom("false"), + LeafAnswer::Exception(inner) => match inner.clone() { + PrologTerm::Compound(functor, args) if functor == "error" && args.len() == 2 => { + inner + } + _ => PrologTerm::compound("throw", [inner]), + }, + LeafAnswer::LeafAnswer { + bindings: _, + residual_goals: _, + } => { + todo!() + } + } + } +} + +/// This is an auxiliary function to turn a count into names of anonymous variables like _A, _B, +/// _AB, etc... +fn count_to_letter_code(mut count: usize) -> String { + let mut letters = Vec::new(); + + loop { + let letter_idx = (count % 26) as u32; + letters.push(char::from_u32('A' as u32 + letter_idx).unwrap()); + count /= 26; + + if count == 0 { + break; + } + } + + letters.into_iter().chain("_".chars()).rev().collect() +} + +impl PrologTerm { + pub(crate) fn from_heapcell( + machine: &mut Machine, + heap_cell: HeapCellValue, + var_names: &mut IndexMap, + ) -> Self { + // Adapted from MachineState::read_term_from_heap + let mut term_stack = vec![]; + let iter = stackful_post_order_iter::( + &mut machine.machine_st.heap, + &mut machine.machine_st.stack, + heap_cell, + ); + + let mut anon_count: usize = 0; + let var_ptr_cmp = |a, b| match a { + Var::Named(name_a) => match b { + Var::Named(name_b) => name_a.cmp(&name_b), + _ => Ordering::Less, + }, + _ => match b { + Var::Named(_) => Ordering::Greater, + _ => Ordering::Equal, + }, + }; + + for addr in iter { + let addr = unmark_cell_bits!(addr); + + read_heap_cell!(addr, + (HeapCellValueTag::Lis) => { + let tail = term_stack.pop().unwrap(); + let head = term_stack.pop().unwrap(); + + let list = match tail { + PrologTerm::Atom(atom) if atom == "[]" => match head { + PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { + // Handle lists of char as strings + PrologTerm::String(a.to_string()) + } + _ => PrologTerm::List(vec![head]), + }, + PrologTerm::List(elems) if elems.is_empty() => match head { + PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { + // Handle lists of char as strings + PrologTerm::String(a.to_string()) + }, + _ => PrologTerm::List(vec![head]), + }, + PrologTerm::List(mut elems) => { + elems.insert(0, head); + PrologTerm::List(elems) + }, + PrologTerm::String(mut elems) => match head { + PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { + // Handle lists of char as strings + elems.insert(0, a.chars().next().unwrap()); + PrologTerm::String(elems) + }, + _ => { + let mut elems: Vec = elems + .chars() + .map(|x| PrologTerm::Atom(x.into())) + .collect(); + elems.insert(0, head); + PrologTerm::List(elems) + } + }, + _ => { + PrologTerm::Compound(".".into(), vec![head, tail]) + } + }; + term_stack.push(list); + } + (HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar) => { + let var = var_names.get(&addr).map(|x| x.borrow().clone()); + match var { + Some(Var::Named(name)) => term_stack.push(PrologTerm::Var(name)), + _ => { + let anon_name = loop { + // Generate a name for the anonymous variable + let anon_name = count_to_letter_code(anon_count); + + // Find if this name is already being used + var_names.sort_by(|_, a, _, b| { + var_ptr_cmp(a.borrow().clone(), b.borrow().clone()) + }); + let binary_result = var_names.binary_search_by(|_,a| { + let var_ptr = Var::Named(anon_name.clone()); + var_ptr_cmp(a.borrow().clone(), var_ptr.clone()) + }); + + match binary_result { + Ok(_) => anon_count += 1, // Name already used + Err(_) => { + // Name not used, assign it to this variable + let var_ptr = VarPtr::from(Var::Named(anon_name.clone())); + var_names.insert(addr, var_ptr); + break anon_name; + }, + } + }; + term_stack.push(PrologTerm::Var(anon_name)); + }, + } + } + (HeapCellValueTag::F64, f) => { + term_stack.push(PrologTerm::Float((*f).into())); + } + (HeapCellValueTag::Char, c) => { + term_stack.push(PrologTerm::Atom(c.into())); + } + (HeapCellValueTag::Fixnum, n) => { + term_stack.push(PrologTerm::Integer(n.into())); + } + (HeapCellValueTag::Cons) => { + match Number::try_from(addr) { + Ok(Number::Integer(i)) => term_stack.push(PrologTerm::Integer((*i).clone())), + Ok(Number::Rational(r)) => term_stack.push(PrologTerm::Rational((*r).clone())), + _ => {} + } + } + (HeapCellValueTag::CStr, s) => { + term_stack.push(PrologTerm::String(s.as_str().to_string())); + } + (HeapCellValueTag::Atom, (name, arity)) => { + //let h = iter.focus().value() as usize; + //let mut arity = arity; + + // Not sure why/if this is needed. + // Might find out with better testing later. + /* + if iter.heap.len() > h + arity + 1 { + let value = iter.heap[h + arity + 1]; + + if let Some(idx) = get_structure_index(value) { + // in the second condition, arity == 0, + // meaning idx cannot pertain to this atom + // if it is the direct subterm of a larger + // structure. + if arity > 0 || !iter.direct_subterm_of_str(h) { + term_stack.push( + Term::Literal(Cell::default(), Literal::CodeIndex(idx)) + ); + + arity += 1; + } + } + } + */ + + if arity == 0 { + let atom_name = name.as_str().to_string(); + if atom_name == "[]" { + term_stack.push(PrologTerm::List(vec![])); + } else { + term_stack.push(PrologTerm::Atom(atom_name)); + } + } else { + let subterms = term_stack + .drain(term_stack.len() - arity ..) + .collect(); + + term_stack.push(PrologTerm::Compound(name.as_str().to_string(), subterms)); + } + } + (HeapCellValueTag::PStr, atom) => { + let tail = term_stack.pop().unwrap(); + + match tail { + PrologTerm::Atom(atom) => { + if atom == "[]" { + term_stack.push(PrologTerm::String(atom.as_str().to_string())); + } + }, + PrologTerm::List(l) => { + let mut list: Vec = atom + .as_str() + .to_string() + .chars() + .map(|x| PrologTerm::Atom(x.to_string())) + .collect(); + list.extend(l.into_iter()); + term_stack.push(PrologTerm::List(list)); + }, + _ => { + let mut list: Vec = atom + .as_str() + .to_string() + .chars() + .map(|x| PrologTerm::Atom(x.to_string())) + .collect(); + + let mut partial_list = PrologTerm::Compound( + ".".into(), + vec![ + list.pop().unwrap(), + tail, + ], + ); + + while let Some(last) = list.pop() { + partial_list = PrologTerm::Compound( + ".".into(), + vec![ + last, + partial_list, + ], + ); + } + + term_stack.push(partial_list); + } + } + } + // I dont know if this is needed here. + /* + (HeapCellValueTag::PStrLoc, h) => { + let atom = cell_as_atom_cell!(iter.heap[h]).get_name(); + let tail = term_stack.pop().unwrap(); + + term_stack.push(Term::PartialString( + Cell::default(), + atom.as_str().to_owned(), + Box::new(tail), + )); + } + */ + _ => { + } + ); + } + + debug_assert_eq!(term_stack.len(), 1); + term_stack.pop().unwrap() + } +} + +/// An iterator though the leaf answers of a query. +pub struct QueryState<'a> { + machine: &'a mut Machine, + term: TermWriteResult, + stub_b: usize, + var_names: IndexMap, + called: bool, } impl Drop for QueryState<'_> { @@ -159,7 +562,7 @@ impl Iterator for QueryState<'_> { self.machine.machine_st.backtrack(); Some(Ok(LeafAnswer::LeafAnswer { - bindings: bindings, + bindings, residual_goals: vec![], })) } diff --git a/src/machine/mod.rs b/src/machine/mod.rs index 7da191467..22c2a0322 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -20,7 +20,6 @@ pub mod machine_indices; pub mod machine_state; pub mod machine_state_impl; pub mod mock_wam; -pub mod parsed_results; pub mod partial_string; pub mod preprocessor; pub mod stack; @@ -62,8 +61,6 @@ use std::path::PathBuf; use std::sync::atomic::AtomicBool; use std::sync::OnceLock; -use self::parsed_results::*; - lazy_static! { pub static ref INTERRUPT: AtomicBool = AtomicBool::new(false); } diff --git a/src/machine/parsed_results.rs b/src/machine/parsed_results.rs deleted file mode 100644 index 4791c7772..000000000 --- a/src/machine/parsed_results.rs +++ /dev/null @@ -1,438 +0,0 @@ -use crate::atom_table::*; -use crate::heap_iter::{stackful_post_order_iter, NonListElider}; -use crate::machine::{F64Offset, F64Ptr, Fixnum, HeapCellValueTag}; -use crate::parser::ast::{Var, VarPtr}; -use dashu::*; -use indexmap::IndexMap; -use ordered_float::OrderedFloat; -use std::cmp::Ordering; -use std::collections::BTreeMap; - -use super::Machine; -use super::{HeapCellValue, Number}; - -/// Represents a leaf answer from a query. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum LeafAnswer { - /// A `true` leaf answer. - True, - /// A `false` leaf answer. - /// - /// This means that there are no more answers for the query. - False, - /// An exception leaf answer. - Exception(PrologTerm), - /// A leaf answer with bindings and residual goals. - LeafAnswer { - /// The bindings of variables in the query. - /// - /// Can be empty. - bindings: BTreeMap, - /// Residual goals. - /// - /// Can be empty. - residual_goals: Vec, - }, -} - -impl LeafAnswer { - /// True if leaf answer failed. - /// - /// This gives [`false`] for exceptions. - pub fn failed(&self) -> bool { - match self { - LeafAnswer::False => true, - _ => false, - } - } - - /// True if leaf answer may have succeeded. - /// - /// When a leaf answer has residual goals the success is conditional on the satisfiability of - /// the contraints they represent. This gives [`false`] for exceptions. - pub fn maybe_succeeded(&self) -> bool { - match self { - LeafAnswer::True => true, - LeafAnswer::LeafAnswer { .. } => true, - _ => false, - } - } -} - -/// Represents a Prolog term. -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PrologTerm { - /// An arbitrary precision integer. - Integer(Integer), - /// An arbitrary precision rational. - Rational(Rational), - /// A float. - Float(OrderedFloat), - /// A Prolog atom. - Atom(String), - /// A Prolog string. - /// - /// In particular, this represents Prolog lists of characters. - String(String), - /// A Prolog list. - List(Vec), - /// A Prolog compound term. - Compound(String, Vec), - /// A Prolog variable. - Var(String), -} - -impl PrologTerm { - /// Creates an integer term. - pub fn integer(value: impl Into) -> Self { - PrologTerm::Integer(value.into()) - } - - /// Creates a rational term. - pub fn rational(value: impl Into) -> Self { - PrologTerm::Rational(value.into()) - } - - /// Creates a float term. - pub fn float(value: impl Into>) -> Self { - PrologTerm::Float(value.into()) - } - - /// Creates an atom term. - pub fn atom(value: impl Into) -> Self { - PrologTerm::Atom(value.into()) - } - - /// Creates a string term. - /// - /// In specific, this represents a list of chars in Prolog. - pub fn string(value: impl Into) -> Self { - PrologTerm::String(value.into()) - } - - /// Creates a list term. - pub fn list(value: impl IntoIterator) -> Self { - PrologTerm::List(value.into_iter().collect()) - } - - /// Creates a compound term. - pub fn compound( - functor: impl Into, - args: impl IntoIterator, - ) -> Self { - PrologTerm::Compound(functor.into(), args.into_iter().collect()) - } - - /// Creates a variable. - pub fn variable(value: impl Into) -> Self { - PrologTerm::Var(value.into()) - } - - /// Creates a conjunction, giving the atom `true` if empty. - pub fn conjunction(value: impl IntoIterator) -> Self { - PrologTerm::try_conjunction(value).unwrap_or(PrologTerm::atom("true")) - } - - /// Creates a conjunction, giving `None` if empty. - pub fn try_conjunction(value: impl IntoIterator) -> Option { - let mut iter = value.into_iter(); - iter.next().map(|first| { - PrologTerm::try_conjunction(iter) - .map(|rest| PrologTerm::compound(",", [first.clone(), rest])) - .unwrap_or(first) - }) - } - - /// Creates a disjunction, giving the atom `false` if empty. - pub fn disjunction(value: impl IntoIterator) -> Self { - PrologTerm::try_disjunction(value).unwrap_or(PrologTerm::atom("false")) - } - - /// Creates a disjunction, giving `None` if empty. - pub fn try_disjunction(value: impl IntoIterator) -> Option { - let mut iter = value.into_iter(); - iter.next().map(|first| { - PrologTerm::try_disjunction(iter) - .map(|rest| PrologTerm::compound(";", [first.clone(), rest])) - .unwrap_or(first) - }) - } -} - -impl From for PrologTerm { - fn from(value: LeafAnswer) -> Self { - match value { - LeafAnswer::True => PrologTerm::atom("true"), - LeafAnswer::False => PrologTerm::atom("false"), - LeafAnswer::Exception(inner) => match inner.clone() { - PrologTerm::Compound(functor, args) if functor == "error" && args.len() == 2 => { - inner - } - _ => PrologTerm::compound("throw", [inner]), - }, - LeafAnswer::LeafAnswer { - bindings: _, - residual_goals: _, - } => { - todo!() - } - } - } -} - -/// This is an auxiliary function to turn a count into names of anonymous variables like _A, _B, -/// _AB, etc... -fn count_to_letter_code(mut count: usize) -> String { - let mut letters = Vec::new(); - - loop { - let letter_idx = (count % 26) as u32; - letters.push(char::from_u32('A' as u32 + letter_idx).unwrap()); - count /= 26; - - if count == 0 { - break; - } - } - - letters.into_iter().chain("_".chars()).rev().collect() -} - -impl PrologTerm { - pub(crate) fn from_heapcell( - machine: &mut Machine, - heap_cell: HeapCellValue, - var_names: &mut IndexMap, - ) -> Self { - // Adapted from MachineState::read_term_from_heap - let mut term_stack = vec![]; - let iter = stackful_post_order_iter::( - &mut machine.machine_st.heap, - &mut machine.machine_st.stack, - heap_cell, - ); - - let mut anon_count: usize = 0; - let var_ptr_cmp = |a, b| match a { - Var::Named(name_a) => match b { - Var::Named(name_b) => name_a.cmp(&name_b), - _ => Ordering::Less, - }, - _ => match b { - Var::Named(_) => Ordering::Greater, - _ => Ordering::Equal, - }, - }; - - for addr in iter { - let addr = unmark_cell_bits!(addr); - - read_heap_cell!(addr, - (HeapCellValueTag::Lis) => { - let tail = term_stack.pop().unwrap(); - let head = term_stack.pop().unwrap(); - - let list = match tail { - PrologTerm::Atom(atom) if atom == "[]" => match head { - PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { - // Handle lists of char as strings - PrologTerm::String(a.to_string()) - } - _ => PrologTerm::List(vec![head]), - }, - PrologTerm::List(elems) if elems.is_empty() => match head { - PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { - // Handle lists of char as strings - PrologTerm::String(a.to_string()) - }, - _ => PrologTerm::List(vec![head]), - }, - PrologTerm::List(mut elems) => { - elems.insert(0, head); - PrologTerm::List(elems) - }, - PrologTerm::String(mut elems) => match head { - PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { - // Handle lists of char as strings - elems.insert(0, a.chars().next().unwrap()); - PrologTerm::String(elems) - }, - _ => { - let mut elems: Vec = elems - .chars() - .map(|x| PrologTerm::Atom(x.into())) - .collect(); - elems.insert(0, head); - PrologTerm::List(elems) - } - }, - _ => { - PrologTerm::Compound(".".into(), vec![head, tail]) - } - }; - term_stack.push(list); - } - (HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar) => { - let var = var_names.get(&addr).map(|x| x.borrow().clone()); - match var { - Some(Var::Named(name)) => term_stack.push(PrologTerm::Var(name)), - _ => { - let anon_name = loop { - // Generate a name for the anonymous variable - let anon_name = count_to_letter_code(anon_count); - - // Find if this name is already being used - var_names.sort_by(|_, a, _, b| { - var_ptr_cmp(a.borrow().clone(), b.borrow().clone()) - }); - let binary_result = var_names.binary_search_by(|_,a| { - let var_ptr = Var::Named(anon_name.clone()); - var_ptr_cmp(a.borrow().clone(), var_ptr.clone()) - }); - - match binary_result { - Ok(_) => anon_count += 1, // Name already used - Err(_) => { - // Name not used, assign it to this variable - let var_ptr = VarPtr::from(Var::Named(anon_name.clone())); - var_names.insert(addr, var_ptr); - break anon_name; - }, - } - }; - term_stack.push(PrologTerm::Var(anon_name)); - }, - } - } - (HeapCellValueTag::F64, f) => { - term_stack.push(PrologTerm::Float(*f)); - } - (HeapCellValueTag::Char, c) => { - term_stack.push(PrologTerm::Atom(c.into())); - } - (HeapCellValueTag::Fixnum, n) => { - term_stack.push(PrologTerm::Integer(n.into())); - } - (HeapCellValueTag::Cons) => { - match Number::try_from(addr) { - Ok(Number::Integer(i)) => term_stack.push(PrologTerm::Integer((*i).clone())), - Ok(Number::Rational(r)) => term_stack.push(PrologTerm::Rational((*r).clone())), - _ => {} - } - } - (HeapCellValueTag::CStr, s) => { - term_stack.push(PrologTerm::String(s.as_str().to_string())); - } - (HeapCellValueTag::Atom, (name, arity)) => { - //let h = iter.focus().value() as usize; - //let mut arity = arity; - - // Not sure why/if this is needed. - // Might find out with better testing later. - /* - if iter.heap.len() > h + arity + 1 { - let value = iter.heap[h + arity + 1]; - - if let Some(idx) = get_structure_index(value) { - // in the second condition, arity == 0, - // meaning idx cannot pertain to this atom - // if it is the direct subterm of a larger - // structure. - if arity > 0 || !iter.direct_subterm_of_str(h) { - term_stack.push( - Term::Literal(Cell::default(), Literal::CodeIndex(idx)) - ); - - arity += 1; - } - } - } - */ - - if arity == 0 { - let atom_name = name.as_str().to_string(); - if atom_name == "[]" { - term_stack.push(PrologTerm::List(vec![])); - } else { - term_stack.push(PrologTerm::Atom(atom_name)); - } - } else { - let subterms = term_stack - .drain(term_stack.len() - arity ..) - .collect(); - - term_stack.push(PrologTerm::Compound(name.as_str().to_string(), subterms)); - } - } - (HeapCellValueTag::PStr, atom) => { - let tail = term_stack.pop().unwrap(); - - match tail { - PrologTerm::Atom(atom) => { - if atom == "[]" { - term_stack.push(PrologTerm::String(atom.as_str().to_string())); - } - }, - PrologTerm::List(l) => { - let mut list: Vec = atom - .as_str() - .to_string() - .chars() - .map(|x| PrologTerm::Atom(x.to_string())) - .collect(); - list.extend(l.into_iter()); - term_stack.push(PrologTerm::List(list)); - }, - _ => { - let mut list: Vec = atom - .as_str() - .to_string() - .chars() - .map(|x| PrologTerm::Atom(x.to_string())) - .collect(); - - let mut partial_list = PrologTerm::Compound( - ".".into(), - vec![ - list.pop().unwrap(), - tail, - ], - ); - - while let Some(last) = list.pop() { - partial_list = PrologTerm::Compound( - ".".into(), - vec![ - last, - partial_list, - ], - ); - } - - term_stack.push(partial_list); - } - } - } - // I dont know if this is needed here. - /* - (HeapCellValueTag::PStrLoc, h) => { - let atom = cell_as_atom_cell!(iter.heap[h]).get_name(); - let tail = term_stack.pop().unwrap(); - - term_stack.push(Term::PartialString( - Cell::default(), - atom.as_str().to_owned(), - Box::new(tail), - )); - } - */ - _ => { - } - ); - } - - debug_assert_eq!(term_stack.len(), 1); - term_stack.pop().unwrap() - } -} From bb5adba4efa25226c6ba57dd1cae9a03e34eabfb Mon Sep 17 00:00:00 2001 From: bakaq Date: Sat, 12 Oct 2024 16:48:13 -0300 Subject: [PATCH 17/28] Rename PrologTerm to Term --- src/machine/lib_machine.rs | 155 ++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 80 deletions(-) diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index ba1be14cc..e05ed2a6c 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -27,17 +27,17 @@ pub enum LeafAnswer { /// This means that there are no more answers for the query. False, /// An exception leaf answer. - Exception(PrologTerm), + Exception(Term), /// A leaf answer with bindings and residual goals. LeafAnswer { /// The bindings of variables in the query. /// /// Can be empty. - bindings: BTreeMap, + bindings: BTreeMap, /// Residual goals. /// /// Can be empty. - residual_goals: Vec, + residual_goals: Vec, }, } @@ -61,7 +61,7 @@ impl LeafAnswer { /// Represents a Prolog term. #[non_exhaustive] #[derive(Debug, Clone, PartialEq)] -pub enum PrologTerm { +pub enum Term { /// An arbitrary precision integer. Integer(Integer), /// An arbitrary precision rational. @@ -75,100 +75,95 @@ pub enum PrologTerm { /// In particular, this represents Prolog lists of characters. String(String), /// A Prolog list. - List(Vec), + List(Vec), /// A Prolog compound term. - Compound(String, Vec), + Compound(String, Vec), /// A Prolog variable. Var(String), } -impl PrologTerm { +impl Term { /// Creates an integer term. pub fn integer(value: impl Into) -> Self { - PrologTerm::Integer(value.into()) + Term::Integer(value.into()) } /// Creates a rational term. pub fn rational(value: impl Into) -> Self { - PrologTerm::Rational(value.into()) + Term::Rational(value.into()) } /// Creates a float term. pub fn float(value: impl Into) -> Self { - PrologTerm::Float(value.into()) + Term::Float(value.into()) } /// Creates an atom term. pub fn atom(value: impl Into) -> Self { - PrologTerm::Atom(value.into()) + Term::Atom(value.into()) } /// Creates a string term. /// /// In specific, this represents a list of chars in Prolog. pub fn string(value: impl Into) -> Self { - PrologTerm::String(value.into()) + Term::String(value.into()) } /// Creates a list term. - pub fn list(value: impl IntoIterator) -> Self { - PrologTerm::List(value.into_iter().collect()) + pub fn list(value: impl IntoIterator) -> Self { + Term::List(value.into_iter().collect()) } /// Creates a compound term. - pub fn compound( - functor: impl Into, - args: impl IntoIterator, - ) -> Self { - PrologTerm::Compound(functor.into(), args.into_iter().collect()) + pub fn compound(functor: impl Into, args: impl IntoIterator) -> Self { + Term::Compound(functor.into(), args.into_iter().collect()) } /// Creates a variable. pub fn variable(value: impl Into) -> Self { - PrologTerm::Var(value.into()) + Term::Var(value.into()) } /// Creates a conjunction, giving the atom `true` if empty. - pub fn conjunction(value: impl IntoIterator) -> Self { - PrologTerm::try_conjunction(value).unwrap_or(PrologTerm::atom("true")) + pub fn conjunction(value: impl IntoIterator) -> Self { + Term::try_conjunction(value).unwrap_or(Term::atom("true")) } /// Creates a conjunction, giving `None` if empty. - pub fn try_conjunction(value: impl IntoIterator) -> Option { + pub fn try_conjunction(value: impl IntoIterator) -> Option { let mut iter = value.into_iter(); iter.next().map(|first| { - PrologTerm::try_conjunction(iter) - .map(|rest| PrologTerm::compound(",", [first.clone(), rest])) + Term::try_conjunction(iter) + .map(|rest| Term::compound(",", [first.clone(), rest])) .unwrap_or(first) }) } /// Creates a disjunction, giving the atom `false` if empty. - pub fn disjunction(value: impl IntoIterator) -> Self { - PrologTerm::try_disjunction(value).unwrap_or(PrologTerm::atom("false")) + pub fn disjunction(value: impl IntoIterator) -> Self { + Term::try_disjunction(value).unwrap_or(Term::atom("false")) } /// Creates a disjunction, giving `None` if empty. - pub fn try_disjunction(value: impl IntoIterator) -> Option { + pub fn try_disjunction(value: impl IntoIterator) -> Option { let mut iter = value.into_iter(); iter.next().map(|first| { - PrologTerm::try_disjunction(iter) - .map(|rest| PrologTerm::compound(";", [first.clone(), rest])) + Term::try_disjunction(iter) + .map(|rest| Term::compound(";", [first.clone(), rest])) .unwrap_or(first) }) } } -impl From for PrologTerm { +impl From for Term { fn from(value: LeafAnswer) -> Self { match value { - LeafAnswer::True => PrologTerm::atom("true"), - LeafAnswer::False => PrologTerm::atom("false"), + LeafAnswer::True => Term::atom("true"), + LeafAnswer::False => Term::atom("false"), LeafAnswer::Exception(inner) => match inner.clone() { - PrologTerm::Compound(functor, args) if functor == "error" && args.len() == 2 => { - inner - } - _ => PrologTerm::compound("throw", [inner]), + Term::Compound(functor, args) if functor == "error" && args.len() == 2 => inner, + _ => Term::compound("throw", [inner]), }, LeafAnswer::LeafAnswer { bindings: _, @@ -198,7 +193,7 @@ fn count_to_letter_code(mut count: usize) -> String { letters.into_iter().chain("_".chars()).rev().collect() } -impl PrologTerm { +impl Term { pub(crate) fn from_heapcell( machine: &mut Machine, heap_cell: HeapCellValue, @@ -233,41 +228,41 @@ impl PrologTerm { let head = term_stack.pop().unwrap(); let list = match tail { - PrologTerm::Atom(atom) if atom == "[]" => match head { - PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { + Term::Atom(atom) if atom == "[]" => match head { + Term::Atom(ref a) if a.chars().collect::>().len() == 1 => { // Handle lists of char as strings - PrologTerm::String(a.to_string()) + Term::String(a.to_string()) } - _ => PrologTerm::List(vec![head]), + _ => Term::List(vec![head]), }, - PrologTerm::List(elems) if elems.is_empty() => match head { - PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { + Term::List(elems) if elems.is_empty() => match head { + Term::Atom(ref a) if a.chars().collect::>().len() == 1 => { // Handle lists of char as strings - PrologTerm::String(a.to_string()) + Term::String(a.to_string()) }, - _ => PrologTerm::List(vec![head]), + _ => Term::List(vec![head]), }, - PrologTerm::List(mut elems) => { + Term::List(mut elems) => { elems.insert(0, head); - PrologTerm::List(elems) + Term::List(elems) }, - PrologTerm::String(mut elems) => match head { - PrologTerm::Atom(ref a) if a.chars().collect::>().len() == 1 => { + Term::String(mut elems) => match head { + Term::Atom(ref a) if a.chars().collect::>().len() == 1 => { // Handle lists of char as strings elems.insert(0, a.chars().next().unwrap()); - PrologTerm::String(elems) + Term::String(elems) }, _ => { - let mut elems: Vec = elems + let mut elems: Vec = elems .chars() - .map(|x| PrologTerm::Atom(x.into())) + .map(|x| Term::Atom(x.into())) .collect(); elems.insert(0, head); - PrologTerm::List(elems) + Term::List(elems) } }, _ => { - PrologTerm::Compound(".".into(), vec![head, tail]) + Term::Compound(".".into(), vec![head, tail]) } }; term_stack.push(list); @@ -275,7 +270,7 @@ impl PrologTerm { (HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar) => { let var = var_names.get(&addr).map(|x| x.borrow().clone()); match var { - Some(Var::Named(name)) => term_stack.push(PrologTerm::Var(name)), + Some(Var::Named(name)) => term_stack.push(Term::Var(name)), _ => { let anon_name = loop { // Generate a name for the anonymous variable @@ -300,28 +295,28 @@ impl PrologTerm { }, } }; - term_stack.push(PrologTerm::Var(anon_name)); + term_stack.push(Term::Var(anon_name)); }, } } (HeapCellValueTag::F64, f) => { - term_stack.push(PrologTerm::Float((*f).into())); + term_stack.push(Term::Float((*f).into())); } (HeapCellValueTag::Char, c) => { - term_stack.push(PrologTerm::Atom(c.into())); + term_stack.push(Term::Atom(c.into())); } (HeapCellValueTag::Fixnum, n) => { - term_stack.push(PrologTerm::Integer(n.into())); + term_stack.push(Term::Integer(n.into())); } (HeapCellValueTag::Cons) => { match Number::try_from(addr) { - Ok(Number::Integer(i)) => term_stack.push(PrologTerm::Integer((*i).clone())), - Ok(Number::Rational(r)) => term_stack.push(PrologTerm::Rational((*r).clone())), + Ok(Number::Integer(i)) => term_stack.push(Term::Integer((*i).clone())), + Ok(Number::Rational(r)) => term_stack.push(Term::Rational((*r).clone())), _ => {} } } (HeapCellValueTag::CStr, s) => { - term_stack.push(PrologTerm::String(s.as_str().to_string())); + term_stack.push(Term::String(s.as_str().to_string())); } (HeapCellValueTag::Atom, (name, arity)) => { //let h = iter.focus().value() as usize; @@ -352,46 +347,46 @@ impl PrologTerm { if arity == 0 { let atom_name = name.as_str().to_string(); if atom_name == "[]" { - term_stack.push(PrologTerm::List(vec![])); + term_stack.push(Term::List(vec![])); } else { - term_stack.push(PrologTerm::Atom(atom_name)); + term_stack.push(Term::Atom(atom_name)); } } else { let subterms = term_stack .drain(term_stack.len() - arity ..) .collect(); - term_stack.push(PrologTerm::Compound(name.as_str().to_string(), subterms)); + term_stack.push(Term::Compound(name.as_str().to_string(), subterms)); } } (HeapCellValueTag::PStr, atom) => { let tail = term_stack.pop().unwrap(); match tail { - PrologTerm::Atom(atom) => { + Term::Atom(atom) => { if atom == "[]" { - term_stack.push(PrologTerm::String(atom.as_str().to_string())); + term_stack.push(Term::String(atom.as_str().to_string())); } }, - PrologTerm::List(l) => { - let mut list: Vec = atom + Term::List(l) => { + let mut list: Vec = atom .as_str() .to_string() .chars() - .map(|x| PrologTerm::Atom(x.to_string())) + .map(|x| Term::Atom(x.to_string())) .collect(); list.extend(l.into_iter()); - term_stack.push(PrologTerm::List(list)); + term_stack.push(Term::List(list)); }, _ => { - let mut list: Vec = atom + let mut list: Vec = atom .as_str() .to_string() .chars() - .map(|x| PrologTerm::Atom(x.to_string())) + .map(|x| Term::Atom(x.to_string())) .collect(); - let mut partial_list = PrologTerm::Compound( + let mut partial_list = Term::Compound( ".".into(), vec![ list.pop().unwrap(), @@ -400,7 +395,7 @@ impl PrologTerm { ); while let Some(last) = list.pop() { - partial_list = PrologTerm::Compound( + partial_list = Term::Compound( ".".into(), vec![ last, @@ -511,7 +506,7 @@ impl Iterator for QueryState<'_> { return Some(Ok(LeafAnswer::False)); } - let mut bindings: BTreeMap = BTreeMap::new(); + let mut bindings: BTreeMap = BTreeMap::new(); let var_dict = &term_write_result.var_dict; @@ -528,9 +523,9 @@ impl Iterator for QueryState<'_> { } let mut term = - PrologTerm::from_heapcell(machine, *term_to_be_printed, &mut var_names.clone()); + Term::from_heapcell(machine, *term_to_be_printed, &mut var_names.clone()); - if let PrologTerm::Var(ref term_str) = term { + if let Term::Var(ref term_str) = term { if *term_str == var_name { continue; } @@ -544,7 +539,7 @@ impl Iterator for QueryState<'_> { var_dict.get_index_of(&VarKey::VarPtr(Var::Named(term_str.clone()).into())); if let Some(idx) = term_idx { if idx < var_name_idx { - let new_term = PrologTerm::Var(var_name); + let new_term = Term::Var(var_name); let new_var_name = term_str.into(); term = new_term; var_name = new_var_name; From ec6286ffab76eefffb098402ef21a0d9b30acd14 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sat, 12 Oct 2024 16:58:38 -0300 Subject: [PATCH 18/28] Shrink MVP API surface --- src/machine/lib_machine.rs | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine.rs index e05ed2a6c..720cba266 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine.rs @@ -41,23 +41,6 @@ pub enum LeafAnswer { }, } -impl LeafAnswer { - /// True if leaf answer failed. - /// - /// This gives [`false`] for exceptions. - pub fn failed(&self) -> bool { - matches!(self, LeafAnswer::False) - } - - /// True if leaf answer may have succeeded. - /// - /// When a leaf answer has residual goals the success is conditional on the satisfiability of - /// the contraints they represent. This gives [`false`] for exceptions. - pub fn maybe_succeeded(&self) -> bool { - matches!(self, LeafAnswer::True | LeafAnswer::LeafAnswer { .. }) - } -} - /// Represents a Prolog term. #[non_exhaustive] #[derive(Debug, Clone, PartialEq)] @@ -156,25 +139,6 @@ impl Term { } } -impl From for Term { - fn from(value: LeafAnswer) -> Self { - match value { - LeafAnswer::True => Term::atom("true"), - LeafAnswer::False => Term::atom("false"), - LeafAnswer::Exception(inner) => match inner.clone() { - Term::Compound(functor, args) if functor == "error" && args.len() == 2 => inner, - _ => Term::compound("throw", [inner]), - }, - LeafAnswer::LeafAnswer { - bindings: _, - residual_goals: _, - } => { - todo!() - } - } - } -} - /// This is an auxiliary function to turn a count into names of anonymous variables like _A, _B, /// _AB, etc... fn count_to_letter_code(mut count: usize) -> String { From 2f82c78bc0c18a5b2d56cd098819879545b27aaa Mon Sep 17 00:00:00 2001 From: bakaq Date: Sat, 12 Oct 2024 17:04:29 -0300 Subject: [PATCH 19/28] Separate lib_machine tests into separate file --- .../lib_integration_test_commands.txt | 0 src/machine/lib_machine/lib_machine_tests.rs | 583 +++++++++++++++++ .../{lib_machine.rs => lib_machine/mod.rs} | 589 +----------------- 3 files changed, 586 insertions(+), 586 deletions(-) rename src/machine/{ => lib_machine}/lib_integration_test_commands.txt (100%) create mode 100644 src/machine/lib_machine/lib_machine_tests.rs rename src/machine/{lib_machine.rs => lib_machine/mod.rs} (56%) diff --git a/src/machine/lib_integration_test_commands.txt b/src/machine/lib_machine/lib_integration_test_commands.txt similarity index 100% rename from src/machine/lib_integration_test_commands.txt rename to src/machine/lib_machine/lib_integration_test_commands.txt diff --git a/src/machine/lib_machine/lib_machine_tests.rs b/src/machine/lib_machine/lib_machine_tests.rs new file mode 100644 index 000000000..63ccba808 --- /dev/null +++ b/src/machine/lib_machine/lib_machine_tests.rs @@ -0,0 +1,583 @@ +use super::*; +use crate::machine::{QueryMatch, QueryResolution, Term}; + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +fn programatic_query() { + let mut machine = Machine::new_lib(); + + machine.load_module_string( + "facts", + String::from( + r#" + triple("a", "p1", "b"). + triple("a", "p2", "b"). + "#, + ), + ); + + let query = String::from(r#"triple("a",P,"b")."#); + let output = machine.run_query(query); + assert_eq!( + output, + Ok(QueryResolution::Matches(vec![ + QueryMatch::from(btreemap! { + "P" => Term::from("p1"), + }), + QueryMatch::from(btreemap! { + "P" => Term::from("p2"), + }), + ])) + ); + + assert_eq!( + machine.run_query(String::from(r#"triple("a","p1","b")."#)), + Ok(QueryResolution::True) + ); + + assert_eq!( + machine.run_query(String::from(r#"triple("x","y","z")."#)), + Ok(QueryResolution::False) + ); +} + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +fn failing_query() { + let mut machine = Machine::new_lib(); + let query = String::from(r#"triple("a",P,"b")."#); + let output = machine.run_query(query); + assert_eq!( + output, + Err(String::from( + "error existence_error procedure / triple 3 / triple 3" + )) + ); +} + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +fn complex_results() { + let mut machine = Machine::new_lib(); + machine.load_module_string( + "facts", + r#" + :- discontiguous(subject_class/2). + :- discontiguous(constructor/2). + + subject_class("Todo", c). + constructor(c, '[{action: "addLink", source: "this", predicate: "todo://state", target: "todo://ready"}]'). + + subject_class("Recipe", xyz). + constructor(xyz, '[{action: "addLink", source: "this", predicate: "recipe://title", target: "literal://string:Meta%20Muffins"}]'). + "#.to_string()); + + let result = machine.run_query(String::from( + "subject_class(\"Todo\", C), constructor(C, Actions).", + )); + assert_eq!( + result, + Ok(QueryResolution::Matches(vec![QueryMatch::from( + btreemap! { + "C" => Term::Atom("c".into()), + "Actions" => Term::Atom("[{action: \"addLink\", source: \"this\", predicate: \"todo://state\", target: \"todo://ready\"}]".into()), + } + ),])) + ); + + let result = machine.run_query(String::from( + "subject_class(\"Recipe\", C), constructor(C, Actions).", + )); + assert_eq!( + result, + Ok(QueryResolution::Matches(vec![QueryMatch::from( + btreemap! { + "C" => Term::Atom("xyz".into()), + "Actions" => Term::Atom("[{action: \"addLink\", source: \"this\", predicate: \"recipe://title\", target: \"literal://string:Meta%20Muffins\"}]".into()), + } + ),])) + ); + + let result = machine.run_query(String::from("subject_class(Class, _).")); + assert_eq!( + result, + Ok(QueryResolution::Matches(vec![ + QueryMatch::from(btreemap! { + "Class" => Term::String("Todo".into()) + }), + QueryMatch::from(btreemap! { + "Class" => Term::String("Recipe".into()) + }), + ])) + ); +} + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +fn empty_predicate() { + let mut machine = Machine::new_lib(); + machine.load_module_string( + "facts", + r#" + :- discontiguous(subject_class/2). + "# + .to_string(), + ); + + let result = machine.run_query(String::from("subject_class(X, _).")); + assert_eq!(result, Ok(QueryResolution::False)); +} + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +fn list_results() { + let mut machine = Machine::new_lib(); + machine.load_module_string( + "facts", + r#" + list([1,2,3]). + "# + .to_string(), + ); + + let result = machine.run_query(String::from("list(X).")); + assert_eq!( + result, + Ok(QueryResolution::Matches(vec![QueryMatch::from( + btreemap! { + "X" => Term::List(vec![ + Term::Integer(1.into()), + Term::Integer(2.into()), + Term::Integer(3.into()), + ]), + } + ),])) + ); +} + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +fn consult() { + let mut machine = Machine::new_lib(); + + machine.consult_module_string( + "facts", + String::from( + r#" + triple("a", "p1", "b"). + triple("a", "p2", "b"). + "#, + ), + ); + + let query = String::from(r#"triple("a",P,"b")."#); + let output = machine.run_query(query); + assert_eq!( + output, + Ok(QueryResolution::Matches(vec![ + QueryMatch::from(btreemap! { + "P" => Term::from("p1"), + }), + QueryMatch::from(btreemap! { + "P" => Term::from("p2"), + }), + ])) + ); + + assert_eq!( + machine.run_query(String::from(r#"triple("a","p1","b")."#)), + Ok(QueryResolution::True) + ); + + assert_eq!( + machine.run_query(String::from(r#"triple("x","y","z")."#)), + Ok(QueryResolution::False) + ); + + machine.consult_module_string( + "facts", + String::from( + r#" + triple("a", "new", "b"). + "#, + ), + ); + + assert_eq!( + machine.run_query(String::from(r#"triple("a","p1","b")."#)), + Ok(QueryResolution::False) + ); + + assert_eq!( + machine.run_query(String::from(r#"triple("a","new","b")."#)), + Ok(QueryResolution::True) + ); +} + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +#[ignore = "uses old flawed interface"] +fn integration_test() { + let mut machine = Machine::new_lib(); + + // File with test commands, i.e. program code to consult and queries to run + let code = include_str!("./lib_integration_test_commands.txt"); + + // Split the code into blocks + let blocks = code.split("====="); + + let mut i = 0; + let mut last_result: Option<_> = None; + // Iterate over the blocks + for block in blocks { + // Trim the block to remove any leading or trailing whitespace + let block = block.trim(); + + // Skip empty blocks + if block.is_empty() { + continue; + } + + // Check if the block is a query + if let Some(query) = block.strip_prefix("query") { + // Parse and execute the query + let result = machine.run_query(query.to_string()); + assert!(result.is_ok()); + + last_result = Some(result); + } else if let Some(code) = block.strip_prefix("consult") { + // Load the code into the machine + machine.consult_module_string("facts", code.to_string()); + } else if let Some(result) = block.strip_prefix("result") { + i += 1; + if let Some(Ok(ref last_result)) = last_result { + println!("\n\n=====Result No. {i}=======\n{last_result}\n==============="); + assert_eq!(last_result.to_string(), result.to_string().trim(),) + } + } + } +} + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +fn findall() { + let mut machine = Machine::new_lib(); + + machine.consult_module_string( + "facts", + String::from( + r#" + triple("a", "p1", "b"). + triple("a", "p2", "b"). + "#, + ), + ); + + let query = + String::from(r#"findall([Predicate, Target], triple(_,Predicate,Target), Result)."#); + let output = machine.run_query(query); + assert_eq!( + output, + Ok(QueryResolution::Matches(vec![QueryMatch::from( + btreemap! { + "Result" => Term::List( + Vec::from([ + Term::List([Term::from("p1"), Term::from("b")].into()), + Term::List([Term::from("p2"), Term::from("b")].into()), + ]) + ), + } + ),])) + ); +} + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +fn dont_return_partial_matches() { + let mut machine = Machine::new_lib(); + + machine.consult_module_string( + "facts", + String::from( + r#" + :- discontiguous(property_resolve/2). + subject_class("Todo", c). + "#, + ), + ); + + let query = String::from(r#"property_resolve(C, "isLiked"), subject_class("Todo", C)."#); + let output = machine.run_query(query); + assert_eq!(output, Ok(QueryResolution::False)); + + let query = String::from(r#"subject_class("Todo", C), property_resolve(C, "isLiked")."#); + let output = machine.run_query(query); + assert_eq!(output, Ok(QueryResolution::False)); +} + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +fn dont_return_partial_matches_without_discountiguous() { + let mut machine = Machine::new_lib(); + + machine.consult_module_string( + "facts", + String::from( + r#" + a("true for a"). + b("true for b"). + "#, + ), + ); + + let query = String::from(r#"a("true for a")."#); + let output = machine.run_query(query); + assert_eq!(output, Ok(QueryResolution::True)); + + let query = String::from(r#"a("true for a"), b("true for b")."#); + let output = machine.run_query(query); + assert_eq!(output, Ok(QueryResolution::True)); + + let query = String::from(r#"a("true for b"), b("true for b")."#); + let output = machine.run_query(query); + assert_eq!(output, Ok(QueryResolution::False)); + + let query = String::from(r#"a("true for a"), b("true for a")."#); + let output = machine.run_query(query); + assert_eq!(output, Ok(QueryResolution::False)); +} + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +fn non_existent_predicate_should_not_cause_panic_when_other_predicates_are_defined() { + let mut machine = Machine::new_lib(); + + machine.consult_module_string( + "facts", + String::from( + r#" + triple("a", "p1", "b"). + triple("a", "p2", "b"). + "#, + ), + ); + + let query = String::from("non_existent_predicate(\"a\",\"p1\",\"b\")."); + + let result = machine.run_query(query); + + assert_eq!( + result, + Err(String::from("error existence_error procedure / non_existent_predicate 3 / non_existent_predicate 3")) + ); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn atom_quoting() { + let mut machine = Machine::new_lib(); + + let query = "X = '.'.".into(); + + let result = machine.run_query(query); + + assert_eq!( + result, + Ok(QueryResolution::Matches(vec![QueryMatch::from( + btreemap! { + "X" => Term::Atom(".".into()), + } + )])) + ); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn rational_number() { + use crate::parser::dashu::rational::RBig; + let mut machine = Machine::new_lib(); + + let query = "X is 1 rdiv 2.".into(); + + let result = machine.run_query(query); + + assert_eq!( + result, + Ok(QueryResolution::Matches(vec![QueryMatch::from( + btreemap! { + "X" => Term::Rational(RBig::from_parts(1.into(), 2u32.into())), + } + )])) + ); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn big_integer() { + use crate::parser::dashu::integer::IBig; + let mut machine = Machine::new_lib(); + + let query = "X is 10^100.".into(); + + let result = machine.run_query(query); + + assert_eq!( + result, + Ok(QueryResolution::Matches(vec![QueryMatch::from( + btreemap! { + "X" => Term::Integer(IBig::from(10).pow(100)), + } + )])) + ); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn complicated_term() { + let mut machine = Machine::new_lib(); + + let query = "X = a(\"asdf\", [42, 2.54, asdf, a, [a,b|_], Z]).".into(); + + let result = machine.run_query(query); + + let expected = Term::Structure( + // Composite term + "a".into(), + vec![ + Term::String("asdf".into()), // String + Term::List(vec![ + Term::Integer(42.into()), // Fixnum + Term::Float(2.54.into()), // Float + Term::Atom("asdf".into()), // Atom + Term::Atom("a".into()), // Char + Term::Structure( + // Partial string + ".".into(), + vec![ + Term::Atom("a".into()), + Term::Structure( + ".".into(), + vec![ + Term::Atom("b".into()), + Term::Var("_A".into()), // Anonymous variable + ], + ), + ], + ), + Term::Var("Z".into()), // Named variable + ]), + ], + ); + + assert_eq!( + result, + Ok(QueryResolution::Matches(vec![QueryMatch::from( + btreemap! { + "X" => expected, + } + )])) + ); +} + +#[test] +#[cfg_attr(miri, ignore = "it takes too long to run")] +fn issue_2341() { + let mut machine = Machine::new_lib(); + + machine.load_module_string( + "facts", + String::from( + r#" + male(stephen). + parent(albert,edward). + father(F,C):-parent(F,C),male(F). + "#, + ), + ); + + let query = String::from(r#"father(F,C)."#); + let output = machine.run_query(query); + + 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(LeafAnswer::False))); + assert_eq!(iterator.next(), None); + } + + { + let mut iterator = machine.run_query_iter("false.".into()); + + assert_eq!(iterator.next(), Some(Ok(LeafAnswer::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(LeafAnswer::True))); + assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False))); + assert_eq!(iterator.next(), None); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn differentiate_anonymous_variables() { + let mut machine = Machine::new_lib(); + + let result = machine.run_query("A = [_,_], _B = 1 ; B = [_,_].".into()); + + assert_eq!( + result, + Ok(QueryResolution::Matches(vec![ + QueryMatch::from(btreemap! { + "A" => Term::List(vec![Term::Var("_A".into()), Term::Var("_C".into())]), + "_B" => Term::Integer(1.into()), + }), + QueryMatch::from(btreemap! { + "B" => Term::List(vec![Term::Var("_A".into()), Term::Var("_C".into())]), + }), + ])) + ); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn order_of_variables_in_binding() { + let mut machine = Machine::new_lib(); + + let result = machine.run_query("X = Y, Z = W.".into()); + + assert_eq!( + result, + Ok(QueryResolution::Matches(vec![QueryMatch::from( + btreemap! { + "X" => Term::Var("Y".into()), + "Z" => Term::Var("W".into()), + } + ),])) + ); +} + diff --git a/src/machine/lib_machine.rs b/src/machine/lib_machine/mod.rs similarity index 56% rename from src/machine/lib_machine.rs rename to src/machine/lib_machine/mod.rs index 720cba266..4aa367a8c 100644 --- a/src/machine/lib_machine.rs +++ b/src/machine/lib_machine/mod.rs @@ -17,6 +17,9 @@ use indexmap::IndexMap; use super::{streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine}; +#[cfg(test)] +mod lib_machine_tests; + /// Represents a leaf answer from a query. #[derive(Debug, Clone, PartialEq)] pub enum LeafAnswer { @@ -624,589 +627,3 @@ impl Machine { } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::machine::{QueryMatch, QueryResolution, Term}; - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - fn programatic_query() { - let mut machine = Machine::new_lib(); - - machine.load_module_string( - "facts", - String::from( - r#" - triple("a", "p1", "b"). - triple("a", "p2", "b"). - "#, - ), - ); - - let query = String::from(r#"triple("a",P,"b")."#); - let output = machine.run_query(query); - assert_eq!( - output, - Ok(QueryResolution::Matches(vec![ - QueryMatch::from(btreemap! { - "P" => Term::from("p1"), - }), - QueryMatch::from(btreemap! { - "P" => Term::from("p2"), - }), - ])) - ); - - assert_eq!( - machine.run_query(String::from(r#"triple("a","p1","b")."#)), - Ok(QueryResolution::True) - ); - - assert_eq!( - machine.run_query(String::from(r#"triple("x","y","z")."#)), - Ok(QueryResolution::False) - ); - } - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - fn failing_query() { - let mut machine = Machine::new_lib(); - let query = String::from(r#"triple("a",P,"b")."#); - let output = machine.run_query(query); - assert_eq!( - output, - Err(String::from( - "error existence_error procedure / triple 3 / triple 3" - )) - ); - } - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - fn complex_results() { - let mut machine = Machine::new_lib(); - machine.load_module_string( - "facts", - r#" - :- discontiguous(subject_class/2). - :- discontiguous(constructor/2). - - subject_class("Todo", c). - constructor(c, '[{action: "addLink", source: "this", predicate: "todo://state", target: "todo://ready"}]'). - - subject_class("Recipe", xyz). - constructor(xyz, '[{action: "addLink", source: "this", predicate: "recipe://title", target: "literal://string:Meta%20Muffins"}]'). - "#.to_string()); - - let result = machine.run_query(String::from( - "subject_class(\"Todo\", C), constructor(C, Actions).", - )); - assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "C" => Term::Atom("c".into()), - "Actions" => Term::Atom("[{action: \"addLink\", source: \"this\", predicate: \"todo://state\", target: \"todo://ready\"}]".into()), - } - ),])) - ); - - let result = machine.run_query(String::from( - "subject_class(\"Recipe\", C), constructor(C, Actions).", - )); - assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "C" => Term::Atom("xyz".into()), - "Actions" => Term::Atom("[{action: \"addLink\", source: \"this\", predicate: \"recipe://title\", target: \"literal://string:Meta%20Muffins\"}]".into()), - } - ),])) - ); - - let result = machine.run_query(String::from("subject_class(Class, _).")); - assert_eq!( - result, - Ok(QueryResolution::Matches(vec![ - QueryMatch::from(btreemap! { - "Class" => Term::String("Todo".into()) - }), - QueryMatch::from(btreemap! { - "Class" => Term::String("Recipe".into()) - }), - ])) - ); - } - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - fn empty_predicate() { - let mut machine = Machine::new_lib(); - machine.load_module_string( - "facts", - r#" - :- discontiguous(subject_class/2). - "# - .to_string(), - ); - - let result = machine.run_query(String::from("subject_class(X, _).")); - assert_eq!(result, Ok(QueryResolution::False)); - } - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - fn list_results() { - let mut machine = Machine::new_lib(); - machine.load_module_string( - "facts", - r#" - list([1,2,3]). - "# - .to_string(), - ); - - let result = machine.run_query(String::from("list(X).")); - assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => Term::List(vec![ - Term::Integer(1.into()), - Term::Integer(2.into()), - Term::Integer(3.into()), - ]), - } - ),])) - ); - } - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - fn consult() { - let mut machine = Machine::new_lib(); - - machine.consult_module_string( - "facts", - String::from( - r#" - triple("a", "p1", "b"). - triple("a", "p2", "b"). - "#, - ), - ); - - let query = String::from(r#"triple("a",P,"b")."#); - let output = machine.run_query(query); - assert_eq!( - output, - Ok(QueryResolution::Matches(vec![ - QueryMatch::from(btreemap! { - "P" => Term::from("p1"), - }), - QueryMatch::from(btreemap! { - "P" => Term::from("p2"), - }), - ])) - ); - - assert_eq!( - machine.run_query(String::from(r#"triple("a","p1","b")."#)), - Ok(QueryResolution::True) - ); - - assert_eq!( - machine.run_query(String::from(r#"triple("x","y","z")."#)), - Ok(QueryResolution::False) - ); - - machine.consult_module_string( - "facts", - String::from( - r#" - triple("a", "new", "b"). - "#, - ), - ); - - assert_eq!( - machine.run_query(String::from(r#"triple("a","p1","b")."#)), - Ok(QueryResolution::False) - ); - - assert_eq!( - machine.run_query(String::from(r#"triple("a","new","b")."#)), - Ok(QueryResolution::True) - ); - } - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - #[ignore = "uses old flawed interface"] - fn integration_test() { - let mut machine = Machine::new_lib(); - - // File with test commands, i.e. program code to consult and queries to run - let code = include_str!("./lib_integration_test_commands.txt"); - - // Split the code into blocks - let blocks = code.split("====="); - - let mut i = 0; - let mut last_result: Option<_> = None; - // Iterate over the blocks - for block in blocks { - // Trim the block to remove any leading or trailing whitespace - let block = block.trim(); - - // Skip empty blocks - if block.is_empty() { - continue; - } - - // Check if the block is a query - if let Some(query) = block.strip_prefix("query") { - // Parse and execute the query - let result = machine.run_query(query.to_string()); - assert!(result.is_ok()); - - last_result = Some(result); - } else if let Some(code) = block.strip_prefix("consult") { - // Load the code into the machine - machine.consult_module_string("facts", code.to_string()); - } else if let Some(result) = block.strip_prefix("result") { - i += 1; - if let Some(Ok(ref last_result)) = last_result { - println!("\n\n=====Result No. {i}=======\n{last_result}\n==============="); - assert_eq!(last_result.to_string(), result.to_string().trim(),) - } - } - } - } - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - fn findall() { - let mut machine = Machine::new_lib(); - - machine.consult_module_string( - "facts", - String::from( - r#" - triple("a", "p1", "b"). - triple("a", "p2", "b"). - "#, - ), - ); - - let query = - String::from(r#"findall([Predicate, Target], triple(_,Predicate,Target), Result)."#); - let output = machine.run_query(query); - assert_eq!( - output, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "Result" => Term::List( - Vec::from([ - Term::List([Term::from("p1"), Term::from("b")].into()), - Term::List([Term::from("p2"), Term::from("b")].into()), - ]) - ), - } - ),])) - ); - } - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - fn dont_return_partial_matches() { - let mut machine = Machine::new_lib(); - - machine.consult_module_string( - "facts", - String::from( - r#" - :- discontiguous(property_resolve/2). - subject_class("Todo", c). - "#, - ), - ); - - let query = String::from(r#"property_resolve(C, "isLiked"), subject_class("Todo", C)."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::False)); - - let query = String::from(r#"subject_class("Todo", C), property_resolve(C, "isLiked")."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::False)); - } - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - fn dont_return_partial_matches_without_discountiguous() { - let mut machine = Machine::new_lib(); - - machine.consult_module_string( - "facts", - String::from( - r#" - a("true for a"). - b("true for b"). - "#, - ), - ); - - let query = String::from(r#"a("true for a")."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::True)); - - let query = String::from(r#"a("true for a"), b("true for b")."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::True)); - - let query = String::from(r#"a("true for b"), b("true for b")."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::False)); - - let query = String::from(r#"a("true for a"), b("true for a")."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::False)); - } - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - fn non_existent_predicate_should_not_cause_panic_when_other_predicates_are_defined() { - let mut machine = Machine::new_lib(); - - machine.consult_module_string( - "facts", - String::from( - r#" - triple("a", "p1", "b"). - triple("a", "p2", "b"). - "#, - ), - ); - - let query = String::from("non_existent_predicate(\"a\",\"p1\",\"b\")."); - - let result = machine.run_query(query); - - assert_eq!( - result, - Err(String::from("error existence_error procedure / non_existent_predicate 3 / non_existent_predicate 3")) - ); - } - - #[test] - #[cfg_attr(miri, ignore)] - fn atom_quoting() { - let mut machine = Machine::new_lib(); - - let query = "X = '.'.".into(); - - let result = machine.run_query(query); - - assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => Term::Atom(".".into()), - } - )])) - ); - } - - #[test] - #[cfg_attr(miri, ignore)] - fn rational_number() { - use crate::parser::dashu::rational::RBig; - let mut machine = Machine::new_lib(); - - let query = "X is 1 rdiv 2.".into(); - - let result = machine.run_query(query); - - assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => Term::Rational(RBig::from_parts(1.into(), 2u32.into())), - } - )])) - ); - } - - #[test] - #[cfg_attr(miri, ignore)] - fn big_integer() { - use crate::parser::dashu::integer::IBig; - let mut machine = Machine::new_lib(); - - let query = "X is 10^100.".into(); - - let result = machine.run_query(query); - - assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => Term::Integer(IBig::from(10).pow(100)), - } - )])) - ); - } - - #[test] - #[cfg_attr(miri, ignore)] - fn complicated_term() { - let mut machine = Machine::new_lib(); - - let query = "X = a(\"asdf\", [42, 2.54, asdf, a, [a,b|_], Z]).".into(); - - let result = machine.run_query(query); - - let expected = Term::Structure( - // Composite term - "a".into(), - vec![ - Term::String("asdf".into()), // String - Term::List(vec![ - Term::Integer(42.into()), // Fixnum - Term::Float(2.54.into()), // Float - Term::Atom("asdf".into()), // Atom - Term::Atom("a".into()), // Char - Term::Structure( - // Partial string - ".".into(), - vec![ - Term::Atom("a".into()), - Term::Structure( - ".".into(), - vec![ - Term::Atom("b".into()), - Term::Var("_A".into()), // Anonymous variable - ], - ), - ], - ), - Term::Var("Z".into()), // Named variable - ]), - ], - ); - - assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => expected, - } - )])) - ); - } - - #[test] - #[cfg_attr(miri, ignore = "it takes too long to run")] - fn issue_2341() { - let mut machine = Machine::new_lib(); - - machine.load_module_string( - "facts", - String::from( - r#" - male(stephen). - parent(albert,edward). - father(F,C):-parent(F,C),male(F). - "#, - ), - ); - - let query = String::from(r#"father(F,C)."#); - let output = machine.run_query(query); - - 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(LeafAnswer::False))); - assert_eq!(iterator.next(), None); - } - - { - let mut iterator = machine.run_query_iter("false.".into()); - - assert_eq!(iterator.next(), Some(Ok(LeafAnswer::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(LeafAnswer::True))); - assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False))); - assert_eq!(iterator.next(), None); - } - - #[test] - #[cfg_attr(miri, ignore)] - fn differentiate_anonymous_variables() { - let mut machine = Machine::new_lib(); - - let result = machine.run_query("A = [_,_], _B = 1 ; B = [_,_].".into()); - - assert_eq!( - result, - Ok(QueryResolution::Matches(vec![ - QueryMatch::from(btreemap! { - "A" => Term::List(vec![Term::Var("_A".into()), Term::Var("_C".into())]), - "_B" => Term::Integer(1.into()), - }), - QueryMatch::from(btreemap! { - "B" => Term::List(vec![Term::Var("_A".into()), Term::Var("_C".into())]), - }), - ])) - ); - } - - #[test] - #[cfg_attr(miri, ignore)] - fn order_of_variables_in_binding() { - let mut machine = Machine::new_lib(); - - let result = machine.run_query("X = Y, Z = W.".into()); - - assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => Term::Var("Y".into()), - "Z" => Term::Var("W".into()), - } - ),])) - ); - } -} From e21c7721814882bada567276f00ceb5a927e1b77 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sat, 12 Oct 2024 17:41:48 -0300 Subject: [PATCH 20/28] Migrate tests to new API --- src/machine/lib_machine/lib_machine_tests.rs | 503 +++++++++---------- src/machine/lib_machine/mod.rs | 10 + tests/scryer/helper.rs | 4 +- 3 files changed, 249 insertions(+), 268 deletions(-) diff --git a/src/machine/lib_machine/lib_machine_tests.rs b/src/machine/lib_machine/lib_machine_tests.rs index 63ccba808..e865f9e23 100644 --- a/src/machine/lib_machine/lib_machine_tests.rs +++ b/src/machine/lib_machine/lib_machine_tests.rs @@ -1,10 +1,10 @@ use super::*; -use crate::machine::{QueryMatch, QueryResolution, Term}; +use crate::MachineBuilder; #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] fn programatic_query() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); machine.load_module_string( "facts", @@ -16,39 +16,36 @@ fn programatic_query() { ), ); - let query = String::from(r#"triple("a",P,"b")."#); - let output = machine.run_query(query); - assert_eq!( - output, - Ok(QueryResolution::Matches(vec![ - QueryMatch::from(btreemap! { - "P" => Term::from("p1"), - }), - QueryMatch::from(btreemap! { - "P" => Term::from("p2"), - }), - ])) - ); + let query = r#"triple("a",P,"b")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); assert_eq!( - machine.run_query(String::from(r#"triple("a","p1","b")."#)), - Ok(QueryResolution::True) + complete_answer, + [ + LeafAnswer::from_bindings([("P", Term::string("p1")),]), + LeafAnswer::from_bindings([("P", Term::string("p2")),]), + ], ); - assert_eq!( - machine.run_query(String::from(r#"triple("x","y","z")."#)), - Ok(QueryResolution::False) - ); + let query = r#"triple("a","p1","b")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + + assert_eq!(complete_answer, [LeafAnswer::True],); + + let query = r#"triple("x","y","z")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + + assert_eq!(complete_answer, [LeafAnswer::False],); } #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] fn failing_query() { - let mut machine = Machine::new_lib(); - let query = String::from(r#"triple("a",P,"b")."#); - let output = machine.run_query(query); + let mut machine = MachineBuilder::default().build(); + let query = r#"triple("a",P,"b")."#; + let complete_answer: Result, _> = machine.run_query(query).collect(); assert_eq!( - output, + complete_answer, Err(String::from( "error existence_error procedure / triple 3 / triple 3" )) @@ -58,7 +55,7 @@ fn failing_query() { #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] fn complex_results() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); machine.load_module_string( "facts", r#" @@ -70,155 +67,151 @@ fn complex_results() { subject_class("Recipe", xyz). constructor(xyz, '[{action: "addLink", source: "this", predicate: "recipe://title", target: "literal://string:Meta%20Muffins"}]'). - "#.to_string()); - - let result = machine.run_query(String::from( - "subject_class(\"Todo\", C), constructor(C, Actions).", - )); - assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "C" => Term::Atom("c".into()), - "Actions" => Term::Atom("[{action: \"addLink\", source: \"this\", predicate: \"todo://state\", target: \"todo://ready\"}]".into()), - } - ),])) + "#, ); - let result = machine.run_query(String::from( - "subject_class(\"Recipe\", C), constructor(C, Actions).", - )); + let complete_answer: Vec<_> = machine + .run_query(r#"subject_class("Todo", C), constructor(C, Actions)."#) + .collect::>() + .unwrap(); assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "C" => Term::Atom("xyz".into()), - "Actions" => Term::Atom("[{action: \"addLink\", source: \"this\", predicate: \"recipe://title\", target: \"literal://string:Meta%20Muffins\"}]".into()), - } - ),])) - ); - - let result = machine.run_query(String::from("subject_class(Class, _).")); + complete_answer, + [LeafAnswer::from_bindings([ + ("C", Term::atom("c")), + ( + "Actions", + Term::atom( + r#"[{action: "addLink", source: "this", predicate: "todo://state", target: "todo://ready"}]"# + ) + ), + ])], + ); + + let complete_answer: Vec<_> = machine + .run_query(r#"subject_class("Recipe", C), constructor(C, Actions)."#) + .collect::>() + .unwrap(); assert_eq!( - result, - Ok(QueryResolution::Matches(vec![ - QueryMatch::from(btreemap! { - "Class" => Term::String("Todo".into()) - }), - QueryMatch::from(btreemap! { - "Class" => Term::String("Recipe".into()) - }), - ])) + complete_answer, + [LeafAnswer::from_bindings([ + ("C", Term::atom("xyz")), + ( + "Actions", + Term::atom( + r#"[{action: "addLink", source: "this", predicate: "recipe://title", target: "literal://string:Meta%20Muffins"}]"# + ) + ), + ])], + ); + + let complete_answer: Vec<_> = machine + .run_query("subject_class(Class, _).") + .collect::>() + .unwrap(); + assert_eq!( + complete_answer, + [ + LeafAnswer::from_bindings([("Class", Term::string("Todo"))]), + LeafAnswer::from_bindings([("Class", Term::string("Recipe"))]), + ], ); } #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] fn empty_predicate() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); machine.load_module_string( "facts", r#" :- discontiguous(subject_class/2). - "# - .to_string(), + "#, ); - let result = machine.run_query(String::from("subject_class(X, _).")); - assert_eq!(result, Ok(QueryResolution::False)); + let complete_answer: Vec<_> = machine + .run_query("subject_class(X, _).") + .collect::>() + .unwrap(); + assert_eq!(complete_answer, [LeafAnswer::False]); } #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] fn list_results() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); machine.load_module_string( "facts", r#" list([1,2,3]). - "# - .to_string(), + "#, ); - let result = machine.run_query(String::from("list(X).")); + let complete_answer: Vec<_> = machine + .run_query("list(X).") + .collect::>() + .unwrap(); assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => Term::List(vec![ - Term::Integer(1.into()), - Term::Integer(2.into()), - Term::Integer(3.into()), - ]), - } - ),])) + complete_answer, + [LeafAnswer::from_bindings([( + "X", + Term::list([Term::integer(1), Term::integer(2), Term::integer(3)]), + )])], ); } #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] fn consult() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); machine.consult_module_string( "facts", - String::from( - r#" - triple("a", "p1", "b"). - triple("a", "p2", "b"). - "#, - ), + r#" + triple("a", "p1", "b"). + triple("a", "p2", "b"). + "#, ); - let query = String::from(r#"triple("a",P,"b")."#); - let output = machine.run_query(query); + let query = r#"triple("a",P,"b")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); assert_eq!( - output, - Ok(QueryResolution::Matches(vec![ - QueryMatch::from(btreemap! { - "P" => Term::from("p1"), - }), - QueryMatch::from(btreemap! { - "P" => Term::from("p2"), - }), - ])) + complete_answer, + [ + LeafAnswer::from_bindings([("P", Term::string("p1"))]), + LeafAnswer::from_bindings([("P", Term::string("p2"))]), + ], ); - assert_eq!( - machine.run_query(String::from(r#"triple("a","p1","b")."#)), - Ok(QueryResolution::True) - ); + let query = r#"triple("a","p1","b")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + assert_eq!(complete_answer, [LeafAnswer::True],); - assert_eq!( - machine.run_query(String::from(r#"triple("x","y","z")."#)), - Ok(QueryResolution::False) - ); + let query = r#"triple("x","y","z")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + assert_eq!(complete_answer, [LeafAnswer::False],); machine.consult_module_string( "facts", - String::from( - r#" + r#" triple("a", "new", "b"). "#, - ), ); - assert_eq!( - machine.run_query(String::from(r#"triple("a","p1","b")."#)), - Ok(QueryResolution::False) - ); + let query = r#"triple("a","p1","b")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + assert_eq!(complete_answer, [LeafAnswer::False],); - assert_eq!( - machine.run_query(String::from(r#"triple("a","new","b")."#)), - Ok(QueryResolution::True) - ); + let query = r#"triple("a","new","b")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + assert_eq!(complete_answer, [LeafAnswer::True]); } +/* #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] #[ignore = "uses old flawed interface"] fn integration_test() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); // File with test commands, i.e. program code to consult and queries to run let code = include_str!("./lib_integration_test_commands.txt"); @@ -257,137 +250,122 @@ fn integration_test() { } } } +*/ #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] fn findall() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); machine.consult_module_string( "facts", - String::from( - r#" - triple("a", "p1", "b"). - triple("a", "p2", "b"). - "#, - ), + r#" + triple("a", "p1", "b"). + triple("a", "p2", "b"). + "#, ); - let query = - String::from(r#"findall([Predicate, Target], triple(_,Predicate,Target), Result)."#); - let output = machine.run_query(query); + let query = r#"findall([Predicate, Target], triple(_,Predicate,Target), Result)."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); assert_eq!( - output, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "Result" => Term::List( - Vec::from([ - Term::List([Term::from("p1"), Term::from("b")].into()), - Term::List([Term::from("p2"), Term::from("b")].into()), - ]) - ), - } - ),])) + complete_answer, + [LeafAnswer::from_bindings([( + "Result", + Term::list([ + Term::list([Term::string("p1"), Term::string("b")]), + Term::list([Term::string("p2"), Term::string("b")]), + ]) + )])] ); } #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] fn dont_return_partial_matches() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); machine.consult_module_string( "facts", - String::from( - r#" - :- discontiguous(property_resolve/2). - subject_class("Todo", c). - "#, - ), + r#" + :- discontiguous(property_resolve/2). + subject_class("Todo", c). + "#, ); - let query = String::from(r#"property_resolve(C, "isLiked"), subject_class("Todo", C)."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::False)); + let query = r#"property_resolve(C, "isLiked"), subject_class("Todo", C)."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + assert_eq!(complete_answer, [LeafAnswer::False]); - let query = String::from(r#"subject_class("Todo", C), property_resolve(C, "isLiked")."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::False)); + let query = r#"subject_class("Todo", C), property_resolve(C, "isLiked")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + assert_eq!(complete_answer, [LeafAnswer::False]); } #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] fn dont_return_partial_matches_without_discountiguous() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); machine.consult_module_string( "facts", - String::from( - r#" - a("true for a"). - b("true for b"). - "#, - ), + r#" + a("true for a"). + b("true for b"). + "#, ); - let query = String::from(r#"a("true for a")."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::True)); + let query = r#"a("true for a")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + assert_eq!(complete_answer, [LeafAnswer::True]); - let query = String::from(r#"a("true for a"), b("true for b")."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::True)); + let query = r#"a("true for a"), b("true for b")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + assert_eq!(complete_answer, [LeafAnswer::True]); - let query = String::from(r#"a("true for b"), b("true for b")."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::False)); + let query = r#"a("true for b"), b("true for b")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + assert_eq!(complete_answer, [LeafAnswer::False]); - let query = String::from(r#"a("true for a"), b("true for a")."#); - let output = machine.run_query(query); - assert_eq!(output, Ok(QueryResolution::False)); + let query = r#"a("true for a"), b("true for a")."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); + assert_eq!(complete_answer, [LeafAnswer::False]); } #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] fn non_existent_predicate_should_not_cause_panic_when_other_predicates_are_defined() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); machine.consult_module_string( "facts", - String::from( - r#" - triple("a", "p1", "b"). - triple("a", "p2", "b"). - "#, - ), + r#" + triple("a", "p1", "b"). + triple("a", "p2", "b"). + "#, ); - let query = String::from("non_existent_predicate(\"a\",\"p1\",\"b\")."); - - let result = machine.run_query(query); + let query = r#"non_existent_predicate("a","p1","b")."#; + let complete_answer: Result, _> = machine.run_query(query).collect(); assert_eq!( - result, - Err(String::from("error existence_error procedure / non_existent_predicate 3 / non_existent_predicate 3")) + complete_answer, + Err(String::from( + "error existence_error procedure / non_existent_predicate 3 / non_existent_predicate 3" + )) ); } #[test] #[cfg_attr(miri, ignore)] fn atom_quoting() { - let mut machine = Machine::new_lib(); - - let query = "X = '.'.".into(); + let mut machine = MachineBuilder::default().build(); - let result = machine.run_query(query); + let query = "X = '.'."; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => Term::Atom(".".into()), - } - )])) + complete_answer, + [LeafAnswer::from_bindings([("X", Term::atom("."))])] ); } @@ -395,19 +373,17 @@ fn atom_quoting() { #[cfg_attr(miri, ignore)] fn rational_number() { use crate::parser::dashu::rational::RBig; - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); - let query = "X is 1 rdiv 2.".into(); - - let result = machine.run_query(query); + let query = "X is 1 rdiv 2."; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => Term::Rational(RBig::from_parts(1.into(), 2u32.into())), - } - )])) + complete_answer, + [LeafAnswer::from_bindings([( + "X", + Term::rational(RBig::from_parts(1.into(), 2u32.into())) + )])] ); } @@ -415,33 +391,30 @@ fn rational_number() { #[cfg_attr(miri, ignore)] fn big_integer() { use crate::parser::dashu::integer::IBig; - let mut machine = Machine::new_lib(); - - let query = "X is 10^100.".into(); + let mut machine = MachineBuilder::default().build(); - let result = machine.run_query(query); + let query = "X is 10^100."; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => Term::Integer(IBig::from(10).pow(100)), - } - )])) + complete_answer, + [LeafAnswer::from_bindings([( + "X", + Term::integer(IBig::from(10).pow(100)) + )])], ); } #[test] #[cfg_attr(miri, ignore)] fn complicated_term() { - let mut machine = Machine::new_lib(); - - let query = "X = a(\"asdf\", [42, 2.54, asdf, a, [a,b|_], Z]).".into(); + let mut machine = MachineBuilder::default().build(); - let result = machine.run_query(query); + let query = r#"X = a("asdf", [42, 2.54, asdf, a, [a,b|_], Z])."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); - let expected = Term::Structure( - // Composite term + let expected = Term::Compound( + // Compound term "a".into(), vec![ Term::String("asdf".into()), // String @@ -450,12 +423,12 @@ fn complicated_term() { Term::Float(2.54.into()), // Float Term::Atom("asdf".into()), // Atom Term::Atom("a".into()), // Char - Term::Structure( + Term::Compound( // Partial string ".".into(), vec![ Term::Atom("a".into()), - Term::Structure( + Term::Compound( ".".into(), vec![ Term::Atom("b".into()), @@ -470,51 +443,45 @@ fn complicated_term() { ); assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => expected, - } - )])) + complete_answer, + [LeafAnswer::from_bindings([("X", expected)])] ); } #[test] #[cfg_attr(miri, ignore = "it takes too long to run")] fn issue_2341() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); machine.load_module_string( "facts", - String::from( - r#" - male(stephen). - parent(albert,edward). - father(F,C):-parent(F,C),male(F). - "#, - ), + r#" + male(stephen). + parent(albert,edward). + father(F,C):-parent(F,C),male(F). + "#, ); - let query = String::from(r#"father(F,C)."#); - let output = machine.run_query(query); + let query = r#"father(F,C)."#; + let complete_answer: Vec<_> = machine.run_query(query).collect::>().unwrap(); - assert_eq!(output, Ok(QueryResolution::False)); + assert_eq!(complete_answer, [LeafAnswer::False]); } #[test] #[cfg_attr(miri, ignore)] fn query_iterator_determinism() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); { - let mut iterator = machine.run_query_iter("X = 1.".into()); + let mut iterator = machine.run_query("X = 1."); iterator.next(); assert_eq!(iterator.next(), None); } { - let mut iterator = machine.run_query_iter("X = 1 ; false.".into()); + let mut iterator = machine.run_query("X = 1 ; false."); iterator.next(); @@ -523,7 +490,7 @@ fn query_iterator_determinism() { } { - let mut iterator = machine.run_query_iter("false.".into()); + let mut iterator = machine.run_query("false."); assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False))); assert_eq!(iterator.next(), None); @@ -533,9 +500,9 @@ fn query_iterator_determinism() { #[test] #[cfg_attr(miri, ignore)] fn query_iterator_backtracking_when_no_variables() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); - let mut iterator = machine.run_query_iter("true;false.".into()); + let mut iterator = machine.run_query("true;false."); assert_eq!(iterator.next(), Some(Ok(LeafAnswer::True))); assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False))); @@ -545,39 +512,43 @@ fn query_iterator_backtracking_when_no_variables() { #[test] #[cfg_attr(miri, ignore)] fn differentiate_anonymous_variables() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); - let result = machine.run_query("A = [_,_], _B = 1 ; B = [_,_].".into()); + let complete_answer: Vec<_> = machine + .run_query("A = [_,_], _B = 1 ; B = [_,_].") + .collect::>() + .unwrap(); assert_eq!( - result, - Ok(QueryResolution::Matches(vec![ - QueryMatch::from(btreemap! { - "A" => Term::List(vec![Term::Var("_A".into()), Term::Var("_C".into())]), - "_B" => Term::Integer(1.into()), - }), - QueryMatch::from(btreemap! { - "B" => Term::List(vec![Term::Var("_A".into()), Term::Var("_C".into())]), - }), - ])) + complete_answer, + [ + LeafAnswer::from_bindings([ + ( + "A", + Term::list([Term::variable("_A"), Term::variable("_C")]) + ), + ("_B", Term::integer(1)), + ]), + LeafAnswer::from_bindings([( + "B", + Term::list([Term::variable("_A"), Term::variable("_C")]) + ),]), + ] ); } #[test] #[cfg_attr(miri, ignore)] fn order_of_variables_in_binding() { - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); - let result = machine.run_query("X = Y, Z = W.".into()); + let complete_answer: Vec<_> = machine.run_query("X = Y, Z = W.").collect::>().unwrap(); assert_eq!( - result, - Ok(QueryResolution::Matches(vec![QueryMatch::from( - btreemap! { - "X" => Term::Var("Y".into()), - "Z" => Term::Var("W".into()), - } - ),])) + complete_answer, + [LeafAnswer::from_bindings([ + ("X", Term::variable("Y")), + ("Z", Term::variable("W")), + ])] ); } - diff --git a/src/machine/lib_machine/mod.rs b/src/machine/lib_machine/mod.rs index 4aa367a8c..55b6817b6 100644 --- a/src/machine/lib_machine/mod.rs +++ b/src/machine/lib_machine/mod.rs @@ -44,6 +44,16 @@ pub enum LeafAnswer { }, } +impl LeafAnswer { + /// Creates a leaf answer with no residual goals. + pub fn from_bindings>(bindings: impl IntoIterator) -> Self { + LeafAnswer::LeafAnswer { + bindings: bindings.into_iter().map(|(k, v)| (k.into(), v)).collect(), + residual_goals: Vec::new(), + } + } +} + /// Represents a Prolog term. #[non_exhaustive] #[derive(Debug, Clone, PartialEq)] diff --git a/tests/scryer/helper.rs b/tests/scryer/helper.rs index 696d2b12a..bb6402a1a 100644 --- a/tests/scryer/helper.rs +++ b/tests/scryer/helper.rs @@ -26,8 +26,8 @@ impl Expectable for &[u8] { /// Tests whether the file can be successfully loaded /// and produces the expected output during it pub(crate) fn load_module_test(file: &str, expected: T) { - use scryer_prolog::Machine; + use scryer_prolog::MachineBuilder; - let mut wam = Machine::with_test_streams(); + let mut wam = MachineBuilder::default().build(); expected.assert_eq(wam.test_load_file(file).as_slice()); } From 3d3baeed824bedf367bb0e632303d66355ae7a55 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sat, 12 Oct 2024 19:08:45 -0300 Subject: [PATCH 21/28] Migrate benches --- benches/run_iai.rs | 4 +-- benches/setup.rs | 31 +++++++++++++------- src/lib.rs | 6 ++-- src/machine/lib_machine/lib_machine_tests.rs | 7 +++-- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/benches/run_iai.rs b/benches/run_iai.rs index c29645c2d..27f003394 100644 --- a/benches/run_iai.rs +++ b/benches/run_iai.rs @@ -5,7 +5,7 @@ mod setup; mod iai { use iai_callgrind::{library_benchmark, library_benchmark_group, main}; - use scryer_prolog::QueryResolution; + use scryer_prolog::LeafAnswer; use super::setup; @@ -13,7 +13,7 @@ mod iai { #[bench::count_edges(setup::prolog_benches()["count_edges"].setup())] #[bench::numlist(setup::prolog_benches()["numlist"].setup())] #[bench::csv_codename(setup::prolog_benches()["csv_codename"].setup())] - fn bench(mut run: impl FnMut() -> QueryResolution) -> QueryResolution { + fn bench(mut run: impl FnMut() -> Vec) -> Vec { run() } diff --git a/benches/setup.rs b/benches/setup.rs index 6087be446..ac055c083 100644 --- a/benches/setup.rs +++ b/benches/setup.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, fs, path::Path}; use maplit::btreemap; -use scryer_prolog::{Machine, QueryResolution, Value}; +use scryer_prolog::{LeafAnswer, Machine, MachineBuilder, Term}; pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> { [ @@ -10,21 +10,21 @@ pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> { "benches/edges.pl", // name of the prolog module file to load. use the same file in multiple benchmarks "independent_set_count(ky, Count).", // query to benchmark in the context of the loaded module. consider making the query adjustable to tune the run time to ~0.1s Strategy::Reuse, - btreemap! { "Count" => Value::Integer(2869176.into()) }, + btreemap! { "Count" => Term::integer(2869176) }, ), ( "numlist", "benches/numlist.pl", "run_numlist(1000000, Head).", Strategy::Reuse, - btreemap! { "Head" => Value::Integer(1.into())}, + btreemap! { "Head" => Term::integer(1) }, ), ( "csv_codename", "benches/csv.pl", "get_codename(\"0020\",Name).", Strategy::Reuse, - btreemap! { "Name" => Value::String("SPACE".into())}, + btreemap! { "Name" => Term::string("SPACE") }, ), ] .map(|b| { @@ -54,7 +54,7 @@ pub struct PrologBenchmark { pub filename: &'static str, pub query: &'static str, pub strategy: Strategy, - pub bindings: BTreeMap<&'static str, Value>, + pub bindings: BTreeMap<&'static str, Term>, } impl PrologBenchmark { @@ -64,28 +64,34 @@ impl PrologBenchmark { .file_stem() .and_then(|s| s.to_str()) .unwrap(); - let mut machine = Machine::new_lib(); + let mut machine = MachineBuilder::default().build(); machine.load_module_string(module_name, program); machine } #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] - pub fn setup(&self) -> impl FnMut() -> QueryResolution { + pub fn setup(&self) -> impl FnMut() -> Vec { let mut machine = self.make_machine(); let query = self.query; move || { use criterion::black_box; - black_box(machine.run_query(black_box(query.to_string()))).unwrap() + black_box( + machine + .run_query(black_box(query)) + .collect::, _>>() + .unwrap(), + ) } } } #[cfg(test)] mod test { + #[test] fn validate_benchmarks() { use super::prolog_benches; - use scryer_prolog::{QueryMatch, QueryResolution}; + use scryer_prolog::LeafAnswer; use std::{fmt::Write, fs}; struct BenchResult { @@ -100,10 +106,13 @@ mod test { let mut machine = r.make_machine(); let setup_inference_count = machine.get_inference_count(); - let result = machine.run_query(r.query.to_string()).unwrap(); + let result: Vec<_> = machine + .run_query(r.query) + .collect::>() + .unwrap(); let query_inference_count = machine.get_inference_count() - setup_inference_count; - let expected = QueryResolution::Matches(vec![QueryMatch::from(r.bindings.clone())]); + let expected = [LeafAnswer::from_bindings(r.bindings.clone())]; assert_eq!(result, expected, "validating benchmark {}", r.name); results.push(BenchResult { diff --git a/src/lib.rs b/src/lib.rs index 9a7a797e0..85ee726ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,6 @@ #[macro_use] extern crate static_assertions; -#[cfg(test)] -#[macro_use] -extern crate maplit; #[macro_use] pub(crate) mod macros; @@ -50,6 +47,7 @@ pub use machine::config::*; pub use machine::lib_machine::*; pub use machine::Machine; +/// Eval a source file in Wasm. #[cfg(target_arch = "wasm32")] #[wasm_bindgen] pub fn eval_code(s: &str) -> String { @@ -57,7 +55,7 @@ pub fn eval_code(s: &str) -> String { console_error_panic_hook::set_once(); - let mut wam = Machine::with_test_streams(); + let mut wam = MachineBuilder::default().build(); let bytes = wam.test_load_string(s); String::from_utf8_lossy(&bytes).to_string() } diff --git a/src/machine/lib_machine/lib_machine_tests.rs b/src/machine/lib_machine/lib_machine_tests.rs index e865f9e23..c05fccf66 100644 --- a/src/machine/lib_machine/lib_machine_tests.rs +++ b/src/machine/lib_machine/lib_machine_tests.rs @@ -420,7 +420,7 @@ fn complicated_term() { Term::String("asdf".into()), // String Term::List(vec![ Term::Integer(42.into()), // Fixnum - Term::Float(2.54.into()), // Float + Term::Float(2.54), // Float Term::Atom("asdf".into()), // Atom Term::Atom("a".into()), // Char Term::Compound( @@ -542,7 +542,10 @@ fn differentiate_anonymous_variables() { fn order_of_variables_in_binding() { let mut machine = MachineBuilder::default().build(); - let complete_answer: Vec<_> = machine.run_query("X = Y, Z = W.").collect::>().unwrap(); + let complete_answer: Vec<_> = machine + .run_query("X = Y, Z = W.") + .collect::>() + .unwrap(); assert_eq!( complete_answer, From 9265d66f58e2a24f0a89aed1f6d907e38f44b385 Mon Sep 17 00:00:00 2001 From: bakaq Date: Sun, 8 Dec 2024 19:47:18 -0300 Subject: [PATCH 22/28] Handle errors in QueryState --- src/machine/lib_machine/lib_machine_tests.rs | 61 ++++++++++++++++++-- src/machine/lib_machine/mod.rs | 39 +++++-------- 2 files changed, 73 insertions(+), 27 deletions(-) diff --git a/src/machine/lib_machine/lib_machine_tests.rs b/src/machine/lib_machine/lib_machine_tests.rs index c05fccf66..5b89d67da 100644 --- a/src/machine/lib_machine/lib_machine_tests.rs +++ b/src/machine/lib_machine/lib_machine_tests.rs @@ -44,10 +44,21 @@ fn failing_query() { let mut machine = MachineBuilder::default().build(); let query = r#"triple("a",P,"b")."#; let complete_answer: Result, _> = machine.run_query(query).collect(); + assert_eq!( complete_answer, - Err(String::from( - "error existence_error procedure / triple 3 / triple 3" + Err(Term::compound( + "error", + [ + Term::compound( + "existence_error", + [ + Term::atom("procedure"), + Term::compound("/", [Term::atom("triple"), Term::integer(3)]), + ] + ), + Term::compound("/", [Term::atom("triple"), Term::integer(3)]), + ], )) ); } @@ -349,8 +360,24 @@ fn non_existent_predicate_should_not_cause_panic_when_other_predicates_are_defin assert_eq!( complete_answer, - Err(String::from( - "error existence_error procedure / non_existent_predicate 3 / non_existent_predicate 3" + Err(Term::compound( + "error", + [ + Term::compound( + "existence_error", + [ + Term::atom("procedure"), + Term::compound( + "/", + [Term::atom("non_existent_predicate"), Term::integer(3)], + ), + ], + ), + Term::compound( + "/", + [Term::atom("non_existent_predicate"), Term::integer(3)] + ), + ], )) ); } @@ -555,3 +582,29 @@ fn order_of_variables_in_binding() { ])] ); } + +#[test] +#[cfg_attr(miri, ignore)] +fn errors_and_exceptions() { + let mut machine = MachineBuilder::default().build(); + + let complete_answer: Vec<_> = machine.run_query("functor(_,_,_).").collect(); + + assert_eq!( + complete_answer, + [Err(Term::compound( + "error", + [ + Term::atom("instantiation_error"), + Term::compound("/", [Term::atom("functor"), Term::integer(3)]), + ], + ))] + ); + + let complete_answer: Vec<_> = machine.run_query("throw(a).").collect(); + + assert_eq!( + complete_answer, + [Ok(LeafAnswer::Exception(Term::atom("a")))] + ); +} diff --git a/src/machine/lib_machine/mod.rs b/src/machine/lib_machine/mod.rs index 55b6817b6..685603236 100644 --- a/src/machine/lib_machine/mod.rs +++ b/src/machine/lib_machine/mod.rs @@ -426,7 +426,7 @@ impl Drop for QueryState<'_> { } impl Iterator for QueryState<'_> { - type Item = Result; + type Item = Result; fn next(&mut self) -> Option { let var_names = &mut self.var_names; @@ -448,30 +448,23 @@ impl Iterator for QueryState<'_> { // 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 + let h = machine.machine_st.heap.len(); + 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(" "); + .heap + .extend(machine.machine_st.ball.stub.clone()); + let exception_term = + Term::from_heapcell(machine, machine.machine_st.heap[h], &mut var_names.clone()); + + if let Term::Compound(functor, args) = &exception_term { + if functor == "error" && args.len() == 2 { + // We have an error + return Some(Err(exception_term)); + } + } - return Some(Err(error_string)); + // We have an exception that is not an error + return Some(Ok(LeafAnswer::Exception(exception_term))); } if machine.machine_st.p == LIB_QUERY_SUCCESS { From a92eac919fee6f920906ffe0c97cbf3a6322d2a3 Mon Sep 17 00:00:00 2001 From: bakaq Date: Thu, 12 Dec 2024 05:34:16 -0300 Subject: [PATCH 23/28] Rename lib_machine_tests.rs to tests.rs --- src/machine/lib_machine/mod.rs | 2 +- src/machine/lib_machine/{lib_machine_tests.rs => tests.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/machine/lib_machine/{lib_machine_tests.rs => tests.rs} (100%) diff --git a/src/machine/lib_machine/mod.rs b/src/machine/lib_machine/mod.rs index 685603236..62352919a 100644 --- a/src/machine/lib_machine/mod.rs +++ b/src/machine/lib_machine/mod.rs @@ -18,7 +18,7 @@ use indexmap::IndexMap; use super::{streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine}; #[cfg(test)] -mod lib_machine_tests; +mod tests; /// Represents a leaf answer from a query. #[derive(Debug, Clone, PartialEq)] diff --git a/src/machine/lib_machine/lib_machine_tests.rs b/src/machine/lib_machine/tests.rs similarity index 100% rename from src/machine/lib_machine/lib_machine_tests.rs rename to src/machine/lib_machine/tests.rs From a75266c3ceac6430606c1a160cd85c3576bf64ec Mon Sep 17 00:00:00 2001 From: bakaq Date: Thu, 12 Dec 2024 05:38:55 -0300 Subject: [PATCH 24/28] Derive Default for StreamConfig --- src/machine/config.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/machine/config.rs b/src/machine/config.rs index 039cf2f45..4e1e05320 100644 --- a/src/machine/config.rs +++ b/src/machine/config.rs @@ -8,17 +8,11 @@ use super::{ }; /// Describes how the streams of a [`Machine`](crate::Machine) will be handled. +#[derive(Default)] pub struct StreamConfig { inner: StreamConfigInner, } -impl Default for StreamConfig { - /// Defaults to using in-memory streams. - fn default() -> Self { - Self::in_memory() - } -} - impl StreamConfig { /// Binds the input, output and error streams to stdin, stdout and stderr. pub fn stdio() -> Self { @@ -37,8 +31,10 @@ impl StreamConfig { } } +#[derive(Default)] enum StreamConfigInner { Stdio, + #[default] Memory, } From 17293a5753b03fde09efa3ccb7f2a96d3ce15214 Mon Sep 17 00:00:00 2001 From: bakaq Date: Thu, 12 Dec 2024 05:43:23 -0300 Subject: [PATCH 25/28] Leave residual goals for later --- src/machine/lib_machine/mod.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/machine/lib_machine/mod.rs b/src/machine/lib_machine/mod.rs index 62352919a..170fa2c92 100644 --- a/src/machine/lib_machine/mod.rs +++ b/src/machine/lib_machine/mod.rs @@ -31,16 +31,12 @@ pub enum LeafAnswer { False, /// An exception leaf answer. Exception(Term), - /// A leaf answer with bindings and residual goals. + /// A leaf answer with bindings. + #[non_exhaustive] LeafAnswer { /// The bindings of variables in the query. - /// - /// Can be empty. bindings: BTreeMap, - /// Residual goals. - /// - /// Can be empty. - residual_goals: Vec, + //residual_goals: Vec, }, } @@ -49,7 +45,6 @@ impl LeafAnswer { pub fn from_bindings>(bindings: impl IntoIterator) -> Self { LeafAnswer::LeafAnswer { bindings: bindings.into_iter().map(|(k, v)| (k.into(), v)).collect(), - residual_goals: Vec::new(), } } } From 500a6cdaf160ff4d4077657e686848fa4a30abc4 Mon Sep 17 00:00:00 2001 From: bakaq Date: Thu, 12 Dec 2024 05:49:52 -0300 Subject: [PATCH 26/28] FIXME in Drop for QueryState --- src/machine/lib_machine/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/machine/lib_machine/mod.rs b/src/machine/lib_machine/mod.rs index 170fa2c92..13a39fcb2 100644 --- a/src/machine/lib_machine/mod.rs +++ b/src/machine/lib_machine/mod.rs @@ -414,8 +414,8 @@ pub struct QueryState<'a> { 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. + // FIXME: This may be wrong if the iterator is not fully consumend, but from testing it + // seems fine. Is this really ok? self.machine.trust_me(); } } @@ -523,7 +523,6 @@ impl Iterator for QueryState<'_> { Some(Ok(LeafAnswer::LeafAnswer { bindings, - residual_goals: vec![], })) } } From 74e3d32ca9350b5ca7d524835b65a0c6d6d75557 Mon Sep 17 00:00:00 2001 From: bakaq Date: Thu, 12 Dec 2024 05:59:59 -0300 Subject: [PATCH 27/28] Use Cow<'a,B> for toplevel configuration --- src/machine/config.rs | 10 ++++++---- src/machine/mod.rs | 8 ++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/machine/config.rs b/src/machine/config.rs index 4e1e05320..2981899d0 100644 --- a/src/machine/config.rs +++ b/src/machine/config.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use rand::{rngs::StdRng, SeedableRng}; use crate::Machine; @@ -41,7 +43,7 @@ enum StreamConfigInner { /// Describes how a [`Machine`](crate::Machine) will be configured. pub struct MachineBuilder { pub(crate) streams: StreamConfig, - pub(crate) toplevel: &'static str, + pub(crate) toplevel: Cow<'static, str>, } impl Default for MachineBuilder { @@ -49,7 +51,7 @@ impl Default for MachineBuilder { fn default() -> Self { MachineBuilder { streams: Default::default(), - toplevel: default_toplevel(), + toplevel: default_toplevel().into(), } } } @@ -67,8 +69,8 @@ impl MachineBuilder { } /// Uses the given toplevel in this configuration. - pub fn with_toplevel(mut self, toplevel: &'static str) -> Self { - self.toplevel = toplevel; + pub fn with_toplevel(mut self, toplevel: impl Into>) -> Self { + self.toplevel = toplevel.into(); self } diff --git a/src/machine/mod.rs b/src/machine/mod.rs index 22c2a0322..a62c0caa8 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -54,6 +54,7 @@ use lazy_static::lazy_static; use ordered_float::OrderedFloat; use rand::rngs::StdRng; +use std::borrow::Cow; use std::cmp::Ordering; use std::env; use std::io::Read; @@ -297,13 +298,16 @@ impl Machine { self.run_module_predicate(atom!("loader"), (atom!("file_load"), 2)); } - fn load_top_level(&mut self, program: &'static str) { + fn load_top_level(&mut self, program: Cow<'static, str>) { let mut path_buf = current_dir(); path_buf.push("src/toplevel.pl"); let path = path_buf.to_str().unwrap(); - let toplevel_stream = Stream::from_static_string(program, &mut self.machine_st.arena); + let toplevel_stream = match program { + Cow::Borrowed(s) => Stream::from_static_string(s, &mut self.machine_st.arena), + Cow::Owned(s) => Stream::from_owned_string(s, &mut self.machine_st.arena), + }; self.load_file(path, toplevel_stream); From 2e910de4859a986ecb66f6190f411d223eeb6776 Mon Sep 17 00:00:00 2001 From: bakaq Date: Thu, 12 Dec 2024 06:03:43 -0300 Subject: [PATCH 28/28] cargo fmt --- src/machine/lib_machine/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/machine/lib_machine/mod.rs b/src/machine/lib_machine/mod.rs index 13a39fcb2..24f22fe78 100644 --- a/src/machine/lib_machine/mod.rs +++ b/src/machine/lib_machine/mod.rs @@ -521,9 +521,7 @@ impl Iterator for QueryState<'_> { // choice point, so we should break. self.machine.machine_st.backtrack(); - Some(Ok(LeafAnswer::LeafAnswer { - bindings, - })) + Some(Ok(LeafAnswer::LeafAnswer { bindings })) } }