mrubyedge_cli/subcommands/repl.rs
1use clap::Args;
2use crossterm::{
3 cursor,
4 event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
5 execute as cs_execute,
6 terminal::{self, ClearType},
7};
8use std::{
9 io::{self, Write},
10 rc::Rc,
11};
12
13use mruby_compiler2_sys as mrbc;
14use mrubyedge::{
15 RObject,
16 yamrb::{helpers::mrb_call_inspect, value::RHashMap},
17};
18
19#[derive(Args)]
20pub struct ReplArgs {
21 /// Show verbose output
22 #[arg(short = 'v', long)]
23 pub verbose: bool,
24}
25
26pub fn execute(args: ReplArgs) -> Result<(), Box<dyn std::error::Error>> {
27 eprintln!("mruby/edge REPL ({})", mrubyedge::version!());
28 eprintln!("Type 'exit[↩]' or press Ctrl+D to quit");
29 eprintln!(
30 "Press Enter to execute, Option+Enter(Shift+Enter also supported in iTerm2) for line continuation"
31 );
32 eprintln!();
33
34 // Initialize VM with empty rite
35 let empty_code = "";
36 let mrb_bin = unsafe {
37 let mut ctx = mrbc::MRubyCompiler2Context::new();
38 ctx.compile(empty_code)?
39 };
40 let mut rite = mrubyedge::rite::load(&mrb_bin)?;
41 let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
42
43 // Enable raw mode
44 terminal::enable_raw_mode()?;
45 let mut stdout = io::stdout();
46
47 let mut line_number = 1;
48 let mut buffer = String::new();
49 let mut current_line = String::new();
50
51 // Print initial prompt
52 print!("repl:{:03}> ", line_number);
53 stdout.flush()?;
54 let mut top_level_lvars: RHashMap<String, Rc<RObject>> = RHashMap::default();
55
56 let mut ctx = unsafe { mrbc::MRubyCompiler2Context::new() };
57
58 let result = (|| -> Result<(), Box<dyn std::error::Error>> {
59 loop {
60 // Read key event
61 if let Event::Key(key_event) = event::read()? {
62 match key_event {
63 // Ctrl+D - Exit
64 KeyEvent {
65 code: KeyCode::Char('d'),
66 modifiers: KeyModifiers::CONTROL,
67 ..
68 } => {
69 terminal::disable_raw_mode()?;
70 println!();
71 break;
72 }
73 // Ctrl+C - Clear current line
74 KeyEvent {
75 code: KeyCode::Char('c'),
76 modifiers: KeyModifiers::CONTROL,
77 ..
78 } => {
79 terminal::disable_raw_mode()?;
80 println!();
81 current_line.clear();
82 buffer.clear();
83 line_number += 1;
84 print!("repl:{:03}> ", line_number);
85 stdout.flush()?;
86 terminal::enable_raw_mode()?;
87 }
88 // Alt+Enter (Option+Enter) - Add line to buffer and continue
89 KeyEvent {
90 code: KeyCode::Enter,
91 modifiers: KeyModifiers::ALT | KeyModifiers::SHIFT,
92 ..
93 } => {
94 terminal::disable_raw_mode()?;
95 println!();
96 buffer.push_str(¤t_line);
97 buffer.push('\n');
98 current_line.clear();
99 print!("repl:{:03}* ", line_number);
100 stdout.flush()?;
101 terminal::enable_raw_mode()?;
102 }
103 // Regular Enter - Execute buffer
104 KeyEvent {
105 code: KeyCode::Enter,
106 modifiers: KeyModifiers::NONE,
107 ..
108 } => {
109 terminal::disable_raw_mode()?;
110 println!();
111
112 // Add current line to buffer
113 if !current_line.is_empty() {
114 buffer.push_str(¤t_line);
115 buffer.push('\n');
116 }
117
118 if buffer.trim().is_empty() {
119 current_line.clear();
120 print!("repl:{:03}> ", line_number);
121 stdout.flush()?;
122 terminal::enable_raw_mode()?;
123 continue;
124 }
125
126 // Check for exit command
127 let trimmed = buffer.trim();
128 if trimmed == "exit" || trimmed == "quit" {
129 break;
130 }
131
132 // Execute buffered code
133 unsafe {
134 if args.verbose {
135 ctx.dump_bytecode(&buffer).unwrap();
136 }
137 match ctx.compile(&buffer) {
138 Ok(mrb_bin) => match mrubyedge::rite::load(&mrb_bin) {
139 Ok(mut new_rite) => {
140 // FIXME: sub ireps's lv not handled yet
141 let top_rep = &new_rite.irep[0];
142 for (reg, name) in top_rep.lv.iter().enumerate() {
143 if let Some(name) = name
144 && let Some(value) = top_level_lvars
145 .get(&name.to_string_lossy().to_string())
146 {
147 vm.regs[reg + 1] = value.clone().into();
148 }
149 }
150 match vm.eval_rite(&mut new_rite) {
151 Ok(result) => match mrb_call_inspect(&mut vm, result) {
152 Ok(inspect_result) => {
153 match TryInto::<String>::try_into(
154 inspect_result.as_ref(),
155 ) {
156 Ok(s) => println!(" => {}", s),
157 Err(_) => println!(" => <unprintable>"),
158 }
159 }
160 Err(_) => println!(" => <inspect failed>"),
161 },
162 Err(e) => {
163 eprintln!("{:?}", e);
164 vm.exception.take();
165 }
166 }
167 // Display top-level local variables
168 if let Some(lv) = &vm.current_irep.lv {
169 for (reg, name) in lv.iter() {
170 let value =
171 vm.regs[*reg].as_ref().cloned().unwrap_or(
172 RObject::nil().to_refcount_assigned(),
173 );
174 top_level_lvars
175 .insert(name.to_string(), value.clone());
176 }
177 for (k, v) in top_level_lvars.iter() {
178 let inspect: String =
179 mrb_call_inspect(&mut vm, v.clone())
180 .unwrap()
181 .as_ref()
182 .try_into()
183 .unwrap();
184 if args.verbose {
185 eprintln!(" [lv] {} => {}", k, inspect);
186 }
187 }
188 }
189 }
190 Err(e) => {
191 eprintln!("Failed to load bytecode: {:?}", e);
192 }
193 },
194 Err(e) => {
195 eprintln!("Compilation error: {}", e);
196 }
197 }
198 }
199
200 buffer.clear();
201 current_line.clear();
202 line_number += 1;
203 print!("repl:{:03}> ", line_number);
204 stdout.flush()?;
205
206 terminal::enable_raw_mode()?;
207 }
208 // Backspace
209 KeyEvent {
210 code: KeyCode::Backspace,
211 ..
212 } => {
213 if !current_line.is_empty() {
214 current_line.pop();
215 cs_execute!(
216 stdout,
217 cursor::MoveLeft(1),
218 terminal::Clear(ClearType::UntilNewLine)
219 )?;
220 stdout.flush()?;
221 }
222 }
223 // Regular character input
224 KeyEvent {
225 code: KeyCode::Char(c),
226 modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
227 ..
228 } => {
229 current_line.push(c);
230 print!("{}", c);
231 stdout.flush()?;
232 }
233 _ => {
234 // Ignore unhandled key events
235 }
236 }
237 }
238 }
239
240 Ok(())
241 })();
242
243 // Disable raw mode
244 terminal::disable_raw_mode()?;
245
246 if args.verbose {
247 eprintln!("REPL session ended");
248 }
249
250 result
251}