noa_parser/peek.rs
1//! Peekable types
2//!
3//! A `Peekable` is a type that can be used to peek at the current position of a
4//! `Scanner` without advancing the scanner.
5
6use crate::errors::{ParseError, ParseResult};
7use crate::recognizer::{Recognizable, recognize};
8use crate::scanner::Scanner;
9use std::marker::PhantomData;
10
11/// A successful peeking result.
12///
13/// A `Peeking` contains the start and end results of a successful peek, the
14/// length of the end slice, and a reference to the data that was peeked.
15#[derive(Debug)]
16pub struct Peeking<'a, T, S, E> {
17 /// The start of the match.
18 pub start: S,
19 /// The end of the match.
20 pub end: E,
21 /// The length of peeked slice.
22 pub end_slice: usize,
23 /// The data that was peeked.
24 pub data: &'a [T],
25}
26
27/// The result of a peeking operation.
28///
29/// A `PeekResult` contains the result of attempting to match a `Peekable`
30/// against the current position of a `Scanner`. If the match succeeds, a
31/// `Found` is returned with the length of the end slice, the start of the
32/// match, and the end of the match. If the match fails, a `NotFound` is
33/// returned.
34pub enum PeekResult<S, E> {
35 /// The match was successful.
36 Found { end_slice: usize, start: S, end: E },
37 /// The match was unsuccessful.
38 NotFound,
39}
40
41/// A type that can be peeked at the current position of a `Scanner`.
42///
43/// A `Peekable` is a type that can be used to peek at the current position of a
44/// `Scanner`. Implementors of `Peekable` must provide a `peek` method that
45/// attempts to match the `Peekable` against the current position of the
46/// `Scanner`.
47///
48/// # Associated Types
49///
50/// * `S` - The type of the start of the match.
51/// * `E` - The type of the end of the match.
52///
53/// # Required Methods
54///
55/// * `peek` - Attempts to match the `Peekable` against the current position of
56/// the `Scanner`.
57pub trait Peekable<'a, T, S, E> {
58 /// Attempt to match the `Peekable` against the current position of the
59 /// `Scanner`.
60 ///
61 /// This method will temporarily advance the position of the `Scanner` to
62 /// find a match. If a match is found, the `Scanner` is rewound to the
63 /// original position and a `PeekResult` is returned. If no match is found,
64 /// the `Scanner` is rewound to the original position and an `Err` is
65 /// returned.
66 ///
67 /// # Arguments
68 ///
69 /// * `data` - The `Scanner` to use when matching.
70 ///
71 /// # Returns
72 ///
73 /// A `PeekResult` if the `Peekable` matches the current position of the
74 /// `Scanner`, or an `Err` otherwise.
75 fn peek(&self, data: &mut Scanner<'a, T>) -> ParseResult<PeekResult<S, E>>;
76}
77
78/// Attempt to match a `Peekable` against the current position of a `Scanner`.
79///
80/// This function will temporarily advance the position of the `Scanner` to find
81/// a match. If a match is found, the `Scanner` is rewound to the original
82/// position and a `Peeking` is returned. If no match is found, the `Scanner` is
83/// rewound to the original position and an `Err` is returned.
84///
85/// # Arguments
86///
87/// * `peekable` - The `Peekable` to attempt to match.
88/// * `scanner` - The `Scanner` to use when matching.
89///
90/// # Returns
91///
92/// A `Peeking` if the `Peekable` matches the current position of the `Scanner`,
93/// or an `Err` otherwise.
94pub fn peek<'a, T, S, E, P: Peekable<'a, T, S, E>>(
95 peekable: P,
96 scanner: &'a mut Scanner<'a, T>,
97) -> ParseResult<Peeking<'a, T, S, E>> {
98 let source_cursor = scanner.current_position();
99 match peekable.peek(scanner)? {
100 PeekResult::Found {
101 end_slice,
102 start,
103 end,
104 } => {
105 let data = &scanner.data()[source_cursor..source_cursor + end_slice];
106 Ok(Peeking {
107 start,
108 end,
109 end_slice,
110 data,
111 })
112 }
113 PeekResult::NotFound => {
114 scanner.jump_to(source_cursor);
115 Err(ParseError::UnexpectedToken)
116 }
117 }
118}
119
120/// A `Peekable` that peeks until the given `element` is found in the
121/// `Scanner`.
122///
123/// This `Peekable` will temporarily advance the position of the `Scanner` to
124/// find a match. If a match is found, the `Scanner` is rewound to the original
125/// position and a `PeekResult` is returned. If no match is found, the `Scanner`
126/// is rewound to the original position and an `Err` is returned.
127pub struct Until<'a, T, V> {
128 element: V,
129 _marker: PhantomData<&'a T>,
130}
131
132impl<'a, T, V> Peekable<'a, T, V, V> for Until<'a, T, V>
133where
134 V: Recognizable<'a, T, V> + Clone,
135{
136 /// Peek until the given `element` is found in the `Scanner`.
137 ///
138 /// This function will temporarily advance the position of the `Scanner` to
139 /// find a match. If a match is found, the `Scanner` is rewound to the
140 /// original position and a `PeekResult` is returned. If no match is found,
141 /// the `Scanner` is rewound to the original position and an `Err` is
142 /// returned.
143 ///
144 /// # Arguments
145 ///
146 /// * `data` - The `Scanner` to use when matching.
147 ///
148 /// # Returns
149 ///
150 /// A `PeekResult` if the `element` matches the current position of the
151 /// `Scanner`, or an `Err` otherwise.
152 fn peek(&self, data: &mut Scanner<'a, T>) -> ParseResult<PeekResult<V, V>> {
153 // create a temporary scanner to peek data
154 let mut scanner = Scanner::new(data.data());
155 while !scanner.is_empty() {
156 match recognize(self.element.clone(), &mut scanner) {
157 Ok(_element) => {
158 return Ok(PeekResult::Found {
159 end_slice: scanner.current_position() - self.element.size(),
160 start: self.element.clone(),
161 end: self.element.clone(),
162 });
163 }
164 Err(_err) => {
165 scanner.bump_by(1);
166 continue;
167 }
168 }
169 }
170 Ok(PeekResult::NotFound)
171 }
172}