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

Programatic use of Machine / Scryer as library
This commit is contained in:
Mark Thom
2023-11-02 16:50:01 -06:00
committed by GitHub
20 changed files with 2014 additions and 171 deletions

48
Cargo.lock generated
View File

@@ -17,6 +17,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 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]] [[package]]
name = "android-tzdata" name = "android-tzdata"
version = "0.1.1" version = "0.1.1"
@@ -189,7 +198,7 @@ checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"memchr", "memchr",
"regex-automata", "regex-automata 0.1.10",
] ]
[[package]] [[package]]
@@ -1164,6 +1173,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]] [[package]]
name = "markup5ever" name = "markup5ever"
version = "0.11.0" version = "0.11.0"
@@ -1787,12 +1802,41 @@ version = "0.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d813022b2e00774a48eaf43caaa3c20b45f040ba8cbf398e2e8911a06668dbe6" 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]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.1.10" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 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]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.20" version = "0.11.20"
@@ -2018,6 +2062,7 @@ dependencies = [
"libc", "libc",
"libffi", "libffi",
"libloading", "libloading",
"maplit",
"modular-bitfield", "modular-bitfield",
"native-tls", "native-tls",
"num-order", "num-order",
@@ -2028,6 +2073,7 @@ dependencies = [
"quote", "quote",
"rand", "rand",
"ref_thread_local", "ref_thread_local",
"regex",
"reqwest", "reqwest",
"ring", "ring",
"ring-wasi", "ring-wasi",

View File

@@ -64,6 +64,7 @@ smallvec = "1.8.0"
static_assertions = "1.1.0" static_assertions = "1.1.0"
ryu = "1.0.9" ryu = "1.0.9"
futures = "0.3" futures = "0.3"
regex = "1.9.1"
libloading = "0.7" libloading = "0.7"
derive_deref = "1.1.1" derive_deref = "1.1.1"
bytes = "1" bytes = "1"
@@ -84,7 +85,7 @@ tokio = { version = "1.28.2", features = ["full"] }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.10", features = ["js"] } 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] [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
@@ -107,6 +108,7 @@ ring = { version = "0.16.13" }
[dev-dependencies] [dev-dependencies]
assert_cmd = "1.0.3" assert_cmd = "1.0.3"
predicates-core = "1.0.2" predicates-core = "1.0.2"
maplit = "1.0.2"
serial_test = "2.0.0" serial_test = "2.0.0"
[patch.crates-io] [patch.crates-io]

View File

@@ -10,7 +10,6 @@ use crate::read::*;
use crate::parser::dashu::{Integer, Rational}; use crate::parser::dashu::{Integer, Rational};
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use tokio::sync::RwLock;
use std::alloc; use std::alloc;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
@@ -20,6 +19,7 @@ use std::mem;
use std::net::TcpListener; use std::net::TcpListener;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::ptr; use std::ptr;
use std::sync::RwLock;
#[macro_export] #[macro_export]
macro_rules! arena_alloc { macro_rules! arena_alloc {
@@ -90,7 +90,8 @@ pub fn lookup_float(
offset: F64Offset, offset: F64Offset,
) -> RcuRef<RawBlock<F64Table>, UnsafeCell<OrderedFloat<f64>>> { ) -> RcuRef<RawBlock<F64Table>, UnsafeCell<OrderedFloat<f64>>> {
let f64table = global_f64table() let f64table = global_f64table()
.blocking_read() .read()
.unwrap()
.upgrade() .upgrade()
.expect("We should only be looking up floats while there is a float table"); .expect("We should only be looking up floats while there is a float table");
@@ -108,12 +109,12 @@ pub fn lookup_float(
impl F64Table { impl F64Table {
#[inline] #[inline]
pub fn new() -> Arc<Self> { 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 // don't inline upgraded, otherwise temporary will be dropped too late in case of None
if let Some(atom_table) = upgraded { if let Some(atom_table) = upgraded {
atom_table atom_table
} else { } 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 // try to upgrade again in case we lost the race on the write lock
if let Some(atom_table) = guard.upgrade() { if let Some(atom_table) = guard.upgrade() {
atom_table atom_table

View File

@@ -12,12 +12,12 @@ use std::slice;
use std::str; use std::str;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use std::sync::RwLock;
use std::sync::Weak; use std::sync::Weak;
use indexmap::IndexSet; use indexmap::IndexSet;
use modular_bitfield::prelude::*; use modular_bitfield::prelude::*;
use tokio::sync::RwLock;
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Atom { pub struct Atom {
@@ -74,7 +74,7 @@ fn global_atom_table() -> &'static RwLock<Weak<AtomTable>> {
#[inline(always)] #[inline(always)]
fn arc_atom_table() -> Option<Arc<AtomTable>> { fn arc_atom_table() -> Option<Arc<AtomTable>> {
global_atom_table().blocking_read().upgrade() global_atom_table().read().unwrap().upgrade()
} }
impl RawBlockTraits for AtomTable { impl RawBlockTraits for AtomTable {
@@ -310,12 +310,12 @@ impl InnerAtomTable {
impl AtomTable { impl AtomTable {
#[inline] #[inline]
pub fn new() -> Arc<Self> { 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 // don't inline upgraded, otherwise temporary will be dropped too late in case of None
if let Some(atom_table) = upgraded { if let Some(atom_table) = upgraded {
atom_table atom_table
} else { } 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 // try to upgrade again in case we lost the race on the write lock
if let Some(atom_table) = guard.upgrade() { if let Some(atom_table) = guard.upgrade() {
atom_table atom_table

View File

@@ -1,5 +1,6 @@
fn main() -> std::process::ExitCode { fn main() -> std::process::ExitCode {
use scryer_prolog::*; use scryer_prolog::*;
use scryer_prolog::atom_table::Atom;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
#[cfg(feature = "repl")] #[cfg(feature = "repl")]
@@ -8,6 +9,20 @@ fn main() -> std::process::ExitCode {
}) })
.unwrap(); .unwrap();
let mut wam = machine::Machine::new(); #[cfg(target_arch = "wasm32")]
wam.run_top_level() 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))
})
} }

View File

@@ -2,6 +2,8 @@
#[macro_use] #[macro_use]
extern crate static_assertions; extern crate static_assertions;
#[cfg(test)]
#[macro_use] extern crate maplit;
#[macro_use] #[macro_use]
pub mod macros; pub mod macros;
@@ -47,7 +49,6 @@ use wasm_bindgen::prelude::*;
#[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 {
use web_sys::console;
use machine::mock_wam::*; use machine::mock_wam::*;
let mut wam = Machine::with_test_streams(); let mut wam = Machine::with_test_streams();

View File

@@ -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. :- non_counted_backtracking check_predicate_property/5.

32
src/machine/config.rs Normal file
View 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
}
}

View File

@@ -5221,16 +5221,22 @@ impl Machine {
self.machine_st.throw_interrupt_exception(); self.machine_st.throw_interrupt_exception();
self.machine_st.backtrack(); self.machine_st.backtrack();
#[cfg(not(target_arch = "wasm32"))] // We have extracted controll over the Tokio runtime to the calling context for enabling library use case
let runtime = tokio::runtime::Runtime::new().unwrap(); // (see https://github.com/mthom/scryer-prolog/pull/1880)
#[cfg(target_arch = "wasm32")] // So we only have access to a runtime handle in here and can't shut it down.
let runtime = tokio::runtime::Builder::new_current_thread() // Since I'm not aware of the consequences of deactivating this new code which came in while PR 1880
.enable_all() // was not merged, I'm only deactivating it for now.
.build()
.unwrap(); //#[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); //let old_runtime = tokio::runtime::Handle::current();
old_runtime.shutdown_background(); //old_runtime.shutdown_background();
} }
} }
Err(_) => unreachable!(), Err(_) => unreachable!(),

File diff suppressed because it is too large Load Diff

484
src/machine/lib_machine.rs Normal file
View 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"),
}),
]))
);
}
}

View File

@@ -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(_)") unreachable!("Stream must be a Stream::Readline(_)")
} }

View File

@@ -221,109 +221,10 @@ pub(crate) fn parse_and_write_parsed_term_to_heap(
impl Machine { impl Machine {
pub fn with_test_streams() -> Self { pub fn with_test_streams() -> Self {
use ref_thread_local::RefThreadLocal; Machine::new(MachineConfig::in_memory())
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
} }
pub fn test_load_file(&mut self, file: &str) -> Vec<u8> { pub fn test_load_file(&mut self, file: &str) -> Vec<u8> {
use std::io::Read;
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(),
&mut self.machine_st.arena, &mut self.machine_st.arena,
@@ -334,8 +235,6 @@ impl Machine {
} }
pub fn test_load_string(&mut self, code: &str) -> Vec<u8> { pub fn test_load_string(&mut self, code: &str) -> Vec<u8> {
use std::io::Read;
let stream = Stream::from_owned_string( let stream = Stream::from_owned_string(
code.to_owned(), code.to_owned(),
&mut self.machine_st.arena, &mut self.machine_st.arena,

View File

@@ -5,18 +5,21 @@ pub mod code_walker;
#[macro_use] #[macro_use]
pub mod loader; pub mod loader;
pub mod compile; pub mod compile;
pub mod config;
pub mod copier; pub mod copier;
pub mod cycle_detection; pub mod cycle_detection;
pub mod disjuncts; pub mod disjuncts;
pub mod dispatch; pub mod dispatch;
pub mod gc; pub mod gc;
pub mod heap; pub mod heap;
pub mod lib_machine;
pub mod load_state; pub mod load_state;
pub mod machine_errors; pub mod machine_errors;
pub mod machine_indices; 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;
@@ -52,9 +55,12 @@ use ordered_float::OrderedFloat;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::env; use std::env;
use std::io::Read;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use tokio::runtime::Runtime;
use self::config::MachineConfig;
use self::parsed_results::*;
use rand::rngs::StdRng; use rand::rngs::StdRng;
use rand::SeedableRng; use rand::SeedableRng;
@@ -71,7 +77,6 @@ pub struct Machine {
pub(super) user_output: Stream, pub(super) user_output: Stream,
pub(super) user_error: Stream, pub(super) user_error: Stream,
pub(super) load_contexts: Vec<LoadContext>, pub(super) load_contexts: Vec<LoadContext>,
pub(super) runtime: Runtime,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
pub(super) foreign_function_table: ForeignFunctionTable, pub(super) foreign_function_table: ForeignFunctionTable,
pub(super) rng: StdRng, 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 BREAK_FROM_DISPATCH_LOOP_LOC: usize = 0;
pub static INSTALL_VERIFY_ATTR_INTERRUPT: usize = 1; pub static INSTALL_VERIFY_ATTR_INTERRUPT: usize = 1;
pub static VERIFY_ATTR_INTERRUPT_LOC: usize = 2; pub static VERIFY_ATTR_INTERRUPT_LOC: usize = 2;
pub static LIB_QUERY_SUCCESS: usize = 3;
pub struct MachinePreludeView<'a> { pub struct MachinePreludeView<'a> {
pub indices: &'a mut IndexStore, pub indices: &'a mut IndexStore,
@@ -240,14 +246,15 @@ 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) { fn load_top_level(&mut self, program: &'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 = let toplevel_stream =
Stream::from_static_string(include_str!("../toplevel.pl"), &mut self.machine_st.arena); Stream::from_static_string(program, &mut self.machine_st.arena);
self.load_file(path, toplevel_stream); 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![]; let mut arg_pstrs = vec![];
for arg in env::args() { for arg in env::args() {
@@ -309,7 +316,16 @@ impl Machine {
arg_pstrs.into_iter() 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) { pub(crate) fn configure_modules(&mut self) {
@@ -382,13 +398,14 @@ impl Machine {
} }
pub(crate) fn add_impls_to_indices(&mut self) { 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( self.code.extend(
vec![ vec![
Instruction::BreakFromDispatchLoop, Instruction::BreakFromDispatchLoop,
Instruction::InstallVerifyAttr, Instruction::InstallVerifyAttr,
Instruction::VerifyAttrInterrupt, Instruction::VerifyAttrInterrupt,
Instruction::BreakFromDispatchLoop, // the location of LIB_QUERY_SUCCESS
Instruction::ExecuteTermGreaterThan, Instruction::ExecuteTermGreaterThan,
Instruction::ExecuteTermLessThan, Instruction::ExecuteTermLessThan,
Instruction::ExecuteTermGreaterThanOrEqual, Instruction::ExecuteTermGreaterThanOrEqual,
@@ -447,23 +464,24 @@ impl Machine {
} }
} }
pub fn new() -> Self { pub fn new(config: MachineConfig) -> Self {
use ref_thread_local::RefThreadLocal; use ref_thread_local::RefThreadLocal;
let args = MachineArgs::new(); let args = MachineArgs::new();
let mut machine_st = MachineState::new(); let mut machine_st = MachineState::new();
let user_input = Stream::stdin(&mut machine_st.arena, args.add_history); let (user_input, user_output, user_error) = match config.streams {
let user_output = Stream::stdout(&mut machine_st.arena); config::StreamConfig::Stdio => (
let user_error = Stream::stderr(&mut machine_st.arena); Stream::stdin(&mut machine_st.arena, args.add_history),
Stream::stdout(&mut machine_st.arena),
#[cfg(not(target_arch = "wasm32"))] Stream::stderr(&mut machine_st.arena),
let runtime = tokio::runtime::Runtime::new().unwrap(); ),
#[cfg(target_arch = "wasm32")] config::StreamConfig::Memory => (
let runtime = tokio::runtime::Builder::new_current_thread() Stream::Null(StreamOptions::default()),
.enable_all() Stream::from_owned_string("".to_owned(), &mut machine_st.arena),
.build() Stream::stderr(&mut machine_st.arena),
.unwrap(); ),
};
let mut wam = Machine { let mut wam = Machine {
machine_st, machine_st,
@@ -473,7 +491,6 @@ impl Machine {
user_output, user_output,
user_error, user_error,
load_contexts: vec![], load_contexts: vec![],
runtime,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
foreign_function_table: Default::default(), foreign_function_table: Default::default(),
rng: StdRng::from_entropy(), rng: StdRng::from_entropy(),
@@ -546,7 +563,7 @@ impl Machine {
} }
wam.load_special_forms(); wam.load_special_forms();
wam.load_top_level(); wam.load_top_level(config.toplevel);
wam.configure_streams(); wam.configure_streams();
wam wam

View 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())
}
}

View File

@@ -21,7 +21,9 @@ use std::fmt::Debug;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::hash::Hash; use std::hash::Hash;
use std::io; 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::mem;
use std::net::{Shutdown, TcpStream}; use std::net::{Shutdown, TcpStream};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};

View File

@@ -51,14 +51,20 @@ use std::env;
use std::ffi::CString; use std::ffi::CString;
use std::fs; use std::fs;
use std::hash::{BuildHasher, BuildHasherDefault}; 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::iter::{once, FromIterator};
use std::mem; 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::num::NonZeroU32;
use std::ops::Sub; use std::ops::Sub;
use std::process; use std::process;
#[cfg(feature = "http")]
use std::str::FromStr; use std::str::FromStr;
#[cfg(feature = "http")]
use std::sync::{Mutex, Arc, Condvar}; use std::sync::{Mutex, Arc, Condvar};
use chrono::{offset::Local, DateTime}; use chrono::{offset::Local, DateTime};
@@ -100,6 +106,7 @@ use warp::hyper::{HeaderMap, Method};
use warp::{Buf, Filter}; use warp::{Buf, Filter};
#[cfg(feature = "http")] #[cfg(feature = "http")]
use reqwest::Url; use reqwest::Url;
#[cfg(feature = "http")]
use futures::future; use futures::future;
#[cfg(feature = "repl")] #[cfg(feature = "repl")]
@@ -4443,14 +4450,17 @@ impl Machine {
if let Some(address_str) = self.machine_st.value_to_str_like(address_sink) { if let Some(address_str) = self.machine_st.value_to_str_like(address_sink) {
let address_string = address_str.as_str(); let address_string = address_str.as_str();
let addr: SocketAddr = match address_string.to_socket_addrs().ok().and_then(|mut s| s.next()) { let addr: SocketAddr = match address_string.to_socket_addrs().ok().and_then(|mut s| s.next()) {
Some(addr) => addr, Some(addr) => addr,
_ => { _ => {
self.machine_st.fail = true; self.machine_st.fail = true;
return Ok(()); 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> { fn get_reader(body: impl Buf + Send + 'static) -> Box<dyn BufRead + Send> {
Box::new(body.reader()) Box::new(body.reader())
@@ -4499,7 +4509,7 @@ impl Machine {
} }
}); });
self.runtime.spawn(async move { runtime.spawn(async move {
match ssl_server { match ssl_server {
Some((key, cert)) => { Some((key, cert)) => {
warp::serve(serve).tls().key(key).cert(cert).run(addr).await 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 = HttpListener { incoming: rx };
let http_listener = arena_alloc!(http_listener, &mut self.machine_st.arena); let http_listener = arena_alloc!(http_listener, &mut self.machine_st.arena);
let addr = self.deref_register(2);
self.machine_st.bind( let addr = self.deref_register(2);
addr.as_var().unwrap(), self.machine_st.bind(
typed_arena_ptr_as_cell!(http_listener), addr.as_var().unwrap(),
); typed_arena_ptr_as_cell!(http_listener),
);
} }
Ok(()) Ok(())
} }
@@ -4598,8 +4609,13 @@ impl Machine {
if interruption { if interruption {
self.machine_st.throw_interrupt_exception(); self.machine_st.throw_interrupt_exception();
self.machine_st.backtrack(); self.machine_st.backtrack();
let old_runtime = std::mem::replace(&mut self.runtime, tokio::runtime::Runtime::new().unwrap()); // We have extracted controll over the Tokio runtime to the calling context for enabling library use case
old_runtime.shutdown_background(); // (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 break
} }
} }

View File

@@ -6,19 +6,17 @@ use std::{
ptr::NonNull, ptr::NonNull,
sync::{ sync::{
atomic::{AtomicPtr, AtomicU8}, atomic::{AtomicPtr, AtomicU8},
Arc, Weak, Arc, Weak, RwLock
}, },
}; };
use tokio::sync::RwLock;
// the epoch counters of all threads that have ever accessed an Rcu // 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 // 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, // having this be shared between all Rcu's is a tradeof,
// writes will be slower as more epoch counters need to be waited for // 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 // 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! { thread_local! {
// odd value means the current thread is about to access the active_epoch of an Rcu // 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)); let epoch_counter = Arc::new(AtomicU8::new(0));
// register the current threads epoch counter on init // register the current threads epoch counter on init
EPOCH_COUNTERS EPOCH_COUNTERS
.blocking_write() .write()
.unwrap()
.push(Arc::downgrade(&epoch_counter)); .push(Arc::downgrade(&epoch_counter));
epoch_counter epoch_counter
}); });
@@ -113,7 +112,7 @@ impl<T> Rcu<T> {
// - the Rcu itself holds one strong count // - the Rcu itself holds one strong count
let arc = unsafe { ManuallyDrop::new(Arc::from_raw(arc_ptr)) }; 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 let mut epochs = epochs
.into_iter() .into_iter()
.flat_map(|elem| { .flat_map(|elem| {

View File

@@ -24,7 +24,9 @@ use rustyline::history::DefaultHistory;
use rustyline::{Config, Editor}; use rustyline::{Config, Editor};
use std::collections::VecDeque; 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; use std::sync::Arc;
type SubtermDeque = VecDeque<(usize, usize)>; type SubtermDeque = VecDeque<(usize, usize)>;
@@ -83,6 +85,7 @@ impl MachineState {
} }
static mut PROMPT: bool = false; static mut PROMPT: bool = false;
#[cfg(feature = "repl")]
const HISTORY_FILE: &'static str = ".scryer_history"; const HISTORY_FILE: &'static str = ".scryer_history";
pub(crate) fn set_prompt(value: bool) { pub(crate) fn set_prompt(value: bool) {
@@ -91,6 +94,7 @@ pub(crate) fn set_prompt(value: bool) {
} }
} }
#[cfg(feature = "repl")]
#[inline] #[inline]
fn get_prompt() -> &'static str { fn get_prompt() -> &'static str {
unsafe { unsafe {
@@ -107,6 +111,7 @@ pub struct ReadlineStream {
#[cfg(feature = "repl")] #[cfg(feature = "repl")]
rl: Editor<Helper, DefaultHistory>, rl: Editor<Helper, DefaultHistory>,
pending_input: CharReader<Cursor<String>>, pending_input: CharReader<Cursor<String>>,
#[allow(dead_code)]
add_history: bool, add_history: bool,
} }
@@ -145,6 +150,7 @@ impl ReadlineStream {
} }
} }
#[allow(unused_variables)]
pub fn set_atoms_for_completion(&mut self, atoms: &Arc<AtomTable>) { pub fn set_atoms_for_completion(&mut self, atoms: &Arc<AtomTable>) {
#[cfg(feature = "repl")] #[cfg(feature = "repl")]
{ {
@@ -215,6 +221,7 @@ impl ReadlineStream {
} }
} }
#[allow(dead_code)]
#[cfg(not(feature = "repl"))] #[cfg(not(feature = "repl"))]
fn save_history(&mut self) {} fn save_history(&mut self) {}

View File

@@ -452,3 +452,4 @@ print_exception_with_check(E) :-
% is expected to be printed instead. % is expected to be printed instead.
; print_exception(E) ; print_exception(E)
). ).