Merge pull request #1880 from coasys/library-use-case
Programatic use of Machine / Scryer as library
This commit is contained in:
48
Cargo.lock
generated
48
Cargo.lock
generated
@@ -17,6 +17,15 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@@ -189,7 +198,7 @@ checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1164,6 +1173,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.11.0"
|
||||
@@ -1787,12 +1802,41 @@ version = "0.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d813022b2e00774a48eaf43caaa3c20b45f040ba8cbf398e2e8911a06668dbe6"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.3.3",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.20"
|
||||
@@ -2018,6 +2062,7 @@ dependencies = [
|
||||
"libc",
|
||||
"libffi",
|
||||
"libloading",
|
||||
"maplit",
|
||||
"modular-bitfield",
|
||||
"native-tls",
|
||||
"num-order",
|
||||
@@ -2028,6 +2073,7 @@ dependencies = [
|
||||
"quote",
|
||||
"rand",
|
||||
"ref_thread_local",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"ring",
|
||||
"ring-wasi",
|
||||
|
||||
@@ -64,6 +64,7 @@ smallvec = "1.8.0"
|
||||
static_assertions = "1.1.0"
|
||||
ryu = "1.0.9"
|
||||
futures = "0.3"
|
||||
regex = "1.9.1"
|
||||
libloading = "0.7"
|
||||
derive_deref = "1.1.1"
|
||||
bytes = "1"
|
||||
@@ -84,7 +85,7 @@ tokio = { version = "1.28.2", features = ["full"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.10", features = ["js"] }
|
||||
tokio = { version = "1.28.2", features = ["sync", "macros", "io-util", "rt"] }
|
||||
tokio = { version = "1.28.2", features = ["sync", "macros", "io-util", "rt", "time"] }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
@@ -107,6 +108,7 @@ ring = { version = "0.16.13" }
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1.0.3"
|
||||
predicates-core = "1.0.2"
|
||||
maplit = "1.0.2"
|
||||
serial_test = "2.0.0"
|
||||
|
||||
[patch.crates-io]
|
||||
|
||||
@@ -10,7 +10,6 @@ use crate::read::*;
|
||||
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
use ordered_float::OrderedFloat;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use std::alloc;
|
||||
use std::cell::UnsafeCell;
|
||||
@@ -20,6 +19,7 @@ use std::mem;
|
||||
use std::net::TcpListener;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ptr;
|
||||
use std::sync::RwLock;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! arena_alloc {
|
||||
@@ -90,7 +90,8 @@ pub fn lookup_float(
|
||||
offset: F64Offset,
|
||||
) -> RcuRef<RawBlock<F64Table>, UnsafeCell<OrderedFloat<f64>>> {
|
||||
let f64table = global_f64table()
|
||||
.blocking_read()
|
||||
.read()
|
||||
.unwrap()
|
||||
.upgrade()
|
||||
.expect("We should only be looking up floats while there is a float table");
|
||||
|
||||
@@ -108,12 +109,12 @@ pub fn lookup_float(
|
||||
impl F64Table {
|
||||
#[inline]
|
||||
pub fn new() -> Arc<Self> {
|
||||
let upgraded = global_f64table().blocking_read().upgrade();
|
||||
let upgraded = global_f64table().read().unwrap().upgrade();
|
||||
// don't inline upgraded, otherwise temporary will be dropped too late in case of None
|
||||
if let Some(atom_table) = upgraded {
|
||||
atom_table
|
||||
} else {
|
||||
let mut guard = global_f64table().blocking_write();
|
||||
let mut guard = global_f64table().write().unwrap();
|
||||
// try to upgrade again in case we lost the race on the write lock
|
||||
if let Some(atom_table) = guard.upgrade() {
|
||||
atom_table
|
||||
|
||||
@@ -12,12 +12,12 @@ use std::slice;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::Weak;
|
||||
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use modular_bitfield::prelude::*;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Atom {
|
||||
@@ -74,7 +74,7 @@ fn global_atom_table() -> &'static RwLock<Weak<AtomTable>> {
|
||||
|
||||
#[inline(always)]
|
||||
fn arc_atom_table() -> Option<Arc<AtomTable>> {
|
||||
global_atom_table().blocking_read().upgrade()
|
||||
global_atom_table().read().unwrap().upgrade()
|
||||
}
|
||||
|
||||
impl RawBlockTraits for AtomTable {
|
||||
@@ -310,12 +310,12 @@ impl InnerAtomTable {
|
||||
impl AtomTable {
|
||||
#[inline]
|
||||
pub fn new() -> Arc<Self> {
|
||||
let upgraded = global_atom_table().blocking_read().upgrade();
|
||||
let upgraded = global_atom_table().read().unwrap().upgrade();
|
||||
// don't inline upgraded, otherwise temporary will be dropped too late in case of None
|
||||
if let Some(atom_table) = upgraded {
|
||||
atom_table
|
||||
} else {
|
||||
let mut guard = global_atom_table().blocking_write();
|
||||
let mut guard = global_atom_table().write().unwrap();
|
||||
// try to upgrade again in case we lost the race on the write lock
|
||||
if let Some(atom_table) = guard.upgrade() {
|
||||
atom_table
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
fn main() -> std::process::ExitCode {
|
||||
use scryer_prolog::*;
|
||||
use scryer_prolog::atom_table::Atom;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[cfg(feature = "repl")]
|
||||
@@ -8,6 +9,20 @@ fn main() -> std::process::ExitCode {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut wam = machine::Machine::new();
|
||||
wam.run_top_level()
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
runtime.block_on(async move {
|
||||
let mut wam = machine::Machine::new(Default::default());
|
||||
wam.run_top_level(atom!("$toplevel"), (atom!("$repl"), 1))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#[macro_use]
|
||||
extern crate static_assertions;
|
||||
#[cfg(test)]
|
||||
#[macro_use] extern crate maplit;
|
||||
|
||||
#[macro_use]
|
||||
pub mod macros;
|
||||
@@ -47,7 +49,6 @@ use wasm_bindgen::prelude::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub fn eval_code(s: &str) -> String {
|
||||
use web_sys::console;
|
||||
use machine::mock_wam::*;
|
||||
|
||||
let mut wam = Machine::with_test_streams();
|
||||
|
||||
@@ -621,6 +621,10 @@ use_module(Module, Exports, Evacuable) :-
|
||||
)
|
||||
).
|
||||
|
||||
consult_stream(Stream, PathFileName) :-
|
||||
'$push_load_state_payload'(Evacuable),
|
||||
file_load(Stream, PathFileName, Subevacuable),
|
||||
'$use_module'(Evacuable, Subevacuable, _).
|
||||
|
||||
:- non_counted_backtracking check_predicate_property/5.
|
||||
|
||||
|
||||
32
src/machine/config.rs
Normal file
32
src/machine/config.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
pub struct MachineConfig {
|
||||
pub streams: StreamConfig,
|
||||
pub toplevel: &'static str,
|
||||
}
|
||||
|
||||
pub enum StreamConfig {
|
||||
Stdio,
|
||||
Memory,
|
||||
}
|
||||
|
||||
impl Default for MachineConfig {
|
||||
fn default() -> Self {
|
||||
MachineConfig {
|
||||
streams: StreamConfig::Stdio,
|
||||
toplevel: include_str!("../toplevel.pl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineConfig {
|
||||
pub fn in_memory() -> Self {
|
||||
MachineConfig {
|
||||
streams: StreamConfig::Memory,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_toplevel(mut self, toplevel: &'static str) -> Self {
|
||||
self.toplevel = toplevel;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -5221,16 +5221,22 @@ impl Machine {
|
||||
self.machine_st.throw_interrupt_exception();
|
||||
self.machine_st.backtrack();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
// We have extracted controll over the Tokio runtime to the calling context for enabling library use case
|
||||
// (see https://github.com/mthom/scryer-prolog/pull/1880)
|
||||
// So we only have access to a runtime handle in here and can't shut it down.
|
||||
// Since I'm not aware of the consequences of deactivating this new code which came in while PR 1880
|
||||
// was not merged, I'm only deactivating it for now.
|
||||
|
||||
//#[cfg(not(target_arch = "wasm32"))]
|
||||
//let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
//#[cfg(target_arch = "wasm32")]
|
||||
//let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
// .enable_all()
|
||||
// .build()
|
||||
// .unwrap();
|
||||
|
||||
let old_runtime = std::mem::replace(&mut self.runtime, runtime);
|
||||
old_runtime.shutdown_background();
|
||||
//let old_runtime = tokio::runtime::Handle::current();
|
||||
//old_runtime.shutdown_background();
|
||||
}
|
||||
}
|
||||
Err(_) => unreachable!(),
|
||||
|
||||
1018
src/machine/lib_integration_test_commands.txt
Normal file
1018
src/machine/lib_integration_test_commands.txt
Normal file
File diff suppressed because it is too large
Load Diff
484
src/machine/lib_machine.rs
Normal file
484
src/machine/lib_machine.rs
Normal file
@@ -0,0 +1,484 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::atom_table;
|
||||
use crate::heap_print::{HCPrinter, HCValueOutputter, PrinterOutputter};
|
||||
use crate::machine::{BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS};
|
||||
use crate::machine::mock_wam::CompositeOpDir;
|
||||
use crate::parser::parser::{Parser, Tokens};
|
||||
use crate::read::write_term_to_heap;
|
||||
use crate::machine::machine_indices::VarKey;
|
||||
use crate::parser::ast::{Var, VarPtr};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::{
|
||||
Machine, MachineConfig, QueryResult, QueryResolutionLine,
|
||||
Atom, AtomCell, HeapCellValue, HeapCellValueTag, Value, QueryResolution,
|
||||
streams::Stream
|
||||
};
|
||||
|
||||
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. since the and/or stack is presumed empty,
|
||||
|
||||
let stub_b = self.machine_st.stack.allocate_or_frame(0);
|
||||
let or_frame = self.machine_st.stack.index_or_frame_mut(0);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn run_query(&mut self, query: String) -> QueryResult {
|
||||
println!("Query: {}", query);
|
||||
// Parse the query so we can analyze and then call the term
|
||||
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");
|
||||
|
||||
// Write parsed term to heap
|
||||
let term_write_result = write_term_to_heap(&term, &mut self.machine_st.heap, &mut self.machine_st.atom_tbl).expect("couldn't write term to heap");
|
||||
|
||||
// Write term to heap
|
||||
self.machine_st.registers[1] = self.machine_st.heap[term_write_result.heap_loc];
|
||||
|
||||
self.machine_st.cp = LIB_QUERY_SUCCESS; // BREAK_FROM_DISPATCH_LOOP_LOC;
|
||||
self.machine_st.p = self.indices.code_dir.get(&(atom!("call"), 1)).expect("couldn't get code index").local().unwrap();
|
||||
|
||||
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();
|
||||
|
||||
self.allocate_stub_choice_point();
|
||||
|
||||
let stub_b = self.machine_st.b;
|
||||
|
||||
let mut matches: Vec<QueryResolutionLine> = Vec::new();
|
||||
// Call the term
|
||||
loop {
|
||||
self.dispatch_loop();
|
||||
|
||||
//println!("b: {}", self.machine_st.b);
|
||||
//println!("stub_b: {}", stub_b);
|
||||
//println!("fail: {}", self.machine_st.fail);
|
||||
|
||||
if self.machine_st.ball.stub.len() != 0 {
|
||||
// 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_st.ball.stub
|
||||
.iter()
|
||||
.filter(|h| match h.get_tag() {
|
||||
HeapCellValueTag::Atom => true,
|
||||
HeapCellValueTag::Fixnum => true,
|
||||
_ => false,
|
||||
})
|
||||
.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 Err(error_string);
|
||||
}
|
||||
|
||||
/*
|
||||
if self.machine_st.fail {
|
||||
// NOTE: only print results on success
|
||||
self.machine_st.fail = false;
|
||||
println!("fail!");
|
||||
matches.push(QueryResolutionLine::False);
|
||||
break;
|
||||
};
|
||||
*/
|
||||
|
||||
if term_write_result.var_dict.is_empty() {
|
||||
if self.machine_st.p == LIB_QUERY_SUCCESS {
|
||||
matches.push(QueryResolutionLine::True);
|
||||
break;
|
||||
} else if self.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC {
|
||||
// NOTE: only print results on success
|
||||
// self.machine_st.fail = false;
|
||||
// println!("b == stub_b");
|
||||
matches.push(QueryResolutionLine::False);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut bindings: BTreeMap<String, Value> = BTreeMap::new();
|
||||
|
||||
for (var_key, term_to_be_printed) in &term_write_result.var_dict {
|
||||
if var_key.to_string().starts_with("_") {
|
||||
continue;
|
||||
}
|
||||
let mut printer = HCPrinter::new(
|
||||
&mut self.machine_st.heap,
|
||||
Arc::clone(&self.machine_st.atom_tbl),
|
||||
&mut self.machine_st.stack,
|
||||
&self.indices.op_dir,
|
||||
PrinterOutputter::new(),
|
||||
*term_to_be_printed,
|
||||
);
|
||||
|
||||
printer.ignore_ops = false;
|
||||
printer.numbervars = true;
|
||||
printer.quoted = true;
|
||||
printer.max_depth = 1000; // NOTE: set this to 0 for unbounded depth
|
||||
printer.double_quotes = true;
|
||||
printer.var_names = var_names.clone();
|
||||
|
||||
let outputter = printer.print();
|
||||
|
||||
let output: String = outputter.result();
|
||||
println!("Result: {} = {}", var_key.to_string(), output);
|
||||
|
||||
bindings.insert(var_key.to_string(), Value::try_from(output).expect("asdfs"));
|
||||
}
|
||||
|
||||
matches.push(QueryResolutionLine::Match(bindings));
|
||||
|
||||
// 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_st.backtrack();
|
||||
|
||||
if self.machine_st.b <= stub_b {
|
||||
// NOTE: out of choicepoints to backtrack through, no
|
||||
// more solutions to gather.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: deallocate stub choice point
|
||||
if self.machine_st.b == stub_b {
|
||||
self.trust_me();
|
||||
}
|
||||
|
||||
Ok(QueryResolution::from(matches))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use super::*;
|
||||
use crate::machine::{QueryMatch, Value, QueryResolution};
|
||||
|
||||
#[test]
|
||||
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]
|
||||
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]
|
||||
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::from("c"),
|
||||
"Actions" => Value::from("[{action: \"addLink\", source: \"this\", predicate: \"todo://state\", target: \"todo://ready\"}]"),
|
||||
}),
|
||||
]))
|
||||
);
|
||||
|
||||
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::from("xyz"),
|
||||
"Actions" => Value::from("[{action: \"addLink\", source: \"this\", predicate: \"recipe://title\", target: \"literal://string:Meta%20Muffins\"}]"),
|
||||
}),
|
||||
]))
|
||||
);
|
||||
|
||||
let result = machine.run_query(String::from("subject_class(Class, _)."));
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"Class" => Value::from("Todo")
|
||||
}),
|
||||
QueryMatch::from(btreemap! {
|
||||
"Class" => Value::from("Recipe")
|
||||
}),
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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::from([
|
||||
Value::Float(OrderedFloat::from(1.0)),
|
||||
Value::Float(OrderedFloat::from(2.0)),
|
||||
Value::Float(OrderedFloat::from(3.0))
|
||||
])
|
||||
)
|
||||
}),
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
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)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#[ignore = "fails on windows"]
|
||||
#[test]
|
||||
fn stress_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;
|
||||
// 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 block.starts_with("query") {
|
||||
// Extract the query from the block
|
||||
let query = &block[5..];
|
||||
|
||||
i += 1;
|
||||
println!("query #{}: {}", i, query);
|
||||
// Parse and execute the query
|
||||
let result = machine.run_query(query.to_string());
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Print the result
|
||||
println!("{:?}", result);
|
||||
} else if block.starts_with("consult") {
|
||||
// Extract the code from the block
|
||||
let code = &block[7..];
|
||||
|
||||
println!("load code: {}", code);
|
||||
|
||||
// Load the code into the machine
|
||||
machine.consult_module_string("facts", code.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
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! {
|
||||
"Predicate" => Value::from("Predicate"),
|
||||
"Result" => Value::List(
|
||||
Vec::from([
|
||||
Value::List([Value::from("p1"), Value::from("b")].into()),
|
||||
Value::List([Value::from("p2"), Value::from("b")].into()),
|
||||
])
|
||||
),
|
||||
"Target" => Value::from("Target"),
|
||||
}),
|
||||
]))
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -672,6 +672,14 @@ impl MachineState {
|
||||
}
|
||||
}
|
||||
|
||||
if let Stream::Byte(_) = stream {
|
||||
return self.read_term(
|
||||
stream,
|
||||
indices,
|
||||
MachineState::read_term_from_user_input_eof_handler
|
||||
)
|
||||
}
|
||||
|
||||
unreachable!("Stream must be a Stream::Readline(_)")
|
||||
}
|
||||
|
||||
|
||||
@@ -221,109 +221,10 @@ pub(crate) fn parse_and_write_parsed_term_to_heap(
|
||||
|
||||
impl Machine {
|
||||
pub fn with_test_streams() -> Self {
|
||||
use ref_thread_local::RefThreadLocal;
|
||||
|
||||
let mut machine_st = MachineState::new();
|
||||
|
||||
let user_input = Stream::Null(StreamOptions::default());
|
||||
let user_output = Stream::from_owned_string("".to_owned(), &mut machine_st.arena);
|
||||
let user_error = Stream::stderr(&mut machine_st.arena);
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut wam = Machine {
|
||||
machine_st,
|
||||
indices: IndexStore::new(),
|
||||
code: Code::new(),
|
||||
user_input,
|
||||
user_output,
|
||||
user_error,
|
||||
load_contexts: vec![],
|
||||
runtime,
|
||||
#[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.borrow()["ops_and_meta_predicates"],
|
||||
&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.borrow()["builtins"], &mut wam.machine_st.arena),
|
||||
&mut wam,
|
||||
ListingSource::from_file_and_path(atom!("builtins.pl"), lib_path.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(ref mut 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();
|
||||
wam.configure_streams();
|
||||
|
||||
wam
|
||||
Machine::new(MachineConfig::in_memory())
|
||||
}
|
||||
|
||||
pub fn test_load_file(&mut self, file: &str) -> Vec<u8> {
|
||||
use std::io::Read;
|
||||
|
||||
let stream = Stream::from_owned_string(
|
||||
std::fs::read_to_string(AsRef::<std::path::Path>::as_ref(file)).unwrap(),
|
||||
&mut self.machine_st.arena,
|
||||
@@ -334,8 +235,6 @@ impl Machine {
|
||||
}
|
||||
|
||||
pub fn test_load_string(&mut self, code: &str) -> Vec<u8> {
|
||||
use std::io::Read;
|
||||
|
||||
let stream = Stream::from_owned_string(
|
||||
code.to_owned(),
|
||||
&mut self.machine_st.arena,
|
||||
|
||||
@@ -5,18 +5,21 @@ pub mod code_walker;
|
||||
#[macro_use]
|
||||
pub mod loader;
|
||||
pub mod compile;
|
||||
pub mod config;
|
||||
pub mod copier;
|
||||
pub mod cycle_detection;
|
||||
pub mod disjuncts;
|
||||
pub mod dispatch;
|
||||
pub mod gc;
|
||||
pub mod heap;
|
||||
pub mod lib_machine;
|
||||
pub mod load_state;
|
||||
pub mod machine_errors;
|
||||
pub mod machine_indices;
|
||||
pub mod machine_state;
|
||||
pub mod machine_state_impl;
|
||||
pub mod mock_wam;
|
||||
pub mod parsed_results;
|
||||
pub mod partial_string;
|
||||
pub mod preprocessor;
|
||||
pub mod stack;
|
||||
@@ -52,9 +55,12 @@ use ordered_float::OrderedFloat;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::env;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use self::config::MachineConfig;
|
||||
use self::parsed_results::*;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
|
||||
@@ -71,7 +77,6 @@ pub struct Machine {
|
||||
pub(super) user_output: Stream,
|
||||
pub(super) user_error: Stream,
|
||||
pub(super) load_contexts: Vec<LoadContext>,
|
||||
pub(super) runtime: Runtime,
|
||||
#[cfg(feature = "ffi")]
|
||||
pub(super) foreign_function_table: ForeignFunctionTable,
|
||||
pub(super) rng: StdRng,
|
||||
@@ -113,6 +118,7 @@ include!(concat!(env!("OUT_DIR"), "/libraries.rs"));
|
||||
pub static BREAK_FROM_DISPATCH_LOOP_LOC: usize = 0;
|
||||
pub static INSTALL_VERIFY_ATTR_INTERRUPT: usize = 1;
|
||||
pub static VERIFY_ATTR_INTERRUPT_LOC: usize = 2;
|
||||
pub static LIB_QUERY_SUCCESS: usize = 3;
|
||||
|
||||
pub struct MachinePreludeView<'a> {
|
||||
pub indices: &'a mut IndexStore,
|
||||
@@ -240,14 +246,15 @@ impl Machine {
|
||||
self.run_module_predicate(atom!("loader"), (atom!("file_load"), 2));
|
||||
}
|
||||
|
||||
fn load_top_level(&mut self) {
|
||||
fn load_top_level(&mut self, program: &'static str) {
|
||||
let mut path_buf = current_dir();
|
||||
|
||||
path_buf.push("src/toplevel.pl");
|
||||
|
||||
let path = path_buf.to_str().unwrap();
|
||||
let toplevel_stream =
|
||||
Stream::from_static_string(include_str!("../toplevel.pl"), &mut self.machine_st.arena);
|
||||
let toplevel_stream =
|
||||
Stream::from_static_string(program, &mut self.machine_st.arena);
|
||||
|
||||
|
||||
self.load_file(path, toplevel_stream);
|
||||
|
||||
@@ -293,7 +300,7 @@ impl Machine {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_top_level(&mut self) -> std::process::ExitCode {
|
||||
pub fn run_top_level(&mut self, module_name: Atom, key: PredicateKey) -> std::process::ExitCode {
|
||||
let mut arg_pstrs = vec![];
|
||||
|
||||
for arg in env::args() {
|
||||
@@ -309,7 +316,16 @@ impl Machine {
|
||||
arg_pstrs.into_iter()
|
||||
));
|
||||
|
||||
self.run_module_predicate(atom!("$toplevel"), (atom!("$repl"), 1))
|
||||
self.run_module_predicate(module_name, key)
|
||||
}
|
||||
|
||||
pub fn set_user_input(&mut self, input: String) {
|
||||
self.user_input = Stream::from_owned_string(input, &mut self.machine_st.arena);
|
||||
}
|
||||
|
||||
pub fn get_user_output(&self) -> String {
|
||||
let output_bytes: Vec<_> = self.user_output.bytes().map(|b| b.unwrap()).collect();
|
||||
String::from_utf8(output_bytes).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn configure_modules(&mut self) {
|
||||
@@ -382,13 +398,14 @@ impl Machine {
|
||||
}
|
||||
|
||||
pub(crate) fn add_impls_to_indices(&mut self) {
|
||||
let impls_offset = self.code.len() + 3;
|
||||
let impls_offset = self.code.len() + 4;
|
||||
|
||||
self.code.extend(
|
||||
vec![
|
||||
Instruction::BreakFromDispatchLoop,
|
||||
Instruction::InstallVerifyAttr,
|
||||
Instruction::VerifyAttrInterrupt,
|
||||
Instruction::BreakFromDispatchLoop, // the location of LIB_QUERY_SUCCESS
|
||||
Instruction::ExecuteTermGreaterThan,
|
||||
Instruction::ExecuteTermLessThan,
|
||||
Instruction::ExecuteTermGreaterThanOrEqual,
|
||||
@@ -447,23 +464,24 @@ impl Machine {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
pub fn new(config: MachineConfig) -> Self {
|
||||
use ref_thread_local::RefThreadLocal;
|
||||
|
||||
let args = MachineArgs::new();
|
||||
let mut machine_st = MachineState::new();
|
||||
|
||||
let user_input = Stream::stdin(&mut machine_st.arena, args.add_history);
|
||||
let user_output = Stream::stdout(&mut machine_st.arena);
|
||||
let user_error = Stream::stderr(&mut machine_st.arena);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
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,
|
||||
@@ -473,7 +491,6 @@ impl Machine {
|
||||
user_output,
|
||||
user_error,
|
||||
load_contexts: vec![],
|
||||
runtime,
|
||||
#[cfg(feature = "ffi")]
|
||||
foreign_function_table: Default::default(),
|
||||
rng: StdRng::from_entropy(),
|
||||
@@ -546,7 +563,7 @@ impl Machine {
|
||||
}
|
||||
|
||||
wam.load_special_forms();
|
||||
wam.load_top_level();
|
||||
wam.load_top_level(config.toplevel);
|
||||
wam.configure_streams();
|
||||
|
||||
wam
|
||||
|
||||
285
src/machine/parsed_results.rs
Normal file
285
src/machine/parsed_results.rs
Normal file
@@ -0,0 +1,285 @@
|
||||
use crate::atom_table::*;
|
||||
use ordered_float::OrderedFloat;
|
||||
use dashu::*;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub type QueryResult = Result<QueryResolution, String>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum QueryResolution {
|
||||
True,
|
||||
False,
|
||||
Matches(Vec<QueryMatch>),
|
||||
}
|
||||
|
||||
#[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(Atom),
|
||||
String(String),
|
||||
List(Vec<Value>),
|
||||
Structure(Atom, Vec<Value>),
|
||||
Var,
|
||||
}
|
||||
|
||||
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 true.
|
||||
if query_result_lines.len() == 1 {
|
||||
match query_result_lines[0].clone() {
|
||||
QueryResolutionLine::Match(m) => {
|
||||
if m.is_empty() {
|
||||
return QueryResolution::True;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// 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| {
|
||||
if let &QueryResolutionLine::Match(_) = l {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
{
|
||||
return QueryResolution::True;
|
||||
}
|
||||
|
||||
// If there is at least one match, return all matches.
|
||||
let all_matches = query_result_lines
|
||||
.into_iter()
|
||||
.filter(|l| {
|
||||
if let &QueryResolutionLine::Match(_) = l {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
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("'") {
|
||||
Ok(Value::String(trimmed[1..trimmed.len() - 1].into()))
|
||||
} else if 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 mut iter = trimmed[1..trimmed.len() - 1].split(",");
|
||||
let mut values = vec![];
|
||||
|
||||
while let Some(value) = iter.next() {
|
||||
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(atom!("{}"), values))
|
||||
} else if trimmed.starts_with("<<") && trimmed.ends_with(">>") {
|
||||
let mut iter = trimmed[2..trimmed.len() - 2].split(",");
|
||||
let mut values = vec![];
|
||||
|
||||
while let Some(value) = iter.next() {
|
||||
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(atom!("<<>>"), 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())
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,9 @@ use std::fmt::Debug;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::hash::Hash;
|
||||
use std::io;
|
||||
use std::io::{BufRead, Cursor, ErrorKind, Read, Seek, SeekFrom, Write};
|
||||
use std::io::{Cursor, ErrorKind, Read, Seek, SeekFrom, Write};
|
||||
#[cfg(feature = "http")]
|
||||
use std::io::BufRead;
|
||||
use std::mem;
|
||||
use std::net::{Shutdown, TcpStream};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
@@ -51,14 +51,20 @@ use std::env;
|
||||
use std::ffi::CString;
|
||||
use std::fs;
|
||||
use std::hash::{BuildHasher, BuildHasherDefault};
|
||||
use std::io::{ErrorKind, Read, BufRead, Write};
|
||||
use std::io::{ErrorKind, Read, Write};
|
||||
#[cfg(feature = "http")]
|
||||
use std::io::BufRead;
|
||||
use std::iter::{once, FromIterator};
|
||||
use std::mem;
|
||||
use std::net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
#[cfg(feature = "http")]
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::num::NonZeroU32;
|
||||
use std::ops::Sub;
|
||||
use std::process;
|
||||
#[cfg(feature = "http")]
|
||||
use std::str::FromStr;
|
||||
#[cfg(feature = "http")]
|
||||
use std::sync::{Mutex, Arc, Condvar};
|
||||
|
||||
use chrono::{offset::Local, DateTime};
|
||||
@@ -100,6 +106,7 @@ use warp::hyper::{HeaderMap, Method};
|
||||
use warp::{Buf, Filter};
|
||||
#[cfg(feature = "http")]
|
||||
use reqwest::Url;
|
||||
#[cfg(feature = "http")]
|
||||
use futures::future;
|
||||
|
||||
#[cfg(feature = "repl")]
|
||||
@@ -4443,14 +4450,17 @@ impl Machine {
|
||||
if let Some(address_str) = self.machine_st.value_to_str_like(address_sink) {
|
||||
let address_string = address_str.as_str();
|
||||
let addr: SocketAddr = match address_string.to_socket_addrs().ok().and_then(|mut s| s.next()) {
|
||||
Some(addr) => addr,
|
||||
_ => {
|
||||
self.machine_st.fail = true;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
Some(addr) => addr,
|
||||
_ => {
|
||||
self.machine_st.fail = true;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::sync_channel(1024);
|
||||
let (tx, rx) = std::sync::mpsc::sync_channel(1024);
|
||||
|
||||
let runtime = tokio::runtime::Handle::current();
|
||||
let _guard = runtime.enter();
|
||||
|
||||
fn get_reader(body: impl Buf + Send + 'static) -> Box<dyn BufRead + Send> {
|
||||
Box::new(body.reader())
|
||||
@@ -4499,7 +4509,7 @@ impl Machine {
|
||||
}
|
||||
});
|
||||
|
||||
self.runtime.spawn(async move {
|
||||
runtime.spawn(async move {
|
||||
match ssl_server {
|
||||
Some((key, cert)) => {
|
||||
warp::serve(serve).tls().key(key).cert(cert).run(addr).await
|
||||
@@ -4512,11 +4522,12 @@ impl Machine {
|
||||
|
||||
let http_listener = HttpListener { incoming: rx };
|
||||
let http_listener = arena_alloc!(http_listener, &mut self.machine_st.arena);
|
||||
let addr = self.deref_register(2);
|
||||
self.machine_st.bind(
|
||||
addr.as_var().unwrap(),
|
||||
typed_arena_ptr_as_cell!(http_listener),
|
||||
);
|
||||
|
||||
let addr = self.deref_register(2);
|
||||
self.machine_st.bind(
|
||||
addr.as_var().unwrap(),
|
||||
typed_arena_ptr_as_cell!(http_listener),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -4598,8 +4609,13 @@ impl Machine {
|
||||
if interruption {
|
||||
self.machine_st.throw_interrupt_exception();
|
||||
self.machine_st.backtrack();
|
||||
let old_runtime = std::mem::replace(&mut self.runtime, tokio::runtime::Runtime::new().unwrap());
|
||||
old_runtime.shutdown_background();
|
||||
// We have extracted controll over the Tokio runtime to the calling context for enabling library use case
|
||||
// (see https://github.com/mthom/scryer-prolog/pull/1880)
|
||||
// So we only have access to a runtime handle in here and can't shut it down.
|
||||
// Since I'm not aware of the consequences of deactivating this new code which came in while PR 1880
|
||||
// was not merged, I'm only deactivating it for now.
|
||||
//let old_runtime = std::mem::replace(&mut self.runtime, tokio::runtime::Runtime::new().unwrap());
|
||||
//old_runtime.shutdown_background();
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
11
src/rcu.rs
11
src/rcu.rs
@@ -6,19 +6,17 @@ use std::{
|
||||
ptr::NonNull,
|
||||
sync::{
|
||||
atomic::{AtomicPtr, AtomicU8},
|
||||
Arc, Weak,
|
||||
Arc, Weak, RwLock
|
||||
},
|
||||
};
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
// the epoch counters of all threads that have ever accessed an Rcu
|
||||
// threads that have finished will have a dangling Weak reference and can be cleand up
|
||||
// having this be shared between all Rcu's is a tradeof,
|
||||
// writes will be slower as more epoch counters need to be waited for
|
||||
// reads should be faster as a thread only needs to register itself once on the first read
|
||||
//
|
||||
static EPOCH_COUNTERS: RwLock<Vec<Weak<AtomicU8>>> = RwLock::const_new(Vec::new());
|
||||
static EPOCH_COUNTERS: RwLock<Vec<Weak<AtomicU8>>> = RwLock::new(Vec::new());
|
||||
|
||||
thread_local! {
|
||||
// odd value means the current thread is about to access the active_epoch of an Rcu
|
||||
@@ -53,7 +51,8 @@ impl<T> Rcu<T> {
|
||||
let epoch_counter = Arc::new(AtomicU8::new(0));
|
||||
// register the current threads epoch counter on init
|
||||
EPOCH_COUNTERS
|
||||
.blocking_write()
|
||||
.write()
|
||||
.unwrap()
|
||||
.push(Arc::downgrade(&epoch_counter));
|
||||
epoch_counter
|
||||
});
|
||||
@@ -113,7 +112,7 @@ impl<T> Rcu<T> {
|
||||
// - the Rcu itself holds one strong count
|
||||
let arc = unsafe { ManuallyDrop::new(Arc::from_raw(arc_ptr)) };
|
||||
|
||||
let epochs = EPOCH_COUNTERS.blocking_read().clone();
|
||||
let epochs = EPOCH_COUNTERS.read().unwrap().clone();
|
||||
let mut epochs = epochs
|
||||
.into_iter()
|
||||
.flat_map(|elem| {
|
||||
|
||||
@@ -24,7 +24,9 @@ use rustyline::history::DefaultHistory;
|
||||
use rustyline::{Config, Editor};
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{Cursor, Error, ErrorKind, Read};
|
||||
use std::io::{Cursor, Read};
|
||||
#[cfg(feature = "repl")]
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::sync::Arc;
|
||||
|
||||
type SubtermDeque = VecDeque<(usize, usize)>;
|
||||
@@ -83,6 +85,7 @@ impl MachineState {
|
||||
}
|
||||
|
||||
static mut PROMPT: bool = false;
|
||||
#[cfg(feature = "repl")]
|
||||
const HISTORY_FILE: &'static str = ".scryer_history";
|
||||
|
||||
pub(crate) fn set_prompt(value: bool) {
|
||||
@@ -91,6 +94,7 @@ pub(crate) fn set_prompt(value: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "repl")]
|
||||
#[inline]
|
||||
fn get_prompt() -> &'static str {
|
||||
unsafe {
|
||||
@@ -107,6 +111,7 @@ pub struct ReadlineStream {
|
||||
#[cfg(feature = "repl")]
|
||||
rl: Editor<Helper, DefaultHistory>,
|
||||
pending_input: CharReader<Cursor<String>>,
|
||||
#[allow(dead_code)]
|
||||
add_history: bool,
|
||||
}
|
||||
|
||||
@@ -145,6 +150,7 @@ impl ReadlineStream {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub fn set_atoms_for_completion(&mut self, atoms: &Arc<AtomTable>) {
|
||||
#[cfg(feature = "repl")]
|
||||
{
|
||||
@@ -215,6 +221,7 @@ impl ReadlineStream {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(feature = "repl"))]
|
||||
fn save_history(&mut self) {}
|
||||
|
||||
|
||||
@@ -452,3 +452,4 @@ print_exception_with_check(E) :-
|
||||
% is expected to be printed instead.
|
||||
; print_exception(E)
|
||||
).
|
||||
|
||||
Reference in New Issue
Block a user