FE 0.16.1
Fast, Effecient FrontEnds
Loading...
Searching...
No Matches
lexer.h
Go to the documentation of this file.
1#pragma once
2
3#include <concepts>
4#include <cstddef>
5
6#include <string>
7#include <string_view>
8
9#include "fe/error.h"
10#include "fe/loc.h"
11#include "fe/ring.h"
12#include "fe/src.h"
13#include "fe/utf8.h"
14
15namespace fe {
16
17/// The blueprint for a lexer with a buffer of @p K tokens to peek into the future (Lexer::ahead).
18/// You can "override" Lexer::next via CRTP (@p S is the child).
19/// The whole source has to sit in @p buf: a Pos is an index into it, so there is nothing left to
20/// keep track of - Lexer::next just hands out the byte range the code point it consumed occupied.
21/// @p S must provide somewhere to report to - publicly, so that fe::Diagnosable holds outside this class too:
22/// ```
23/// class MyLexer : public fe::Lexer<K, MyLexer> {
24/// public:
25/// fe::Driver& driver(); ///< The default diagnostic below lands in its Driver::error.
26/// };
27/// ```
28/// Lexer::utf8_err and Lexer::char_err come with a default; declare either in @p S to word it differently.
29template<size_t K, class S>
30requires(K >= 1) class Lexer {
31private:
32 S& self() { return *static_cast<S*>(this); }
33 const S& self() const { return *static_cast<const S*>(this); }
34
35 /// The one place Lexer dispatches to `S::next`; @p S is complete by the time this body is instantiated.
36 /// The check is spelled inline: Lexer::next is protected, so a named concept would be false outside this class.
37 char32_t next_() {
38 static_assert(
39 requires(S& s) {
40 { s.next() } -> std::convertible_to<char32_t>;
41 }, "`S::next` must yield the code point it consumed");
42 return self().next();
43 }
44
45public:
46 Lexer(std::string_view buf)
47 : Lexer(buf, nullptr) {}
48 Lexer(const Src& src)
49 : Lexer(src.buf(), &src) {}
50
51protected:
52 /// Delegate here to funnel both of the above into a single ctor of your own.
53 Lexer(std::string_view buf, const Src* src)
54 : buf_(buf)
55 , src_(src) {
56 if (buf_.starts_with(utf8::Bom)) cursor_ = utf8::Bom.size();
57 for (size_t i = 0; i != K; ++i)
58 ahead_[i] = decode();
59 start();
60 }
61
62 /// A decoded code point together with the byte range it occupies.
63 struct Ahead {
64 char32_t c = utf8::EoF;
66 };
67
68 char32_t ahead(size_t i = 0) const { return ahead_[i].c; }
69
70 /// Is Lexer::ahead the end of a line - `\n`, `\r\n`, or utf8::EoF?
71 /// Works for any @p K: the `\n` of a `\r\n` is looked up in Lexer::buf_ directly.
72 bool eol() const {
73 auto c = ahead();
74 if (c == '\n' || c == utf8::EoF) return true;
75 auto off = ahead_[0].end.off;
76 return c == '\r' && off != buf_.size() && buf_[off] == '\n';
77 }
78
79 /// Loc%ation of the next character to be consumed (Lexer::ahead()); empty once the buffer is exhausted.
80 Loc peek() const { return {src_, ahead_[0].begin, ahead_[0].end}; }
81
82 /// Invoke before assembling the next token.
83 void start() { loc_ = peek().anew_begin(); }
84
85 /// @name Text
86 /// What has been lexed since Lexer::start.
87 /// The whole source sits in Lexer::buf_, so Lexer::loc_ already *is* the token and Lexer::view costs nothing.
88 ///@{
89 std::string_view view() const { return buf_.substr(loc_.begin.off, loc_.size()); }
90
91 /// Lexer::view, case-folded - what a case-insensitive language like FORTRAN or SQL wants to intern.
92 /// @note Byte-wise, which is all it takes: only ASCII folds, and no UTF-8 sequence spells it.
93 std::string lower() const { return fold(fe::utf8::tolower); }
94 std::string upper() const { return fold(fe::utf8::toupper); }
95
96 std::string fold(char32_t (*f)(char32_t) noexcept) const {
97 std::string res(view());
98 for (auto& c : res)
99 c = (char)f((char32_t)(uint8_t)c);
100 return res;
101 }
102 ///@}
103
104 /// @name Accept
105 ///@{
106
107 /// Get next `char32_t` in Lexer::buf_ and extend Lexer::loc_ to cover it.
108 /// @returns utf8::Invalid on an invalid UTF-8 sequence.
109 char32_t next() {
110 loc_.end = ahead_[0].end;
111 return ahead_.put(decode()).c;
112 }
113
114 /// Accept next character in Lexer::buf_ and Lexer::next it, if @p pred holds.
115 bool accept(std::predicate<char32_t> auto pred) {
116 if (!pred(ahead())) return false;
117 next_();
118 return true;
119 }
120
121 // clang-format off
122 bool accept(char32_t c) { return accept([c](char32_t d) { return c == d; }); }
123 bool accept(char c) { return accept((char32_t)c); }
124 bool accept(char8_t c) { return accept((char32_t)c); }
125 // clang-format on
126
127 /// Lexer::next as long as @p pred holds.
128 /// An ASCII run is taken straight out of Lexer::buf_ - no code point is decoded and the lookahead
129 /// is re-primed once at the end, which is what makes scanning an identifier or a stretch of white
130 /// space cost a compare per byte.
131 /// A character beyond ASCII falls back to Lexer::next, so @p pred may match one.
132 /// @note Only worth it if the lexed text is expected to be long such as identifiers or comments.
133 /// @returns the run just consumed.
134 std::string_view accept_while(std::predicate<char32_t> auto pred) {
135 auto begin = ahead_[0].begin.off;
136
137 while (true) {
138 auto run = ahead_[0].begin.off;
139 for (; run != buf_.size() && (uint8_t)buf_[run] < 0x80 && pred((char32_t)(uint8_t)buf_[run]); ++run) {}
140
141 skip_to(ahead_[0].begin.off, run);
142
143 auto c = ahead();
144 if (c < 0x80 || c == utf8::EoF || !pred(c)) break;
145 next_();
146 }
147
148 return buf_.substr(begin, loc_.end.off - begin);
149 }
150
151 /// Lexer::next up to - but not including - the next byte that is @p a or @p b.
152 /// The stop bytes are a *set*: whichever comes first ends the run, and this is no substring search.
153 /// Stops at the end of Lexer::buf_ if none of them ever shows up.
154 /// No UTF-8 sequence spells an ASCII byte, so such a run needs no decoding at all:
155 /// this is how to skip a comment or a string literal,
156 /// where Lexer::accept_while pays a compare - and Lexer::next a whole utf8::decode - per character.
157 /// @note Nothing in the run is validated as UTF-8 - malformed bytes cannot spell @p a or @p b either.
158 /// @returns the run just consumed.
159 std::string_view accept_while_none_of(char8_t a, char8_t b) {
160 assert(a < 0x80 && b < 0x80 && "only an ASCII byte can be searched for without decoding");
161 auto begin = ahead_[0].begin.off;
162 auto run = begin;
163
164 for (auto e = buf_.size(); run != e; ++run)
165 if (auto c = (char8_t)buf_[run]; c == a || c == b) break;
166
167 return skip_to(begin, run);
168 }
169
170 /// As Lexer::accept_while_none_of(char8_t, char8_t), but a single stop byte, which one `memchr` finds outright.
171 std::string_view accept_while_none_of(char8_t a) {
172 assert(a < 0x80 && "only an ASCII byte can be searched for without decoding");
173 auto begin = ahead_[0].begin.off;
174 auto pos = buf_.find((char)a, begin);
175 return skip_to(begin, pos == std::string_view::npos ? buf_.size() : pos);
176 }
177
178 /// Lexer::next up to - but not including - the next occurrence of @p seq.
179 /// Where Lexer::accept_while_none_of takes a set of bytes, this one takes a *sequence*:
180 /// only @p seq, spelled in exactly that order, ends the run - which is what closes a `/*` comment.
181 /// Stops at the end of Lexer::buf_ if @p seq never shows up.
182 /// @note Nothing in the run is decoded or validated as UTF-8.
183 /// @returns the run just consumed.
184 std::string_view accept_until(std::string_view seq) {
185 assert(!seq.empty() && "an empty sequence matches at once, so the lexer would not advance");
186 auto begin = ahead_[0].begin.off;
187 auto pos = buf_.find(seq, begin);
188 return skip_to(begin, pos == std::string_view::npos ? buf_.size() : pos);
189 }
190 ///@}
191
192 /// @name Recover
193 /// Lexer::next input that cannot be part of a token, report it, and keep the current lexer going.
194 /// Invoke after Lexer::start, so Lexer::loc_ spans exactly what was discarded.
195 ///@{
196 /// A whole run of malformed UTF-8, if any, reported as one `S::utf8_err`.
197 /// Check this *before* your token dispatch: utf8::Invalid is no code point and matches no rule of yours.
199 if (!accept(utf8::Invalid)) return false;
200 while (accept(utf8::Invalid)) {}
201 self().utf8_err();
202 return true;
203 }
204
205 /// One character, reported as `S::char_err`.
206 /// This is the last resort of your token dispatch: nothing in your language starts with it.
207 /// @warning Never at utf8::EoF - accept that first or your lexer will spin.
209 auto c = ahead();
210 next_();
211 self().char_err(c);
212 }
213 ///@}
214
215 /// @name Diagnostics
216 /// The defaults @p S may replace with one of its own.
217 /// Each yields the Error it reported into, so a Note can be chained.
218 ///@{
219 fe::Error& error() { return self().driver().error(); }
220 const fe::Error& error() const { return self().driver().error(); }
221
222 /// Lexer::recover_utf8 discarded the malformed bytes at Lexer::loc_.
224 static_assert(Diagnosable<S>, "provide `fe::Driver& driver()` in your lexer - or a `utf8_err` of your own");
225 return error().e(loc_, "invalid UTF-8 sequence");
226 }
227
228 /// Lexer::recover_char discarded @p c at Lexer::loc_.
229 fe::Error& char_err(char32_t c) {
230 static_assert(Diagnosable<S>, "provide `fe::Driver& driver()` in your lexer - or a `char_err` of your own");
231 return error().e(loc_, "invalid input character `{}`", utf8::Char32(c));
232 }
233 ///@}
234
235 std::string_view buf_;
236 const Src* src_;
237 size_t cursor_ = 0; ///< Byte offset of the first not yet decoded character.
239 Loc loc_; ///< Loc%ation of the token we are currently constructing - see Lexer::view.
240
241private:
242 /// Consume Lexer::buf_ up to byte offset @p run, without decoding a thing.
243 /// @returns that range.
244 std::string_view skip_to(size_t begin, size_t run) {
245 if (run != begin) {
246 loc_.end = Pos((uint32_t)run);
247 cursor_ = run;
248 for (size_t i = 0; i != K; ++i)
249 ahead_.put(decode());
250 }
251
252 return buf_.substr(begin, run - begin);
253 }
254
255 Ahead decode() {
256 auto begin = cursor_;
257 auto c = utf8::decode(buf_, cursor_);
258 return {c, Pos((uint32_t)begin), Pos((uint32_t)cursor_)};
259 }
260};
261
262} // 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
Lexer(const Src &src)
Definition lexer.h:48
char32_t next()
Get next char32_t in Lexer::buf_ and extend Lexer::loc_ to cover it.
Definition lexer.h:109
void start()
Invoke before assembling the next token.
Definition lexer.h:83
bool recover_utf8()
Definition lexer.h:198
std::string_view accept_while_none_of(char8_t a)
As Lexer::accept_while_none_of(char8_t, char8_t), but a single stop byte, which one memchr finds outr...
Definition lexer.h:171
Loc loc_
Location of the token we are currently constructing - see Lexer::view.
Definition lexer.h:239
std::string_view accept_while(std::predicate< char32_t > auto pred)
Lexer::next as long as pred holds.
Definition lexer.h:134
Lexer(std::string_view buf)
Definition lexer.h:46
std::string_view accept_until(std::string_view seq)
Lexer::next up to - but not including - the next occurrence of seq.
Definition lexer.h:184
Loc peek() const
Location of the next character to be consumed (Lexer::ahead()); empty once the buffer is exhausted.
Definition lexer.h:80
size_t cursor_
Byte offset of the first not yet decoded character.
Definition lexer.h:237
char32_t c
Definition lexer.h:64
bool accept(std::predicate< char32_t > auto pred)
Accept next character in Lexer::buf_ and Lexer::next it, if pred holds.
Definition lexer.h:115
const fe::Error & error() const
Definition lexer.h:220
char32_t ahead(size_t i=0) const
Definition lexer.h:68
Ring< Ahead, K > ahead_
Definition lexer.h:238
std::string_view accept_while_none_of(char8_t a, char8_t b)
Lexer::next up to - but not including - the next byte that is a or b.
Definition lexer.h:159
std::string_view view() const
Definition lexer.h:89
std::string lower() const
Lexer::view, case-folded - what a case-insensitive language like FORTRAN or SQL wants to intern.
Definition lexer.h:93
fe::Error & char_err(char32_t c)
Lexer::recover_char discarded c at Lexer::loc_.
Definition lexer.h:229
fe::Error & error()
Definition lexer.h:219
bool accept(char c)
Definition lexer.h:123
const Src * src_
Definition lexer.h:236
Lexer(std::string_view buf, const Src *src)
Delegate here to funnel both of the above into a single ctor of your own.
Definition lexer.h:53
void recover_char()
One character, reported as S::char_err.
Definition lexer.h:208
fe::Error & utf8_err()
Lexer::recover_utf8 discarded the malformed bytes at Lexer::loc_.
Definition lexer.h:223
bool accept(char8_t c)
Definition lexer.h:124
bool accept(char32_t c)
Definition lexer.h:122
std::string fold(char32_t(*f)(char32_t) noexcept) const
Definition lexer.h:96
std::string_view buf_
Definition lexer.h:235
bool eol() const
Is Lexer::ahead the end of a line - \n, \r\n, or utf8::EoF?
Definition lexer.h:72
std::string upper() const
Definition lexer.h:94
A decoded code point together with the byte range it occupies.
Definition lexer.h:63
A ring buffer with N elements.
Definition ring.h:15
T put(T item)
Puts item into buffer.
Definition ring.h:46
The content of one source file together with the offsets its rows start at.
Definition src.h:30
Every frontend building block reports into the Error of S::driver.
Definition error.h:154
char32_t decode(std::istream &is)
Decodes the next UTF-8 sequence from is into a single char32_t.
Definition utf8.h:64
constexpr char32_t toupper(char32_t c) noexcept
Definition utf8.h:156
constexpr char32_t tolower(char32_t c) noexcept
Definition utf8.h:155
Definition algo.h:17
Location within a Src: the half-open byte range [Loc::begin, Loc::end).
Definition loc.h:42
Pos end
It's called end because - just like an STL iterator - it refers to the byte one past the last one wit...
Definition loc.h:93
Byte offset into a Src; pass around as value.
Definition loc.h:16
constexpr Pos() noexcept=default
Creates an invalid Position.
Wrapper for char32_t with an operator<< that writes UTF-8.
Definition utf8.h:125