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