/* Parsing driver.
Copyright (C) 2025 Free Software Foundation, Inc.
GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
. */
#include "elna/boot/driver.h"
#include
namespace elna::boot
{
source_position make_position(const yy::location& location)
{
auto start_location = boot::location(static_cast(location.begin.line),
static_cast(location.begin.column));
auto end_location = boot::location(static_cast(location.end.line),
static_cast(location.end.column));
return source_position(start_location, end_location);
}
syntax_error::syntax_error(const std::string& message, const yy::location& location)
: diagnostic(make_position(location)), message(message)
{
}
std::string syntax_error::what() const
{
return message;
}
static char unescape_character(char escape)
{
switch (escape)
{
case 'n':
return '\n';
case 't':
return '\t';
case 'f':
return '\f';
case 'r':
return '\r';
case 'v':
return '\v';
case '\\':
return '\\';
case '\'':
return '\'';
case '"':
return '"';
case '0':
return '\0';
default:
return escape_invalid_char;
}
}
static bool is_hex(char character)
{
return (character >= '0' && character <= '9')
|| (character >= 'a' && character <= 'f')
|| (character >= 'A' && character <= 'F');
}
constexpr unsigned char utf8_lead_tag_2byte = 0xC0;
constexpr unsigned char utf8_lead_tag_3byte = 0xE0;
constexpr unsigned char utf8_lead_tag_4byte = 0xF0;
constexpr int utf8_continuation_bits = 6;
constexpr uint32_t utf8_continuation_mask = 0x3F;
constexpr unsigned char utf8_continuation_tag = 0x80;
constexpr uint32_t utf8_surrogate_range_start = 0xD800;
constexpr uint32_t utf8_surrogate_range_end = 0xDFFF;
constexpr uint32_t utf8_max_unicode_codepoint = 0x10FFFF;
static std::optional encode_utf8(uint32_t codepoint)
{
constexpr uint32_t utf8_1byte_max = 0x80;
constexpr uint32_t utf8_2byte_max = 0x800;
constexpr uint32_t utf8_3byte_max = 0x10000;
std::string result;
if ((codepoint >= utf8_surrogate_range_start && codepoint <= utf8_surrogate_range_end)
|| codepoint > utf8_max_unicode_codepoint)
{
return std::nullopt;
}
else if (codepoint < utf8_1byte_max)
{
result.push_back(static_cast(codepoint));
}
else if (codepoint < utf8_2byte_max)
{
result.push_back(static_cast(utf8_continuation_tag | (codepoint & utf8_continuation_mask)));
result.push_back(static_cast(utf8_lead_tag_2byte | (codepoint >> utf8_continuation_bits)));
}
else if (codepoint < utf8_3byte_max)
{
for (int i = 0; i < 2; ++i)
{
result.push_back(static_cast(utf8_continuation_tag | (codepoint & utf8_continuation_mask)));
codepoint >>= utf8_continuation_bits;
}
result.push_back(static_cast(utf8_lead_tag_3byte | codepoint));
}
else
{
for (int i = 0; i < 3; ++i)
{
result.push_back(static_cast(utf8_continuation_tag | (codepoint & utf8_continuation_mask)));
codepoint >>= utf8_continuation_bits;
}
result.push_back(static_cast(utf8_lead_tag_4byte | codepoint));
}
return std::optional(result);
}
static std::optional> decode_utf8(const char *current_position)
{
constexpr unsigned char lead_tag_4byte_test = 0xF8;
constexpr uint32_t lead_payload_mask_2byte = 0x1F;
constexpr uint32_t lead_payload_mask_3byte = 0x0F;
constexpr uint32_t lead_payload_mask_4byte = 0x07;
constexpr unsigned char ascii_bit_mask = 0x80;
const unsigned char lead = static_cast(current_position[0]);
std::ptrdiff_t extra;
uint32_t codepoint;
if ((lead & ascii_bit_mask) == 0x00)
{
extra = 0;
codepoint = lead;
}
else if ((lead & utf8_lead_tag_3byte) == utf8_lead_tag_2byte)
{
extra = 1;
codepoint = lead & lead_payload_mask_2byte;
}
else if ((lead & utf8_lead_tag_4byte) == utf8_lead_tag_3byte)
{
extra = 2;
codepoint = lead & lead_payload_mask_3byte;
}
else if ((lead & lead_tag_4byte_test) == utf8_lead_tag_4byte)
{
extra = 3;
codepoint = lead & lead_payload_mask_4byte;
}
else
{
return std::nullopt;
}
for (int i = 1; i <= extra; ++i)
{
const unsigned char continuation_byte = static_cast(current_position[i]);
if ((continuation_byte & utf8_lead_tag_2byte) != utf8_continuation_tag)
{
return std::nullopt;
}
codepoint = (codepoint << utf8_continuation_bits) | (continuation_byte & utf8_continuation_mask);
}
return std::make_pair(codepoint, extra + 1);
}
static std::optional> parse_codepoint_notation(const char *current_position)
{
if (current_position[0] != 'U' || current_position[1] != '+')
{
return std::nullopt;
}
current_position += 2;
std::ptrdiff_t consumed{ 2 };
const char *digits_start = current_position;
while (is_hex(*current_position))
{
++current_position;
}
constexpr std::size_t max_digits = 8; // Unicode maximum is U+10FFFF.
consumed += current_position - digits_start;
if (current_position == digits_start || std::cmp_greater(consumed, max_digits))
{
return std::nullopt;
}
const std::string hex_digits(digits_start, current_position);
const std::uint32_t codepoint = static_cast(std::stoul(hex_digits, nullptr, 16));
if ((codepoint >= utf8_surrogate_range_start && codepoint <= utf8_surrogate_range_end)
|| codepoint > utf8_max_unicode_codepoint)
{
return std::nullopt;
}
return std::make_pair(codepoint, consumed);
}
static std::ptrdiff_t decode_one_character(const char *current_position, std::string& output)
{
if (current_position[0] == '\\' && current_position[1] == 'x')
{
current_position += 2;
if (!is_hex(current_position[0]) || !is_hex(current_position[1]))
{
return -1;
}
const std::string hex_digits(current_position, 2);
const char character = static_cast(std::stoi(hex_digits, nullptr, 16));
output.push_back(character);
return 4;
}
else if (current_position[0] == '\\' && current_position[1] == '{')
{
current_position += 2;
auto unescaped = parse_codepoint_notation(current_position);
if (!unescaped.has_value())
{
return -1;
}
const auto [codepoint, consumed] = unescaped.value();
if (current_position[consumed] != '}')
{
return -1;
}
if (auto encoded = encode_utf8(codepoint))
{
std::ranges::reverse_copy(encoded.value(), std::back_inserter(output));
return consumed + 3;
}
else
{
return -1;
}
}
else if (*current_position == '\\')
{
++current_position;
const char escape = unescape_character(*current_position);
if (escape == escape_invalid_char)
{
return -1;
}
output.push_back(escape);
return 2;
}
else
{
output.push_back(*current_position);
return 1;
}
}
std::optional parse_character_literal(const char *escape)
{
const char *current_position = escape + 1;
std::uint32_t result{ 0 };
if (current_position[0] == '\\' && current_position[1] == '{')
{
current_position += 2;
auto unescaped = parse_codepoint_notation(current_position);
if (!unescaped.has_value())
{
return std::nullopt;
}
const auto [codepoint, consumed] = unescaped.value();
if (current_position[consumed] != '}')
{
return std::nullopt;
}
result = codepoint;
current_position += consumed + 1;
}
else if (current_position[0] == '\\')
{
char const escape = unescape_character(current_position[1]);
if (escape == escape_invalid_char)
{
return std::nullopt;
}
current_position += 2;
result = static_cast(static_cast(escape));
}
else
{
if (auto decoded = decode_utf8(current_position))
{
result = decoded->first;
current_position += decoded->second;
}
else
{
return std::nullopt;
}
}
return *current_position == '`' ? std::optional(result) : std::nullopt;
}
std::optional parse_byte_literal(const char *escape)
{
std::string result;
const char *current_position = escape + 1;
const ptrdiff_t consumed = decode_one_character(current_position, result);
// Unicode consumes more than 4 characters and is not supported here.
if (consumed == -1 || consumed > 4 || current_position[consumed] != '\'')
{
return std::nullopt;
}
else
{
return static_cast(result.at(0));
}
}
std::optional parse_string_literal(const char *escape)
{
std::string result;
const char *current_position = escape + 1;
while (*current_position != '\0')
{
const ptrdiff_t consumed = decode_one_character(current_position, result);
if (consumed == -1)
{
return std::nullopt;
}
else
{
current_position += consumed;
}
}
result.pop_back(); // Remove the terminating quote character.
return result;
}
}