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