spore_vm/
repl.rs

1use rustyline::DefaultEditor;
2
3use crate::{parser::ast::Node, val::ProtectedVal, Vm};
4
5/// Run an read/evaluate/print/loop.
6///
7/// ```rust
8/// fn main() -> Result<(), Box<dyn std::error::Error>> {
9///     let mut repl = spore_vm::repl::Repl::new(spore_vm::Vm::default())?;
10///     loop {
11///         if let Err(err) = repl.eval_next_input() {
12///             match err {
13///                 rustyline::error::ReadlineError::Eof
14///                 | rustyline::error::ReadlineError::Interrupted => return Ok(()),
15///                 err => println!("{err}"),
16///             }
17///         }
18///     }
19/// }
20/// ```
21pub struct Repl {
22    vm: Vm,
23    editor: DefaultEditor,
24}
25
26impl Repl {
27    /// Create a new REPL.
28    pub fn new(vm: Vm) -> rustyline::Result<Repl> {
29        let editor = DefaultEditor::new()?;
30        Ok(Repl { vm, editor })
31    }
32
33    /// Convert `self` into a [Vm].
34    pub fn into_vm(self) -> Vm {
35        self.vm
36    }
37
38    /// Get the underlying [Vm].
39    pub fn as_vm_mut(&mut self) -> &mut Vm {
40        &mut self.vm
41    }
42
43    /// Get the underlying [Vm].
44    pub fn as_vm(&self) -> &Vm {
45        &self.vm
46    }
47
48    /// Evaluate the next user input. The input is read through `stdin` and the result is written
49    /// through `stdout`.
50    pub fn eval_next_input(&mut self) -> rustyline::Result<ProtectedVal<'_>> {
51        let mut input = String::new();
52        fn input_is_ready(input: &str) -> rustyline::Result<bool> {
53            if input.is_empty() {
54                return Ok(false);
55            }
56            for node_or_err in Node::parse(&input) {
57                match node_or_err {
58                    Ok(_) => {}
59                    Err(crate::parser::ast::AstParseError::UnclosedParen) => {
60                        return Ok(false);
61                    }
62                    Err(err) => {
63                        return rustyline::Result::Err(rustyline::error::ReadlineError::Io(
64                            std::io::Error::new(std::io::ErrorKind::InvalidInput, err),
65                        ))
66                    }
67                }
68            }
69            Ok(true)
70        }
71        while !input_is_ready(&input)? {
72            let prompt = if input.is_empty() { ">> " } else { ".. " };
73            match self.editor.readline(prompt) {
74                Ok(line) => input.push_str(line.as_str()),
75                Err(err) => return Err(err),
76            };
77        }
78        let res = self
79            .vm
80            .eval_str(&input)
81            .inspect(|v| println!("{v}"))
82            .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err).into());
83        let _ = self.editor.add_history_entry(input);
84        res
85    }
86}