FE 0.13.1
Header-only C++ frontend library
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 Loc loc() const { return {curr_.src, start_, curr_.end}; }
84 Loc operator()() const { return loc(); }
85 operator Loc() const { return loc(); }
86
87 private:
88 Pos start_;
89 const Loc& curr_;
90 };
91
92 /// Factory method to build a Parser::Tracker.
93 Tracker tracker() { return {ahead().loc().begin, curr_}; }
94 Tracker tracker(Pos begin) { return {begin, curr_}; } ///< As above but start tracking at @p begin.
95 Tracker tracker(Loc begin) { return {begin.begin, curr_}; }
96 ///@}
97
98 /// @name Shift Token
99 ///@{
100 /// Get lookahead.
101 Tok ahead(size_t i = 0) const { return ahead_[i]; }
102
103 /// Invoke Lexer to retrieve next Token.
104 Tok lex() {
105 auto result = ahead();
106 curr_ = result.loc();
107 ahead_.put(self().lexer().lex());
108 return result;
109 }
110
111 /// If Parser::ahead() is a @p tag, consume and return it, otherwise yield `std::nullopt`.
112 Tok accept(Tag tag) {
113 if (tag != ahead().tag()) return {};
114 return lex();
115 }
116
117 /// Parser::lex Parser::ahead() which must be a @p tag.
118 /// Issue error with @p ctxt otherwise.
119 Tok expect(Tag tag, std::string_view ctxt) {
120 if (ahead().tag() == tag) return lex();
121 self().syntax_err(tag, ctxt);
122 return {};
123 }
124
125 /// As above but builds the context via std::format.
126 template<class... Args>
127 Tok expect(Tag tag, std::format_string<Args...> fmt, Args&&... args) {
128 if (ahead().tag() == tag) return lex();
129 self().syntax_err(tag, std::format(fmt, std::forward<Args>(args)...));
130 return {};
131 }
132
133 /// Consume Parser::ahead which must be a @p tag; asserts otherwise.
134 Tok eat([[maybe_unused]] Tag tag) {
135 assert(tag == ahead().tag() && "internal parser error");
136 return lex();
137 }
138 ///@}
139
140 /// RAII helper that anchors a @p Tag for its lifetime; use Parser::anchor to build one.
141 class Anchor {
142 public:
143 Anchor(const Anchor&) = delete;
144 Anchor& operator=(const Anchor&) = delete;
145
146 Anchor(Parser& parser, Tag tag)
147 : parser_(parser) {
148 parser_.anchors_.emplace_back(tag);
149 }
150
151 ~Anchor() { parser_.anchors_.pop_back(); }
152
153 private:
154 Parser& parser_;
155 };
156
157 /// @name Anchor
158 /// An *anchor* is a @p Tag that an enclosing context is waiting for.
159 /// E.g., while parsing a parenthesized expression, `)` is anchored:
160 /// a nested parser must not swallow it but bail out, so the enclosing context can Parser::expect it.
161 /// A `)` that is *not* anchored, however, is simply bogus and Parser::recover discards it.
162 ///@{
163
164 /// Factory method to build a Parser::Anchor; Parser::expect @p tag yourself at the end of the scope.
165 /// Use like this:
166 /// ```
167 /// if (accept(Tag::D_paren_l)) {
168 /// auto _ = this->anchor(Tag::D_paren_r);
169 /// auto expr = parse_expr();
170 /// expect(Tag::D_paren_r, "parenthesized expression");
171 /// return expr;
172 /// }
173 /// ```
174 [[nodiscard]] Anchor anchor(Tag tag) { return {*this, tag}; }
175
176 /// Is @p tag anchored by an enclosing context?
177 /// Scans the innermost anchor first, but *any* enclosing context counts.
178 bool anchored(Tag tag) const { return std::find(anchors_.rbegin(), anchors_.rend(), tag) != anchors_.rend(); }
179
180 /// Parser::lex all Tok%ens whose Tag satisfies @p pred and that are not Parser::anchored;
181 /// report each one as `S::unanchored_err`.
182 /// This turns an otherwise fatal Tok%en into a mere error message and keeps the current parser going.
183 template<std::predicate<Tag> P>
184 void recover(P pred, std::string_view ctxt) {
185 while (pred(ahead().tag()) && !anchored(ahead().tag()))
186 self().unanchored_err(lex(), ctxt);
187 }
188
189 /// As above but only recovers from @p tag.
190 void recover(Tag tag, std::string_view ctxt) {
191 recover([tag](Tag t) { return t == tag; }, ctxt);
192 }
193 ///@}
194
195 /// @name Diagnostics
196 /// The defaults @p S may replace with one of its own.
197 ///@{
198 fe::Error& error() { return self().driver().error(); }
199 const fe::Error& error() const { return self().driver().error(); }
200
201 /// Parser::expect did not find @p what while parsing @p ctxt.
202 /// Backtick @p what yourself if it is a literal token rather than a phrase.
203 void syntax_err(std::string_view what, Tok tok, std::string_view ctxt) {
204 static_assert(
205 requires(S& s) { s.driver(); },
206 "provide `fe::Driver& driver()` in your parser - or a `syntax_err` of your own");
207 self().driver().error(tok.loc(), "expected {}, got `{}` while parsing {}", what, tok, ctxt);
208 }
209
210 /// As above but uses Parser::ahead as @p tok.
211 void syntax_err(std::string_view what, std::string_view ctxt) { self().syntax_err(what, ahead(), ctxt); }
212
213 /// As above but spells @p tag out via Parser::tag2str_.
214 void syntax_err(Tag tag, std::string_view ctxt) { self().syntax_err(tag2str_(tag), ahead(), ctxt); }
215
216 /// Parser::recover discarded @p tok while parsing @p ctxt.
217 void unanchored_err(Tok tok, std::string_view ctxt) {
218 static_assert(
219 requires(S& s) { s.driver(); },
220 "provide `fe::Driver& driver()` in your parser - or an `unanchored_err` of your own");
221 self().driver().error(tok.loc(), "ignoring unmatched `{}` while parsing {}", tok, ctxt);
222 }
223 ///@}
224
225 /// Spells @p tag out via `Tok::tag2str` if there is one - a bare enumerator would render as its number.
226 static auto tag2str_(Tag tag) {
227 if constexpr (requires { Tok::tag2str(tag); })
228 return std::format("`{}`", Tok::tag2str(tag));
229 else
230 return std::format("`{}`", tag);
231 }
232
235 std::deque<Tag> anchors_;
236};
237
238} // namespace fe
Collects diagnostics and hands each to the Diag that lays it out.
Definition error.h:27
Error & error(Loc loc, std::format_string< Args... > s, Args &&... args)
Definition error.h:103
RAII helper that anchors a Tag for its lifetime; use Parser::anchor to build one.
Definition parser.h:141
Anchor & operator=(const Anchor &)=delete
Anchor(const Anchor &)=delete
Anchor(Parser &parser, Tag tag)
Definition parser.h:146
Loc operator()() const
Definition parser.h:84
Tracker(Pos start, Loc &curr)
Definition parser.h:79
Loc loc() const
Definition parser.h:83
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:94
void init()
Definition parser.h:59
void syntax_err(Tag tag, std::string_view ctxt)
As above but spells tag out via Parser::tag2str_.
Definition parser.h:214
void recover(P pred, std::string_view ctxt)
Parser::lex all Tokens whose Tag satisfies pred and that are not Parser::anchored; report each one as...
Definition parser.h:184
bool anchored(Tag tag) const
Is tag anchored by an enclosing context?
Definition parser.h:178
Tracker tracker(Loc begin)
Definition parser.h:95
std::deque< Tag > anchors_
Definition parser.h:235
void syntax_err(std::string_view what, std::string_view ctxt)
As above but uses Parser::ahead as tok.
Definition parser.h:211
Tok lex()
Invoke Lexer to retrieve next Token.
Definition parser.h:104
Ring< Tok, K > ahead_
Definition parser.h:233
Loc curr_
Definition parser.h:234
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:226
void unanchored_err(Tok tok, std::string_view ctxt)
Parser::recover discarded tok while parsing ctxt.
Definition parser.h:217
Tok eat(Tag tag)
Consume Parser::ahead which must be a tag; asserts otherwise.
Definition parser.h:134
fe::Error & error()
Definition parser.h:198
Tok expect(Tag tag, std::string_view ctxt)
Parser::lex Parser::ahead() which must be a tag.
Definition parser.h:119
Anchor anchor(Tag tag)
Factory method to build a Parser::Anchor; Parser::expect tag yourself at the end of the scope.
Definition parser.h:174
const fe::Error & error() const
Definition parser.h:199
Tok accept(Tag tag)
If Parser::ahead() is a tag, consume and return it, otherwise yield std::nullopt.
Definition parser.h:112
void syntax_err(std::string_view what, Tok tok, std::string_view ctxt)
Parser::expect did not find what while parsing ctxt.
Definition parser.h:203
Tracker tracker()
Factory method to build a Parser::Tracker.
Definition parser.h:93
Tok expect(Tag tag, std::format_string< Args... > fmt, Args &&... args)
As above but builds the context via std::format.
Definition parser.h:127
Tok ahead(size_t i=0) const
Definition parser.h:101
void recover(Tag tag, std::string_view ctxt)
As above but only recovers from tag.
Definition parser.h:190
A ring buffer with N elements.
Definition ring.h:15
Definition algo.h:17
Location within a Src: the half-open byte range [Loc::begin, Loc::end).
Definition loc.h:46
Pos begin
Definition loc.h:94
Byte offset into a Src; pass around as value.
Definition loc.h:18