1use std::error;
8use std::fmt;
9use std::io;
10use std::sync::PoisonError;
11
12use artichoke_readline::{get_readline_edit_mode, rl_read_init_file};
13use artichoke_repl_history::repl_history_file;
14use rustyline::Editor;
15use rustyline::config::Builder;
16use rustyline::error::ReadlineError;
17use rustyline::history::FileHistory;
18use termcolor::WriteColor;
19
20use crate::backend::state::parser::Context;
21use crate::backtrace;
22use crate::filename::REPL;
23use crate::parser::repl::Parser;
24use crate::prelude::{Parser as _, *};
25
26#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
30pub struct ParserAllocError {
31 _private: (),
32}
33
34impl ParserAllocError {
35 #[must_use]
37 pub const fn new() -> Self {
38 Self { _private: () }
39 }
40}
41
42impl fmt::Display for ParserAllocError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 f.write_str("Failed to initialize Ruby parser")
45 }
46}
47
48impl error::Error for ParserAllocError {}
49
50#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
52pub struct ParserLineCountError {
53 _private: (),
54}
55
56impl ParserLineCountError {
57 #[must_use]
59 pub const fn new() -> Self {
60 Self { _private: () }
61 }
62}
63
64impl fmt::Display for ParserLineCountError {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.write_str("The interpreter has parsed too many lines and must exit")
67 }
68}
69
70impl error::Error for ParserLineCountError {}
71
72#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
76pub struct ParserInternalError {
77 _private: (),
78}
79
80impl ParserInternalError {
81 #[must_use]
83 pub const fn new() -> Self {
84 Self { _private: () }
85 }
86}
87
88impl fmt::Display for ParserInternalError {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 f.write_str("A fatal parsing error occurred")
91 }
92}
93
94impl error::Error for ParserInternalError {}
95
96#[derive(Debug)]
98struct UnhandledReadlineError(ReadlineError);
99
100impl fmt::Display for UnhandledReadlineError {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 write!(f, "Unhandled REPL Readline error: {}", self.0)
103 }
104}
105
106impl error::Error for UnhandledReadlineError {
107 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
108 Some(&self.0)
109 }
110}
111
112#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
114pub struct PromptConfig<'a, 'b, 'c> {
115 pub simple: &'a str,
117 pub continued: &'b str,
119 pub result_prefix: &'c str,
122}
123
124impl Default for PromptConfig<'_, '_, '_> {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130impl PromptConfig<'_, '_, '_> {
131 #[must_use]
154 pub const fn new() -> Self {
155 Self {
156 simple: ">>> ",
157 continued: "... ",
158 result_prefix: "=> ",
159 }
160 }
161}
162
163fn preamble(interp: &mut Artichoke) -> Result<String, Error> {
169 let description = interp.eval(b"RUBY_DESCRIPTION")?.try_convert_into_mut::<&str>(interp)?;
170 let compiler = interp
171 .eval(b"ARTICHOKE_COMPILER_VERSION")?
172 .try_convert_into_mut::<&str>(interp)?;
173 let mut buf = String::with_capacity(description.len() + 2 + compiler.len() + 1);
174 buf.push_str(description);
175 buf.push_str("\n[");
176 buf.push_str(compiler);
177 buf.push(']');
178 Ok(buf)
179}
180
181fn init<W>(interp: &mut Artichoke, mut output: W) -> Result<(), Box<dyn error::Error>>
185where
186 W: io::Write,
187{
188 writeln!(&mut output, "{}", preamble(interp)?)?;
189
190 interp.reset_parser()?;
191 let context = unsafe { Context::new_unchecked(REPL.to_vec()) };
193 interp.push_context(context)?;
194
195 Ok(())
196}
197
198pub fn run<Wout, Werr>(
215 output: Wout,
216 error: Werr,
217 config: Option<PromptConfig<'_, '_, '_>>,
218) -> Result<(), Box<dyn error::Error>>
219where
220 Wout: io::Write,
221 Werr: io::Write + WriteColor,
222{
223 let mut interp = crate::interpreter()?;
224 let result = entrypoint(&mut interp, output, error, config);
230 interp.close();
232 result
233}
234
235fn entrypoint<Wout, Werr>(
236 interp: &mut Artichoke,
237 mut output: Wout,
238 error: Werr,
239 config: Option<PromptConfig<'_, '_, '_>>,
240) -> Result<(), Box<dyn error::Error>>
241where
242 Wout: io::Write,
243 Werr: io::Write + WriteColor,
244{
245 init(interp, &mut output)?;
247
248 let mut editor_config = Builder::new();
251 if let Some(inputrc_config) = rl_read_init_file() {
252 if let Some(edit_mode) = get_readline_edit_mode(inputrc_config) {
253 editor_config = editor_config.edit_mode(edit_mode.into());
254 }
255 }
256
257 let mut rl =
259 Editor::<Parser<'_>, FileHistory>::with_config(editor_config.build()).map_err(UnhandledReadlineError)?;
260
261 let parser = Parser::new(interp).ok_or_else(ParserAllocError::new)?;
271 rl.set_helper(Some(parser));
272
273 let hist_file = repl_history_file();
275 if let Some(ref hist_file) = hist_file {
276 let _ignored = rl.load_history(hist_file);
279 }
280
281 let result = repl_loop(&mut rl, output, error, &config.unwrap_or_default());
283
284 if let Some(ref hist_file) = hist_file {
286 let _ignored = rl.save_history(hist_file);
289 }
290
291 result
292}
293
294fn repl_loop<Wout, Werr>(
295 rl: &mut Editor<Parser<'_>, FileHistory>,
296 mut output: Wout,
297 mut error: Werr,
298 config: &PromptConfig<'_, '_, '_>,
299) -> Result<(), Box<dyn error::Error>>
300where
301 Wout: io::Write,
302 Werr: io::Write + WriteColor,
303{
304 loop {
305 let readline = rl.readline(config.simple);
306 match readline {
307 Ok(input) if input.is_empty() => {}
308 Ok(input) if input == "exit" || input == "exit()" => {
310 rl.add_history_entry(input)?;
311 break;
312 }
313 Ok(input) => {
314 eval_single_input(rl, &mut output, &mut error, config, &input)?;
317 rl.add_history_entry(input)?;
318 }
319 Err(ReadlineError::Interrupted) => {
321 writeln!(output, "^C")?;
322 }
323 Err(ReadlineError::Eof) => break,
325 Err(err) => return Err(Box::new(UnhandledReadlineError(err))),
326 };
327 }
328 Ok(())
329}
330
331fn eval_single_input<Wout, Werr>(
332 rl: &mut Editor<Parser<'_>, FileHistory>,
333 mut output: Wout,
334 error: Werr,
335 config: &PromptConfig<'_, '_, '_>,
336 input: &str,
337) -> Result<(), Box<dyn error::Error>>
338where
339 Wout: io::Write,
340 Werr: io::Write + WriteColor,
341{
342 let parser = rl.helper().ok_or_else(ParserAllocError::new)?;
343 let mut lock = parser.inner.lock().unwrap_or_else(PoisonError::into_inner);
344 let interp = lock.interp();
345
346 match interp.eval(input.as_bytes()) {
347 Ok(_) if input.bytes().last() == Some(b';') => {}
360 Ok(value) => {
370 let result = value.inspect(interp);
371 output.write_all(config.result_prefix.as_bytes())?;
372 output.write_all(result.as_slice())?;
373 output.write_all(b"\n")?;
374 }
375 Err(ref exc) => backtrace::format_repl_trace_into(error, interp, exc)?,
376 }
377
378 interp
379 .add_fetch_lineno(input.lines().count())
380 .map_err(|_| ParserLineCountError::new())?;
381
382 interp.incremental_gc()?;
384
385 Ok(())
386}