Rename LeafAnswer
This commit is contained in:
@@ -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<QueryResolutionLine, String>;
|
||||
type Item = Result<LeafAnswer, String>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<String, PrologTerm> = 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<QueryResolution, String>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum QueryResolution {
|
||||
pub enum LeafAnswer {
|
||||
True,
|
||||
False,
|
||||
Matches(Vec<QueryMatch>),
|
||||
}
|
||||
|
||||
fn write_prolog_value_as_json<W: Write>(
|
||||
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>),
|
||||
Exception(PrologTerm),
|
||||
LeafAnswer {
|
||||
bindings: BTreeMap<String, PrologTerm>,
|
||||
residual_goals: Vec<PrologTerm>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -396,248 +289,3 @@ impl PrologTerm {
|
||||
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