rust_bf/commands/
repl.rs

1use std::io::{self, IsTerminal, Write};
2use clap::Args;
3
4use crate::repl::{execute_bare_once, repl_loop, select_mode, ReplMode, ModeFlagOverride};
5
6#[derive(Args, Debug)]
7#[command(disable_help_flag = true)]
8pub struct ReplArgs {
9    /// Force non-interactive bare mode
10    #[arg(long = "bare", conflicts_with = "editor")]
11    pub bare: bool,
12
13    /// Force interactive mode (errors if stdin is not a TTY)
14    #[arg(long = "editor", conflicts_with = "bare")]
15    pub editor: bool,
16
17    /// Show this help
18    #[arg(short = 'h', long = "help", action = clap::ArgAction::SetTrue)]
19    help: bool,
20}
21
22
23// Public entry point for the REPL from main.rs
24pub fn run(program: &str, help: bool, mode_flag: ModeFlagOverride) -> i32 {
25    if help {
26        usage_and_exit(program, 0);
27    }
28
29    // Determine mode: flags -> env -> auto-detect via is_terminal()
30    let mode = match select_mode(mode_flag) {
31        Ok(m) => m,
32        Err(msg) => {
33            eprintln!("{program}: {msg}");
34            let _ = io::stderr().flush();
35            return 1;
36        }
37    };
38
39    // Install SIGINT (ctrl+c) handler to flush and exit(0) immediately
40    if let Err(e) = ctrlc::set_handler(|| {
41        let _ = io::stdout().flush();
42        let _ = io::stderr().flush();
43        std::process::exit(0);
44    }) {
45        eprintln!("{program}: failed to set ctrl+c handler: {e}");
46        let _ = io::stderr().flush();
47        return 1;
48    }
49
50    match mode {
51        ReplMode::Editor => {
52            // Print banners/prompts only if stderr is a TTY
53            if io::stderr().is_terminal() {
54                eprintln!("Brainfuck REPL (interactive editor mode)");
55                eprintln!("Ctrl+d/Ctrl+z Enter (Windows) executes the current buffer. Press ctrl+c to exit");
56                let _ = io::stderr().flush();
57            }
58
59            if let Err(e) = repl_loop() {
60                eprintln!("{program}: REPL error: {e}");
61                let _ = io::stderr().flush();
62                return 1;
63            }
64
65            0
66        }
67        ReplMode::Bare => {
68            // Bare mode: read stdin until EOF, execute once, exit 0
69            match execute_bare_once() {
70                Ok(_) => 0,
71                Err(e) => {
72                    eprintln!("{program}: REPL error: {e}");
73                    let _ = io::stderr().flush();
74                    1
75                }
76            }
77        }
78    }
79}
80
81fn usage_and_exit(program: &str, code: i32) -> ! {
82    eprintln!(
83        r#"Usage:
84  {0} repl   # Start a Brainfuck REPL (read-eval-print loop)
85
86Options:
87  --help,   -h        Show this help
88  --bare              Force non-interactive bare mode
89  --editor            Force interactive editor mode (errors if stdin is not a TTY)
90
91Description:
92  Starts a REPL where you can enter Brainfuck code and execute it live.
93
94Meta commands (line starts with ":")
95  :exit            Exit immediately (code 0)
96  :help            Show this help
97  :reset           Clear current buffer (history is preserved)
98  :dump            Print buffer (content → stdout; framing → stderr)
99    -n             Include line numbers (stdout)
100    --stderr       Send everything to stderr
101
102Notes:
103    - While editing, non-Brainfuck characters are ignored; only valid instructions are executed.
104    - Ctrl+D executes the current buffer on *nix/macOS.
105    - Ctrl+Z and Enter will execute the current buffer on Windows.
106    - Ctrl+C exits the REPL immediately.
107    - The REPL will print a newline after each execution for readability.
108    - Each execution starts with a fresh memory and pointer.
109    - The REPL will exit after a single execution if the environment variable `BF_REPL_ONCE` is set to `1`.
110    - Mode selection:
111        * Flags: --bare|--editor override environment and auto-detection.
112        * Env: BF_REPL_MODE=bare|editor overrides auto-detection (flags, when preset, will override env).
113        * Auto-detect: if stdin is a TTY, starts in interactive editor mode; otherwise, bare mode.
114        * Prompts/banners suppressed if stderr is not a TTY.
115
116"#,
117        program
118    );
119    let _ = io::stderr().flush();
120    std::process::exit(code);
121}