You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中如何区分Lisp REPL的输入提示与普通输出?

如何区分Lisp REPL的输入提示与普通输出?

我正在启动一个Command,向其stdin写入数据并读取其stdout,将输出管道传输至xterm.js。这个命令是长期运行的交互式Lisp REPL(比如SBCL),代码能正常运行,但遇到一个问题:无法判断该进程是在请求输入还是仅输出内容。比如从stdout读到不带换行符的> ,可能是进程在提示用户输入,但也可能是某个长时间运行的求值过程恰好输出了> 却尚未结束,怎么区分这两种情况?

我的Rust代码如下:

// using the os_pipe crate to create a new pipe with both, stdout and stderr,
// to capture both, in correct order
let (reader, writer_out) = os_pipe::pipe().unwrap();
let writer_err = writer_out.try_clone().unwrap();

let mut c = Command::new("path/to/sbcl");
let mut child = 
    c
    .stdin(Stdio::piped())
    .stdout(writer_out)
    .stderr(writer_err)
    .spawn()
    .expect("command failed to start");

let (sender, receiver) = bounded::<String>(0);

let mut f = BufReader::new(reader);
thread::spawn(move || {
    
    let mut buf = String::new();
    // using the utf8_chars crate to iterate over chars of the bufreader
    for c in f.chars().map(|x| x.unwrap()) {
        buf.push(c);
        if c == '\n' {
            sender.send(buf.clone()).expect("Failed to send.");
            buf.clear();
        } 
        // matches "> " or similar
        // if a "> " was sent without newline, I still want to capture it 
        else if RE_PROMPT.is_match(&buf) {
            sender.send(buf.clone()).expect("Failed to send.");
            buf.clear();
        }
    }
});

解决方案

1. 使用伪终端(PTY)替代普通管道

这是最可靠的方案,因为交互式REPL通常会针对终端环境调整行为,只有连接到终端时才会正确输出输入提示,还能通过终端状态判断进程是否在等待输入。

在Rust中可以用pty crate实现:

use pty::fork;
use std::io::{Read, Write};
use std::sync::mpsc::{bounded, Sender};
use std::thread;

let (sender, receiver) = bounded::<String>(0);

// 创建伪终端并启动子进程
let mut fork = fork().unwrap();
if fork.is_child() {
    // 在子进程中执行SBCL
    std::process::Command::new("path/to/sbcl")
        .status()
        .unwrap();
    std::process::exit(0);
}

// 父进程读取PTY输出
let mut pty = fork.pty().unwrap();
thread::spawn(move || {
    let mut buf = [0u8; 1024];
    let mut output = String::new();
    loop {
        match pty.read(&mut buf) {
            Ok(n) if n > 0 => {
                let chunk = String::from_utf8_lossy(&buf[..n]);
                output.push_str(&chunk);
                
                // 匹配输入提示(可根据REPL实际提示调整正则,比如SBCL默认是"* ")
                if RE_PROMPT.is_match(&output) {
                    // 写入空字节测试进程是否阻塞等待输入
                    if pty.write_all(b"\n").is_ok() {
                        sender.send(output.clone()).expect("Failed to send");
                        output.clear();
                    }
                } else if output.contains('\n') {
                    // 普通换行输出直接发送
                    sender.send(output.clone()).expect("Failed to send");
                    output.clear();
                }
            }
            _ => break,
        }
    }
});

2. 结合进程状态与输出上下文判断

如果不想用PTY,可以通过检查子进程状态+输出上下文辅助判断:

  • 用child.try_wait()检查进程是否处于运行且等待输入的状态
  • 输入提示通常出现在行首,且前一段输出是以换行结尾的完整结果

修改代码逻辑示例:

use std::sync::{Arc, Mutex};

// 用Arc<Mutex>共享缓冲区状态
let shared_buf = Arc::new(Mutex::new(String::new()));
let buf_clone = shared_buf.clone();

// 读取线程更新共享缓冲区
thread::spawn(move || {
    let mut f = BufReader::new(reader);
    let mut buf = String::new();
    for c in f.chars().map(|x| x.unwrap()) {
        buf.push(c);
        *buf_clone.lock().unwrap() = buf.clone();
        
        if c == '\n' {
            sender.send(buf.clone()).expect("Failed to send.");
            buf.clear();
            *buf_clone.lock().unwrap() = String::new();
        }
    }
});

// 主线程定期检查进程状态与缓冲区
thread::spawn(move || {
    loop {
        match child.try_wait() {
            Ok(None) => {
                let current_buf = shared_buf.lock().unwrap().clone();
                if RE_PROMPT.is_match(&current_buf) {
                    // 写入测试字符,判断进程是否响应
                    child.stdin.as_mut().unwrap().write_all(b" ").unwrap();
                    thread::sleep(std::time::Duration::from_millis(100));
                    // 根据后续输出判断是否为输入提示,再决定是否发送缓冲区内容
                }
            }
            _ => break,
        }
        thread::sleep(std::time::Duration::from_millis(500));
    }
});

3. 优化正则匹配规则

针对REPL提示做更精准匹配:

  • 匹配行首的> (用^> 正则,开启多行模式)
  • 覆盖REPL不同模式的提示格式,比如SBCL调试模式的debug>

内容的提问来源于stack exchange,提问作者User9123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 08:01:43