miden_assembly_syntax/ast/path/
path_buf.rs1use alloc::string::{String, ToString};
2use core::{
3 fmt,
4 ops::Deref,
5 str::{self, FromStr},
6};
7
8use miden_core::utils::{
9 ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
10};
11
12use super::{Path, PathError};
13use crate::ast::Ident;
14
15#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[cfg_attr(
21 all(feature = "arbitrary", test),
22 miden_test_serde_macros::serde_test(winter_serde(true))
23)]
24pub struct PathBuf {
25 pub(super) inner: String,
26}
27
28impl Deref for PathBuf {
29 type Target = Path;
30
31 #[inline(always)]
32 fn deref(&self) -> &Self::Target {
33 self.as_ref()
34 }
35}
36
37impl AsRef<Path> for PathBuf {
38 #[inline(always)]
39 fn as_ref(&self) -> &Path {
40 Path::new(&self.inner)
41 }
42}
43
44impl AsRef<str> for PathBuf {
45 #[inline(always)]
46 fn as_ref(&self) -> &str {
47 &self.inner
48 }
49}
50
51impl<'a> From<&'a Path> for PathBuf {
52 #[inline(always)]
53 fn from(path: &'a Path) -> Self {
54 path.to_path_buf()
55 }
56}
57
58impl PathBuf {
60 pub fn with_capacity(capacity: usize) -> Self {
62 Self { inner: String::with_capacity(capacity) }
63 }
64
65 pub fn new<S>(source: &S) -> Result<Self, PathError>
80 where
81 S: AsRef<str> + ?Sized,
82 {
83 let source = source.as_ref();
84
85 let validated = Path::validate(source)?;
86
87 let mut buf = PathBuf::with_capacity(validated.byte_len());
89 if validated.is_absolute() && !validated.as_str().starts_with("::") {
90 buf.inner.push_str("::");
91 }
92 buf.inner.push_str(validated.as_str());
93
94 Ok(buf)
95 }
96
97 pub fn absolute<S>(source: &S) -> Self
99 where
100 S: AsRef<str> + ?Sized,
101 {
102 let source = source.as_ref();
103 Path::new(source).to_absolute().into_owned()
104 }
105
106 pub fn relative<S>(source: &S) -> Self
108 where
109 S: AsRef<str> + ?Sized,
110 {
111 let source = source.as_ref();
112 match source.strip_prefix("::") {
113 Some(rest) => Self { inner: rest.to_string() },
114 None => Self { inner: source.to_string() },
115 }
116 }
117
118 #[inline]
120 pub fn as_path(&self) -> &Path {
121 self.as_ref()
122 }
123
124 pub fn into_boxed_path(self) -> alloc::boxed::Box<Path> {
126 let inner = self.inner.into_boxed_str();
127 let inner = alloc::boxed::Box::into_raw(inner);
128 unsafe { alloc::boxed::Box::from_raw(inner as *mut Path) }
130 }
131}
132
133impl PathBuf {
135 pub fn set_parent<P>(&mut self, parent: &P)
142 where
143 P: AsRef<Path> + ?Sized,
144 {
145 let parent = parent.as_ref();
146 match self.split_last() {
147 Some((last, _)) => {
148 let parent = parent.as_str();
149 let mut buf = String::with_capacity(last.len() + parent.len() + 2);
150 if !parent.is_empty() {
151 buf.push_str(parent);
152 buf.push_str("::");
153 }
154 buf.push_str(last);
155 self.inner = buf;
156 },
157 None => {
158 self.inner.clear();
159 self.inner.push_str(parent.as_str());
160 },
161 }
162 }
163
164 pub fn push<P>(&mut self, path: &P)
170 where
171 P: AsRef<Path> + ?Sized,
172 {
173 let path = path.as_ref();
174
175 if path.is_empty() {
176 return;
177 }
178
179 if path.is_absolute() {
180 self.inner.clear();
181 if !path.as_str().starts_with("::") {
183 self.inner.push_str("::");
184 }
185 self.inner.push_str(path.as_str());
186 return;
187 }
188
189 if self.is_empty() {
190 self.inner.push_str(path.as_str());
191 return;
192 }
193
194 for component in path.components() {
195 self.inner.push_str("::");
196 let component = component.unwrap();
197 self.inner.push_str(component.as_str());
198 }
199 }
200
201 pub fn pop(&mut self) -> bool {
205 match self.parent() {
206 Some(parent) => {
207 let buf = parent.as_str().to_string();
208 self.inner = buf;
209 true
210 },
211 None => false,
212 }
213 }
214}
215
216impl<'a> core::ops::AddAssign<&'a Path> for PathBuf {
217 fn add_assign(&mut self, rhs: &'a Path) {
218 self.push(rhs);
219 }
220}
221
222impl<'a> core::ops::AddAssign<&'a str> for PathBuf {
223 fn add_assign(&mut self, rhs: &'a str) {
224 self.push(rhs);
225 }
226}
227
228impl<'a> core::ops::AddAssign<&'a Ident> for PathBuf {
229 fn add_assign(&mut self, rhs: &'a Ident) {
230 self.push(rhs.as_str());
231 }
232}
233
234impl<'a> core::ops::AddAssign<&'a crate::ast::ProcedureName> for PathBuf {
235 fn add_assign(&mut self, rhs: &'a crate::ast::ProcedureName) {
236 self.push(rhs.as_str());
237 }
238}
239
240impl<'a> TryFrom<&'a str> for PathBuf {
241 type Error = PathError;
242
243 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
244 PathBuf::new(value)
245 }
246}
247
248impl TryFrom<String> for PathBuf {
249 type Error = PathError;
250
251 fn try_from(value: String) -> Result<Self, Self::Error> {
252 Path::validate(&value)?;
253 Ok(PathBuf { inner: value })
254 }
255}
256
257impl From<Ident> for PathBuf {
258 fn from(component: Ident) -> Self {
259 PathBuf { inner: component.as_str().to_string() }
260 }
261}
262
263impl From<PathBuf> for String {
264 fn from(path: PathBuf) -> Self {
265 path.inner
266 }
267}
268
269impl From<PathBuf> for alloc::sync::Arc<Path> {
270 fn from(value: PathBuf) -> Self {
271 value.into_boxed_path().into()
272 }
273}
274
275impl From<alloc::borrow::Cow<'_, Path>> for PathBuf {
276 fn from(value: alloc::borrow::Cow<'_, Path>) -> Self {
277 value.into_owned()
278 }
279}
280
281impl FromStr for PathBuf {
282 type Err = PathError;
283
284 #[inline]
285 fn from_str(value: &str) -> Result<Self, Self::Err> {
286 Self::new(value)
287 }
288}
289
290impl Serializable for PathBuf {
291 fn write_into<W: ByteWriter>(&self, target: &mut W) {
292 self.as_path().write_into(target);
293 }
294}
295
296impl Serializable for Path {
297 fn write_into<W: ByteWriter>(&self, target: &mut W) {
298 target.write_u16(self.byte_len().try_into().expect("invalid path: too long"));
299 target.write_bytes(self.as_str().as_bytes());
300 }
301}
302
303impl Deserializable for PathBuf {
304 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
305 let len = source.read_u16()? as usize;
306 let path = source.read_slice(len)?;
307 let path =
308 str::from_utf8(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
309 Self::new(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))
310 }
311}
312
313#[cfg(feature = "serde")]
314impl serde::Serialize for PathBuf {
315 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
316 where
317 S: serde::Serializer,
318 {
319 serializer.serialize_str(self.inner.as_str())
320 }
321}
322
323#[cfg(feature = "serde")]
324impl<'de> serde::Deserialize<'de> for PathBuf {
325 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
326 where
327 D: serde::Deserializer<'de>,
328 {
329 let inner = <&'de str as serde::Deserialize<'de>>::deserialize(deserializer)?;
330
331 PathBuf::new(inner).map_err(serde::de::Error::custom)
332 }
333}
334
335impl fmt::Display for PathBuf {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 fmt::Display::fmt(self.as_path(), f)
338 }
339}
340
341#[cfg(test)]
346mod tests {
347
348 use miden_core::assert_matches;
349
350 use super::{PathBuf, PathError};
351 use crate::{Path, ast::IdentError};
352
353 #[test]
354 fn single_component_path() {
355 let path = PathBuf::new("foo").unwrap();
356 assert!(!path.is_absolute());
357 assert_eq!(path.components().count(), 1);
358 assert_eq!(path.last(), Some("foo"));
359 assert_eq!(path.first(), Some("foo"));
360 }
361
362 #[test]
363 fn relative_path_two_components() {
364 let path = PathBuf::new("foo::bar").unwrap();
365 assert!(!path.is_absolute());
366 assert_eq!(path.components().count(), 2);
367 assert_eq!(path.last(), Some("bar"));
368 assert_eq!(path.first(), Some("foo"));
369 }
370
371 #[test]
372 fn relative_path_three_components() {
373 let path = PathBuf::new("foo::bar::baz").unwrap();
374 assert!(!path.is_absolute());
375 assert_eq!(path.components().count(), 3);
376 assert_eq!(path.last(), Some("baz"));
377 assert_eq!(path.first(), Some("foo"));
378 assert_eq!(path.parent().map(|p| p.as_str()), Some("foo::bar"));
379 }
380
381 #[test]
382 fn single_quoted_component() {
383 let path = PathBuf::new("\"miden:base/account@0.1.0\"").unwrap();
384 assert!(!path.is_absolute());
385 assert_eq!(path.components().count(), 1);
386 assert_eq!(path.last(), Some("miden:base/account@0.1.0"));
387 assert_eq!(path.first(), Some("miden:base/account@0.1.0"));
388 }
389
390 #[test]
391 fn trailing_quoted_component() {
392 let path = PathBuf::new("foo::\"miden:base/account@0.1.0\"").unwrap();
393 assert!(!path.is_absolute());
394 assert_eq!(path.components().count(), 2);
395 assert_eq!(path.last(), Some("miden:base/account@0.1.0"));
396 assert_eq!(path.first(), Some("foo"));
397 }
398
399 #[test]
400 fn interspersed_quoted_component() {
401 let path = PathBuf::new("foo::\"miden:base/account@0.1.0\"::item").unwrap();
402 assert!(!path.is_absolute());
403 assert_eq!(path.components().count(), 3);
404 assert_eq!(path.last(), Some("item"));
405 assert_eq!(path.first(), Some("foo"));
406 assert_eq!(path.parent().map(|p| p.as_str()), Some("foo::\"miden:base/account@0.1.0\""));
407 }
408
409 #[test]
410 fn exec_path() {
411 let path = PathBuf::new("$exec::bar::baz").unwrap();
412 assert!(path.is_absolute());
413 assert_eq!(path.components().count(), 4);
414 assert_eq!(path.last(), Some("baz"));
415 assert_eq!(path.first(), Some("$exec"));
416 }
417
418 #[test]
419 fn kernel_path() {
420 let path = PathBuf::new("$kernel::bar::baz").unwrap();
421 std::dbg!(&path);
422 assert!(path.is_absolute());
423 assert_eq!(path.components().count(), 4);
424 assert_eq!(path.last(), Some("baz"));
425 assert_eq!(path.first(), Some("$kernel"));
426 }
427
428 #[test]
429 fn invalid_path_empty() {
430 let result = Path::validate("");
431 assert_matches!(result, Err(PathError::Empty));
432 }
433
434 #[test]
435 fn invalid_path_empty_component() {
436 let result = Path::validate("::");
437 assert_matches!(result, Err(PathError::EmptyComponent));
438 }
439
440 #[test]
441 fn invalid_path_trailing_delimiter() {
442 let result = Path::validate("foo::");
443 assert_matches!(result, Err(PathError::InvalidComponent(IdentError::Empty)));
444 }
445
446 #[test]
447 fn invalid_path_invalid_character() {
448 let result = Path::validate("#foo::bar");
449 assert_matches!(result, Err(PathError::InvalidComponent(IdentError::InvalidChars { .. })));
450 }
451}