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