Skip to main content

miden_assembly_syntax/ast/path/
components.rs

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