Skip to main content

presk_syntax/parser/
mod.rs

1//! Recursive-descent parser with Pratt expressions.
2//!
3//! Layout: the token stream contains `Newline` tokens. Inside `(`, `[` and
4//! dimension-argument `{…}` the parser is in *free* mode and newlines are
5//! skipped; in function bodies, module bodies and configuration blocks it is
6//! in *lines* mode, where an expression ends at a newline unless the next
7//! token is a binary operator that cannot begin an expression (see
8//! `expr.rs`). Declarations recover at the next declaration keyword at
9//! column 0.
10
11mod config;
12mod decl;
13mod expr;
14mod ty;
15
16use crate::ast::{Ident, Program};
17use crate::diag::Diagnostic;
18use crate::lexer;
19use crate::source::{FileId, SourceMap, Span};
20use crate::token::{Kw, Op, TokKind, Token};
21
22/// Parse one file. Lexer and parser diagnostics are returned together; the
23/// program is returned even when there are errors (with the failing
24/// declarations dropped) so that later phases can still report on the rest.
25pub fn parse_file(sm: &SourceMap, file: FileId) -> (Program, Vec<Diagnostic>) {
26    let lexed = lexer::lex(file, sm.text(file));
27    let mut p = Parser { toks: lexed.tokens, pos: 0, free: 0, diags: lexed.diags, file, line_starts: line_starts(sm.text(file)) };
28    let decls = p.program();
29    (Program { file, decls }, p.diags)
30}
31
32/// Parse a file holding exactly one type (`(idx[64][seq: 8], idx[64][seq: 8])`),
33/// as written in the header of a `.psk.d` data file.
34pub fn parse_type(sm: &SourceMap, file: FileId) -> (Option<crate::ast::Ty>, Vec<Diagnostic>) {
35    let lexed = lexer::lex(file, sm.text(file));
36    let mut p = Parser { toks: lexed.tokens, pos: 0, free: 1, diags: lexed.diags, file, line_starts: line_starts(sm.text(file)) };
37    p.skip_newlines();
38    let ty = p.ty().ok();
39    p.skip_newlines();
40    if ty.is_some() && !matches!(p.peek_kind(), TokKind::Eof) {
41        let span = p.peek().span;
42        let found = p.peek_kind();
43        p.error_at(span, format!("expected end of type, found {found}"), "unexpected here");
44    }
45    (ty, p.diags)
46}
47
48fn line_starts(text: &str) -> Vec<u32> {
49    let mut v = vec![0];
50    for (i, b) in text.bytes().enumerate() {
51        if b == b'\n' {
52            v.push(i as u32 + 1);
53        }
54    }
55    v
56}
57
58/// Marker error: the diagnostic has already been recorded.
59#[derive(Debug)]
60pub(crate) struct ParseError;
61
62pub(crate) type PResult<T> = Result<T, ParseError>;
63
64pub(crate) struct Parser {
65    toks: Vec<Token>,
66    pos: usize,
67    /// Depth of brackets inside which newlines are insignificant.
68    free: u32,
69    diags: Vec<Diagnostic>,
70    file: FileId,
71    line_starts: Vec<u32>,
72}
73
74impl Parser {
75    // ----- token access -------------------------------------------------
76
77    /// Next token, skipping newlines in free mode.
78    pub(crate) fn peek(&mut self) -> &Token {
79        if self.free > 0 {
80            while matches!(self.toks[self.pos].kind, TokKind::Newline) {
81                self.pos += 1;
82            }
83        }
84        &self.toks[self.pos]
85    }
86
87    pub(crate) fn peek_kind(&mut self) -> TokKind {
88        self.peek().kind.clone()
89    }
90
91    /// Token at `pos + n`, ignoring newlines in free mode.
92    pub(crate) fn peek_nth(&mut self, n: usize) -> &Token {
93        self.peek();
94        let mut i = self.pos;
95        let mut left = n;
96        while left > 0 {
97            i += 1;
98            if self.free > 0 {
99                while matches!(self.toks[i].kind, TokKind::Newline) {
100                    i += 1;
101                }
102            }
103            left -= 1;
104        }
105        &self.toks[i.min(self.toks.len() - 1)]
106    }
107
108    pub(crate) fn bump(&mut self) -> Token {
109        self.peek();
110        let t = self.toks[self.pos].clone();
111        if !matches!(t.kind, TokKind::Eof) {
112            self.pos += 1;
113        }
114        t
115    }
116
117    pub(crate) fn at(&mut self, kind: &TokKind) -> bool {
118        &self.peek().kind == kind
119    }
120
121    pub(crate) fn at_op(&mut self, op: Op) -> bool {
122        self.at(&TokKind::Op(op))
123    }
124
125    pub(crate) fn at_kw(&mut self, kw: Kw) -> bool {
126        self.at(&TokKind::Kw(kw))
127    }
128
129    /// Contextual keyword: an identifier with this exact text.
130    pub(crate) fn at_word(&mut self, word: &str) -> bool {
131        matches!(&self.peek().kind, TokKind::Ident(s) if s == word)
132    }
133
134    pub(crate) fn eat_op(&mut self, op: Op) -> Option<Token> {
135        if self.at_op(op) { Some(self.bump()) } else { None }
136    }
137
138    pub(crate) fn eat_kw(&mut self, kw: Kw) -> Option<Token> {
139        if self.at_kw(kw) { Some(self.bump()) } else { None }
140    }
141
142    pub(crate) fn expect_op(&mut self, op: Op) -> PResult<Token> {
143        if let Some(t) = self.eat_op(op) { Ok(t) } else { Err(self.unexpected(&format!("`{}`", op.text()))) }
144    }
145
146    pub(crate) fn expect_kw(&mut self, kw: Kw) -> PResult<Token> {
147        if let Some(t) = self.eat_kw(kw) { Ok(t) } else { Err(self.unexpected(&format!("`{}`", kw.text()))) }
148    }
149
150    pub(crate) fn expect_word(&mut self, word: &str) -> PResult<Token> {
151        if self.at_word(word) { Ok(self.bump()) } else { Err(self.unexpected(&format!("`{word}`"))) }
152    }
153
154    pub(crate) fn expect_ident(&mut self, what: &str) -> PResult<Ident> {
155        match self.peek_kind() {
156            TokKind::Ident(name) => {
157                let t = self.bump();
158                Ok(Ident { name, span: t.span })
159            }
160            _ => Err(self.unexpected(what)),
161        }
162    }
163
164    /// Skip newlines regardless of mode (between declarations, after `=`).
165    pub(crate) fn skip_newlines(&mut self) {
166        while matches!(self.toks[self.pos].kind, TokKind::Newline) {
167            self.pos += 1;
168        }
169    }
170
171    pub(crate) fn at_newline(&self) -> bool {
172        matches!(self.toks[self.pos].kind, TokKind::Newline)
173    }
174
175    /// Run `f` with newlines insignificant.
176    pub(crate) fn free<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
177        self.free += 1;
178        let r = f(self);
179        self.free -= 1;
180        r
181    }
182
183    /// Run `f` in lines mode even inside brackets (module bodies, config blocks).
184    pub(crate) fn lines<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
185        let saved = self.free;
186        self.free = 0;
187        let r = f(self);
188        self.free = saved;
189        r
190    }
191
192    pub(crate) fn prev_span(&self) -> Span {
193        self.toks[self.pos.saturating_sub(1)].span
194    }
195
196    pub(crate) fn column(&self, span: Span) -> u32 {
197        let line = self.line_starts.partition_point(|&s| s <= span.start).max(1) - 1;
198        span.start - self.line_starts[line]
199    }
200
201    // ----- errors -------------------------------------------------------
202
203    pub(crate) fn unexpected(&mut self, expected: &str) -> ParseError {
204        let t = self.peek().clone();
205        let found = t.kind.to_string();
206        let span = if matches!(t.kind, TokKind::Newline | TokKind::Eof) { Span::new(self.file, t.span.start as usize, t.span.start as usize) } else { t.span };
207        self.diags.push(
208            Diagnostic::error("E0010", format!("expected {expected}, found {found}"))
209                .label(span, format!("expected {expected} here")),
210        );
211        ParseError
212    }
213
214    pub(crate) fn error_at(&mut self, span: Span, message: impl Into<String>, label: impl Into<String>) -> ParseError {
215        self.diags.push(Diagnostic::error("E0011", message).label(span, label));
216        ParseError
217    }
218}