use super::{
streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine, MachineConfig,
- QueryResolutionLine, QueryResult, PrologTerm,
+ LeafAnswer, PrologTerm,
};
pub struct QueryState<'a> {
}
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;
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();
// 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![] }))
}
}
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,
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);
}
}
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);
}
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 {
- 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 {
+pub enum LeafAnswer {
True,
False,
- Match(BTreeMap<String, PrologTerm>),
+ Exception(PrologTerm),
+ LeafAnswer {
+ bindings: BTreeMap<String, PrologTerm>,
+ residual_goals: Vec<PrologTerm>,
+ },
}
#[derive(Debug, Clone, PartialEq, Eq)]
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())
- }
-}