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