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