miden_assembly_syntax/library/
path.rs1use alloc::{
2 borrow::Cow,
3 string::{String, ToString},
4 sync::Arc,
5 vec::Vec,
6};
7use core::{
8 fmt,
9 str::{self, FromStr},
10};
11
12use miden_core::utils::{
13 ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
14};
15use miden_debug_types::Span;
16#[cfg(feature = "serde")]
17use serde::{Deserialize, Serialize};
18use smallvec::smallvec;
19
20use crate::{
21 LibraryNamespace,
22 ast::{Ident, IdentError},
23};
24
25#[derive(Debug, thiserror::Error)]
27pub enum PathError {
28 #[error("invalid library path: cannot be empty")]
29 Empty,
30 #[error("invalid library path component: cannot be empty")]
31 EmptyComponent,
32 #[error("invalid library path component: {0}")]
33 InvalidComponent(crate::ast::IdentError),
34 #[error("invalid library path: contains invalid utf8 byte sequences")]
35 InvalidUtf8,
36 #[error(transparent)]
37 InvalidNamespace(crate::library::LibraryNamespaceError),
38 #[error("cannot join a path with reserved name to other paths")]
39 UnsupportedJoin,
40}
41
42pub enum LibraryPathComponent<'a> {
47 Namespace(&'a LibraryNamespace),
49 Normal(&'a Ident),
51}
52
53impl<'a> LibraryPathComponent<'a> {
54 #[inline(always)]
56 pub fn as_str(&self) -> &'a str {
57 match self {
58 Self::Namespace(ns) => ns.as_str(),
59 Self::Normal(id) => id.as_str(),
60 }
61 }
62
63 #[inline]
65 pub fn to_ident(&self) -> Ident {
66 match self {
67 Self::Namespace(ns) => ns.to_ident(),
68 Self::Normal(id) => Ident::clone(id),
69 }
70 }
71}
72
73impl Eq for LibraryPathComponent<'_> {}
74
75impl PartialEq for LibraryPathComponent<'_> {
76 fn eq(&self, other: &Self) -> bool {
77 match (self, other) {
78 (Self::Namespace(a), Self::Namespace(b)) => a == b,
79 (Self::Normal(a), Self::Normal(b)) => a == b,
80 _ => false,
81 }
82 }
83}
84
85impl PartialEq<str> for LibraryPathComponent<'_> {
86 fn eq(&self, other: &str) -> bool {
87 self.as_ref().eq(other)
88 }
89}
90
91impl AsRef<str> for LibraryPathComponent<'_> {
92 fn as_ref(&self) -> &str {
93 match self {
94 Self::Namespace(ns) => ns.as_str(),
95 Self::Normal(ident) => ident.as_str(),
96 }
97 }
98}
99
100impl fmt::Display for LibraryPathComponent<'_> {
101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102 f.write_str(self.as_ref())
103 }
104}
105
106impl From<LibraryPathComponent<'_>> for Ident {
107 #[inline]
108 fn from(component: LibraryPathComponent<'_>) -> Self {
109 component.to_ident()
110 }
111}
112
113type Components = smallvec::SmallVec<[Ident; 1]>;
115
116#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
121pub struct LibraryPath {
122 inner: Arc<LibraryPathInner>,
123}
124
125#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
128#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
129struct LibraryPathInner {
130 ns: LibraryNamespace,
132 components: Components,
134}
135
136impl LibraryPath {
137 pub fn new(source: impl AsRef<str>) -> Result<Self, PathError> {
153 let source = source.as_ref();
154 if source.is_empty() {
155 return Err(PathError::Empty);
156 }
157
158 let mut parts = source.split("::");
160 let ns = parts
161 .next()
162 .ok_or(PathError::Empty)
163 .and_then(|part| LibraryNamespace::new(part).map_err(PathError::InvalidNamespace))?;
164
165 let mut components = Components::default();
167 parts.map(Ident::new).try_for_each(|part| {
168 part.map_err(PathError::InvalidComponent).map(|c| components.push(c))
169 })?;
170
171 Ok(Self::make(ns, components))
172 }
173
174 pub fn new_from_components<I>(ns: LibraryNamespace, components: I) -> Self
176 where
177 I: IntoIterator<Item = Ident>,
178 {
179 Self::make(ns, components.into_iter().collect())
180 }
181
182 #[inline]
183 fn make(ns: LibraryNamespace, components: Components) -> Self {
184 Self {
185 inner: Arc::new(LibraryPathInner { ns, components }),
186 }
187 }
188}
189
190impl LibraryPath {
192 #[allow(clippy::len_without_is_empty)]
194 pub fn len(&self) -> usize {
195 self.inner.components.iter().map(|c| c.len()).sum::<usize>()
196 + self.inner.ns.as_str().len()
197 + (self.inner.components.len() * 2)
198 }
199
200 pub fn byte_len(&self) -> usize {
202 self.inner.components.iter().map(|c| c.len()).sum::<usize>()
203 + self.inner.ns.as_str().len()
204 + (self.inner.components.len() * 2)
205 }
206
207 pub fn path(&self) -> Cow<'_, str> {
209 if self.inner.components.is_empty() {
210 Cow::Borrowed(self.inner.ns.as_str())
211 } else {
212 Cow::Owned(self.to_string())
213 }
214 }
215
216 pub fn namespace(&self) -> &LibraryNamespace {
218 &self.inner.ns
219 }
220
221 pub fn last(&self) -> &str {
223 self.last_component().as_str()
224 }
225
226 pub fn last_component(&self) -> LibraryPathComponent<'_> {
228 self.inner
229 .components
230 .last()
231 .map(LibraryPathComponent::Normal)
232 .unwrap_or_else(|| LibraryPathComponent::Namespace(&self.inner.ns))
233 }
234
235 pub fn num_components(&self) -> usize {
239 self.inner.components.len() + 1
240 }
241
242 pub fn components(&self) -> impl Iterator<Item = LibraryPathComponent<'_>> + '_ {
244 core::iter::once(LibraryPathComponent::Namespace(&self.inner.ns))
245 .chain(self.inner.components.iter().map(LibraryPathComponent::Normal))
246 }
247
248 pub fn is_kernel_path(&self) -> bool {
250 matches!(self.inner.ns, LibraryNamespace::Kernel)
251 }
252
253 pub fn is_exec_path(&self) -> bool {
255 matches!(self.inner.ns, LibraryNamespace::Exec)
256 }
257
258 pub fn is_anon_path(&self) -> bool {
260 matches!(self.inner.ns, LibraryNamespace::Anon)
261 }
262
263 pub fn starts_with(&self, other: &LibraryPath) -> bool {
265 let mut a = self.components();
266 let mut b = other.components();
267 loop {
268 match (a.next(), b.next()) {
269 (_, None) => break true,
271 (None, _) => break false,
273 (Some(a), Some(b)) => {
274 if a != b {
276 break false;
277 }
278 },
279 }
280 }
281 }
282}
283
284impl LibraryPath {
286 pub fn set_namespace(&mut self, ns: LibraryNamespace) {
288 let inner = Arc::make_mut(&mut self.inner);
289 inner.ns = ns;
290 }
291
292 pub fn join(&self, other: &Self) -> Result<Self, PathError> {
299 if other.inner.ns.is_reserved() {
300 return Err(PathError::UnsupportedJoin);
301 }
302
303 let mut path = self.clone();
304 {
305 let inner = Arc::make_mut(&mut path.inner);
306 inner.components.push(other.inner.ns.to_ident());
307 inner.components.extend(other.inner.components.iter().cloned());
308 }
309
310 Ok(path)
311 }
312
313 pub fn push(&mut self, component: impl AsRef<str>) -> Result<(), PathError> {
317 let component = component.as_ref().parse::<Ident>().map_err(PathError::InvalidComponent)?;
318 self.push_ident(component);
319 Ok(())
320 }
321
322 pub fn push_ident(&mut self, component: Ident) {
324 let inner = Arc::make_mut(&mut self.inner);
325 inner.components.push(component);
326 }
327
328 pub fn append<S>(&self, component: S) -> Result<Self, PathError>
332 where
333 S: AsRef<str>,
334 {
335 let mut path = self.clone();
336 path.push(component)?;
337 Ok(path)
338 }
339
340 pub fn append_ident(&self, component: Ident) -> Result<Self, PathError> {
344 let mut path = self.clone();
345 path.push_ident(component);
346 Ok(path)
347 }
348
349 pub fn prepend<S>(&self, component: S) -> Result<Self, PathError>
358 where
359 S: AsRef<str>,
360 {
361 let ns = component
362 .as_ref()
363 .parse::<LibraryNamespace>()
364 .map_err(PathError::InvalidNamespace)?;
365 let component = self.inner.ns.to_ident();
366 let mut components = smallvec![component];
367 components.extend(self.inner.components.iter().cloned());
368 Ok(Self::make(ns, components))
369 }
370
371 pub fn pop(&mut self) -> Option<Ident> {
373 let inner = Arc::make_mut(&mut self.inner);
374 inner.components.pop()
375 }
376
377 pub fn strip_last(&self) -> Option<Self> {
380 match self.inner.components.len() {
381 0 => None,
382 1 => Some(Self::make(self.inner.ns.clone(), smallvec![])),
383 _ => {
384 let ns = self.inner.ns.clone();
385 let mut components = self.inner.components.clone();
386 components.pop();
387 Some(Self::make(ns, components))
388 },
389 }
390 }
391
392 pub fn validate<S>(source: S) -> Result<usize, PathError>
397 where
398 S: AsRef<str>,
399 {
400 let source = source.as_ref();
401
402 let mut count = 0;
403 let mut components = source.split("::");
404
405 let ns = components.next().ok_or(PathError::Empty)?;
406 LibraryNamespace::validate(ns).map_err(PathError::InvalidNamespace)?;
407 count += 1;
408
409 for component in components {
410 validate_component(component)?;
411 count += 1;
412 }
413
414 Ok(count)
415 }
416
417 pub fn append_unchecked<S>(&self, component: S) -> Self
421 where
422 S: AsRef<str>,
423 {
424 let component = component.as_ref().to_string().into_boxed_str();
425 let component = Ident::from_raw_parts(Span::unknown(Arc::from(component)));
426 let mut path = self.clone();
427 path.push_ident(component);
428 path
429 }
430}
431
432impl<'a> TryFrom<Vec<LibraryPathComponent<'a>>> for LibraryPath {
433 type Error = PathError;
434 fn try_from(iter: Vec<LibraryPathComponent<'a>>) -> Result<Self, Self::Error> {
435 let mut iter = iter.into_iter();
436 let ns = match iter.next() {
437 None => return Err(PathError::Empty),
438 Some(LibraryPathComponent::Namespace(ns)) => ns.clone(),
439 Some(LibraryPathComponent::Normal(ident)) => {
440 LibraryNamespace::try_from(ident.clone()).map_err(PathError::InvalidNamespace)?
441 },
442 };
443 let mut components = Components::default();
444 for component in iter {
445 match component {
446 LibraryPathComponent::Normal(ident) => components.push(ident.clone()),
447 LibraryPathComponent::Namespace(LibraryNamespace::User(name)) => {
448 components.push(Ident::from_raw_parts(Span::unknown(name.clone())));
449 },
450 LibraryPathComponent::Namespace(_) => return Err(PathError::UnsupportedJoin),
451 }
452 }
453 Ok(Self::make(ns, components))
454 }
455}
456
457impl From<LibraryNamespace> for LibraryPath {
458 fn from(ns: LibraryNamespace) -> Self {
459 Self::make(ns, smallvec![])
460 }
461}
462
463impl From<LibraryPath> for String {
464 fn from(path: LibraryPath) -> Self {
465 path.to_string()
466 }
467}
468
469impl TryFrom<String> for LibraryPath {
470 type Error = PathError;
471
472 #[inline]
473 fn try_from(value: String) -> Result<Self, Self::Error> {
474 Self::new(value)
475 }
476}
477
478impl<'a> TryFrom<&'a str> for LibraryPath {
479 type Error = PathError;
480
481 #[inline]
482 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
483 Self::new(value)
484 }
485}
486
487impl FromStr for LibraryPath {
488 type Err = PathError;
489
490 #[inline]
491 fn from_str(value: &str) -> Result<Self, Self::Err> {
492 Self::new(value)
493 }
494}
495
496impl Serializable for LibraryPath {
497 fn write_into<W: ByteWriter>(&self, target: &mut W) {
498 let len = self.byte_len();
499
500 target.write_u16(len as u16);
501 target.write_bytes(self.inner.ns.as_str().as_bytes());
502 for component in self.inner.components.iter() {
503 target.write_bytes(b"::");
504 target.write_bytes(component.as_str().as_bytes());
505 }
506 }
507}
508
509impl Deserializable for LibraryPath {
510 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
511 let len = source.read_u16()? as usize;
512 let path = source.read_slice(len)?;
513 let path =
514 str::from_utf8(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
515 Self::new(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))
516 }
517}
518
519#[cfg(feature = "serde")]
520impl serde::Serialize for LibraryPath {
521 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
522 where
523 S: serde::Serializer,
524 {
525 if serializer.is_human_readable() {
526 let name = format!("{}", self);
527 serializer.serialize_str(&name)
528 } else {
529 self.inner.serialize(serializer)
530 }
531 }
532}
533
534#[cfg(feature = "serde")]
535impl<'de> serde::Deserialize<'de> for LibraryPath {
536 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
537 where
538 D: serde::Deserializer<'de>,
539 {
540 if deserializer.is_human_readable() {
541 let name = <&'de str as serde::Deserialize>::deserialize(deserializer)?;
542 Self::new(name).map_err(serde::de::Error::custom)
543 } else {
544 let inner = <Arc<LibraryPathInner> as serde::Deserialize>::deserialize(deserializer)?;
545 Ok(Self { inner })
546 }
547 }
548}
549
550impl fmt::Display for LibraryPath {
551 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
552 write!(f, "{}", self.inner.ns)?;
553 for component in self.inner.components.iter() {
554 write!(f, "::{component}")?;
555 }
556 Ok(())
557 }
558}
559
560fn validate_component(component: &str) -> Result<(), PathError> {
561 if component.is_empty() {
562 Err(PathError::EmptyComponent)
563 } else if component.len() > LibraryNamespace::MAX_LENGTH {
564 Err(PathError::InvalidComponent(IdentError::InvalidLength {
565 max: LibraryNamespace::MAX_LENGTH,
566 }))
567 } else {
568 Ident::validate(component).map_err(PathError::InvalidComponent)
569 }
570}
571
572#[cfg(any(test, feature = "arbitrary"))]
576impl proptest::prelude::Arbitrary for LibraryPath {
577 type Parameters = ();
578
579 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
580 use proptest::prelude::*;
581
582 let wasm_cm_style = LibraryPath::new_from_components(
583 LibraryNamespace::Anon,
584 [Ident::new("namespace-kebab:package-kebab/interface-kebab@1.0.0").unwrap()],
585 );
586 let path_len_2 = LibraryPath::new_from_components(
587 LibraryNamespace::User("user_ns".into()),
588 [Ident::new("user_module").unwrap()],
589 );
590 let path_len_3 = LibraryPath::new_from_components(
591 LibraryNamespace::User("userns".into()),
592 [Ident::new("user_path1").unwrap(), Ident::new("user_module").unwrap()],
593 );
594 prop_oneof![Just(wasm_cm_style), Just(path_len_2), Just(path_len_3)].boxed()
595 }
596
597 type Strategy = proptest::prelude::BoxedStrategy<Self>;
598}
599
600#[cfg(test)]
605mod tests {
606
607 use miden_core::{
608 assert_matches,
609 utils::{Deserializable, Serializable},
610 };
611 use proptest::prelude::*;
612
613 use super::{super::LibraryNamespaceError, IdentError, LibraryPath, PathError};
614
615 #[test]
616 fn new_path() {
617 let path = LibraryPath::new("foo").unwrap();
618 assert_eq!(path.num_components(), 1);
619
620 let path = LibraryPath::new("foo::bar").unwrap();
621 assert_eq!(path.num_components(), 2);
622
623 let path = LibraryPath::new("foo::bar::baz").unwrap();
624 assert_eq!(path.num_components(), 3);
625
626 let path = LibraryPath::new("miden:base/account@0.1.0").unwrap();
627 assert_eq!(path.num_components(), 1);
628
629 let path = LibraryPath::new("$exec::bar::baz").unwrap();
630 assert_eq!(path.num_components(), 3);
631
632 let path = LibraryPath::new("$kernel::bar::baz").unwrap();
633 assert_eq!(path.num_components(), 3);
634 }
635
636 #[test]
637 fn new_path_fail() {
638 let path = LibraryPath::new("");
639 assert_matches!(path, Err(PathError::Empty));
640
641 let path = LibraryPath::new("::");
642 assert_matches!(path, Err(PathError::InvalidNamespace(LibraryNamespaceError::Empty)));
643
644 let path = LibraryPath::new("foo::");
645 assert_matches!(path, Err(PathError::InvalidComponent(IdentError::Empty)));
646
647 let path = LibraryPath::new("::foo");
648 assert_matches!(path, Err(PathError::InvalidNamespace(LibraryNamespaceError::Empty)));
649
650 let path = LibraryPath::new("#foo::bar");
651 assert_matches!(
652 path,
653 Err(PathError::InvalidNamespace(LibraryNamespaceError::InvalidStart))
654 );
655 }
656
657 proptest! {
658 #[test]
659 fn path_serialization_roundtrip(path in any::<LibraryPath>()) {
660 let bytes = path.to_bytes();
661 let deserialized = LibraryPath::read_from_bytes(&bytes).unwrap();
662 assert_eq!(path, deserialized);
663 }
664 }
665}