lune_utils/fmt/error/
stack_trace.rs1use std::fmt;
2use std::str::FromStr;
3
4fn parse_path(s: &str) -> Option<(&str, &str)> {
5 let path = s.strip_prefix("[string \"")?;
6 let (path, after) = path.split_once("\"]:")?;
7
8 let path = match path.split_once(':') {
11 Some((before, _)) => before,
12 None => path,
13 };
14
15 Some((path, after))
16}
17
18fn parse_function_name(s: &str) -> Option<&str> {
19 s.strip_prefix("in function '")
20 .and_then(|s| s.strip_suffix('\''))
21}
22
23fn parse_line_number(s: &str) -> (Option<usize>, &str) {
24 match s.split_once(':') {
25 Some((before, after)) => (before.parse::<usize>().ok(), after),
26 None => (None, s),
27 }
28}
29
30#[derive(Debug, Default, Clone, Copy)]
34pub enum StackTraceSource {
35 C,
37 #[default]
39 Lua,
40}
41
42impl StackTraceSource {
43 #[must_use]
47 pub const fn is_c(self) -> bool {
48 matches!(self, Self::C)
49 }
50
51 #[must_use]
55 pub const fn is_lua(self) -> bool {
56 matches!(self, Self::Lua)
57 }
58}
59
60#[derive(Debug, Default, Clone)]
64pub struct StackTraceLine {
65 source: StackTraceSource,
66 path: Option<String>,
67 line_number: Option<usize>,
68 function_name: Option<String>,
69}
70
71impl StackTraceLine {
72 #[must_use]
76 pub fn source(&self) -> StackTraceSource {
77 self.source
78 }
79
80 #[must_use]
84 pub fn path(&self) -> Option<&str> {
85 self.path.as_deref()
86 }
87
88 #[must_use]
92 pub fn line_number(&self) -> Option<usize> {
93 self.line_number
94 }
95
96 #[must_use]
100 pub fn function_name(&self) -> Option<&str> {
101 self.function_name.as_deref()
102 }
103
104 #[must_use]
114 pub const fn is_empty(&self) -> bool {
115 self.path.is_none() && self.line_number.is_none() && self.function_name.is_none()
116 }
117}
118
119impl FromStr for StackTraceLine {
120 type Err = String;
121 fn from_str(s: &str) -> Result<Self, Self::Err> {
122 if let Some(after) = s.strip_prefix("[C]: ") {
123 let function_name = parse_function_name(after).map(ToString::to_string);
124
125 Ok(Self {
126 source: StackTraceSource::C,
127 path: None,
128 line_number: None,
129 function_name,
130 })
131 } else if let Some((path, after)) = parse_path(s) {
132 let (line_number, after) = parse_line_number(after);
133 let function_name = parse_function_name(after).map(ToString::to_string);
134
135 Ok(Self {
136 source: StackTraceSource::Lua,
137 path: Some(path.to_string()),
138 line_number,
139 function_name,
140 })
141 } else {
142 Err(String::from("unknown format"))
143 }
144 }
145}
146
147impl fmt::Display for StackTraceLine {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 if matches!(self.source, StackTraceSource::C) {
150 write!(f, "Script '[C]'")?;
151 } else {
152 write!(f, "Script '{}'", self.path.as_deref().unwrap_or("[?]"))?;
153 if let Some(line_number) = self.line_number {
154 write!(f, ", Line {line_number}")?;
155 }
156 }
157 if let Some(function_name) = self.function_name.as_deref() {
158 write!(f, " - function '{function_name}'")?;
159 }
160 Ok(())
161 }
162}
163
164#[derive(Debug, Default, Clone)]
168pub struct StackTrace {
169 lines: Vec<StackTraceLine>,
170}
171
172impl StackTrace {
173 #[must_use]
177 pub fn lines(&self) -> &[StackTraceLine] {
178 &self.lines
179 }
180
181 #[must_use]
185 pub fn lines_mut(&mut self) -> &mut Vec<StackTraceLine> {
186 &mut self.lines
187 }
188}
189
190impl FromStr for StackTrace {
191 type Err = String;
192 fn from_str(s: &str) -> Result<Self, Self::Err> {
193 let (_, after) = s
194 .split_once("stack traceback:")
195 .ok_or_else(|| String::from("missing 'stack traceback:' prefix"))?;
196 let lines = after
197 .trim()
198 .lines()
199 .filter_map(|line| {
200 let line = line.trim();
201 if line.is_empty() {
202 None
203 } else {
204 Some(line.parse())
205 }
206 })
207 .collect::<Result<Vec<_>, _>>()?;
208 Ok(StackTrace { lines })
209 }
210}