FE 0.13.1
Header-only C++ frontend library
Loading...
Searching...
No Matches
lexer.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstddef>
4
5#include <string>
6#include <string_view>
7
8#include "fe/driver.h"
9#include "fe/loc.h"
10#include "fe/ring.h"
11#include "fe/src.h"
12#include "fe/utf8.h"
13
14namespace fe {
15
16/// The blueprint for a lexer with a buffer of @p K tokens to peek into the future (Lexer::ahead).
17/// You can "override" Lexer::next via CRTP (@p S is the child).
18/// The whole source has to sit in @p buf: a Pos is an index into it, so there is nothing left to
19/// keep track of - Lexer::next just hands out the byte range the code point it consumed occupied.
20/// @p S must provide somewhere to report to:
21/// ```
22/// class MyLexer : public fe::Lexer<K, MyLexer> {
23/// fe::Driver& driver(); ///< The default diagnostic below lands in its Driver::error.
24///
25/// friend fe::Lexer<K, MyLexer>; ///< Otherwise, this may be private.
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>
30class Lexer {
31private:
32 S& self() { return *static_cast<S*>(this); }
33 const S& self() const { return *static_cast<const S*>(this); }
34
35public:
36 Lexer(std::string_view buf)
37 : Lexer(buf, nullptr) {}
38 Lexer(const Src& src)
39 : Lexer(src.buf(), &src) {}
40
41protected:
42 /// Delegate here to funnel both of the above into a single ctor of your own.
43 Lexer(std::string_view buf, const Src* src)
44 : buf_(buf)
45 , src_(src) {
46 if (buf_.starts_with(utf8::Bom)) cursor_ = utf8::Bom.size();
47 for (size_t i = 0; i != K; ++i)
48 ahead_[i] = decode();
49 start();
50 }
51
52 /// A decoded code point together with the byte range it occupies.
53 struct Ahead {
54 char32_t c = utf8::EoF;
56 };
57
58 char32_t ahead(size_t i = 0) const { return ahead_[i].c; }
59
60 /// Loc%ation of the next character to be consumed (Lexer::ahead()); empty once the buffer is exhausted.
61 Loc peek() const { return {src_, ahead_[0].begin, ahead_[0].end}; }
62
63 /// Invoke before assembling the next token.
64 void start() {
65 loc_ = peek().anew_begin();
66 str_.clear();
67 }
68
69 /// Get next `char32_t` in Lexer::buf_ and extend Lexer::loc_ to cover it.
70 /// @returns utf8::Invalid on an invalid UTF-8 sequence.
71 char32_t next() {
72 loc_.end = ahead_[0].end;
73 return ahead_.put(decode()).c;
74 }
75
76 /// @name Accept
77 /// Accept next character in Lexer::buf_, depending on some condition.
78 ///@{
79 /// What should happen to the accepted char?
80 /// Normalize identifiers via Append::Lower or Append::Upper for case-insensitive languages like FORTRAN or SQL.
81 enum class Append {
82 Off, ///< Do not append accepted char to Lexer::str_.
83 On, ///< Append accepted char as is to Lexer::str_.
84 Lower, ///< Append accepted char via fe::utf8::tolower` to Lexer::str_.
85 Upper, ///< Append accepted char via fe::utf8::toupper` to Lexer::str_.
86 };
87
88 /// @returns `true` if @p pred holds.
89 /// In this case invoke Lexer::next() and append to Lexer::str_, if @p append.
90 template<Append append = Append::On, class Pred>
91 bool accept(Pred pred) {
92 if (pred(ahead())) {
93 auto c = self().next();
94 if constexpr (append != Append::Off) {
95 if constexpr (append == Append::Lower) c = fe::utf8::tolower(c);
96 if constexpr (append == Append::Upper) c = fe::utf8::toupper(c);
97 str_ += c;
98 }
99 return true;
100 }
101 return false;
102 }
103
104 // clang-format off
105 template<Append append = Append::On> bool accept(char32_t c) { return accept<append>([c](char32_t d) { return c == d; }); }
106 template<Append append = Append::On> bool accept(char c) { return accept<append>((char32_t)c); }
107 template<Append append = Append::On> bool accept(char8_t c) { return accept<append>((char32_t)c); }
108 // clang-format on
109 ///@}
110
111 /// @name Recover
112 /// Lexer::next input that cannot be part of a token, report it, and keep the current lexer going.
113 /// Invoke after Lexer::start, so Lexer::loc_ spans exactly what was discarded.
114 ///@{
115 /// A whole run of malformed UTF-8, if any, reported as one `S::utf8_err`.
116 /// Check this *before* your token dispatch: utf8::Invalid is no code point and matches no rule of yours.
118 if (!accept<Append::Off>(utf8::Invalid)) return false;
119 while (accept<Append::Off>(utf8::Invalid)) {}
120 self().utf8_err();
121 return true;
122 }
123
124 /// One character, reported as `S::char_err`.
125 /// This is the last resort of your token dispatch: nothing in your language starts with it.
126 /// @warning Never at utf8::EoF - accept that first or your lexer will spin.
128 auto c = ahead();
129 self().next();
130 self().char_err(c);
131 }
132 ///@}
133
134 /// @name Diagnostics
135 /// The defaults @p S may replace with one of its own.
136 ///@{
137 /// Lexer::recover_utf8 discarded the malformed bytes at Lexer::loc_.
138 void utf8_err() {
139 static_assert(
140 requires(S& s) { s.driver(); },
141 "provide `fe::Driver& driver()` in your lexer - or a `utf8_err` of your own");
142 self().driver().error(loc_, "invalid UTF-8 sequence");
143 }
144
145 /// Lexer::recover_char discarded @p c at Lexer::loc_.
146 void char_err(char32_t c) {
147 static_assert(
148 requires(S& s) { s.driver(); },
149 "provide `fe::Driver& driver()` in your lexer - or a `char_err` of your own");
150 self().driver().error(loc_, "invalid input character `{}`", utf8::Char32(c));
151 }
152 ///@}
153
154 std::string_view buf_;
155 const Src* src_;
156 size_t cursor_ = 0; ///< Byte offset of the first not yet decoded character.
158 Loc loc_; ///< Loc%ation of the token we are currently constructing within Lexer::str_,
159 std::string str_;
160
161private:
162 Ahead decode() {
163 auto begin = cursor_;
164 auto c = utf8::decode(buf_, cursor_);
165 return {c, Pos((uint32_t)begin), Pos((uint32_t)cursor_)};
166 }
167};
168
169} // namespace fe
Lexer(const Src &src)
Definition lexer.h:38
bool accept(char8_t c)
Definition lexer.h:107
char32_t next()
Get next char32_t in Lexer::buf_ and extend Lexer::loc_ to cover it.
Definition lexer.h:71
void start()
Invoke before assembling the next token.
Definition lexer.h:64
bool recover_utf8()
Definition lexer.h:117
Loc loc_
Location of the token we are currently constructing within Lexer::str_,.
Definition lexer.h:158
Lexer(std::string_view buf)
Definition lexer.h:36
Loc peek() const
Location of the next character to be consumed (Lexer::ahead()); empty once the buffer is exhausted.
Definition lexer.h:61
size_t cursor_
Byte offset of the first not yet decoded character.
Definition lexer.h:156
char32_t c
Definition lexer.h:54
bool accept(char c)
Definition lexer.h:106
void utf8_err()
Definition lexer.h:138
char32_t ahead(size_t i=0) const
Definition lexer.h:58
Ring< Ahead, K > ahead_
Definition lexer.h:157
const Src * src_
Definition lexer.h:155
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:43
std::string str_
Definition lexer.h:159
void char_err(char32_t c)
Lexer::recover_char discarded c at Lexer::loc_.
Definition lexer.h:146
void recover_char()
One character, reported as S::char_err.
Definition lexer.h:127
@ Upper
Append accepted char via fe::utf8::toupper` to Lexer::str_.
Definition lexer.h:85
@ Lower
Append accepted char via fe::utf8::tolower` to Lexer::str_.
Definition lexer.h:84
@ Off
Do not append accepted char to Lexer::str_.
Definition lexer.h:82
bool accept(Pred pred)
Definition lexer.h:91
std::string_view buf_
Definition lexer.h:154
bool accept(char32_t c)
Definition lexer.h:105
A decoded code point together with the byte range it occupies.
Definition lexer.h:53
A ring buffer with N elements.
Definition ring.h:15
The content of one source file together with the offsets its rows start at.
Definition src.h:44
char32_t tolower(char32_t c) noexcept
Definition utf8.h:179
char32_t decode(std::istream &is)
Decodes the next UTF-8 sequence from is into a single char32_t.
Definition utf8.h:66
char32_t toupper(char32_t c) noexcept
Definition utf8.h:180
Definition algo.h:17
Location within a Src: the half-open byte range [Loc::begin, Loc::end).
Definition loc.h:46
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:95
constexpr Loc anew_begin() const
Definition loc.h:67
Byte offset into a Src; pass around as value.
Definition loc.h:18
constexpr Pos()=default
Creates an invalid Position.
Wrapper for char32_t with an operator<< that writes UTF-8.
Definition utf8.h:147