Rename LeafAnswer

This commit is contained in:
bakaq
2024-09-07 01:15:05 -03:00
parent 8e7dc9dba8
commit 29fc55cb28
2 changed files with 16 additions and 372 deletions

View File

@@ -11,7 +11,7 @@ use indexmap::IndexMap;
use super::{ use super::{
streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine, MachineConfig, streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine, MachineConfig,
QueryResolutionLine, QueryResult, PrologTerm, LeafAnswer, PrologTerm,
}; };
pub struct QueryState<'a> { pub struct QueryState<'a> {
@@ -31,7 +31,7 @@ impl Drop for QueryState<'_> {
} }
impl Iterator for QueryState<'_> { impl Iterator for QueryState<'_> {
type Item = Result<QueryResolutionLine, String>; type Item = Result<LeafAnswer, String>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let var_names = &mut self.var_names; let var_names = &mut self.var_names;
@@ -82,10 +82,10 @@ impl Iterator for QueryState<'_> {
if machine.machine_st.p == LIB_QUERY_SUCCESS { if machine.machine_st.p == LIB_QUERY_SUCCESS {
if term_write_result.var_dict.is_empty() { if term_write_result.var_dict.is_empty() {
self.machine.machine_st.backtrack(); 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 { } 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<String, PrologTerm> = BTreeMap::new(); let mut bindings: BTreeMap<String, PrologTerm> = BTreeMap::new();
@@ -138,7 +138,7 @@ impl Iterator for QueryState<'_> {
// choice point, so we should break. // choice point, so we should break.
self.machine.machine_st.backtrack(); 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; self.machine_st.block = stub_b;
} }
pub fn run_query(&mut self, query: String) -> QueryResult { pub fn run_query(&mut self, query: String) -> QueryState {
self.run_query_iter(query).collect()
}
pub fn run_query_iter(&mut self, query: String) -> QueryState {
let mut parser = Parser::new( let mut parser = Parser::new(
Stream::from_owned_string(query, &mut self.machine_st.arena), Stream::from_owned_string(query, &mut self.machine_st.arena),
&mut self.machine_st, &mut self.machine_st,
@@ -767,14 +763,14 @@ mod tests {
iterator.next(); iterator.next();
assert_eq!(iterator.next(), Some(Ok(QueryResolutionLine::False))); assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False)));
assert_eq!(iterator.next(), None); assert_eq!(iterator.next(), None);
} }
{ {
let mut iterator = machine.run_query_iter("false.".into()); 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); assert_eq!(iterator.next(), None);
} }
} }
@@ -786,8 +782,8 @@ mod tests {
let mut iterator = machine.run_query_iter("true;false.".into()); let mut iterator = machine.run_query_iter("true;false.".into());
assert_eq!(iterator.next(), Some(Ok(QueryResolutionLine::True))); assert_eq!(iterator.next(), Some(Ok(LeafAnswer::True)));
assert_eq!(iterator.next(), Some(Ok(QueryResolutionLine::False))); assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False)));
assert_eq!(iterator.next(), None); assert_eq!(iterator.next(), None);
} }

View File

@@ -7,126 +7,19 @@ use indexmap::IndexMap;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::BTreeMap; 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::Machine;
use super::{HeapCellValue, Number}; use super::{HeapCellValue, Number};
pub type QueryResult = Result<QueryResolution, String>;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryResolution { pub enum LeafAnswer {
True, True,
False, False,
Matches(Vec<QueryMatch>), Exception(PrologTerm),
} LeafAnswer {
bindings: BTreeMap<String, PrologTerm>,
fn write_prolog_value_as_json<W: Write>( residual_goals: Vec<PrologTerm>,
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::<serde_json::Value>(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<W: std::fmt::Write>(
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<String, PrologTerm>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryResolutionLine {
True,
False,
Match(BTreeMap<String, PrologTerm>),
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@@ -396,248 +289,3 @@ impl PrologTerm {
term_stack.pop().unwrap() term_stack.pop().unwrap()
} }
} }
impl From<BTreeMap<&str, PrologTerm>> for QueryMatch {
fn from(bindings: BTreeMap<&str, PrologTerm>) -> Self {
QueryMatch {
bindings: bindings
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect::<BTreeMap<_, _>>(),
}
}
}
impl From<BTreeMap<String, PrologTerm>> for QueryMatch {
fn from(bindings: BTreeMap<String, PrologTerm>) -> Self {
QueryMatch { bindings }
}
}
impl From<Vec<QueryResolutionLine>> for QueryResolution {
fn from(query_result_lines: Vec<QueryResolutionLine>) -> 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::<Vec<_>>();
if !all_matches.is_empty() {
return QueryResolution::Matches(all_matches);
}
QueryResolution::False
}
}
impl FromIterator<QueryResolutionLine> for QueryResolution {
fn from_iter<I: IntoIterator<Item = QueryResolutionLine>>(iter: I) -> Self {
// TODO: Probably a good idea to implement From<Vec<QueryResolutionLine>> based on this
// instead.
iter.into_iter().collect::<Vec<_>>().into()
}
}
fn split_response_string(input: &str) -> Vec<String> {
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<String, String> {
let mut map: HashMap<String, String> = 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<String> for QueryResolutionLine {
type Error = ();
fn try_from(string: String) -> Result<Self, Self::Error> {
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::<BTreeMap<_, _>>(),
)),
}
}
}
fn split_nested_list(input: &str) -> Vec<String> {
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<String> for PrologTerm {
type Error = ();
fn try_from(string: String) -> Result<Self, Self::Error> {
let trimmed = string.trim();
if let Ok(float_value) = string.parse::<f64>() {
Ok(PrologTerm::Float(OrderedFloat(float_value)))
} else if let Ok(int_value) = string.parse::<i128>() {
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::<Result<Vec<_>, _>>()?;
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())
}
}