FE 0.16.1
Fast, Effecient FrontEnds
Loading...
Searching...
No Matches
parser.h
Go to the documentation of this file.
1#pragma once
2
3#include <concepts>
4
5#include <algorithm>
6#include <type_traits>
7
8#include "fe/assert.h"
9#include "fe/error.h"
10#include "fe/format.h"
11#include "fe/loc.h"
12#include "fe/ring.h"
13#include "fe/vector.h"
14
15namespace fe {
16
17/// What fe::Parser needs of its Tok%en type: a Tag and a Loc, copied in and out of the lookahead by value.
18/// Parser::accept and Parser::expect indicate failure by returning a default-constructed Tok%en,
19/// so `Tok()` must be falsy and tagged Tag::Nil.
20/// That sentinel is only checkable here if `Tok()` is usable in a constant expression - Parser::init asserts it.
21/// The probe for that yields a `bool`, as MSVC mistypes an enum as the non-type argument of a template.
22template<class Tok, class Tag>
23concept Token = requires(const Tok tok) {
24 Tag::Nil;
25 { tok.tag() } -> std::convertible_to<Tag>;
26 { tok.loc() } -> std::convertible_to<Loc>;
27 requires std::equality_comparable<Tag>;
28 requires std::semiregular<Tok>;
29 requires std::constructible_from<bool, Tok>;
30 requires !requires { std::bool_constant<Tok().tag() == Tag::Nil>(); } || Tok().tag() == Tag::Nil;
31};
32
33/// What fe::Parser needs of its CRTP child @p S beyond fe::Diagnosable: the Lexer to pull Tok%ens from.
34template<class S, class Tok>
35concept Lexable = requires(S& s) {
36 { s.lexer().lex() } -> std::convertible_to<Tok>;
37};
38
39/// The blueprint for a [recursive descent](https://en.wikipedia.org/wiki/Recursive_descent_parser)/
40/// [ascent parser](https://en.wikipedia.org/wiki/Recursive_ascent_parser) using a @p K lookahead of `Tok`ens.
41/// Parser::accept and Parser::expect indicate failure by constructing a @p Tok%en with its default constructor.
42/// Hence, @p Tok must be an fe::Token - default-constructible, testable as a `bool`, and tagged Tag::Nil:
43/// ```
44/// class Tok {
45/// public:
46/// enum class Tag {
47/// Nil,
48/// // ...
49/// };
50/// Tok() {} // default constructor yields the "failure" token
51/// // ...
52/// explicit operator bool() const { return tag_ != Tag::Nil; }
53/// // ...
54/// };
55///
56/// // Your Parser:
57/// if (auto tok = accept(Tok::Tag::My_Tag)) {
58/// do_something(tok);
59/// }
60/// ```
61/// @p S must provide the Lexer to pull from and the Driver to report to - publicly,
62/// so that fe::Lexable and fe::Diagnosable hold outside this class too:
63/// ```
64/// class MyParser : public fe::Parser<Tok, Tok::Tag, K, MyParser> {
65/// public:
66/// Lexer& lexer(); ///< Parser::lex pulls the next Tok%en from here.
67/// fe::Driver& driver(); ///< The default diagnostics below land in its Driver::error.
68/// };
69/// ```
70/// Parser::syntax_err and Parser::unanchored_err come with a default; declare either in @p S to word it differently.
71/// Each is one name with one signature, so a declaration in @p S replaces it outright - no `using` of the base needed.
72template<class Tok, class Tag, size_t K, class S>
73requires Token<Tok, Tag> && (K >= 1) class Parser {
74private:
75 S& self() { return *static_cast<S*>(this); }
76 const S& self() const { return *static_cast<const S*>(this); }
77
78protected:
79 /// @name Construction
80 ///@{
81 void init() {
82 static_assert(Lexable<S, Tok>, "provide `Lexer& lexer()` in your parser - its `lex` is what feeds the Parser");
83 assert(!Tok() && Tok().tag() == Tag::Nil && "a default-constructed Tok must be the Nil token");
84 ahead_.reset();
85 for (size_t i = 0; i != K; ++i)
86 ahead_[i] = self().lexer().lex();
87 curr_ = ahead().loc().anew_begin();
88 }
89 ///@}
90
91 /// @name Tracker
92 /// Track Loc%ation in the source file.
93 /// Use like this:
94 /// ```
95 /// auto track = tracker();
96 /// auto foo = parse_foo();
97 /// auto bar = parse_bar();
98 /// auto foobar = new FooBar(track, foo, bar);
99 /// ```
100 ///@{
101 class Tracker {
102 public:
103 Tracker(Pos start, Loc& curr)
104 : start_(start)
105 , curr_(curr) {}
106
107 /// If nothing was consumed since this Tracker started (a total parse failure for whatever it was tracking),
108 /// @p curr_'s end still precedes @p start_; yield a zero-width Loc at @p start_ instead of a backwards one.
109 Loc loc() const { return {curr_.src, start_, start_ <= curr_.end ? curr_.end : start_}; }
110 Loc operator()() const { return loc(); }
111 operator Loc() const { return loc(); }
112
113 private:
114 Pos start_;
115 const Loc& curr_;
116 };
117
118 /// Factory method to build a Parser::Tracker.
119 Tracker tracker() { return {ahead().loc().begin, curr_}; }
120 Tracker tracker(Pos begin) { return {begin, curr_}; } ///< As above but start tracking at @p begin.
121 Tracker tracker(Loc begin) { return {begin.begin, curr_}; }
122 ///@}
123
124 /// @name Shift Token
125 ///@{
126 /// Get lookahead.
127 Tok ahead(size_t i = 0) const { return ahead_[i]; }
128
129 /// Invoke Lexer to retrieve next Token.
130 Tok lex() {
131 auto result = ahead();
132 curr_ = result.loc();
133 ahead_.put(self().lexer().lex());
134 return result;
135 }
136
137 /// If Parser::ahead() is a @p tag, consume and return it, otherwise yield the Nil `Tok()`.
138 Tok accept(Tag tag) {
139 if (tag != ahead().tag()) return {};
140 return lex();
141 }
142
143 /// Parser::lex Parser::ahead() which must be a @p tag.
144 /// Issue error with @p ctxt otherwise.
145 Tok expect(Tag tag, Cite ctxt) {
146 if (ahead().tag() == tag) return lex();
147 self().syntax_err(tag, ctxt);
148 return {};
149 }
150
151 /// As above but builds the context via fe::format_cite.
152 template<class... Args>
153 Tok expect(Tag tag, cite_string<Args...> fmt, Args&&... args) {
154 if (ahead().tag() == tag) return lex();
155 self().syntax_err(tag, format_cite(fmt, std::forward<Args>(args)...));
156 return {};
157 }
158
159 /// Consume Parser::ahead which must be a @p tag; asserts otherwise.
160 Tok eat([[maybe_unused]] Tag tag) {
161 assert(tag == ahead().tag() && "internal parser error");
162 return lex();
163 }
164 ///@}
165
166 /// One anchor; the *left* end opened the context, the *right* one is what it is still waiting for.
167 struct Anchor {
168 Tok l_tok; ///< The Tok%en that opened the context.
169 Tag r_tag; ///< The closing Tag that context is waiting for.
170 };
171
172 /// RAII helper that pushes a Parser::Anchor for its lifetime; use Parser::anchor to build one.
174 public:
175 ScopedAnchor(const ScopedAnchor&) = delete;
177
178 ScopedAnchor(Parser& parser, Tok l_tok, Tag r_tag)
179 : parser_(parser) {
180 parser_.anchors_.emplace_back(l_tok, r_tag);
181 }
182
183 ~ScopedAnchor() { parser_.anchors_.pop_back(); }
184
185 private:
186 Parser& parser_;
187 };
188
189 /// @name Anchor
190 /// An *anchor* is a @p Tag that an enclosing context is waiting for.
191 /// E.g., while parsing a parenthesized expression, `)` is anchored:
192 /// a nested parser must not swallow it but bail out, so the enclosing context can Parser::expect it.
193 /// A `)` that is *not* anchored, however, is simply bogus and Parser::recover discards it.
194 ///@{
195
196 /// Factory method to build a Parser::ScopedAnchor; Parser::expect @p r_tag yourself at the end of the scope.
197 /// @p l_tok is the Tok%en that opened this context and Parser::syntax_err notes it on a missing @p r_tag,
198 /// which is all it takes to point a `)` that never came back at its `(`.
199 /// Use like this:
200 /// ```
201 /// if (auto paren_l = accept(Tag::D_paren_l)) {
202 /// auto _ = anchor(paren_l, Tag::D_paren_r);
203 /// auto expr = parse_expr();
204 /// expect(Tag::D_paren_r, "parenthesized expression");
205 /// return expr;
206 /// }
207 /// ```
208 [[nodiscard]] ScopedAnchor anchor(Tok l_tok, Tag r_tag) { return {*this, l_tok, r_tag}; }
209
210 /// The innermost Anchor waiting for @p r_tag - `nullptr` if no enclosing context is.
211 /// Scans the innermost anchor first, but *any* enclosing context counts.
212 const Anchor* find_anchor(Tag r_tag) const {
213 if (r_tag == Tag::Nil) return nullptr;
214 auto i
215 = std::find_if(anchors_.rbegin(), anchors_.rend(), [r_tag](const Anchor& a) { return a.r_tag == r_tag; });
216 return i == anchors_.rend() ? nullptr : &*i;
217 }
218
219 /// Is @p r_tag anchored by an enclosing context?
220 bool is_anchored(Tag r_tag) const { return find_anchor(r_tag); }
221
222 /// Parser::lex all Tok%ens whose Tag satisfies @p pred and that are not Parser::is_anchored;
223 /// report the whole run as a single `S::unanchored_err`.
224 /// This turns an otherwise fatal Tok%en into a mere error message and keeps the current parser going.
225 /// One mistake discards one run, so one run is one diagnostic: a message per token buries the real error.
226 template<std::predicate<Tag> P>
227 void recover(P pred, Cite ctxt) {
228 auto discard = [this, &pred] { return pred(ahead().tag()) && !is_anchored(ahead().tag()); };
229 if (!discard()) return;
230
231 auto first = lex();
232 auto loc = first.loc();
233 size_t n = 1;
234 for (; discard(); ++n)
235 loc.end = lex().loc().end;
236
237 self().unanchored_err(first, loc, n, ctxt);
238 }
239
240 /// As above but only recovers from @p tag.
241 void recover(Tag tag, Cite ctxt) {
242 recover([tag](Tag t) { return t == tag; }, ctxt);
243 }
244 ///@}
245
246 /// What Parser::expect was after - a Tag, or free-form markup.
247 /// Both spellings are one parameter rather than two overloads, so that Parser::syntax_err stays a single
248 /// signature: a declaration in @p S then replaces it instead of hiding a set of siblings alongside it.
249 struct Expected {
250 Tag tag = Tag::Nil; ///< Tag::Nil, if the expectation was not a Tag.
251 Cited what; ///< Already rendered as markup - Parser::tag2str_ backticks a Tag.
252
254 : tag(tag)
255 , what(tag2str_(tag)) {}
257 : what(std::move(what)) {}
259 : what(std::string(what.view())) {}
260 template<size_t N>
261 Expected(const char (&what)[N])
262 : Expected(Cite(what)) {}
263 };
264
265 /// @name Diagnostics
266 /// The defaults @p S may replace with one of its own.
267 /// Each yields the Error it reported into, so a Note can be chained.
268 ///@{
269 fe::Error& error() { return self().driver().error(); }
270 const fe::Error& error() const { return self().driver().error(); }
271
272 /// Parser::expect did not find @p what while parsing @p ctxt; @p got defaults to Parser::ahead.
273 /// @p ctxt is markup, so backtick a literal token within it yourself.
274 /// A @p what the innermost Anchor is waiting for gets a Note pointing back at the Tok%en that opened it -
275 /// the `(` of a `)` that never came.
276 fe::Error& syntax_err(Expected what, Cite ctxt, Tok got = {}) {
277 static_assert(Diagnosable<S>, "provide `fe::Driver& driver()` in your parser - or a `syntax_err` of your own");
278 static_assert(Formattable<Tok>, "provide a `std::formatter` for your Tok - or a `syntax_err` of your own");
279 if (!got) got = ahead();
280 auto& err = error().e(got.loc(), "expected {}, got `{}` while parsing {}", what.what, got, ctxt);
281 if (!anchors_.empty() && anchors_.back().r_tag == what.tag) {
282 const auto& a = anchors_.back();
283 err.n(a.l_tok.loc(), "to match this `{}`", a.l_tok);
284 }
285 return err;
286 }
287
288 /// Parser::recover discarded a run of @p n Tok%ens starting with @p tok and spanning @p loc while parsing @p ctxt.
289 fe::Error& unanchored_err(Tok tok, Loc loc, size_t n, Cite ctxt) {
290 static_assert(Diagnosable<S>,
291 "provide `fe::Driver& driver()` in your parser - or an `unanchored_err` of your own");
292 static_assert(Formattable<Tok>, "provide a `std::formatter` for your Tok - or an `unanchored_err` of your own");
293 if (n == 1) return error().e(loc, "ignoring stray `{}` while parsing {}", tok, ctxt);
294 return error().e(loc, "ignoring {} stray tokens starting with `{}` while parsing {}", n, tok, ctxt);
295 }
296 ///@}
297
298 /// Spells @p tag out via `Tok::tag2str` if there is one - a bare enumerator would render as its number.
299 static auto tag2str_(Tag tag) {
300 if constexpr (requires { Tok::tag2str(tag); }) {
301 return format_cite("`{}`", Tok::tag2str(tag));
302 } else {
303 static_assert(Formattable<Tag>, "provide `Tok::tag2str` - or a `std::formatter` for your Tag");
304 return format_cite("`{}`", tag);
305 }
306 }
307
310 Vector<Anchor, 8> anchors_; // Inline space: 8
311};
312
313} // namespace fe
Collects diagnostics and hands each to the Diag that lays it out.
Definition error.h:25
Error & e(Loc loc, cite_string< Args... > s, Args &&... args)
Definition error.h:98
RAII helper that pushes a Parser::Anchor for its lifetime; use Parser::anchor to build one.
Definition parser.h:173
ScopedAnchor(const ScopedAnchor &)=delete
ScopedAnchor & operator=(const ScopedAnchor &)=delete
ScopedAnchor(Parser &parser, Tok l_tok, Tag r_tag)
Definition parser.h:178
Loc operator()() const
Definition parser.h:110
Tracker(Pos start, Loc &curr)
Definition parser.h:103
Loc loc() const
If nothing was consumed since this Tracker started (a total parse failure for whatever it was trackin...
Definition parser.h:109
The blueprint for a recursive descent/ ascent parser using a K lookahead of Tokens.
Definition parser.h:73
Tracker tracker(Pos begin)
As above but start tracking at begin.
Definition parser.h:120
void init()
Definition parser.h:81
void recover(P pred, Cite ctxt)
Parser::lex all Tokens whose Tag satisfies pred and that are not Parser::is_anchored; report the whol...
Definition parser.h:227
ScopedAnchor anchor(Tok l_tok, Tag r_tag)
Factory method to build a Parser::ScopedAnchor; Parser::expect r_tag yourself at the end of the scope...
Definition parser.h:208
Tracker tracker(Loc begin)
Definition parser.h:121
fe::Error & syntax_err(Expected what, Cite ctxt, Tok got={})
Parser::expect did not find what while parsing ctxt; got defaults to Parser::ahead.
Definition parser.h:276
Tok lex()
Invoke Lexer to retrieve next Token.
Definition parser.h:130
Tok l_tok
The Token that opened the context.
Definition parser.h:168
Ring< Tok, K > ahead_
Definition parser.h:308
Loc curr_
Definition parser.h:309
static auto tag2str_(Tag tag)
Spells tag out via Tok::tag2str if there is one - a bare enumerator would render as its number.
Definition parser.h:299
Tok eat(Tag tag)
Consume Parser::ahead which must be a tag; asserts otherwise.
Definition parser.h:160
fe::Error & error()
Definition parser.h:269
Tok expect(Tag tag, Cite ctxt)
Parser::lex Parser::ahead() which must be a tag.
Definition parser.h:145
fe::Error & unanchored_err(Tok tok, Loc loc, size_t n, Cite ctxt)
Parser::recover discarded a run of n Tokens starting with tok and spanning loc while parsing ctxt.
Definition parser.h:289
void recover(Tag tag, Cite ctxt)
As above but only recovers from tag.
Definition parser.h:241
Vector< Anchor, 8 > anchors_
Definition parser.h:310
const fe::Error & error() const
Definition parser.h:270
const Anchor * find_anchor(Tag r_tag) const
The innermost Anchor waiting for r_tag - nullptr if no enclosing context is.
Definition parser.h:212
bool is_anchored(Tag r_tag) const
Is r_tag anchored by an enclosing context?
Definition parser.h:220
Tag r_tag
The closing Tag that context is waiting for.
Definition parser.h:169
Tok accept(Tag tag)
If Parser::ahead() is a tag, consume and return it, otherwise yield the Nil Tok().
Definition parser.h:138
Tok expect(Tag tag, cite_string< Args... > fmt, Args &&... args)
As above but builds the context via fe::format_cite.
Definition parser.h:153
Tracker tracker()
Factory method to build a Parser::Tracker.
Definition parser.h:119
Tok ahead(size_t i=0) const
Definition parser.h:127
One anchor; the left end opened the context, the right one is what it is still waiting for.
Definition parser.h:167
A ring buffer with N elements.
Definition ring.h:15
This is a thin wrapper for ankerl::svector<T, N, A> which is a drop-in replacement for std::vector<T,...
Definition vector.h:25
A borrowed fragment whose backticks stay markup; format_cite escapes every other argument.
Definition term.h:195
An owning message fragment that spells `citations` of its own; what format_cite yields.
Definition term.h:178
Every frontend building block reports into the Error of S::driver.
Definition error.h:154
What fe::Parser needs of its CRTP child S beyond fe::Diagnosable: the Lexer to pull Tokens from.
Definition parser.h:35
What fe::Parser needs of its Token type: a Tag and a Loc, copied in and out of the lookahead by value...
Definition parser.h:23
std::format_string< detail::cite_arg_t< Args >... > cite_string
A std::format_string whose backticks delimit a `citation` while those of its arguments are data.
Definition term.h:237
Definition algo.h:17
Cited format_cite(cite_string< Args... > fmt, Args &&... args)
<
Definition term.h:242
Definition span.h:150
Location within a Src: the half-open byte range [Loc::begin, Loc::end).
Definition loc.h:42
Pos begin
Definition loc.h:92
What Parser::expect was after - a Tag, or free-form markup.
Definition parser.h:249
Expected(const char(&what)[N])
Definition parser.h:261
Cited what
Already rendered as markup - Parser::tag2str_ backticks a Tag.
Definition parser.h:251
Expected(Cited what)
Definition parser.h:256
Tag tag
Tag::Nil, if the expectation was not a Tag.
Definition parser.h:250
Expected(Cite what)
Definition parser.h:258
Expected(Tag tag)
Definition parser.h:253
Byte offset into a Src; pass around as value.
Definition loc.h:16