use std::fmt; use std::str::FromStr; fn parse_path(s: &str) -> Option<(&str, &str)> { let path = s.strip_prefix("[string \"")?; let (path, after) = path.split_once("\"]:")?; // Remove line number after any found colon, this may // exist if the source path is from a rust source file let path = match path.split_once(':') { Some((before, _)) => before, None => path, }; Some((path, after)) } fn parse_function_name(s: &str) -> Option<&str> { s.strip_prefix("in function '") .and_then(|s| s.strip_suffix('\'')) } fn parse_line_number(s: &str) -> (Option, &str) { match s.split_once(':') { Some((before, after)) => (before.parse::().ok(), after), None => (None, s), } } /** Source of a stack trace line parsed from a [`LuaError`]. */ #[derive(Debug, Default, Clone, Copy)] pub enum StackTraceSource { /// Error originated from a C / Rust function. C, /// Error originated from a Lua (user) function. #[default] Lua, } impl StackTraceSource { /** Returns `true` if the error originated from a C / Rust function, `false` otherwise. */ #[must_use] pub const fn is_c(self) -> bool { matches!(self, Self::C) } /** Returns `true` if the error originated from a Lua (user) function, `false` otherwise. */ #[must_use] pub const fn is_lua(self) -> bool { matches!(self, Self::Lua) } } /** Stack trace line parsed from a [`LuaError`]. */ #[derive(Debug, Default, Clone)] pub struct StackTraceLine { source: StackTraceSource, path: Option, line_number: Option, function_name: Option, } impl StackTraceLine { /** Returns the source of the stack trace line. */ #[must_use] pub fn source(&self) -> StackTraceSource { self.source } /** Returns the path, if it exists. */ #[must_use] pub fn path(&self) -> Option<&str> { self.path.as_deref() } /** Returns the line number, if it exists. */ #[must_use] pub fn line_number(&self) -> Option { self.line_number } /** Returns the function name, if it exists. */ #[must_use] pub fn function_name(&self) -> Option<&str> { self.function_name.as_deref() } /** Returns `true` if the stack trace line contains no "useful" information, `false` otherwise. Useful information is determined as one of: - A path - A line number - A function name */ #[must_use] pub const fn is_empty(&self) -> bool { self.path.is_none() && self.line_number.is_none() && self.function_name.is_none() } } impl FromStr for StackTraceLine { type Err = String; fn from_str(s: &str) -> Result { if let Some(after) = s.strip_prefix("[C]: ") { let function_name = parse_function_name(after).map(ToString::to_string); Ok(Self { source: StackTraceSource::C, path: None, line_number: None, function_name, }) } else if let Some((path, after)) = parse_path(s) { let (line_number, after) = parse_line_number(after); let function_name = parse_function_name(after).map(ToString::to_string); Ok(Self { source: StackTraceSource::Lua, path: Some(path.to_string()), line_number, function_name, }) } else { Err(String::from("unknown format")) } } } impl fmt::Display for StackTraceLine { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if matches!(self.source, StackTraceSource::C) { write!(f, "Script '[C]'")?; } else { write!(f, "Script '{}'", self.path.as_deref().unwrap_or("[?]"))?; if let Some(line_number) = self.line_number { write!(f, ", Line {line_number}")?; } } if let Some(function_name) = self.function_name.as_deref() { write!(f, " - function '{function_name}'")?; } Ok(()) } } /** Stack trace parsed from a [`LuaError`]. */ #[derive(Debug, Default, Clone)] pub struct StackTrace { lines: Vec, } impl StackTrace { /** Returns the individual stack trace lines. */ #[must_use] pub fn lines(&self) -> &[StackTraceLine] { &self.lines } /** Returns the individual stack trace lines, mutably. */ #[must_use] pub fn lines_mut(&mut self) -> &mut Vec { &mut self.lines } } impl FromStr for StackTrace { type Err = String; fn from_str(s: &str) -> Result { let (_, after) = s .split_once("stack traceback:") .ok_or_else(|| String::from("missing 'stack traceback:' prefix"))?; let lines = after .trim() .lines() .filter_map(|line| { let line = line.trim(); if line.is_empty() { None } else { Some(line.parse()) } }) .collect::, _>>()?; Ok(StackTrace { lines }) } }