Skip to main content

wolf_parser/
lib.rs

1//! The wolfpack parser: tokens to a syntax tree (spec chapters 2 and 3).
2//!
3//! Recursive descent for declarations and statements, a Pratt parser for expressions. The parser
4//! checks the file skeleton (chapter 2) while it reads the sections. It never stops at an error:
5//! it reports the problem, skips to a safe point (the end of the line or a closing delimiter) and
6//! continues, so that all errors of a file are shown at once. It never panics, and a nesting limit
7//! (E0314) keeps deep input from overflowing the stack.
8
9mod expr;
10mod items;
11mod skeleton;
12mod ty;
13
14use wolf_ast::{File, NodeId};
15use wolf_diag::{Diagnostic, codes};
16use wolf_lexer::{Token, TokenKind};
17use wolf_span::{FileId, Span};
18
19/// The deepest nesting of expressions, statements, types and patterns the parser accepts.
20pub const MAX_DEPTH: u32 = 256;
21
22/// The stack size needed to parse, print and drop a tree nested up to [`MAX_DEPTH`] in a debug
23/// build, with a wide margin. The `wolf` binary runs the compiler on a thread of this size;
24/// other users of the parser should do the same.
25pub const STACK_SIZE: usize = 64 * 1024 * 1024;
26
27/// The result of parsing one file.
28#[derive(Debug)]
29pub struct Parsed {
30    /// The syntax tree, possibly with error nodes.
31    pub file: File,
32    /// Syntax errors, in the order they were found.
33    pub diagnostics: Vec<Diagnostic>,
34}
35
36/// Parses the tokens of one file, as produced by `wolf_lexer::lex`.
37pub fn parse(file: FileId, text: &str, tokens: &[Token]) -> Parsed {
38    let mut parser = Parser::new(file, text, tokens);
39    let tree = parser.file();
40    let too_deep = parser.depth_reported;
41    let mut diagnostics = parser.diagnostics;
42    if !too_deep {
43        diagnostics.extend(skeleton::check(file, &tree));
44    }
45    Parsed {
46        file: tree,
47        diagnostics,
48    }
49}
50
51/// Whether newlines are significant in the current delimiter (spec chapter 1, section 7).
52#[derive(Clone, Copy, PartialEq, Eq)]
53enum Lines {
54    /// Inside a block, a `match`, a declaration or at the top level: a newline ends things.
55    Significant,
56    /// Inside `( )`, `[ ]` or the braces of a literal or pattern: newlines are ignored.
57    Ignored,
58}
59
60pub(crate) struct Parser<'a> {
61    file: FileId,
62    text: &'a str,
63    /// The tokens; the parser may split `>>` into two `>` tokens in type context.
64    tokens: Vec<Token>,
65    pos: usize,
66    lines: Vec<Lines>,
67    pub(crate) diagnostics: Vec<Diagnostic>,
68    next_id: u32,
69    depth: u32,
70    depth_reported: bool,
71    /// Set when a section header was found inside a block: every open block closes at once.
72    pub(crate) resync_to_top: bool,
73    /// Set when an `Error` token from the lexer was consumed. The rest of the line is skipped
74    /// without further messages, because the lexer already reported the problem.
75    poisoned: bool,
76    /// Error count at the start of the current section entry.
77    pub(crate) line_errors: usize,
78    /// The labels of the enclosing loops of the current function or closure body; a name after
79    /// `break` is a label when it is one of them (spec chapter 3, section 4, rule 5).
80    pub(crate) labels: Vec<String>,
81}
82
83impl<'a> Parser<'a> {
84    fn new(file: FileId, text: &'a str, tokens: &[Token]) -> Parser<'a> {
85        let mut tokens = tokens.to_vec();
86        let ends_with_eof = tokens.last().is_some_and(|t| t.kind == TokenKind::Eof);
87        if !ends_with_eof {
88            let end = u32::try_from(text.len()).unwrap_or(u32::MAX);
89            tokens.push(Token {
90                kind: TokenKind::Eof,
91                span: Span::new(file, end, end),
92            });
93        }
94        Parser {
95            file,
96            text,
97            tokens,
98            pos: 0,
99            lines: vec![Lines::Significant],
100            diagnostics: Vec::new(),
101            next_id: 0,
102            depth: 0,
103            depth_reported: false,
104            resync_to_top: false,
105            poisoned: false,
106            line_errors: 0,
107            labels: Vec::new(),
108        }
109    }
110
111    // ----- token access ----------------------------------------------------------------------
112
113    /// The token `n` positions ahead of `pos`, without skipping anything. Clamped to `Eof`.
114    fn raw(&self, n: usize) -> &Token {
115        let index = (self.pos + n).min(self.tokens.len().saturating_sub(1));
116        // `tokens` always ends with Eof, so it is never empty.
117        match self.tokens.get(index) {
118            Some(token) => token,
119            None => &EOF_TOKEN,
120        }
121    }
122
123    fn ignoring_lines(&self) -> bool {
124        self.lines.last() == Some(&Lines::Ignored)
125    }
126
127    /// Index of the `n`-th next token that is visible in the current newline mode.
128    fn visible_index(&self, n: usize) -> usize {
129        let mut index = self.pos;
130        let mut seen = 0;
131        loop {
132            let token = self.tokens.get(index);
133            let skip = self.ignoring_lines() && token.is_some_and(|t| t.kind == TokenKind::Newline);
134            if !skip {
135                if seen == n || token.is_none_or(|t| t.kind == TokenKind::Eof) {
136                    return index.min(self.tokens.len().saturating_sub(1));
137                }
138                seen += 1;
139            }
140            index += 1;
141        }
142    }
143
144    /// The `n`-th next visible token.
145    pub(crate) fn nth(&self, n: usize) -> &Token {
146        match self.tokens.get(self.visible_index(n)) {
147            Some(token) => token,
148            None => &EOF_TOKEN,
149        }
150    }
151
152    pub(crate) fn peek(&self) -> &TokenKind {
153        &self.nth(0).kind
154    }
155
156    pub(crate) fn peek_span(&self) -> Span {
157        self.nth(0).span
158    }
159
160    /// Whether the next visible token has the same kind as `kind` (literal values are ignored).
161    pub(crate) fn at(&self, kind: &TokenKind) -> bool {
162        same_kind(self.peek(), kind)
163    }
164
165    pub(crate) fn nth_is(&self, n: usize, kind: &TokenKind) -> bool {
166        same_kind(&self.nth(n).kind, kind)
167    }
168
169    /// Whether the next visible token is the identifier `word` (a contextual keyword).
170    pub(crate) fn at_word(&self, word: &str) -> bool {
171        self.nth_word(0, word)
172    }
173
174    pub(crate) fn nth_word(&self, n: usize, word: &str) -> bool {
175        let token = self.nth(n);
176        token.kind == TokenKind::Ident && self.source(token.span) == word
177    }
178
179    /// Consumes the next visible token and returns it.
180    pub(crate) fn bump(&mut self) -> Token {
181        let index = self.visible_index(0);
182        let token = self.tokens.get(index).cloned().unwrap_or(EOF_TOKEN.clone());
183        if token.kind == TokenKind::Error {
184            self.poisoned = true;
185        }
186        if token.kind != TokenKind::Eof {
187            self.pos = index + 1;
188        } else {
189            self.pos = index;
190        }
191        token
192    }
193
194    pub(crate) fn eat(&mut self, kind: &TokenKind) -> bool {
195        if self.at(kind) {
196            self.bump();
197            true
198        } else {
199            false
200        }
201    }
202
203    /// Consumes `kind` and then any newlines: after `.`, `:`, `->`, `=` and binary operators a
204    /// line break does not end anything (spec chapter 1, section 7, rule 2).
205    pub(crate) fn eat_then_lines(&mut self, kind: &TokenKind) -> bool {
206        if self.eat(kind) {
207            self.skip_newlines();
208            true
209        } else {
210            false
211        }
212    }
213
214    /// Like [`Parser::expect`], then skips newlines.
215    pub(crate) fn expect_then_lines(&mut self, kind: &TokenKind, what: &str) -> Option<Span> {
216        let span = self.expect(kind, what)?;
217        self.skip_newlines();
218        Some(span)
219    }
220
221    pub(crate) fn eat_word(&mut self, word: &str) -> bool {
222        if self.at_word(word) {
223            self.bump();
224            true
225        } else {
226            false
227        }
228    }
229
230    /// Consumes `kind` or reports E0301. Returns the span of the token when it was there.
231    pub(crate) fn expect(&mut self, kind: &TokenKind, what: &str) -> Option<Span> {
232        if self.at(kind) {
233            return Some(self.bump().span);
234        }
235        self.unexpected(what);
236        None
237    }
238
239    /// Skips newline tokens, even where they are significant.
240    pub(crate) fn skip_newlines(&mut self) {
241        while self.raw(0).kind == TokenKind::Newline {
242            self.pos += 1;
243        }
244    }
245
246    /// Whether the next raw token ends the current line (or the file).
247    pub(crate) fn at_line_end(&self) -> bool {
248        matches!(self.raw(0).kind, TokenKind::Newline | TokenKind::Eof)
249    }
250
251    pub(crate) fn source(&self, span: Span) -> &'a str {
252        self.text.get(span.range()).unwrap_or("")
253    }
254
255    pub(crate) fn prev_span(&self) -> Span {
256        let index = self.pos.saturating_sub(1);
257        self.tokens
258            .get(index)
259            .map(|t| t.span)
260            .unwrap_or(Span::new(self.file, 0, 0))
261    }
262
263    pub(crate) fn span_from(&self, start: Span) -> Span {
264        let end = self.prev_span();
265        if end.end < start.start {
266            start
267        } else {
268            start.to(end)
269        }
270    }
271
272    pub(crate) fn with_lines<T>(&mut self, mode: Lines, f: impl FnOnce(&mut Self) -> T) -> T {
273        self.lines.push(mode);
274        let result = f(self);
275        self.lines.pop();
276        result
277    }
278
279    pub(crate) fn ignore_lines<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
280        self.with_lines(Lines::Ignored, f)
281    }
282
283    pub(crate) fn significant_lines<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
284        self.with_lines(Lines::Significant, f)
285    }
286
287    pub(crate) fn id(&mut self) -> NodeId {
288        let id = NodeId(self.next_id);
289        self.next_id += 1;
290        id
291    }
292
293    // ----- errors -----------------------------------------------------------------------------
294
295    pub(crate) fn report(&mut self, diagnostic: Diagnostic) {
296        // After the nesting limit the parser skips to the end; anything else would be noise.
297        if self.depth_reported && diagnostic.code != codes::E0314 {
298            return;
299        }
300        self.diagnostics.push(diagnostic);
301    }
302
303    /// Reports E0301 at the next token, unless it is an `Error` token (already reported by the
304    /// lexer) or the nesting limit was hit.
305    pub(crate) fn unexpected(&mut self, expected: &str) {
306        let token = self.nth(0).clone();
307        if token.kind == TokenKind::Error || self.depth_reported || self.poisoned {
308            return;
309        }
310        let found = describe(&token, self.source(token.span));
311        // Point at the end of the line, not at the line break itself.
312        let span = if token.kind == TokenKind::Newline {
313            Span::new(token.span.file, token.span.start, token.span.start)
314        } else {
315            token.span
316        };
317        self.report(
318            Diagnostic::new(codes::E0301, format!("expected {expected}, found {found}"))
319                .primary(span, format!("expected {expected}")),
320        );
321    }
322
323    /// Ends the current line after a statement or entry. If the line contained a lexer error,
324    /// the rest of it is skipped silently and `true` is returned.
325    pub(crate) fn end_line_if_poisoned(&mut self) -> bool {
326        if !self.poisoned {
327            return false;
328        }
329        self.recover();
330        self.poisoned = false;
331        true
332    }
333
334    /// Clears the poisoned state at the start of a line and remembers how many errors there
335    /// were, so the end of the line can tell whether this line already reported one.
336    pub(crate) fn start_line(&mut self) -> usize {
337        self.poisoned = false;
338        self.diagnostics.len()
339    }
340
341    /// At the end of a statement or entry: if the line already has an error, or the next token
342    /// is a range (E0307), handle that and skip the rest of the line. Returns `true` if handled.
343    pub(crate) fn end_line_after_error(&mut self, errors_before: usize) -> bool {
344        // A section header was found inside a block: stay right there.
345        if self.resync_to_top {
346            return true;
347        }
348        if self.end_line_if_poisoned() {
349            return true;
350        }
351        if matches!(self.raw(0).kind, TokenKind::DotDot | TokenKind::DotDotEq) {
352            let span = self.raw(0).span;
353            self.report(
354                Diagnostic::new(codes::E0307, "range outside `for` or index brackets").primary(
355                    span,
356                    "ranges are allowed only in `for i in a..b` and `items[a..b]`",
357                ),
358            );
359            self.recover();
360            return true;
361        }
362        if self.diagnostics.len() > errors_before {
363            self.recover();
364            return true;
365        }
366        false
367    }
368
369    /// Skips to a safe point: the next newline outside nested delimiters, or before a closing
370    /// delimiter that belongs to an enclosing construct.
371    pub(crate) fn recover(&mut self) {
372        let mut depth = 0u32;
373        loop {
374            let kind = self.raw(0).kind.clone();
375            match kind {
376                TokenKind::Eof => return,
377                TokenKind::Newline if depth == 0 && !self.ignoring_lines() => return,
378                TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => depth += 1,
379                TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
380                    if depth == 0 {
381                        return;
382                    }
383                    depth -= 1;
384                }
385                _ => {}
386            }
387            self.pos += 1;
388        }
389    }
390
391    /// Enters one nesting level. Returns `false` (and reports E0314 once) past the limit.
392    pub(crate) fn enter(&mut self) -> bool {
393        self.depth += 1;
394        if self.depth <= MAX_DEPTH {
395            return true;
396        }
397        if !self.depth_reported {
398            self.depth_reported = true;
399            let span = self.peek_span();
400            self.report(
401                Diagnostic::new(codes::E0314, "nesting too deep")
402                    .primary(span, format!("more than {MAX_DEPTH} nested levels"))
403                    .help("split the expression or the code into smaller parts"),
404            );
405        }
406        false
407    }
408
409    pub(crate) fn leave(&mut self) {
410        self.depth = self.depth.saturating_sub(1);
411    }
412
413    /// Skips everything up to the end of the file. Used after the nesting limit was hit.
414    pub(crate) fn give_up(&mut self) {
415        self.pos = self.tokens.len().saturating_sub(1);
416    }
417
418    pub(crate) fn too_deep(&self) -> bool {
419        self.depth_reported
420    }
421}
422
423static EOF_TOKEN: Token = Token {
424    kind: TokenKind::Eof,
425    span: Span {
426        file: FILE_ZERO,
427        start: 0,
428        end: 0,
429    },
430};
431
432const FILE_ZERO: FileId = wolf_span::FileId::ZERO;
433
434/// Compares token kinds without looking at literal values.
435pub(crate) fn same_kind(a: &TokenKind, b: &TokenKind) -> bool {
436    std::mem::discriminant(a) == std::mem::discriminant(b)
437}
438
439/// "identifier `foo`", "`}`", "new line" for messages.
440pub(crate) fn describe(token: &Token, source: &str) -> String {
441    match token.kind {
442        TokenKind::Ident => format!("identifier `{source}`"),
443        TokenKind::Int(_) | TokenKind::Float(_) | TokenKind::Str(_) => format!("`{source}`"),
444        _ => token.kind.to_string(),
445    }
446}