Merge pull request #2582 from bakaq/rework_library_interface
Rework library interface
This commit is contained in:
@@ -5,7 +5,7 @@ mod setup;
|
||||
mod iai {
|
||||
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
|
||||
|
||||
use scryer_prolog::QueryResolution;
|
||||
use scryer_prolog::LeafAnswer;
|
||||
|
||||
use super::setup;
|
||||
|
||||
@@ -13,7 +13,7 @@ mod iai {
|
||||
#[bench::count_edges(setup::prolog_benches()["count_edges"].setup())]
|
||||
#[bench::numlist(setup::prolog_benches()["numlist"].setup())]
|
||||
#[bench::csv_codename(setup::prolog_benches()["csv_codename"].setup())]
|
||||
fn bench(mut run: impl FnMut() -> QueryResolution) -> QueryResolution {
|
||||
fn bench(mut run: impl FnMut() -> Vec<LeafAnswer>) -> Vec<LeafAnswer> {
|
||||
run()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{collections::BTreeMap, fs, path::Path};
|
||||
|
||||
use maplit::btreemap;
|
||||
use scryer_prolog::{Machine, QueryResolution, Value};
|
||||
use scryer_prolog::{LeafAnswer, Machine, MachineBuilder, Term};
|
||||
|
||||
pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
|
||||
[
|
||||
@@ -10,21 +10,21 @@ pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
|
||||
"benches/edges.pl", // name of the prolog module file to load. use the same file in multiple benchmarks
|
||||
"independent_set_count(ky, Count).", // query to benchmark in the context of the loaded module. consider making the query adjustable to tune the run time to ~0.1s
|
||||
Strategy::Reuse,
|
||||
btreemap! { "Count" => Value::Integer(2869176.into()) },
|
||||
btreemap! { "Count" => Term::integer(2869176) },
|
||||
),
|
||||
(
|
||||
"numlist",
|
||||
"benches/numlist.pl",
|
||||
"run_numlist(1000000, Head).",
|
||||
Strategy::Reuse,
|
||||
btreemap! { "Head" => Value::Integer(1.into())},
|
||||
btreemap! { "Head" => Term::integer(1) },
|
||||
),
|
||||
(
|
||||
"csv_codename",
|
||||
"benches/csv.pl",
|
||||
"get_codename(\"0020\",Name).",
|
||||
Strategy::Reuse,
|
||||
btreemap! { "Name" => Value::String("SPACE".into())},
|
||||
btreemap! { "Name" => Term::string("SPACE") },
|
||||
),
|
||||
]
|
||||
.map(|b| {
|
||||
@@ -54,7 +54,7 @@ pub struct PrologBenchmark {
|
||||
pub filename: &'static str,
|
||||
pub query: &'static str,
|
||||
pub strategy: Strategy,
|
||||
pub bindings: BTreeMap<&'static str, Value>,
|
||||
pub bindings: BTreeMap<&'static str, Term>,
|
||||
}
|
||||
|
||||
impl PrologBenchmark {
|
||||
@@ -64,28 +64,34 @@ impl PrologBenchmark {
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap();
|
||||
let mut machine = Machine::new_lib();
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
machine.load_module_string(module_name, program);
|
||||
machine
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
pub fn setup(&self) -> impl FnMut() -> QueryResolution {
|
||||
pub fn setup(&self) -> impl FnMut() -> Vec<LeafAnswer> {
|
||||
let mut machine = self.make_machine();
|
||||
let query = self.query;
|
||||
move || {
|
||||
use criterion::black_box;
|
||||
black_box(machine.run_query(black_box(query.to_string()))).unwrap()
|
||||
black_box(
|
||||
machine
|
||||
.run_query(black_box(query))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
#[test]
|
||||
fn validate_benchmarks() {
|
||||
use super::prolog_benches;
|
||||
use scryer_prolog::{QueryMatch, QueryResolution};
|
||||
use scryer_prolog::LeafAnswer;
|
||||
use std::{fmt::Write, fs};
|
||||
|
||||
struct BenchResult {
|
||||
@@ -100,10 +106,13 @@ mod test {
|
||||
let mut machine = r.make_machine();
|
||||
let setup_inference_count = machine.get_inference_count();
|
||||
|
||||
let result = machine.run_query(r.query.to_string()).unwrap();
|
||||
let result: Vec<_> = machine
|
||||
.run_query(r.query)
|
||||
.collect::<Result<_, _>>()
|
||||
.unwrap();
|
||||
let query_inference_count = machine.get_inference_count() - setup_inference_count;
|
||||
|
||||
let expected = QueryResolution::Matches(vec![QueryMatch::from(r.bindings.clone())]);
|
||||
let expected = [LeafAnswer::from_bindings(r.bindings.clone())];
|
||||
assert_eq!(result, expected, "validating benchmark {}", r.name);
|
||||
|
||||
results.push(BenchResult {
|
||||
|
||||
16
src/lib.rs
16
src/lib.rs
@@ -1,10 +1,9 @@
|
||||
//! A free software ISO Prolog system.
|
||||
#![recursion_limit = "4112"]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate static_assertions;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate maplit;
|
||||
|
||||
#[macro_use]
|
||||
pub(crate) mod macros;
|
||||
@@ -46,9 +45,9 @@ use wasm_bindgen::prelude::*;
|
||||
// Re-exports
|
||||
pub use machine::config::*;
|
||||
pub use machine::lib_machine::*;
|
||||
pub use machine::parsed_results::*;
|
||||
pub use machine::Machine;
|
||||
|
||||
/// Eval a source file in Wasm.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub fn eval_code(s: &str) -> String {
|
||||
@@ -56,14 +55,15 @@ pub fn eval_code(s: &str) -> String {
|
||||
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let mut wam = Machine::with_test_streams();
|
||||
let mut wam = MachineBuilder::default().build();
|
||||
let bytes = wam.test_load_string(s);
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
}
|
||||
|
||||
/// The entry point for the Scryer Prolog CLI.
|
||||
pub fn run_binary() -> std::process::ExitCode {
|
||||
use crate::atom_table::Atom;
|
||||
use crate::machine::{Machine, INTERRUPT};
|
||||
use crate::machine::INTERRUPT;
|
||||
|
||||
#[cfg(feature = "repl")]
|
||||
ctrlc::set_handler(move || {
|
||||
@@ -84,7 +84,9 @@ pub fn run_binary() -> std::process::ExitCode {
|
||||
.unwrap();
|
||||
|
||||
runtime.block_on(async move {
|
||||
let mut wam = Machine::new(Default::default());
|
||||
let mut wam = MachineBuilder::default()
|
||||
.with_streams(StreamConfig::stdio())
|
||||
.build();
|
||||
wam.run_module_predicate(atom!("$toplevel"), (atom!("$repl"), 0))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,32 +1,189 @@
|
||||
pub struct MachineConfig {
|
||||
pub streams: StreamConfig,
|
||||
pub toplevel: &'static str,
|
||||
use std::borrow::Cow;
|
||||
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
use crate::Machine;
|
||||
|
||||
use super::{
|
||||
bootstrapping_compile, current_dir, import_builtin_impls, libraries, load_module, Atom,
|
||||
CompilationTarget, IndexStore, ListingSource, MachineArgs, MachineState, Stream, StreamOptions,
|
||||
};
|
||||
|
||||
/// Describes how the streams of a [`Machine`](crate::Machine) will be handled.
|
||||
#[derive(Default)]
|
||||
pub struct StreamConfig {
|
||||
inner: StreamConfigInner,
|
||||
}
|
||||
|
||||
pub enum StreamConfig {
|
||||
impl StreamConfig {
|
||||
/// Binds the input, output and error streams to stdin, stdout and stderr.
|
||||
pub fn stdio() -> Self {
|
||||
StreamConfig {
|
||||
inner: StreamConfigInner::Stdio,
|
||||
}
|
||||
}
|
||||
|
||||
/// Binds the output stream to a memory buffer, and the error stream to stderr.
|
||||
///
|
||||
/// The input stream is ignored.
|
||||
pub fn in_memory() -> Self {
|
||||
StreamConfig {
|
||||
inner: StreamConfigInner::Memory,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum StreamConfigInner {
|
||||
Stdio,
|
||||
#[default]
|
||||
Memory,
|
||||
}
|
||||
|
||||
impl Default for MachineConfig {
|
||||
/// Describes how a [`Machine`](crate::Machine) will be configured.
|
||||
pub struct MachineBuilder {
|
||||
pub(crate) streams: StreamConfig,
|
||||
pub(crate) toplevel: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl Default for MachineBuilder {
|
||||
/// Defaults to using in-memory streams.
|
||||
fn default() -> Self {
|
||||
MachineConfig {
|
||||
streams: StreamConfig::Stdio,
|
||||
toplevel: include_str!("../toplevel.pl"),
|
||||
MachineBuilder {
|
||||
streams: Default::default(),
|
||||
toplevel: default_toplevel().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineConfig {
|
||||
pub fn in_memory() -> Self {
|
||||
MachineConfig {
|
||||
streams: StreamConfig::Memory,
|
||||
..Default::default()
|
||||
}
|
||||
impl MachineBuilder {
|
||||
/// Creates a default configuration.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn with_toplevel(mut self, toplevel: &'static str) -> Self {
|
||||
self.toplevel = toplevel;
|
||||
/// Uses the given `crate::StreamConfig` in this configuration.
|
||||
pub fn with_streams(mut self, streams: StreamConfig) -> Self {
|
||||
self.streams = streams;
|
||||
self
|
||||
}
|
||||
|
||||
/// Uses the given toplevel in this configuration.
|
||||
pub fn with_toplevel(mut self, toplevel: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.toplevel = toplevel.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the [`Machine`](crate::Machine) from this configuration.
|
||||
pub fn build(self) -> Machine {
|
||||
let args = MachineArgs::new();
|
||||
let mut machine_st = MachineState::new();
|
||||
|
||||
let (user_input, user_output, user_error) = match self.streams.inner {
|
||||
StreamConfigInner::Stdio => (
|
||||
Stream::stdin(&mut machine_st.arena, args.add_history),
|
||||
Stream::stdout(&mut machine_st.arena),
|
||||
Stream::stderr(&mut machine_st.arena),
|
||||
),
|
||||
StreamConfigInner::Memory => (
|
||||
Stream::Null(StreamOptions::default()),
|
||||
Stream::from_owned_string("".to_owned(), &mut machine_st.arena),
|
||||
Stream::stderr(&mut machine_st.arena),
|
||||
),
|
||||
};
|
||||
|
||||
let mut wam = Machine {
|
||||
machine_st,
|
||||
indices: IndexStore::new(),
|
||||
code: vec![],
|
||||
user_input,
|
||||
user_output,
|
||||
user_error,
|
||||
load_contexts: vec![],
|
||||
#[cfg(feature = "ffi")]
|
||||
foreign_function_table: Default::default(),
|
||||
rng: StdRng::from_entropy(),
|
||||
};
|
||||
|
||||
let mut lib_path = current_dir();
|
||||
|
||||
lib_path.pop();
|
||||
lib_path.push("lib");
|
||||
|
||||
wam.add_impls_to_indices();
|
||||
|
||||
bootstrapping_compile(
|
||||
Stream::from_static_string(
|
||||
libraries::get("ops_and_meta_predicates")
|
||||
.expect("library ops_and_meta_predicates should exist"),
|
||||
&mut wam.machine_st.arena,
|
||||
),
|
||||
&mut wam,
|
||||
ListingSource::from_file_and_path(
|
||||
atom!("ops_and_meta_predicates.pl"),
|
||||
lib_path.clone(),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
bootstrapping_compile(
|
||||
Stream::from_static_string(
|
||||
libraries::get("builtins").expect("library builtins should exist"),
|
||||
&mut wam.machine_st.arena,
|
||||
),
|
||||
&mut wam,
|
||||
ListingSource::from_file_and_path(atom!("builtins.pl"), lib_path.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(builtins) = wam.indices.modules.get_mut(&atom!("builtins")) {
|
||||
load_module(
|
||||
&mut wam.machine_st,
|
||||
&mut wam.indices.code_dir,
|
||||
&mut wam.indices.op_dir,
|
||||
&mut wam.indices.meta_predicates,
|
||||
&CompilationTarget::User,
|
||||
builtins,
|
||||
);
|
||||
|
||||
import_builtin_impls(&wam.indices.code_dir, builtins);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
lib_path.pop(); // remove the "lib" at the end
|
||||
|
||||
bootstrapping_compile(
|
||||
Stream::from_static_string(include_str!("../loader.pl"), &mut wam.machine_st.arena),
|
||||
&mut wam,
|
||||
ListingSource::from_file_and_path(atom!("loader.pl"), lib_path.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
wam.configure_modules();
|
||||
|
||||
if let Some(loader) = wam.indices.modules.get(&atom!("loader")) {
|
||||
load_module(
|
||||
&mut wam.machine_st,
|
||||
&mut wam.indices.code_dir,
|
||||
&mut wam.indices.op_dir,
|
||||
&mut wam.indices.meta_predicates,
|
||||
&CompilationTarget::User,
|
||||
loader,
|
||||
);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
wam.load_special_forms();
|
||||
wam.load_top_level(self.toplevel);
|
||||
wam.configure_streams();
|
||||
|
||||
wam
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a static string slice to the default toplevel
|
||||
pub fn default_toplevel() -> &'static str {
|
||||
include_str!("../toplevel.pl")
|
||||
}
|
||||
|
||||
@@ -1,832 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::atom_table;
|
||||
use crate::machine::machine_indices::VarKey;
|
||||
use crate::machine::mock_wam::CompositeOpDir;
|
||||
use crate::machine::{BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS};
|
||||
use crate::parser::ast::{Var, VarPtr};
|
||||
use crate::parser::parser::{Parser, Tokens};
|
||||
use crate::read::{write_term_to_heap, TermWriteResult};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::{
|
||||
streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine, MachineConfig,
|
||||
QueryResolutionLine, QueryResult, Value,
|
||||
};
|
||||
|
||||
pub struct QueryState<'a> {
|
||||
machine: &'a mut Machine,
|
||||
term: TermWriteResult,
|
||||
stub_b: usize,
|
||||
var_names: IndexMap<HeapCellValue, VarPtr>,
|
||||
called: bool,
|
||||
}
|
||||
|
||||
impl Drop for QueryState<'_> {
|
||||
fn drop(&mut self) {
|
||||
// This may be wrong if the iterator is not fully consumend, but from testing it seems
|
||||
// fine.
|
||||
self.machine.trust_me();
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for QueryState<'_> {
|
||||
type Item = Result<QueryResolutionLine, String>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let var_names = &mut self.var_names;
|
||||
let term_write_result = &self.term;
|
||||
let machine = &mut self.machine;
|
||||
|
||||
// No more choicepoints, end iteration
|
||||
if self.called && machine.machine_st.b <= self.stub_b {
|
||||
return None;
|
||||
}
|
||||
|
||||
machine.dispatch_loop();
|
||||
|
||||
self.called = true;
|
||||
|
||||
if !machine.machine_st.ball.stub.is_empty() {
|
||||
// NOTE: this means an exception was thrown, at which
|
||||
// point we backtracked to the stub choice point.
|
||||
// this should halt the search for solutions as it
|
||||
// does in the Scryer top-level. the exception term is
|
||||
// contained in self.machine_st.ball.
|
||||
let error_string = self
|
||||
.machine
|
||||
.machine_st
|
||||
.ball
|
||||
.stub
|
||||
.iter()
|
||||
.filter(|h| {
|
||||
matches!(
|
||||
h.get_tag(),
|
||||
HeapCellValueTag::Atom | HeapCellValueTag::Fixnum
|
||||
)
|
||||
})
|
||||
.map(|h| match h.get_tag() {
|
||||
HeapCellValueTag::Atom => {
|
||||
let (name, _) = cell_as_atom_cell!(h).get_name_and_arity();
|
||||
name.as_str().to_string()
|
||||
}
|
||||
HeapCellValueTag::Fixnum => h.get_value().clone().to_string(),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ");
|
||||
|
||||
return Some(Err(error_string));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
} else if machine.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC {
|
||||
return Some(Ok(QueryResolutionLine::False));
|
||||
}
|
||||
|
||||
let mut bindings: BTreeMap<String, Value> = BTreeMap::new();
|
||||
|
||||
let var_dict = &term_write_result.var_dict;
|
||||
|
||||
for (var_key, term_to_be_printed) in var_dict.iter() {
|
||||
let mut var_name = var_key.to_string();
|
||||
if var_name.starts_with('_') {
|
||||
let should_print = var_names.values().any(|x| match x.borrow().clone() {
|
||||
Var::Named(v) => v == var_name,
|
||||
_ => false,
|
||||
});
|
||||
if !should_print {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut term =
|
||||
Value::from_heapcell(machine, *term_to_be_printed, &mut var_names.clone());
|
||||
|
||||
if let Value::Var(ref term_str) = term {
|
||||
if *term_str == var_name {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Var dict is in the order things appear in the query. If var_name appears
|
||||
// after term in the query, switch their places.
|
||||
let var_name_idx = var_dict
|
||||
.get_index_of(&VarKey::VarPtr(Var::Named(var_name.clone()).into()))
|
||||
.unwrap();
|
||||
let term_idx =
|
||||
var_dict.get_index_of(&VarKey::VarPtr(Var::Named(term_str.clone()).into()));
|
||||
if let Some(idx) = term_idx {
|
||||
if idx < var_name_idx {
|
||||
let new_term = Value::Var(var_name);
|
||||
let new_var_name = term_str.into();
|
||||
term = new_term;
|
||||
var_name = new_var_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bindings.insert(var_name, term);
|
||||
}
|
||||
|
||||
// NOTE: there are outstanding choicepoints, backtrack
|
||||
// through them for further solutions. if
|
||||
// self.machine_st.b == stub_b we've backtracked to the stub
|
||||
// choice point, so we should break.
|
||||
self.machine.machine_st.backtrack();
|
||||
|
||||
Some(Ok(QueryResolutionLine::Match(bindings)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
pub fn new_lib() -> Self {
|
||||
Machine::new(MachineConfig::in_memory())
|
||||
}
|
||||
|
||||
pub fn load_module_string(&mut self, module_name: &str, program: String) {
|
||||
let stream = Stream::from_owned_string(program, &mut self.machine_st.arena);
|
||||
self.load_file(module_name, stream);
|
||||
}
|
||||
|
||||
pub fn consult_module_string(&mut self, module_name: &str, program: String) {
|
||||
let stream = Stream::from_owned_string(program, &mut self.machine_st.arena);
|
||||
self.machine_st.registers[1] = stream_as_cell!(stream);
|
||||
self.machine_st.registers[2] = atom_as_cell!(&atom_table::AtomTable::build_with(
|
||||
&self.machine_st.atom_tbl,
|
||||
module_name
|
||||
));
|
||||
|
||||
self.run_module_predicate(atom!("loader"), (atom!("consult_stream"), 2));
|
||||
}
|
||||
|
||||
fn allocate_stub_choice_point(&mut self) {
|
||||
// NOTE: create a choice point to terminate the dispatch_loop
|
||||
// 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(stub_b);
|
||||
|
||||
or_frame.prelude.num_cells = 0;
|
||||
or_frame.prelude.e = 0;
|
||||
or_frame.prelude.cp = 0;
|
||||
or_frame.prelude.b = 0;
|
||||
or_frame.prelude.bp = BREAK_FROM_DISPATCH_LOOP_LOC;
|
||||
or_frame.prelude.boip = 0;
|
||||
or_frame.prelude.biip = 0;
|
||||
or_frame.prelude.tr = 0;
|
||||
or_frame.prelude.h = 0;
|
||||
or_frame.prelude.b0 = 0;
|
||||
or_frame.prelude.attr_var_queue_len = 0;
|
||||
|
||||
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 {
|
||||
self.run_query_iter(query).collect()
|
||||
}
|
||||
|
||||
pub fn run_query_iter(&mut self, query: String) -> QueryState {
|
||||
let mut parser = Parser::new(
|
||||
Stream::from_owned_string(query, &mut self.machine_st.arena),
|
||||
&mut self.machine_st,
|
||||
);
|
||||
let op_dir = CompositeOpDir::new(&self.indices.op_dir, None);
|
||||
let term = parser
|
||||
.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");
|
||||
|
||||
let var_names: IndexMap<_, _> = term_write_result
|
||||
.var_dict
|
||||
.iter()
|
||||
.map(|(var_key, cell)| match var_key {
|
||||
// NOTE: not the intention behind Var::InSitu here but
|
||||
// we can hijack it to store anonymous variables
|
||||
// without creating problems.
|
||||
VarKey::AnonVar(h) => (*cell, VarPtr::from(Var::InSitu(*h))),
|
||||
VarKey::VarPtr(var_ptr) => (*cell, var_ptr.clone()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 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;
|
||||
QueryState {
|
||||
machine: self,
|
||||
term: term_write_result,
|
||||
stub_b,
|
||||
var_names,
|
||||
called: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::machine::{QueryMatch, QueryResolution, Value};
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn programatic_query() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
String::from(
|
||||
r#"
|
||||
triple("a", "p1", "b").
|
||||
triple("a", "p2", "b").
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
let query = String::from(r#"triple("a",P,"b")."#);
|
||||
let output = machine.run_query(query);
|
||||
assert_eq!(
|
||||
output,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"P" => Value::from("p1"),
|
||||
}),
|
||||
QueryMatch::from(btreemap! {
|
||||
"P" => Value::from("p2"),
|
||||
}),
|
||||
]))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("a","p1","b")."#)),
|
||||
Ok(QueryResolution::True)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("x","y","z")."#)),
|
||||
Ok(QueryResolution::False)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn failing_query() {
|
||||
let mut machine = Machine::new_lib();
|
||||
let query = String::from(r#"triple("a",P,"b")."#);
|
||||
let output = machine.run_query(query);
|
||||
assert_eq!(
|
||||
output,
|
||||
Err(String::from(
|
||||
"error existence_error procedure / triple 3 / triple 3"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn complex_results() {
|
||||
let mut machine = Machine::new_lib();
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
:- discontiguous(subject_class/2).
|
||||
:- discontiguous(constructor/2).
|
||||
|
||||
subject_class("Todo", c).
|
||||
constructor(c, '[{action: "addLink", source: "this", predicate: "todo://state", target: "todo://ready"}]').
|
||||
|
||||
subject_class("Recipe", xyz).
|
||||
constructor(xyz, '[{action: "addLink", source: "this", predicate: "recipe://title", target: "literal://string:Meta%20Muffins"}]').
|
||||
"#.to_string());
|
||||
|
||||
let result = machine.run_query(String::from(
|
||||
"subject_class(\"Todo\", C), constructor(C, Actions).",
|
||||
));
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![QueryMatch::from(
|
||||
btreemap! {
|
||||
"C" => Value::Atom("c".into()),
|
||||
"Actions" => Value::Atom("[{action: \"addLink\", source: \"this\", predicate: \"todo://state\", target: \"todo://ready\"}]".into()),
|
||||
}
|
||||
),]))
|
||||
);
|
||||
|
||||
let result = machine.run_query(String::from(
|
||||
"subject_class(\"Recipe\", C), constructor(C, Actions).",
|
||||
));
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![QueryMatch::from(
|
||||
btreemap! {
|
||||
"C" => Value::Atom("xyz".into()),
|
||||
"Actions" => Value::Atom("[{action: \"addLink\", source: \"this\", predicate: \"recipe://title\", target: \"literal://string:Meta%20Muffins\"}]".into()),
|
||||
}
|
||||
),]))
|
||||
);
|
||||
|
||||
let result = machine.run_query(String::from("subject_class(Class, _)."));
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"Class" => Value::String("Todo".into())
|
||||
}),
|
||||
QueryMatch::from(btreemap! {
|
||||
"Class" => Value::String("Recipe".into())
|
||||
}),
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn empty_predicate() {
|
||||
let mut machine = Machine::new_lib();
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
:- discontiguous(subject_class/2).
|
||||
"#
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
let result = machine.run_query(String::from("subject_class(X, _)."));
|
||||
assert_eq!(result, Ok(QueryResolution::False));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn list_results() {
|
||||
let mut machine = Machine::new_lib();
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
list([1,2,3]).
|
||||
"#
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
let result = machine.run_query(String::from("list(X)."));
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![QueryMatch::from(
|
||||
btreemap! {
|
||||
"X" => Value::List(vec![
|
||||
Value::Integer(1.into()),
|
||||
Value::Integer(2.into()),
|
||||
Value::Integer(3.into()),
|
||||
]),
|
||||
}
|
||||
),]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn consult() {
|
||||
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(r#"triple("a",P,"b")."#);
|
||||
let output = machine.run_query(query);
|
||||
assert_eq!(
|
||||
output,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"P" => Value::from("p1"),
|
||||
}),
|
||||
QueryMatch::from(btreemap! {
|
||||
"P" => Value::from("p2"),
|
||||
}),
|
||||
]))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("a","p1","b")."#)),
|
||||
Ok(QueryResolution::True)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("x","y","z")."#)),
|
||||
Ok(QueryResolution::False)
|
||||
);
|
||||
|
||||
machine.consult_module_string(
|
||||
"facts",
|
||||
String::from(
|
||||
r#"
|
||||
triple("a", "new", "b").
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("a","p1","b")."#)),
|
||||
Ok(QueryResolution::False)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("a","new","b")."#)),
|
||||
Ok(QueryResolution::True)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
#[ignore = "uses old flawed interface"]
|
||||
fn integration_test() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
// File with test commands, i.e. program code to consult and queries to run
|
||||
let code = include_str!("./lib_integration_test_commands.txt");
|
||||
|
||||
// Split the code into blocks
|
||||
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
|
||||
let block = block.trim();
|
||||
|
||||
// Skip empty blocks
|
||||
if block.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the block is a query
|
||||
if let Some(query) = block.strip_prefix("query") {
|
||||
// Parse and execute the query
|
||||
let result = machine.run_query(query.to_string());
|
||||
assert!(result.is_ok());
|
||||
|
||||
last_result = Some(result);
|
||||
} else if let Some(code) = block.strip_prefix("consult") {
|
||||
// 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. {i}=======\n{last_result}\n===============");
|
||||
assert_eq!(last_result.to_string(), result.to_string().trim(),)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn findall() {
|
||||
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(r#"findall([Predicate, Target], triple(_,Predicate,Target), Result)."#);
|
||||
let output = machine.run_query(query);
|
||||
assert_eq!(
|
||||
output,
|
||||
Ok(QueryResolution::Matches(vec![QueryMatch::from(
|
||||
btreemap! {
|
||||
"Result" => Value::List(
|
||||
Vec::from([
|
||||
Value::List([Value::from("p1"), Value::from("b")].into()),
|
||||
Value::List([Value::from("p2"), Value::from("b")].into()),
|
||||
])
|
||||
),
|
||||
}
|
||||
),]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
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]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
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]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
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"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn atom_quoting() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
let query = "X = '.'.".into();
|
||||
|
||||
let result = machine.run_query(query);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![QueryMatch::from(
|
||||
btreemap! {
|
||||
"X" => Value::Atom(".".into()),
|
||||
}
|
||||
)]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn rational_number() {
|
||||
use crate::parser::dashu::rational::RBig;
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
let query = "X is 1 rdiv 2.".into();
|
||||
|
||||
let result = machine.run_query(query);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![QueryMatch::from(
|
||||
btreemap! {
|
||||
"X" => Value::Rational(RBig::from_parts(1.into(), 2u32.into())),
|
||||
}
|
||||
)]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn big_integer() {
|
||||
use crate::parser::dashu::integer::IBig;
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
let query = "X is 10^100.".into();
|
||||
|
||||
let result = machine.run_query(query);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![QueryMatch::from(
|
||||
btreemap! {
|
||||
"X" => Value::Integer(IBig::from(10).pow(100)),
|
||||
}
|
||||
)]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn complicated_term() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
let query = "X = a(\"asdf\", [42, 2.54, asdf, a, [a,b|_], Z]).".into();
|
||||
|
||||
let result = machine.run_query(query);
|
||||
|
||||
let expected = Value::Structure(
|
||||
// Composite term
|
||||
"a".into(),
|
||||
vec![
|
||||
Value::String("asdf".into()), // String
|
||||
Value::List(vec![
|
||||
Value::Integer(42.into()), // Fixnum
|
||||
Value::Float(2.54.into()), // Float
|
||||
Value::Atom("asdf".into()), // Atom
|
||||
Value::Atom("a".into()), // Char
|
||||
Value::Structure(
|
||||
// Partial string
|
||||
".".into(),
|
||||
vec![
|
||||
Value::Atom("a".into()),
|
||||
Value::Structure(
|
||||
".".into(),
|
||||
vec![
|
||||
Value::Atom("b".into()),
|
||||
Value::Var("_A".into()), // Anonymous variable
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Value::Var("Z".into()), // Named variable
|
||||
]),
|
||||
],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![QueryMatch::from(
|
||||
btreemap! {
|
||||
"X" => expected,
|
||||
}
|
||||
)]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn issue_2341() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
String::from(
|
||||
r#"
|
||||
male(stephen).
|
||||
parent(albert,edward).
|
||||
father(F,C):-parent(F,C),male(F).
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
let query = String::from(r#"father(F,C)."#);
|
||||
let output = machine.run_query(query);
|
||||
|
||||
assert_eq!(output, Ok(QueryResolution::False));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn query_iterator_determinism() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
{
|
||||
let mut iterator = machine.run_query_iter("X = 1.".into());
|
||||
|
||||
iterator.next();
|
||||
assert_eq!(iterator.next(), None);
|
||||
}
|
||||
|
||||
{
|
||||
let mut iterator = machine.run_query_iter("X = 1 ; false.".into());
|
||||
|
||||
iterator.next();
|
||||
|
||||
assert_eq!(iterator.next(), Some(Ok(QueryResolutionLine::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(), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn query_iterator_backtracking_when_no_variables() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
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(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn differentiate_anonymous_variables() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
let result = machine.run_query("A = [_,_], _B = 1 ; B = [_,_].".into());
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"A" => Value::List(vec![Value::Var("_A".into()), Value::Var("_C".into())]),
|
||||
"_B" => Value::Integer(1.into()),
|
||||
}),
|
||||
QueryMatch::from(btreemap! {
|
||||
"B" => Value::List(vec![Value::Var("_A".into()), Value::Var("_C".into())]),
|
||||
}),
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn order_of_variables_in_binding() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
let result = machine.run_query("X = Y, Z = W.".into());
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![QueryMatch::from(
|
||||
btreemap! {
|
||||
"X" => Value::Var("Y".into()),
|
||||
"Z" => Value::Var("W".into()),
|
||||
}
|
||||
),]))
|
||||
);
|
||||
}
|
||||
}
|
||||
624
src/machine/lib_machine/mod.rs
Normal file
624
src/machine/lib_machine/mod.rs
Normal file
@@ -0,0 +1,624 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::atom_table;
|
||||
use crate::heap_iter::{stackful_post_order_iter, NonListElider};
|
||||
use crate::machine::machine_indices::VarKey;
|
||||
use crate::machine::mock_wam::CompositeOpDir;
|
||||
use crate::machine::{
|
||||
F64Offset, F64Ptr, Fixnum, Number, BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS,
|
||||
};
|
||||
use crate::parser::ast::{Var, VarPtr};
|
||||
use crate::parser::parser::{Parser, Tokens};
|
||||
use crate::read::{write_term_to_heap, TermWriteResult};
|
||||
|
||||
use dashu::{Integer, Rational};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::{streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Represents a leaf answer from a query.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum LeafAnswer {
|
||||
/// A `true` leaf answer.
|
||||
True,
|
||||
/// A `false` leaf answer.
|
||||
///
|
||||
/// This means that there are no more answers for the query.
|
||||
False,
|
||||
/// An exception leaf answer.
|
||||
Exception(Term),
|
||||
/// A leaf answer with bindings.
|
||||
#[non_exhaustive]
|
||||
LeafAnswer {
|
||||
/// The bindings of variables in the query.
|
||||
bindings: BTreeMap<String, Term>,
|
||||
//residual_goals: Vec<Term>,
|
||||
},
|
||||
}
|
||||
|
||||
impl LeafAnswer {
|
||||
/// Creates a leaf answer with no residual goals.
|
||||
pub fn from_bindings<S: Into<String>>(bindings: impl IntoIterator<Item = (S, Term)>) -> Self {
|
||||
LeafAnswer::LeafAnswer {
|
||||
bindings: bindings.into_iter().map(|(k, v)| (k.into(), v)).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a Prolog term.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Term {
|
||||
/// An arbitrary precision integer.
|
||||
Integer(Integer),
|
||||
/// An arbitrary precision rational.
|
||||
Rational(Rational),
|
||||
/// A float.
|
||||
Float(f64),
|
||||
/// A Prolog atom.
|
||||
Atom(String),
|
||||
/// A Prolog string.
|
||||
///
|
||||
/// In particular, this represents Prolog lists of characters.
|
||||
String(String),
|
||||
/// A Prolog list.
|
||||
List(Vec<Term>),
|
||||
/// A Prolog compound term.
|
||||
Compound(String, Vec<Term>),
|
||||
/// A Prolog variable.
|
||||
Var(String),
|
||||
}
|
||||
|
||||
impl Term {
|
||||
/// Creates an integer term.
|
||||
pub fn integer(value: impl Into<Integer>) -> Self {
|
||||
Term::Integer(value.into())
|
||||
}
|
||||
|
||||
/// Creates a rational term.
|
||||
pub fn rational(value: impl Into<Rational>) -> Self {
|
||||
Term::Rational(value.into())
|
||||
}
|
||||
|
||||
/// Creates a float term.
|
||||
pub fn float(value: impl Into<f64>) -> Self {
|
||||
Term::Float(value.into())
|
||||
}
|
||||
|
||||
/// Creates an atom term.
|
||||
pub fn atom(value: impl Into<String>) -> Self {
|
||||
Term::Atom(value.into())
|
||||
}
|
||||
|
||||
/// Creates a string term.
|
||||
///
|
||||
/// In specific, this represents a list of chars in Prolog.
|
||||
pub fn string(value: impl Into<String>) -> Self {
|
||||
Term::String(value.into())
|
||||
}
|
||||
|
||||
/// Creates a list term.
|
||||
pub fn list(value: impl IntoIterator<Item = Term>) -> Self {
|
||||
Term::List(value.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Creates a compound term.
|
||||
pub fn compound(functor: impl Into<String>, args: impl IntoIterator<Item = Term>) -> Self {
|
||||
Term::Compound(functor.into(), args.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Creates a variable.
|
||||
pub fn variable(value: impl Into<String>) -> Self {
|
||||
Term::Var(value.into())
|
||||
}
|
||||
|
||||
/// Creates a conjunction, giving the atom `true` if empty.
|
||||
pub fn conjunction(value: impl IntoIterator<Item = Term>) -> Self {
|
||||
Term::try_conjunction(value).unwrap_or(Term::atom("true"))
|
||||
}
|
||||
|
||||
/// Creates a conjunction, giving `None` if empty.
|
||||
pub fn try_conjunction(value: impl IntoIterator<Item = Term>) -> Option<Self> {
|
||||
let mut iter = value.into_iter();
|
||||
iter.next().map(|first| {
|
||||
Term::try_conjunction(iter)
|
||||
.map(|rest| Term::compound(",", [first.clone(), rest]))
|
||||
.unwrap_or(first)
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a disjunction, giving the atom `false` if empty.
|
||||
pub fn disjunction(value: impl IntoIterator<Item = Term>) -> Self {
|
||||
Term::try_disjunction(value).unwrap_or(Term::atom("false"))
|
||||
}
|
||||
|
||||
/// Creates a disjunction, giving `None` if empty.
|
||||
pub fn try_disjunction(value: impl IntoIterator<Item = Term>) -> Option<Self> {
|
||||
let mut iter = value.into_iter();
|
||||
iter.next().map(|first| {
|
||||
Term::try_disjunction(iter)
|
||||
.map(|rest| Term::compound(";", [first.clone(), rest]))
|
||||
.unwrap_or(first)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 Term {
|
||||
pub(crate) fn from_heapcell(
|
||||
machine: &mut Machine,
|
||||
heap_cell: HeapCellValue,
|
||||
var_names: &mut IndexMap<HeapCellValue, VarPtr>,
|
||||
) -> Self {
|
||||
// Adapted from MachineState::read_term_from_heap
|
||||
let mut term_stack = vec![];
|
||||
let iter = stackful_post_order_iter::<NonListElider>(
|
||||
&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 {
|
||||
Term::Atom(atom) if atom == "[]" => match head {
|
||||
Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
|
||||
// Handle lists of char as strings
|
||||
Term::String(a.to_string())
|
||||
}
|
||||
_ => Term::List(vec![head]),
|
||||
},
|
||||
Term::List(elems) if elems.is_empty() => match head {
|
||||
Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
|
||||
// Handle lists of char as strings
|
||||
Term::String(a.to_string())
|
||||
},
|
||||
_ => Term::List(vec![head]),
|
||||
},
|
||||
Term::List(mut elems) => {
|
||||
elems.insert(0, head);
|
||||
Term::List(elems)
|
||||
},
|
||||
Term::String(mut elems) => match head {
|
||||
Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
|
||||
// Handle lists of char as strings
|
||||
elems.insert(0, a.chars().next().unwrap());
|
||||
Term::String(elems)
|
||||
},
|
||||
_ => {
|
||||
let mut elems: Vec<Term> = elems
|
||||
.chars()
|
||||
.map(|x| Term::Atom(x.into()))
|
||||
.collect();
|
||||
elems.insert(0, head);
|
||||
Term::List(elems)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
Term::Compound(".".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(Term::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(Term::Var(anon_name));
|
||||
},
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::F64, f) => {
|
||||
term_stack.push(Term::Float((*f).into()));
|
||||
}
|
||||
(HeapCellValueTag::Char, c) => {
|
||||
term_stack.push(Term::Atom(c.into()));
|
||||
}
|
||||
(HeapCellValueTag::Fixnum, n) => {
|
||||
term_stack.push(Term::Integer(n.into()));
|
||||
}
|
||||
(HeapCellValueTag::Cons) => {
|
||||
match Number::try_from(addr) {
|
||||
Ok(Number::Integer(i)) => term_stack.push(Term::Integer((*i).clone())),
|
||||
Ok(Number::Rational(r)) => term_stack.push(Term::Rational((*r).clone())),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::CStr, s) => {
|
||||
term_stack.push(Term::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(Term::List(vec![]));
|
||||
} else {
|
||||
term_stack.push(Term::Atom(atom_name));
|
||||
}
|
||||
} else {
|
||||
let subterms = term_stack
|
||||
.drain(term_stack.len() - arity ..)
|
||||
.collect();
|
||||
|
||||
term_stack.push(Term::Compound(name.as_str().to_string(), subterms));
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::PStr, atom) => {
|
||||
let tail = term_stack.pop().unwrap();
|
||||
|
||||
match tail {
|
||||
Term::Atom(atom) => {
|
||||
if atom == "[]" {
|
||||
term_stack.push(Term::String(atom.as_str().to_string()));
|
||||
}
|
||||
},
|
||||
Term::List(l) => {
|
||||
let mut list: Vec<Term> = atom
|
||||
.as_str()
|
||||
.to_string()
|
||||
.chars()
|
||||
.map(|x| Term::Atom(x.to_string()))
|
||||
.collect();
|
||||
list.extend(l.into_iter());
|
||||
term_stack.push(Term::List(list));
|
||||
},
|
||||
_ => {
|
||||
let mut list: Vec<Term> = atom
|
||||
.as_str()
|
||||
.to_string()
|
||||
.chars()
|
||||
.map(|x| Term::Atom(x.to_string()))
|
||||
.collect();
|
||||
|
||||
let mut partial_list = Term::Compound(
|
||||
".".into(),
|
||||
vec![
|
||||
list.pop().unwrap(),
|
||||
tail,
|
||||
],
|
||||
);
|
||||
|
||||
while let Some(last) = list.pop() {
|
||||
partial_list = Term::Compound(
|
||||
".".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()
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator though the leaf answers of a query.
|
||||
pub struct QueryState<'a> {
|
||||
machine: &'a mut Machine,
|
||||
term: TermWriteResult,
|
||||
stub_b: usize,
|
||||
var_names: IndexMap<HeapCellValue, VarPtr>,
|
||||
called: bool,
|
||||
}
|
||||
|
||||
impl Drop for QueryState<'_> {
|
||||
fn drop(&mut self) {
|
||||
// FIXME: This may be wrong if the iterator is not fully consumend, but from testing it
|
||||
// seems fine. Is this really ok?
|
||||
self.machine.trust_me();
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for QueryState<'_> {
|
||||
type Item = Result<LeafAnswer, Term>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let var_names = &mut self.var_names;
|
||||
let term_write_result = &self.term;
|
||||
let machine = &mut self.machine;
|
||||
|
||||
// No more choicepoints, end iteration
|
||||
if self.called && machine.machine_st.b <= self.stub_b {
|
||||
return None;
|
||||
}
|
||||
|
||||
machine.dispatch_loop();
|
||||
|
||||
self.called = true;
|
||||
|
||||
if !machine.machine_st.ball.stub.is_empty() {
|
||||
// NOTE: this means an exception was thrown, at which
|
||||
// point we backtracked to the stub choice point.
|
||||
// this should halt the search for solutions as it
|
||||
// does in the Scryer top-level. the exception term is
|
||||
// contained in self.machine_st.ball.
|
||||
let h = machine.machine_st.heap.len();
|
||||
machine
|
||||
.machine_st
|
||||
.heap
|
||||
.extend(machine.machine_st.ball.stub.clone());
|
||||
let exception_term =
|
||||
Term::from_heapcell(machine, machine.machine_st.heap[h], &mut var_names.clone());
|
||||
|
||||
if let Term::Compound(functor, args) = &exception_term {
|
||||
if functor == "error" && args.len() == 2 {
|
||||
// We have an error
|
||||
return Some(Err(exception_term));
|
||||
}
|
||||
}
|
||||
|
||||
// We have an exception that is not an error
|
||||
return Some(Ok(LeafAnswer::Exception(exception_term)));
|
||||
}
|
||||
|
||||
if machine.machine_st.p == LIB_QUERY_SUCCESS {
|
||||
if term_write_result.var_dict.is_empty() {
|
||||
self.machine.machine_st.backtrack();
|
||||
return Some(Ok(LeafAnswer::True));
|
||||
}
|
||||
} else if machine.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC {
|
||||
return Some(Ok(LeafAnswer::False));
|
||||
}
|
||||
|
||||
let mut bindings: BTreeMap<String, Term> = BTreeMap::new();
|
||||
|
||||
let var_dict = &term_write_result.var_dict;
|
||||
|
||||
for (var_key, term_to_be_printed) in var_dict.iter() {
|
||||
let mut var_name = var_key.to_string();
|
||||
if var_name.starts_with('_') {
|
||||
let should_print = var_names.values().any(|x| match x.borrow().clone() {
|
||||
Var::Named(v) => v == var_name,
|
||||
_ => false,
|
||||
});
|
||||
if !should_print {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut term =
|
||||
Term::from_heapcell(machine, *term_to_be_printed, &mut var_names.clone());
|
||||
|
||||
if let Term::Var(ref term_str) = term {
|
||||
if *term_str == var_name {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Var dict is in the order things appear in the query. If var_name appears
|
||||
// after term in the query, switch their places.
|
||||
let var_name_idx = var_dict
|
||||
.get_index_of(&VarKey::VarPtr(Var::Named(var_name.clone()).into()))
|
||||
.unwrap();
|
||||
let term_idx =
|
||||
var_dict.get_index_of(&VarKey::VarPtr(Var::Named(term_str.clone()).into()));
|
||||
if let Some(idx) = term_idx {
|
||||
if idx < var_name_idx {
|
||||
let new_term = Term::Var(var_name);
|
||||
let new_var_name = term_str.into();
|
||||
term = new_term;
|
||||
var_name = new_var_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bindings.insert(var_name, term);
|
||||
}
|
||||
|
||||
// NOTE: there are outstanding choicepoints, backtrack
|
||||
// through them for further solutions. if
|
||||
// self.machine_st.b == stub_b we've backtracked to the stub
|
||||
// choice point, so we should break.
|
||||
self.machine.machine_st.backtrack();
|
||||
|
||||
Some(Ok(LeafAnswer::LeafAnswer { bindings }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
/// Loads a module into the [`Machine`] from a string.
|
||||
pub fn load_module_string(&mut self, module_name: &str, program: impl Into<String>) {
|
||||
let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena);
|
||||
self.load_file(module_name, stream);
|
||||
}
|
||||
|
||||
/// Consults a module into the [`Machine`] from a string.
|
||||
pub fn consult_module_string(&mut self, module_name: &str, program: impl Into<String>) {
|
||||
let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena);
|
||||
self.machine_st.registers[1] = stream_as_cell!(stream);
|
||||
self.machine_st.registers[2] = atom_as_cell!(&atom_table::AtomTable::build_with(
|
||||
&self.machine_st.atom_tbl,
|
||||
module_name
|
||||
));
|
||||
|
||||
self.run_module_predicate(atom!("loader"), (atom!("consult_stream"), 2));
|
||||
}
|
||||
|
||||
fn allocate_stub_choice_point(&mut self) {
|
||||
// NOTE: create a choice point to terminate the dispatch_loop
|
||||
// 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(stub_b);
|
||||
|
||||
or_frame.prelude.num_cells = 0;
|
||||
or_frame.prelude.e = 0;
|
||||
or_frame.prelude.cp = 0;
|
||||
or_frame.prelude.b = 0;
|
||||
or_frame.prelude.bp = BREAK_FROM_DISPATCH_LOOP_LOC;
|
||||
or_frame.prelude.boip = 0;
|
||||
or_frame.prelude.biip = 0;
|
||||
or_frame.prelude.tr = 0;
|
||||
or_frame.prelude.h = 0;
|
||||
or_frame.prelude.b0 = 0;
|
||||
or_frame.prelude.attr_var_queue_len = 0;
|
||||
|
||||
self.machine_st.b = stub_b;
|
||||
self.machine_st.hb = self.machine_st.heap.len();
|
||||
self.machine_st.block = stub_b;
|
||||
}
|
||||
|
||||
/// Runs a query.
|
||||
pub fn run_query(&mut self, query: impl Into<String>) -> QueryState {
|
||||
let mut parser = Parser::new(
|
||||
Stream::from_owned_string(query.into(), &mut self.machine_st.arena),
|
||||
&mut self.machine_st,
|
||||
);
|
||||
let op_dir = CompositeOpDir::new(&self.indices.op_dir, None);
|
||||
let term = parser
|
||||
.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");
|
||||
|
||||
let var_names: IndexMap<_, _> = term_write_result
|
||||
.var_dict
|
||||
.iter()
|
||||
.map(|(var_key, cell)| match var_key {
|
||||
// NOTE: not the intention behind Var::InSitu here but
|
||||
// we can hijack it to store anonymous variables
|
||||
// without creating problems.
|
||||
VarKey::AnonVar(h) => (*cell, VarPtr::from(Var::InSitu(*h))),
|
||||
VarKey::VarPtr(var_ptr) => (*cell, var_ptr.clone()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 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;
|
||||
QueryState {
|
||||
machine: self,
|
||||
term: term_write_result,
|
||||
stub_b,
|
||||
var_names,
|
||||
called: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
610
src/machine/lib_machine/tests.rs
Normal file
610
src/machine/lib_machine/tests.rs
Normal file
@@ -0,0 +1,610 @@
|
||||
use super::*;
|
||||
use crate::MachineBuilder;
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn programatic_query() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
String::from(
|
||||
r#"
|
||||
triple("a", "p1", "b").
|
||||
triple("a", "p2", "b").
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
let query = r#"triple("a",P,"b")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[
|
||||
LeafAnswer::from_bindings([("P", Term::string("p1")),]),
|
||||
LeafAnswer::from_bindings([("P", Term::string("p2")),]),
|
||||
],
|
||||
);
|
||||
|
||||
let query = r#"triple("a","p1","b")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
|
||||
assert_eq!(complete_answer, [LeafAnswer::True],);
|
||||
|
||||
let query = r#"triple("x","y","z")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
|
||||
assert_eq!(complete_answer, [LeafAnswer::False],);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn failing_query() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
let query = r#"triple("a",P,"b")."#;
|
||||
let complete_answer: Result<Vec<_>, _> = machine.run_query(query).collect();
|
||||
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
Err(Term::compound(
|
||||
"error",
|
||||
[
|
||||
Term::compound(
|
||||
"existence_error",
|
||||
[
|
||||
Term::atom("procedure"),
|
||||
Term::compound("/", [Term::atom("triple"), Term::integer(3)]),
|
||||
]
|
||||
),
|
||||
Term::compound("/", [Term::atom("triple"), Term::integer(3)]),
|
||||
],
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn complex_results() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
:- discontiguous(subject_class/2).
|
||||
:- discontiguous(constructor/2).
|
||||
|
||||
subject_class("Todo", c).
|
||||
constructor(c, '[{action: "addLink", source: "this", predicate: "todo://state", target: "todo://ready"}]').
|
||||
|
||||
subject_class("Recipe", xyz).
|
||||
constructor(xyz, '[{action: "addLink", source: "this", predicate: "recipe://title", target: "literal://string:Meta%20Muffins"}]').
|
||||
"#,
|
||||
);
|
||||
|
||||
let complete_answer: Vec<_> = machine
|
||||
.run_query(r#"subject_class("Todo", C), constructor(C, Actions)."#)
|
||||
.collect::<Result<_, _>>()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[LeafAnswer::from_bindings([
|
||||
("C", Term::atom("c")),
|
||||
(
|
||||
"Actions",
|
||||
Term::atom(
|
||||
r#"[{action: "addLink", source: "this", predicate: "todo://state", target: "todo://ready"}]"#
|
||||
)
|
||||
),
|
||||
])],
|
||||
);
|
||||
|
||||
let complete_answer: Vec<_> = machine
|
||||
.run_query(r#"subject_class("Recipe", C), constructor(C, Actions)."#)
|
||||
.collect::<Result<_, _>>()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[LeafAnswer::from_bindings([
|
||||
("C", Term::atom("xyz")),
|
||||
(
|
||||
"Actions",
|
||||
Term::atom(
|
||||
r#"[{action: "addLink", source: "this", predicate: "recipe://title", target: "literal://string:Meta%20Muffins"}]"#
|
||||
)
|
||||
),
|
||||
])],
|
||||
);
|
||||
|
||||
let complete_answer: Vec<_> = machine
|
||||
.run_query("subject_class(Class, _).")
|
||||
.collect::<Result<_, _>>()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[
|
||||
LeafAnswer::from_bindings([("Class", Term::string("Todo"))]),
|
||||
LeafAnswer::from_bindings([("Class", Term::string("Recipe"))]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn empty_predicate() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
:- discontiguous(subject_class/2).
|
||||
"#,
|
||||
);
|
||||
|
||||
let complete_answer: Vec<_> = machine
|
||||
.run_query("subject_class(X, _).")
|
||||
.collect::<Result<_, _>>()
|
||||
.unwrap();
|
||||
assert_eq!(complete_answer, [LeafAnswer::False]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn list_results() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
list([1,2,3]).
|
||||
"#,
|
||||
);
|
||||
|
||||
let complete_answer: Vec<_> = machine
|
||||
.run_query("list(X).")
|
||||
.collect::<Result<_, _>>()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[LeafAnswer::from_bindings([(
|
||||
"X",
|
||||
Term::list([Term::integer(1), Term::integer(2), Term::integer(3)]),
|
||||
)])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn consult() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
machine.consult_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
triple("a", "p1", "b").
|
||||
triple("a", "p2", "b").
|
||||
"#,
|
||||
);
|
||||
|
||||
let query = r#"triple("a",P,"b")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[
|
||||
LeafAnswer::from_bindings([("P", Term::string("p1"))]),
|
||||
LeafAnswer::from_bindings([("P", Term::string("p2"))]),
|
||||
],
|
||||
);
|
||||
|
||||
let query = r#"triple("a","p1","b")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(complete_answer, [LeafAnswer::True],);
|
||||
|
||||
let query = r#"triple("x","y","z")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(complete_answer, [LeafAnswer::False],);
|
||||
|
||||
machine.consult_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
triple("a", "new", "b").
|
||||
"#,
|
||||
);
|
||||
|
||||
let query = r#"triple("a","p1","b")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(complete_answer, [LeafAnswer::False],);
|
||||
|
||||
let query = r#"triple("a","new","b")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(complete_answer, [LeafAnswer::True]);
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
#[ignore = "uses old flawed interface"]
|
||||
fn integration_test() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
// File with test commands, i.e. program code to consult and queries to run
|
||||
let code = include_str!("./lib_integration_test_commands.txt");
|
||||
|
||||
// Split the code into blocks
|
||||
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
|
||||
let block = block.trim();
|
||||
|
||||
// Skip empty blocks
|
||||
if block.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the block is a query
|
||||
if let Some(query) = block.strip_prefix("query") {
|
||||
// Parse and execute the query
|
||||
let result = machine.run_query(query.to_string());
|
||||
assert!(result.is_ok());
|
||||
|
||||
last_result = Some(result);
|
||||
} else if let Some(code) = block.strip_prefix("consult") {
|
||||
// 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. {i}=======\n{last_result}\n===============");
|
||||
assert_eq!(last_result.to_string(), result.to_string().trim(),)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn findall() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
machine.consult_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
triple("a", "p1", "b").
|
||||
triple("a", "p2", "b").
|
||||
"#,
|
||||
);
|
||||
|
||||
let query = r#"findall([Predicate, Target], triple(_,Predicate,Target), Result)."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[LeafAnswer::from_bindings([(
|
||||
"Result",
|
||||
Term::list([
|
||||
Term::list([Term::string("p1"), Term::string("b")]),
|
||||
Term::list([Term::string("p2"), Term::string("b")]),
|
||||
])
|
||||
)])]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn dont_return_partial_matches() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
machine.consult_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
:- discontiguous(property_resolve/2).
|
||||
subject_class("Todo", c).
|
||||
"#,
|
||||
);
|
||||
|
||||
let query = r#"property_resolve(C, "isLiked"), subject_class("Todo", C)."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(complete_answer, [LeafAnswer::False]);
|
||||
|
||||
let query = r#"subject_class("Todo", C), property_resolve(C, "isLiked")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(complete_answer, [LeafAnswer::False]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn dont_return_partial_matches_without_discountiguous() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
machine.consult_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
a("true for a").
|
||||
b("true for b").
|
||||
"#,
|
||||
);
|
||||
|
||||
let query = r#"a("true for a")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(complete_answer, [LeafAnswer::True]);
|
||||
|
||||
let query = r#"a("true for a"), b("true for b")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(complete_answer, [LeafAnswer::True]);
|
||||
|
||||
let query = r#"a("true for b"), b("true for b")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(complete_answer, [LeafAnswer::False]);
|
||||
|
||||
let query = r#"a("true for a"), b("true for a")."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
assert_eq!(complete_answer, [LeafAnswer::False]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn non_existent_predicate_should_not_cause_panic_when_other_predicates_are_defined() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
machine.consult_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
triple("a", "p1", "b").
|
||||
triple("a", "p2", "b").
|
||||
"#,
|
||||
);
|
||||
|
||||
let query = r#"non_existent_predicate("a","p1","b")."#;
|
||||
let complete_answer: Result<Vec<_>, _> = machine.run_query(query).collect();
|
||||
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
Err(Term::compound(
|
||||
"error",
|
||||
[
|
||||
Term::compound(
|
||||
"existence_error",
|
||||
[
|
||||
Term::atom("procedure"),
|
||||
Term::compound(
|
||||
"/",
|
||||
[Term::atom("non_existent_predicate"), Term::integer(3)],
|
||||
),
|
||||
],
|
||||
),
|
||||
Term::compound(
|
||||
"/",
|
||||
[Term::atom("non_existent_predicate"), Term::integer(3)]
|
||||
),
|
||||
],
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn atom_quoting() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
let query = "X = '.'.";
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[LeafAnswer::from_bindings([("X", Term::atom("."))])]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn rational_number() {
|
||||
use crate::parser::dashu::rational::RBig;
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
let query = "X is 1 rdiv 2.";
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[LeafAnswer::from_bindings([(
|
||||
"X",
|
||||
Term::rational(RBig::from_parts(1.into(), 2u32.into()))
|
||||
)])]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn big_integer() {
|
||||
use crate::parser::dashu::integer::IBig;
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
let query = "X is 10^100.";
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[LeafAnswer::from_bindings([(
|
||||
"X",
|
||||
Term::integer(IBig::from(10).pow(100))
|
||||
)])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn complicated_term() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
let query = r#"X = a("asdf", [42, 2.54, asdf, a, [a,b|_], Z])."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
|
||||
let expected = Term::Compound(
|
||||
// Compound term
|
||||
"a".into(),
|
||||
vec![
|
||||
Term::String("asdf".into()), // String
|
||||
Term::List(vec![
|
||||
Term::Integer(42.into()), // Fixnum
|
||||
Term::Float(2.54), // Float
|
||||
Term::Atom("asdf".into()), // Atom
|
||||
Term::Atom("a".into()), // Char
|
||||
Term::Compound(
|
||||
// Partial string
|
||||
".".into(),
|
||||
vec![
|
||||
Term::Atom("a".into()),
|
||||
Term::Compound(
|
||||
".".into(),
|
||||
vec![
|
||||
Term::Atom("b".into()),
|
||||
Term::Var("_A".into()), // Anonymous variable
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Term::Var("Z".into()), // Named variable
|
||||
]),
|
||||
],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[LeafAnswer::from_bindings([("X", expected)])]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||
fn issue_2341() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
male(stephen).
|
||||
parent(albert,edward).
|
||||
father(F,C):-parent(F,C),male(F).
|
||||
"#,
|
||||
);
|
||||
|
||||
let query = r#"father(F,C)."#;
|
||||
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
|
||||
|
||||
assert_eq!(complete_answer, [LeafAnswer::False]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn query_iterator_determinism() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
{
|
||||
let mut iterator = machine.run_query("X = 1.");
|
||||
|
||||
iterator.next();
|
||||
assert_eq!(iterator.next(), None);
|
||||
}
|
||||
|
||||
{
|
||||
let mut iterator = machine.run_query("X = 1 ; false.");
|
||||
|
||||
iterator.next();
|
||||
|
||||
assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False)));
|
||||
assert_eq!(iterator.next(), None);
|
||||
}
|
||||
|
||||
{
|
||||
let mut iterator = machine.run_query("false.");
|
||||
|
||||
assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False)));
|
||||
assert_eq!(iterator.next(), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn query_iterator_backtracking_when_no_variables() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
let mut iterator = machine.run_query("true;false.");
|
||||
|
||||
assert_eq!(iterator.next(), Some(Ok(LeafAnswer::True)));
|
||||
assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False)));
|
||||
assert_eq!(iterator.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn differentiate_anonymous_variables() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
let complete_answer: Vec<_> = machine
|
||||
.run_query("A = [_,_], _B = 1 ; B = [_,_].")
|
||||
.collect::<Result<_, _>>()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[
|
||||
LeafAnswer::from_bindings([
|
||||
(
|
||||
"A",
|
||||
Term::list([Term::variable("_A"), Term::variable("_C")])
|
||||
),
|
||||
("_B", Term::integer(1)),
|
||||
]),
|
||||
LeafAnswer::from_bindings([(
|
||||
"B",
|
||||
Term::list([Term::variable("_A"), Term::variable("_C")])
|
||||
),]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn order_of_variables_in_binding() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
let complete_answer: Vec<_> = machine
|
||||
.run_query("X = Y, Z = W.")
|
||||
.collect::<Result<_, _>>()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[LeafAnswer::from_bindings([
|
||||
("X", Term::variable("Y")),
|
||||
("Z", Term::variable("W")),
|
||||
])]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn errors_and_exceptions() {
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
|
||||
let complete_answer: Vec<_> = machine.run_query("functor(_,_,_).").collect();
|
||||
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[Err(Term::compound(
|
||||
"error",
|
||||
[
|
||||
Term::atom("instantiation_error"),
|
||||
Term::compound("/", [Term::atom("functor"), Term::integer(3)]),
|
||||
],
|
||||
))]
|
||||
);
|
||||
|
||||
let complete_answer: Vec<_> = machine.run_query("throw(a).").collect();
|
||||
|
||||
assert_eq!(
|
||||
complete_answer,
|
||||
[Ok(LeafAnswer::Exception(Term::atom("a")))]
|
||||
);
|
||||
}
|
||||
@@ -231,10 +231,7 @@ pub(crate) fn parse_and_write_parsed_term_to_heap(
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
pub fn with_test_streams() -> Self {
|
||||
Machine::new(MachineConfig::in_memory())
|
||||
}
|
||||
|
||||
/// For use in tests.
|
||||
pub fn test_load_file(&mut self, file: &str) -> Vec<u8> {
|
||||
let stream = Stream::from_owned_string(
|
||||
std::fs::read_to_string(AsRef::<std::path::Path>::as_ref(file)).unwrap(),
|
||||
@@ -245,6 +242,7 @@ impl Machine {
|
||||
self.user_output.bytes().map(|b| b.unwrap()).collect()
|
||||
}
|
||||
|
||||
/// For use in tests.
|
||||
pub fn test_load_string(&mut self, code: &str) -> Vec<u8> {
|
||||
let stream = Stream::from_owned_string(code.to_owned(), &mut self.machine_st.arena);
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ pub mod machine_indices;
|
||||
pub mod machine_state;
|
||||
pub mod machine_state_impl;
|
||||
pub mod mock_wam;
|
||||
pub mod parsed_results;
|
||||
pub mod partial_string;
|
||||
pub mod preprocessor;
|
||||
pub mod stack;
|
||||
@@ -55,7 +54,7 @@ use lazy_static::lazy_static;
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::Ordering;
|
||||
use std::env;
|
||||
use std::io::Read;
|
||||
@@ -63,13 +62,13 @@ use std::path::PathBuf;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use self::config::MachineConfig;
|
||||
use self::parsed_results::*;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref INTERRUPT: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
|
||||
/// An instance of Scryer Prolog.
|
||||
///
|
||||
/// Created with [`MachineBuilder::build`](crate::machine::config::MachineBuilder::build).
|
||||
#[derive(Debug)]
|
||||
pub struct Machine {
|
||||
pub(super) machine_st: MachineState,
|
||||
@@ -262,6 +261,7 @@ impl Machine {
|
||||
)
|
||||
}
|
||||
|
||||
/// Gets the current inference count.
|
||||
pub fn get_inference_count(&mut self) -> u64 {
|
||||
self.machine_st
|
||||
.cwil
|
||||
@@ -298,13 +298,16 @@ impl Machine {
|
||||
self.run_module_predicate(atom!("loader"), (atom!("file_load"), 2));
|
||||
}
|
||||
|
||||
fn load_top_level(&mut self, program: &'static str) {
|
||||
fn load_top_level(&mut self, program: Cow<'static, str>) {
|
||||
let mut path_buf = current_dir();
|
||||
|
||||
path_buf.push("src/toplevel.pl");
|
||||
|
||||
let path = path_buf.to_str().unwrap();
|
||||
let toplevel_stream = Stream::from_static_string(program, &mut self.machine_st.arena);
|
||||
let toplevel_stream = match program {
|
||||
Cow::Borrowed(s) => Stream::from_static_string(s, &mut self.machine_st.arena),
|
||||
Cow::Owned(s) => Stream::from_owned_string(s, &mut self.machine_st.arena),
|
||||
};
|
||||
|
||||
self.load_file(path, toplevel_stream);
|
||||
|
||||
@@ -480,114 +483,6 @@ impl Machine {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new(config: MachineConfig) -> Self {
|
||||
let args = MachineArgs::new();
|
||||
let mut machine_st = MachineState::new();
|
||||
|
||||
let (user_input, user_output, user_error) = match config.streams {
|
||||
config::StreamConfig::Stdio => (
|
||||
Stream::stdin(&mut machine_st.arena, args.add_history),
|
||||
Stream::stdout(&mut machine_st.arena),
|
||||
Stream::stderr(&mut machine_st.arena),
|
||||
),
|
||||
config::StreamConfig::Memory => (
|
||||
Stream::Null(StreamOptions::default()),
|
||||
Stream::from_owned_string("".to_owned(), &mut machine_st.arena),
|
||||
Stream::stderr(&mut machine_st.arena),
|
||||
),
|
||||
};
|
||||
|
||||
let mut wam = Machine {
|
||||
machine_st,
|
||||
indices: IndexStore::new(),
|
||||
code: vec![],
|
||||
user_input,
|
||||
user_output,
|
||||
user_error,
|
||||
load_contexts: vec![],
|
||||
#[cfg(feature = "ffi")]
|
||||
foreign_function_table: Default::default(),
|
||||
rng: StdRng::from_entropy(),
|
||||
};
|
||||
|
||||
let mut lib_path = current_dir();
|
||||
|
||||
lib_path.pop();
|
||||
lib_path.push("lib");
|
||||
|
||||
wam.add_impls_to_indices();
|
||||
|
||||
bootstrapping_compile(
|
||||
Stream::from_static_string(
|
||||
libraries::get("ops_and_meta_predicates")
|
||||
.expect("library ops_and_meta_predicates should exist"),
|
||||
&mut wam.machine_st.arena,
|
||||
),
|
||||
&mut wam,
|
||||
ListingSource::from_file_and_path(
|
||||
atom!("ops_and_meta_predicates.pl"),
|
||||
lib_path.clone(),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
bootstrapping_compile(
|
||||
Stream::from_static_string(
|
||||
libraries::get("builtins").expect("library builtins should exist"),
|
||||
&mut wam.machine_st.arena,
|
||||
),
|
||||
&mut wam,
|
||||
ListingSource::from_file_and_path(atom!("builtins.pl"), lib_path.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(builtins) = wam.indices.modules.get_mut(&atom!("builtins")) {
|
||||
load_module(
|
||||
&mut wam.machine_st,
|
||||
&mut wam.indices.code_dir,
|
||||
&mut wam.indices.op_dir,
|
||||
&mut wam.indices.meta_predicates,
|
||||
&CompilationTarget::User,
|
||||
builtins,
|
||||
);
|
||||
|
||||
import_builtin_impls(&wam.indices.code_dir, builtins);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
lib_path.pop(); // remove the "lib" at the end
|
||||
|
||||
bootstrapping_compile(
|
||||
Stream::from_static_string(include_str!("../loader.pl"), &mut wam.machine_st.arena),
|
||||
&mut wam,
|
||||
ListingSource::from_file_and_path(atom!("loader.pl"), lib_path.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
wam.configure_modules();
|
||||
|
||||
if let Some(loader) = wam.indices.modules.get(&atom!("loader")) {
|
||||
load_module(
|
||||
&mut wam.machine_st,
|
||||
&mut wam.indices.code_dir,
|
||||
&mut wam.indices.op_dir,
|
||||
&mut wam.indices.meta_predicates,
|
||||
&CompilationTarget::User,
|
||||
loader,
|
||||
);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
wam.load_special_forms();
|
||||
wam.load_top_level(config.toplevel);
|
||||
wam.configure_streams();
|
||||
|
||||
wam
|
||||
}
|
||||
|
||||
pub(crate) fn configure_streams(&mut self) {
|
||||
self.user_input
|
||||
.options_mut()
|
||||
|
||||
@@ -1,643 +0,0 @@
|
||||
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<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: &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::<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)
|
||||
}
|
||||
}
|
||||
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<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, Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum QueryResolutionLine {
|
||||
True,
|
||||
False,
|
||||
Match(BTreeMap<String, Value>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Value {
|
||||
Integer(Integer),
|
||||
Rational(Rational),
|
||||
Float(OrderedFloat<f64>),
|
||||
Atom(String),
|
||||
String(String),
|
||||
List(Vec<Value>),
|
||||
Structure(String, Vec<Value>),
|
||||
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<HeapCellValue, VarPtr>,
|
||||
) -> Self {
|
||||
// Adapted from MachineState::read_term_from_heap
|
||||
let mut term_stack = vec![];
|
||||
let iter = stackful_post_order_iter::<NonListElider>(
|
||||
&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::<Vec<_>>().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::<Vec<_>>().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::<Vec<_>>().len() == 1 => {
|
||||
// Handle lists of char as strings
|
||||
elems.insert(0, a.chars().next().unwrap());
|
||||
Value::String(elems)
|
||||
},
|
||||
_ => {
|
||||
let mut elems: Vec<Value> = 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();
|
||||
|
||||
match tail {
|
||||
Value::Atom(atom) => {
|
||||
if atom == "[]" {
|
||||
term_stack.push(Value::String(atom.as_str().to_string()));
|
||||
}
|
||||
},
|
||||
Value::List(l) => {
|
||||
let mut list: Vec<Value> = atom
|
||||
.as_str()
|
||||
.to_string()
|
||||
.chars()
|
||||
.map(|x| Value::Atom(x.to_string()))
|
||||
.collect();
|
||||
list.extend(l.into_iter());
|
||||
term_stack.push(Value::List(list));
|
||||
},
|
||||
_ => {
|
||||
let mut list: Vec<Value> = 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<BTreeMap<&str, Value>> for QueryMatch {
|
||||
fn from(bindings: BTreeMap<&str, Value>) -> Self {
|
||||
QueryMatch {
|
||||
bindings: bindings
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v))
|
||||
.collect::<BTreeMap<_, _>>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BTreeMap<String, Value>> for QueryMatch {
|
||||
fn from(bindings: BTreeMap<String, Value>) -> 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, Value), ()> {
|
||||
let key = k.to_string();
|
||||
let value = v.to_string();
|
||||
Ok((key, Value::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 Value {
|
||||
type Error = ();
|
||||
fn try_from(string: String) -> Result<Self, Self::Error> {
|
||||
let trimmed = string.trim();
|
||||
|
||||
if let Ok(float_value) = string.parse::<f64>() {
|
||||
Ok(Value::Float(OrderedFloat(float_value)))
|
||||
} else if let Ok(int_value) = string.parse::<i128>() {
|
||||
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::<Result<Vec<_>, _>>()?;
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@ impl Expectable for &[u8] {
|
||||
/// Tests whether the file can be successfully loaded
|
||||
/// and produces the expected output during it
|
||||
pub(crate) fn load_module_test<T: Expectable>(file: &str, expected: T) {
|
||||
use scryer_prolog::Machine;
|
||||
use scryer_prolog::MachineBuilder;
|
||||
|
||||
let mut wam = Machine::with_test_streams();
|
||||
let mut wam = MachineBuilder::default().build();
|
||||
expected.assert_eq(wam.test_load_file(file).as_slice());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user