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