Skip to main content

nodejs/
repl.rs

1//! Interactive REPL (`node --repl`, or `node` on a TTY).
2//!
3//! Keeps one persistent host across lines (module globals, `var`/`function`
4//! declarations, classes and requires survive between prompts). A line whose
5//! braces/parens/brackets are unbalanced accumulates continuation lines until the
6//! delimiters close, so multi-line functions and objects can be entered.
7//!
8//! No startup banner is printed — the prompt appears immediately (house rule).
9
10use nu_ansi_term::Color;
11use reedline::{DefaultPrompt, DefaultPromptSegment, Reedline, Signal};
12
13/// Run the REPL loop.
14pub fn run() {
15    crate::host::reset_host();
16    let mut line_editor = Reedline::create();
17    let prompt = DefaultPrompt::new(
18        DefaultPromptSegment::Basic("> ".to_string()),
19        DefaultPromptSegment::Empty,
20    );
21
22    loop {
23        match line_editor.read_line(&prompt) {
24            Ok(Signal::Success(mut buffer)) => {
25                if buffer.trim().is_empty() {
26                    continue;
27                }
28                // Accumulate continuation lines while delimiters stay open.
29                let cont_prompt = DefaultPrompt::new(
30                    DefaultPromptSegment::Basic("... ".to_string()),
31                    DefaultPromptSegment::Empty,
32                );
33                while unbalanced(&buffer) {
34                    match line_editor.read_line(&cont_prompt) {
35                        Ok(Signal::Success(more)) => {
36                            buffer.push('\n');
37                            buffer.push_str(&more);
38                        }
39                        _ => break,
40                    }
41                }
42                run_line(&buffer);
43            }
44            Ok(Signal::CtrlC) => continue,
45            Ok(Signal::CtrlD) => break,
46            Ok(_) => continue,
47            Err(_) => break,
48        }
49    }
50}
51
52/// True while the buffer has more open `{`/`(`/`[` than close (ignoring
53/// delimiters inside string/template/char literals). A coarse check — good
54/// enough to know whether to keep reading continuation lines.
55fn unbalanced(s: &str) -> bool {
56    let mut depth: i32 = 0;
57    let mut quote: Option<char> = None;
58    let mut escaped = false;
59    for c in s.chars() {
60        if let Some(q) = quote {
61            if escaped {
62                escaped = false;
63            } else if c == '\\' {
64                escaped = true;
65            } else if c == q {
66                quote = None;
67            }
68            continue;
69        }
70        match c {
71            '"' | '\'' | '`' => quote = Some(c),
72            '{' | '(' | '[' => depth += 1,
73            '}' | ')' | ']' => depth -= 1,
74            _ => {}
75        }
76    }
77    depth > 0
78}
79
80fn run_line(src: &str) {
81    match crate::compile(src) {
82        Ok(prog) => match crate::run_compiled(prog) {
83            Ok(_) => {}
84            Err(e) => eprintln!("{}", Color::Red.paint(e)),
85        },
86        Err(e) => eprintln!("{}", Color::Red.paint(e)),
87    }
88}