mod compiled;
mod js;
mod python;
mod ruby;
use crate::cli::compile::lang::SourceLang;
use anyhow::Result;
use std::str::FromStr;
use super::args::Cli;
pub fn repl(cli: &Cli, lang: &str) -> Result<()> {
let lang = SourceLang::from_str(lang)?;
match lang {
SourceLang::Js => js::run(cli, false),
SourceLang::Ts => js::run(cli, true),
SourceLang::Rust | SourceLang::Go | SourceLang::C | SourceLang::Cpp => {
compiled::run(cli, lang)
}
SourceLang::Python => python::run(cli),
SourceLang::Ruby => ruby::run(),
}
}
pub(super) struct ReplHelper;
impl rustyline::completion::Completer for ReplHelper {
type Candidate = String;
}
impl rustyline::hint::Hinter for ReplHelper {
type Hint = String;
}
impl rustyline::validate::Validator for ReplHelper {}
impl rustyline::highlight::Highlighter for ReplHelper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
_default: bool,
) -> std::borrow::Cow<'b, str> {
match crate::cli::style::highlight_prompt(prompt) {
Some(s) => std::borrow::Cow::Owned(s),
None => std::borrow::Cow::Borrowed(prompt),
}
}
}
impl rustyline::Helper for ReplHelper {}
pub(super) enum Flow {
Continue,
Exit,
}
pub(super) fn read_loop<F>(prompt: &str, mut on_line: F) -> Result<()>
where
F: FnMut(&str) -> Flow,
{
use anyhow::Context;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use rustyline::history::FileHistory;
let mut rl: Editor<ReplHelper, FileHistory> = Editor::new().context("rustyline init")?;
rl.set_helper(Some(ReplHelper));
let prompt = format!("{prompt}> ");
loop {
match rl.readline(&prompt) {
Ok(line) => {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let _ = rl.add_history_entry(trimmed);
match on_line(trimmed) {
Flow::Continue => continue,
Flow::Exit => break,
}
}
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break,
Err(e) => {
eprintln!(
" {}",
crate::cli::style::fail(&format!("readline error: {e}"))
);
break;
}
}
}
Ok(())
}
pub(super) fn clean_repl_err(raw: &str) -> String {
let s = crate::cli::style::humanize_error(raw);
s.strip_prefix("compile failed: ").unwrap_or(&s).to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dispatch_rejects_unknown_language() {
use clap::Parser;
let cli = Cli::parse_from(["burn"]);
let err = repl(&cli, "haskell").unwrap_err();
let msg = err.to_string();
assert!(msg.contains("haskell"), "must name the bad lang: {msg}");
}
#[test]
fn clean_repl_err_strips_compile_prefix() {
let out = clean_repl_err("compile failed: SyntaxError: bad");
assert!(!out.starts_with("compile failed:"), "got: {out}");
assert!(out.contains("SyntaxError"), "keeps the detail: {out}");
}
}