29template<
size_t K,
class S>
32 S& self() {
return *
static_cast<S*
>(
this); }
33 const S& self()
const {
return *
static_cast<const S*
>(
this); }
40 { s.next() } -> std::convertible_to<char32_t>;
41 },
"`S::next` must yield the code point it consumed");
47 :
Lexer(buf, nullptr) {}
49 :
Lexer(src.buf(), &src) {}
56 if (
buf_.starts_with(utf8::Bom))
cursor_ = utf8::Bom.size();
57 for (
size_t i = 0; i != K; ++i)
64 char32_t c = utf8::EoF;
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';
96 std::string
fold(
char32_t (*f)(
char32_t)
noexcept)
const {
97 std::string res(
view());
99 c = (char)f((
char32_t)(uint8_t)c);
111 return ahead_.put(decode()).c;
115 bool accept(std::predicate<char32_t>
auto pred) {
116 if (!pred(
ahead()))
return false;
122 bool accept(
char32_t c) {
return accept([c](
char32_t d) {
return c == d; }); }
135 auto begin =
ahead_[0].begin.off;
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) {}
141 skip_to(
ahead_[0].begin.off, run);
144 if (c < 0x80 || c == utf8::EoF || !pred(c))
break;
148 return buf_.substr(begin,
loc_.end.off - begin);
160 assert(a < 0x80 && b < 0x80 &&
"only an ASCII byte can be searched for without decoding");
161 auto begin =
ahead_[0].begin.off;
164 for (
auto e =
buf_.size(); run != e; ++run)
165 if (
auto c = (
char8_t)
buf_[run]; c == a || c == b)
break;
167 return skip_to(begin, run);
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);
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);
199 if (!
accept(utf8::Invalid))
return false;
200 while (
accept(utf8::Invalid)) {}
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");
230 static_assert(
Diagnosable<S>,
"provide `fe::Driver& driver()` in your lexer - or a `char_err` of your own");
244 std::string_view skip_to(
size_t begin,
size_t run) {
248 for (
size_t i = 0; i != K; ++i)
252 return buf_.substr(begin, run - begin);
256 auto begin = cursor_;
258 return {c,
Pos((uint32_t)begin),
Pos((uint32_t)cursor_)};
Collects diagnostics and hands each to the Diag that lays it out.
Error & e(Loc loc, cite_string< Args... > s, Args &&... args)
char32_t next()
Get next char32_t in Lexer::buf_ and extend Lexer::loc_ to cover it.
void start()
Invoke before assembling the next token.
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...
Loc loc_
Location of the token we are currently constructing - see Lexer::view.
std::string_view accept_while(std::predicate< char32_t > auto pred)
Lexer::next as long as pred holds.
Lexer(std::string_view buf)
std::string_view accept_until(std::string_view seq)
Lexer::next up to - but not including - the next occurrence of seq.
Loc peek() const
Location of the next character to be consumed (Lexer::ahead()); empty once the buffer is exhausted.
size_t cursor_
Byte offset of the first not yet decoded character.
bool accept(std::predicate< char32_t > auto pred)
Accept next character in Lexer::buf_ and Lexer::next it, if pred holds.
const fe::Error & error() const
char32_t ahead(size_t i=0) const
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.
std::string_view view() const
std::string lower() const
Lexer::view, case-folded - what a case-insensitive language like FORTRAN or SQL wants to intern.
fe::Error & char_err(char32_t c)
Lexer::recover_char discarded c at Lexer::loc_.
Lexer(std::string_view buf, const Src *src)
Delegate here to funnel both of the above into a single ctor of your own.
void recover_char()
One character, reported as S::char_err.
fe::Error & utf8_err()
Lexer::recover_utf8 discarded the malformed bytes at Lexer::loc_.
std::string fold(char32_t(*f)(char32_t) noexcept) const
bool eol() const
Is Lexer::ahead the end of a line - \n, \r\n, or utf8::EoF?
std::string upper() const
A decoded code point together with the byte range it occupies.
A ring buffer with N elements.
T put(T item)
Puts item into buffer.
The content of one source file together with the offsets its rows start at.
Every frontend building block reports into the Error of S::driver.
char32_t decode(std::istream &is)
Decodes the next UTF-8 sequence from is into a single char32_t.
constexpr char32_t toupper(char32_t c) noexcept
constexpr char32_t tolower(char32_t c) noexcept
Location within a Src: the half-open byte range [Loc::begin, Loc::end).
Pos end
It's called end because - just like an STL iterator - it refers to the byte one past the last one wit...
Byte offset into a Src; pass around as value.
constexpr Pos() noexcept=default
Creates an invalid Position.
Wrapper for char32_t with an operator<< that writes UTF-8.