FE 0.15.0
A C++23 toolkit for writing compiler/interpreter 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 <deque>
7#include <format>
8
9#include "fe/driver.h"
10#include "fe/loc.h"
11#include "fe/ring.h"
12
13namespace fe {
14
15/// The blueprint for a [recursive descent](https://en.wikipedia.org/wiki/Recursive_descent_parser)/
16/// [ascent parser](https://en.wikipedia.org/wiki/Recursive_ascent_parser) using a @p K lookahead of `Tok`ens.
17/// Parser::accept and Parser::expect indicate failure by constructing a @p Tok%en with its default constructor.
18/// Hence, @p Tok must be default-constructible *and* testable as a `bool` (to check for that failure):
19/// ```
20/// class Tok {
21/// public:
22/// enum class Tag {
23/// Nil,
24/// // ...
25/// };
26/// Tok() {} // default constructor yields the "failure" token
27/// // ...
28/// explicit operator bool() const { return tag_ != Tag::Nil; }
29/// // ...
30/// };
31///
32/// // Your Parser:
33/// if (auto tok = accept(Tok::Tag::My_Tag)) {
34/// do_something(tok);
35/// }
36/// ```
37/// @p S must provide the Lexer to pull from and the Driver to report to:
38/// ```
39/// class MyParser : public fe::Parser<Tok, Tok::Tag, K, MyParser> {
40/// Lexer& lexer(); ///< Parser::lex pulls the next Tok%en from here.
41/// fe::Driver& driver(); ///< The default diagnostics below land in its Driver::error.
42///
43/// friend fe::Parser<Tok, Tok::Tag, K, MyParser>; ///< Otherwise, these may be private.
44/// };
45/// ```
46/// Parser::syntax_err and Parser::unanchored_err come with a default; declare either in @p S to word it differently.
47/// All Parser::syntax_err overloads funnel through the one taking a `what`/`Tok`/`ctxt`; override just that one.
48/// @warning Declaring *any* `syntax_err` in @p S hides all of them, so add `using Super::syntax_err;`.
49template<class Tok, class Tag, size_t K, class S>
50requires std::is_default_constructible_v<Tok>
51 && (std::is_convertible_v<Tok, bool> || std::is_constructible_v<bool, Tok>)class Parser {
52private:
53 S& self() { return *static_cast<S*>(this); }
54 const S& self() const { return *static_cast<const S*>(this); }
55
56protected:
57 /// @name Construction
58 ///@{
59 void init() {
60 ahead_.reset();
61 for (size_t i = 0; i != K; ++i)
62 ahead_[i] = self().lexer().lex();
63 curr_ = ahead().loc().anew_begin();
64 }
65 ///@}
66
67 /// @name Tracker
68 /// Track Loc%ation in the source file.
69 /// Use like this:
70 /// ```
71 /// auto track = tracker();
72 /// auto foo = parse_foo();
73 /// auto bar = parse_bar();
74 /// auto foobar = new FooBar(track, foo, bar);
75 /// ```
76 ///@{
77 class Tracker {
78 public:
79 Tracker(Pos start, Loc& curr)
80 : start_(start)
81 , curr_(curr) {}
82
83 /// If nothing was consumed since this Tracker started (a total parse failure for whatever it was tracking),
84 /// @p curr_'s end still precedes @p start_; yield a zero-width Loc at @p start_ instead of a backwards one.
85 Loc loc() const { return {curr_.src, start_, start_ <= curr_.end ? curr_.end : start_}; }
86 Loc operator()() const { return loc(); }
87 operator Loc() const { return loc(); }
88
89 private:
90 Pos start_;
91 const Loc& curr_;
92 };
93
94 /// Factory method to build a Parser::Tracker.
95 Tracker tracker() { return {ahead().loc().begin, curr_}; }
96 Tracker tracker(Pos begin) { return {begin, curr_}; } ///< As above but start tracking at @p begin.
97 Tracker tracker(Loc begin) { return {begin.begin, curr_}; }
98 ///@}
99
100 /// @name Shift Token
101 ///@{
102 /// Get lookahead.
103 Tok ahead(size_t i = 0) const { return ahead_[i]; }
104
105 /// Invoke Lexer to retrieve next Token.
106 Tok lex() {
107 auto result = ahead();
108 curr_ = result.loc();
109 ahead_.put(self().lexer().lex());
110 return result;
111 }
112
113 /// If Parser::ahead() is a @p tag, consume and return it, otherwise yield `std::nullopt`.
114 Tok accept(Tag tag) {
115 if (tag != ahead().tag()) return {};
116 return lex();
117 }
118
119 /// Parser::lex Parser::ahead() which must be a @p tag.
120 /// Issue error with @p ctxt otherwise.
121 Tok expect(Tag tag, Cite ctxt) {
122 if (ahead().tag() == tag) return lex();
123 self().syntax_err(tag, ctxt);
124 return {};
125 }
126
127 /// As above but builds the context via fe::format_cite.
128 template<class... Args>
129 Tok expect(Tag tag, cite_string<Args...> fmt, Args&&... args) {
130 if (ahead().tag() == tag) return lex();
131 self().syntax_err(tag, format_cite(fmt, std::forward<Args>(args)...));
132 return {};
133 }
134
135 /// Consume Parser::ahead which must be a @p tag; asserts otherwise.
136 Tok eat([[maybe_unused]] Tag tag) {
137 assert(tag == ahead().tag() && "internal parser error");
138 return lex();
139 }
140 ///@}
141
142 /// RAII helper that anchors a @p Tag for its lifetime; use Parser::anchor to build one.
143 class Anchor {
144 public:
145 Anchor(const Anchor&) = delete;
146 Anchor& operator=(const Anchor&) = delete;
147
148 Anchor(Parser& parser, Tag tag)
149 : parser_(parser) {
150 parser_.anchors_.emplace_back(tag);
151 }
152
153 ~Anchor() { parser_.anchors_.pop_back(); }
154
155 private:
156 Parser& parser_;
157 };
158
159 /// @name Anchor
160 /// An *anchor* is a @p Tag that an enclosing context is waiting for.
161 /// E.g., while parsing a parenthesized expression, `)` is anchored:
162 /// a nested parser must not swallow it but bail out, so the enclosing context can Parser::expect it.
163 /// A `)` that is *not* anchored, however, is simply bogus and Parser::recover discards it.
164 ///@{
165
166 /// Factory method to build a Parser::Anchor; Parser::expect @p tag yourself at the end of the scope.
167 /// Use like this:
168 /// ```
169 /// if (accept(Tag::D_paren_l)) {
170 /// auto _ = this->anchor(Tag::D_paren_r);
171 /// auto expr = parse_expr();
172 /// expect(Tag::D_paren_r, "parenthesized expression");
173 /// return expr;
174 /// }
175 /// ```
176 [[nodiscard]] Anchor anchor(Tag tag) { return {*this, tag}; }
177
178 /// Is @p tag anchored by an enclosing context?
179 /// Scans the innermost anchor first, but *any* enclosing context counts.
180 bool anchored(Tag tag) const { return std::find(anchors_.rbegin(), anchors_.rend(), tag) != anchors_.rend(); }
181
182 /// Parser::lex all Tok%ens whose Tag satisfies @p pred and that are not Parser::anchored;
183 /// report the whole run as a single `S::unanchored_err`.
184 /// This turns an otherwise fatal Tok%en into a mere error message and keeps the current parser going.
185 /// One mistake discards one run, so one run is one diagnostic: a message per token buries the real error.
186 template<std::predicate<Tag> P>
187 void recover(P pred, Cite ctxt) {
188 auto discard = [this, &pred] { return pred(ahead().tag()) && !anchored(ahead().tag()); };
189 if (!discard()) return;
190
191 auto first = lex();
192 auto loc = first.loc();
193 size_t n = 1;
194 for (; discard(); ++n)
195 loc.end = lex().loc().end;
196
197 self().unanchored_err(first, loc, n, ctxt);
198 }
199
200 /// As above but only recovers from @p tag.
201 void recover(Tag tag, Cite ctxt) {
202 recover([tag](Tag t) { return t == tag; }, ctxt);
203 }
204 ///@}
205
206 /// @name Diagnostics
207 /// The defaults @p S may replace with one of its own.
208 /// Each yields the Error it reported into, so a Note can be chained.
209 ///@{
210 fe::Error& error() { return self().driver().error(); }
211 const fe::Error& error() const { return self().driver().error(); }
212
213 /// Parser::expect did not find @p what while parsing @p ctxt.
214 /// Both are Cite: a context string is *markup*, so backtick a literal token within it yourself.
215 fe::Error& syntax_err(Cite what, Tok tok, Cite ctxt) {
216 static_assert(
217 requires(S& s) { s.driver(); },
218 "provide `fe::Driver& driver()` in your parser - or a `syntax_err` of your own");
219 return error().e(tok.loc(), "expected {}, got `{}` while parsing {}", what, tok, ctxt);
220 }
221
222 /// As above but uses Parser::ahead as @p tok.
223 /// @note `decltype(auto)`, so an override of the funnel above may yield something else - or nothing.
224 decltype(auto) syntax_err(Cite what, Cite ctxt) { return self().syntax_err(what, ahead(), ctxt); }
225
226 /// As above but spells @p tag out via Parser::tag2str_.
227 decltype(auto) syntax_err(Tag tag, Cite ctxt) { return self().syntax_err(tag2str_(tag), ahead(), ctxt); }
228
229 /// Parser::recover discarded a run of @p n Tok%ens starting with @p tok and spanning @p loc while parsing @p ctxt.
230 fe::Error& unanchored_err(Tok tok, Loc loc, size_t n, Cite ctxt) {
231 static_assert(
232 requires(S& s) { s.driver(); },
233 "provide `fe::Driver& driver()` in your parser - or an `unanchored_err` of your own");
234 if (n == 1) return error().e(loc, "ignoring unmatched `{}` while parsing {}", tok, ctxt);
235 return error().e(loc, "ignoring {} unmatched tokens starting with `{}` while parsing {}", n, tok, ctxt);
236 }
237 ///@}
238
239 /// Spells @p tag out via `Tok::tag2str` if there is one - a bare enumerator would render as its number.
240 static auto tag2str_(Tag tag) {
241 if constexpr (requires { Tok::tag2str(tag); })
242 return format_cite("`{}`", Tok::tag2str(tag));
243 else
244 return format_cite("`{}`", tag);
245 }
246
249 std::deque<Tag> anchors_;
250};
251
252} // namespace fe
Collects diagnostics and hands each to the Diag that lays it out.
Definition error.h:23
Error & e(Loc loc, cite_string< Args... > s, Args &&... args)
Definition error.h:96
RAII helper that anchors a Tag for its lifetime; use Parser::anchor to build one.
Definition parser.h:143
Anchor & operator=(const Anchor &)=delete
Anchor(const Anchor &)=delete
Anchor(Parser &parser, Tag tag)
Definition parser.h:148
Loc operator()() const
Definition parser.h:86
Tracker(Pos start, Loc &curr)
Definition parser.h:79
Loc loc() const
If nothing was consumed since this Tracker started (a total parse failure for whatever it was trackin...
Definition parser.h:85
The blueprint for a recursive descent/ ascent parser using a K lookahead of Tokens.
Definition parser.h:51
Tracker tracker(Pos begin)
As above but start tracking at begin.
Definition parser.h:96
void init()
Definition parser.h:59
decltype(auto) syntax_err(Cite what, Cite ctxt)
As above but uses Parser::ahead as tok.
Definition parser.h:224
void recover(P pred, Cite ctxt)
Parser::lex all Tokens whose Tag satisfies pred and that are not Parser::anchored; report the whole r...
Definition parser.h:187
bool anchored(Tag tag) const
Is tag anchored by an enclosing context?
Definition parser.h:180
Tracker tracker(Loc begin)
Definition parser.h:97
decltype(auto) syntax_err(Tag tag, Cite ctxt)
As above but spells tag out via Parser::tag2str_.
Definition parser.h:227
std::deque< Tag > anchors_
Definition parser.h:249
Tok lex()
Invoke Lexer to retrieve next Token.
Definition parser.h:106
Ring< Tok, K > ahead_
Definition parser.h:247
Loc curr_
Definition parser.h:248
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:240
Tok eat(Tag tag)
Consume Parser::ahead which must be a tag; asserts otherwise.
Definition parser.h:136
fe::Error & error()
Definition parser.h:210
Tok expect(Tag tag, Cite ctxt)
Parser::lex Parser::ahead() which must be a tag.
Definition parser.h:121
fe::Error & syntax_err(Cite what, Tok tok, Cite ctxt)
Parser::expect did not find what while parsing ctxt.
Definition parser.h:215
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:230
void recover(Tag tag, Cite ctxt)
As above but only recovers from tag.
Definition parser.h:201
Anchor anchor(Tag tag)
Factory method to build a Parser::Anchor; Parser::expect tag yourself at the end of the scope.
Definition parser.h:176
const fe::Error & error() const
Definition parser.h:211
Tok accept(Tag tag)
If Parser::ahead() is a tag, consume and return it, otherwise yield std::nullopt.
Definition parser.h:114
Tok expect(Tag tag, cite_string< Args... > fmt, Args &&... args)
As above but builds the context via fe::format_cite.
Definition parser.h:129
Tracker tracker()
Factory method to build a Parser::Tracker.
Definition parser.h:95
Tok ahead(size_t i=0) const
Definition parser.h:103
A ring buffer with N elements.
Definition ring.h:15
A borrowed fragment whose backticks stay markup; format_cite escapes every other argument.
Definition term.h:200
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:242
Definition algo.h:17
Cited format_cite(cite_string< Args... > fmt, Args &&... args)
<
Definition term.h:247
Location within a Src: the half-open byte range [Loc::begin, Loc::end).
Definition loc.h:42
Pos begin
Definition loc.h:92
Byte offset into a Src; pass around as value.
Definition loc.h:16