Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework library interface #2582

Merged
merged 28 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8e7dc9d
Rename PrologTerm
bakaq Sep 7, 2024
29fc55c
Rename LeafAnswer
bakaq Sep 7, 2024
658e39a
Machine and stream config rework
bakaq Sep 30, 2024
71dec62
Basic docs and non_exhaustive for PrologTerm
bakaq Sep 30, 2024
4480e7c
Associated functions for creating PrologTerm
bakaq Sep 30, 2024
e74fd11
Conjunctions, disjunction, and LeafAnswer to PrologTerm
bakaq Sep 30, 2024
0433706
More PrologTerm documentation
bakaq Sep 30, 2024
1375f44
LeafAnswer docs and success checking methods
bakaq Sep 30, 2024
c13fc2d
Docs for run_binary()
bakaq Sep 30, 2024
cb040da
Docs for Machine and QueryState
bakaq Sep 30, 2024
dc8348b
Add interfaces for QueryState methods
bakaq Sep 30, 2024
e6cc408
Document test methods
bakaq Sep 30, 2024
cab6173
#[deny(missing_docs)]
bakaq Sep 30, 2024
79fbd9e
Fix Machine links
bakaq Sep 30, 2024
d336cbc
MachineBuilder
bakaq Oct 12, 2024
33d8abf
Remove parsed_results.rs
bakaq Oct 12, 2024
bb5adba
Rename PrologTerm to Term
bakaq Oct 12, 2024
ec6286f
Shrink MVP API surface
bakaq Oct 12, 2024
2f82c78
Separate lib_machine tests into separate file
bakaq Oct 12, 2024
e21c772
Migrate tests to new API
bakaq Oct 12, 2024
3d3baee
Migrate benches
bakaq Oct 12, 2024
9265d66
Handle errors in QueryState
bakaq Dec 8, 2024
a92eac9
Rename lib_machine_tests.rs to tests.rs
bakaq Dec 12, 2024
a75266c
Derive Default for StreamConfig
bakaq Dec 12, 2024
17293a5
Leave residual goals for later
bakaq Dec 12, 2024
500a6cd
FIXME in Drop for QueryState
bakaq Dec 12, 2024
74e3d32
Use Cow<'a,B> for toplevel configuration
bakaq Dec 12, 2024
2e910de
cargo fmt
bakaq Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions benches/run_iai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ mod setup;
mod iai {
use iai_callgrind::{library_benchmark, library_benchmark_group, main};

use scryer_prolog::QueryResolution;
use scryer_prolog::LeafAnswer;

use super::setup;

#[library_benchmark]
#[bench::count_edges(setup::prolog_benches()["count_edges"].setup())]
#[bench::numlist(setup::prolog_benches()["numlist"].setup())]
#[bench::csv_codename(setup::prolog_benches()["csv_codename"].setup())]
fn bench(mut run: impl FnMut() -> QueryResolution) -> QueryResolution {
fn bench(mut run: impl FnMut() -> Vec<LeafAnswer>) -> Vec<LeafAnswer> {
run()
}

Expand Down
31 changes: 20 additions & 11 deletions benches/setup.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{collections::BTreeMap, fs, path::Path};

use maplit::btreemap;
use scryer_prolog::{Machine, QueryResolution, Value};
use scryer_prolog::{LeafAnswer, Machine, MachineBuilder, Term};

pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
[
Expand All @@ -10,21 +10,21 @@ pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
"benches/edges.pl", // name of the prolog module file to load. use the same file in multiple benchmarks
"independent_set_count(ky, Count).", // query to benchmark in the context of the loaded module. consider making the query adjustable to tune the run time to ~0.1s
Strategy::Reuse,
btreemap! { "Count" => Value::Integer(2869176.into()) },
btreemap! { "Count" => Term::integer(2869176) },
),
(
"numlist",
"benches/numlist.pl",
"run_numlist(1000000, Head).",
Strategy::Reuse,
btreemap! { "Head" => Value::Integer(1.into())},
btreemap! { "Head" => Term::integer(1) },
),
(
"csv_codename",
"benches/csv.pl",
"get_codename(\"0020\",Name).",
Strategy::Reuse,
btreemap! { "Name" => Value::String("SPACE".into())},
btreemap! { "Name" => Term::string("SPACE") },
),
]
.map(|b| {
Expand Down Expand Up @@ -54,7 +54,7 @@ pub struct PrologBenchmark {
pub filename: &'static str,
pub query: &'static str,
pub strategy: Strategy,
pub bindings: BTreeMap<&'static str, Value>,
pub bindings: BTreeMap<&'static str, Term>,
}

impl PrologBenchmark {
Expand All @@ -64,28 +64,34 @@ impl PrologBenchmark {
.file_stem()
.and_then(|s| s.to_str())
.unwrap();
let mut machine = Machine::new_lib();
let mut machine = MachineBuilder::default().build();
machine.load_module_string(module_name, program);
machine
}

#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
pub fn setup(&self) -> impl FnMut() -> QueryResolution {
pub fn setup(&self) -> impl FnMut() -> Vec<LeafAnswer> {
let mut machine = self.make_machine();
let query = self.query;
move || {
use criterion::black_box;
black_box(machine.run_query(black_box(query.to_string()))).unwrap()
black_box(
machine
.run_query(black_box(query))
.collect::<Result<Vec<_>, _>>()
.unwrap(),
)
}
}
}

#[cfg(test)]
mod test {

#[test]
fn validate_benchmarks() {
use super::prolog_benches;
use scryer_prolog::{QueryMatch, QueryResolution};
use scryer_prolog::LeafAnswer;
use std::{fmt::Write, fs};

struct BenchResult {
Expand All @@ -100,10 +106,13 @@ mod test {
let mut machine = r.make_machine();
let setup_inference_count = machine.get_inference_count();

let result = machine.run_query(r.query.to_string()).unwrap();
let result: Vec<_> = machine
.run_query(r.query)
.collect::<Result<_, _>>()
.unwrap();
let query_inference_count = machine.get_inference_count() - setup_inference_count;

let expected = QueryResolution::Matches(vec![QueryMatch::from(r.bindings.clone())]);
let expected = [LeafAnswer::from_bindings(r.bindings.clone())];
assert_eq!(result, expected, "validating benchmark {}", r.name);

results.push(BenchResult {
Expand Down
16 changes: 9 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
//! A free software ISO Prolog system.
#![recursion_limit = "4112"]
#![deny(missing_docs)]

#[macro_use]
extern crate static_assertions;
#[cfg(test)]
#[macro_use]
extern crate maplit;

#[macro_use]
pub(crate) mod macros;
Expand Down Expand Up @@ -46,24 +45,25 @@ use wasm_bindgen::prelude::*;
// Re-exports
pub use machine::config::*;
pub use machine::lib_machine::*;
pub use machine::parsed_results::*;
pub use machine::Machine;

/// Eval a source file in Wasm.
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn eval_code(s: &str) -> String {
use machine::mock_wam::*;

console_error_panic_hook::set_once();

let mut wam = Machine::with_test_streams();
let mut wam = MachineBuilder::default().build();
let bytes = wam.test_load_string(s);
String::from_utf8_lossy(&bytes).to_string()
}

/// The entry point for the Scryer Prolog CLI.
pub fn run_binary() -> std::process::ExitCode {
use crate::atom_table::Atom;
use crate::machine::{Machine, INTERRUPT};
use crate::machine::INTERRUPT;

#[cfg(feature = "repl")]
ctrlc::set_handler(move || {
Expand All @@ -84,7 +84,9 @@ pub fn run_binary() -> std::process::ExitCode {
.unwrap();

runtime.block_on(async move {
let mut wam = Machine::new(Default::default());
let mut wam = MachineBuilder::default()
.with_streams(StreamConfig::stdio())
.build();
wam.run_module_predicate(atom!("$toplevel"), (atom!("$repl"), 0))
})
}
189 changes: 173 additions & 16 deletions src/machine/config.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,189 @@
pub struct MachineConfig {
pub streams: StreamConfig,
pub toplevel: &'static str,
use std::borrow::Cow;

use rand::{rngs::StdRng, SeedableRng};

use crate::Machine;

use super::{
bootstrapping_compile, current_dir, import_builtin_impls, libraries, load_module, Atom,
CompilationTarget, IndexStore, ListingSource, MachineArgs, MachineState, Stream, StreamOptions,
};

/// Describes how the streams of a [`Machine`](crate::Machine) will be handled.
#[derive(Default)]
pub struct StreamConfig {
inner: StreamConfigInner,
}

impl StreamConfig {
/// Binds the input, output and error streams to stdin, stdout and stderr.
pub fn stdio() -> Self {
StreamConfig {
inner: StreamConfigInner::Stdio,
}
}

/// Binds the output stream to a memory buffer, and the error stream to stderr.
///
/// The input stream is ignored.
pub fn in_memory() -> Self {
StreamConfig {
inner: StreamConfigInner::Memory,
}
}
}

pub enum StreamConfig {
#[derive(Default)]
enum StreamConfigInner {
Stdio,
#[default]
Memory,
}

impl Default for MachineConfig {
/// Describes how a [`Machine`](crate::Machine) will be configured.
pub struct MachineBuilder {
pub(crate) streams: StreamConfig,
pub(crate) toplevel: Cow<'static, str>,
}

impl Default for MachineBuilder {
/// Defaults to using in-memory streams.
fn default() -> Self {
MachineConfig {
streams: StreamConfig::Stdio,
toplevel: include_str!("../toplevel.pl"),
MachineBuilder {
streams: Default::default(),
toplevel: default_toplevel().into(),
}
}
}

impl MachineConfig {
pub fn in_memory() -> Self {
MachineConfig {
streams: StreamConfig::Memory,
..Default::default()
}
impl MachineBuilder {
/// Creates a default configuration.
pub fn new() -> Self {
Default::default()
bakaq marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn with_toplevel(mut self, toplevel: &'static str) -> Self {
self.toplevel = toplevel;
/// Uses the given `crate::StreamConfig` in this configuration.
pub fn with_streams(mut self, streams: StreamConfig) -> Self {
self.streams = streams;
self
}

/// Uses the given toplevel in this configuration.
pub fn with_toplevel(mut self, toplevel: impl Into<Cow<'static, str>>) -> Self {
self.toplevel = toplevel.into();
self
}

/// Builds the [`Machine`](crate::Machine) from this configuration.
pub fn build(self) -> Machine {
let args = MachineArgs::new();
let mut machine_st = MachineState::new();

let (user_input, user_output, user_error) = match self.streams.inner {
StreamConfigInner::Stdio => (
Stream::stdin(&mut machine_st.arena, args.add_history),
Stream::stdout(&mut machine_st.arena),
Stream::stderr(&mut machine_st.arena),
),
StreamConfigInner::Memory => (
Stream::Null(StreamOptions::default()),
Stream::from_owned_string("".to_owned(), &mut machine_st.arena),
Stream::stderr(&mut machine_st.arena),
),
};

let mut wam = Machine {
machine_st,
indices: IndexStore::new(),
code: vec![],
user_input,
user_output,
user_error,
load_contexts: vec![],
#[cfg(feature = "ffi")]
foreign_function_table: Default::default(),
rng: StdRng::from_entropy(),
};

let mut lib_path = current_dir();

lib_path.pop();
lib_path.push("lib");

wam.add_impls_to_indices();

bootstrapping_compile(
Stream::from_static_string(
libraries::get("ops_and_meta_predicates")
.expect("library ops_and_meta_predicates should exist"),
&mut wam.machine_st.arena,
),
&mut wam,
ListingSource::from_file_and_path(
atom!("ops_and_meta_predicates.pl"),
lib_path.clone(),
),
)
.unwrap();

bootstrapping_compile(
Stream::from_static_string(
libraries::get("builtins").expect("library builtins should exist"),
&mut wam.machine_st.arena,
),
&mut wam,
ListingSource::from_file_and_path(atom!("builtins.pl"), lib_path.clone()),
)
.unwrap();

if let Some(builtins) = wam.indices.modules.get_mut(&atom!("builtins")) {
load_module(
&mut wam.machine_st,
&mut wam.indices.code_dir,
&mut wam.indices.op_dir,
&mut wam.indices.meta_predicates,
&CompilationTarget::User,
builtins,
);

import_builtin_impls(&wam.indices.code_dir, builtins);
} else {
unreachable!()
}

lib_path.pop(); // remove the "lib" at the end

bootstrapping_compile(
Stream::from_static_string(include_str!("../loader.pl"), &mut wam.machine_st.arena),
&mut wam,
ListingSource::from_file_and_path(atom!("loader.pl"), lib_path.clone()),
)
.unwrap();

wam.configure_modules();

if let Some(loader) = wam.indices.modules.get(&atom!("loader")) {
load_module(
&mut wam.machine_st,
&mut wam.indices.code_dir,
&mut wam.indices.op_dir,
&mut wam.indices.meta_predicates,
&CompilationTarget::User,
loader,
);
} else {
unreachable!()
}

wam.load_special_forms();
wam.load_top_level(self.toplevel);
wam.configure_streams();

wam
}
}

/// Returns a static string slice to the default toplevel
pub fn default_toplevel() -> &'static str {
include_str!("../toplevel.pl")
}
Loading
Loading