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        for result in Iter::new(path) {
168            result?;
169        }
170
171        Ok(Path::new(path))
172    }
173
174    /// Get a [Path] corresponding to [Self::KERNEL_PATH]
175    pub const fn kernel_path() -> &'static Path {
176        Path::KERNEL
177    }
178
179    /// Get a [Path] corresponding to [Self::EXEC_PATH]
180    pub const fn exec_path() -> &'static Path {
181        Path::EXEC
182    }
183
184    #[inline]
185    pub const fn as_str(&self) -> &str {
186        &self.inner
187    }
188
189    #[inline]
190    pub fn as_mut_str(&mut self) -> &mut str {
191        &mut self.inner
192    }
193
194    /// Get an [Ident] that is equivalent to this [Path], so long as the path has only a single
195    /// component.
196    ///
197    /// Returns `None` if the path cannot be losslessly represented as a single component.
198    pub fn as_ident(&self) -> Option<Ident> {
199        let mut components = self.components().filter_map(|c| c.ok());
200        match components.next()? {
201            component @ PathComponent::Normal(_) => {
202                if components.next().is_none() {
203                    component.to_ident()
204                } else {
205                    None
206                }
207            },
208            PathComponent::Root => None,
209        }
210    }
211
212    /// Convert this [Path] to an owned [PathBuf]
213    pub fn to_path_buf(&self) -> PathBuf {
214        PathBuf { inner: self.inner.to_string() }
215    }
216}
217
218/// Accesssors
219impl Path {
220    /// Returns true if this path is empty (i.e. has no components)
221    pub fn is_empty(&self) -> bool {
222        matches!(&self.inner, "" | "::" | "\"\"")
223    }
224
225    /// Returns the number of components in the path
226    pub fn len(&self) -> usize {
227        self.components().count()
228    }
229
230    /// Return the size of the path in [char]s when displayed as a string
231    pub fn char_len(&self) -> usize {
232        self.inner.chars().count()
233    }
234
235    /// Return the size of the path in bytes when displayed as a string
236    #[inline]
237    pub fn byte_len(&self) -> usize {
238        self.inner.len()
239    }
240
241    /// Returns true if this path is an absolute path
242    pub fn is_absolute(&self) -> bool {
243        matches!(self.components().next(), Some(Ok(PathComponent::Root)))
244    }
245
246    /// Make this path absolute, if not already
247    ///
248    /// NOTE: This does not _resolve_ the path, it simply ensures the path has the root prefix
249    pub fn to_absolute(&self) -> Cow<'_, Path> {
250        if self.is_absolute() {
251            Cow::Borrowed(self)
252        } else {
253            let mut buf = PathBuf::with_capacity(self.byte_len() + 2);
254            buf.push_component("::");
255            buf.extend_with_components(self.components()).expect("invalid path");
256            Cow::Owned(buf)
257        }
258    }
259
260    /// Strip the root prefix from this path, if it has one.
261    pub fn to_relative(&self) -> &Path {
262        match self.inner.strip_prefix("::") {
263            Some(rest) => Path::new(rest),
264            None => self,
265        }
266    }
267
268    /// Returns the [Path] without its final component, if there is one.
269    ///
270    /// This means it may return an empty [Path] for relative paths with a single component.
271    ///
272    /// Returns `None` if the path terminates with the root prefix, or if it is empty.
273    pub fn parent(&self) -> Option<&Path> {
274        let mut components = self.components();
275        match components.next_back()?.ok()? {
276            PathComponent::Root => None,
277            _ => Some(components.as_path()),
278        }
279    }
280
281    /// Returns an iterator over all components of the path.
282    pub fn components(&self) -> Iter<'_> {
283        Iter::new(&self.inner)
284    }
285
286    /// Get the first non-root component of this path as a `str`
287    ///
288    /// Returns `None` if the path is empty, or consists only of the root prefix.
289    pub fn first(&self) -> Option<&str> {
290        self.split_first().map(|(first, _)| first)
291    }
292
293    /// Get the first non-root component of this path as a `str`
294    ///
295    /// Returns `None` if the path is empty, or consists only of the root prefix.
296    pub fn last(&self) -> Option<&str> {
297        self.split_last().map(|(last, _)| last)
298    }
299
300    /// Splits this path on the first non-root component, returning it and a new [Path] of the
301    /// remaining components.
302    ///
303    /// Returns `None` if there are no components to split
304    pub fn split_first(&self) -> Option<(&str, &Path)> {
305        let mut components = self.components();
306        match components.next()?.ok()? {
307            PathComponent::Root => {
308                let first = components.next().and_then(|c| c.ok()).map(|c| c.as_str())?;
309                Some((first, components.as_path()))
310            },
311            first @ PathComponent::Normal(_) => Some((first.as_str(), components.as_path())),
312        }
313    }
314
315    /// Splits this path on the last component, returning it and a new [Path] of the remaining
316    /// components.
317    ///
318    /// Returns `None` if there are no components to split
319    pub fn split_last(&self) -> Option<(&str, &Path)> {
320        let mut components = self.components();
321        match components.next_back()?.ok()? {
322            PathComponent::Root => None,
323            last @ PathComponent::Normal(_) => Some((last.as_str(), components.as_path())),
324        }
325    }
326
327    /// Returns true if this path is for the root kernel module.
328    pub fn is_kernel_path(&self) -> bool {
329        match self.inner.strip_prefix("::") {
330            Some(Self::KERNEL_PATH) => true,
331            Some(_) => false,
332            None => &self.inner == Self::KERNEL_PATH,
333        }
334    }
335
336    /// Returns true if this path is for the root kernel module or an item in it
337    pub fn is_in_kernel(&self) -> bool {
338        if self.is_kernel_path() {
339            return true;
340        }
341
342        match self.split_last() {
343            Some((_, prefix)) => prefix.is_kernel_path(),
344            None => false,
345        }
346    }
347
348    /// Returns true if this path is for an executable module.
349    pub fn is_exec_path(&self) -> bool {
350        match self.inner.strip_prefix("::") {
351            Some(Self::EXEC_PATH) => true,
352            Some(_) => false,
353            None => &self.inner == Self::EXEC_PATH,
354        }
355    }
356
357    /// Returns true if this path is for the executable module or an item in it
358    pub fn is_in_exec(&self) -> bool {
359        if self.is_exec_path() {
360            return true;
361        }
362
363        match self.split_last() {
364            Some((_, prefix)) => prefix.is_exec_path(),
365            None => false,
366        }
367    }
368
369    /// Returns true if the current path, sans root component, starts with `prefix`
370    #[inline]
371    pub fn starts_with<Prefix>(&self, prefix: &Prefix) -> bool
372    where
373        Prefix: ?Sized,
374        Self: StartsWith<Prefix>,
375    {
376        <Self as StartsWith<Prefix>>::starts_with(self, prefix)
377    }
378
379    /// Returns true if the current path, including root component, starts with `prefix`
380    #[inline]
381    pub fn starts_with_exactly<Prefix>(&self, prefix: &Prefix) -> bool
382    where
383        Prefix: ?Sized,
384        Self: StartsWith<Prefix>,
385    {
386        <Self as StartsWith<Prefix>>::starts_with_exactly(self, prefix)
387    }
388
389    /// Create an owned [PathBuf] with `path` adjoined to `self`.
390    ///
391    /// If `path` is absolute, it replaces the current path.
392    ///
393    /// The semantics of how `other` is joined to `self` in the resulting path depends on the
394    /// implementation of [Join] used. The implementation for [Path] and [PathBuf] joins all
395    /// components of `other` to self`; while the implementation for [prim@str], string-like values,
396    /// and identifiers/symbols joins just a single component. You must be careful to ensure that
397    /// if you are passing a string here, that you specifically want to join it as a single
398    /// component, or the resulting path may be different than you expect. It is recommended that
399    /// you use `Path::new(&string)` if you want to be explicit about treating a string-like value
400    /// as a multi-component path.
401    #[inline]
402    pub fn join<P>(&self, other: &P) -> PathBuf
403    where
404        P: ?Sized,
405        Path: Join<P>,
406    {
407        <Path as Join<P>>::join(self, other)
408    }
409
410    /// Canonicalize this path by ensuring that all components are in canonical form.
411    ///
412    /// Canonical form dictates that:
413    ///
414    /// * A component is quoted only if it requires quoting, and unquoted otherwise
415    /// * Is made absolute if relative and the first component is $kernel or $exec
416    ///
417    /// Returns `Err` if the path is invalid
418    pub fn canonicalize(&self) -> Result<PathBuf, PathError> {
419        let mut buf = PathBuf::with_capacity(self.byte_len());
420        buf.extend_with_components(self.components())?;
421        Ok(buf)
422    }
423}
424
425impl StartsWith<str> for Path {
426    fn starts_with(&self, prefix: &str) -> bool {
427        if prefix.is_empty() {
428            return true;
429        }
430        if prefix.starts_with("::") {
431            self.inner.starts_with(prefix)
432        } else {
433            match self.inner.strip_prefix("::") {
434                Some(rest) => rest.starts_with(prefix),
435                None => self.inner.starts_with(prefix),
436            }
437        }
438    }
439
440    #[inline]
441    fn starts_with_exactly(&self, prefix: &str) -> bool {
442        self.inner.starts_with(prefix)
443    }
444}
445
446impl StartsWith<Path> for Path {
447    fn starts_with(&self, prefix: &Path) -> bool {
448        <Self as StartsWith<str>>::starts_with(self, prefix.as_str())
449    }
450
451    #[inline]
452    fn starts_with_exactly(&self, prefix: &Path) -> bool {
453        <Self as StartsWith<str>>::starts_with_exactly(self, prefix.as_str())
454    }
455}
456
457impl PartialEq<str> for Path {
458    fn eq(&self, other: &str) -> bool {
459        &self.inner == other
460    }
461}
462
463impl PartialEq<PathBuf> for Path {
464    fn eq(&self, other: &PathBuf) -> bool {
465        &self.inner == other.inner.as_str()
466    }
467}
468
469impl PartialEq<&PathBuf> for Path {
470    fn eq(&self, other: &&PathBuf) -> bool {
471        &self.inner == other.inner.as_str()
472    }
473}
474
475impl PartialEq<Path> for PathBuf {
476    fn eq(&self, other: &Path) -> bool {
477        self.inner.as_str() == &other.inner
478    }
479}
480
481impl PartialEq<&Path> for Path {
482    fn eq(&self, other: &&Path) -> bool {
483        self.inner == other.inner
484    }
485}
486
487impl PartialEq<alloc::boxed::Box<Path>> for Path {
488    fn eq(&self, other: &alloc::boxed::Box<Path>) -> bool {
489        self.inner == other.inner
490    }
491}
492
493impl PartialEq<alloc::rc::Rc<Path>> for Path {
494    fn eq(&self, other: &alloc::rc::Rc<Path>) -> bool {
495        self.inner == other.inner
496    }
497}
498
499impl PartialEq<alloc::sync::Arc<Path>> for Path {
500    fn eq(&self, other: &alloc::sync::Arc<Path>) -> bool {
501        self.inner == other.inner
502    }
503}
504
505impl PartialEq<alloc::borrow::Cow<'_, Path>> for Path {
506    fn eq(&self, other: &alloc::borrow::Cow<'_, Path>) -> bool {
507        self.inner == other.as_ref().inner
508    }
509}
510
511impl fmt::Display for Path {
512    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
513        f.write_str(&self.inner)
514    }
515}
516
517#[cfg(test)]
518mod tests {
519    use super::*;
520
521    #[test]
522    fn test_canonicalize_path_identity() -> Result<(), PathError> {
523        let path = Path::new("foo::bar");
524        let canonicalized = path.canonicalize()?;
525
526        assert_eq!(canonicalized.as_path(), path);
527        Ok(())
528    }
529
530    #[test]
531    fn test_canonicalize_path_kernel_is_absolute() -> Result<(), PathError> {
532        let path = Path::new("$kernel::bar");
533        let canonicalized = path.canonicalize()?;
534
535        let expected = Path::new("::$kernel::bar");
536        assert_eq!(canonicalized.as_path(), expected);
537        Ok(())
538    }
539
540    #[test]
541    fn test_canonicalize_path_exec_is_absolute() -> Result<(), PathError> {
542        let path = Path::new("$exec::$main");
543        let canonicalized = path.canonicalize()?;
544
545        let expected = Path::new("::$exec::$main");
546        assert_eq!(canonicalized.as_path(), expected);
547        Ok(())
548    }
549
550    #[test]
551    fn test_canonicalize_path_remove_unnecessary_quoting() -> Result<(), PathError> {
552        let path = Path::new("foo::\"bar\"");
553        let canonicalized = path.canonicalize()?;
554
555        let expected = Path::new("foo::bar");
556        assert_eq!(canonicalized.as_path(), expected);
557        Ok(())
558    }
559
560    #[test]
561    fn test_canonicalize_path_preserve_necessary_quoting() -> Result<(), PathError> {
562        let path = Path::new("foo::\"bar::baz\"");
563        let canonicalized = path.canonicalize()?;
564
565        assert_eq!(canonicalized.as_path(), path);
566        Ok(())
567    }
568
569    #[test]
570    fn test_canonicalize_path_add_required_quoting_to_components_without_delimiter()
571    -> Result<(), PathError> {
572        let path = Path::new("foo::$bar");
573        let canonicalized = path.canonicalize()?;
574
575        let expected = Path::new("foo::\"$bar\"");
576        assert_eq!(canonicalized.as_path(), expected);
577        Ok(())
578    }
579}