scryer_prolog/machine/
config.rs

1use std::borrow::Cow;
2use std::io::Write;
3use std::sync::mpsc::{channel, Receiver, Sender};
4
5use rand::{rngs::StdRng, SeedableRng};
6
7use crate::Machine;
8
9use super::{
10    bootstrapping_compile, current_dir, import_builtin_impls, libraries, load_module, Arena, Atom,
11    Callback, CompilationTarget, IndexStore, ListingSource, MachineArgs, MachineState, Stream,
12};
13
14#[derive(Default)]
15enum OutputStreamConfigInner {
16    #[default]
17    Memory,
18    Stdout,
19    Stderr,
20    Callback(Callback),
21}
22
23impl std::fmt::Debug for OutputStreamConfigInner {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            Self::Memory => write!(f, "Memory"),
27            Self::Stdout => write!(f, "Stdout"),
28            Self::Stderr => write!(f, "Stderr"),
29            Self::Callback(_) => f.debug_tuple("Callback").field(&"<callback>").finish(),
30        }
31    }
32}
33
34/// Configuration for an output stream.
35#[derive(Debug, Default)]
36pub struct OutputStreamConfig {
37    inner: OutputStreamConfigInner,
38}
39
40impl OutputStreamConfig {
41    /// Sends output to stdout.
42    pub fn stdout() -> Self {
43        Self {
44            inner: OutputStreamConfigInner::Stdout,
45        }
46    }
47
48    /// Sends output to stderr.
49    pub fn stderr() -> Self {
50        Self {
51            inner: OutputStreamConfigInner::Stderr,
52        }
53    }
54
55    /// Keeps output in a memory buffer.
56    pub fn memory() -> Self {
57        Self {
58            inner: OutputStreamConfigInner::Memory,
59        }
60    }
61
62    /// Calls a callback with the output whenever the stream is written to.
63    pub fn callback(callback: Callback) -> Self {
64        Self {
65            inner: OutputStreamConfigInner::Callback(callback),
66        }
67    }
68
69    fn into_stream(self, arena: &mut Arena) -> Stream {
70        match self.inner {
71            OutputStreamConfigInner::Memory => Stream::from_owned_string("".to_owned(), arena),
72            OutputStreamConfigInner::Stdout => Stream::stdout(arena),
73            OutputStreamConfigInner::Stderr => Stream::stderr(arena),
74            OutputStreamConfigInner::Callback(callback) => Stream::from_callback(callback, arena),
75        }
76    }
77}
78
79#[derive(Debug)]
80enum InputStreamConfigInner {
81    String(Cow<'static, str>),
82    Stdin,
83    Channel(Receiver<Vec<u8>>),
84}
85
86impl Default for InputStreamConfigInner {
87    fn default() -> Self {
88        Self::String("".into())
89    }
90}
91
92/// Configuration for an input stream;
93#[derive(Debug, Default)]
94pub struct InputStreamConfig {
95    inner: InputStreamConfigInner,
96}
97
98impl InputStreamConfig {
99    /// Gets input from string.
100    pub fn string(s: impl Into<Cow<'static, str>>) -> Self {
101        Self {
102            inner: InputStreamConfigInner::String(s.into()),
103        }
104    }
105
106    /// Gets input from stdin.
107    pub fn stdin() -> Self {
108        Self {
109            inner: InputStreamConfigInner::Stdin,
110        }
111    }
112
113    /// Connects the input to the receiving end of a channel.
114    pub fn channel() -> (UserInput, Self) {
115        let (sender, receiver) = channel();
116        (
117            UserInput { inner: sender },
118            Self {
119                inner: InputStreamConfigInner::Channel(receiver),
120            },
121        )
122    }
123
124    fn into_stream(self, arena: &mut Arena, add_history: bool) -> Stream {
125        match self.inner {
126            InputStreamConfigInner::String(s) => match s {
127                Cow::Owned(s) => Stream::from_owned_string(s, arena),
128                Cow::Borrowed(s) => Stream::from_static_string(s, arena),
129            },
130            InputStreamConfigInner::Stdin => Stream::stdin(arena, add_history),
131            InputStreamConfigInner::Channel(channel) => Stream::input_channel(channel, arena),
132        }
133    }
134}
135
136/// Describes how the streams of a [`Machine`](crate::Machine) will be handled.
137pub struct StreamConfig {
138    user_input: InputStreamConfig,
139    user_output: OutputStreamConfig,
140    user_error: OutputStreamConfig,
141}
142
143impl Default for StreamConfig {
144    fn default() -> Self {
145        Self::in_memory()
146    }
147}
148
149impl StreamConfig {
150    /// Binds the input, output and error streams to stdin, stdout and stderr.
151    pub fn stdio() -> Self {
152        StreamConfig {
153            user_input: InputStreamConfig::stdin(),
154            user_output: OutputStreamConfig::stdout(),
155            user_error: OutputStreamConfig::stderr(),
156        }
157    }
158
159    /// Binds the output and error streams to memory buffers and has an empty input.
160    pub fn in_memory() -> Self {
161        StreamConfig {
162            user_input: InputStreamConfig::string(String::new()),
163            user_output: OutputStreamConfig::memory(),
164            user_error: OutputStreamConfig::memory(),
165        }
166    }
167
168    /// Calls the given callbacks when the respective streams are written to.
169    ///
170    /// This also returns a handler to the stdin of the [`Machine`](crate::Machine).
171    pub fn from_callbacks(stdout: Option<Callback>, stderr: Option<Callback>) -> (UserInput, Self) {
172        let (user_input, channel_stream) = InputStreamConfig::channel();
173        (
174            user_input,
175            StreamConfig {
176                user_input: channel_stream,
177                user_output: stdout
178                    .map_or_else(OutputStreamConfig::memory, OutputStreamConfig::callback),
179                user_error: stderr
180                    .map_or_else(OutputStreamConfig::memory, OutputStreamConfig::callback),
181            },
182        )
183    }
184
185    /// Configures the `user_input` stream.
186    pub fn with_user_input(self, user_input: InputStreamConfig) -> Self {
187        Self { user_input, ..self }
188    }
189
190    /// Configures the `user_output` stream.
191    pub fn with_user_output(self, user_output: OutputStreamConfig) -> Self {
192        Self {
193            user_output,
194            ..self
195        }
196    }
197
198    /// Configures the `user_error` stream.
199    pub fn with_user_error(self, user_error: OutputStreamConfig) -> Self {
200        Self { user_error, ..self }
201    }
202
203    fn into_streams(self, arena: &mut Arena, add_history: bool) -> (Stream, Stream, Stream) {
204        (
205            self.user_input.into_stream(arena, add_history),
206            self.user_output.into_stream(arena),
207            self.user_error.into_stream(arena),
208        )
209    }
210}
211
212/// A handler for the stdin of the [`Machine`](crate::Machine).
213#[derive(Debug)]
214pub struct UserInput {
215    inner: Sender<Vec<u8>>,
216}
217
218impl Write for UserInput {
219    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
220        self.inner
221            .send(buf.into())
222            .map(|_| buf.len())
223            .map_err(|_| std::io::ErrorKind::BrokenPipe.into())
224    }
225
226    fn flush(&mut self) -> std::io::Result<()> {
227        Ok(())
228    }
229}
230
231/// Describes how a [`Machine`](crate::Machine) will be configured.
232pub struct MachineBuilder {
233    pub(crate) streams: StreamConfig,
234    pub(crate) toplevel: Cow<'static, str>,
235}
236
237impl Default for MachineBuilder {
238    /// Defaults to using in-memory streams.
239    fn default() -> Self {
240        MachineBuilder {
241            streams: Default::default(),
242            toplevel: default_toplevel().into(),
243        }
244    }
245}
246
247impl MachineBuilder {
248    /// Creates a default configuration.
249    pub fn new() -> Self {
250        Default::default()
251    }
252
253    /// Uses the given `crate::StreamConfig` in this configuration.
254    pub fn with_streams(mut self, streams: StreamConfig) -> Self {
255        self.streams = streams;
256        self
257    }
258
259    /// Uses the given toplevel in this configuration.
260    pub fn with_toplevel(mut self, toplevel: impl Into<Cow<'static, str>>) -> Self {
261        self.toplevel = toplevel.into();
262        self
263    }
264
265    /// Builds the [`Machine`](crate::Machine) from this configuration.
266    pub fn build(self) -> Machine {
267        let args = MachineArgs::new();
268        let mut machine_st = MachineState::new();
269
270        let (user_input, user_output, user_error) = self
271            .streams
272            .into_streams(&mut machine_st.arena, args.add_history);
273
274        let mut wam = Machine {
275            machine_st,
276            indices: IndexStore::new(),
277            code: vec![],
278            user_input,
279            user_output,
280            user_error,
281            load_contexts: vec![],
282            #[cfg(feature = "ffi")]
283            foreign_function_table: Default::default(),
284            rng: StdRng::from_entropy(),
285        };
286
287        let mut lib_path = current_dir();
288
289        lib_path.pop();
290        lib_path.push("lib");
291
292        wam.add_impls_to_indices();
293
294        bootstrapping_compile(
295            Stream::from_static_string(
296                libraries::get("ops_and_meta_predicates")
297                    .expect("library ops_and_meta_predicates should exist"),
298                &mut wam.machine_st.arena,
299            ),
300            &mut wam,
301            ListingSource::from_file_and_path(
302                atom!("ops_and_meta_predicates.pl"),
303                lib_path.clone(),
304            ),
305        )
306        .unwrap();
307
308        bootstrapping_compile(
309            Stream::from_static_string(
310                libraries::get("builtins").expect("library builtins should exist"),
311                &mut wam.machine_st.arena,
312            ),
313            &mut wam,
314            ListingSource::from_file_and_path(atom!("builtins.pl"), lib_path.clone()),
315        )
316        .unwrap();
317
318        if let Some(builtins) = wam.indices.modules.get_mut(&atom!("builtins")) {
319            load_module(
320                &mut wam.machine_st,
321                &mut wam.indices.code_dir,
322                &mut wam.indices.op_dir,
323                &mut wam.indices.meta_predicates,
324                &CompilationTarget::User,
325                builtins,
326            );
327
328            import_builtin_impls(&wam.indices.code_dir, builtins);
329        } else {
330            unreachable!()
331        }
332
333        lib_path.pop(); // remove the "lib" at the end
334
335        bootstrapping_compile(
336            Stream::from_static_string(include_str!("../loader.pl"), &mut wam.machine_st.arena),
337            &mut wam,
338            ListingSource::from_file_and_path(atom!("loader.pl"), lib_path.clone()),
339        )
340        .unwrap();
341
342        wam.configure_modules();
343
344        if let Some(loader) = wam.indices.modules.get(&atom!("loader")) {
345            load_module(
346                &mut wam.machine_st,
347                &mut wam.indices.code_dir,
348                &mut wam.indices.op_dir,
349                &mut wam.indices.meta_predicates,
350                &CompilationTarget::User,
351                loader,
352            );
353        } else {
354            unreachable!()
355        }
356
357        wam.load_special_forms();
358        wam.load_top_level(self.toplevel);
359        wam.configure_streams();
360
361        wam
362    }
363}
364
365/// Returns a static string slice to the default toplevel
366pub fn default_toplevel() -> &'static str {
367    include_str!("../toplevel.pl")
368}