lune_utils/fmt/error/
stack_trace.rs1use std::fmt;
2use std::str::FromStr;
3
4fn unwrap_braced_path(s: &str) -> &str {
5 s.strip_prefix("[string \"")
6 .and_then(|s2| s2.strip_suffix("\"]"))
7 .unwrap_or(s)
8}
9
10fn parse_path(s: &str) -> Option<(&str, &str)> {
11 let (path, after) = unwrap_braced_path(s).split_once(':')?;
12 let path = unwrap_braced_path(path);
13
14 let path = match path.split_once(':') {
17 Some((before, _)) => before,
18 None => path,
19 };
20
21 Some((path, after))
22}
23
24fn parse_function_name(s: &str) -> Option<&str> {
25 s.strip_prefix("in function '")
26 .and_then(|s| s.strip_suffix('\''))
27}
28
29fn parse_line_number(s: &str) -> (Option<usize>, &str) {
30 match s.split_once(':') {
31 Some((before, after)) => (before.parse::<usize>().ok(), after),
32 None => (None, s),
33 }
34}
35
36#[derive(Debug, Default, Clone, Copy)]
40pub enum StackTraceSource {
41 C,
43 #[default]
45 Lua,
46}
47
48impl StackTraceSource {
49 #[must_use]
53 pub const fn is_c(self) -> bool {
54 matches!(self, Self::C)
55 }
56
57 #[must_use]
61 pub const fn is_lua(self) -> bool {
62 matches!(self, Self::Lua)
63 }
64}
65
66#[derive(Debug, Default, Clone)]
70pub struct StackTraceLine {
71 source: StackTraceSource,
72 path: Option<String>,
73 line_number: Option<usize>,
74 function_name: Option<String>,
75}
76
77impl StackTraceLine {
78 #[must_use]
82 pub fn source(&self) -> StackTraceSource {
83 self.source
84 }
85
86 #[must_use]
90 pub fn path(&self) -> Option<&str> {
91 self.path.as_deref()
92 }
93
94 #[must_use]
98 pub fn line_number(&self) -> Option<usize> {
99 self.line_number
100 }
101
102 #[must_use]
106 pub fn function_name(&self) -> Option<&str> {
107 self.function_name.as_deref()
108 }
109
110 #[must_use]
120 pub const fn is_empty(&self) -> bool {
121 self.path.is_none() && self.line_number.is_none() && self.function_name.is_none()
122 }
123}
124
125impl FromStr for StackTraceLine {
126 type Err = String;
127 fn from_str(s: &str) -> Result<Self, Self::Err> {
128 if let Some(after) = s.strip_prefix("[C]: ") {
129 let function_name = parse_function_name(after).map(ToString::to_string);
130
131 Ok(Self {
132 source: StackTraceSource::C,
133 path: None,
134 line_number: None,
135 function_name,
136 })
137 } else if let Some((path, after)) = parse_path(s) {
138 let (line_number, after) = parse_line_number(after);
139 let function_name = parse_function_name(after).map(ToString::to_string);
140
141 Ok(Self {
142 source: StackTraceSource::Lua,
143 path: Some(path.to_string()),
144 line_number,
145 function_name,
146 })
147 } else {
148 Err(String::from("unknown format"))
149 }
150 }
151}
152
153impl fmt::Display for StackTraceLine {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 if matches!(self.source, StackTraceSource::C) {
156 write!(f, "Script '[C]'")?;
157 } else {
158 write!(f, "Script '{}'", self.path.as_deref().unwrap_or("[?]"))?;
159 if let Some(line_number) = self.line_number {
160 write!(f, ", Line {line_number}")?;
161 }
162 }
163 if let Some(function_name) = self.function_name.as_deref() {
164 write!(f, " - function '{function_name}'")?;
165 }
166 Ok(())
167 }
168}
169
170#[derive(Debug, Default, Clone)]
174pub struct StackTrace {
175 lines: Vec<StackTraceLine>,
176}
177
178impl StackTrace {
179 #[must_use]
183 pub fn lines(&self) -> &[StackTraceLine] {
184 &self.lines
185 }
186
187 #[must_use]
191 pub fn lines_mut(&mut self) -> &mut Vec<StackTraceLine> {
192 &mut self.lines
193 }
194}
195
196impl FromStr for StackTrace {
197 type Err = String;
198 fn from_str(s: &str) -> Result<Self, Self::Err> {
199 let (_, after) = s
200 .split_once("stack traceback:")
201 .ok_or_else(|| String::from("missing 'stack traceback:' prefix"))?;
202 let lines = after
203 .trim()
204 .lines()
205 .filter_map(|line| {
206 let line = line.trim();
207 if line.is_empty() {
208 None
209 } else {
210 Some(line.parse())
211 }
212 })
213 .collect::<Result<Vec<_>, _>>()?;
214 Ok(StackTrace { lines })
215 }
216}