Skip to main content

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(&current_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(&current_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}