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