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#[derive(Debug, Default)]
36pub struct OutputStreamConfig {
37 inner: OutputStreamConfigInner,
38}
39
40impl OutputStreamConfig {
41 pub fn stdout() -> Self {
43 Self {
44 inner: OutputStreamConfigInner::Stdout,
45 }
46 }
47
48 pub fn stderr() -> Self {
50 Self {
51 inner: OutputStreamConfigInner::Stderr,
52 }
53 }
54
55 pub fn memory() -> Self {
57 Self {
58 inner: OutputStreamConfigInner::Memory,
59 }
60 }
61
62 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#[derive(Debug, Default)]
94pub struct InputStreamConfig {
95 inner: InputStreamConfigInner,
96}
97
98impl InputStreamConfig {
99 pub fn string(s: impl Into<Cow<'static, str>>) -> Self {
101 Self {
102 inner: InputStreamConfigInner::String(s.into()),
103 }
104 }
105
106 pub fn stdin() -> Self {
108 Self {
109 inner: InputStreamConfigInner::Stdin,
110 }
111 }
112
113 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
136pub 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 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 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 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 pub fn with_user_input(self, user_input: InputStreamConfig) -> Self {
187 Self { user_input, ..self }
188 }
189
190 pub fn with_user_output(self, user_output: OutputStreamConfig) -> Self {
192 Self {
193 user_output,
194 ..self
195 }
196 }
197
198 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#[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
231pub struct MachineBuilder {
233 pub(crate) streams: StreamConfig,
234 pub(crate) toplevel: Cow<'static, str>,
235}
236
237impl Default for MachineBuilder {
238 fn default() -> Self {
240 MachineBuilder {
241 streams: Default::default(),
242 toplevel: default_toplevel().into(),
243 }
244 }
245}
246
247impl MachineBuilder {
248 pub fn new() -> Self {
250 Default::default()
251 }
252
253 pub fn with_streams(mut self, streams: StreamConfig) -> Self {
255 self.streams = streams;
256 self
257 }
258
259 pub fn with_toplevel(mut self, toplevel: impl Into<Cow<'static, str>>) -> Self {
261 self.toplevel = toplevel.into();
262 self
263 }
264
265 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(); 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
365pub fn default_toplevel() -> &'static str {
367 include_str!("../toplevel.pl")
368}