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, 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    /// Extends `self` with `component`.
202    ///
203    /// Unlike [`Self::push`], which appends another `Path` to the buffer - this method appends the
204    /// given string as a single path component, ensuring that the content is quoted properly if
205    /// needed.
206    pub fn push_component<S>(&mut self, component: &S)
207    where
208        S: AsRef<str> + ?Sized,
209    {
210        let component = component.as_ref();
211        match component {
212            "" | "\"\"" => (),
213            "::" if self.inner.is_empty() => {
214                self.inner.push_str("::");
215            },
216            component => {
217                if !self.is_empty() {
218                    self.inner.push_str("::");
219                }
220
221                if component.starts_with('"') && component.ends_with('"') {
222                    self.inner.push_str(component);
223                } else {
224                    match Ident::validate(component) {
225                        // If the component is a valid MASM identifier, and does not contain `::`,
226                        // it does not need quoting
227                        Ok(_) if !component.contains("::") => {
228                            self.inner.push_str(component);
229                        },
230                        // If either of these validation occurs, the component requires quoting
231                        Ok(_)
232                        | Err(
233                            crate::ast::IdentError::Casing(_)
234                            | crate::ast::IdentError::InvalidChars { .. },
235                        ) => {
236                            self.inner.push('"');
237                            self.inner.push_str(component);
238                            self.inner.push('"');
239                        },
240                        // These two variants are unreachable because:
241                        //
242                        // 1. Length is not checked by Ident::validate
243                        // 2. We've already checked that the component is non-empty
244                        Err(
245                            crate::ast::IdentError::InvalidLength { .. }
246                            | crate::ast::IdentError::Empty,
247                        ) => unreachable!(),
248                    }
249                }
250            },
251        }
252    }
253
254    /// Truncates `self` to [`Path::parent`].
255    ///
256    /// Returns `false` if `self.parent()` is `None`, otherwise `true`.
257    pub fn pop(&mut self) -> bool {
258        match self.parent() {
259            Some(parent) => {
260                let buf = parent.as_str().to_string();
261                self.inner = buf;
262                true
263            },
264            None => false,
265        }
266    }
267}
268
269impl<'a> core::ops::AddAssign<&'a Path> for PathBuf {
270    fn add_assign(&mut self, rhs: &'a Path) {
271        self.push(rhs);
272    }
273}
274
275impl<'a> core::ops::AddAssign<&'a str> for PathBuf {
276    fn add_assign(&mut self, rhs: &'a str) {
277        self.push(rhs);
278    }
279}
280
281impl<'a> core::ops::AddAssign<&'a Ident> for PathBuf {
282    fn add_assign(&mut self, rhs: &'a Ident) {
283        self.push(rhs.as_str());
284    }
285}
286
287impl<'a> core::ops::AddAssign<&'a crate::ast::ProcedureName> for PathBuf {
288    fn add_assign(&mut self, rhs: &'a crate::ast::ProcedureName) {
289        self.push(rhs.as_str());
290    }
291}
292
293impl<'a> TryFrom<&'a str> for PathBuf {
294    type Error = PathError;
295
296    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
297        PathBuf::new(value)
298    }
299}
300
301impl TryFrom<String> for PathBuf {
302    type Error = PathError;
303
304    fn try_from(value: String) -> Result<Self, Self::Error> {
305        Path::validate(&value)?;
306        Ok(PathBuf { inner: value })
307    }
308}
309
310impl From<Ident> for PathBuf {
311    fn from(component: Ident) -> Self {
312        PathBuf { inner: component.as_str().to_string() }
313    }
314}
315
316impl From<PathBuf> for String {
317    fn from(path: PathBuf) -> Self {
318        path.inner
319    }
320}
321
322impl From<PathBuf> for alloc::sync::Arc<Path> {
323    fn from(value: PathBuf) -> Self {
324        value.into_boxed_path().into()
325    }
326}
327
328impl From<alloc::borrow::Cow<'_, Path>> for PathBuf {
329    fn from(value: alloc::borrow::Cow<'_, Path>) -> Self {
330        value.into_owned()
331    }
332}
333
334impl FromStr for PathBuf {
335    type Err = PathError;
336
337    #[inline]
338    fn from_str(value: &str) -> Result<Self, Self::Err> {
339        Self::new(value)
340    }
341}
342
343impl Serializable for PathBuf {
344    fn write_into<W: ByteWriter>(&self, target: &mut W) {
345        self.as_path().write_into(target);
346    }
347}
348
349impl Serializable for Path {
350    fn write_into<W: ByteWriter>(&self, target: &mut W) {
351        target.write_u16(self.byte_len().try_into().expect("invalid path: too long"));
352        target.write_bytes(self.as_str().as_bytes());
353    }
354}
355
356impl Deserializable for PathBuf {
357    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
358        let len = source.read_u16()? as usize;
359        let path = source.read_slice(len)?;
360        let path =
361            str::from_utf8(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
362        Self::new(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))
363    }
364}
365
366#[cfg(feature = "serde")]
367impl serde::Serialize for PathBuf {
368    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
369    where
370        S: serde::Serializer,
371    {
372        serializer.serialize_str(self.inner.as_str())
373    }
374}
375
376#[cfg(feature = "serde")]
377impl<'de> serde::Deserialize<'de> for PathBuf {
378    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
379    where
380        D: serde::Deserializer<'de>,
381    {
382        let inner = <&'de str as serde::Deserialize<'de>>::deserialize(deserializer)?;
383
384        PathBuf::new(inner).map_err(serde::de::Error::custom)
385    }
386}
387
388impl fmt::Display for PathBuf {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        fmt::Display::fmt(self.as_path(), f)
391    }
392}
393
394// TESTS
395// ================================================================================================
396
397/// Tests
398#[cfg(test)]
399mod tests {
400
401    use miden_core::assert_matches;
402
403    use super::{PathBuf, PathError};
404    use crate::{Path, PathComponent, ast::IdentError};
405
406    #[test]
407    fn single_component_path() {
408        let path = PathBuf::new("foo").unwrap();
409        assert!(!path.is_absolute());
410        assert_eq!(path.components().count(), 1);
411        assert_eq!(path.last(), Some("foo"));
412        assert_eq!(path.first(), Some("foo"));
413    }
414
415    #[test]
416    fn relative_path_two_components() {
417        let path = PathBuf::new("foo::bar").unwrap();
418        assert!(!path.is_absolute());
419        assert_eq!(path.components().count(), 2);
420        assert_eq!(path.last(), Some("bar"));
421        assert_eq!(path.first(), Some("foo"));
422    }
423
424    #[test]
425    fn relative_path_three_components() {
426        let path = PathBuf::new("foo::bar::baz").unwrap();
427        assert!(!path.is_absolute());
428        assert_eq!(path.components().count(), 3);
429        assert_eq!(path.last(), Some("baz"));
430        assert_eq!(path.first(), Some("foo"));
431        assert_eq!(path.parent().map(|p| p.as_str()), Some("foo::bar"));
432    }
433
434    #[test]
435    fn single_quoted_component() {
436        let path = PathBuf::new("\"miden:base/account@0.1.0\"").unwrap();
437        assert!(!path.is_absolute());
438        assert_eq!(path.components().count(), 1);
439        assert_eq!(path.last(), Some("\"miden:base/account@0.1.0\""));
440        assert_eq!(path.first(), Some("\"miden:base/account@0.1.0\""));
441    }
442
443    #[test]
444    fn trailing_quoted_component() {
445        let path = PathBuf::new("foo::\"miden:base/account@0.1.0\"").unwrap();
446        assert!(!path.is_absolute());
447        assert_eq!(path.components().count(), 2);
448        assert_eq!(path.last(), Some("\"miden:base/account@0.1.0\""));
449        assert_eq!(path.first(), Some("foo"));
450    }
451
452    #[test]
453    fn interspersed_quoted_component() {
454        let path = PathBuf::new("foo::\"miden:base/account@0.1.0\"::item").unwrap();
455        assert!(!path.is_absolute());
456        assert_eq!(path.components().count(), 3);
457        assert_eq!(path.last(), Some("item"));
458        assert_eq!(path.first(), Some("foo"));
459        assert_eq!(path.parent().map(|p| p.as_str()), Some("foo::\"miden:base/account@0.1.0\""));
460    }
461
462    #[test]
463    fn mixed_quoted_components_regression() {
464        let component = "::root_ns:root@1.0.0";
465        let module = "abi_transform_tx_kernel_get_inputs_4";
466        let function = "miden::protocol::active_note::get_inputs";
467        let quoted_function = "\"miden::protocol::active_note::get_inputs\"";
468
469        let p1 = PathBuf::new(&format!("{component}::{module}::\"{function}\"")).unwrap();
470        let mut p2 = PathBuf::new(component).unwrap();
471        p2.push_component(module);
472        p2.push_component(function);
473
474        assert_eq!(p1, p2);
475
476        // Forward iteration of path components
477
478        let mut p1components = p1.components();
479        let mut p2components = p2.components();
480
481        let p1root = p1components.next().unwrap().unwrap();
482        let p2root = p2components.next().unwrap().unwrap();
483
484        assert_eq!(p1root, PathComponent::Root);
485        assert_eq!(p1root, p2root);
486
487        let p1component = p1components.next().unwrap().unwrap();
488        let p2component = p2components.next().unwrap().unwrap();
489
490        assert_eq!(p1component, PathComponent::Normal("root_ns:root@1.0.0"));
491        assert_eq!(p1component, p2component);
492
493        let p1module = p1components.next().unwrap().unwrap();
494        let p2module = p2components.next().unwrap().unwrap();
495
496        assert_eq!(p1module, PathComponent::Normal(module));
497        assert_eq!(p1module, p2module);
498
499        let p1function = p1components.next().unwrap().unwrap();
500        let p2function = p2components.next().unwrap().unwrap();
501
502        assert_eq!(p1function, PathComponent::Normal(quoted_function));
503        assert_eq!(p1function, p2function);
504
505        // Backward iteration of path components
506
507        let mut p1components = p1.components();
508        let mut p2components = p2.components();
509
510        let p1function = p1components.next_back().unwrap().unwrap();
511        let p2function = p2components.next_back().unwrap().unwrap();
512
513        assert_eq!(p1function, PathComponent::Normal(quoted_function));
514        assert_eq!(p1function, p2function);
515
516        let p1module = p1components.next_back().unwrap().unwrap();
517        let p2module = p2components.next_back().unwrap().unwrap();
518
519        assert_eq!(p1module, PathComponent::Normal(module));
520        assert_eq!(p1module, p2module);
521
522        let p1component = p1components.next_back().unwrap().unwrap();
523        let p2component = p2components.next_back().unwrap().unwrap();
524
525        assert_eq!(p1component, PathComponent::Normal("root_ns:root@1.0.0"));
526        assert_eq!(p1component, p2component);
527
528        let p1root = p1components.next_back().unwrap().unwrap();
529        let p2root = p2components.next_back().unwrap().unwrap();
530
531        assert_eq!(p1root, PathComponent::Root);
532        assert_eq!(p1root, p2root);
533
534        assert!(p1.is_absolute());
535        assert_eq!(p1.components().count(), 4);
536        assert_eq!(p1.last(), Some(quoted_function));
537        assert_eq!(p1.first(), Some("root_ns:root@1.0.0"));
538        let parent = p1.parent().unwrap();
539        assert_eq!(parent.as_str(), "::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4");
540        assert_eq!(parent.parent().map(|p| p.as_str()), Some("::root_ns:root@1.0.0"));
541
542        assert!(p2.is_absolute());
543        assert_eq!(p2.components().count(), 4);
544        assert_eq!(p2.last(), Some(quoted_function));
545        assert_eq!(p2.first(), Some("root_ns:root@1.0.0"));
546        let parent = p2.parent().unwrap();
547        assert_eq!(parent.as_str(), "::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4");
548        assert_eq!(parent.parent().map(|p| p.as_str()), Some("::root_ns:root@1.0.0"));
549    }
550
551    #[test]
552    fn exec_path() {
553        let path = PathBuf::new("$exec::bar::baz").unwrap();
554        assert!(path.is_absolute());
555        assert_eq!(path.components().count(), 4);
556        assert_eq!(path.last(), Some("baz"));
557        assert_eq!(path.first(), Some("$exec"));
558    }
559
560    #[test]
561    fn kernel_path() {
562        let path = PathBuf::new("$kernel::bar::baz").unwrap();
563        assert!(path.is_absolute());
564        assert_eq!(path.components().count(), 4);
565        assert_eq!(path.last(), Some("baz"));
566        assert_eq!(path.first(), Some("$kernel"));
567    }
568
569    #[test]
570    fn invalid_path_empty() {
571        let result = Path::validate("");
572        assert_matches!(result, Err(PathError::Empty));
573    }
574
575    #[test]
576    fn invalid_path_empty_component() {
577        let result = Path::validate("::");
578        assert_matches!(result, Err(PathError::EmptyComponent));
579    }
580
581    #[test]
582    fn invalid_path_trailing_delimiter() {
583        let result = Path::validate("foo::");
584        assert_matches!(result, Err(PathError::InvalidComponent(IdentError::Empty)));
585    }
586
587    #[test]
588    fn invalid_path_invalid_character() {
589        let result = Path::validate("#foo::bar");
590        assert_matches!(result, Err(PathError::InvalidComponent(IdentError::InvalidChars { .. })));
591    }
592}