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, 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        // Ensure we canonicalize paths that are de-facto absolute to use the root prefix
88        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    /// Create an absolute [Path] from a pre-validated string
98    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    /// Create a relative [Path] from a pre-validated string
107    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    /// Get a [Path] corresponding to the this [PathBuf]
119    #[inline]
120    pub fn as_path(&self) -> &Path {
121        self.as_ref()
122    }
123
124    /// Convert this mutable [PathBuf] into an owned, read-only [`alloc::boxed::Box<Path>`]
125    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        // SAFETY: This cast is safe because *mut Path is equivalent to *mut str
129        unsafe { alloc::boxed::Box::from_raw(inner as *mut Path) }
130    }
131}
132
133/// Mutation
134impl PathBuf {
135    /// Overrides the parent prefix of this path.
136    ///
137    /// The parent prefix is the part of the path consisting of all components but the last one.
138    ///
139    /// If there is only a single component in `self`, this function is equivalent to appending
140    /// `self` to `parent`.
141    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    /// Extends `self` with `path`
165    ///
166    /// If `path` is absolute, it replaces the current path.
167    ///
168    /// This function ensures that the joined path correctly delimits each path component.
169    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            // Handle special symbols which are de-facto absolute by making the root prefix explicit
182            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    /// Truncates `self` to [`Path::parent`].
202    ///
203    /// Returns `false` if `self.parent()` is `None`, otherwise `true`.
204    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// TESTS
342// ================================================================================================
343
344/// Tests
345#[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}