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,
6    terminal::{self, ClearType},
7};
8use std::io::{self, Write};
9
10use mruby_compiler2_sys as mrbc;
11use mrubyedge::yamrb::helpers::mrb_call_inspect;
12
13#[derive(Args)]
14pub struct ReplArgs {
15    /// Show verbose output
16    #[arg(short = 'v', long)]
17    pub verbose: bool,
18}
19
20pub fn execute(args: ReplArgs) -> Result<(), Box<dyn std::error::Error>> {
21    eprintln!("mruby/edge REPL ({})", mrubyedge::version!());
22    eprintln!("Type 'exit[↩]' or press Ctrl+D to quit");
23    eprintln!(
24        "Press Enter to execute, Option+Enter(Shift+Enter also supported in iTerm2) for line continuation"
25    );
26    eprintln!();
27
28    // Initialize VM with empty rite
29    let empty_code = "";
30    let mrb_bin = unsafe {
31        let mut ctx = mrbc::MRubyCompiler2Context::new();
32        ctx.compile(empty_code)?
33    };
34    let mut rite = mrubyedge::rite::load(&mrb_bin)?;
35    let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
36
37    // Enable raw mode
38    terminal::enable_raw_mode()?;
39    let mut stdout = io::stdout();
40
41    let mut line_number = 1;
42    let mut buffer = String::new();
43    let mut current_line = String::new();
44
45    // Print initial prompt
46    print!("repl:{:03}> ", line_number);
47    stdout.flush()?;
48
49    let result = (|| -> Result<(), Box<dyn std::error::Error>> {
50        loop {
51            // Read key event
52            if let Event::Key(key_event) = event::read()? {
53                match key_event {
54                    // Ctrl+D - Exit
55                    KeyEvent {
56                        code: KeyCode::Char('d'),
57                        modifiers: KeyModifiers::CONTROL,
58                        ..
59                    } => {
60                        terminal::disable_raw_mode()?;
61                        println!();
62                        break;
63                    }
64                    // Ctrl+C - Clear current line
65                    KeyEvent {
66                        code: KeyCode::Char('c'),
67                        modifiers: KeyModifiers::CONTROL,
68                        ..
69                    } => {
70                        terminal::disable_raw_mode()?;
71                        println!();
72                        current_line.clear();
73                        buffer.clear();
74                        line_number += 1;
75                        print!("repl:{:03}> ", line_number);
76                        stdout.flush()?;
77                        terminal::enable_raw_mode()?;
78                    }
79                    // Alt+Enter (Option+Enter) - Add line to buffer and continue
80                    KeyEvent {
81                        code: KeyCode::Enter,
82                        modifiers: KeyModifiers::ALT | KeyModifiers::SHIFT,
83                        ..
84                    } => {
85                        terminal::disable_raw_mode()?;
86                        println!();
87                        buffer.push_str(&current_line);
88                        buffer.push('\n');
89                        current_line.clear();
90                        print!("repl:{:03}* ", line_number);
91                        stdout.flush()?;
92                        terminal::enable_raw_mode()?;
93                    }
94                    // Regular Enter - Execute buffer
95                    KeyEvent {
96                        code: KeyCode::Enter,
97                        modifiers: KeyModifiers::NONE,
98                        ..
99                    } => {
100                        terminal::disable_raw_mode()?;
101                        println!();
102
103                        // Add current line to buffer
104                        if !current_line.is_empty() {
105                            buffer.push_str(&current_line);
106                            buffer.push('\n');
107                        }
108
109                        if buffer.trim().is_empty() {
110                            current_line.clear();
111                            print!("repl:{:03}> ", line_number);
112                            stdout.flush()?;
113                            terminal::enable_raw_mode()?;
114                            continue;
115                        }
116
117                        // Check for exit command
118                        let trimmed = buffer.trim();
119                        if trimmed == "exit" || trimmed == "quit" {
120                            break;
121                        }
122
123                        // Execute buffered code
124                        unsafe {
125                            let mut ctx = mrbc::MRubyCompiler2Context::new();
126                            match ctx.compile(&buffer) {
127                                Ok(mrb_bin) => match mrubyedge::rite::load(&mrb_bin) {
128                                    Ok(mut new_rite) => match vm.eval_rite(&mut new_rite) {
129                                        Ok(result) => match mrb_call_inspect(&mut vm, result) {
130                                            Ok(inspect_result) => {
131                                                match TryInto::<String>::try_into(
132                                                    inspect_result.as_ref(),
133                                                ) {
134                                                    Ok(s) => println!(" => {}", s),
135                                                    Err(_) => println!(" => <unprintable>"),
136                                                }
137                                            }
138                                            Err(_) => println!(" => <inspect failed>"),
139                                        },
140                                        Err(e) => {
141                                            eprintln!("{:?}", e);
142                                            vm.exception.take();
143                                        }
144                                    },
145                                    Err(e) => {
146                                        eprintln!("Failed to load bytecode: {:?}", e);
147                                    }
148                                },
149                                Err(e) => {
150                                    eprintln!("Compilation error: {}", e);
151                                }
152                            }
153                        }
154
155                        buffer.clear();
156                        current_line.clear();
157                        line_number += 1;
158                        print!("repl:{:03}> ", line_number);
159                        stdout.flush()?;
160
161                        terminal::enable_raw_mode()?;
162                    }
163                    // Backspace
164                    KeyEvent {
165                        code: KeyCode::Backspace,
166                        ..
167                    } => {
168                        if !current_line.is_empty() {
169                            current_line.pop();
170                            execute!(
171                                stdout,
172                                cursor::MoveLeft(1),
173                                terminal::Clear(ClearType::UntilNewLine)
174                            )?;
175                            stdout.flush()?;
176                        }
177                    }
178                    // Regular character input
179                    KeyEvent {
180                        code: KeyCode::Char(c),
181                        modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
182                        ..
183                    } => {
184                        current_line.push(c);
185                        print!("{}", c);
186                        stdout.flush()?;
187                    }
188                    _ => {
189                        // Ignore unhandled key events
190                    }
191                }
192            }
193        }
194
195        Ok(())
196    })();
197
198    // Disable raw mode
199    terminal::disable_raw_mode()?;
200
201    if args.verbose {
202        eprintln!("REPL session ended");
203    }
204
205    result
206}