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 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), } pub fn write_prolog_value_as_json( writer: &mut W, value: &Value, ) -> 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) => { 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) } } Value::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(']') } Value::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 { True, False, Match(BTreeMap), } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Value { Integer(Integer), Rational(Rational), Float(OrderedFloat), Atom(String), String(String), List(Vec), Structure(String, Vec), Var(String), } /// 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 Value { 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 { Value::Atom(atom) if atom == "[]" => match head { Value::Atom(ref a) if a.chars().collect::>().len() == 1 => { // Handle lists of char as strings Value::String(a.to_string()) } _ => Value::List(vec![head]), }, Value::List(elems) if elems.is_empty() => match head { Value::Atom(ref a) if a.chars().collect::>().len() == 1 => { // Handle lists of char as strings Value::String(a.to_string()) }, _ => Value::List(vec![head]), }, Value::List(mut elems) => { elems.insert(0, head); Value::List(elems) }, Value::String(mut elems) => match head { Value::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) }, _ => { let mut elems: Vec = elems .chars() .map(|x| Value::Atom(x.into())) .collect(); elems.insert(0, head); Value::List(elems) } }, _ => Value::Structure(".".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(Value::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(Value::Var(anon_name)); }, } } (HeapCellValueTag::F64, f) => { term_stack.push(Value::Float(*f)); } (HeapCellValueTag::Char, c) => { term_stack.push(Value::Atom(c.into())); } (HeapCellValueTag::Fixnum, n) => { term_stack.push(Value::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())), _ => {} } } (HeapCellValueTag::CStr, s) => { term_stack.push(Value::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(Value::List(vec![])); } else { term_stack.push(Value::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)); } } (HeapCellValueTag::PStr, atom) => { let tail = term_stack.pop().unwrap(); if let Value::Atom(atom) = tail { if atom == "[]" { term_stack.push(Value::String(atom.as_str().to_string())); } } else { let mut list: Vec = atom .as_str() .to_string() .chars() .map(|x| Value::Atom(x.to_string())) .collect(); let mut partial_list = Value::Structure( ".".into(), vec![ list.pop().unwrap(), tail, ], ); while let Some(last) = list.pop() { partial_list = Value::Structure( ".".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() } } impl From> for QueryMatch { fn from(bindings: BTreeMap<&str, Value>) -> 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, Value), ()> { let key = k.to_string(); let value = v.to_string(); Ok((key, Value::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 Value { 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))) } else if let Ok(int_value) = string.parse::() { Ok(Value::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())) } 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) .collect::, _>>()?; Ok(Value::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(Value::try_from(value)?); } } Ok(Value::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(Value::try_from(value)?); } } Ok(Value::Structure("<<>>".into(), values)) } else if !trimmed.contains(',') && !trimmed.contains('\'') && !trimmed.contains('"') { Ok(Value::String(trimmed.into())) } else { Err(()) } } } impl From<&str> for Value { fn from(str: &str) -> Self { Value::String(str.to_string()) } }