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    #[inline(always)]
26    pub fn as_str(&self) -> &'a str {
27        match self {
28            Self::Root => "::",
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        match self {
37            Self::Root => None,
38            Self::Normal(id) => Some(Ident::from_raw_parts(Span::unknown(Arc::from(
39                id.to_string().into_boxed_str(),
40            )))),
41        }
42    }
43
44    /// Get the size in [prim@char]s of this component when printed
45    pub fn char_len(&self) -> usize {
46        match self {
47            Self::Root => 2,
48            Self::Normal(id) => id.chars().count(),
49        }
50    }
51}
52
53impl PartialEq<str> for PathComponent<'_> {
54    fn eq(&self, other: &str) -> bool {
55        self.as_str().eq(other)
56    }
57}
58
59impl AsRef<str> for PathComponent<'_> {
60    #[inline(always)]
61    fn as_ref(&self) -> &str {
62        self.as_str()
63    }
64}
65
66impl fmt::Display for PathComponent<'_> {
67    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68        f.write_str(self.as_str())
69    }
70}
71
72/// Returns an iterator over the path components represented in the provided source.
73///
74/// A path consists of at list of components separated by `::` delimiter. A path must contain
75/// at least one component.
76///
77/// # Errors
78///
79/// Returns an error if:
80///
81/// * The path is empty.
82/// * Any component of the path is empty.
83/// * Any component is not a valid identifier (quoted or unquoted) in Miden Assembly syntax, i.e.
84///   starts with an ASCII alphabetic character, contains only printable ASCII characters, except
85///   for `::`, which must only be used as a path separator.
86pub struct Iter<'a> {
87    components: Components<'a>,
88}
89
90impl<'a> Iter<'a> {
91    pub fn new(path: &'a str) -> Self {
92        Self {
93            components: Components {
94                path,
95                front: State::Start,
96                back: State::Body,
97            },
98        }
99    }
100
101    #[inline]
102    pub fn as_path(&self) -> &'a Path {
103        Path::new(self.components.path)
104    }
105}
106
107impl FusedIterator for Iter<'_> {}
108
109impl<'a> Iterator for Iter<'a> {
110    type Item = Result<PathComponent<'a>, PathError>;
111
112    fn next(&mut self) -> Option<Self::Item> {
113        match self.components.next() {
114            Some(Ok(PathComponent::Normal(component)))
115                if component.len() > Path::MAX_COMPONENT_LENGTH =>
116            {
117                Some(Err(PathError::InvalidComponent(crate::ast::IdentError::InvalidLength {
118                    max: Path::MAX_COMPONENT_LENGTH,
119                })))
120            },
121            next => next,
122        }
123    }
124}
125
126impl<'a> DoubleEndedIterator for Iter<'a> {
127    fn next_back(&mut self) -> Option<Self::Item> {
128        match self.components.next_back() {
129            Some(Ok(PathComponent::Normal(component)))
130                if component.len() > Path::MAX_COMPONENT_LENGTH =>
131            {
132                Some(Err(PathError::InvalidComponent(crate::ast::IdentError::InvalidLength {
133                    max: Path::MAX_COMPONENT_LENGTH,
134                })))
135            },
136            next => next,
137        }
138    }
139}
140
141/// The underlying path component iterator used by [Iter]
142struct Components<'a> {
143    /// The path left to parse components from
144    path: &'a str,
145    // To support double-ended iteration, these states keep tack of what has been produced from
146    // each end
147    front: State,
148    back: State,
149}
150
151#[derive(Debug, Copy, Clone, PartialEq)]
152enum State {
153    // We're at the start of the path
154    Start,
155    // We're parsing components of the path
156    Body,
157    // We've started parsing a quoted component
158    QuoteOpened,
159    // We've parsed a quoted component
160    QuoteClosed,
161    // We're at the end of the path
162    Done,
163}
164
165impl<'a> Components<'a> {
166    fn finished(&self) -> bool {
167        match (self.front, self.back) {
168            (State::Done, _) => true,
169            (_, State::Done) => true,
170            (State::Body | State::QuoteOpened | State::QuoteClosed, State::Start) => true,
171            (..) => false,
172        }
173    }
174}
175
176impl<'a> Iterator for Components<'a> {
177    type Item = Result<PathComponent<'a>, PathError>;
178
179    fn next(&mut self) -> Option<Self::Item> {
180        while !self.finished() {
181            match self.front {
182                State::Start => match self.path.strip_prefix("::") {
183                    Some(rest) => {
184                        self.path = rest;
185                        self.front = State::Body;
186                        return Some(Ok(PathComponent::Root));
187                    },
188                    None if self.path.starts_with(Path::KERNEL_PATH)
189                        || self.path.starts_with(Path::EXEC_PATH) =>
190                    {
191                        self.front = State::Body;
192                        return Some(Ok(PathComponent::Root));
193                    },
194                    None => {
195                        self.front = State::Body;
196                    },
197                },
198                State::Body => {
199                    if let Some(rest) = self.path.strip_prefix('"') {
200                        self.front = State::QuoteOpened;
201                        self.path = rest;
202                        continue;
203                    }
204                    match self.path.split_once("::") {
205                        Some(("", rest)) => {
206                            self.path = rest;
207                            return Some(Err(PathError::InvalidComponent(
208                                crate::ast::IdentError::Empty,
209                            )));
210                        },
211                        Some((component, rest)) => {
212                            if rest.is_empty() {
213                                self.path = "::";
214                            } else {
215                                self.path = rest;
216                            }
217                            if let Err(err) =
218                                Ident::validate(component).map_err(PathError::InvalidComponent)
219                            {
220                                return Some(Err(err));
221                            }
222                            return Some(Ok(PathComponent::Normal(component)));
223                        },
224                        None if self.path.is_empty() => {
225                            self.front = State::Done;
226                        },
227                        None => {
228                            self.front = State::Done;
229                            let component = self.path;
230                            self.path = "";
231                            if let Err(err) =
232                                Ident::validate(component).map_err(PathError::InvalidComponent)
233                            {
234                                return Some(Err(err));
235                            }
236                            return Some(Ok(PathComponent::Normal(component)));
237                        },
238                    }
239                },
240                State::QuoteOpened => match self.path.split_once('"') {
241                    Some(("", rest)) => {
242                        self.path = rest;
243                        self.front = State::QuoteClosed;
244                        return Some(Err(PathError::EmptyComponent));
245                    },
246                    Some((quoted, rest)) => {
247                        self.path = rest;
248                        self.front = State::QuoteClosed;
249                        return Some(Ok(PathComponent::Normal(quoted)));
250                    },
251                    None => {
252                        self.front = State::Done;
253                        return Some(Err(PathError::UnclosedQuotedComponent));
254                    },
255                },
256                State::QuoteClosed => {
257                    if self.path.is_empty() {
258                        self.front = State::Done;
259                        continue;
260                    }
261                    match self.path.strip_prefix("::") {
262                        Some(rest) => {
263                            self.path = rest;
264                            self.front = State::Body;
265                        },
266                        None => {
267                            self.front = State::Done;
268                            return Some(Err(PathError::MissingPathSeparator));
269                        },
270                    }
271                },
272                State::Done => break,
273            }
274        }
275
276        None
277    }
278}
279
280impl<'a> DoubleEndedIterator for Components<'a> {
281    fn next_back(&mut self) -> Option<Self::Item> {
282        while !self.finished() {
283            match self.back {
284                State::Start => {
285                    self.back = State::Done;
286                    match self.path {
287                        "" => break,
288                        "::" => return Some(Ok(PathComponent::Root)),
289                        other => {
290                            assert!(
291                                other.starts_with(Path::KERNEL_PATH)
292                                    || other.starts_with(Path::EXEC_PATH),
293                                "expected path in start state to be a valid path prefix, got '{other}'"
294                            );
295                            return Some(Ok(PathComponent::Root));
296                        },
297                    }
298                },
299                State::Body => {
300                    if let Some(rest) = self.path.strip_suffix('"') {
301                        self.back = State::QuoteClosed;
302                        self.path = rest;
303                        continue;
304                    }
305                    match self.path.rsplit_once("::") {
306                        Some(("", "")) => {
307                            self.back = State::Start;
308                            continue;
309                        },
310                        Some((prefix, component)) => {
311                            if prefix.is_empty() {
312                                self.path = "::";
313                                self.back = State::Start;
314                            } else {
315                                self.path = prefix;
316                            }
317                            if let Err(err) =
318                                Ident::validate(component).map_err(PathError::InvalidComponent)
319                            {
320                                return Some(Err(err));
321                            }
322                            return Some(Ok(PathComponent::Normal(component)));
323                        },
324                        None if self.path.is_empty() => {
325                            self.back = State::Start;
326                        },
327                        None => {
328                            self.back = State::Start;
329                            let component = self.path;
330                            if component.starts_with(Path::KERNEL_PATH)
331                                || component.starts_with(Path::EXEC_PATH)
332                            {
333                                self.path = "::";
334                            } else {
335                                self.path = "";
336                            }
337                            if let Err(err) =
338                                Ident::validate(component).map_err(PathError::InvalidComponent)
339                            {
340                                return Some(Err(err));
341                            }
342                            return Some(Ok(PathComponent::Normal(component)));
343                        },
344                    }
345                },
346                State::QuoteOpened => {
347                    if self.path.is_empty() {
348                        self.back = State::Start;
349                        continue;
350                    }
351                    match self.path.strip_suffix("::") {
352                        Some("") => {
353                            self.back = State::Start;
354                        },
355                        Some(rest) => {
356                            self.path = rest;
357                            self.back = State::Body;
358                        },
359                        None => {
360                            self.back = State::Done;
361                            return Some(Err(PathError::MissingPathSeparator));
362                        },
363                    }
364                },
365                State::QuoteClosed => match self.path.rsplit_once('"') {
366                    Some((rest, "")) => {
367                        self.path = rest;
368                        self.back = State::QuoteOpened;
369                        return Some(Err(PathError::EmptyComponent));
370                    },
371                    Some((rest, quoted)) => {
372                        self.path = rest;
373                        self.back = State::QuoteOpened;
374                        return Some(Ok(PathComponent::Normal(quoted)));
375                    },
376                    None => {
377                        self.back = State::Done;
378                        return Some(Err(PathError::UnclosedQuotedComponent));
379                    },
380                },
381                State::Done => break,
382            }
383        }
384        None
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use miden_core::assert_matches;
391
392    use super::*;
393
394    #[test]
395    fn empty_path() {
396        let mut components = Iter::new("");
397        assert_matches!(components.next(), None);
398    }
399
400    #[test]
401    fn empty_path_back() {
402        let mut components = Iter::new("");
403        assert_matches!(components.next_back(), None);
404    }
405
406    #[test]
407    fn root_prefix_path() {
408        let mut components = Iter::new("::");
409        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
410        assert_matches!(components.next(), None);
411    }
412
413    #[test]
414    fn root_prefix_path_back() {
415        let mut components = Iter::new("::");
416        assert_matches!(components.next_back(), Some(Ok(PathComponent::Root)));
417        assert_matches!(components.next_back(), None);
418    }
419
420    #[test]
421    fn absolute_path() {
422        let mut components = Iter::new("::foo");
423        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
424        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
425        assert_matches!(components.next(), None);
426    }
427
428    #[test]
429    fn absolute_path_back() {
430        let mut components = Iter::new("::foo");
431        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
432        assert_matches!(components.next_back(), Some(Ok(PathComponent::Root)));
433        assert_matches!(components.next_back(), None);
434    }
435
436    #[test]
437    fn absolute_nested_path() {
438        let mut components = Iter::new("::foo::bar");
439        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
440        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
441        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
442        assert_matches!(components.next(), None);
443    }
444
445    #[test]
446    fn absolute_nested_path_back() {
447        let mut components = Iter::new("::foo::bar");
448        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("bar"))));
449        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
450        assert_matches!(components.next_back(), Some(Ok(PathComponent::Root)));
451        assert_matches!(components.next_back(), None);
452    }
453
454    #[test]
455    fn relative_path() {
456        let mut components = Iter::new("foo");
457        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
458        assert_matches!(components.next(), None);
459    }
460
461    #[test]
462    fn relative_path_back() {
463        let mut components = Iter::new("foo");
464        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
465        assert_matches!(components.next_back(), None);
466    }
467
468    #[test]
469    fn relative_nested_path() {
470        let mut components = Iter::new("foo::bar");
471        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
472        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
473        assert_matches!(components.next(), None);
474    }
475
476    #[test]
477    fn relative_nested_path_back() {
478        let mut components = Iter::new("foo::bar");
479        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("bar"))));
480        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
481        assert_matches!(components.next_back(), None);
482    }
483
484    #[test]
485    fn special_path() {
486        let mut components = Iter::new("$kernel");
487        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
488        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("$kernel"))));
489        assert_matches!(components.next(), None);
490    }
491
492    #[test]
493    fn special_path_back() {
494        let mut components = Iter::new("$kernel");
495        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("$kernel"))));
496        assert_matches!(components.next_back(), Some(Ok(PathComponent::Root)));
497        assert_matches!(components.next_back(), None);
498    }
499
500    #[test]
501    fn special_nested_path() {
502        let mut components = Iter::new("$kernel::bar");
503        assert_matches!(components.next(), Some(Ok(PathComponent::Root)));
504        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("$kernel"))));
505        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
506        assert_matches!(components.next(), None);
507    }
508
509    #[test]
510    fn special_nested_path_back() {
511        let mut components = Iter::new("$kernel::bar");
512        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("bar"))));
513        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("$kernel"))));
514        assert_matches!(components.next_back(), Some(Ok(PathComponent::Root)));
515        assert_matches!(components.next_back(), None);
516    }
517
518    #[test]
519    fn path_with_quoted_component() {
520        let mut components = Iter::new("\"foo\"");
521        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
522        assert_matches!(components.next(), None);
523    }
524
525    #[test]
526    fn path_with_quoted_component_back() {
527        let mut components = Iter::new("\"foo\"");
528        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
529        assert_matches!(components.next_back(), None);
530    }
531
532    #[test]
533    fn nested_path_with_quoted_component() {
534        let mut components = Iter::new("foo::\"bar\"");
535        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
536        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
537        assert_matches!(components.next(), None);
538    }
539
540    #[test]
541    fn nested_path_with_quoted_component_back() {
542        let mut components = Iter::new("foo::\"bar\"");
543        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("bar"))));
544        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
545        assert_matches!(components.next_back(), None);
546    }
547
548    #[test]
549    fn nested_path_with_interspersed_quoted_component() {
550        let mut components = Iter::new("foo::\"bar\"::baz");
551        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("foo"))));
552        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("bar"))));
553        assert_matches!(components.next(), Some(Ok(PathComponent::Normal("baz"))));
554        assert_matches!(components.next(), None);
555    }
556
557    #[test]
558    fn nested_path_with_interspersed_quoted_component_back() {
559        let mut components = Iter::new("foo::\"bar\"::baz");
560        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("baz"))));
561        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("bar"))));
562        assert_matches!(components.next_back(), Some(Ok(PathComponent::Normal("foo"))));
563        assert_matches!(components.next_back(), None);
564    }
565}