mcfunction_debugger/adapter/
utils.rs1use crate::{
20 adapter::LISTENER_NAME,
21 generator::{
22 parser::command::resource_location::ResourceLocation, partition::SuspensionPositionInLine,
23 },
24};
25use debug_adapter_protocol::types::{Source, StackFrame};
26use futures::Stream;
27use minect::{
28 command::{QueryScoreboardOutput, SummonNamedEntityOutput},
29 log::LogEvent,
30};
31use std::{fmt::Display, path::Path, str::FromStr};
32use tokio_stream::StreamExt;
33
34pub fn parse_function_path(path: &Path) -> Result<(&Path, ResourceLocation), String> {
35 let datapack = find_parent_datapack(path).ok_or_else(|| {
36 format!(
37 "does not denote a path in a datapack directory with a pack.mcmeta file: {}",
38 &path.display()
39 )
40 })?;
41 let data_path = path.strip_prefix(datapack.join("data")).map_err(|_| {
42 format!(
43 "does not denote a path in the data directory of datapack {}: {}",
44 &datapack.display(),
45 &path.display()
46 )
47 })?;
48 let function = get_function_name(data_path, &path)?;
49 Ok((datapack, function))
50}
51
52pub fn find_parent_datapack(mut path: &Path) -> Option<&Path> {
53 while let Some(p) = path.parent() {
54 path = p;
55 let pack_mcmeta_path = path.join("pack.mcmeta");
56 if pack_mcmeta_path.is_file() {
57 return Some(path);
58 }
59 }
60 None
61}
62
63pub fn get_function_name(
64 data_path: impl AsRef<Path>,
65 path: impl AsRef<Path>,
66) -> Result<ResourceLocation, String> {
67 let namespace = data_path.as_ref()
68 .iter()
69 .next()
70 .ok_or_else(|| {
71 format!(
72 "contains an invalid path: {}",
73 path.as_ref().display()
74 )
75 })?
76 .to_str()
77 .unwrap() ;
79 let fn_path = data_path
80 .as_ref()
81 .strip_prefix(Path::new(namespace).join("functions"))
82 .map_err(|_| format!("contains an invalid path: {}", path.as_ref().display()))?
83 .with_extension("")
84 .to_str()
85 .unwrap() .replace(std::path::MAIN_SEPARATOR, "/");
87 Ok(ResourceLocation::new(&namespace, &fn_path))
88}
89
90pub(crate) fn events_between<'l>(
91 events: impl Stream<Item = LogEvent> + 'l,
92 start: &'l str,
93 stop: &'l str,
94) -> impl Stream<Item = LogEvent> + 'l {
95 events
96 .skip_while(move |event| !is_summon_output(event, start))
97 .skip(1) .take_while(move |event| !is_summon_output(event, stop))
99}
100fn is_summon_output(event: &LogEvent, name: &str) -> bool {
101 event.executor == LISTENER_NAME
102 && event
103 .output
104 .parse::<SummonNamedEntityOutput>()
105 .ok()
106 .filter(|output| output.name == name)
107 .is_some()
108}
109
110pub struct StoppedEvent {
111 pub function: ResourceLocation,
112 pub line_number: usize,
113 pub column_number: usize,
114 pub position_in_line: SuspensionPositionInLine,
115}
116impl FromStr for StoppedEvent {
117 type Err = ();
118
119 fn from_str(string: &str) -> Result<Self, Self::Err> {
120 fn from_str_inner(string: &str) -> Option<StoppedEvent> {
121 let string = string.strip_prefix("stopped+")?;
122 let (string, position_in_line) = string.rsplit_once('+')?;
123 let (string, column_number) = string.rsplit_once('+')?;
124 let (function, line_number) = string.rsplit_once('+')?;
125 Some(StoppedEvent {
126 function: parse_resource_location(function, '+')?,
127 line_number: line_number.parse().ok()?,
128 column_number: column_number.parse().ok()?,
129 position_in_line: position_in_line.parse().ok()?,
130 })
131 }
132 from_str_inner(string).ok_or(())
133 }
134}
135impl Display for StoppedEvent {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 write!(
138 f,
139 "stopped+{}+{}+{}+{}",
140 write_resource_location(&self.function, '+'),
141 self.line_number,
142 self.column_number,
143 self.position_in_line,
144 )
145 }
146}
147
148pub(crate) struct StoppedData {
149 pub(crate) function: ResourceLocation,
150 pub(crate) line_number: usize,
151 pub(crate) position_in_line: SuspensionPositionInLine,
152 pub(crate) stack_trace: Vec<McfunctionStackFrame>,
153}
154
155pub(crate) struct McfunctionStackFrame {
156 pub(crate) id: i32,
157 pub(crate) location: SourceLocation,
158}
159impl McfunctionStackFrame {
160 pub(crate) fn to_stack_frame(
161 &self,
162 datapack: impl AsRef<Path>,
163 line_offset: usize,
164 column_offset: usize,
165 ) -> StackFrame {
166 let path = datapack
167 .as_ref()
168 .join("data")
169 .join(self.location.function.mcfunction_path())
170 .display()
171 .to_string();
172 StackFrame::builder()
173 .id(self.id)
174 .name(self.location.get_name())
175 .source(Some(Source::builder().path(Some(path)).build()))
176 .line((self.location.line_number - line_offset) as i32)
177 .column((self.location.column_number - column_offset) as i32)
178 .build()
179 }
180
181 pub(crate) fn parse(event: LogEvent) -> Option<McfunctionStackFrame> {
182 let location = event.executor.parse().ok()?;
183 let output = event.output.parse::<QueryScoreboardOutput>().ok()?;
184 let depth = output.score;
185 Some(McfunctionStackFrame {
186 id: depth,
187 location,
188 })
189 }
190}
191
192#[derive(Clone, Debug)]
193pub(crate) struct SourceLocation {
194 pub(crate) function: ResourceLocation,
195 pub(crate) line_number: usize,
196 pub(crate) column_number: usize,
197}
198impl FromStr for SourceLocation {
199 type Err = ();
200
201 fn from_str(string: &str) -> Result<Self, Self::Err> {
202 fn from_str_inner(string: &str) -> Option<SourceLocation> {
203 let has_column = 3 == string.bytes().filter(|b| *b == b':').count();
204 let (function_line_number, column_number) = if has_column {
205 let (function_line_number, column_number) = string.rsplit_once(':')?;
206 let column_number = column_number.parse().ok()?;
207 (function_line_number, column_number)
208 } else {
209 (string, 1)
210 };
211
212 let (function, line_number) = function_line_number.rsplit_once(':')?;
213 let function = parse_resource_location(function, ':')?;
214 let line_number = line_number.parse().ok()?;
215
216 Some(SourceLocation {
217 function,
218 line_number,
219 column_number,
220 })
221 }
222 from_str_inner(string).ok_or(())
223 }
224}
225impl SourceLocation {
226 pub(crate) fn get_name(&self) -> String {
227 format!("{}:{}", self.function, self.line_number)
228 }
229}
230
231fn parse_resource_location(function: &str, seperator: char) -> Option<ResourceLocation> {
232 if let [orig_ns, orig_fn @ ..] = function.split(seperator).collect::<Vec<_>>().as_slice() {
233 Some(ResourceLocation::new(orig_ns, &orig_fn.join("/")))
234 } else {
235 None
236 }
237}
238
239fn write_resource_location(function: &ResourceLocation, seperator: char) -> String {
240 format!(
241 "{}{}{}",
242 function.namespace(),
243 seperator,
244 function.path().replace("/", &seperator.to_string()),
245 )
246}