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, PathComponent, 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 validated.canonicalize()
88 }
89
90 pub(super) fn extend_with_components<'a>(
92 &mut self,
93 components: impl IntoIterator<Item = Result<PathComponent<'a>, PathError>>,
94 ) -> Result<(), PathError> {
95 for component in components {
96 self.push_component(component?.as_str());
97 }
98 Ok(())
99 }
100
101 pub fn absolute<S>(source: &S) -> Self
103 where
104 S: AsRef<str> + ?Sized,
105 {
106 let source = source.as_ref();
107 Path::new(source).to_absolute().into_owned()
108 }
109
110 pub fn relative<S>(source: &S) -> Self
112 where
113 S: AsRef<str> + ?Sized,
114 {
115 let source = source.as_ref();
116 match source.strip_prefix("::") {
117 Some(rest) => Self { inner: rest.to_string() },
118 None => Self { inner: source.to_string() },
119 }
120 }
121
122 #[inline]
124 pub fn as_path(&self) -> &Path {
125 self.as_ref()
126 }
127
128 pub fn into_boxed_path(self) -> alloc::boxed::Box<Path> {
130 let inner = self.inner.into_boxed_str();
131 let inner = alloc::boxed::Box::into_raw(inner);
132 unsafe { alloc::boxed::Box::from_raw(inner as *mut Path) }
134 }
135}
136
137impl PathBuf {
139 pub fn set_parent<P>(&mut self, parent: &P)
146 where
147 P: AsRef<Path> + ?Sized,
148 {
149 let parent = parent.as_ref();
150 match self.split_last() {
151 Some((last, _)) => {
152 let reparented = parent.join(last);
153 let _ = core::mem::replace(self, reparented);
154 },
155 None => {
156 self.inner.clear();
157 self.inner.push_str(parent.as_str());
158 },
159 }
160 }
161
162 pub fn push<P>(&mut self, path: &P)
169 where
170 P: AsRef<Path> + ?Sized,
171 {
172 let path = path.as_ref();
173
174 self.extend_with_components(path.components()).expect("invalid path");
175 }
176
177 pub fn push_component<S>(&mut self, component: &S)
189 where
190 S: AsRef<str> + ?Sized,
191 {
192 let component = component.as_ref();
193 match component {
194 "" | "\"\"" => (),
195 "::" if self.inner.is_empty() => {
196 self.inner.push_str("::");
197 },
198 "::" => {
199 self.inner.clear();
202 self.inner.push_str("::");
203 },
204 component => {
205 if !self.is_empty() {
207 self.inner.push_str("::");
208 }
209
210 let is_quoted = component.starts_with('"') && component.ends_with('"');
211 let is_special = component == Path::KERNEL_PATH || component == Path::EXEC_PATH;
212 let requires_quoting = !is_special && Ident::requires_quoting(component);
213
214 if is_special && self.inner.is_empty() {
215 self.inner.push_str("::");
217 self.inner.push_str(component);
218 } else if requires_quoting && !is_quoted {
219 self.inner.push('"');
221 self.inner.push_str(component);
222 self.inner.push('"');
223 } else if !requires_quoting && is_quoted {
224 self.inner.push_str(&component[1..(component.len() - 1)]);
226 } else {
227 self.inner.push_str(component);
229 }
230 },
231 }
232 }
233
234 pub fn pop(&mut self) -> bool {
238 match self.parent() {
239 Some(parent) => {
240 let buf = parent.as_str().to_string();
241 self.inner = buf;
242 true
243 },
244 None => false,
245 }
246 }
247}
248
249impl<'a> core::ops::AddAssign<&'a Path> for PathBuf {
250 fn add_assign(&mut self, rhs: &'a Path) {
251 self.push(rhs);
252 }
253}
254
255impl<'a> core::ops::AddAssign<&'a str> for PathBuf {
256 fn add_assign(&mut self, rhs: &'a str) {
257 self.push_component(rhs);
258 }
259}
260
261impl<'a> core::ops::AddAssign<&'a Ident> for PathBuf {
262 fn add_assign(&mut self, rhs: &'a Ident) {
263 self.push_component(rhs.as_str());
264 }
265}
266
267impl<'a> core::ops::AddAssign<&'a crate::ast::ProcedureName> for PathBuf {
268 fn add_assign(&mut self, rhs: &'a crate::ast::ProcedureName) {
269 self.push_component(rhs.as_str());
270 }
271}
272
273impl<'a> TryFrom<&'a str> for PathBuf {
274 type Error = PathError;
275
276 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
277 PathBuf::new(value)
278 }
279}
280
281impl TryFrom<String> for PathBuf {
282 type Error = PathError;
283
284 fn try_from(value: String) -> Result<Self, Self::Error> {
285 Path::validate(&value)?;
286 Ok(PathBuf { inner: value })
287 }
288}
289
290impl From<Ident> for PathBuf {
291 fn from(component: Ident) -> Self {
292 let mut buf = PathBuf::with_capacity(component.as_str().len());
293 buf.push_component(component.as_str());
294 buf
295 }
296}
297
298impl From<PathBuf> for String {
299 fn from(path: PathBuf) -> Self {
300 path.inner
301 }
302}
303
304impl From<PathBuf> for alloc::sync::Arc<Path> {
305 fn from(value: PathBuf) -> Self {
306 value.into_boxed_path().into()
307 }
308}
309
310impl From<alloc::borrow::Cow<'_, Path>> for PathBuf {
311 fn from(value: alloc::borrow::Cow<'_, Path>) -> Self {
312 value.into_owned()
313 }
314}
315
316impl FromStr for PathBuf {
317 type Err = PathError;
318
319 #[inline]
320 fn from_str(value: &str) -> Result<Self, Self::Err> {
321 Self::new(value)
322 }
323}
324
325impl Serializable for PathBuf {
326 fn write_into<W: ByteWriter>(&self, target: &mut W) {
327 self.as_path().write_into(target);
328 }
329}
330
331impl Serializable for Path {
332 fn write_into<W: ByteWriter>(&self, target: &mut W) {
333 target.write_u16(self.byte_len().try_into().expect("invalid path: too long"));
334 target.write_bytes(self.as_str().as_bytes());
335 }
336}
337
338impl Deserializable for PathBuf {
339 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
340 let len = source.read_u16()? as usize;
341 let path = source.read_slice(len)?;
342 let path =
343 str::from_utf8(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
344 Path::validate(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
345 Ok(PathBuf { inner: path.to_string() })
349 }
350}
351
352#[cfg(feature = "serde")]
353impl serde::Serialize for PathBuf {
354 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
355 where
356 S: serde::Serializer,
357 {
358 serializer.serialize_str(self.inner.as_str())
359 }
360}
361
362#[cfg(feature = "serde")]
363impl<'de> serde::Deserialize<'de> for PathBuf {
364 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
365 where
366 D: serde::Deserializer<'de>,
367 {
368 use serde::de::Visitor;
369
370 struct PathVisitor;
371
372 impl<'de> Visitor<'de> for PathVisitor {
373 type Value = PathBuf;
374
375 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
376 formatter.write_str("a valid Path/PathBuf")
377 }
378
379 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
380 where
381 E: serde::de::Error,
382 {
383 Path::validate(v).map_err(serde::de::Error::custom)?;
384 Ok(PathBuf { inner: v.to_string() })
389 }
390
391 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
392 where
393 E: serde::de::Error,
394 {
395 Path::validate(&v).map_err(serde::de::Error::custom)?;
396 Ok(PathBuf { inner: v })
401 }
402 }
403
404 deserializer.deserialize_any(PathVisitor)
405 }
406}
407
408impl fmt::Display for PathBuf {
409 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410 fmt::Display::fmt(self.as_path(), f)
411 }
412}
413
414#[cfg(test)]
419mod tests {
420
421 use miden_core::assert_matches;
422
423 use super::{PathBuf, PathError};
424 use crate::{Path, PathComponent, ast::IdentError};
425
426 #[test]
427 fn single_component_path() {
428 let path = PathBuf::new("foo").unwrap();
429 assert!(!path.is_absolute());
430 assert_eq!(path.components().count(), 1);
431 assert_eq!(path.last(), Some("foo"));
432 assert_eq!(path.first(), Some("foo"));
433 }
434
435 #[test]
436 fn relative_path_two_components() {
437 let path = PathBuf::new("foo::bar").unwrap();
438 assert!(!path.is_absolute());
439 assert_eq!(path.components().count(), 2);
440 assert_eq!(path.last(), Some("bar"));
441 assert_eq!(path.first(), Some("foo"));
442 }
443
444 #[test]
445 fn relative_path_three_components() {
446 let path = PathBuf::new("foo::bar::baz").unwrap();
447 assert!(!path.is_absolute());
448 assert_eq!(path.components().count(), 3);
449 assert_eq!(path.last(), Some("baz"));
450 assert_eq!(path.first(), Some("foo"));
451 assert_eq!(path.parent().map(|p| p.as_str()), Some("foo::bar"));
452 }
453
454 #[test]
455 fn single_quoted_component() {
456 let path = PathBuf::new("\"miden::base/account@0.1.0\"").unwrap();
457 assert!(!path.is_absolute());
458 assert_eq!(path.components().count(), 1);
459 assert_eq!(path.last(), Some("miden::base/account@0.1.0"));
460 assert_eq!(path.first(), Some("miden::base/account@0.1.0"));
461 }
462
463 #[test]
464 fn trailing_quoted_component() {
465 let path = PathBuf::new("foo::\"miden::base/account@0.1.0\"").unwrap();
466 assert!(!path.is_absolute());
467 assert_eq!(path.components().count(), 2);
468 assert_eq!(path.last(), Some("miden::base/account@0.1.0"));
469 assert_eq!(path.first(), Some("foo"));
470 }
471
472 #[test]
473 fn interspersed_quoted_component() {
474 let path = PathBuf::new("foo::\"miden::base/account@0.1.0\"::item").unwrap();
475 assert!(!path.is_absolute());
476 assert_eq!(path.components().count(), 3);
477 assert_eq!(path.last(), Some("item"));
478 assert_eq!(path.first(), Some("foo"));
479 assert_eq!(path.parent().map(|p| p.as_str()), Some("foo::\"miden::base/account@0.1.0\""));
480 }
481
482 #[test]
483 fn mixed_quoted_components_regression() {
484 let component = "::root_ns:root@1.0.0";
485 let module = "abi_transform_tx_kernel_get_inputs_4";
486 let function = "miden::protocol::active_note::get_inputs";
487 let quoted_function = "\"miden::protocol::active_note::get_inputs\"";
488
489 let p1 = PathBuf::new(&format!("{component}::{module}::\"{function}\"")).unwrap();
490 let mut p2 = PathBuf::new(component).unwrap();
491 p2.push_component(module);
492 p2.push_component(function);
493
494 assert_eq!(p1, p2);
495
496 let mut p1components = p1.components();
499 let mut p2components = p2.components();
500
501 let p1root = p1components.next().unwrap().unwrap();
502 let p2root = p2components.next().unwrap().unwrap();
503
504 assert_eq!(p1root, PathComponent::Root);
505 assert_eq!(p1root, p2root);
506
507 let p1component = p1components.next().unwrap().unwrap();
508 let p2component = p2components.next().unwrap().unwrap();
509
510 assert_eq!(p1component, PathComponent::Normal("\"root_ns:root@1.0.0\""));
511 assert_eq!(p1component, p2component);
512
513 let p1module = p1components.next().unwrap().unwrap();
514 let p2module = p2components.next().unwrap().unwrap();
515
516 assert_eq!(p1module, PathComponent::Normal(module));
517 assert_eq!(p1module, p2module);
518
519 let p1function = p1components.next().unwrap().unwrap();
520 let p2function = p2components.next().unwrap().unwrap();
521
522 assert_eq!(p1function, PathComponent::Normal(quoted_function));
523 assert_eq!(p1function, p2function);
524
525 let mut p1components = p1.components();
528 let mut p2components = p2.components();
529
530 let p1function = p1components.next_back().unwrap().unwrap();
531 let p2function = p2components.next_back().unwrap().unwrap();
532
533 assert_eq!(p1function, PathComponent::Normal(quoted_function));
534 assert_eq!(p1function, p2function);
535
536 let p1module = p1components.next_back().unwrap().unwrap();
537 let p2module = p2components.next_back().unwrap().unwrap();
538
539 assert_eq!(p1module, PathComponent::Normal(module));
540 assert_eq!(p1module, p2module);
541
542 let p1component = p1components.next_back().unwrap().unwrap();
543 let p2component = p2components.next_back().unwrap().unwrap();
544
545 assert_eq!(p1component, PathComponent::Normal("\"root_ns:root@1.0.0\""));
546 assert_eq!(p1component, p2component);
547
548 let p1root = p1components.next_back().unwrap().unwrap();
549 let p2root = p2components.next_back().unwrap().unwrap();
550
551 assert_eq!(p1root, PathComponent::Root);
552 assert_eq!(p1root, p2root);
553
554 assert!(p1.is_absolute());
555 assert_eq!(p1.components().count(), 4);
556 assert_eq!(p1.last(), Some(function));
557 assert_eq!(p1.first(), Some("root_ns:root@1.0.0"));
558 let parent = p1.parent().unwrap();
559 assert_eq!(
560 parent.as_str(),
561 "::\"root_ns:root@1.0.0\"::abi_transform_tx_kernel_get_inputs_4"
562 );
563 assert_eq!(parent.parent().map(|p| p.as_str()), Some("::\"root_ns:root@1.0.0\""));
564
565 assert!(p2.is_absolute());
566 assert_eq!(p2.components().count(), 4);
567 assert_eq!(p2.last(), Some(function));
568 assert_eq!(p2.first(), Some("root_ns:root@1.0.0"));
569 let parent = p2.parent().unwrap();
570 assert_eq!(
571 parent.as_str(),
572 "::\"root_ns:root@1.0.0\"::abi_transform_tx_kernel_get_inputs_4"
573 );
574 assert_eq!(parent.parent().map(|p| p.as_str()), Some("::\"root_ns:root@1.0.0\""));
575 }
576
577 #[test]
578 fn exec_path() {
579 let path = PathBuf::new("$exec::bar::baz").unwrap();
580 assert!(path.is_absolute());
581 assert_eq!(path.components().count(), 4);
582 assert_eq!(path.last(), Some("baz"));
583 assert_eq!(path.first(), Some("$exec"));
584 }
585
586 #[test]
587 fn kernel_path() {
588 let path = PathBuf::new("$kernel::bar::baz").unwrap();
589 assert!(path.is_absolute());
590 assert_eq!(path.components().count(), 4);
591 assert_eq!(path.last(), Some("baz"));
592 assert_eq!(path.first(), Some("$kernel"));
593 }
594
595 #[test]
596 fn invalid_path_empty() {
597 let result = Path::validate("");
598 assert_matches!(result, Err(PathError::Empty));
599 }
600
601 #[test]
602 fn invalid_path_empty_component() {
603 let result = Path::validate("::");
604 assert_matches!(result, Err(PathError::EmptyComponent));
605 }
606
607 #[test]
608 fn invalid_path_trailing_delimiter() {
609 let result = Path::validate("foo::");
610 assert_matches!(result, Err(PathError::InvalidComponent(IdentError::Empty)));
611 }
612
613 #[test]
614 fn invalid_path_invalid_character() {
615 let result = Path::validate("#foo::bar");
616 assert_matches!(result, Err(PathError::InvalidComponent(IdentError::InvalidChars { .. })));
617 }
618}