run-rs 0.6.2

Run a subset of Rust as an interpreted script
/// A script runtime abort. What a compiled binary would report as a panic,
/// carried as a typed error so `main` can print the panic header and exit
/// with the panic status 101, matching real Rust.
#[derive(Debug)]
pub struct ScriptPanic {
    /// File and line of the innermost script frame, for the panic header.
    pub file: String,
    pub line: u32,
    /// The message with the script backtrace lines appended.
    pub rendered: String,
}

impl std::fmt::Display for ScriptPanic {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.rendered)
    }
}

impl std::error::Error for ScriptPanic {}

/// `main` returned a `Result::Err` value. A compiled binary prints it as
/// `Error: ...` and exits 1, so this marks that outcome apart from a panic.
#[derive(Debug)]
pub struct ErrReturn(pub String);

impl std::fmt::Display for ErrReturn {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Error: {}", self.0)
    }
}

impl std::error::Error for ErrReturn {}

/// Wrap a runtime error as a `ScriptPanic` carrying the script backtrace.
/// Frames arrive innermost first as (function, file, line), line 0 meaning
/// unknown. Deep chains cap at a fixed count so runaway recursion stays
/// readable.
pub(super) fn trace_error(
    e: anyhow::Error,
    frames: impl Iterator<Item = (String, String, u32)>,
) -> anyhow::Error {
    const SHOWN: usize = 15;
    let mut msg = format!("{e:#}");
    // A closure called from inside a bridge runs its own exec and wraps
    // first; the panic origin must stay that innermost site, not this
    // outer exec's current frame.
    let mut origin: Option<(String, u32)> = e
        .downcast_ref::<ScriptPanic>()
        .map(|p| (p.file.clone(), p.line));
    let mut hidden = 0usize;
    for (i, (func, file, line)) in frames.enumerate() {
        if origin.is_none() {
            origin = Some((file.clone(), line));
        }
        if i >= SHOWN {
            hidden += 1;
            continue;
        }
        if file.is_empty() {
            msg.push_str(&format!("\n  at {func}"));
        } else if line == 0 {
            msg.push_str(&format!("\n  at {func} ({file})"));
        } else {
            msg.push_str(&format!("\n  at {func} ({file}:{line})"));
        }
    }
    if hidden > 0 {
        msg.push_str(&format!("\n  ... {hidden} more frames"));
    }
    let (file, line) = origin.unwrap_or_default();
    anyhow::Error::new(ScriptPanic {
        file,
        line,
        rendered: msg,
    })
}