miden_assembly_syntax/ast/path/
path.rs1use 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#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[repr(transparent)]
13pub struct Path {
14 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 fmt::Formatter) -> 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
130impl Path {
132 pub const MAX_COMPONENT_LENGTH: usize = (u16::MAX as usize) - 2;
138
139 pub const EMPTY: &Path = unsafe { &*("" as *const str as *const Path) };
141
142 pub const KERNEL_PATH: &str = "$kernel";
144 pub const ABSOLUTE_KERNEL_PATH: &str = "::$kernel";
145 pub const KERNEL: &Path =
146 unsafe { &*(Self::ABSOLUTE_KERNEL_PATH as *const str as *const Path) };
147
148 pub const EXEC_PATH: &str = "$exec";
150 pub const ABSOLUTE_EXEC_PATH: &str = "::$exec";
151 pub const EXEC: &Path = unsafe { &*(Self::ABSOLUTE_EXEC_PATH as *const str as *const Path) };
152
153 pub fn new<S: AsRef<str> + ?Sized>(path: &S) -> &Path {
154 unsafe { &*(path.as_ref() as *const str as *const Path) }
156 }
157
158 pub fn from_mut(path: &mut str) -> &mut Path {
159 unsafe { &mut *(path as *mut str as *mut Path) }
161 }
162
163 pub fn validate(path: &str) -> Result<&Path, PathError> {
165 match path {
166 "" | "\"\"" => return Err(PathError::Empty),
167 "::" => return Err(PathError::EmptyComponent),
168 _ => (),
169 }
170
171 if path.len() > u16::MAX as usize {
172 return Err(PathError::TooLong { max: u16::MAX as usize });
173 }
174
175 for result in Iter::new(path) {
176 result?;
177 }
178
179 Ok(Path::new(path))
180 }
181
182 pub const fn kernel_path() -> &'static Path {
184 Path::KERNEL
185 }
186
187 pub const fn exec_path() -> &'static Path {
189 Path::EXEC
190 }
191
192 #[inline]
193 pub const fn as_str(&self) -> &str {
194 &self.inner
195 }
196
197 #[inline]
198 pub fn as_mut_str(&mut self) -> &mut str {
199 &mut self.inner
200 }
201
202 pub fn as_ident(&self) -> Option<Ident> {
207 let mut components = self.components().filter_map(Result::ok);
208 match components.next()? {
209 component @ PathComponent::Normal(_) => {
210 if components.next().is_none() {
211 component.to_ident()
212 } else {
213 None
214 }
215 },
216 PathComponent::Root => None,
217 }
218 }
219
220 pub fn to_path_buf(&self) -> PathBuf {
222 PathBuf { inner: self.inner.to_string() }
223 }
224
225 pub fn from_ident(ident: &Ident) -> Cow<'_, Path> {
228 let ident = ident.as_str();
229 if Ident::requires_quoting(ident) {
230 let mut buf = PathBuf::with_capacity(ident.len() + 2);
231 buf.push_component(ident);
232 Cow::Owned(buf)
233 } else {
234 Cow::Borrowed(Path::new(ident))
235 }
236 }
237}
238
239impl Path {
241 pub fn is_empty(&self) -> bool {
243 matches!(&self.inner, "" | "::" | "\"\"")
244 }
245
246 pub fn len(&self) -> usize {
248 self.components().count()
249 }
250
251 pub fn char_len(&self) -> usize {
253 self.inner.chars().count()
254 }
255
256 #[inline]
258 pub fn byte_len(&self) -> usize {
259 self.inner.len()
260 }
261
262 pub fn is_absolute(&self) -> bool {
264 matches!(self.components().next(), Some(Ok(PathComponent::Root)))
265 }
266
267 pub fn to_absolute(&self) -> Result<Cow<'_, Path>, PathError> {
276 if self.is_absolute() {
277 for component in self.components() {
278 component?;
279 }
280 if self.byte_len() > u16::MAX as usize {
281 return Err(PathError::TooLong { max: u16::MAX as usize });
282 }
283 Ok(Cow::Borrowed(self))
284 } else {
285 let mut buf = PathBuf::with_capacity(self.byte_len() + 2);
286 buf.push_component("::");
287 buf.extend_with_components(self.components())?;
288 if buf.byte_len() > u16::MAX as usize {
289 return Err(PathError::TooLong { max: u16::MAX as usize });
290 }
291 Ok(Cow::Owned(buf))
292 }
293 }
294
295 pub fn to_relative(&self) -> &Path {
297 match self.inner.strip_prefix("::") {
298 Some(rest) => Path::new(rest),
299 None => self,
300 }
301 }
302
303 pub fn parent(&self) -> Option<&Path> {
309 let mut components = self.components();
310 match components.next_back()?.ok()? {
311 PathComponent::Root => None,
312 _ => Some(components.as_path()),
313 }
314 }
315
316 pub fn components(&self) -> Iter<'_> {
318 Iter::new(&self.inner)
319 }
320
321 pub fn first(&self) -> Option<&str> {
325 self.split_first().map(|(first, _)| first)
326 }
327
328 pub fn last(&self) -> Option<&str> {
332 self.split_last().map(|(last, _)| last)
333 }
334
335 pub fn procedure_name(&self) -> Result<Option<crate::ast::ProcedureName>, PathError> {
339 use crate::ast::ProcedureName;
340
341 let Some(last) = self.components().next_back() else {
342 return Ok(None);
343 };
344 let name = last?;
345 if name == PathComponent::Root {
346 return Ok(None);
347 }
348 if name.is_quoted() {
349 ProcedureName::new(format!("\"{}\"", name.as_str()))
350 .map(Some)
351 .map_err(PathError::InvalidComponent)
352 } else {
353 ProcedureName::new(name.as_str()).map(Some).map_err(PathError::InvalidComponent)
354 }
355 }
356
357 pub fn split_first(&self) -> Option<(&str, &Path)> {
362 let mut components = self.components();
363 match components.next()?.ok()? {
364 PathComponent::Root => {
365 let first = components.next().and_then(Result::ok).map(|c| c.as_str())?;
366 Some((first, components.as_path()))
367 },
368 first @ PathComponent::Normal(_) => Some((first.as_str(), components.as_path())),
369 }
370 }
371
372 pub fn split_last(&self) -> Option<(&str, &Path)> {
377 let mut components = self.components();
378 match components.next_back()?.ok()? {
379 PathComponent::Root => None,
380 last @ PathComponent::Normal(_) => Some((last.as_str(), components.as_path())),
381 }
382 }
383
384 pub fn is_kernel_path(&self) -> bool {
386 match self.inner.strip_prefix("::") {
387 Some(Self::KERNEL_PATH) => true,
388 Some(_) => false,
389 None => &self.inner == Self::KERNEL_PATH,
390 }
391 }
392
393 pub fn is_in_kernel(&self) -> bool {
395 if self.is_kernel_path() {
396 return true;
397 }
398
399 match self.split_last() {
400 Some((_, prefix)) => prefix.is_kernel_path(),
401 None => false,
402 }
403 }
404
405 pub fn is_exec_path(&self) -> bool {
407 match self.inner.strip_prefix("::") {
408 Some(Self::EXEC_PATH) => true,
409 Some(_) => false,
410 None => &self.inner == Self::EXEC_PATH,
411 }
412 }
413
414 pub fn is_in_exec(&self) -> bool {
416 if self.is_exec_path() {
417 return true;
418 }
419
420 match self.split_last() {
421 Some((_, prefix)) => prefix.is_exec_path(),
422 None => false,
423 }
424 }
425
426 #[inline]
434 pub fn starts_with<Prefix>(&self, prefix: &Prefix) -> bool
435 where
436 Prefix: ?Sized,
437 Self: StartsWith<Prefix>,
438 {
439 <Self as StartsWith<Prefix>>::starts_with(self, prefix)
440 }
441
442 #[inline]
450 pub fn starts_with_exactly<Prefix>(&self, prefix: &Prefix) -> bool
451 where
452 Prefix: ?Sized,
453 Self: StartsWith<Prefix>,
454 {
455 <Self as StartsWith<Prefix>>::starts_with_exactly(self, prefix)
456 }
457
458 pub fn strip_prefix<'a>(&'a self, prefix: &Self) -> Option<&'a Self> {
464 let mut components = self.components();
465 for prefix_component in prefix.components() {
466 let prefix_component = prefix_component.expect("invalid prefix path");
475 match (components.next(), prefix_component) {
476 (Some(Ok(PathComponent::Root)), PathComponent::Root) => (),
477 (Some(Ok(c @ PathComponent::Normal(_))), pc @ PathComponent::Normal(_)) => {
478 if c.as_str() != pc.as_str() {
479 return None;
480 }
481 },
482 (Some(Ok(_) | Err(_)) | None, _) => return None,
483 }
484 }
485 Some(components.as_path())
486 }
487
488 #[inline]
501 pub fn join<P>(&self, other: &P) -> PathBuf
502 where
503 P: ?Sized,
504 Path: Join<P>,
505 {
506 <Path as Join<P>>::join(self, other)
507 }
508
509 pub fn canonicalize(&self) -> Result<PathBuf, PathError> {
518 let mut buf = PathBuf::with_capacity(self.byte_len());
519 buf.extend_with_components(self.components())?;
520 if buf.byte_len() > u16::MAX as usize {
521 return Err(PathError::TooLong { max: u16::MAX as usize });
522 }
523 Ok(buf)
524 }
525}
526
527impl StartsWith<str> for Path {
528 fn starts_with(&self, prefix: &str) -> bool {
529 let this = self.to_relative();
530 <Path as StartsWith<str>>::starts_with_exactly(this, prefix)
531 }
532
533 #[inline]
534 fn starts_with_exactly(&self, prefix: &str) -> bool {
535 match prefix {
536 "" => true,
537 "::" => self.is_absolute(),
538 prefix => {
539 let mut components = self.components();
540 let prefix = if let Some(prefix) = prefix.strip_prefix("::") {
541 let is_absolute =
542 components.next().is_some_and(|c| matches!(c, Ok(PathComponent::Root)));
543 if !is_absolute {
544 return false;
545 }
546 prefix
547 } else {
548 prefix
549 };
550 components.next().is_some_and(
551 |c| matches!(c, Ok(c @ PathComponent::Normal(_)) if c.as_str() == prefix),
552 )
553 },
554 }
555 }
556}
557
558impl StartsWith<Path> for Path {
559 fn starts_with(&self, prefix: &Path) -> bool {
560 let this = self.to_relative();
561 let prefix = prefix.to_relative();
562 <Path as StartsWith<Path>>::starts_with_exactly(this, prefix)
563 }
564
565 #[inline]
566 fn starts_with_exactly(&self, prefix: &Path) -> bool {
567 let mut components = self.components();
568 for prefix_component in prefix.components() {
569 let prefix_component = prefix_component.expect("invalid prefix path");
578 match (components.next(), prefix_component) {
579 (Some(Ok(PathComponent::Root)), PathComponent::Root) => {},
580 (Some(Ok(c @ PathComponent::Normal(_))), pc @ PathComponent::Normal(_)) => {
581 if c.as_str() != pc.as_str() {
582 return false;
583 }
584 },
585 (Some(Ok(_) | Err(_)) | None, _) => return false,
586 }
587 }
588 true
589 }
590}
591
592impl PartialEq<str> for Path {
593 fn eq(&self, other: &str) -> bool {
594 &self.inner == other
595 }
596}
597
598impl PartialEq<PathBuf> for Path {
599 fn eq(&self, other: &PathBuf) -> bool {
600 &self.inner == other.inner.as_str()
601 }
602}
603
604impl PartialEq<&PathBuf> for Path {
605 fn eq(&self, other: &&PathBuf) -> bool {
606 &self.inner == other.inner.as_str()
607 }
608}
609
610impl PartialEq<Path> for PathBuf {
611 fn eq(&self, other: &Path) -> bool {
612 self.inner.as_str() == &other.inner
613 }
614}
615
616impl PartialEq<&Path> for Path {
617 fn eq(&self, other: &&Path) -> bool {
618 self.inner == other.inner
619 }
620}
621
622impl PartialEq<alloc::boxed::Box<Path>> for Path {
623 fn eq(&self, other: &alloc::boxed::Box<Path>) -> bool {
624 self.inner == other.inner
625 }
626}
627
628impl PartialEq<alloc::rc::Rc<Path>> for Path {
629 fn eq(&self, other: &alloc::rc::Rc<Path>) -> bool {
630 self.inner == other.inner
631 }
632}
633
634impl PartialEq<alloc::sync::Arc<Path>> for Path {
635 fn eq(&self, other: &alloc::sync::Arc<Path>) -> bool {
636 self.inner == other.inner
637 }
638}
639
640impl PartialEq<Cow<'_, Path>> for Path {
641 fn eq(&self, other: &Cow<'_, Path>) -> bool {
642 self.inner == other.as_ref().inner
643 }
644}
645
646impl fmt::Display for Path {
647 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
648 f.write_str(&self.inner)
649 }
650}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655
656 #[test]
657 fn test_path_split_with_quotes_and_large_components() -> Result<(), PathError> {
658 let path = Path::new(
659 "::\"miden:counter-contract/counter-contract@0.1.0\"::counter_contract::_RNvXsg_NtNtCseaniv3neBPi_10miden_base5types7storageINtB5_10StorageMapNtNtCscxjsWiPtnzN_11miden_field4word4WordNtNtB19_10wasm_miden4FeltEINtNtCs3NSMmx5ugmE_4core7convert4FromNtNtNtCs52sTGLXFK3W_14miden_base_sys8bindings5types13StorageSlotIdE4fromCsanZ5gV8dMQ0_16counter_contract",
660 );
661 let canonicalized = path.canonicalize()?;
662
663 assert_eq!(canonicalized.as_path(), path);
664 Ok(())
665 }
666
667 #[test]
668 fn test_canonicalize_path_identity() -> Result<(), PathError> {
669 let path = Path::new("foo::bar");
670 let canonicalized = path.canonicalize()?;
671
672 assert_eq!(canonicalized.as_path(), path);
673 Ok(())
674 }
675
676 #[test]
677 fn test_canonicalize_path_kernel_is_absolute() -> Result<(), PathError> {
678 let path = Path::new("$kernel::bar");
679 let canonicalized = path.canonicalize()?;
680
681 let expected = Path::new("::$kernel::bar");
682 assert_eq!(canonicalized.as_path(), expected);
683 Ok(())
684 }
685
686 #[test]
687 fn test_canonicalize_path_exec_is_absolute() -> Result<(), PathError> {
688 let path = Path::new("$exec::$main");
689 let canonicalized = path.canonicalize()?;
690
691 let expected = Path::new("::$exec::$main");
692 assert_eq!(canonicalized.as_path(), expected);
693 Ok(())
694 }
695
696 #[test]
697 fn test_to_absolute_rejects_invalid_absolute_path_component() {
698 let source = alloc::format!("::{}", "a".repeat(Path::MAX_COMPONENT_LENGTH + 1));
699 let err = Path::new(&source)
700 .to_absolute()
701 .expect_err("absolute paths must still validate their components");
702
703 assert!(matches!(
704 err,
705 PathError::InvalidComponent(crate::ast::IdentError::InvalidLength { .. })
706 ));
707 }
708
709 #[test]
710 fn test_to_absolute_rejects_oversized_absolute_result() {
711 let source = alloc::format!("{}aa", "a::".repeat(21_844));
712 assert_eq!(source.len(), (u16::MAX as usize) - 1);
713
714 let err = Path::new(&source)
715 .to_absolute()
716 .expect_err("adding the absolute path prefix must preserve the path length bound");
717
718 assert!(matches!(err, PathError::TooLong { max } if max == u16::MAX as usize));
719 }
720
721 #[test]
722 fn test_canonicalize_path_remove_unnecessary_quoting() -> Result<(), PathError> {
723 let path = Path::new("foo::\"bar\"");
724 let canonicalized = path.canonicalize()?;
725
726 let expected = Path::new("foo::bar");
727 assert_eq!(canonicalized.as_path(), expected);
728 Ok(())
729 }
730
731 #[test]
732 fn test_canonicalize_path_preserve_necessary_quoting() -> Result<(), PathError> {
733 let path = Path::new("foo::\"bar::baz\"");
734 let canonicalized = path.canonicalize()?;
735
736 assert_eq!(canonicalized.as_path(), path);
737 Ok(())
738 }
739
740 #[test]
741 fn test_canonicalize_path_add_required_quoting_to_components_without_delimiter()
742 -> Result<(), PathError> {
743 let path = Path::new("foo::$bar");
744 let canonicalized = path.canonicalize()?;
745
746 let expected = Path::new("foo::\"$bar\"");
747 assert_eq!(canonicalized.as_path(), expected);
748 Ok(())
749 }
750
751 #[test]
752 fn test_canonicalize_path_rejects_canonical_result_longer_than_u16_max() {
753 let component = alloc::format!("{}-", "a".repeat(254));
754 let mut source = alloc::string::String::new();
755 for i in 0..255 {
756 if i > 0 {
757 source.push_str("::");
758 }
759 source.push_str(&component);
760 }
761
762 let path = Path::validate(&source).expect("source path must be pre-canonicalization valid");
763 let err = path.canonicalize().expect_err(
764 "canonicalization must reject paths that exceed the serialization length bound",
765 );
766
767 assert!(matches!(err, PathError::TooLong { max } if max == u16::MAX as usize));
768 }
769}