miden_assembly_syntax/ast/path/
path_buf.rs1use 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#[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
58impl PathBuf {
60 pub fn with_capacity(capacity: usize) -> Self {
62 Self { inner: String::with_capacity(capacity) }
63 }
64
65 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 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 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 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 #[inline]
120 pub fn as_path(&self) -> &Path {
121 self.as_ref()
122 }
123
124 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 unsafe { alloc::boxed::Box::from_raw(inner as *mut Path) }
130 }
131}
132
133impl PathBuf {
135 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 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 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 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 Ok(_) if !component.contains("::") => {
228 self.inner.push_str(component);
229 },
230 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 Err(
245 crate::ast::IdentError::InvalidLength { .. }
246 | crate::ast::IdentError::Empty,
247 ) => unreachable!(),
248 }
249 }
250 },
251 }
252 }
253
254 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#[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 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 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}