1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use mruby::eval::{EvalContext, MrbEval};
use mruby::gc::MrbGarbageCollection;
use mruby::sys;
use mruby::MrbError;
use rustyline::error::ReadlineError;
use rustyline::Editor;
use std::io::{self, Write};
use crate::parser::{self, Parser, State};
const REPL_FILENAME: &str = "(rirb)";
#[derive(Debug)]
pub enum Error {
Fatal,
ReplInit,
ReplParse(parser::Error),
Ruby(MrbError),
Io(io::Error),
}
pub struct PromptConfig {
pub simple: String,
pub continued: String,
pub result_prefix: String,
}
impl Default for PromptConfig {
fn default() -> Self {
Self {
simple: ">>> ".to_owned(),
continued: "... ".to_owned(),
result_prefix: "=> ".to_owned(),
}
}
}
fn preamble() -> Result<String, Error> {
let mut buf = String::new();
let metadata = rustc_version::version_meta().map_err(|_| Error::ReplInit)?;
buf.push_str(sys::mruby_sys_version(true).as_str());
buf.push('\n');
buf.push('[');
buf.push_str(format!("Compiled with rustc {}", metadata.semver).as_str());
if let Some(mut commit) = metadata.commit_hash {
commit.truncate(7);
buf.push(' ');
buf.push_str(commit.as_str());
}
if let Some(date) = metadata.commit_date {
buf.push(' ');
buf.push_str(date.as_str());
}
buf.push(']');
Ok(buf)
}
pub fn run(
mut output: impl Write,
mut error: impl Write,
config: Option<PromptConfig>,
) -> Result<(), Error> {
writeln!(output, "{}", preamble()?).map_err(Error::Io)?;
let config = config.unwrap_or_else(Default::default);
let interp = mruby::interpreter().map_err(Error::Ruby)?;
mruby_gems::rubygems::rack::init(&interp).map_err(Error::Ruby)?;
mruby_gems::rubygems::mustermann::init(&interp).map_err(Error::Ruby)?;
let parser = Parser::new(&interp).ok_or(Error::ReplInit)?;
interp.push_context(EvalContext::new(REPL_FILENAME));
unsafe {
let api = interp.borrow();
(*api.ctx).lineno = 1;
}
let mut rl = Editor::<()>::new();
let mut buf = String::new();
let mut parser_state = State::default();
loop {
let prompt = if parser_state.is_code_block_open() {
config.continued.as_str()
} else {
config.simple.as_str()
};
let readline = rl.readline(prompt);
match readline {
Ok(line) => {
buf.push_str(line.as_str());
parser_state = parser.parse(buf.as_str()).map_err(Error::ReplParse)?;
if parser_state.is_code_block_open() {
buf.push('\n');
continue;
}
match interp.eval(buf.as_str()) {
Ok(value) => writeln!(output, "{}{}", config.result_prefix, value.inspect())
.map_err(Error::Io)?,
Err(MrbError::Exec(backtrace)) => {
writeln!(error, "Backtrace:").map_err(Error::Io)?;
for frame in backtrace.lines() {
writeln!(error, " {}", frame).map_err(Error::Io)?;
}
}
Err(err) => return Err(Error::Ruby(err)),
}
for line in buf.lines() {
rl.add_history_entry(line);
unsafe {
let api = interp.borrow();
(*api.ctx).lineno += 1;
}
}
interp.incremental_gc();
buf.clear();
}
Err(ReadlineError::Interrupted) => {
buf.clear();
parser_state = State::default();
writeln!(output, "^C").map_err(Error::Io)?;
continue;
}
Err(ReadlineError::Eof) => break,
Err(_) => return Err(Error::Fatal),
};
}
Ok(())
}