Skip to main content

miden_assembly_syntax/ast/path/
path.rs

1use alloc::{
2    borrow::{Borrow, Cow, ToOwned},
3    string::ToString,
4};
5use core::fmt;
6
7use super::{Iter, Join, PathBuf, PathComponent, PathError, StartsWith};
8use crate::ast::Ident;
9
10/// A borrowed reference to a subset of a path, e.g. another [Path] or a [PathBuf]
11#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[repr(transparent)]
13pub struct Path {
14    /// A view into the selected components of the path, i.e. the parts delimited by `::`
15    inner: str,
16}
17
18impl fmt::Debug for Path {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        fmt::Debug::fmt(&self.inner, f)
21    }
22}
23
24#[cfg(feature = "serde")]
25impl serde::Serialize for Path {
26    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
27    where
28        S: serde::Serializer,
29    {
30        serializer.serialize_str(&self.inner)
31    }
32}
33
34#[cfg(feature = "serde")]
35impl<'de> serde::Deserialize<'de> for &'de Path {
36    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
37    where
38        D: serde::Deserializer<'de>,
39    {
40        use serde::de::Visitor;
41
42        struct PathVisitor;
43
44        impl<'de> Visitor<'de> for PathVisitor {
45            type Value = &'de Path;
46
47            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
48                formatter.write_str("a borrowed Path")
49            }
50
51            fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
52            where
53                E: serde::de::Error,
54            {
55                Path::validate(v).map_err(serde::de::Error::custom)
56            }
57        }
58
59        deserializer.deserialize_any(PathVisitor)
60    }
61}
62
63impl ToOwned for Path {
64    type Owned = PathBuf;
65    #[inline]
66    fn to_owned(&self) -> PathBuf {
67        self.to_path_buf()
68    }
69    #[inline]
70    fn clone_into(&self, target: &mut Self::Owned) {
71        self.inner.clone_into(&mut target.inner)
72    }
73}
74
75impl Borrow<Path> for PathBuf {
76    fn borrow(&self) -> &Path {
77        Path::new(self)
78    }
79}
80
81impl AsRef<str> for Path {
82    #[inline]
83    fn as_ref(&self) -> &str {
84        &self.inner
85    }
86}
87
88impl AsRef<Path> for str {
89    #[inline(always)]
90    fn as_ref(&self) -> &Path {
91        unsafe { &*(self as *const str as *const Path) }
92    }
93}
94
95impl AsRef<Path> for Ident {
96    #[inline(always)]
97    fn as_ref(&self) -> &Path {
98        self.as_str().as_ref()
99    }
100}
101
102impl AsRef<Path> for crate::ast::ProcedureName {
103    #[inline(always)]
104    fn as_ref(&self) -> &Path {
105        let ident: &Ident = self.as_ref();
106        ident.as_str().as_ref()
107    }
108}
109
110impl AsRef<Path> for crate::ast::QualifiedProcedureName {
111    #[inline(always)]
112    fn as_ref(&self) -> &Path {
113        self.as_path()
114    }
115}
116
117impl AsRef<Path> for Path {
118    #[inline(always)]
119    fn as_ref(&self) -> &Path {
120        self
121    }
122}
123
124impl From<&Path> for alloc::sync::Arc<Path> {
125    fn from(path: &Path) -> Self {
126        path.to_path_buf().into()
127    }
128}
129
130/// Conversions
131impl Path {
132    /// Path components  must be 255 bytes or less
133    pub const MAX_COMPONENT_LENGTH: usize = u8::MAX as usize;
134
135    /// An empty path for use as a default value, placeholder, comparisons, etc.
136    pub const EMPTY: &Path = unsafe { &*("" as *const str as *const Path) };
137
138    /// Base kernel path.
139    pub const KERNEL_PATH: &str = "$kernel";
140    pub const ABSOLUTE_KERNEL_PATH: &str = "::$kernel";
141    pub const KERNEL: &Path =
142        unsafe { &*(Self::ABSOLUTE_KERNEL_PATH as *const str as *const Path) };
143
144    /// Path for an executable module.
145    pub const EXEC_PATH: &str = "$exec";
146    pub const ABSOLUTE_EXEC_PATH: &str = "::$exec";
147    pub const EXEC: &Path = unsafe { &*(Self::ABSOLUTE_EXEC_PATH as *const str as *const Path) };
148
149    pub fn new<S: AsRef<str> + ?Sized>(path: &S) -> &Path {
150        // SAFETY: The representation of Path is equivalent to str
151        unsafe { &*(path.as_ref() as *const str as *const Path) }
152    }
153
154    pub fn from_mut(path: &mut str) -> &mut Path {
155        // SAFETY: The representation of Path is equivalent to str
156        unsafe { &mut *(path as *mut str as *mut Path) }
157    }
158
159    /// Verify that `path` meets all the requirements for a valid [Path]
160    pub fn validate(path: &str) -> Result<&Path, PathError> {
161        match path {
162            "" | "\"\"" => return Err(PathError::Empty),
163            "::" => return Err(PathError::EmptyComponent),
164            _ => (),
165        }
166
167        if path.len() > u16::MAX as usize {
168            return Err(PathError::TooLong { max: u16::MAX as usize });
169        }
170
171        for result in Iter::new(path) {
172            result?;
173        }
174
175        Ok(Path::new(path))
176    }
177
178    /// Get a [Path] corresponding to [Self::KERNEL_PATH]
179    pub const fn kernel_path() -> &'static Path {
180        Path::KERNEL
181    }
182
183    /// Get a [Path] corresponding to [Self::EXEC_PATH]
184    pub const fn exec_path() -> &'static Path {
185        Path::EXEC
186    }
187
188    #[inline]
189    pub const fn as_str(&self) -> &str {
190        &self.inner
191    }
192
193    #[inline]
194    pub fn as_mut_str(&mut self) -> &mut str {
195        &mut self.inner
196    }
197
198    /// Get an [Ident] that is equivalent to this [Path], so long as the path has only a single
199    /// component.
200    ///
201    /// Returns `None` if the path cannot be losslessly represented as a single component.
202    pub fn as_ident(&self) -> Option<Ident> {
203        let mut components = self.components().filter_map(|c| c.ok());
204        match components.next()? {
205            component @ PathComponent::Normal(_) => {
206                if components.next().is_none() {
207                    component.to_ident()
208                } else {
209                    None
210                }
211            },
212            PathComponent::Root => None,
213        }
214    }
215
216    /// Convert this [Path] to an owned [PathBuf]
217    pub fn to_path_buf(&self) -> PathBuf {
218        PathBuf { inner: self.inner.to_string() }
219    }
220
221    /// Convert an [Ident] to an equivalent [Path] or [PathBuf], depending on whether the identifier
222    /// would require quoting as a path.
223    pub fn from_ident(ident: &Ident) -> Cow<'_, Path> {
224        let ident = ident.as_str();
225        if Ident::requires_quoting(ident) {
226            let mut buf = PathBuf::with_capacity(ident.len() + 2);
227            buf.push_component(ident);
228            Cow::Owned(buf)
229        } else {
230            Cow::Borrowed(Path::new(ident))
231        }
232    }
233}
234
235/// Accesssors
236impl Path {
237    /// Returns true if this path is empty (i.e. has no components)
238    pub fn is_empty(&self) -> bool {
239        matches!(&self.inner, "" | "::" | "\"\"")
240    }
241
242    /// Returns the number of components in the path
243    pub fn len(&self) -> usize {
244        self.components().count()
245    }
246
247    /// Return the size of the path in [char]s when displayed as a string
248    pub fn char_len(&self) -> usize {
249        self.inner.chars().count()
250    }
251
252    /// Return the size of the path in bytes when displayed as a string
253    #[inline]
254    pub fn byte_len(&self) -> usize {
255        self.inner.len()
256    }
257
258    /// Returns true if this path is an absolute path
259    pub fn is_absolute(&self) -> bool {
260        matches!(self.components().next(), Some(Ok(PathComponent::Root)))
261    }
262
263    /// Make this path absolute, if not already
264    ///
265    /// NOTE: This does not _resolve_ the path, it simply ensures the path has the root prefix
266    pub fn to_absolute(&self) -> Cow<'_, Path> {
267        if self.is_absolute() {
268            Cow::Borrowed(self)
269        } else {
270            let mut buf = PathBuf::with_capacity(self.byte_len() + 2);
271            buf.push_component("::");
272            buf.extend_with_components(self.components()).expect("invalid path");
273            Cow::Owned(buf)
274        }
275    }
276
277    /// Strip the root prefix from this path, if it has one.
278    pub fn to_relative(&self) -> &Path {
279        match self.inner.strip_prefix("::") {
280            Some(rest) => Path::new(rest),
281            None => self,
282        }
283    }
284
285    /// Returns the [Path] without its final component, if there is one.
286    ///
287    /// This means it may return an empty [Path] for relative paths with a single component.
288    ///
289    /// Returns `None` if the path terminates with the root prefix, or if it is empty.
290    pub fn parent(&self) -> Option<&Path> {
291        let mut components = self.components();
292        match components.next_back()?.ok()? {
293            PathComponent::Root => None,
294            _ => Some(components.as_path()),
295        }
296    }
297
298    /// Returns an iterator over all components of the path.
299    pub fn components(&self) -> Iter<'_> {
300        Iter::new(&self.inner)
301    }
302
303    /// Get the first non-root component of this path as a `str`
304    ///
305    /// Returns `None` if the path is empty, or consists only of the root prefix.
306    pub fn first(&self) -> Option<&str> {
307        self.split_first().map(|(first, _)| first)
308    }
309
310    /// Get the first non-root component of this path as a `str`
311    ///
312    /// Returns `None` if the path is empty, or consists only of the root prefix.
313    pub fn last(&self) -> Option<&str> {
314        self.split_last().map(|(last, _)| last)
315    }
316
317    /// Splits this path on the first non-root component, returning it and a new [Path] of the
318    /// remaining components.
319    ///
320    /// Returns `None` if there are no components to split
321    pub fn split_first(&self) -> Option<(&str, &Path)> {
322        let mut components = self.components();
323        match components.next()?.ok()? {
324            PathComponent::Root => {
325                let first = components.next().and_then(|c| c.ok()).map(|c| c.as_str())?;
326                Some((first, components.as_path()))
327            },
328            first @ PathComponent::Normal(_) => Some((first.as_str(), components.as_path())),
329        }
330    }
331
332    /// Splits this path on the last component, returning it and a new [Path] of the remaining
333    /// components.
334    ///
335    /// Returns `None` if there are no components to split
336    pub fn split_last(&self) -> Option<(&str, &Path)> {
337        let mut components = self.components();
338        match components.next_back()?.ok()? {
339            PathComponent::Root => None,
340            last @ PathComponent::Normal(_) => Some((last.as_str(), components.as_path())),
341        }
342    }
343
344    /// Returns true if this path is for the root kernel module.
345    pub fn is_kernel_path(&self) -> bool {
346        match self.inner.strip_prefix("::") {
347            Some(Self::KERNEL_PATH) => true,
348            Some(_) => false,
349            None => &self.inner == Self::KERNEL_PATH,
350        }
351    }
352
353    /// Returns true if this path is for the root kernel module or an item in it
354    pub fn is_in_kernel(&self) -> bool {
355        if self.is_kernel_path() {
356            return true;
357        }
358
359        match self.split_last() {
360            Some((_, prefix)) => prefix.is_kernel_path(),
361            None => false,
362        }
363    }
364
365    /// Returns true if this path is for an executable module.
366    pub fn is_exec_path(&self) -> bool {
367        match self.inner.strip_prefix("::") {
368            Some(Self::EXEC_PATH) => true,
369            Some(_) => false,
370            None => &self.inner == Self::EXEC_PATH,
371        }
372    }
373
374    /// Returns true if this path is for the executable module or an item in it
375    pub fn is_in_exec(&self) -> bool {
376        if self.is_exec_path() {
377            return true;
378        }
379
380        match self.split_last() {
381            Some((_, prefix)) => prefix.is_exec_path(),
382            None => false,
383        }
384    }
385
386    /// Returns true if the current path, sans root component, starts with `prefix`
387    ///
388    /// The matching semantics of `Prefix` depend on the implementation of [`StartsWith<Prefix>`],
389    /// in particular, if `Prefix` is `str`, then the prefix is matched against the first non-root
390    /// component of `self`, regardless of whether the string contains path delimiters (i.e. `::`).
391    ///
392    /// See the [StartsWith] trait for more details.
393    #[inline]
394    pub fn starts_with<Prefix>(&self, prefix: &Prefix) -> bool
395    where
396        Prefix: ?Sized,
397        Self: StartsWith<Prefix>,
398    {
399        <Self as StartsWith<Prefix>>::starts_with(self, prefix)
400    }
401
402    /// Returns true if the current path, including root component, starts with `prefix`
403    ///
404    /// The matching semantics of `Prefix` depend on the implementation of [`StartsWith<Prefix>`],
405    /// in particular, if `Prefix` is `str`, then the prefix is matched against the first component
406    /// of `self`, regardless of whether the string contains path delimiters (i.e. `::`).
407    ///
408    /// See the [StartsWith] trait for more details.
409    #[inline]
410    pub fn starts_with_exactly<Prefix>(&self, prefix: &Prefix) -> bool
411    where
412        Prefix: ?Sized,
413        Self: StartsWith<Prefix>,
414    {
415        <Self as StartsWith<Prefix>>::starts_with_exactly(self, prefix)
416    }
417
418    /// Strips `prefix` from `self`, or returns `None` if `self` does not start with `prefix`.
419    ///
420    /// NOTE: Prefixes must be exact, i.e. if you call `path.strip_prefix(prefix)` and `path` is
421    /// relative but `prefix` is absolute, then this will return `None`. The same is true if `path`
422    /// is absolute and `prefix` is relative.
423    pub fn strip_prefix<'a>(&'a self, prefix: &Self) -> Option<&'a Self> {
424        let mut components = self.components();
425        for prefix_component in prefix.components() {
426            // All `Path` APIs assume that a `Path` is valid upon construction, though this is not
427            // actually enforced currently. We assert here if iterating over the components of the
428            // path finds an invalid component, because we expected the caller to have already
429            // validated the path
430            //
431            // In the future, we will likely enforce validity at construction so that iterating
432            // over its components is infallible, but that will require a breaking change to some
433            // APIs
434            let prefix_component = prefix_component.expect("invalid prefix path");
435            match (components.next(), prefix_component) {
436                (Some(Ok(PathComponent::Root)), PathComponent::Root) => (),
437                (Some(Ok(c @ PathComponent::Normal(_))), pc @ PathComponent::Normal(_)) => {
438                    if c.as_str() != pc.as_str() {
439                        return None;
440                    }
441                },
442                (Some(Ok(_) | Err(_)) | None, _) => return None,
443            }
444        }
445        Some(components.as_path())
446    }
447
448    /// Create an owned [PathBuf] with `path` adjoined to `self`.
449    ///
450    /// If `path` is absolute, it replaces the current path.
451    ///
452    /// The semantics of how `other` is joined to `self` in the resulting path depends on the
453    /// implementation of [Join] used. The implementation for [Path] and [PathBuf] joins all
454    /// components of `other` to self`; while the implementation for [prim@str], string-like values,
455    /// and identifiers/symbols joins just a single component. You must be careful to ensure that
456    /// if you are passing a string here, that you specifically want to join it as a single
457    /// component, or the resulting path may be different than you expect. It is recommended that
458    /// you use `Path::new(&string)` if you want to be explicit about treating a string-like value
459    /// as a multi-component path.
460    #[inline]
461    pub fn join<P>(&self, other: &P) -> PathBuf
462    where
463        P: ?Sized,
464        Path: Join<P>,
465    {
466        <Path as Join<P>>::join(self, other)
467    }
468
469    /// Canonicalize this path by ensuring that all components are in canonical form.
470    ///
471    /// Canonical form dictates that:
472    ///
473    /// * A component is quoted only if it requires quoting, and unquoted otherwise
474    /// * Is made absolute if relative and the first component is $kernel or $exec
475    ///
476    /// Returns `Err` if the path is invalid
477    pub fn canonicalize(&self) -> Result<PathBuf, PathError> {
478        let mut buf = PathBuf::with_capacity(self.byte_len());
479        buf.extend_with_components(self.components())?;
480        Ok(buf)
481    }
482}
483
484impl StartsWith<str> for Path {
485    fn starts_with(&self, prefix: &str) -> bool {
486        let this = self.to_relative();
487        <Path as StartsWith<str>>::starts_with_exactly(this, prefix)
488    }
489
490    #[inline]
491    fn starts_with_exactly(&self, prefix: &str) -> bool {
492        match prefix {
493            "" => true,
494            "::" => self.is_absolute(),
495            prefix => {
496                let mut components = self.components();
497                let prefix = if let Some(prefix) = prefix.strip_prefix("::") {
498                    let is_absolute =
499                        components.next().is_some_and(|c| matches!(c, Ok(PathComponent::Root)));
500                    if !is_absolute {
501                        return false;
502                    }
503                    prefix
504                } else {
505                    prefix
506                };
507                components.next().is_some_and(
508                    |c| matches!(c, Ok(c @ PathComponent::Normal(_)) if c.as_str() == prefix),
509                )
510            },
511        }
512    }
513}
514
515impl StartsWith<Path> for Path {
516    fn starts_with(&self, prefix: &Path) -> bool {
517        let this = self.to_relative();
518        let prefix = prefix.to_relative();
519        <Path as StartsWith<Path>>::starts_with_exactly(this, prefix)
520    }
521
522    #[inline]
523    fn starts_with_exactly(&self, prefix: &Path) -> bool {
524        let mut components = self.components();
525        for prefix_component in prefix.components() {
526            // All `Path` APIs assume that a `Path` is valid upon construction, though this is not
527            // actually enforced currently. We assert here if iterating over the components of the
528            // path finds an invalid component, because we expected the caller to have already
529            // validated the path
530            //
531            // In the future, we will likely enforce validity at construction so that iterating
532            // over its components is infallible, but that will require a breaking change to some
533            // APIs
534            let prefix_component = prefix_component.expect("invalid prefix path");
535            match (components.next(), prefix_component) {
536                (Some(Ok(PathComponent::Root)), PathComponent::Root) => continue,
537                (Some(Ok(c @ PathComponent::Normal(_))), pc @ PathComponent::Normal(_)) => {
538                    if c.as_str() != pc.as_str() {
539                        return false;
540                    }
541                },
542                (Some(Ok(_) | Err(_)) | None, _) => return false,
543            }
544        }
545        true
546    }
547}
548
549impl PartialEq<str> for Path {
550    fn eq(&self, other: &str) -> bool {
551        &self.inner == other
552    }
553}
554
555impl PartialEq<PathBuf> for Path {
556    fn eq(&self, other: &PathBuf) -> bool {
557        &self.inner == other.inner.as_str()
558    }
559}
560
561impl PartialEq<&PathBuf> for Path {
562    fn eq(&self, other: &&PathBuf) -> bool {
563        &self.inner == other.inner.as_str()
564    }
565}
566
567impl PartialEq<Path> for PathBuf {
568    fn eq(&self, other: &Path) -> bool {
569        self.inner.as_str() == &other.inner
570    }
571}
572
573impl PartialEq<&Path> for Path {
574    fn eq(&self, other: &&Path) -> bool {
575        self.inner == other.inner
576    }
577}
578
579impl PartialEq<alloc::boxed::Box<Path>> for Path {
580    fn eq(&self, other: &alloc::boxed::Box<Path>) -> bool {
581        self.inner == other.inner
582    }
583}
584
585impl PartialEq<alloc::rc::Rc<Path>> for Path {
586    fn eq(&self, other: &alloc::rc::Rc<Path>) -> bool {
587        self.inner == other.inner
588    }
589}
590
591impl PartialEq<alloc::sync::Arc<Path>> for Path {
592    fn eq(&self, other: &alloc::sync::Arc<Path>) -> bool {
593        self.inner == other.inner
594    }
595}
596
597impl PartialEq<alloc::borrow::Cow<'_, Path>> for Path {
598    fn eq(&self, other: &alloc::borrow::Cow<'_, Path>) -> bool {
599        self.inner == other.as_ref().inner
600    }
601}
602
603impl fmt::Display for Path {
604    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
605        f.write_str(&self.inner)
606    }
607}
608
609#[cfg(test)]
610mod tests {
611    use super::*;
612
613    #[test]
614    fn test_canonicalize_path_identity() -> Result<(), PathError> {
615        let path = Path::new("foo::bar");
616        let canonicalized = path.canonicalize()?;
617
618        assert_eq!(canonicalized.as_path(), path);
619        Ok(())
620    }
621
622    #[test]
623    fn test_canonicalize_path_kernel_is_absolute() -> Result<(), PathError> {
624        let path = Path::new("$kernel::bar");
625        let canonicalized = path.canonicalize()?;
626
627        let expected = Path::new("::$kernel::bar");
628        assert_eq!(canonicalized.as_path(), expected);
629        Ok(())
630    }
631
632    #[test]
633    fn test_canonicalize_path_exec_is_absolute() -> Result<(), PathError> {
634        let path = Path::new("$exec::$main");
635        let canonicalized = path.canonicalize()?;
636
637        let expected = Path::new("::$exec::$main");
638        assert_eq!(canonicalized.as_path(), expected);
639        Ok(())
640    }
641
642    #[test]
643    fn test_canonicalize_path_remove_unnecessary_quoting() -> Result<(), PathError> {
644        let path = Path::new("foo::\"bar\"");
645        let canonicalized = path.canonicalize()?;
646
647        let expected = Path::new("foo::bar");
648        assert_eq!(canonicalized.as_path(), expected);
649        Ok(())
650    }
651
652    #[test]
653    fn test_canonicalize_path_preserve_necessary_quoting() -> Result<(), PathError> {
654        let path = Path::new("foo::\"bar::baz\"");
655        let canonicalized = path.canonicalize()?;
656
657        assert_eq!(canonicalized.as_path(), path);
658        Ok(())
659    }
660
661    #[test]
662    fn test_canonicalize_path_add_required_quoting_to_components_without_delimiter()
663    -> Result<(), PathError> {
664        let path = Path::new("foo::$bar");
665        let canonicalized = path.canonicalize()?;
666
667        let expected = Path::new("foo::\"$bar\"");
668        assert_eq!(canonicalized.as_path(), expected);
669        Ok(())
670    }
671}