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, stderr: Option) -> (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>>>, } impl Write for UserInput { fn write(&mut self, buf: &[u8]) -> std::io::Result { 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>>>, stdout: Option, stderr: Option, }, } /// 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>) -> 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") }