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 {
|
mod iai {
|
||||||
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
|
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
|
||||||
|
|
||||||
use scryer_prolog::QueryResolution;
|
use scryer_prolog::LeafAnswer;
|
||||||
|
|
||||||
use super::setup;
|
use super::setup;
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ mod iai {
|
|||||||
#[bench::count_edges(setup::prolog_benches()["count_edges"].setup())]
|
#[bench::count_edges(setup::prolog_benches()["count_edges"].setup())]
|
||||||
#[bench::numlist(setup::prolog_benches()["numlist"].setup())]
|
#[bench::numlist(setup::prolog_benches()["numlist"].setup())]
|
||||||
#[bench::csv_codename(setup::prolog_benches()["csv_codename"].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()
|
run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{collections::BTreeMap, fs, path::Path};
|
use std::{collections::BTreeMap, fs, path::Path};
|
||||||
|
|
||||||
use maplit::btreemap;
|
use maplit::btreemap;
|
||||||
use scryer_prolog::{Machine, QueryResolution, Value};
|
use scryer_prolog::{LeafAnswer, Machine, MachineBuilder, Term};
|
||||||
|
|
||||||
pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
|
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
|
"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
|
"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,
|
Strategy::Reuse,
|
||||||
btreemap! { "Count" => Value::Integer(2869176.into()) },
|
btreemap! { "Count" => Term::integer(2869176) },
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"numlist",
|
"numlist",
|
||||||
"benches/numlist.pl",
|
"benches/numlist.pl",
|
||||||
"run_numlist(1000000, Head).",
|
"run_numlist(1000000, Head).",
|
||||||
Strategy::Reuse,
|
Strategy::Reuse,
|
||||||
btreemap! { "Head" => Value::Integer(1.into())},
|
btreemap! { "Head" => Term::integer(1) },
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"csv_codename",
|
"csv_codename",
|
||||||
"benches/csv.pl",
|
"benches/csv.pl",
|
||||||
"get_codename(\"0020\",Name).",
|
"get_codename(\"0020\",Name).",
|
||||||
Strategy::Reuse,
|
Strategy::Reuse,
|
||||||
btreemap! { "Name" => Value::String("SPACE".into())},
|
btreemap! { "Name" => Term::string("SPACE") },
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
.map(|b| {
|
.map(|b| {
|
||||||
@@ -54,7 +54,7 @@ pub struct PrologBenchmark {
|
|||||||
pub filename: &'static str,
|
pub filename: &'static str,
|
||||||
pub query: &'static str,
|
pub query: &'static str,
|
||||||
pub strategy: Strategy,
|
pub strategy: Strategy,
|
||||||
pub bindings: BTreeMap<&'static str, Value>,
|
pub bindings: BTreeMap<&'static str, Term>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrologBenchmark {
|
impl PrologBenchmark {
|
||||||
@@ -64,28 +64,34 @@ impl PrologBenchmark {
|
|||||||
.file_stem()
|
.file_stem()
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut machine = Machine::new_lib();
|
let mut machine = MachineBuilder::default().build();
|
||||||
machine.load_module_string(module_name, program);
|
machine.load_module_string(module_name, program);
|
||||||
machine
|
machine
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
#[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 mut machine = self.make_machine();
|
||||||
let query = self.query;
|
let query = self.query;
|
||||||
move || {
|
move || {
|
||||||
use criterion::black_box;
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validate_benchmarks() {
|
fn validate_benchmarks() {
|
||||||
use super::prolog_benches;
|
use super::prolog_benches;
|
||||||
use scryer_prolog::{QueryMatch, QueryResolution};
|
use scryer_prolog::LeafAnswer;
|
||||||
use std::{fmt::Write, fs};
|
use std::{fmt::Write, fs};
|
||||||
|
|
||||||
struct BenchResult {
|
struct BenchResult {
|
||||||
@@ -100,10 +106,13 @@ mod test {
|
|||||||
let mut machine = r.make_machine();
|
let mut machine = r.make_machine();
|
||||||
let setup_inference_count = machine.get_inference_count();
|
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 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);
|
assert_eq!(result, expected, "validating benchmark {}", r.name);
|
||||||
|
|
||||||
results.push(BenchResult {
|
results.push(BenchResult {
|
||||||
|
|||||||
16
src/lib.rs
16
src/lib.rs
@@ -1,10 +1,9 @@
|
|||||||
|
//! A free software ISO Prolog system.
|
||||||
#![recursion_limit = "4112"]
|
#![recursion_limit = "4112"]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate static_assertions;
|
extern crate static_assertions;
|
||||||
#[cfg(test)]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate maplit;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub(crate) mod macros;
|
pub(crate) mod macros;
|
||||||
@@ -46,9 +45,9 @@ use wasm_bindgen::prelude::*;
|
|||||||
// Re-exports
|
// Re-exports
|
||||||
pub use machine::config::*;
|
pub use machine::config::*;
|
||||||
pub use machine::lib_machine::*;
|
pub use machine::lib_machine::*;
|
||||||
pub use machine::parsed_results::*;
|
|
||||||
pub use machine::Machine;
|
pub use machine::Machine;
|
||||||
|
|
||||||
|
/// Eval a source file in Wasm.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn eval_code(s: &str) -> String {
|
pub fn eval_code(s: &str) -> String {
|
||||||
@@ -56,14 +55,15 @@ pub fn eval_code(s: &str) -> String {
|
|||||||
|
|
||||||
console_error_panic_hook::set_once();
|
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);
|
let bytes = wam.test_load_string(s);
|
||||||
String::from_utf8_lossy(&bytes).to_string()
|
String::from_utf8_lossy(&bytes).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The entry point for the Scryer Prolog CLI.
|
||||||
pub fn run_binary() -> std::process::ExitCode {
|
pub fn run_binary() -> std::process::ExitCode {
|
||||||
use crate::atom_table::Atom;
|
use crate::atom_table::Atom;
|
||||||
use crate::machine::{Machine, INTERRUPT};
|
use crate::machine::INTERRUPT;
|
||||||
|
|
||||||
#[cfg(feature = "repl")]
|
#[cfg(feature = "repl")]
|
||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
@@ -84,7 +84,9 @@ pub fn run_binary() -> std::process::ExitCode {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
runtime.block_on(async move {
|
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))
|
wam.run_module_predicate(atom!("$toplevel"), (atom!("$repl"), 0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,189 @@
|
|||||||
pub struct MachineConfig {
|
use std::borrow::Cow;
|
||||||
pub streams: StreamConfig,
|
|
||||||
pub toplevel: &'static str,
|
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,
|
Stdio,
|
||||||
|
#[default]
|
||||||
Memory,
|
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 {
|
fn default() -> Self {
|
||||||
MachineConfig {
|
MachineBuilder {
|
||||||
streams: StreamConfig::Stdio,
|
streams: Default::default(),
|
||||||
toplevel: include_str!("../toplevel.pl"),
|
toplevel: default_toplevel().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MachineConfig {
|
impl MachineBuilder {
|
||||||
pub fn in_memory() -> Self {
|
/// Creates a default configuration.
|
||||||
MachineConfig {
|
pub fn new() -> Self {
|
||||||
streams: StreamConfig::Memory,
|
Default::default()
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_toplevel(mut self, toplevel: &'static str) -> Self {
|
/// Uses the given `crate::StreamConfig` in this configuration.
|
||||||
self.toplevel = toplevel;
|
pub fn with_streams(mut self, streams: StreamConfig) -> Self {
|
||||||
|
self.streams = streams;
|
||||||
self
|
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 {
|
impl Machine {
|
||||||
pub fn with_test_streams() -> Self {
|
/// For use in tests.
|
||||||
Machine::new(MachineConfig::in_memory())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn test_load_file(&mut self, file: &str) -> Vec<u8> {
|
pub fn test_load_file(&mut self, file: &str) -> Vec<u8> {
|
||||||
let stream = Stream::from_owned_string(
|
let stream = Stream::from_owned_string(
|
||||||
std::fs::read_to_string(AsRef::<std::path::Path>::as_ref(file)).unwrap(),
|
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()
|
self.user_output.bytes().map(|b| b.unwrap()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For use in tests.
|
||||||
pub fn test_load_string(&mut self, code: &str) -> Vec<u8> {
|
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);
|
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;
|
||||||
pub mod machine_state_impl;
|
pub mod machine_state_impl;
|
||||||
pub mod mock_wam;
|
pub mod mock_wam;
|
||||||
pub mod parsed_results;
|
|
||||||
pub mod partial_string;
|
pub mod partial_string;
|
||||||
pub mod preprocessor;
|
pub mod preprocessor;
|
||||||
pub mod stack;
|
pub mod stack;
|
||||||
@@ -55,7 +54,7 @@ use lazy_static::lazy_static;
|
|||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
|
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
use rand::SeedableRng;
|
use std::borrow::Cow;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
@@ -63,13 +62,13 @@ use std::path::PathBuf;
|
|||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use self::config::MachineConfig;
|
|
||||||
use self::parsed_results::*;
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref INTERRUPT: AtomicBool = AtomicBool::new(false);
|
pub static ref INTERRUPT: AtomicBool = AtomicBool::new(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An instance of Scryer Prolog.
|
||||||
|
///
|
||||||
|
/// Created with [`MachineBuilder::build`](crate::machine::config::MachineBuilder::build).
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Machine {
|
pub struct Machine {
|
||||||
pub(super) machine_st: MachineState,
|
pub(super) machine_st: MachineState,
|
||||||
@@ -262,6 +261,7 @@ impl Machine {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the current inference count.
|
||||||
pub fn get_inference_count(&mut self) -> u64 {
|
pub fn get_inference_count(&mut self) -> u64 {
|
||||||
self.machine_st
|
self.machine_st
|
||||||
.cwil
|
.cwil
|
||||||
@@ -298,13 +298,16 @@ impl Machine {
|
|||||||
self.run_module_predicate(atom!("loader"), (atom!("file_load"), 2));
|
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();
|
let mut path_buf = current_dir();
|
||||||
|
|
||||||
path_buf.push("src/toplevel.pl");
|
path_buf.push("src/toplevel.pl");
|
||||||
|
|
||||||
let path = path_buf.to_str().unwrap();
|
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);
|
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) {
|
pub(crate) fn configure_streams(&mut self) {
|
||||||
self.user_input
|
self.user_input
|
||||||
.options_mut()
|
.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
|
/// Tests whether the file can be successfully loaded
|
||||||
/// and produces the expected output during it
|
/// and produces the expected output during it
|
||||||
pub(crate) fn load_module_test<T: Expectable>(file: &str, expected: T) {
|
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());
|
expected.assert_eq(wam.test_load_file(file).as_slice());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user