Skip to main content

miden_assembly_syntax/ast/path/
path_buf.rs

1use 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// ITEM PATH
16// ================================================================================================
17
18/// Path to an item in a library, i.e. module, procedure, constant or type.
19#[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
58/// Constructors
59impl PathBuf {
60    /// Get an empty [PathBuf] with `capacity` bytes allocated for the underlying path storage
61    pub fn with_capacity(capacity: usize) -> Self {
62        Self { inner: String::with_capacity(capacity) }
63    }
64
65    /// Returns a new path created from the provided source.
66    ///
67    /// A path consists of at list of components separated by `::` delimiter. A path must contain
68    /// at least one component.
69    ///
70    /// # Errors
71    ///
72    /// Returns an error if:
73    ///
74    /// * The path is empty.
75    /// * Any component of the path is empty.
76    /// * Any component is not a valid identifier (quoted or unquoted) in Miden Assembly syntax,
77    ///   i.e. starts with an ASCII alphabetic character, contains only printable ASCII characters,
78    ///   except for `::`, which must only be used as a path separator.
79    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    /// Extends this [PathBuf] from a collection of possibly-invalid path components
91    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    /// Create an absolute [Path] from a pre-validated string
102    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    /// Create a relative [Path] from a pre-validated string
111    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    /// Get a [Path] corresponding to the this [PathBuf]
123    #[inline]
124    pub fn as_path(&self) -> &Path {
125        self.as_ref()
126    }
127
128    /// Convert this mutable [PathBuf] into an owned, read-only [`alloc::boxed::Box<Path>`]
129    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        // SAFETY: This cast is safe because *mut Path is equivalent to *mut str
133        unsafe { alloc::boxed::Box::from_raw(inner as *mut Path) }
134    }
135}
136
137/// Mutation
138impl PathBuf {
139    /// Overrides the parent prefix of this path.
140    ///
141    /// The parent prefix is the part of the path consisting of all components but the last one.
142    ///
143    /// If there is only a single component in `self`, this function is equivalent to appending
144    /// `self` to `parent`.
145    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    /// Extends `self` with `path`
163    ///
164    /// If `path` is absolute, it replaces the current path.
165    ///
166    /// This function ensures that the joined path correctly delimits each path component, and that
167    /// each component is in canonical form.
168    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    /// Extends `self` with `component`.
178    ///
179    /// Unlike [`Self::push`], which appends another `Path` to the buffer - this method appends the
180    /// given string as a single path component, ensuring that the content is quoted properly if
181    /// needed.
182    ///
183    /// If `::` is pushed, it is treated as a literal root component (i.e. it makes the path
184    /// absolute). On a non-empty [PathBuf], this has the effect of clearing the path, similar to
185    /// what happens if you call [`PathBuf::push`] with an absolute path.
186    ///
187    /// Pushing components using this method guarantees they are in canonical form.
188    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                // Pushing the root component on a non-empty path, resets it to an empty absolute
200                // path
201                self.inner.clear();
202                self.inner.push_str("::");
203            },
204            component => {
205                // Add a delimiter if the path is non-empty
206                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                    // Special namespaces are always absolute
216                    self.inner.push_str("::");
217                    self.inner.push_str(component);
218                } else if requires_quoting && !is_quoted {
219                    // Quote when necessary
220                    self.inner.push('"');
221                    self.inner.push_str(component);
222                    self.inner.push('"');
223                } else if !requires_quoting && is_quoted {
224                    // Unquote unnecessary quoting
225                    self.inner.push_str(&component[1..(component.len() - 1)]);
226                } else {
227                    // No quoting required, or already quoted
228                    self.inner.push_str(component);
229                }
230            },
231        }
232    }
233
234    /// Truncates `self` to [`Path::parent`].
235    ///
236    /// Returns `false` if `self.parent()` is `None`, otherwise `true`.
237    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        // We deserialize like this due to our deserialization tests expecting round-trips to be
346        // identical to what was serialized, though ideally we'd like to canonicalize paths that
347        // were deserialized. We should probably change how these tests work in the future.
348        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                // We deserialize like this due to our deserialization tests expecting round-trips
385                // to be identical to what was serialized, though ideally we'd like
386                // to canonicalize paths that were deserialized. We should probably
387                // change how these tests work in the future.
388                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                // We deserialize like this due to our deserialization tests expecting round-trips
397                // to be identical to what was serialized, though ideally we'd like
398                // to canonicalize paths that were deserialized. We should probably
399                // change how these tests work in the future.
400                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// TESTS
415// ================================================================================================
416
417/// Tests
418#[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        // Forward iteration of path components
497
498        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        // Backward iteration of path components
526
527        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}