Skip to main content

nifty_syntax/parser/
mod.rs

1//! Recursive-descent parser. On an error it records a diagnostic, skips to the end of the
2//! line and carries on, so one mistake reports once instead of cascading.
3
4mod expr;
5mod item;
6mod stmt;
7#[cfg(test)]
8mod tests;
9
10use crate::ast::{File, Ident};
11use crate::diagnostic::Diagnostic;
12use crate::lexer;
13use crate::span::{FileId, Span};
14use crate::token::{Token, TokenKind};
15
16/// The diagnostic is already recorded; the caller should recover.
17#[derive(Debug)]
18struct Reported;
19
20type ParseResult<T> = Result<T, Reported>;
21
22/// Lexes and parses `source`, returning the tree and every diagnostic in source order.
23pub(crate) fn parse(source: &str, file: FileId) -> (File, Vec<Diagnostic>) {
24    let (tokens, mut diagnostics) = lexer::lex(source, file);
25    let mut parser = Parser {
26        file,
27        source,
28        tokens,
29        pos: 0,
30        diagnostics: Vec::new(),
31    };
32    let file = parser.parse_file();
33    diagnostics.extend(parser.diagnostics);
34    diagnostics.sort_by_key(|diagnostic| diagnostic.span.start);
35    (file, diagnostics)
36}
37
38struct Parser<'src> {
39    file: FileId,
40    source: &'src str,
41    /// Always ends with `Eof`, and `bump` never moves past it.
42    tokens: Vec<Token>,
43    pos: usize,
44    diagnostics: Vec<Diagnostic>,
45}
46
47impl Parser<'_> {
48    fn parse_file(&mut self) -> File {
49        let mut items = Vec::new();
50        loop {
51            self.skip_newlines();
52            match self.peek() {
53                TokenKind::Eof => return File { items },
54                TokenKind::RBrace => {
55                    let token = self.bump();
56                    self.report("this `}` has no matching `{`", token.span);
57                }
58                _ => match self.parse_item() {
59                    Ok(item) => {
60                        items.push(item);
61                        self.finish_line();
62                    }
63                    Err(Reported) => self.recover_to_line_end(),
64                },
65            }
66        }
67    }
68
69    fn current(&self) -> Token {
70        self.tokens[self.pos]
71    }
72
73    fn peek(&self) -> TokenKind {
74        self.current().kind
75    }
76
77    fn peek_nth(&self, n: usize) -> TokenKind {
78        self.tokens
79            .get(self.pos + n)
80            .map_or(TokenKind::Eof, |token| token.kind)
81    }
82
83    fn at(&self, kind: TokenKind) -> bool {
84        self.peek() == kind
85    }
86
87    fn bump(&mut self) -> Token {
88        let token = self.current();
89        if token.kind != TokenKind::Eof {
90            self.pos += 1;
91        }
92        token
93    }
94
95    fn eat(&mut self, kind: TokenKind) -> Option<Token> {
96        self.at(kind).then(|| self.bump())
97    }
98
99    fn expect(&mut self, kind: TokenKind) -> ParseResult<Token> {
100        if let Some(token) = self.eat(kind) {
101            return Ok(token);
102        }
103        self.unexpected(&kind.describe())
104    }
105
106    fn expect_ident(&mut self) -> ParseResult<Ident> {
107        let token = self.expect(TokenKind::Ident)?;
108        Ok(Ident {
109            name: self.text(token.span).to_owned(),
110            span: token.span,
111        })
112    }
113
114    /// Reports "expected {expected}, found …" at the current token.
115    fn unexpected<T>(&mut self, expected: &str) -> ParseResult<T> {
116        let found = self.current();
117        Err(self.report(
118            format!("expected {expected}, found {}", found.kind.describe()),
119            found.span,
120        ))
121    }
122
123    fn report(&mut self, message: impl Into<String>, span: Span) -> Reported {
124        self.diagnostics.push(Diagnostic::new(message, span));
125        Reported
126    }
127
128    fn text(&self, span: Span) -> &str {
129        &self.source[span.range()]
130    }
131
132    fn skip_newlines(&mut self) {
133        while self.eat(TokenKind::Newline).is_some() {}
134    }
135
136    /// The span from `start` to the end of the last consumed token.
137    fn span_from(&self, start: usize) -> Span {
138        let end = self
139            .pos
140            .checked_sub(1)
141            .map_or(start, |previous| self.tokens[previous].span.end);
142        Span::new(self.file, start, end)
143    }
144
145    /// A statement must end at a line break, a closing `}` or the end of the file.
146    fn finish_line(&mut self) {
147        match self.peek() {
148            TokenKind::Newline => {
149                self.bump();
150            }
151            TokenKind::RBrace | TokenKind::Eof => {}
152            _ => {
153                let token = self.current();
154                self.report(
155                    format!(
156                        "expected end of line, found {}; put each statement on its own line",
157                        token.kind.describe()
158                    ),
159                    token.span,
160                );
161                self.recover_to_line_end();
162            }
163        }
164    }
165
166    /// Skips to the next line break or the `}` closing the current block, ignoring any
167    /// nested in brackets along the way.
168    fn recover_to_line_end(&mut self) {
169        let mut depth = 0usize;
170        loop {
171            let kind = self.peek();
172            match kind {
173                TokenKind::Eof => return,
174                TokenKind::Newline | TokenKind::RBrace if depth == 0 => return,
175                TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => depth += 1,
176                TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
177                    depth = depth.saturating_sub(1);
178                }
179                _ => {}
180            }
181            self.bump();
182        }
183    }
184
185    /// Parses `item (, item)* ,? close` after the opening bracket; newlines are free inside.
186    fn comma_list<T>(
187        &mut self,
188        close: TokenKind,
189        mut item: impl FnMut(&mut Self) -> ParseResult<T>,
190    ) -> ParseResult<Vec<T>> {
191        let mut items = Vec::new();
192        loop {
193            self.skip_newlines();
194            if self.eat(close).is_some() {
195                return Ok(items);
196            }
197            items.push(item(self)?);
198            self.skip_newlines();
199            if self.eat(TokenKind::Comma).is_none() {
200                self.expect(close)?;
201                return Ok(items);
202            }
203        }
204    }
205}