256 lines
7.4 KiB
Rust
256 lines
7.4 KiB
Rust
use std::cell::RefCell;
|
|
use std::io::{Seek, SeekFrom, Write};
|
|
use std::rc::Rc;
|
|
use std::{borrow::Cow, io::Cursor};
|
|
|
|
use rand::{rngs::StdRng, SeedableRng};
|
|
|
|
use crate::Machine;
|
|
|
|
use super::{
|
|
bootstrapping_compile, current_dir, import_builtin_impls, libraries, load_module, Atom,
|
|
Callback, 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,
|
|
}
|
|
}
|
|
|
|
/// Calls the given callbacks when the respective streams are written to.
|
|
///
|
|
/// This also returns a handler to the stdin do the [`Machine`](crate::Machine).
|
|
pub fn with_callbacks(stdout: Option<Callback>, stderr: Option<Callback>) -> (UserInput, Self) {
|
|
let stdin = Rc::new(RefCell::new(Cursor::new(Vec::new())));
|
|
(
|
|
UserInput {
|
|
inner: stdin.clone(),
|
|
},
|
|
StreamConfig {
|
|
inner: StreamConfigInner::Callbacks {
|
|
stdin,
|
|
stdout,
|
|
stderr,
|
|
},
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
/// A handler for the stdin of the [`Machine`](crate::Machine).
|
|
#[derive(Debug)]
|
|
pub struct UserInput {
|
|
inner: Rc<RefCell<Cursor<Vec<u8>>>>,
|
|
}
|
|
|
|
impl Write for UserInput {
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
let mut inner = self.inner.borrow_mut();
|
|
let pos = inner.position();
|
|
|
|
inner.seek(SeekFrom::End(0))?;
|
|
let result = inner.write(buf);
|
|
inner.seek(SeekFrom::Start(pos))?;
|
|
|
|
result
|
|
}
|
|
|
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
self.inner.borrow_mut().flush()
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
enum StreamConfigInner {
|
|
Stdio,
|
|
#[default]
|
|
Memory,
|
|
Callbacks {
|
|
stdin: Rc<RefCell<Cursor<Vec<u8>>>>,
|
|
stdout: Option<Callback>,
|
|
stderr: Option<Callback>,
|
|
},
|
|
}
|
|
|
|
/// 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 {
|
|
MachineBuilder {
|
|
streams: Default::default(),
|
|
toplevel: default_toplevel().into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MachineBuilder {
|
|
/// Creates a default configuration.
|
|
pub fn new() -> Self {
|
|
Default::default()
|
|
}
|
|
|
|
/// 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),
|
|
),
|
|
StreamConfigInner::Callbacks {
|
|
stdin,
|
|
stdout,
|
|
stderr,
|
|
} => (
|
|
Stream::input_channel(stdin, &mut machine_st.arena),
|
|
stdout.map_or_else(
|
|
|| Stream::Null(StreamOptions::default()),
|
|
|x| Stream::from_callback(x, &mut machine_st.arena),
|
|
),
|
|
stderr.map_or_else(
|
|
|| Stream::Null(StreamOptions::default()),
|
|
|x| Stream::from_callback(x, &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")
|
|
}
|