Skip to main content

libgraphql_parser/
graphql_token_stream.rs

1//! Streaming lexer that produces [`GraphQLToken`]s given some
2//! [`GraphQLTokenSource`] with a bounded lookahead buffer.
3
4use std::collections::VecDeque;
5
6use crate::token::GraphQLToken;
7use crate::token::GraphQLTokenKind;
8use crate::token_source::GraphQLTokenSource;
9
10/// Streaming lexer that produces [`GraphQLToken`]s given some
11/// [`GraphQLTokenSource`] with a bounded lookahead buffer.
12///
13/// This structure accepts any [`GraphQLTokenSource`] and provides
14/// lookahead capabilities while maintaining efficient streaming
15/// behavior. It centralizes buffering, peeking, and lookahead logic.
16///
17/// Since trivia is already attached to tokens by the lexer, the
18/// parser can simply call `peek()` and `consume()` without worrying
19/// about trivia.
20///
21/// # Internal Buffer Management
22///
23/// Tokens are stored in a [`VecDeque`] ring buffer. Unconsumed
24/// tokens are buffered at the back; `consume()` pops from the front
25/// and returns the owned token via O(1) `pop_front()`.
26///
27/// # Type Parameters
28///
29/// * `'src` - The lifetime of the source text that tokens are lexed
30///   from.
31/// * `TTokenSource` - The underlying token source, which must
32///   implement [`GraphQLTokenSource`] (i.e.,
33///   `Iterator<Item = GraphQLToken>`).
34///
35/// # Future TODOs
36///
37/// - Consider adding a `GraphQLTokenStreamOptions` struct to
38///   configure behavior:
39///   - `include_trivia: bool` - Whether to include
40///     preceding_trivia in tokens (can be disabled for performance
41///     when trivia is not needed)
42///   - `max_tokens: Option<usize>` - Limit total tokens returned
43///     (DoS protection)
44pub struct GraphQLTokenStream<
45    'src,
46    TTokenSource: GraphQLTokenSource<'src>,
47> {
48    token_source: TTokenSource,
49    /// Ring buffer of unconsumed tokens. Grows at the back via
50    /// `ensure_buffer_has()`; consumed from the front via
51    /// `pop_front()`.
52    buffer: VecDeque<GraphQLToken<'src>>,
53}
54
55impl<'src, TTokenSource: GraphQLTokenSource<'src>>
56    GraphQLTokenStream<'src, TTokenSource>
57{
58    /// Advance to the next token and return it as an owned value.
59    ///
60    /// Returns `None` if the stream is exhausted.
61    pub fn consume(&mut self) -> Option<GraphQLToken<'src>> {
62        self.ensure_buffer_has(1);
63        self.buffer.pop_front()
64    }
65
66    /// Returns the number of [`GraphQLToken`]s currently buffered
67    /// (unconsumed).
68    pub fn current_buffer_len(&self) -> usize {
69        self.buffer.len()
70    }
71
72    /// Fill the buffer to ensure it has at least `count`
73    /// unconsumed elements.
74    fn ensure_buffer_has(&mut self, count: usize) {
75        while self.buffer.len() < count {
76            if let Some(token) = self.token_source.next() {
77                self.buffer.push_back(token);
78            } else {
79                break;
80            }
81        }
82    }
83
84    /// Check if we've reached the end of the stream.
85    ///
86    /// Returns `true` if there are no more tokens to consume, or
87    /// if the next token is `Eof`.
88    pub fn is_at_end(&mut self) -> bool {
89        match self.peek() {
90            None => true,
91            Some(token) => {
92                matches!(token.kind, GraphQLTokenKind::Eof)
93            },
94        }
95    }
96
97    /// Creates a new token stream from a token source.
98    pub fn new(token_source: TTokenSource) -> Self {
99        Self {
100            token_source,
101            buffer: VecDeque::new(),
102        }
103    }
104
105    /// Peek at the next token without consuming it.
106    ///
107    /// Returns the front of the buffer (filling it first if
108    /// empty). Returns `None` if the stream is exhausted.
109    #[inline]
110    pub fn peek(&mut self) -> Option<&GraphQLToken<'src>> {
111        self.peek_nth(0)
112    }
113
114    /// Peek at the nth token ahead (0-indexed from next unconsumed
115    /// token).
116    ///
117    /// `peek_nth(0)` is equivalent to `peek()`.
118    ///
119    /// Fills the buffer up to `n+1` elements if needed. Returns
120    /// `None` if the stream ends before reaching position n.
121    pub fn peek_nth(
122        &mut self,
123        n: usize,
124    ) -> Option<&GraphQLToken<'src>> {
125        self.ensure_buffer_has(n + 1);
126        self.buffer.get(n)
127    }
128}