5#include <unordered_map>
14const std::unordered_map<std::string, TokenType> keywords = {
29[[nodiscard]]
bool is_identifier_start(
char c)
31 return std::isalpha(
static_cast<unsigned char>(c)) != 0 || c ==
'_';
34[[nodiscard]]
bool is_identifier_char(
char c)
36 return std::isalnum(
static_cast<unsigned char>(c)) != 0 || c ==
'_';
39[[nodiscard]]
bool is_digit(
char c)
41 return std::isdigit(
static_cast<unsigned char>(c)) != 0;
44[[nodiscard]]
bool is_hex_digit(
char c)
46 return std::isxdigit(
static_cast<unsigned char>(c)) != 0;
49[[nodiscard]]
bool is_octal_digit(
char c)
51 return c >=
'0' && c <=
'7';
54[[nodiscard]]
bool is_binary_digit(
char c)
56 return c ==
'0' || c ==
'1';
95 return "integer_literal";
97 return "string_literal";
99 return "char_literal";
172Lexer::Lexer(gsl::not_null<const TextFormatter *> format, std::string content)
173 : format_{format}, content_{std::move(content)}
178 : format_{format}, content_{std::move(content)}, context_{context}
184 if (context_ !=
nullptr) {
185 return context_->
make_error(pos, std::move(message));
187 return FormattableError{format_->
format(
"{}:{}: {}", pos.
line, pos.
column, message)};
192 std::vector<Token> tokens;
194 while (!is_at_end()) {
195 skip_whitespace_except_newline();
206 char next = peek_next();
212 if (next ==
'\r' && current_ + 2 < content_.size() && content_[current_ + 2] ==
'\n') {
229 if (peek_next() ==
'/') {
233 if (peek_next() ==
'*') {
234 auto result = skip_block_comment();
235 if (!result.has_value()) {
250 if (is_identifier_start(c)) {
251 tokens.push_back(consume_identifier_or_keyword());
257 auto result = consume_number();
258 if (!result.has_value()) {
261 tokens.push_back(std::move(result).value());
267 auto result = consume_string();
268 if (!result.has_value()) {
271 tokens.push_back(std::move(result).value());
276 tokens.push_back(consume_operator());
283char Lexer::peek()
const
288 return content_[current_];
291char Lexer::peek_next()
const
293 if (current_ + 1 >= content_.size()) {
296 return content_[current_ + 1];
304 char c = content_[current_];
316bool Lexer::is_at_end()
const
318 return current_ >= content_.size();
321void Lexer::skip_whitespace_except_newline()
323 while (!is_at_end()) {
325 if (c ==
' ' || c ==
'\t' || c ==
'\r') {
334void Lexer::skip_line_comment()
340 while (!is_at_end() && peek() !=
'\n') {
346ChainableResult<void> Lexer::skip_block_comment()
348 SourcePosition start_pos = current_position();
354 while (!is_at_end()) {
355 if (peek() ==
'*' && peek_next() ==
'/') {
363 return make_error(start_pos,
"unterminated block comment");
366Token Lexer::consume_identifier_or_keyword()
368 SourcePosition start_pos = current_position();
371 while (!is_at_end() && is_identifier_char(peek())) {
375 auto it = keywords.find(text);
376 if (it != keywords.end()) {
377 return Token{it->second, std::move(text), start_pos};
383ChainableResult<Token> Lexer::consume_number()
385 SourcePosition start_pos = current_position();
389 if (peek() ==
'0' && !is_at_end()) {
394 if (next ==
'x' || next ==
'X') {
396 if (!is_hex_digit(peek())) {
397 return make_error(start_pos, format_->
format(
"invalid hexadecimal literal '{}'", text));
399 while (!is_at_end() && is_hex_digit(peek())) {
403 while (!is_at_end() && (peek() ==
'u' || peek() ==
'U' || peek() ==
'l' || peek() ==
'L')) {
406 std::int64_t value = 0;
407 auto [ptr, ec] = std::from_chars(text.data() + 2, text.data() + text.size(), value, 16);
408 if (ec != std::errc{}) {
409 return make_error(start_pos, format_->
format(
"invalid hexadecimal literal '{}'", text));
411 return Token{std::move(text), value, start_pos};
415 if (next ==
'b' || next ==
'B') {
417 if (!is_binary_digit(peek())) {
418 return make_error(start_pos, format_->
format(
"invalid binary literal '{}'", text));
420 while (!is_at_end() && is_binary_digit(peek())) {
424 while (!is_at_end() && (peek() ==
'u' || peek() ==
'U' || peek() ==
'l' || peek() ==
'L')) {
427 std::int64_t value = 0;
428 auto [ptr, ec] = std::from_chars(text.data() + 2, text.data() + text.size(), value, 2);
429 if (ec != std::errc{}) {
430 return make_error(start_pos, format_->
format(
"invalid binary literal '{}'", text));
432 return Token{std::move(text), value, start_pos};
436 if (is_octal_digit(next)) {
437 while (!is_at_end() && is_octal_digit(peek())) {
441 while (!is_at_end() && (peek() ==
'u' || peek() ==
'U' || peek() ==
'l' || peek() ==
'L')) {
444 std::int64_t value = 0;
445 auto [ptr, ec] = std::from_chars(text.data(), text.data() + text.size(), value, 8);
446 if (ec != std::errc{}) {
447 return make_error(start_pos, format_->
format(
"invalid octal literal '{}'", text));
449 return Token{std::move(text), value, start_pos};
453 return Token{std::move(text), 0, start_pos};
457 while (!is_at_end() && is_digit(peek())) {
461 while (!is_at_end() && (peek() ==
'u' || peek() ==
'U' || peek() ==
'l' || peek() ==
'L')) {
465 std::int64_t value = 0;
466 auto [ptr, ec] = std::from_chars(text.data(), text.data() + text.size(), value, 10);
467 if (ec != std::errc{}) {
468 return make_error(start_pos, format_->
format(
"invalid decimal literal '{}'", text));
470 return Token{std::move(text), value, start_pos};
473ChainableResult<Token> Lexer::consume_string()
475 SourcePosition start_pos = current_position();
481 while (!is_at_end() && peek() !=
'"') {
486 return make_error(start_pos,
"unterminated string literal");
490 if (c ==
'\\' && !is_at_end()) {
493 char escaped = advance();
528 return make_error(start_pos,
"unterminated string literal");
538Token Lexer::consume_operator()
540 SourcePosition start_pos = current_position();
636SourcePosition Lexer::current_position()
const
638 return SourcePosition{line_, column_};
Context object providing rich error formatting for C/C++ parsing.
FormattableError make_error(SourcePosition pos, const std::string &message) const
Creates a FormattableError with source context.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
ChainableResult< std::vector< Token > > lex()
Tokenizes the entire source content.
Lexer(gsl::not_null< const TextFormatter * > format, std::string content)
Constructs a lexer for the given source content.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > ¶ms) const
Formats a string with styled parameters using fmtlib syntax.
std::string token_type_name(TokenType type)
Returns a human-readable name for a token type.
TokenType
Enumeration of token types recognized by the C parser lexer.
Represents a position within source content.
std::size_t line
1-based line number
std::size_t column
1-based column number