Skip to main content

yog_script_analyze/parser/
mod.rs

1//! The parser core: a cursor over tokens, problems and recovery, and the
2//! turn-taking — each form is parsed by its word (`words/`) or by a generic
3//! form here (types, members, blocks, expressions by precedence).
4
5mod await_using;
6mod block;
7mod decl;
8pub(crate) mod expr;
9mod header;
10mod lambda;
11mod member;
12mod params;
13pub(crate) mod patch;
14pub(crate) mod pattern;
15mod postfix;
16mod primary;
17mod query;
18mod types;
19
20use yog_ids::script_problem;
21
22use crate::source::token::{Punct, Span, Tok, Token};
23use crate::words::{Starts, Word};
24use yog_analysis::Report;
25
26/// A form that didn't parse: its problem is reported already.
27#[derive(Debug)]
28pub struct Fail;
29
30pub type PResult<T> = Result<T, Fail>;
31
32pub struct Parser<'t, 'r, 'a> {
33    toks: &'t [Token],
34    pos: usize,
35    pub(crate) report: &'r mut Report<'a>,
36    /// Patches are allowed (a `.yns` source).
37    pub(crate) nanites: bool,
38    /// A `{` here ends the expression (a system's period, a guard) instead
39    /// of starting a trailing lambda.
40    no_brace: bool,
41}
42
43impl<'t, 'r, 'a> Parser<'t, 'r, 'a> {
44    /// Over `toks`, which end with `Eof`.
45    pub fn new(toks: &'t [Token], report: &'r mut Report<'a>, nanites: bool) -> Self {
46        Parser {
47            toks,
48            pos: 0,
49            report,
50            nanites,
51            no_brace: false,
52        }
53    }
54
55    /// How many tokens are read.
56    pub(crate) fn consumed(&self) -> usize {
57        self.pos
58    }
59
60    pub(crate) fn tok(&self) -> &'t Tok {
61        self.nth(0)
62    }
63
64    /// The token `n` ahead (the last, `Eof`, past the end).
65    pub(crate) fn nth(&self, n: usize) -> &'t Tok {
66        let last = self.toks.len().saturating_sub(1);
67        &self.toks[(self.pos + n).min(last)].tok
68    }
69
70    pub(crate) fn span(&self) -> Span {
71        self.span_at(0)
72    }
73
74    /// Where the token `n` ahead is.
75    pub(crate) fn span_at(&self, n: usize) -> Span {
76        self.toks[(self.pos + n).min(self.toks.len() - 1)].span
77    }
78
79    /// Where the token before this one ends.
80    pub(crate) fn prev(&self) -> Span {
81        self.pos
82            .checked_sub(1)
83            .map_or_else(|| self.span(), |i| self.toks[i].span)
84    }
85
86    pub(crate) fn bump(&mut self) -> &'t Token {
87        let t = &self.toks[self.pos.min(self.toks.len() - 1)];
88        if t.tok != Tok::Eof {
89            self.pos += 1;
90        }
91        t
92    }
93
94    pub(crate) fn at(&self, p: Punct) -> bool {
95        *self.tok() == Tok::Punct(p)
96    }
97
98    /// Whether `p` comes next, right against the token here (`>>` is two
99    /// `>` with nothing between).
100    pub(crate) fn joined(&self, p: Punct) -> bool {
101        self.nth_is(1, p) && self.span_at(0).end == self.span_at(1).start
102    }
103
104    pub(crate) fn nth_is(&self, n: usize, p: Punct) -> bool {
105        *self.nth(n) == Tok::Punct(p)
106    }
107
108    pub(crate) fn eat(&mut self, p: Punct) -> bool {
109        let here = self.at(p);
110        if here {
111            self.bump();
112        }
113        here
114    }
115
116    pub(crate) fn need(&mut self, p: Punct) -> PResult<Span> {
117        if self.at(p) {
118            return Ok(self.bump().span);
119        }
120        Err(self.expected(&format!("`{}`", p.text())))
121    }
122
123    /// The `;` ending a statement or a member.
124    pub(crate) fn end(&mut self) -> PResult<()> {
125        self.need(Punct::Semi).map(drop)
126    }
127
128    /// Whether `tok` is `word` (reserved or written as a name).
129    pub(crate) fn is_word(tok: &Tok, word: &Word) -> bool {
130        match tok {
131            Tok::Keyword(w) => *w == word,
132            Tok::Ident(text) => !word.reserved && text == word.text,
133            _ => false,
134        }
135    }
136
137    pub(crate) fn at_word(&self, word: &Word) -> bool {
138        Self::is_word(self.tok(), word)
139    }
140
141    pub(crate) fn nth_word(&self, n: usize, word: &Word) -> bool {
142        Self::is_word(self.nth(n), word)
143    }
144
145    pub(crate) fn eat_word(&mut self, word: &Word) -> bool {
146        let here = self.at_word(word);
147        if here {
148            self.bump();
149        }
150        here
151    }
152
153    pub(crate) fn need_word(&mut self, word: &Word) -> PResult<Span> {
154        if self.at_word(word) {
155            return Ok(self.bump().span);
156        }
157        Err(self.expected(&format!("`{}`", word.text)))
158    }
159
160    /// Whether a form of `word` starts here.
161    pub(crate) fn starts(&self, word: &Word, starts: Starts) -> bool {
162        starts.map_or_else(|| self.at_word(word), |f| f(self))
163    }
164
165    /// `what` was expected here: reported.
166    pub(crate) fn expected(&mut self, what: &str) -> Fail {
167        let found = describe(self.tok());
168        self.error(
169            script_problem::EXPECTED,
170            self.span(),
171            format!("expected {what}, found {found}"),
172        )
173    }
174
175    pub(crate) fn error(
176        &mut self,
177        code: &str,
178        span: Span,
179        message: impl std::fmt::Display,
180    ) -> Fail {
181        self.report.error(code, span, message);
182        Fail
183    }
184
185    /// Past a form that failed: through its `;`, or through the `{ … }`
186    /// that ended it, or up to a `}` closing what it's in.
187    pub(crate) fn recover(&mut self) {
188        let start = self.pos;
189        let mut depth = 0usize;
190        loop {
191            match self.tok() {
192                Tok::Eof => return,
193                Tok::Punct(Punct::Semi) if depth == 0 => {
194                    self.bump();
195                    return;
196                }
197                Tok::Punct(Punct::LBrace | Punct::LParen | Punct::LBracket) => depth += 1,
198                Tok::Punct(Punct::RBrace | Punct::RParen | Punct::RBracket) => {
199                    if depth == 0 {
200                        // A `)` or `]` nothing opened, met first: skipped, or
201                        // what failed on it would fail on it again. A `}`
202                        // closes the block around.
203                        if self.pos == start && !self.at(Punct::RBrace) {
204                            self.bump();
205                        }
206                        return;
207                    }
208                    depth -= 1;
209                    if depth == 0 && self.at(Punct::RBrace) {
210                        self.bump();
211                        return;
212                    }
213                }
214                _ => {}
215            }
216            self.bump();
217        }
218    }
219
220    /// `f` with `{` ending expressions (a system's period, a guard).
221    pub(crate) fn no_brace<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
222        let was = std::mem::replace(&mut self.no_brace, true);
223        let out = f(self);
224        self.no_brace = was;
225        out
226    }
227
228    /// `f` with `{` allowed again (inside brackets and blocks).
229    pub(crate) fn braces<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
230        let was = std::mem::replace(&mut self.no_brace, false);
231        let out = f(self);
232        self.no_brace = was;
233        out
234    }
235}
236
237/// A token as a problem names it.
238fn describe(tok: &Tok) -> String {
239    match tok {
240        Tok::Ident(name) => format!("`{name}`"),
241        Tok::Keyword(w) => format!("`{}`", w.text),
242        Tok::Punct(p) => format!("`{}`", p.text()),
243        Tok::Int(_) | Tok::Float(_) | Tok::Unit(..) => "a number".into(),
244        Tok::Version(v) => format!("the version `{v}`"),
245        Tok::Str(_) | Tok::RawStr(_) => "a string".into(),
246        Tok::Char(_) => "a character".into(),
247        Tok::Lifetime(name) => format!("the lifetime `'{name}`"),
248        Tok::Doc(_) => "a doc comment".into(),
249        Tok::Eof => "the end of the file".into(),
250    }
251}