Skip to main content

miden_assembly_syntax/ast/path/
components.rs

1use alloc::{string::ToString, sync::Arc};
2use core::{fmt, iter::FusedIterator};
3
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7use super::{Path, PathError};
8use crate::{ast::Ident, debuginfo::Span};
9
10// PATH COMPONENT
11// ================================================================================================
12
13/// Represents a single component of a [Path]
14#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16pub enum PathComponent<'a> {
17    /// The root anchor, indicating that the path is absolute/fully qualified
18    Root,
19    /// A normal component of the path, i.e. an identifier
20    Normal(&'a str),
21}
22
23impl<'a> PathComponent<'a> {
24    /// Get this component as a [prim@str]
25    ///
26    /// NOTE: If the component is quoted, the resulting string does _not_ contain quotes. Depending
27    /// on how the resulting string is used, you may need to ensure quotes are added manually. In
28    /// general, the `Path`/`PathBuf` APIs handle this for you.
29    pub fn as_str(&self) -> &'a str {
30        match self {
31            Self::Root => "::",
32            Self::Normal(id) if id.starts_with('"') && id.ends_with('"') => &id[1..(id.len() - 1)],
33            Self::Normal(id) => id,
34        }
35    }
36
37    /// Get this component as an [Ident], if it represents an identifier
38    #[inline]
39    pub fn to_ident(&self) -> Option<Ident> {
40        if matches!(self, Self::Root) {
41            None
42        } else {
43            Some(Ident::from_raw_parts(Span::unknown(Arc::from(
44                self.as_str().to_string().into_boxed_str(),
45            ))))
46        }
47    }
48
49    /// Get the size in [prim@char]s of this component when printed
50    pub fn char_len(&self) -> usize {
51        self.as_str().chars().count()
52    }
53
54    /// Returns true if this path component is a quoted string
55    pub fn is_quoted(&self) -> bool {
56        matches!(self, Self::Normal(component) if component.starts_with('"') && component.ends_with('"'))
57    }
58
59    /// Returns true if this path component requires quoting when displayed/stored as a string
60    pub fn requires_quoting(&self) -> bool {
61        match self {
62            Self::Root => false,
63            Self::Normal(Path::KERNEL_PATH | Path::EXEC_PATH) => false,
64            Self::Normal(component)
65                if component.contains("::") || Ident::requires_quoting(component) =>
66            {
67                true
68            },
69            Self::Normal(_) => false,
70        }
71    }
72}
73
74impl PartialEq<str> for PathComponent<'_> {
75    fn eq(&self, other: &str) -> bool {
76        self.as_str().eq(other)
77    }
78}
79
80impl AsRef<str> for PathComponent<'_> {
81    #[inline(always)]
82    fn as_ref(&self) -> &str {
83        self.as_str()
84    }
85}
86
87impl fmt::Display for PathComponent<'_> {
88    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89        f.write_str(self.as_str())
90    }
91}
92
93/// Returns an iterator over the path components represented in the provided source.
94///
95/// A path consists of at list of components separated by `::` delimiter. A path must contain
96/// at least one component. Path components may be quoted, in which `::` delimiters are ignored.
97/// Quoted path components may also contain characters that are not otherwise valid identifiers in
98/// Miden Assembly.
99///
100/// Note that quoted components may not contain nested quotes - the appearance of a nested quote in
101/// a quoted component will be treated as a closing quote resulting in unexpected behavior or
102/// validation errors as a result.
103///
104/// # Errors
105///
106/// Returns an error if:
107///
108/// * The path is empty.
109/// * Any component of the path is empty.
110/// * Any quoted component is missing a closing/opening quote (depending on order of iteration)
111/// * Any unquoted component is not a valid identifier (quoted or unquoted) in Miden Assembly
112///   syntax, i.e. starts with an ASCII alphabetic character, contains only printable ASCII
113///   characters, except for `::`, which must only be used as a path separator.
114#[derive(Debug)]
115pub struct Iter<'a> {
116    components: Components<'a>,
117}
118
119impl<'a> Iter<'a> {
120    pub fn new(path: &'a str) -> Self {
121        Self {
122            components: Components {
123                path,
124                original: path,
125                front_pos: 0,
126                front: State::Start,
127                back_pos: path.len(),
128                back: State::Body,
129            },
130        }
131    }
132
133    #[inline]
134    pub fn as_path(&self) -> &'a Path {
135        Path::new(self.components.path)
136    }
137}
138
139impl FusedIterator for Iter<'_> {}
140
141impl<'a> Iterator for Iter<'a> {
142    type Item = Result<PathComponent<'a>, PathError>;
143
144    fn next(&mut self) -> Option<Self::Item> {
145        match self.components.next() {
146            Some(Ok(component @ PathComponent::Normal(_)))
147                if component.as_str().chars().count() > Path::MAX_COMPONENT_LENGTH =>
148            {
149                Some(Err(PathError::InvalidComponent(crate::ast::IdentError::InvalidLength {
150                    max: Path::MAX_COMPONENT_LENGTH,
151                })))
152            },
153            next => next,
154        }
155    }
156}
157
158impl<'a> DoubleEndedIterator for Iter<'a> {
159    fn next_back(&mut self) -> Option<Self::Item> {
160        match self.components.next_back() {
161            Some(Ok(component @ PathComponent::Normal(_)))
162                if component.as_str().chars().count() > Path::MAX_COMPONENT_LENGTH =>
163            {
164                Some(Err(PathError::InvalidComponent(crate::ast::IdentError::InvalidLength {
165                    max: Path::MAX_COMPONENT_LENGTH,
166                })))
167            },
168            next => next,
169        }
170    }
171}
172
173/// The underlying path component iterator used by [Iter]
174#[derive(Debug)]
175struct Components<'a> {
176    original: &'a str,
177    /// The path left to parse components from
178    path: &'a str,
179    // To support double-ended iteration, these states keep tack of what has been produced from
180    // each end
181    front_pos: usize,
182    front: State,
183    back_pos: usize,
184    back: State,
185}
186
187#[derive(Debug, Copy, Clone, PartialEq)]
188enum State {
189    // We're at the start of the path
190    Start,
191    // We're parsing components of the path
192    Body,
193    // We've started parsing a quoted component
194    QuoteOpened(usize),
195    // We've parsed a quoted component
196    QuoteClosed(usize),
197    // We're at the end of the path
198    Done,
199}
200
201impl<'a> Components<'a> {
202    fn finished(&self) -> bool {
203        match (self.front, self.back) {
204            (State::Done, _) => true,
205            (_, State::Done) => true,
206            (State::Body | State::QuoteOpened(_) | State::QuoteClosed(_), State::Start) => true,
207            (..) => false,
208        }
209    }
210}
211
212impl<'a> Iterator for Components<'a> {
213    type Item = Result<PathComponent<'a>, PathError>;
214
215    fn next(&mut self) -> Option<Self::Item> {
216        // This is used when consuming a quoted item, to hold the result of the QuoteOpened state
217        // until we've finished transitioning through the QuoteClosed state. It is never used
218        // otherwise.
219        let mut quote_opened = None;
220        while !self.finished() || quote_opened.is_some() {
221            match self.front {
222                State::Start => match self.path.strip_prefix("::") {
223                    Some(rest) => {
224                        self.path = rest;
225                        self.front = State::Body;
226                        self.front_pos += 2;
227                        return Some(Ok(PathComponent::Root));
228                    },
229                    None => {
230                        self.front = State::Body;
231                    },
232                },
233                State::Body => {
234                    if let Some(rest) = self.path.strip_prefix('"') {
235                        self.front = State::QuoteOpened(self.front_pos);
236                        self.front_pos += 1;
237                        self.path = rest;
238                        continue;
239                    }
240                    match self.path.split_once("::") {
241                        Some(("", rest)) => {
242                            self.path = rest;
243                            self.front_pos += 2;
244                            return Some(Err(PathError::InvalidComponent(
245                                crate::ast::IdentError::Empty,
246                            )));
247                        },
248                        Some((component, rest)) => {
249                            self.front_pos += component.len() + 2;
250                            if rest.is_empty() {
251                                self.path = "::";
252                            } else {
253                                self.path = rest;
254                            }
255                            if let Err(err) =
256                                Ident::validate(component).map_err(PathError::InvalidComponent)
257                            {
258                                return Some(Err(err));
259                            }
260                            return Some(Ok(PathComponent::Normal(component)));
261                        },
262                        None if self.path.is_empty() => {
263                            self.front = State::Done;
264                        },
265                        None => {
266                            self.front = State::Done;
267                            let component = self.path;
268                            self.path = "";
269                            if let Err(err) =
270                                Ident::validate(component).map_err(PathError::InvalidComponent)
271                            {
272                                return Some(Err(err));
273                            }
274                            self.front_pos += component.len();
275                            return Some(Ok(PathComponent::Normal(component)));
276                        },
277                    }
278                },
279                State::QuoteOpened(opened_at) => match self.path.split_once('"') {
280                    Some(("", rest)) => {
281                        self.path = rest;
282                        self.front = State::QuoteClosed(self.front_pos);
283                        self.front_pos += 1;
284                        quote_opened = Some(Err(PathError::EmptyComponent));
285                    },
286                    Some((quoted, rest)) => {
287                        self.path = rest;
288                        self.front_pos += quoted.len();
289                        self.front = State::QuoteClosed(self.front_pos);
290                        self.front_pos += 1;
291                        let quoted = &self.original[opened_at..self.front_pos];
292                        quote_opened = Some(Ok(PathComponent::Normal(quoted)));
293                    },
294                    None => {
295                        self.front = State::Done;
296                        self.front_pos += self.path.len();
297                        return Some(Err(PathError::UnclosedQuotedComponent));
298                    },
299                },
300                State::QuoteClosed(_) => {
301                    if self.path.is_empty() {
302                        self.front = State::Done;
303                    } else {
304                        match self.path.strip_prefix("::") {
305                            Some(rest) => {
306                                self.path = rest;
307                                self.front = State::Body;
308                                self.front_pos += 2;
309                            },
310                            // If we would raise an error, but we have a quoted component to return
311                            // first, leave the state untouched, return the quoted component, and we
312                            // will return here on the next call to `next_back`
313                            None if quote_opened.is_some() => (),
314                            None => {
315                                self.front = State::Done;
316                                return Some(Err(PathError::MissingPathSeparator));
317                            },
318                        }
319                    }
320
321                    if quote_opened.is_some() {
322                        return quote_opened;
323                    }
324                },
325                State::Done => break,
326            }
327        }
328
329        None
330    }
331}
332
333impl<'a> DoubleEndedIterator for Components<'a> {
334    fn next_back(&mut self) -> Option<Self::Item> {
335        // This is used when consuming a quoted item, to hold the result of the QuoteClosed state
336        // until we've finished transitioning through the QuoteOpened state. It is never used
337        // otherwise.
338        let mut quote_closed = None;
339        while !self.finished() || quote_closed.is_some() {
340            match self.back {
341                State::Start => {
342                    self.back = State::Done;
343                    match self.path {
344                        "" => break,
345                        "::" => {
346                            self.back_pos = 0;
347                            return Some(Ok(PathComponent::Root));
348                        },
349                        other => {
350                            return Some(Ok(PathComponent::Normal(other)));
351                        },
352                    }
353                },
354                State::Body => {
355                    if let Some(rest) = self.path.strip_suffix('"') {
356                        self.back = State::QuoteClosed(self.back_pos);
357                        self.back_pos -= 1;
358                        self.path = rest;
359                        continue;
360                    }
361                    match self.path.rsplit_once("::") {
362                        Some(("", "")) => {
363                            self.back = State::Start;
364                            self.back_pos -= 2;
365                            continue;
366                        },
367                        Some((prefix, component)) => {
368                            self.back_pos -= component.len() + 2;
369                            if prefix.is_empty() {
370                                self.path = "::";
371                                self.back = State::Start;
372                            } else {
373                                self.path = prefix;
374                            }
375                            if let Err(err) =
376                                Ident::validate(component).map_err(PathError::InvalidComponent)
377                            {
378                                return Some(Err(err));
379                            }
380                            return Some(Ok(PathComponent::Normal(component)));
381                        },
382                        None if self.path.is_empty() => {
383                            self.back = State::Start;
384                        },
385                        None => {
386                            self.back = State::Start;
387                            let component = self.path;
388                            self.path = "";
389                            self.back_pos = 0;
390                            if let Err(err) =
391                                Ident::validate(component).map_err(PathError::InvalidComponent)
392                            {
393                                return Some(Err(err));
394                            }
395                            return Some(Ok(PathComponent::Normal(component)));
396                        },
397                    }
398                },
399                State::QuoteOpened(_) => {
400                    if self.path.is_empty() {
401                        self.back = State::Start;
402                    } else {
403                        match self.path.strip_suffix("::") {
404                            Some("") => {
405                                self.back = State::Start;
406                                self.back_pos -= 2;
407                            },
408                            Some(rest) => {
409                                self.back_pos -= 2;
410                                self.path = rest;
411                                self.back = State::Body;
412                            },
413                            // If we would raise an error, but we have a quoted component to return
414                            // first, leave the state untouched, return the quoted component, and we
415                            // will return here on the next call to `next_back`
416                            None if quote_closed.is_some() => (),
417                            None => {
418                                self.back = State::Done;
419                                return Some(Err(PathError::MissingPathSeparator));
420                            },
421                        }
422                    }
423
424                    if quote_closed.is_some() {
425                        return quote_closed;
426                    }
427                },
428                State::QuoteClosed(closed_at) => match self.path.rsplit_once('"') {
429                    Some((rest, "")) => {
430                        self.back_pos -= 1;
431                        self.path = rest;
432                        self.back = State::QuoteOpened(self.back_pos);
433                        quote_closed = Some(Err(PathError::EmptyComponent));
434                    },
435                    Some((rest, quoted)) => {
436                        self.back_pos -= quoted.len() + 1;
437                        let quoted = &self.original[self.back_pos..closed_at];
438                        self.path = rest;
439                        self.back = State::QuoteOpened(self.back_pos);
440                        quote_closed = Some(Ok(PathComponent::Normal(quoted)));
441                    },
442                    None => {
443                        self.back = State::Done;
444                        self.back_pos = 0;
445                        return Some(Err(PathError::UnclosedQuotedComponent));
446                    },
447                },
448                State::Done => break,
449            }
450        }
451        None
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use miden_core::assert_matches;
458
459    use super::*;
460
461    #[test]
462    fn empty_path() {
463        let mut components = Iter::new("");
464        assert_matches!(components.next(), None);
465    }
466
467    #[test]
468    fn empty_path_back() {
469        let mut components = Iter::new("");
470        assert_matches!(components.next_back(), None);
471    }
472
473    #[test]
474    fn root_prefix_path() {
475        let mut components = Iter::new("::");
476        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
477        assert_matches!(components.next(), None);
478    }
479
480    #[test]
481    fn root_prefix_path_back() {
482        let mut components = Iter::new("::");
483        assert_matches!(components.next_back(), Some(Ok(PathComponent::Root)));
484        assert_matches!(components.next_back(), None);
485    }
486
487    #[test]
488    fn absolute_path() {
489        let mut components = Iter::new("::foo");
490        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
491        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
492        assert_matches!(components.next(), None);
493    }
494
495    #[test]
496    fn absolute_path_back() {
497        let mut components = Iter::new("::foo");
498        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
499        assert_matches!(components.next_back(), Some(Ok(PathComponent::Root)));
500        assert_matches!(components.next_back(), None);
501    }
502
503    #[test]
504    fn absolute_nested_path() {
505        let mut components = Iter::new("::foo::bar");
506        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
507        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
508        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
509        assert_matches!(components.next(), None);
510    }
511
512    #[test]
513    fn absolute_nested_path_back() {
514        let mut components = Iter::new("::foo::bar");
515        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("bar"))));
516        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
517        assert_matches!(components.next_back(), Some(Ok(PathComponent::Root)));
518        assert_matches!(components.next_back(), None);
519    }
520
521    #[test]
522    fn relative_path() {
523        let mut components = Iter::new("foo");
524        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
525        assert_matches!(components.next(), None);
526    }
527
528    #[test]
529    fn relative_path_back() {
530        let mut components = Iter::new("foo");
531        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
532        assert_matches!(components.next_back(), None);
533    }
534
535    #[test]
536    fn relative_nested_path() {
537        let mut components = Iter::new("foo::bar");
538        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
539        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
540        assert_matches!(components.next(), None);
541    }
542
543    #[test]
544    fn relative_nested_path_back() {
545        let mut components = Iter::new("foo::bar");
546        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("bar"))));
547        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
548        assert_matches!(components.next_back(), None);
549    }
550
551    #[test]
552    fn special_path() {
553        let mut components = Iter::new("$kernel");
554        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("$kernel"))));
555        assert_matches!(components.next(), None);
556
557        let mut components = Iter::new("::$kernel");
558        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
559        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("$kernel"))));
560        assert_matches!(components.next(), None);
561    }
562
563    #[test]
564    fn special_path_back() {
565        let mut components = Iter::new("$kernel");
566        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("$kernel"))));
567        assert_matches!(components.next_back(), None);
568
569        let mut components = Iter::new("::$kernel");
570        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("$kernel"))));
571        assert_matches!(components.next_back(), Some(Ok(PathComponent::Root)));
572        assert_matches!(components.next_back(), None);
573    }
574
575    #[test]
576    fn special_nested_path() {
577        let mut components = Iter::new("$kernel::bar");
578        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("$kernel"))));
579        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
580        assert_matches!(components.next(), None);
581
582        let mut components = Iter::new("::$kernel::bar");
583        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
584        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("$kernel"))));
585        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
586        assert_matches!(components.next(), None);
587    }
588
589    #[test]
590    fn special_nested_path_back() {
591        let mut components = Iter::new("$kernel::bar");
592        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("bar"))));
593        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("$kernel"))));
594        assert_matches!(components.next_back(), None);
595
596        let mut components = Iter::new("::$kernel::bar");
597        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("bar"))));
598        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("$kernel"))));
599        assert_matches!(components.next_back(), Some(Ok(PathComponent::Root)));
600        assert_matches!(components.next_back(), None);
601    }
602
603    #[test]
604    fn path_with_quoted_component() {
605        let mut components = Iter::new("\"foo\"");
606        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("\"foo\""))));
607        assert_matches!(components.next(), None);
608    }
609
610    #[test]
611    fn path_with_quoted_component_back() {
612        let mut components = Iter::new("\"foo\"");
613        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("\"foo\""))));
614        assert_matches!(components.next_back(), None);
615    }
616
617    #[test]
618    fn nested_path_with_quoted_component() {
619        let mut components = Iter::new("foo::\"bar\"");
620        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
621        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("\"bar\""))));
622        assert_matches!(components.next(), None);
623    }
624
625    #[test]
626    fn nested_path_with_quoted_component_back() {
627        let mut components = Iter::new("foo::\"bar\"");
628        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("\"bar\""))));
629        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
630        assert_matches!(components.next_back(), None);
631    }
632
633    #[test]
634    fn nested_path_with_interspersed_quoted_component() {
635        let mut components = Iter::new("foo::\"bar\"::baz");
636        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
637        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("\"bar\""))));
638        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("baz"))));
639        assert_matches!(components.next(), None);
640    }
641
642    #[test]
643    fn nested_path_with_interspersed_quoted_component_back() {
644        let mut components = Iter::new("foo::\"bar\"::baz");
645        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("baz"))));
646        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("\"bar\""))));
647        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
648        assert_matches!(components.next_back(), None);
649    }
650}