Config file and package tracking utility
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

411 lines
10 KiB

/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <algorithm> // replace
#include <cstddef> // size_t
#include <cstdint> // int8_t
#include <limits> // numeric_limits
#include <string>
#include <string_view>
#include "util/format/builder.h"
#include "util/format/formatter.h"
#include "util/format/parser.h"
#include "util/meta/assert.h"
namespace Util::Format {
Parser::Parser(std::string_view format, size_t parameterCount)
: GenericLexer(format)
, m_parameterCount(parameterCount)
{
checkFormatParameterConsistency();
}
Parser::~Parser()
{
}
// -----------------------------------------
void Parser::checkFormatParameterConsistency()
{
size_t braceOpen = 0;
size_t braceClose = 0;
while (!isEOF()) {
char peek0 = peek();
char peek1 = peek(1);
if (peek0 == '{' && peek1 == '{') {
ignore(2);
continue;
}
if (peek0 == '}' && peek1 == '}') {
ignore(2);
continue;
}
if (peek0 == '{') {
braceOpen++;
if (peek1 >= '0' && peek1 <= '9') {
m_mode = ArgumentIndexingMode::Manual;
}
}
if (peek0 == '}') {
braceClose++;
}
ignore();
}
m_index = 0;
VERIFY(!(braceOpen < braceClose), "extra open braces in format string");
VERIFY(!(braceOpen > braceClose), "extra closing braces in format string");
if (m_mode == ArgumentIndexingMode::Automatic) {
VERIFY(!(braceOpen < m_parameterCount), "format string does not reference all passed parameters");
VERIFY(!(braceOpen > m_parameterCount), "format string references nonexistent parameter");
}
}
size_t Parser::stringToNumber(std::string_view value)
{
size_t result = 0;
for (size_t i = 0; i < value.length(); ++i) {
VERIFY(value[i] >= '0' && value[i] <= '9', "unexpected '%c'", value[i]);
result *= 10;
result += value[i] - '0'; // Subtract ASCII 48 to get the number
}
return result;
}
std::string_view Parser::consumeLiteral()
{
const auto begin = tell();
while (!isEOF()) {
char peek0 = peek();
char peek1 = peek(1);
if (peek0 == '{' && peek1 == '{') {
ignore(2);
continue;
}
if (peek0 == '}' && peek1 == '}') {
ignore(2);
continue;
}
// Get literal before the specifier {}
if (peek0 == '{' || peek0 == '}') {
return m_input.substr(begin, tell() - begin);
}
ignore();
}
return m_input.substr(begin);
}
std::optional<size_t> Parser::consumeIndex()
{
if (!consumeSpecific('{')) {
VERIFY_NOT_REACHED();
return {};
}
switch (m_mode) {
case ArgumentIndexingMode::Automatic: {
VERIFY(consumeSpecific(':') || peek() == '}', "expecting ':' or '}', not '%c'", peek());
return {};
}
case ArgumentIndexingMode::Manual: {
const auto begin = tell();
while (!isEOF()) {
char peek0 = peek();
if (peek0 == '}' || peek0 == ':') {
break;
}
VERIFY(peek0 >= '0' && peek0 <= '9', "expecting number, not '%c'", peek0);
ignore();
}
size_t result = stringToNumber(m_input.substr(begin, tell() - begin));
if (peek() == ':') {
ignore();
}
return result;
}
};
VERIFY_NOT_REACHED();
}
void Parser::parseSpecifier(Specifier& specifier, ParameterType type)
{
if (consumeSpecific('}') || isEOF()) {
return;
}
// Alignment
char peek0 = peek();
char peek1 = peek(1);
if (peek1 == '<' || peek1 == '>' || peek1 == '^') {
specifier.fill = peek0;
specifier.align = static_cast<Builder::Align>(peek1);
ignore(2);
}
enum State {
AfterAlign,
AfterSign,
AfterAlternativeForm,
AfterZeroPadding,
AfterWidth,
AfterDot,
AfterPrecision,
AfterType,
} state { State::AfterAlign };
size_t widthBegin = std::numeric_limits<size_t>::max();
size_t precisionBegin = std::numeric_limits<size_t>::max();
size_t widthEnd = 0;
size_t precisionEnd = 0;
std::string_view acceptedTypes = "bdoxaefgscpBXAFG";
while (true) {
char peek0 = peek();
if (peek0 == '}') {
ignore();
break;
}
if (isEOF()) {
break;
}
// Sign is only valid for numeric types
if (peek0 == '+' || peek0 == '-' || peek0 == ' ') {
VERIFY(state < State::AfterSign, "unexpected '%c' at this position", peek0);
state = State::AfterSign;
specifier.sign = static_cast<Builder::Sign>(peek0);
}
// Alternative form is only valid for numeric types
if (peek0 == '#') {
VERIFY(state < State::AfterAlternativeForm, "unexpected '#' at this position");
state = State::AfterAlternativeForm;
specifier.alternativeForm = true;
}
// Sign aware zero padding is only valid for numeric types
if (peek0 == '0') {
if (state < State::AfterWidth) {
VERIFY(state < State::AfterZeroPadding, "unexpected '0' at this position");
state = State::AfterZeroPadding;
specifier.zeroPadding = true;
}
}
if (peek0 >= '0' && peek0 <= '9') {
if (widthBegin == std::numeric_limits<size_t>::max() && state < State::AfterDot) {
VERIFY(state < State::AfterWidth, "unexpected '%c' at this position", peek0);
state = State::AfterWidth;
widthBegin = tell();
}
if (precisionBegin == std::numeric_limits<size_t>::max() && state == State::AfterDot) {
state = State::AfterPrecision;
precisionBegin = tell();
}
}
if (peek0 == '.') {
if (state == State::AfterWidth) {
widthEnd = tell();
}
VERIFY(state < State::AfterDot, "unexpected '.' at this position");
state = State::AfterDot;
}
if ((peek0 >= 'a' && peek0 <= 'z')
|| (peek0 >= 'A' && peek0 <= 'Z')) {
if (state == State::AfterWidth) {
widthEnd = tell();
}
if (state == State::AfterPrecision) {
precisionEnd = tell();
}
VERIFY(state < State::AfterType, "unexpected '%c' at this position", peek0);
state = State::AfterType;
VERIFY(acceptedTypes.find(peek0) != std::string_view::npos, "unexpected '%c' at this position", peek0);
specifier.type = static_cast<PresentationType>(peek0);
}
ignore();
}
if (widthBegin != std::numeric_limits<size_t>::max()) {
if (widthEnd == 0) {
// We parse until after the closing '}', so take this into account
widthEnd = tell() - 1;
}
specifier.width = stringToNumber(m_input.substr(widthBegin, widthEnd - widthBegin));
}
if (precisionBegin != std::numeric_limits<size_t>::max()) {
if (precisionEnd == 0) {
// We parse until after the closing '}', so take this into account
precisionEnd = tell() - 1;
}
specifier.precision = static_cast<int8_t>(stringToNumber(m_input.substr(precisionBegin, precisionEnd - precisionBegin)));
}
checkSpecifierType(specifier, type);
}
constexpr void Parser::checkSpecifierIntegralType(const Specifier& specifier)
{
switch (specifier.type) {
case PresentationType::None:
case PresentationType::Binary:
case PresentationType::BinaryUppercase:
case PresentationType::Character:
case PresentationType::Decimal:
case PresentationType::Octal:
case PresentationType::Hex:
case PresentationType::HexUppercase:
break;
default:
VERIFY(false, "invalid type specifier");
};
// Invalid: precision
VERIFY(specifier.precision == -1, "invalid specifier option");
}
constexpr void Parser::checkSpecifierFloatingPointType(const Specifier& specifier)
{
switch (specifier.type) {
case PresentationType::None:
case PresentationType::Hexfloat:
case PresentationType::HexfloatUppercase:
case PresentationType::Exponent:
case PresentationType::ExponentUppercase:
case PresentationType::FixedPoint:
case PresentationType::FixedPointUppercase:
case PresentationType::General:
case PresentationType::GeneralUppercase:
break;
default:
VERIFY(false, "invalid type specifier");
}
}
constexpr void Parser::checkSpecifierCharType(const Specifier& specifier)
{
checkSpecifierIntegralType(specifier);
// Valid: fill + align, width
// Invalid: sign, alternativeForm, zeroPadding, precision
if (specifier.type == PresentationType::None
|| specifier.type == PresentationType::Character) {
VERIFY(specifier.sign == Builder::Sign::None, "invalid specifier option");
VERIFY(specifier.alternativeForm == false, "invalid specifier option");
VERIFY(specifier.zeroPadding == false, "invalid specifier option");
}
// Precision checked in Integral
}
constexpr void Parser::checkSpecifierCStringType(const Specifier& specifier)
{
switch (specifier.type) {
case PresentationType::None:
case PresentationType::String:
case PresentationType::Pointer:
break;
default:
VERIFY(false, "invalid type specifier");
}
// Valid: fill + align, width
// Invalid: sign, alternativeForm, zeroPadding, precision
VERIFY(specifier.sign == Builder::Sign::None, "invalid specifier option");
VERIFY(specifier.alternativeForm == false, "invalid specifier option");
VERIFY(specifier.zeroPadding == false, "invalid specifier option");
VERIFY(specifier.precision == -1, "invalid specifier option");
}
constexpr void Parser::checkSpecifierStringType(const Specifier& specifier)
{
checkSpecifierCStringType(specifier);
VERIFY(specifier.type != PresentationType::Pointer, "invalid type specifier");
}
constexpr void Parser::checkSpecifierPointerType(const Specifier& specifier)
{
checkSpecifierCStringType(specifier);
VERIFY(specifier.type != PresentationType::String, "invalid type specifier");
}
constexpr void Parser::checkSpecifierContainerType(const Specifier& specifier)
{
switch (specifier.type) {
case PresentationType::None:
case PresentationType::Container:
break;
default:
VERIFY(false, "invalid type specifier");
}
// Valid: fill + align, alternativeForm, width
// Invalid: sign, zeroPadding, precision
VERIFY(specifier.sign == Builder::Sign::None, "invalid specifier option");
VERIFY(specifier.zeroPadding == false, "invalid specifier option");
VERIFY(specifier.precision == -1, "invalid specifier option");
}
constexpr void Parser::checkSpecifierType(const Specifier& specifier, ParameterType type)
{
switch (type) {
case ParameterType::Integral:
checkSpecifierIntegralType(specifier);
break;
case ParameterType::FloatingPoint:
checkSpecifierFloatingPointType(specifier);
break;
case ParameterType::Char:
checkSpecifierCharType(specifier);
break;
case ParameterType::CString:
checkSpecifierCStringType(specifier);
break;
case ParameterType::String:
checkSpecifierStringType(specifier);
break;
case ParameterType::Pointer:
checkSpecifierPointerType(specifier);
break;
case ParameterType::Container:
checkSpecifierContainerType(specifier);
break;
default:
VERIFY_NOT_REACHED();
}
}
} // namespace Util::Format