Rename LeafAnswer
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user