Skip to main content

varyk_syntax/parser/
mod.rs

1//! The parser: a hand-written recursive-descent / Pratt parser over a
2//! token slice. This module holds the token-cursor primitives shared by
3//! every parsing function; `expr.rs` holds expression parsing, `item.rs`
4//! item parsing (`fn`, `struct`, `mod`) and the top-level [`Parser::parse_program`],
5//! `stmt.rs` statement parsing, `pattern.rs` pattern parsing, and `ty.rs`
6//! type parsing.
7
8mod expr;
9mod item;
10mod pattern;
11mod stmt;
12mod ty;
13
14use crate::ast::{PathStart, Program};
15use crate::error::{FixIt, SyntaxError, V0001, V0002};
16use crate::span::{FileId, Span};
17use crate::token::{Token, TokenKind};
18
19/// The message for a `#` where no attribute can go (M5a spec 2.2).
20pub(super) const STRAY_HASH: &str = "an attribute (`#[..]`) can only go before a struct field, an enum variant, or a \
21     top-level function";
22
23/// Parses a whole token stream for a single file into a [`Program`] plus
24/// every syntax error found. On the first `V0002` (or an unrecoverable
25/// `V0001`, such as a reserved keyword where an item is expected), parsing
26/// stops and the items parsed so far are returned alongside the errors.
27pub fn parse(tokens: &[Token], file: FileId) -> (Program, Vec<SyntaxError>) {
28    let mut parser = Parser::new(tokens, file);
29    let program = parser.parse_program();
30    (program, parser.errors().to_vec())
31}
32
33/// Parses a token slice for a single file. No error recovery beyond what
34/// each parsing function documents: `V0002` always stops parsing outright,
35/// since without a reliable recovery point there is nothing safe to guess.
36/// Most other diagnostic codes are recorded and parsing continues (for
37/// example the `&` / `&mut` sigil's `V0010`), but a few `V0001`s have no
38/// operand left to fall back to — a reserved keyword or an unsupported
39/// macro name where an expression is expected — and stop parsing too, per
40/// the call sites that produce them.
41pub(crate) struct Parser<'a> {
42    tokens: &'a [Token],
43    pos: usize,
44    file: FileId,
45    errors: Vec<SyntaxError>,
46    /// Set while parsing an `if` or `while` condition, so
47    /// that a `{` right after a path is treated as the start of the
48    /// block, not a struct literal `Name { ... }` — the same ambiguity
49    /// Rust resolves the same way.
50    in_condition: bool,
51    /// Set right before parsing a `for` loop's head starts, and consumed
52    /// by the very next `(` reached as a primary expression (`parse_primary`),
53    /// if any: it never survives past that first primary. Lets a range
54    /// found directly inside that group (`for i in (0..3)`) get a message
55    /// naming the parentheses themselves, rather than the generic "only in
56    /// the head of a `for` loop" one, since this group IS the head.
57    for_head_leading_paren: bool,
58}
59
60impl<'a> Parser<'a> {
61    pub(crate) fn new(tokens: &'a [Token], file: FileId) -> Self {
62        Self {
63            tokens,
64            pos: 0,
65            file,
66            errors: Vec::new(),
67            in_condition: false,
68            for_head_leading_paren: false,
69        }
70    }
71
72    /// Every syntax error recorded so far, in the order they were found.
73    pub(crate) fn errors(&self) -> &[SyntaxError] {
74        &self.errors
75    }
76
77    // --- Cursor -----------------------------------------------------------
78
79    fn peek(&self) -> Option<&TokenKind> {
80        self.tokens.get(self.pos).map(|t| &t.kind)
81    }
82
83    /// The kind of the token `offset` positions past the current one,
84    /// without consuming anything. `peek_at(0)` is [`Parser::peek`].
85    fn peek_at(&self, offset: usize) -> Option<&TokenKind> {
86        self.tokens.get(self.pos + offset).map(|t| &t.kind)
87    }
88
89    fn peek_token(&self) -> Option<&Token> {
90        self.tokens.get(self.pos)
91    }
92
93    /// Consumes and returns the current token, or `None` at the end of the
94    /// stream.
95    fn bump(&mut self) -> Option<Token> {
96        let token = self.tokens.get(self.pos).cloned();
97        if token.is_some() {
98            self.pos += 1;
99        }
100        token
101    }
102
103    /// The span an error should carry when there is no current token to
104    /// point at: a zero-width span right after the last token (or at byte
105    /// 0 of an empty file).
106    fn eof_span(&self) -> Span {
107        let end = self.tokens.last().map(|t| t.span.end).unwrap_or(0);
108        Span::new(self.file, end, end)
109    }
110
111    /// The span of the current token, or [`Parser::eof_span`] if the
112    /// stream is exhausted.
113    fn current_span(&self) -> Span {
114        self.peek_token()
115            .map(|t| t.span)
116            .unwrap_or_else(|| self.eof_span())
117    }
118
119    /// A span running from the start of `from` to the end of `to`, in this
120    /// parser's file.
121    fn span_from(&self, from: Span, to: Span) -> Span {
122        Span::new(self.file, from.start, to.end)
123    }
124
125    /// Consumes a path-start keyword (`crate`, `self`, `super`) and the
126    /// `::` right after it, when the current token is one of the three
127    /// AND is immediately followed by `::` (spec 3.1, 3.3); a keyword used
128    /// any other way (a method receiver, a bare misuse) is left alone, for
129    /// the caller to handle as it already does. Consumes nothing and
130    /// returns [`PathStart::None`] otherwise.
131    fn take_path_start(&mut self) -> PathStart {
132        let leading = match self.peek() {
133            Some(TokenKind::CrateKw) => PathStart::Crate,
134            Some(TokenKind::SelfKw) => PathStart::SelfMod,
135            Some(TokenKind::SuperKw) => PathStart::Super,
136            _ => return PathStart::None,
137        };
138        if self.peek_at(1) != Some(&TokenKind::ColonColon) {
139            return PathStart::None;
140        }
141        self.bump(); // the keyword
142        self.bump(); // `::`
143        leading
144    }
145
146    /// Consumes the current token if its kind equals `kind`, without
147    /// recording an error either way if it does not match.
148    fn bump_if(&mut self, kind: &TokenKind) -> bool {
149        if self.peek() == Some(kind) {
150            self.bump();
151            true
152        } else {
153            false
154        }
155    }
156
157    /// Runs `f` with the `in_condition` flag temporarily cleared,
158    /// restoring it afterwards regardless of whether `f` succeeds. Used
159    /// wherever a nested context — parentheses, call arguments, a block's
160    /// body — already disambiguates struct-literal syntax on its own, so
161    /// the enclosing condition's suppression must not leak into it (a
162    /// leak would make `if f(Point { x: 1 }) { }` or
163    /// `if { Point { x: 1 } } { }` fail to parse, even though the
164    /// parentheses/braces already remove the ambiguity `if`'s own
165    /// condition has).
166    fn without_condition<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
167        let was_in_condition = self.in_condition;
168        self.in_condition = false;
169        let result = f(self);
170        self.in_condition = was_in_condition;
171        result
172    }
173
174    fn push_error(&mut self, code: &'static str, span: Span, message: impl Into<String>) {
175        self.errors.push(SyntaxError::new(span, code, message));
176    }
177
178    fn push_error_with_fix_it(
179        &mut self,
180        code: &'static str,
181        span: Span,
182        message: impl Into<String>,
183        fix_it: FixIt,
184    ) {
185        self.errors
186            .push(SyntaxError::new(span, code, message).with_fix_it(fix_it));
187    }
188
189    /// Records `V0002` at the current token, "expected `what`", or
190    /// [`STRAY_HASH`] when that token is a `#`.
191    fn push_expected(&mut self, what: &str) {
192        let span = self.current_span();
193        if self.peek() == Some(&TokenKind::Hash) {
194            self.push_error(V0002, span, STRAY_HASH);
195        } else {
196            self.push_error(V0002, span, format!("expected {what}"));
197        }
198    }
199
200    /// Consumes the current token if its kind equals `expected`, recording
201    /// `V0002` and returning `Err` otherwise.
202    fn expect(&mut self, expected: TokenKind, what: &str) -> Result<Token, ()> {
203        if self.peek() == Some(&expected) {
204            Ok(self.bump().expect("peek just confirmed a token is present"))
205        } else {
206            self.push_expected(what);
207            Err(())
208        }
209    }
210
211    /// Consumes an [`crate::token::TokenKind::Identifier`], recording
212    /// `V0002` and returning `Err` for anything else, or `V0001` for a
213    /// reserved Rust keyword (spec 4.1) or for `self` (spec 2.5): `self` is
214    /// a keyword everywhere except a method's own receiver position, which
215    /// `item.rs`'s `parse_self_receiver` reads before this function ever
216    /// sees it.
217    fn expect_identifier(&mut self, what: &str) -> Result<crate::ast::Ident, ()> {
218        // `as` is a Varyk keyword but not a Varyk name, so it reads as the
219        // Rust keyword it also is.
220        let keyword = match self.peek() {
221            Some(TokenKind::ReservedKeyword(word)) => Some(word.as_str()),
222            Some(TokenKind::As) => Some("as"),
223            Some(TokenKind::Async) => Some("async"),
224            Some(TokenKind::Await) => Some("await"),
225            _ => None,
226        };
227        if let Some(word) = keyword {
228            let message =
229                format!("`{word}` is a Rust keyword and cannot be used as a name in Varyk");
230            let span = self.current_span();
231            self.bump();
232            self.push_error(V0001, span, message);
233            return Err(());
234        }
235        match self.peek() {
236            Some(TokenKind::SelfKw) => {
237                let span = self.current_span();
238                self.bump();
239                self.push_error(
240                    V0001,
241                    span,
242                    "`self` is a keyword and can only be used as a method's first parameter, \
243                     or to start a path (`self::name`)",
244                );
245                Err(())
246            }
247            Some(TokenKind::CrateKw) => {
248                let span = self.current_span();
249                self.bump();
250                self.push_error(
251                    V0001,
252                    span,
253                    "`crate` is a keyword and can only start a path, followed by `::`",
254                );
255                Err(())
256            }
257            Some(TokenKind::SuperKw) => {
258                let span = self.current_span();
259                let after_super = self.pos >= 2
260                    && self.tokens[self.pos - 1].kind == TokenKind::ColonColon
261                    && self.tokens[self.pos - 2].kind == TokenKind::SuperKw;
262                self.bump();
263                let message = if after_super {
264                    "`super::super` is not supported; write the path from `crate::` instead"
265                } else {
266                    "`super` is a keyword and can only start a path, followed by `::`"
267                };
268                self.push_error(V0001, span, message);
269                Err(())
270            }
271            Some(TokenKind::UseKw) => {
272                let span = self.current_span();
273                self.bump();
274                self.push_error(
275                    V0001,
276                    span,
277                    "`use` is a keyword and can only start a `use` item",
278                );
279                Err(())
280            }
281            Some(TokenKind::Identifier(_)) => {
282                let token = self.bump().expect("peek just confirmed a token is present");
283                let name = match token.kind {
284                    TokenKind::Identifier(name) => name,
285                    _ => unreachable!("matched above"),
286                };
287                Ok(crate::ast::Ident {
288                    name,
289                    span: token.span,
290                })
291            }
292            _ => {
293                self.push_expected(what);
294                Err(())
295            }
296        }
297    }
298
299    /// Like [`Parser::expect_identifier`], but also rejects `_`: valid as a
300    /// `let` binding's discard, but not as a function, struct, module,
301    /// field, or parameter name (rustc itself rejects `fn _`).
302    fn expect_name_identifier(&mut self, what: &str) -> Result<crate::ast::Ident, ()> {
303        let ident = self.expect_identifier(what)?;
304        if ident.name == "_" {
305            self.push_error(V0002, ident.span, "`_` cannot be used as a name here");
306            return Err(());
307        }
308        Ok(ident)
309    }
310}