Merge pull request #2309 from coasys/library-use-case

Iron-out edge cases for library use-case, adding extensive real-world test assertions
This commit is contained in:
Mark Thom
2024-02-28 14:34:54 -07:00
committed by GitHub
16 changed files with 12522 additions and 248 deletions

View File

@@ -1,11 +1,10 @@
fn main() -> std::process::ExitCode {
use scryer_prolog::atom_table::Atom;
use scryer_prolog::*;
use std::sync::atomic::Ordering;
#[cfg(feature = "repl")]
ctrlc::set_handler(move || {
scryer_prolog::machine::INTERRUPT.store(true, Ordering::Relaxed);
scryer_prolog::machine::INTERRUPT.store(true, std::sync::atomic::Ordering::Relaxed);
})
.unwrap();

View File

@@ -27,6 +27,7 @@ use std::collections::HashMap;
use std::convert::TryFrom;
use std::error::Error;
use std::ffi::{c_void, CString};
use std::ptr::addr_of_mut;
use libffi::low::type_tag::STRUCT;
use libffi::low::{ffi_abi_FFI_DEFAULT_ABI, ffi_cif, ffi_type, prep_cif, types, CodePtr};
@@ -90,20 +91,20 @@ impl ForeignFunctionTable {
fn map_type_ffi(&mut self, source: &Atom) -> *mut ffi_type {
unsafe {
match source {
atom!("sint64") => &mut types::sint64,
atom!("sint32") => &mut types::sint32,
atom!("sint16") => &mut types::sint16,
atom!("sint8") => &mut types::sint8,
atom!("uint64") => &mut types::uint64,
atom!("uint32") => &mut types::uint32,
atom!("uint16") => &mut types::uint16,
atom!("uint8") => &mut types::uint8,
atom!("bool") => &mut types::sint8,
atom!("void") => &mut types::void,
atom!("cstr") => &mut types::pointer,
atom!("ptr") => &mut types::pointer,
atom!("f32") => &mut types::float,
atom!("f64") => &mut types::double,
atom!("sint64") => addr_of_mut!(types::sint64),
atom!("sint32") => addr_of_mut!(types::sint32),
atom!("sint16") => addr_of_mut!(types::sint16),
atom!("sint8") => addr_of_mut!(types::sint8),
atom!("uint64") => addr_of_mut!(types::uint64),
atom!("uint32") => addr_of_mut!(types::uint32),
atom!("uint16") => addr_of_mut!(types::uint16),
atom!("uint8") => addr_of_mut!(types::uint8),
atom!("bool") => addr_of_mut!(types::sint8),
atom!("void") => addr_of_mut!(types::void),
atom!("cstr") => addr_of_mut!(types::pointer),
atom!("ptr") => addr_of_mut!(types::pointer),
atom!("f32") => addr_of_mut!(types::float),
atom!("f64") => addr_of_mut!(types::double),
struct_name => match self.structs.get_mut(&*struct_name.as_str()) {
Some(ref mut struct_type) => &mut struct_type.ffi_type,
None => unreachable!(),
@@ -161,7 +162,7 @@ impl ForeignFunctionTable {
}
fn build_pointer_args(
args: &mut Vec<Value>,
args: &mut [Value],
type_args: &[*mut ffi_type],
structs_table: &mut HashMap<String, StructImpl>,
) -> Result<PointerArgs, FFIError> {

View File

@@ -1727,7 +1727,7 @@ impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> {
self.print_stream(stream, max_depth);
}
(ArenaHeaderTag::TcpListener, listener) => {
self.print_tcp_listener(&*listener, max_depth);
self.print_tcp_listener(&listener, max_depth);
}
(ArenaHeaderTag::Dropped, _value) => {
self.print_impromptu_atom(atom!("$dropped_value"));

File diff suppressed because one or more lines are too long

View File

@@ -39,10 +39,10 @@ impl Machine {
fn allocate_stub_choice_point(&mut self) {
// NOTE: create a choice point to terminate the dispatch_loop
// if an exception is thrown. since the and/or stack is presumed empty,
// if an exception is thrown.
let stub_b = self.machine_st.stack.allocate_or_frame(0);
let or_frame = self.machine_st.stack.index_or_frame_mut(0);
let or_frame = self.machine_st.stack.index_or_frame_mut(stub_b);
or_frame.prelude.num_cells = 0;
or_frame.prelude.e = 0;
@@ -58,6 +58,7 @@ impl Machine {
self.machine_st.b = stub_b;
self.machine_st.hb = self.machine_st.heap.len();
self.machine_st.block = stub_b;
}
pub fn run_query(&mut self, query: String) -> QueryResult {
@@ -72,24 +73,13 @@ impl Machine {
.read_term(&op_dir, Tokens::Default)
.expect("Failed to parse query");
self.allocate_stub_choice_point();
// Write parsed term to heap
let term_write_result =
write_term_to_heap(&term, &mut self.machine_st.heap, &self.machine_st.atom_tbl)
.expect("couldn't write term to heap");
// Write term to heap
self.machine_st.registers[1] = self.machine_st.heap[term_write_result.heap_loc];
self.machine_st.cp = LIB_QUERY_SUCCESS; // BREAK_FROM_DISPATCH_LOOP_LOC;
self.machine_st.p = self
.indices
.code_dir
.get(&(atom!("call"), 1))
.expect("couldn't get code index")
.local()
.unwrap();
self.machine_st.b0 = self.machine_st.b;
let var_names: IndexMap<_, _> = term_write_result
.var_dict
.iter()
@@ -102,7 +92,19 @@ impl Machine {
})
.collect();
self.allocate_stub_choice_point();
// Write term to heap
self.machine_st.registers[1] = self.machine_st.heap[term_write_result.heap_loc];
self.machine_st.cp = LIB_QUERY_SUCCESS; // BREAK_FROM_DISPATCH_LOOP_LOC;
let call_index_p = self
.indices
.code_dir
.get(&(atom!("call"), 1))
.expect("couldn't get code index")
.local()
.unwrap();
self.machine_st.execute_at_index(1, call_index_p);
let stub_b = self.machine_st.b;
@@ -156,17 +158,17 @@ impl Machine {
};
*/
if term_write_result.var_dict.is_empty() {
if self.machine_st.p == LIB_QUERY_SUCCESS {
if self.machine_st.p == LIB_QUERY_SUCCESS {
if term_write_result.var_dict.is_empty() {
matches.push(QueryResolutionLine::True);
break;
} else if self.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC {
// NOTE: only print results on success
// self.machine_st.fail = false;
// println!("b == stub_b");
matches.push(QueryResolutionLine::False);
break;
}
} else if self.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC {
// NOTE: only print results on success
// self.machine_st.fail = false;
// println!("b == stub_b");
matches.push(QueryResolutionLine::False);
break;
}
let mut bindings: BTreeMap<String, Value> = BTreeMap::new();
@@ -462,6 +464,7 @@ mod tests {
let blocks = code.split("=====");
let mut i = 0;
let mut last_result: Option<_> = None;
// Iterate over the blocks
for block in blocks {
// Trim the block to remove any leading or trailing whitespace
@@ -474,20 +477,24 @@ mod tests {
// Check if the block is a query
if let Some(query) = block.strip_prefix("query") {
i += 1;
println!("query #{}: {}", i, query);
// Parse and execute the query
let result = machine.run_query(query.to_string());
assert!(result.is_ok());
// Print the result
println!("{:?}", result);
last_result = Some(result);
} else if let Some(code) = block.strip_prefix("consult") {
println!("load code: {}", code);
// Load the code into the machine
machine.consult_module_string("facts", code.to_string());
} else if let Some(result) = block.strip_prefix("result") {
i += 1;
if let Some(Ok(ref last_result)) = last_result {
println!(
"\n\n=====Result No. {}=======\n{}\n===============",
i,
last_result.to_string().trim()
);
assert_eq!(last_result.to_string().trim(), result.to_string().trim(),)
}
}
}
}
@@ -524,4 +531,82 @@ mod tests {
),]))
);
}
#[test]
fn dont_return_partial_matches() {
let mut machine = Machine::new_lib();
machine.consult_module_string(
"facts",
String::from(
r#"
:- discontiguous(property_resolve/2).
subject_class("Todo", c).
"#,
),
);
let query = String::from(r#"property_resolve(C, "isLiked"), subject_class("Todo", C)."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::False));
let query = String::from(r#"subject_class("Todo", C), property_resolve(C, "isLiked")."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::False));
}
#[test]
fn dont_return_partial_matches_without_discountiguous() {
let mut machine = Machine::new_lib();
machine.consult_module_string(
"facts",
String::from(
r#"
a("true for a").
b("true for b").
"#,
),
);
let query = String::from(r#"a("true for a")."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::True));
let query = String::from(r#"a("true for a"), b("true for b")."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::True));
let query = String::from(r#"a("true for b"), b("true for b")."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::False));
let query = String::from(r#"a("true for a"), b("true for a")."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::False));
}
#[test]
fn non_existent_predicate_should_not_cause_panic_when_other_predicates_are_defined() {
let mut machine = Machine::new_lib();
machine.consult_module_string(
"facts",
String::from(
r#"
triple("a", "p1", "b").
triple("a", "p2", "b").
"#,
),
);
let query = String::from("non_existent_predicate(\"a\",\"p1\",\"b\").");
let result = machine.run_query(query);
assert_eq!(
result,
Err(String::from("error existence_error procedure / non_existent_predicate 3 / non_existent_predicate 3"))
);
}
}

View File

@@ -1022,19 +1022,6 @@ pub enum SessionError {
QueryCannotBeDefinedAsFact,
}
#[derive(Debug)]
pub(crate) enum EvalSession {
// EntrySuccess,
Error(SessionError),
}
impl From<SessionError> for EvalSession {
#[inline]
fn from(err: SessionError) -> Self {
EvalSession::Error(err)
}
}
impl From<std::io::Error> for SessionError {
#[inline]
fn from(err: std::io::Error) -> SessionError {
@@ -1055,10 +1042,3 @@ impl From<CompilationError> for SessionError {
SessionError::CompilationError(err)
}
}
impl From<ParserError> for EvalSession {
#[inline]
fn from(err: ParserError) -> Self {
EvalSession::from(SessionError::from(err))
}
}

View File

@@ -13,6 +13,84 @@ pub enum QueryResolution {
Matches(Vec<QueryMatch>),
}
pub fn prolog_value_to_json_string(value: Value) -> String {
match value {
Value::Integer(i) => format!("{}", i),
Value::Float(f) => format!("{}", f),
Value::Rational(r) => format!("{}", r),
Value::Atom(a) => format!("{}", a.as_str()),
Value::String(s) => {
if let Err(_e) = serde_json::from_str::<serde_json::Value>(s.as_str()) {
//treat as string literal
//escape double quotes
format!(
"\"{}\"",
s.replace('\"', "\\\"")
.replace('\n', "\\n")
.replace('\t', "\\t")
.replace('\r', "\\r")
)
} else {
//return valid json string
s
}
}
Value::List(l) => {
let mut string_result = "[".to_string();
for (i, v) in l.iter().enumerate() {
if i > 0 {
string_result.push(',');
}
string_result.push_str(&prolog_value_to_json_string(v.clone()));
}
string_result.push(']');
string_result
}
Value::Structure(s, l) => {
let mut string_result = format!("\"{}\":[", s.as_str());
for (i, v) in l.iter().enumerate() {
if i > 0 {
string_result.push(',');
}
string_result.push_str(&prolog_value_to_json_string(v.clone()));
}
string_result.push(']');
string_result
}
_ => "null".to_string(),
}
}
fn prolog_match_to_json_string(query_match: &QueryMatch) -> String {
let mut string_result = "{".to_string();
for (i, (k, v)) in query_match.bindings.iter().enumerate() {
if i > 0 {
string_result.push(',');
}
string_result.push_str(&format!(
"\"{}\":{}",
k,
prolog_value_to_json_string(v.clone())
));
}
string_result.push('}');
string_result
}
impl ToString for QueryResolution {
fn to_string(&self) -> String {
match self {
QueryResolution::True => "true".to_string(),
QueryResolution::False => "false".to_string(),
QueryResolution::Matches(matches) => {
let matches_json: Vec<String> =
matches.iter().map(prolog_match_to_json_string).collect();
format!("[{}]", matches_json.join(","))
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueryMatch {
pub bindings: BTreeMap<String, Value>,

View File

@@ -229,7 +229,7 @@ impl<R: Read> CharRead for CharReader<R> {
match self.inner.read(word_slice) {
Err(e) => return Some(Err(e)),
Ok(nread) if nread == 0 => return Some(Err(bad_bytes_error(&self.buf))),
Ok(0) => return Some(Err(bad_bytes_error(&self.buf))),
Ok(nread) => {
self.buf.extend_from_slice(&word_slice[0..nread]);
}