Browse Source
Add all utility and unit test code from the riyyi/manafiles project at commit 6f0e3d6063ab75ad81899135689569e440ddb813, link at: github.com/riyyi/manafiles/tree/6f0e3d6063ab75ad81899135689569e440ddb813master
Riyyi
2 years ago
59 changed files with 8446 additions and 0 deletions
@ -0,0 +1,568 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021-2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <algorithm> // find_if |
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdint> // uint8_t |
||||||
|
#include <cstdio> // printf |
||||||
|
#include <cstring> // strcmp |
||||||
|
#include <limits> // numeric_limits |
||||||
|
#include <string> // stod, stoi, stoul |
||||||
|
#include <string_view> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "util/argparser.h" |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
ArgParser::ArgParser() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
ArgParser::~ArgParser() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::printError(char parameter, Error error) |
||||||
|
{ |
||||||
|
char tmp[] { parameter, '\0' }; |
||||||
|
printError(tmp, error, false); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::printError(const char* parameter, Error error, bool longName) |
||||||
|
{ |
||||||
|
if (!m_errorReporting) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (error == Error::OptionInvalid) { |
||||||
|
printf("%s: invalid option -- '%s'\n", m_name, parameter); |
||||||
|
} |
||||||
|
else if (error == Error::OptionUnrecognized) { |
||||||
|
printf("%s: unrecognized option -- '%s'\n", m_name, parameter); |
||||||
|
} |
||||||
|
else if (error == Error::OptionDoesntAllowArgument) { |
||||||
|
printf("%s: option '--%s' doesn't allow an argument\n", m_name, parameter); |
||||||
|
} |
||||||
|
else if (error == Error::OptionRequiresArgument) { |
||||||
|
if (longName) { |
||||||
|
printf("%s: option '--%s' requires an argument\n", m_name, parameter); |
||||||
|
} |
||||||
|
else { |
||||||
|
printf("%s: option requires an argument -- '%s'\n", m_name, parameter); |
||||||
|
} |
||||||
|
} |
||||||
|
else if (error == Error::OptionInvalidArgumentType) { |
||||||
|
if (longName) { |
||||||
|
printf("%s: option '--%s' invalid type\n", m_name, parameter); |
||||||
|
} |
||||||
|
else { |
||||||
|
printf("%s: option invalid type -- '%s'\n", m_name, parameter); |
||||||
|
} |
||||||
|
} |
||||||
|
else if (error == Error::ArgumentExtraOperand) { |
||||||
|
printf("%s: extra operand '%s'\n", m_name, parameter); |
||||||
|
} |
||||||
|
else if (error == Error::ArgumentRequired) { |
||||||
|
printf("%s: missing required argument '%s'\n", m_name, parameter); |
||||||
|
} |
||||||
|
else if (error == Error::ArgumentInvalidType) { |
||||||
|
printf("%s: invalid argument type '%s'\n", m_name, parameter); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: Print command usage, if it's enabled.
|
||||||
|
} |
||||||
|
|
||||||
|
// Required: directly after || separated by space
|
||||||
|
// Optional: directly after
|
||||||
|
bool ArgParser::parseShortOption(std::string_view option, std::string_view next) |
||||||
|
{ |
||||||
|
bool result = true; |
||||||
|
|
||||||
|
#ifndef NDEBUG |
||||||
|
printf("Parsing short option: '%s'\n", option.data()); |
||||||
|
#endif |
||||||
|
|
||||||
|
char c; |
||||||
|
std::string_view value; |
||||||
|
for (size_t i = 0; i < option.size(); ++i) { |
||||||
|
c = option.at(i); |
||||||
|
|
||||||
|
#ifndef NDEBUG |
||||||
|
printf("short '%c'\n", c); |
||||||
|
#endif |
||||||
|
|
||||||
|
auto foundOption = std::find_if(m_options.begin(), m_options.end(), [&c](Option& it) -> bool { |
||||||
|
return it.shortName == c; |
||||||
|
}); |
||||||
|
|
||||||
|
// Option does not exist
|
||||||
|
if (foundOption == m_options.cend()) { |
||||||
|
printError(c, Error::OptionInvalid); |
||||||
|
|
||||||
|
if (m_exitOnFirstError) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (foundOption->requiresArgument == Required::No) { |
||||||
|
// FIXME: Figure out why providing a nullptr breaks the lambda here.
|
||||||
|
result = foundOption->acceptValue(""); |
||||||
|
} |
||||||
|
else if (foundOption->requiresArgument == Required::Yes) { |
||||||
|
value = option.substr(i + 1); |
||||||
|
if (value.empty() && next.empty()) { |
||||||
|
foundOption->error = Error::OptionRequiresArgument; |
||||||
|
printError(c, Error::OptionRequiresArgument); |
||||||
|
result = false; |
||||||
|
} |
||||||
|
else if (!value.empty()) { |
||||||
|
result = foundOption->acceptValue(value.data()); |
||||||
|
if (!result) { |
||||||
|
printError(c, Error::OptionInvalidArgumentType); |
||||||
|
} |
||||||
|
} |
||||||
|
else if (next[0] == '-') { |
||||||
|
foundOption->error = Error::OptionRequiresArgument; |
||||||
|
printError(c, Error::OptionRequiresArgument); |
||||||
|
result = false; |
||||||
|
} |
||||||
|
else { |
||||||
|
result = foundOption->acceptValue(next.data()); |
||||||
|
m_optionIndex++; |
||||||
|
if (!result) { |
||||||
|
printError(c, Error::OptionInvalidArgumentType); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
else if (foundOption->requiresArgument == Required::Optional) { |
||||||
|
value = option.substr(i + 1); |
||||||
|
if (!value.empty()) { |
||||||
|
result = foundOption->acceptValue(value.data()); |
||||||
|
if (!result) { |
||||||
|
printError(c, Error::OptionInvalidArgumentType); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
// Required: directly after, separated by '=' || separated by space
|
||||||
|
// Optional: directly after, separated by '='
|
||||||
|
bool ArgParser::parseLongOption(std::string_view option, std::string_view next) |
||||||
|
{ |
||||||
|
std::string name = std::string(option.substr(0, option.find_first_of('='))); |
||||||
|
std::string_view value = option.substr(option.find_first_of('=') + 1); |
||||||
|
|
||||||
|
auto foundOption = std::find_if(m_options.begin(), m_options.end(), [&name](Option& it) -> bool { |
||||||
|
return it.longName && it.longName == name; |
||||||
|
}); |
||||||
|
|
||||||
|
if (foundOption == m_options.cend()) { |
||||||
|
printError(name.data(), Error::OptionUnrecognized); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
enum class ArgumentProvided : uint8_t { |
||||||
|
No, |
||||||
|
DirectlyAfter, |
||||||
|
Seperated, |
||||||
|
}; |
||||||
|
|
||||||
|
auto argument = ArgumentProvided::No; |
||||||
|
if (name != value || option.find('=') != std::string_view::npos) { |
||||||
|
argument = ArgumentProvided::DirectlyAfter; |
||||||
|
} |
||||||
|
else if (!next.empty() && next[0] != '-') { |
||||||
|
argument = ArgumentProvided::Seperated; |
||||||
|
value = next; |
||||||
|
} |
||||||
|
|
||||||
|
bool result = true; |
||||||
|
|
||||||
|
if (foundOption->requiresArgument == Required::No) { |
||||||
|
if (argument == ArgumentProvided::DirectlyAfter) { |
||||||
|
foundOption->error = Error::OptionDoesntAllowArgument; |
||||||
|
printError(name.data(), Error::OptionDoesntAllowArgument); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
result = foundOption->acceptValue(""); |
||||||
|
} |
||||||
|
else if (foundOption->requiresArgument == Required::Yes) { |
||||||
|
if (argument == ArgumentProvided::No) { |
||||||
|
foundOption->error = Error::OptionRequiresArgument; |
||||||
|
printError(name.data(), Error::OptionRequiresArgument); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
result = foundOption->acceptValue(value.data()); |
||||||
|
if (!result) { |
||||||
|
printError(name.data(), Error::OptionInvalidArgumentType); |
||||||
|
} |
||||||
|
|
||||||
|
if (argument == ArgumentProvided::Seperated) { |
||||||
|
m_optionIndex++; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (foundOption->requiresArgument == Required::Optional) { |
||||||
|
if (argument == ArgumentProvided::DirectlyAfter) { |
||||||
|
result = foundOption->acceptValue(value.data()); |
||||||
|
if (!result) { |
||||||
|
printError(name.data(), Error::OptionInvalidArgumentType); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
bool ArgParser::parseArgument(std::string_view argument) |
||||||
|
{ |
||||||
|
bool result = true; |
||||||
|
|
||||||
|
for (;;) { |
||||||
|
// Run out of argument handlers
|
||||||
|
if (m_argumentIndex >= m_arguments.size()) { |
||||||
|
printError(argument.data(), Error::ArgumentExtraOperand); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
Argument& currentArgument = m_arguments.at(m_argumentIndex); |
||||||
|
result = currentArgument.acceptValue(argument.data()); |
||||||
|
|
||||||
|
if (result) { |
||||||
|
currentArgument.addedValues++; |
||||||
|
if (currentArgument.addedValues >= currentArgument.maxValues) { |
||||||
|
m_argumentIndex++; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (currentArgument.minValues == 0) { |
||||||
|
m_argumentIndex++; |
||||||
|
continue; |
||||||
|
} |
||||||
|
else { |
||||||
|
printError(argument.data(), Error::ArgumentInvalidType); |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
bool ArgParser::parse(int argc, const char* argv[]) |
||||||
|
{ |
||||||
|
bool result = true; |
||||||
|
|
||||||
|
// Set looping indices
|
||||||
|
m_optionIndex = 1; |
||||||
|
m_argumentIndex = 0; |
||||||
|
|
||||||
|
// By default parse all '-' prefixed parameters as options
|
||||||
|
m_nonOptionMode = false; |
||||||
|
|
||||||
|
// Get program name
|
||||||
|
m_name = argv[0] + std::string_view(argv[0]).find_last_of('/') + 1; |
||||||
|
|
||||||
|
std::string_view argument; |
||||||
|
std::string_view next; |
||||||
|
for (; m_optionIndex < (size_t)argc; ++m_optionIndex) { |
||||||
|
|
||||||
|
#ifndef NDEBUG |
||||||
|
printf("argv[%zu]: %s\n", m_optionIndex, argv[m_optionIndex]); |
||||||
|
#endif |
||||||
|
|
||||||
|
// Get the current and next parameter
|
||||||
|
argument = argv[m_optionIndex]; |
||||||
|
if (m_optionIndex + 1 < (size_t)argc && argv[m_optionIndex + 1][0] != '-') { |
||||||
|
next = argv[m_optionIndex + 1]; |
||||||
|
} |
||||||
|
else { |
||||||
|
next = {}; |
||||||
|
} |
||||||
|
|
||||||
|
// Stop parsing '-' prefixed parameters as options afer '--'
|
||||||
|
if (argument.compare("--") == 0) { |
||||||
|
m_nonOptionMode = true; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// Long Option
|
||||||
|
if (!m_nonOptionMode && argument[0] == '-' && argument[1] == '-') { |
||||||
|
argument = argument.substr(argument.find_first_not_of('-')); |
||||||
|
if (!parseLongOption(argument, next)) { |
||||||
|
result = false; |
||||||
|
} |
||||||
|
} |
||||||
|
// Short Option
|
||||||
|
else if (!m_nonOptionMode && argument[0] == '-') { |
||||||
|
argument = argument.substr(argument.find_first_not_of('-')); |
||||||
|
if (!parseShortOption(argument, next)) { |
||||||
|
result = false; |
||||||
|
} |
||||||
|
} |
||||||
|
// Argument
|
||||||
|
else { |
||||||
|
if (m_stopParsingOnFirstNonOption) { |
||||||
|
m_nonOptionMode = true; |
||||||
|
} |
||||||
|
if (!parseArgument(argument)) { |
||||||
|
result = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (m_exitOnFirstError && !result) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Check any leftover arguments for required
|
||||||
|
for (; m_argumentIndex < m_arguments.size(); ++m_argumentIndex) { |
||||||
|
Argument& currentArgument = m_arguments.at(m_argumentIndex); |
||||||
|
if (currentArgument.minValues > currentArgument.addedValues) { |
||||||
|
result = false; |
||||||
|
printError(currentArgument.name ? currentArgument.name : "", Error::ArgumentRequired); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (result) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
for (auto& option : m_options) { |
||||||
|
if (option.longName && strcmp(option.longName, "help") == 0) { |
||||||
|
printf("Try '%s --help' for more information.\n", m_name); |
||||||
|
break; |
||||||
|
} |
||||||
|
if (option.shortName == 'h') { |
||||||
|
printf("Try '%s -h' for more information.\n", m_name); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
void ArgParser::addOption(Option&& option) |
||||||
|
{ |
||||||
|
m_options.push_back(option); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString) |
||||||
|
{ |
||||||
|
addOption({ shortName, longName, nullptr, usageString, manString, Required::No, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addOption(const char*& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||||
|
{ |
||||||
|
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addOption(std::string& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||||
|
{ |
||||||
|
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addOption(std::string_view& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||||
|
{ |
||||||
|
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addOption(int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||||
|
{ |
||||||
|
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addOption(unsigned int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||||
|
{ |
||||||
|
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addOption(double& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||||
|
{ |
||||||
|
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addOption(std::vector<std::string>& values, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument) |
||||||
|
{ |
||||||
|
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(values) }); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
void ArgParser::addArgument(Argument&& argument) |
||||||
|
{ |
||||||
|
m_arguments.push_back(argument); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addArgument(bool& value, const char* name, const char* usageString, const char* manString, Required required) |
||||||
|
{ |
||||||
|
size_t minValues = required == Required::Yes ? 1 : 0; |
||||||
|
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addArgument(const char*& value, const char* name, const char* usageString, const char* manString, Required required) |
||||||
|
{ |
||||||
|
size_t minValues = required == Required::Yes ? 1 : 0; |
||||||
|
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addArgument(std::string& value, const char* name, const char* usageString, const char* manString, Required required) |
||||||
|
{ |
||||||
|
size_t minValues = required == Required::Yes ? 1 : 0; |
||||||
|
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addArgument(std::string_view& value, const char* name, const char* usageString, const char* manString, Required required) |
||||||
|
{ |
||||||
|
size_t minValues = required == Required::Yes ? 1 : 0; |
||||||
|
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addArgument(int& value, const char* name, const char* usageString, const char* manString, Required required) |
||||||
|
{ |
||||||
|
size_t minValues = required == Required::Yes ? 1 : 0; |
||||||
|
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addArgument(unsigned int& value, const char* name, const char* usageString, const char* manString, Required required) |
||||||
|
{ |
||||||
|
size_t minValues = required == Required::Yes ? 1 : 0; |
||||||
|
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addArgument(double& value, const char* name, const char* usageString, const char* manString, Required required) |
||||||
|
{ |
||||||
|
size_t minValues = required == Required::Yes ? 1 : 0; |
||||||
|
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) }); |
||||||
|
} |
||||||
|
|
||||||
|
void ArgParser::addArgument(std::vector<std::string>& values, const char* name, const char* usageString, const char* manString, Required required) |
||||||
|
{ |
||||||
|
size_t minValues = required == Required::Yes ? 1 : 0; |
||||||
|
addArgument({ name, usageString, manString, minValues, values.max_size(), 0, getAcceptFunction(values) }); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
AcceptFunction ArgParser::getAcceptFunction(bool& value) |
||||||
|
{ |
||||||
|
return [&value](const char*) -> bool { |
||||||
|
value = true; |
||||||
|
return true; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
AcceptFunction ArgParser::getAcceptFunction(const char*& value) |
||||||
|
{ |
||||||
|
return [&value](const char* input) -> bool { |
||||||
|
value = input; |
||||||
|
return true; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
AcceptFunction ArgParser::getAcceptFunction(std::string& value) |
||||||
|
{ |
||||||
|
return [&value](const char* input) -> bool { |
||||||
|
value = input; |
||||||
|
return true; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
AcceptFunction ArgParser::getAcceptFunction(std::string_view& value) |
||||||
|
{ |
||||||
|
return [&value](const char* input) -> bool { |
||||||
|
value = input; |
||||||
|
return true; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
AcceptFunction ArgParser::getAcceptFunction(int& value) |
||||||
|
{ |
||||||
|
return [&value](const char* input) -> bool { |
||||||
|
const char* validate = input; |
||||||
|
for (; *validate != '\0'; ++validate) { |
||||||
|
// - [0-9]
|
||||||
|
if (*validate != 45 && (*validate < 48 || *validate > 57)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
value = std::stoi(input); |
||||||
|
return true; |
||||||
|
} |
||||||
|
catch (...) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
AcceptFunction ArgParser::getAcceptFunction(unsigned int& value) |
||||||
|
{ |
||||||
|
return [&value](const char* input) -> bool { |
||||||
|
const char* validate = input; |
||||||
|
for (; *validate != '\0'; ++validate) { |
||||||
|
// [0-9]
|
||||||
|
if (*validate < 48 || *validate > 57) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
unsigned long convert = 0; |
||||||
|
try { |
||||||
|
convert = std::stoul(input); |
||||||
|
} |
||||||
|
catch (...) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (convert <= std::numeric_limits<unsigned int>::max()) { |
||||||
|
value = static_cast<unsigned int>(convert); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
AcceptFunction ArgParser::getAcceptFunction(double& value) |
||||||
|
{ |
||||||
|
return [&value](const char* input) -> bool { |
||||||
|
const char* validate = input; |
||||||
|
for (; *validate != '\0'; ++validate) { |
||||||
|
// . [0-9]
|
||||||
|
if (*validate != 46 && (*validate < 48 || *validate > 57)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
value = std::stod(input); |
||||||
|
return true; |
||||||
|
} |
||||||
|
catch (...) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
AcceptFunction ArgParser::getAcceptFunction(std::vector<std::string>& value) |
||||||
|
{ |
||||||
|
return [&value](const char* input) -> bool { |
||||||
|
value.push_back(input); |
||||||
|
return true; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,120 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdint> // uint8_t |
||||||
|
#include <functional> // function |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
using AcceptFunction = std::function<bool(const char*)>; |
||||||
|
|
||||||
|
class ArgParser final { |
||||||
|
public: |
||||||
|
ArgParser(); |
||||||
|
virtual ~ArgParser(); |
||||||
|
|
||||||
|
enum class Required : uint8_t { |
||||||
|
No, |
||||||
|
Yes, |
||||||
|
Optional, |
||||||
|
}; |
||||||
|
|
||||||
|
enum class Error : uint8_t { |
||||||
|
None, |
||||||
|
OptionInvalid, // For short options
|
||||||
|
OptionUnrecognized, // For long options
|
||||||
|
OptionDoesntAllowArgument, |
||||||
|
OptionRequiresArgument, |
||||||
|
OptionInvalidArgumentType, |
||||||
|
ArgumentExtraOperand, |
||||||
|
ArgumentRequired, |
||||||
|
ArgumentInvalidType, |
||||||
|
}; |
||||||
|
|
||||||
|
struct Option { |
||||||
|
char shortName { 0 }; |
||||||
|
const char* longName { nullptr }; |
||||||
|
const char* argumentName { nullptr }; |
||||||
|
const char* usageString { nullptr }; |
||||||
|
const char* manString { nullptr }; |
||||||
|
Required requiresArgument; |
||||||
|
AcceptFunction acceptValue; |
||||||
|
|
||||||
|
Error error = Error::None; |
||||||
|
}; |
||||||
|
|
||||||
|
struct Argument { |
||||||
|
const char* name { nullptr }; |
||||||
|
const char* usageString { nullptr }; |
||||||
|
const char* manString { nullptr }; |
||||||
|
size_t minValues { 0 }; |
||||||
|
size_t maxValues { 1 }; |
||||||
|
size_t addedValues { 0 }; |
||||||
|
AcceptFunction acceptValue; |
||||||
|
}; |
||||||
|
|
||||||
|
bool parse(int argc, const char* argv[]); |
||||||
|
|
||||||
|
void addOption(Option&& option); |
||||||
|
void addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString); |
||||||
|
void addOption(const char*& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||||
|
void addOption(std::string& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||||
|
void addOption(std::string_view& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||||
|
void addOption(int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||||
|
void addOption(unsigned int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||||
|
void addOption(double& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||||
|
void addOption(std::vector<std::string>& values, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No); |
||||||
|
|
||||||
|
void addArgument(Argument&& argument); |
||||||
|
void addArgument(bool& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||||
|
void addArgument(const char*& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||||
|
void addArgument(std::string& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||||
|
void addArgument(std::string_view& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||||
|
void addArgument(int& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||||
|
void addArgument(unsigned int& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||||
|
void addArgument(double& value, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||||
|
void addArgument(std::vector<std::string>& values, const char* name, const char* usageString, const char* manString, Required required = Required::No); |
||||||
|
|
||||||
|
void setErrorReporting(bool state) { m_errorReporting = state; } |
||||||
|
void setExitOnFirstError(bool state) { m_exitOnFirstError = state; } |
||||||
|
void setStopParsingOnFirstNonOption(bool state) { m_stopParsingOnFirstNonOption = state; } |
||||||
|
|
||||||
|
private: |
||||||
|
void printError(char parameter, Error error); |
||||||
|
void printError(const char* parameter, Error error, bool longName = true); |
||||||
|
bool parseShortOption(std::string_view option, std::string_view next); |
||||||
|
bool parseLongOption(std::string_view option, std::string_view next); |
||||||
|
bool parseArgument(std::string_view argument); |
||||||
|
|
||||||
|
AcceptFunction getAcceptFunction(bool& value); |
||||||
|
AcceptFunction getAcceptFunction(const char*& value); |
||||||
|
AcceptFunction getAcceptFunction(std::string& value); |
||||||
|
AcceptFunction getAcceptFunction(std::string_view& value); |
||||||
|
AcceptFunction getAcceptFunction(int& value); |
||||||
|
AcceptFunction getAcceptFunction(unsigned int& value); |
||||||
|
AcceptFunction getAcceptFunction(double& value); |
||||||
|
AcceptFunction getAcceptFunction(std::vector<std::string>& value); |
||||||
|
|
||||||
|
bool m_errorReporting { true }; |
||||||
|
bool m_exitOnFirstError { true }; |
||||||
|
bool m_stopParsingOnFirstNonOption { false }; |
||||||
|
|
||||||
|
size_t m_optionIndex { 1 }; |
||||||
|
size_t m_argumentIndex { 0 }; |
||||||
|
bool m_nonOptionMode { false }; |
||||||
|
|
||||||
|
const char* m_name { nullptr }; |
||||||
|
std::vector<Option> m_options; |
||||||
|
std::vector<Argument> m_arguments; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,89 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021-2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <cstdint> // int32_t |
||||||
|
#include <filesystem> |
||||||
|
#include <fstream> // ifstream, ios, ofstream |
||||||
|
#include <memory> // make_unique |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "util/file.h" |
||||||
|
#include "util/meta/assert.h" |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
File::File(const std::string& path) |
||||||
|
: m_path(path) |
||||||
|
{ |
||||||
|
// Create input stream object and open file
|
||||||
|
std::ifstream file(path, std::ios::in); |
||||||
|
VERIFY(file.is_open(), "failed to open file: '{}'", path); |
||||||
|
|
||||||
|
// Get length of the file
|
||||||
|
file.seekg(0, std::ios::end); |
||||||
|
int32_t size = file.tellg(); |
||||||
|
file.seekg(0, std::ios::beg); |
||||||
|
VERIFY(size != -1, "failed to read file length: '{}', path"); |
||||||
|
|
||||||
|
// Allocate memory filled with zeros
|
||||||
|
auto buffer = std::make_unique<char[]>(size); |
||||||
|
|
||||||
|
// Fill buffer with file contents
|
||||||
|
file.read(buffer.get(), size); |
||||||
|
file.close(); |
||||||
|
|
||||||
|
m_data = std::string(buffer.get(), size); |
||||||
|
} |
||||||
|
|
||||||
|
File::~File() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
File File::create(const std::string& path) |
||||||
|
{ |
||||||
|
if (!std::filesystem::exists(path)) { |
||||||
|
std::ofstream { path }; |
||||||
|
} |
||||||
|
|
||||||
|
return Util::File(path); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
void File::clear() |
||||||
|
{ |
||||||
|
m_data.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
File& File::append(const std::string& data) |
||||||
|
{ |
||||||
|
m_data.append(data); |
||||||
|
|
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
File& File::replace(size_t index, size_t length, const std::string& data) |
||||||
|
{ |
||||||
|
m_data.replace(index, length, data); |
||||||
|
|
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
File& File::flush() |
||||||
|
{ |
||||||
|
// Create output stream object and open file
|
||||||
|
std::ofstream file(m_path, std::ios::out | std::ios::trunc); |
||||||
|
VERIFY(file.is_open(), "failed to open file: '{}'", m_path); |
||||||
|
|
||||||
|
// Write data to disk
|
||||||
|
file.write(m_data.c_str(), m_data.size()); |
||||||
|
|
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,34 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021-2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <string> |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
class File { |
||||||
|
public: |
||||||
|
File(const std::string& path); |
||||||
|
virtual ~File(); |
||||||
|
|
||||||
|
static File create(const std::string& path); |
||||||
|
|
||||||
|
void clear(); |
||||||
|
File& append(const std::string& data); |
||||||
|
File& replace(size_t index, size_t length, const std::string& data); |
||||||
|
File& flush(); |
||||||
|
|
||||||
|
const char* c_str() const { return m_data.c_str(); } |
||||||
|
const std::string& data() const { return m_data; } |
||||||
|
const std::string& path() const { return m_path; } |
||||||
|
|
||||||
|
private: |
||||||
|
std::string m_path; |
||||||
|
std::string m_data; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,6 @@ |
|||||||
|
# -*- yaml -*- |
||||||
|
|
||||||
|
--- |
||||||
|
# FIXME: Figure out why NOLINTBEGIN/NOLINTEND doesnt work |
||||||
|
Checks: -misc-unused-using-decls |
||||||
|
... |
@ -0,0 +1,222 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <algorithm> // min |
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t |
||||||
|
#include <iomanip> // setprecision |
||||||
|
#include <ios> // defaultfloat, fixed |
||||||
|
#include <limits> // numeric_limits |
||||||
|
#include <sstream> // stringstream |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/format/builder.h" |
||||||
|
#include "util/format/parser.h" |
||||||
|
#include "util/meta/assert.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
void Builder::putLiteral(std::string_view literal) |
||||||
|
{ |
||||||
|
for (size_t i = 0; i < literal.length(); ++i) { |
||||||
|
putCharacter(literal[i]); |
||||||
|
if (literal[i] == '{' || literal[i] == '}') { |
||||||
|
++i; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static constexpr std::string numberToString(size_t value, uint8_t base, bool uppercase) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
if (value > std::numeric_limits<uint32_t>::max()) { |
||||||
|
result.reserve(64); |
||||||
|
} |
||||||
|
else if (value > std::numeric_limits<uint16_t>::max()) { |
||||||
|
result.reserve(32); |
||||||
|
} |
||||||
|
|
||||||
|
constexpr const auto& lookupLowercase = "0123456789abcdef"; |
||||||
|
constexpr const auto& lookupUppercase = "0123456789ABCDEF"; |
||||||
|
|
||||||
|
if (value == 0) { |
||||||
|
result = '0'; |
||||||
|
} |
||||||
|
else if (uppercase) { |
||||||
|
while (value > 0) { |
||||||
|
result.insert(0, 1, lookupUppercase[value % base]); |
||||||
|
value /= base; |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
while (value > 0) { |
||||||
|
result.insert(0, 1, lookupLowercase[value % base]); |
||||||
|
value /= base; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
void Builder::putU64(size_t value, |
||||||
|
uint8_t base, |
||||||
|
bool uppercase, |
||||||
|
char fill, |
||||||
|
Align align, |
||||||
|
Sign sign, |
||||||
|
bool alternativeForm, |
||||||
|
bool zeroPadding, |
||||||
|
size_t width, |
||||||
|
bool isNegative) const |
||||||
|
{ |
||||||
|
std::string number = numberToString(value, base, uppercase); |
||||||
|
|
||||||
|
// Sign
|
||||||
|
std::string prefix = ""; |
||||||
|
switch (sign) { |
||||||
|
case Sign::None: |
||||||
|
case Sign::Negative: |
||||||
|
if (isNegative) { |
||||||
|
prefix = '-'; |
||||||
|
} |
||||||
|
break; |
||||||
|
case Sign::Both: |
||||||
|
prefix = (isNegative) ? '-' : '+'; |
||||||
|
break; |
||||||
|
case Sign::Space: |
||||||
|
prefix = (isNegative) ? '-' : ' '; |
||||||
|
break; |
||||||
|
default: |
||||||
|
VERIFY_NOT_REACHED(); |
||||||
|
}; |
||||||
|
|
||||||
|
// Alternative form
|
||||||
|
if (alternativeForm) { |
||||||
|
switch (base) { |
||||||
|
case 2: |
||||||
|
prefix += (uppercase) ? "0B" : "0b"; |
||||||
|
break; |
||||||
|
case 8: |
||||||
|
prefix += '0'; |
||||||
|
break; |
||||||
|
case 10: |
||||||
|
break; |
||||||
|
case 16: |
||||||
|
prefix += (uppercase) ? "0X" : "0x"; |
||||||
|
break; |
||||||
|
default: |
||||||
|
VERIFY_NOT_REACHED(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!zeroPadding) { |
||||||
|
number.insert(0, prefix); |
||||||
|
} |
||||||
|
else { |
||||||
|
if (align != Builder::Align::None) { |
||||||
|
number.insert(0, prefix); |
||||||
|
} |
||||||
|
fill = '0'; |
||||||
|
} |
||||||
|
|
||||||
|
size_t length = number.length(); |
||||||
|
if (width < length) { |
||||||
|
m_builder.write(number.data(), length); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
switch (align) { |
||||||
|
case Align::Left: |
||||||
|
m_builder.write(number.data(), length); |
||||||
|
m_builder << std::string(width - length, fill); |
||||||
|
break; |
||||||
|
case Align::Center: { |
||||||
|
size_t half = (width - length) / 2; |
||||||
|
m_builder << std::string(half, fill); |
||||||
|
m_builder.write(number.data(), length); |
||||||
|
m_builder << std::string(width - half - length, fill); |
||||||
|
break; |
||||||
|
} |
||||||
|
case Align::Right: |
||||||
|
m_builder << std::string(width - length, fill); |
||||||
|
m_builder.write(number.data(), length); |
||||||
|
break; |
||||||
|
case Align::None: |
||||||
|
if (zeroPadding) { |
||||||
|
m_builder << prefix; |
||||||
|
m_builder << std::string(width - length - prefix.length(), fill); |
||||||
|
} |
||||||
|
else { |
||||||
|
m_builder << std::string(width - length, fill); |
||||||
|
} |
||||||
|
m_builder.write(number.data(), length); |
||||||
|
break; |
||||||
|
default: |
||||||
|
VERIFY_NOT_REACHED(); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
void Builder::putI64(int64_t value, |
||||||
|
uint8_t base, |
||||||
|
bool uppercase, |
||||||
|
char fill, |
||||||
|
Align align, |
||||||
|
Sign sign, |
||||||
|
bool alternativeForm, |
||||||
|
bool zeroPadding, |
||||||
|
size_t width) const |
||||||
|
{ |
||||||
|
bool isNegative = value < 0; |
||||||
|
value = isNegative ? -value : value; |
||||||
|
putU64(static_cast<uint64_t>(value), base, uppercase, fill, align, sign, alternativeForm, zeroPadding, width, isNegative); |
||||||
|
} |
||||||
|
|
||||||
|
void Builder::putF64(double number, uint8_t precision) const |
||||||
|
{ |
||||||
|
precision = std::min(precision, static_cast<uint8_t>(std::numeric_limits<double>::digits10)); |
||||||
|
|
||||||
|
std::stringstream stream; |
||||||
|
stream |
||||||
|
<< std::fixed << std::setprecision(precision) |
||||||
|
<< number |
||||||
|
<< std::defaultfloat << std::setprecision(6); |
||||||
|
std::string string = stream.str(); |
||||||
|
m_builder << string; |
||||||
|
} |
||||||
|
|
||||||
|
void Builder::putString(std::string_view string, char fill, Align align, size_t width) const |
||||||
|
{ |
||||||
|
size_t length = string.length(); |
||||||
|
if (width < length) { |
||||||
|
m_builder.write(string.data(), length); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
switch (align) { |
||||||
|
case Align::None: |
||||||
|
case Align::Left: |
||||||
|
m_builder.write(string.data(), length); |
||||||
|
m_builder << std::string(width - length, fill); |
||||||
|
break; |
||||||
|
case Align::Center: { |
||||||
|
size_t half = (width - length) / 2; |
||||||
|
m_builder << std::string(half, fill); |
||||||
|
m_builder.write(string.data(), length); |
||||||
|
m_builder << std::string(width - half - length, fill); |
||||||
|
break; |
||||||
|
} |
||||||
|
case Align::Right: |
||||||
|
m_builder << std::string(width - length, fill); |
||||||
|
m_builder.write(string.data(), length); |
||||||
|
break; |
||||||
|
default: |
||||||
|
VERIFY_NOT_REACHED(); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::Format
|
@ -0,0 +1,85 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdint> // int32_t, int64_t, uint8_t, uint32_t |
||||||
|
#include <sstream> // stringstream |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
class Builder { |
||||||
|
public: |
||||||
|
enum class Align : uint8_t { |
||||||
|
None, |
||||||
|
Left = '<', |
||||||
|
Right = '>', |
||||||
|
Center = '^', |
||||||
|
}; |
||||||
|
|
||||||
|
enum class Sign : uint8_t { |
||||||
|
None, |
||||||
|
Negative = '-', |
||||||
|
Both = '+', |
||||||
|
Space = ' ', |
||||||
|
}; |
||||||
|
|
||||||
|
explicit Builder(std::stringstream& builder) |
||||||
|
: m_builder(builder) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
void putLiteral(std::string_view literal); |
||||||
|
|
||||||
|
void putU64(size_t value, |
||||||
|
uint8_t base = 10, |
||||||
|
bool uppercase = false, |
||||||
|
char fill = ' ', |
||||||
|
Align align = Align::Right, |
||||||
|
Sign sign = Sign::Negative, |
||||||
|
bool alternativeForm = false, |
||||||
|
bool zeroPadding = false, |
||||||
|
size_t width = 0, |
||||||
|
bool isNegative = false) const; |
||||||
|
|
||||||
|
void putI64(int64_t value, |
||||||
|
uint8_t base = 10, |
||||||
|
bool uppercase = false, |
||||||
|
char fill = ' ', |
||||||
|
Align align = Align::Right, |
||||||
|
Sign sign = Sign::Negative, |
||||||
|
bool alternativeForm = false, |
||||||
|
bool zeroPadding = false, |
||||||
|
size_t width = 0) const; |
||||||
|
|
||||||
|
void putF64(double number, uint8_t precision = 6) const; |
||||||
|
void putCharacter(char character) const { m_builder.write(&character, 1); } |
||||||
|
void putString(std::string_view string, char fill = ' ', Align align = Align::Left, size_t width = 0) const; |
||||||
|
|
||||||
|
const std::stringstream& builder() const { return m_builder; } |
||||||
|
std::stringstream& builder() { return m_builder; } |
||||||
|
|
||||||
|
private: |
||||||
|
std::stringstream& m_builder; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util::Format
|
||||||
|
|
||||||
|
#if 0 |
||||||
|
// Defaults based on type
|
||||||
|
switch (type) { |
||||||
|
case SpecifierType::Integral: |
||||||
|
specifier.align = Builder::Align::Right; |
||||||
|
specifier.type = PresentationType::Decimal; |
||||||
|
break; |
||||||
|
case SpecifierType::FloatingPoint: |
||||||
|
specifier.align = Builder::Align::Right; |
||||||
|
specifier.type = PresentationType::General; |
||||||
|
break; |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,176 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <cstdint> // uint8_t |
||||||
|
#include <cstdio> // FILE, fputs, stdout |
||||||
|
#include <sstream> // stringstream |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/format/color.h" |
||||||
|
#include "util/format/format.h" |
||||||
|
#include "util/meta/assert.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
TextStyle::TextStyle(Emphasis emphasis) |
||||||
|
: m_emphasis(emphasis) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
TextStyle::TextStyle(bool isForeground, TerminalColor color) |
||||||
|
{ |
||||||
|
if (isForeground) { |
||||||
|
m_foregroundColor = color; |
||||||
|
} |
||||||
|
else { |
||||||
|
m_backgroundColor = color; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TextStyle& TextStyle::operator|=(const TextStyle& rhs) |
||||||
|
{ |
||||||
|
if (m_foregroundColor == TerminalColor::None) { |
||||||
|
m_foregroundColor = rhs.m_foregroundColor; |
||||||
|
} |
||||||
|
else { |
||||||
|
VERIFY(rhs.m_foregroundColor == TerminalColor::None, "can't OR a terminal color"); |
||||||
|
} |
||||||
|
|
||||||
|
if (m_backgroundColor == TerminalColor::None) { |
||||||
|
m_backgroundColor = rhs.m_backgroundColor; |
||||||
|
} |
||||||
|
else { |
||||||
|
VERIFY(rhs.m_backgroundColor == TerminalColor::None, "can't OR a terminal color"); |
||||||
|
} |
||||||
|
|
||||||
|
m_emphasis = static_cast<Emphasis>(static_cast<uint8_t>(m_emphasis) | static_cast<uint8_t>(rhs.m_emphasis)); |
||||||
|
|
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
TextStyle fg(TerminalColor foreground) |
||||||
|
{ |
||||||
|
return TextStyle(true, foreground); |
||||||
|
} |
||||||
|
|
||||||
|
TextStyle bg(TerminalColor background) |
||||||
|
{ |
||||||
|
return TextStyle(false, background); |
||||||
|
} |
||||||
|
|
||||||
|
TextStyle operator|(TextStyle lhs, const TextStyle& rhs) |
||||||
|
{ |
||||||
|
return lhs |= rhs; |
||||||
|
} |
||||||
|
|
||||||
|
TextStyle operator|(Emphasis lhs, Emphasis rhs) |
||||||
|
{ |
||||||
|
return TextStyle { lhs } | rhs; |
||||||
|
} |
||||||
|
|
||||||
|
bool operator&(Emphasis lhs, Emphasis rhs) |
||||||
|
{ |
||||||
|
return static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
void setDisplayAttributes(std::stringstream& stream, const TextStyle& style) |
||||||
|
{ |
||||||
|
bool hasForeground = style.foregroundColor() != TerminalColor::None; |
||||||
|
bool hasBackground = style.backgroundColor() != TerminalColor::None; |
||||||
|
bool hasEmphasis = style.emphasis() != Emphasis::None; |
||||||
|
if (!hasForeground && !hasBackground && !hasEmphasis) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
stream.write("\033[", 2); |
||||||
|
|
||||||
|
if (hasForeground) { |
||||||
|
stream << format("{}", static_cast<uint8_t>(style.foregroundColor())); |
||||||
|
} |
||||||
|
|
||||||
|
if (hasBackground) { |
||||||
|
if (hasForeground) { |
||||||
|
stream.write(";", 1); |
||||||
|
} |
||||||
|
stream << format("{}", static_cast<uint8_t>(style.backgroundColor()) + 10); |
||||||
|
} |
||||||
|
|
||||||
|
stream.write("m", 1); |
||||||
|
|
||||||
|
if (hasEmphasis) { |
||||||
|
|
||||||
|
#define ESCAPE_ATTRIBUTE(escape, attribute) \ |
||||||
|
if (style.emphasis() & escape) { \
|
||||||
|
stream.write("\033[", 2); \
|
||||||
|
stream.write(attribute, 1); \
|
||||||
|
stream.write("m", 1); \
|
||||||
|
} |
||||||
|
|
||||||
|
ESCAPE_ATTRIBUTE(Emphasis::Bold, "1"); |
||||||
|
ESCAPE_ATTRIBUTE(Emphasis::Faint, "2"); |
||||||
|
ESCAPE_ATTRIBUTE(Emphasis::Italic, "3"); |
||||||
|
ESCAPE_ATTRIBUTE(Emphasis::Underline, "4"); |
||||||
|
ESCAPE_ATTRIBUTE(Emphasis::Blink, "5"); |
||||||
|
ESCAPE_ATTRIBUTE(Emphasis::Reverse, "7"); |
||||||
|
ESCAPE_ATTRIBUTE(Emphasis::Conceal, "8"); |
||||||
|
ESCAPE_ATTRIBUTE(Emphasis::Strikethrough, "9"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void coloredVariadicFormat(std::stringstream& stream, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters) |
||||||
|
{ |
||||||
|
setDisplayAttributes(stream, style); |
||||||
|
variadicFormat(stream, format, parameters); |
||||||
|
stream.write("\033[0m", 4); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
void coloredVariadicPrint(FILE* file, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters) |
||||||
|
{ |
||||||
|
std::stringstream stream; |
||||||
|
setDisplayAttributes(stream, style); |
||||||
|
variadicFormat(stream, format, parameters); |
||||||
|
stream.write("\033[0m", 4); |
||||||
|
|
||||||
|
std::string string = stream.str(); |
||||||
|
fputs(string.c_str(), file); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
ColorPrintOperatorStyle::ColorPrintOperatorStyle(FILE* file, const TextStyle& style) |
||||||
|
: m_file(file) |
||||||
|
, m_style(style) |
||||||
|
, m_stream() |
||||||
|
, m_builder(m_stream) |
||||||
|
{ |
||||||
|
setDisplayAttributes(m_stream, style); |
||||||
|
} |
||||||
|
|
||||||
|
ColorPrintOperatorStyle::~ColorPrintOperatorStyle() |
||||||
|
{ |
||||||
|
m_stream.write("\033[0m", 4); |
||||||
|
std::string string = m_stream.str(); |
||||||
|
fputs(string.c_str(), m_file); |
||||||
|
} |
||||||
|
|
||||||
|
ColorPrintOperatorStyle print(const TextStyle& style) |
||||||
|
{ |
||||||
|
return ColorPrintOperatorStyle(stdout, style); |
||||||
|
} |
||||||
|
|
||||||
|
ColorPrintOperatorStyle print(FILE* file, const TextStyle& style) |
||||||
|
{ |
||||||
|
return ColorPrintOperatorStyle(file, style); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::Format
|
@ -0,0 +1,156 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdint> // uint8_t |
||||||
|
#include <cstdio> // FILE, stdout |
||||||
|
#include <sstream> // stringstream |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/format/format.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
enum class TerminalColor : uint8_t { |
||||||
|
None = 0, |
||||||
|
Black = 30, |
||||||
|
Red, |
||||||
|
Green, |
||||||
|
Yellow, |
||||||
|
Blue, |
||||||
|
Magenta, |
||||||
|
Cyan, |
||||||
|
White, |
||||||
|
BrightBlack = 90, |
||||||
|
BrightRed, |
||||||
|
BrightGreen, |
||||||
|
BrightYellow, |
||||||
|
BrightBlue, |
||||||
|
BrightMagenta, |
||||||
|
BrightCyan, |
||||||
|
BrightWhite |
||||||
|
}; |
||||||
|
|
||||||
|
// Bit field
|
||||||
|
enum class Emphasis : uint8_t { |
||||||
|
None = 0, // Attribute 0
|
||||||
|
Bold = 1, // Attribute 1
|
||||||
|
Faint = 1 << 1, // Attribute 2
|
||||||
|
Italic = 1 << 2, // Attribute 3
|
||||||
|
Underline = 1 << 3, // Attribute 4
|
||||||
|
Blink = 1 << 4, // Attribute 5
|
||||||
|
Reverse = 1 << 5, // Attribute 7
|
||||||
|
Conceal = 1 << 6, // Attribute 8
|
||||||
|
Strikethrough = 1 << 7, // Attribute 9
|
||||||
|
}; |
||||||
|
|
||||||
|
class TextStyle { |
||||||
|
private: |
||||||
|
friend TextStyle fg(TerminalColor foreground); |
||||||
|
friend TextStyle bg(TerminalColor background); |
||||||
|
|
||||||
|
public: |
||||||
|
TextStyle(Emphasis emphasis); |
||||||
|
|
||||||
|
// Operator pipe equal, reads the same way as +=
|
||||||
|
TextStyle& operator|=(const TextStyle& rhs); |
||||||
|
|
||||||
|
TerminalColor foregroundColor() const { return m_foregroundColor; } |
||||||
|
TerminalColor backgroundColor() const { return m_backgroundColor; } |
||||||
|
Emphasis emphasis() const { return m_emphasis; } |
||||||
|
|
||||||
|
private: |
||||||
|
TextStyle(bool isForeground, TerminalColor color); |
||||||
|
|
||||||
|
TerminalColor m_foregroundColor { TerminalColor::None }; |
||||||
|
TerminalColor m_backgroundColor { TerminalColor::None }; |
||||||
|
Emphasis m_emphasis { 0 }; |
||||||
|
}; |
||||||
|
|
||||||
|
TextStyle fg(TerminalColor foreground); |
||||||
|
TextStyle bg(TerminalColor background); |
||||||
|
TextStyle operator|(TextStyle lhs, const TextStyle& rhs); |
||||||
|
TextStyle operator|(Emphasis lhs, Emphasis rhs); |
||||||
|
bool operator&(Emphasis lhs, Emphasis rhs); |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
void coloredVariadicFormat(std::stringstream& stream, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters); |
||||||
|
|
||||||
|
template<typename... Parameters> |
||||||
|
std::string format(const TextStyle& style, std::string_view format, const Parameters&... parameters) |
||||||
|
{ |
||||||
|
std::stringstream stream; |
||||||
|
VariadicParameters variadicParameters { parameters... }; |
||||||
|
coloredVariadicFormat(stream, style, format, variadicParameters); |
||||||
|
return stream.str(); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename... Parameters> |
||||||
|
void formatTo(std::string& output, const TextStyle& style, std::string_view format, const Parameters&... parameters) |
||||||
|
{ |
||||||
|
std::stringstream stream; |
||||||
|
VariadicParameters variadicParameters { parameters... }; |
||||||
|
coloredVariadicFormat(stream, style, format, variadicParameters); |
||||||
|
output += stream.str(); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
void coloredVariadicPrint(FILE* file, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters); |
||||||
|
|
||||||
|
template<size_t N, typename... Parameters> |
||||||
|
void print(const TextStyle& style, const char (&format)[N], const Parameters&... parameters) |
||||||
|
{ |
||||||
|
VariadicParameters variadicParameters { parameters... }; |
||||||
|
coloredVariadicPrint(stdout, style, { format, N - 1 }, variadicParameters); |
||||||
|
} |
||||||
|
|
||||||
|
template<size_t N, typename... Parameters> |
||||||
|
void print(FILE* file, const TextStyle& style, const char (&format)[N], const Parameters&... parameters) |
||||||
|
{ |
||||||
|
VariadicParameters variadicParameters { parameters... }; |
||||||
|
coloredVariadicPrint(file, style, { format, N - 1 }, variadicParameters); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
class ColorPrintOperatorStyle { |
||||||
|
public: |
||||||
|
ColorPrintOperatorStyle(FILE* file, const TextStyle& style); |
||||||
|
virtual ~ColorPrintOperatorStyle(); |
||||||
|
|
||||||
|
Builder& builder() { return m_builder; } |
||||||
|
|
||||||
|
private: |
||||||
|
FILE* m_file; |
||||||
|
TextStyle m_style; |
||||||
|
|
||||||
|
std::stringstream m_stream; |
||||||
|
Builder m_builder; |
||||||
|
}; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
const ColorPrintOperatorStyle& operator<<(const ColorPrintOperatorStyle& colorPrintOperatorStyle, const T& value) |
||||||
|
{ |
||||||
|
_format(const_cast<ColorPrintOperatorStyle&>(colorPrintOperatorStyle).builder(), value); |
||||||
|
return colorPrintOperatorStyle; |
||||||
|
} |
||||||
|
|
||||||
|
ColorPrintOperatorStyle print(const TextStyle& style); |
||||||
|
ColorPrintOperatorStyle print(FILE* file, const TextStyle& style); |
||||||
|
|
||||||
|
} // namespace Util::Format
|
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
using Util::Format::format; |
||||||
|
using Util::Format::formatTo; |
||||||
|
using Util::Format::print; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,76 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <sstream> // stringstream |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/format/builder.h" |
||||||
|
#include "util/format/format.h" |
||||||
|
#include "util/format/parser.h" |
||||||
|
#include "util/meta/assert.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
void variadicFormatImpl(Builder& builder, Parser& parser, TypeErasedParameters& parameters) |
||||||
|
{ |
||||||
|
// Consume everything until '{' or EOF
|
||||||
|
const auto literal = parser.consumeLiteral(); |
||||||
|
builder.putLiteral(literal); |
||||||
|
|
||||||
|
// Reached end of format string
|
||||||
|
if (parser.isEOF()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Consume index + ':'
|
||||||
|
auto indexMaybe = parser.consumeIndex(); |
||||||
|
|
||||||
|
// Get parameter at index, or next
|
||||||
|
size_t index = indexMaybe.has_value() ? indexMaybe.value() : parameters.tell(); |
||||||
|
VERIFY(index < parameters.size(), "argument not found at index '%zu'", index); |
||||||
|
auto& parameter = parameters.parameter(index); |
||||||
|
|
||||||
|
// Format the parameter
|
||||||
|
parameter.format(builder, parser, parameter.value); |
||||||
|
|
||||||
|
// Go to next parameter
|
||||||
|
parameters.ignore(); |
||||||
|
|
||||||
|
// Recurse
|
||||||
|
if (!parser.isEOF()) { |
||||||
|
variadicFormatImpl(builder, parser, parameters); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void variadicFormat(std::stringstream& stream, std::string_view format, TypeErasedParameters& parameters) |
||||||
|
{ |
||||||
|
Builder builder { stream }; |
||||||
|
Parser parser { format, parameters.size() }; |
||||||
|
|
||||||
|
variadicFormatImpl(builder, parser, parameters); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
FormatOperatorStyle::FormatOperatorStyle(std::string& output) |
||||||
|
: m_output(output) |
||||||
|
, m_stream() |
||||||
|
, m_builder(m_stream) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
FormatOperatorStyle::~FormatOperatorStyle() |
||||||
|
{ |
||||||
|
m_output = m_stream.str(); |
||||||
|
} |
||||||
|
|
||||||
|
FormatOperatorStyle formatTo(std::string& output) |
||||||
|
{ |
||||||
|
return FormatOperatorStyle(output); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::Format
|
@ -0,0 +1,119 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <span> |
||||||
|
#include <sstream> // stringstream |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/format/formatter.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
class Builder; |
||||||
|
class Parser; |
||||||
|
|
||||||
|
struct Parameter { |
||||||
|
const void* value; |
||||||
|
void (*format)(Builder& builder, Parser& parser, const void* value); |
||||||
|
}; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
void formatParameterValue(Builder& builder, Parser& parser, const void* value) |
||||||
|
{ |
||||||
|
Formatter<T> formatter; |
||||||
|
formatter.parse(parser); |
||||||
|
formatter.format(builder, *static_cast<const T*>(value)); |
||||||
|
} |
||||||
|
|
||||||
|
// Type erasure improves both compile time and binary size significantly
|
||||||
|
class TypeErasedParameters { |
||||||
|
public: |
||||||
|
const Parameter parameter(size_t index) { return m_parameters[index]; } |
||||||
|
|
||||||
|
size_t tell() const { return m_index; } |
||||||
|
size_t size() const { return m_parameters.size(); } |
||||||
|
bool isEOF() const { return m_index >= m_parameters.size(); } |
||||||
|
void ignore() { m_index++; } |
||||||
|
|
||||||
|
protected: |
||||||
|
size_t m_index { 0 }; |
||||||
|
std::span<const Parameter> m_parameters; |
||||||
|
}; |
||||||
|
|
||||||
|
template<typename... Parameters> |
||||||
|
class VariadicParameters final : public TypeErasedParameters { |
||||||
|
public: |
||||||
|
VariadicParameters(const Parameters&... parameters) |
||||||
|
: m_templatedParameters({ { ¶meters, formatParameterValue<Parameters> }... }) |
||||||
|
{ |
||||||
|
m_parameters = m_templatedParameters; |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
std::array<Parameter, sizeof...(Parameters)> m_templatedParameters; |
||||||
|
}; |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
void variadicFormatImpl(Builder& builder, Parser& parser, TypeErasedParameters& parameters); |
||||||
|
void variadicFormat(std::stringstream& stream, std::string_view format, TypeErasedParameters& parameters); |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
template<typename... Parameters> |
||||||
|
std::string format(std::string_view format, const Parameters&... parameters) |
||||||
|
{ |
||||||
|
std::stringstream stream; |
||||||
|
VariadicParameters variadicParameters { parameters... }; |
||||||
|
variadicFormat(stream, format, variadicParameters); |
||||||
|
return stream.str(); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename... Parameters> |
||||||
|
void formatTo(std::string& output, std::string_view format, const Parameters&... parameters) |
||||||
|
{ |
||||||
|
std::stringstream stream; |
||||||
|
VariadicParameters variadicParameters { parameters... }; |
||||||
|
variadicFormat(stream, format, variadicParameters); |
||||||
|
output += stream.str(); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
class FormatOperatorStyle { |
||||||
|
public: |
||||||
|
FormatOperatorStyle(std::string& output); |
||||||
|
virtual ~FormatOperatorStyle(); |
||||||
|
|
||||||
|
Builder& builder() { return m_builder; } |
||||||
|
|
||||||
|
private: |
||||||
|
std::string& m_output; |
||||||
|
std::stringstream m_stream; |
||||||
|
Builder m_builder; |
||||||
|
}; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
const FormatOperatorStyle& operator<<(const FormatOperatorStyle& formatOperatorStyle, const T& value) |
||||||
|
{ |
||||||
|
_format(const_cast<FormatOperatorStyle&>(formatOperatorStyle).builder(), value); |
||||||
|
return formatOperatorStyle; |
||||||
|
} |
||||||
|
|
||||||
|
FormatOperatorStyle formatTo(std::string& output); |
||||||
|
|
||||||
|
} // namespace Util::Format
|
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
using Util::Format::format; |
||||||
|
using Util::Format::formatTo; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,117 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <cstdint> // uint8_t |
||||||
|
#include <cstring> // strlen |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/format/builder.h" |
||||||
|
#include "util/format/formatter.h" |
||||||
|
#include "util/format/parser.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
// Char
|
||||||
|
|
||||||
|
template<> |
||||||
|
void Formatter<char>::format(Builder& builder, char value) const |
||||||
|
{ |
||||||
|
if (specifier.type != PresentationType::None && specifier.type != PresentationType::Character) { |
||||||
|
// "Type char is a distinct type that has an implementation-defined
|
||||||
|
// choice of “signed char” or “unsigned char” as its underlying type."
|
||||||
|
// http://eel.is/c++draft/basic#fundamental
|
||||||
|
Formatter<signed char> formatter { .specifier = specifier }; |
||||||
|
return formatter.format(builder, static_cast<signed char>(value)); |
||||||
|
} |
||||||
|
|
||||||
|
Formatter<std::string_view> formatter { .specifier = specifier }; |
||||||
|
return formatter.format(builder, { &value, 1 }); |
||||||
|
} |
||||||
|
|
||||||
|
template<> |
||||||
|
void Formatter<bool>::format(Builder& builder, bool value) const |
||||||
|
{ |
||||||
|
switch (specifier.type) { |
||||||
|
case PresentationType::Binary: |
||||||
|
case PresentationType::BinaryUppercase: |
||||||
|
case PresentationType::Character: |
||||||
|
case PresentationType::Decimal: |
||||||
|
case PresentationType::Octal: |
||||||
|
case PresentationType::Hex: |
||||||
|
case PresentationType::HexUppercase: { |
||||||
|
Formatter<uint8_t> formatter { .specifier = specifier }; |
||||||
|
return formatter.format(builder, static_cast<uint8_t>(value)); |
||||||
|
} |
||||||
|
default: |
||||||
|
break; |
||||||
|
}; |
||||||
|
|
||||||
|
Formatter<std::string_view> formatter { .specifier = specifier }; |
||||||
|
formatter.format(builder, value ? "true" : "false"); |
||||||
|
} |
||||||
|
|
||||||
|
// String
|
||||||
|
|
||||||
|
template<> |
||||||
|
void Formatter<std::string_view>::format(Builder& builder, std::string_view value) const |
||||||
|
{ |
||||||
|
builder.putString(value, specifier.fill, specifier.align, specifier.width); |
||||||
|
} |
||||||
|
|
||||||
|
void Formatter<const char*>::parse(Parser& parser) |
||||||
|
{ |
||||||
|
parser.parseSpecifier(specifier, Parser::ParameterType::CString); |
||||||
|
} |
||||||
|
|
||||||
|
void Formatter<const char*>::format(Builder& builder, const char* value) const |
||||||
|
{ |
||||||
|
if (specifier.type == PresentationType::Pointer) { |
||||||
|
Formatter<uintptr_t> formatter { .specifier = specifier }; |
||||||
|
formatter.specifier.alternativeForm = true; |
||||||
|
formatter.specifier.type = PresentationType::Hex; |
||||||
|
return formatter.format(builder, reinterpret_cast<uintptr_t>(value)); |
||||||
|
} |
||||||
|
|
||||||
|
Formatter<std::string_view>::format( |
||||||
|
builder, |
||||||
|
value != nullptr ? std::string_view { value, strlen(value) } : "nullptr"); |
||||||
|
} |
||||||
|
|
||||||
|
// Pointer
|
||||||
|
|
||||||
|
void Formatter<std::nullptr_t>::format(Builder& builder, std::nullptr_t) const |
||||||
|
{ |
||||||
|
Formatter<const void*>::format(builder, 0); |
||||||
|
} |
||||||
|
|
||||||
|
// For debugging
|
||||||
|
|
||||||
|
void Formatter<Specifier>::format(Builder& builder, Specifier value) const |
||||||
|
{ |
||||||
|
char result[255]; |
||||||
|
sprintf(result, R"( |
||||||
|
fill: {%c} |
||||||
|
align: {%c} |
||||||
|
sign: {%c} |
||||||
|
alternativeForm: {%d} |
||||||
|
zeroPadding: {%d} |
||||||
|
width: {%zu} |
||||||
|
precision: {%d} |
||||||
|
type: {%c} |
||||||
|
)", |
||||||
|
value.fill, |
||||||
|
static_cast<char>(value.align), |
||||||
|
static_cast<char>(value.sign), |
||||||
|
value.alternativeForm, |
||||||
|
value.zeroPadding, |
||||||
|
value.width, |
||||||
|
value.precision, |
||||||
|
static_cast<char>(value.type)); |
||||||
|
builder.putString(std::string_view(result)); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::Format
|
@ -0,0 +1,325 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cassert> |
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdint> // int8_t, uint8_t, uintptr_t |
||||||
|
#include <map> |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
#include <type_traits> // is_integral_v, is_same |
||||||
|
#include <unordered_map> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "util/format/builder.h" |
||||||
|
#include "util/format/parser.h" |
||||||
|
#include "util/meta/concepts.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
enum class PresentationType : uint8_t { |
||||||
|
None, |
||||||
|
// Interger
|
||||||
|
Binary = 'b', |
||||||
|
BinaryUppercase = 'B', |
||||||
|
Decimal = 'd', |
||||||
|
Octal = 'o', |
||||||
|
Hex = 'x', |
||||||
|
HexUppercase = 'X', |
||||||
|
// Floating-point
|
||||||
|
Hexfloat = 'a', |
||||||
|
HexfloatUppercase = 'A', |
||||||
|
Exponent = 'e', |
||||||
|
ExponentUppercase = 'E', |
||||||
|
FixedPoint = 'f', |
||||||
|
FixedPointUppercase = 'F', |
||||||
|
General = 'g', |
||||||
|
GeneralUppercase = 'G', |
||||||
|
// Character
|
||||||
|
Character = 'c', |
||||||
|
// String
|
||||||
|
String = 's', |
||||||
|
// Pointer
|
||||||
|
Pointer = 'p', |
||||||
|
// Container
|
||||||
|
Container = 'C', |
||||||
|
}; |
||||||
|
|
||||||
|
struct Specifier { |
||||||
|
char fill = ' '; |
||||||
|
Builder::Align align = Builder::Align::None; |
||||||
|
|
||||||
|
Builder::Sign sign = Builder::Sign::None; |
||||||
|
|
||||||
|
bool alternativeForm = false; |
||||||
|
bool zeroPadding = false; |
||||||
|
size_t width = 0; |
||||||
|
int8_t precision = -1; |
||||||
|
|
||||||
|
PresentationType type = PresentationType::None; |
||||||
|
}; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
struct Formatter { |
||||||
|
Specifier specifier; |
||||||
|
|
||||||
|
void parse(Parser& parser) |
||||||
|
{ |
||||||
|
if constexpr (std::is_same_v<T, char>) { |
||||||
|
parser.parseSpecifier(specifier, Parser::ParameterType::Char); |
||||||
|
} |
||||||
|
else if (std::is_same_v<T, bool>) { |
||||||
|
parser.parseSpecifier(specifier, Parser::ParameterType::Char); |
||||||
|
} |
||||||
|
else if (std::is_same_v<T, std::string_view>) { |
||||||
|
parser.parseSpecifier(specifier, Parser::ParameterType::String); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void format(Builder&, T) const {} |
||||||
|
}; |
||||||
|
|
||||||
|
// Integral
|
||||||
|
|
||||||
|
template<Integral T> |
||||||
|
struct Formatter<T> { |
||||||
|
Specifier specifier; |
||||||
|
|
||||||
|
void parse(Parser& parser) |
||||||
|
{ |
||||||
|
parser.parseSpecifier(specifier, Parser::ParameterType::Integral); |
||||||
|
} |
||||||
|
|
||||||
|
void format(Builder& builder, T value) const |
||||||
|
{ |
||||||
|
if (specifier.type == PresentationType::Character) { |
||||||
|
assert(value >= 0 && value <= 127); |
||||||
|
|
||||||
|
Formatter<std::string_view> formatter { .specifier = specifier }; |
||||||
|
formatter.specifier.type = PresentationType::String; |
||||||
|
return formatter.format(builder, { reinterpret_cast<const char*>(&value), 1 }); |
||||||
|
} |
||||||
|
|
||||||
|
uint8_t base = 0; |
||||||
|
bool uppercase = false; |
||||||
|
switch (specifier.type) { |
||||||
|
case PresentationType::BinaryUppercase: |
||||||
|
uppercase = true; |
||||||
|
case PresentationType::Binary: |
||||||
|
base = 2; |
||||||
|
break; |
||||||
|
case PresentationType::Octal: |
||||||
|
base = 8; |
||||||
|
break; |
||||||
|
case PresentationType::None: |
||||||
|
case PresentationType::Decimal: |
||||||
|
base = 10; |
||||||
|
break; |
||||||
|
case PresentationType::HexUppercase: |
||||||
|
uppercase = true; |
||||||
|
case PresentationType::Hex: |
||||||
|
base = 16; |
||||||
|
break; |
||||||
|
default: |
||||||
|
assert(false); |
||||||
|
}; |
||||||
|
|
||||||
|
if constexpr (std::is_unsigned_v<T>) { |
||||||
|
builder.putU64( |
||||||
|
value, base, uppercase, specifier.fill, specifier.align, specifier.sign, |
||||||
|
specifier.alternativeForm, specifier.zeroPadding, specifier.width); |
||||||
|
} |
||||||
|
else { |
||||||
|
builder.putI64( |
||||||
|
value, base, uppercase, specifier.fill, specifier.align, specifier.sign, |
||||||
|
specifier.alternativeForm, specifier.zeroPadding, specifier.width); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// Floating point
|
||||||
|
|
||||||
|
template<FloatingPoint T> |
||||||
|
struct Formatter<T> { |
||||||
|
Specifier specifier; |
||||||
|
|
||||||
|
void parse(Parser& parser) |
||||||
|
{ |
||||||
|
parser.parseSpecifier(specifier, Parser::ParameterType::FloatingPoint); |
||||||
|
} |
||||||
|
|
||||||
|
void format(Builder& builder, T value) const |
||||||
|
{ |
||||||
|
if (specifier.precision < 0) { |
||||||
|
builder.putF64(value); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
builder.putF64(value, specifier.precision); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// Char
|
||||||
|
|
||||||
|
template<> |
||||||
|
void Formatter<char>::format(Builder& builder, char value) const; |
||||||
|
|
||||||
|
template<> |
||||||
|
void Formatter<bool>::format(Builder& builder, bool value) const; |
||||||
|
|
||||||
|
// String
|
||||||
|
|
||||||
|
template<> |
||||||
|
void Formatter<std::string_view>::format(Builder& builder, std::string_view value) const; |
||||||
|
|
||||||
|
template<> |
||||||
|
struct Formatter<std::string> : Formatter<std::string_view> { |
||||||
|
}; |
||||||
|
|
||||||
|
template<> |
||||||
|
struct Formatter<const char*> : Formatter<std::string_view> { |
||||||
|
void parse(Parser& parser); |
||||||
|
void format(Builder& builder, const char* value) const; |
||||||
|
}; |
||||||
|
|
||||||
|
template<> |
||||||
|
struct Formatter<char*> : Formatter<const char*> { |
||||||
|
}; |
||||||
|
|
||||||
|
template<size_t N> |
||||||
|
struct Formatter<char[N]> : Formatter<const char*> { |
||||||
|
}; |
||||||
|
|
||||||
|
// Pointer
|
||||||
|
|
||||||
|
template<typename T> |
||||||
|
struct Formatter<T*> : Formatter<uintptr_t> { |
||||||
|
void parse(Parser& parser) |
||||||
|
{ |
||||||
|
parser.parseSpecifier(specifier, Parser::ParameterType::Pointer); |
||||||
|
specifier.alternativeForm = true; |
||||||
|
specifier.type = PresentationType::Hex; |
||||||
|
} |
||||||
|
|
||||||
|
void format(Builder& builder, T* value) const |
||||||
|
{ |
||||||
|
Formatter<uintptr_t>::format(builder, reinterpret_cast<uintptr_t>(value)); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
template<> |
||||||
|
struct Formatter<std::nullptr_t> : Formatter<const void*> { |
||||||
|
void format(Builder& builder, std::nullptr_t) const; |
||||||
|
}; |
||||||
|
|
||||||
|
// Container
|
||||||
|
|
||||||
|
template<typename T> |
||||||
|
struct Formatter<std::vector<T>> : Formatter<T> { |
||||||
|
Specifier specifier; |
||||||
|
|
||||||
|
void parse(Parser& parser) |
||||||
|
{ |
||||||
|
parser.parseSpecifier(specifier, Parser::ParameterType::Container); |
||||||
|
} |
||||||
|
|
||||||
|
void format(Builder& builder, const std::vector<T>& value) const |
||||||
|
{ |
||||||
|
std::string indent = std::string(specifier.width, specifier.fill); |
||||||
|
|
||||||
|
builder.putCharacter('{'); |
||||||
|
if (specifier.alternativeForm) { |
||||||
|
builder.putCharacter('\n'); |
||||||
|
} |
||||||
|
for (auto it = value.cbegin(); it != value.cend(); ++it) { |
||||||
|
builder.putString(indent); |
||||||
|
|
||||||
|
Formatter<T>::format(builder, *it); |
||||||
|
|
||||||
|
// Add comma, except after the last element
|
||||||
|
if (it != std::prev(value.end(), 1)) { |
||||||
|
builder.putCharacter(','); |
||||||
|
} |
||||||
|
else if (!specifier.alternativeForm) { |
||||||
|
builder.putString(indent); |
||||||
|
} |
||||||
|
|
||||||
|
if (specifier.alternativeForm) { |
||||||
|
builder.putCharacter('\n'); |
||||||
|
} |
||||||
|
} |
||||||
|
builder.putCharacter('}'); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
#define UTIL_FORMAT_FORMAT_AS_MAP(type) \ |
||||||
|
template<typename K, typename V> \
|
||||||
|
struct Formatter<type<K, V>> \
|
||||||
|
: Formatter<K> \
|
||||||
|
, Formatter<V> { \
|
||||||
|
Specifier specifier; \
|
||||||
|
\
|
||||||
|
void parse(Parser& parser) \
|
||||||
|
{ \
|
||||||
|
parser.parseSpecifier(specifier, Parser::ParameterType::Container); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
void format(Builder& builder, const type<K, V>& value) const \
|
||||||
|
{ \
|
||||||
|
std::string indent = std::string(specifier.width, specifier.fill); \
|
||||||
|
\
|
||||||
|
builder.putCharacter('{'); \
|
||||||
|
if (specifier.alternativeForm) { \
|
||||||
|
builder.putCharacter('\n'); \
|
||||||
|
} \
|
||||||
|
auto last = value.end(); \
|
||||||
|
for (auto it = value.begin(); it != last; ++it) { \
|
||||||
|
builder.putString(indent); \
|
||||||
|
builder.putCharacter('"'); \
|
||||||
|
Formatter<K>::format(builder, it->first); \
|
||||||
|
builder.putCharacter('"'); \
|
||||||
|
builder.putString((specifier.width > 0) ? ": " : ":"); \
|
||||||
|
Formatter<V>::format(builder, it->second); \
|
||||||
|
\
|
||||||
|
/* Add comma, except after the last element */ \
|
||||||
|
if (std::next(it) != last) { \
|
||||||
|
builder.putCharacter(','); \
|
||||||
|
} \
|
||||||
|
else if (!specifier.alternativeForm) { \
|
||||||
|
builder.putString(indent); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
if (specifier.alternativeForm) { \
|
||||||
|
builder.putCharacter('\n'); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
builder.putCharacter('}'); \
|
||||||
|
} \
|
||||||
|
} |
||||||
|
|
||||||
|
UTIL_FORMAT_FORMAT_AS_MAP(std::map); |
||||||
|
UTIL_FORMAT_FORMAT_AS_MAP(std::unordered_map); |
||||||
|
|
||||||
|
// For debugging
|
||||||
|
|
||||||
|
template<> |
||||||
|
struct Formatter<Specifier> : Formatter<std::nullptr_t> { |
||||||
|
void format(Builder& builder, Specifier value) const; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util::Format
|
||||||
|
|
||||||
|
#if 0 |
||||||
|
|
||||||
|
TODO: |
||||||
|
- Split assert.h so it can work in format headers! (formatter.h) |
||||||
|
v Add unit tests |
||||||
|
- Expand Builder::putf60 |
||||||
|
- Add Builder::putf80 (?) |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,109 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <cstdio> // FILE |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "util/format/color.h" |
||||||
|
#include "util/format/log.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
std::string formatTimeElapsed() |
||||||
|
{ |
||||||
|
return format("{:.3}s ", s_timer.elapsedNanoseconds() / 1000000000.0); |
||||||
|
} |
||||||
|
|
||||||
|
std::string formatType(LogType type) |
||||||
|
{ |
||||||
|
std::string output; |
||||||
|
|
||||||
|
formatTo(output, "["); |
||||||
|
switch (type) { |
||||||
|
case LogType::Trace: |
||||||
|
formatTo(output, "trace"); |
||||||
|
break; |
||||||
|
case LogType::Debug: |
||||||
|
formatTo(output, fg(TerminalColor::Magenta), "debug"); |
||||||
|
break; |
||||||
|
case LogType::Success: |
||||||
|
formatTo(output, fg(TerminalColor::Green), "success"); |
||||||
|
break; |
||||||
|
case LogType::Info: |
||||||
|
formatTo(output, fg(TerminalColor::Blue), "info"); |
||||||
|
break; |
||||||
|
case LogType::Warn: |
||||||
|
formatTo(output, Emphasis::Bold | fg(TerminalColor::Yellow), "warn"); |
||||||
|
break; |
||||||
|
case LogType::Error: |
||||||
|
formatTo(output, Emphasis::Bold | fg(TerminalColor::Red), "error"); |
||||||
|
break; |
||||||
|
case LogType::Critical: |
||||||
|
formatTo(output, Emphasis::Bold | fg(TerminalColor::White) | bg(TerminalColor::Red), "critical"); |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
}; |
||||||
|
formatTo(output, "] "); |
||||||
|
|
||||||
|
return output; |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
LogOperatorStyle::LogOperatorStyle(FILE* file, LogType type) |
||||||
|
: m_file(file) |
||||||
|
, m_type(type) |
||||||
|
, m_stream() |
||||||
|
, m_builder(m_stream) |
||||||
|
{ |
||||||
|
m_stream << formatTimeElapsed(); |
||||||
|
m_stream << formatType(type); |
||||||
|
} |
||||||
|
|
||||||
|
LogOperatorStyle::~LogOperatorStyle() |
||||||
|
{ |
||||||
|
m_stream.write("\n", 1); |
||||||
|
std::string string = m_stream.str(); |
||||||
|
fputs(string.c_str(), m_file); |
||||||
|
} |
||||||
|
|
||||||
|
LogOperatorStyle trace() |
||||||
|
{ |
||||||
|
return LogOperatorStyle(stdout, LogType::Trace); |
||||||
|
} |
||||||
|
|
||||||
|
LogOperatorStyle debug() |
||||||
|
{ |
||||||
|
return LogOperatorStyle(stdout, LogType::Debug); |
||||||
|
} |
||||||
|
|
||||||
|
LogOperatorStyle success() |
||||||
|
{ |
||||||
|
return LogOperatorStyle(stdout, LogType::Success); |
||||||
|
} |
||||||
|
|
||||||
|
LogOperatorStyle info() |
||||||
|
{ |
||||||
|
return LogOperatorStyle(stdout, LogType::Info); |
||||||
|
} |
||||||
|
|
||||||
|
LogOperatorStyle warn() |
||||||
|
{ |
||||||
|
return LogOperatorStyle(stderr, LogType::Warn); |
||||||
|
} |
||||||
|
|
||||||
|
LogOperatorStyle error() |
||||||
|
{ |
||||||
|
return LogOperatorStyle(stderr, LogType::Error); |
||||||
|
} |
||||||
|
|
||||||
|
LogOperatorStyle critical() |
||||||
|
{ |
||||||
|
return LogOperatorStyle(stderr, LogType::Critical); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::Format
|
@ -0,0 +1,97 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstdio> // FILE, stderr, stdout |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/format/format.h" |
||||||
|
#include "util/format/print.h" |
||||||
|
#include "util/timer.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
static Util::Timer s_timer; |
||||||
|
|
||||||
|
enum class LogType : uint8_t { |
||||||
|
Trace, // White
|
||||||
|
Debug, // Purple
|
||||||
|
Success, // Green
|
||||||
|
Info, // Blue
|
||||||
|
Warn, // Bold yellow
|
||||||
|
Error, // Bold red
|
||||||
|
Critical, // Bold on red
|
||||||
|
}; |
||||||
|
|
||||||
|
std::string formatTimeElapsed(); |
||||||
|
std::string formatType(LogType type); |
||||||
|
|
||||||
|
#define LOG_FUNCTION(name, file, type) \ |
||||||
|
template<size_t N, typename... Parameters> \
|
||||||
|
void name(const char(&format)[N], const Parameters&... parameters) \
|
||||||
|
{ \
|
||||||
|
print(file, "{}", formatTimeElapsed()); \
|
||||||
|
print(file, "{}", formatType(type)); \
|
||||||
|
VariadicParameters variadicParameters { parameters... }; \
|
||||||
|
print(file, format, variadicParameters); \
|
||||||
|
print(file, "\n"); \
|
||||||
|
} |
||||||
|
|
||||||
|
LOG_FUNCTION(trace, stdout, LogType::Trace); |
||||||
|
LOG_FUNCTION(debug, stdout, LogType::Debug); |
||||||
|
LOG_FUNCTION(success, stdout, LogType::Success); |
||||||
|
LOG_FUNCTION(info, stdout, LogType::Info); |
||||||
|
LOG_FUNCTION(warn, stderr, LogType::Warn); |
||||||
|
LOG_FUNCTION(error, stderr, LogType::Error); |
||||||
|
LOG_FUNCTION(critical, stderr, LogType::Critical); |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
class LogOperatorStyle { |
||||||
|
public: |
||||||
|
LogOperatorStyle(FILE* file, LogType type); |
||||||
|
virtual ~LogOperatorStyle(); |
||||||
|
|
||||||
|
Builder& builder() { return m_builder; } |
||||||
|
|
||||||
|
private: |
||||||
|
FILE* m_file; |
||||||
|
LogType m_type; |
||||||
|
|
||||||
|
std::stringstream m_stream; |
||||||
|
Builder m_builder; |
||||||
|
}; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
const LogOperatorStyle& operator<<(const LogOperatorStyle& logOperatorStyle, const T& value) |
||||||
|
{ |
||||||
|
_format(const_cast<LogOperatorStyle&>(logOperatorStyle).builder(), value); |
||||||
|
return logOperatorStyle; |
||||||
|
} |
||||||
|
|
||||||
|
LogOperatorStyle trace(); |
||||||
|
LogOperatorStyle debug(); |
||||||
|
LogOperatorStyle success(); |
||||||
|
LogOperatorStyle info(); |
||||||
|
LogOperatorStyle warn(); |
||||||
|
LogOperatorStyle error(); |
||||||
|
LogOperatorStyle critical(); |
||||||
|
|
||||||
|
} // namespace Util::Format
|
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
using Util::Format::critical; |
||||||
|
using Util::Format::debug; |
||||||
|
using Util::Format::error; |
||||||
|
using Util::Format::info; |
||||||
|
using Util::Format::success; |
||||||
|
using Util::Format::trace; |
||||||
|
using Util::Format::warn; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,419 @@ |
|||||||
|
/*
|
||||||
|
* 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/format/print.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"; |
||||||
|
|
||||||
|
static int p = 1; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
if (!p) { |
||||||
|
p++; |
||||||
|
Util::Format::print("{}\n", specifier); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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
|
@ -0,0 +1,61 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <optional> |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/genericlexer.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
class Builder; |
||||||
|
struct Specifier; |
||||||
|
|
||||||
|
class Parser final : public GenericLexer { |
||||||
|
public: |
||||||
|
enum class ArgumentIndexingMode { |
||||||
|
Automatic, // {} ,{}
|
||||||
|
Manual, // {0},{1}
|
||||||
|
}; |
||||||
|
|
||||||
|
enum class ParameterType { |
||||||
|
Integral, |
||||||
|
FloatingPoint, |
||||||
|
Char, |
||||||
|
CString, |
||||||
|
String, |
||||||
|
Pointer, |
||||||
|
Container, |
||||||
|
}; |
||||||
|
|
||||||
|
Parser(std::string_view format, size_t parameterCount); |
||||||
|
virtual ~Parser(); |
||||||
|
|
||||||
|
void checkFormatParameterConsistency(); |
||||||
|
size_t stringToNumber(std::string_view value); |
||||||
|
|
||||||
|
std::string_view consumeLiteral(); |
||||||
|
std::optional<size_t> consumeIndex(); |
||||||
|
|
||||||
|
void parseSpecifier(Specifier& specifier, ParameterType type); |
||||||
|
constexpr void checkSpecifierIntegralType(const Specifier& specifier); |
||||||
|
constexpr void checkSpecifierFloatingPointType(const Specifier& specifier); |
||||||
|
constexpr void checkSpecifierCharType(const Specifier& specifier); |
||||||
|
constexpr void checkSpecifierCStringType(const Specifier& specifier); |
||||||
|
constexpr void checkSpecifierStringType(const Specifier& specifier); |
||||||
|
constexpr void checkSpecifierPointerType(const Specifier& specifier); |
||||||
|
constexpr void checkSpecifierContainerType(const Specifier& specifier); |
||||||
|
constexpr void checkSpecifierType(const Specifier& specifier, ParameterType type); |
||||||
|
|
||||||
|
private: |
||||||
|
ArgumentIndexingMode m_mode { ArgumentIndexingMode::Automatic }; |
||||||
|
size_t m_parameterCount { 0 }; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util::Format
|
@ -0,0 +1,52 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <cstdio> // FILE, fputs, stdout, stderr |
||||||
|
#include <iomanip> // setprecision |
||||||
|
#include <ios> // defaultfloat, fixed |
||||||
|
#include <sstream> // stringstream |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/format/print.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
void variadicPrint(FILE* file, std::string_view format, TypeErasedParameters& parameters) |
||||||
|
{ |
||||||
|
std::stringstream stream; |
||||||
|
variadicFormat(stream, format, parameters); |
||||||
|
|
||||||
|
std::string string = stream.str(); |
||||||
|
fputs(string.c_str(), file); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
PrintOperatorStyle::PrintOperatorStyle(FILE* file) |
||||||
|
: m_file(file) |
||||||
|
, m_stream() |
||||||
|
, m_builder(m_stream) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
PrintOperatorStyle::~PrintOperatorStyle() |
||||||
|
{ |
||||||
|
std::string string = m_stream.str(); |
||||||
|
fputs(string.c_str(), m_file); |
||||||
|
} |
||||||
|
|
||||||
|
PrintOperatorStyle print() |
||||||
|
{ |
||||||
|
return PrintOperatorStyle(stdout); |
||||||
|
} |
||||||
|
|
||||||
|
PrintOperatorStyle print(FILE* file) |
||||||
|
{ |
||||||
|
return PrintOperatorStyle(file); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::Format
|
@ -0,0 +1,69 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstdint> // uint8_t |
||||||
|
#include <cstdio> // FILE, stdout |
||||||
|
#include <sstream> // stringstream |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/format/format.h" |
||||||
|
#include "util/timer.h" |
||||||
|
|
||||||
|
namespace Util::Format { |
||||||
|
|
||||||
|
void variadicPrint(FILE* file, std::string_view format, TypeErasedParameters& parameters); |
||||||
|
|
||||||
|
template<size_t N, typename... Parameters> |
||||||
|
void print(const char (&format)[N], const Parameters&... parameters) |
||||||
|
{ |
||||||
|
VariadicParameters variadicParameters { parameters... }; |
||||||
|
variadicPrint(stdout, { format, N - 1 }, variadicParameters); |
||||||
|
} |
||||||
|
|
||||||
|
template<size_t N, typename... Parameters> |
||||||
|
void print(FILE* file, const char (&format)[N], const Parameters&... parameters) |
||||||
|
{ |
||||||
|
VariadicParameters variadicParameters { parameters... }; |
||||||
|
variadicPrint(file, { format, N - 1 }, variadicParameters); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
class PrintOperatorStyle { |
||||||
|
public: |
||||||
|
PrintOperatorStyle(FILE* file); |
||||||
|
virtual ~PrintOperatorStyle(); |
||||||
|
|
||||||
|
Builder& builder() { return m_builder; } |
||||||
|
|
||||||
|
private: |
||||||
|
FILE* m_file; |
||||||
|
|
||||||
|
std::stringstream m_stream; |
||||||
|
Builder m_builder; |
||||||
|
}; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
const PrintOperatorStyle& operator<<(const PrintOperatorStyle& printOperatorStyle, const T& value) |
||||||
|
{ |
||||||
|
_format(const_cast<PrintOperatorStyle&>(printOperatorStyle).builder(), value); |
||||||
|
return printOperatorStyle; |
||||||
|
} |
||||||
|
|
||||||
|
PrintOperatorStyle print(); |
||||||
|
PrintOperatorStyle print(FILE* file); |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
} // namespace Util::Format
|
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
using Util::Format::print; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,71 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <algorithm> // max, min |
||||||
|
|
||||||
|
#include "util/genericlexer.h" |
||||||
|
#include "util/meta/assert.h" |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
GenericLexer::GenericLexer(std::string_view input) |
||||||
|
: m_input(input) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
GenericLexer::~GenericLexer() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
size_t GenericLexer::tell() const |
||||||
|
{ |
||||||
|
return m_index; |
||||||
|
} |
||||||
|
|
||||||
|
size_t GenericLexer::tellRemaining() const |
||||||
|
{ |
||||||
|
return m_input.length() - m_index; |
||||||
|
} |
||||||
|
|
||||||
|
bool GenericLexer::isEOF() const |
||||||
|
{ |
||||||
|
return m_index >= m_input.length(); |
||||||
|
} |
||||||
|
|
||||||
|
char GenericLexer::peek(size_t offset) const |
||||||
|
{ |
||||||
|
return (m_index + offset < m_input.length()) ? m_input[m_index + offset] : '\0'; |
||||||
|
} |
||||||
|
|
||||||
|
void GenericLexer::ignore(size_t count) |
||||||
|
{ |
||||||
|
m_index += std::min(count, m_input.length() - m_index); |
||||||
|
} |
||||||
|
|
||||||
|
void GenericLexer::retreat(size_t count) |
||||||
|
{ |
||||||
|
m_index -= std::min(count, m_index); |
||||||
|
} |
||||||
|
|
||||||
|
char GenericLexer::consume() |
||||||
|
{ |
||||||
|
VERIFY(!isEOF()); |
||||||
|
return m_input[m_index++]; |
||||||
|
} |
||||||
|
|
||||||
|
bool GenericLexer::consumeSpecific(const char& character) |
||||||
|
{ |
||||||
|
if (peek() != character) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
ignore(); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,41 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
class GenericLexer { |
||||||
|
public: |
||||||
|
GenericLexer(std::string_view input); |
||||||
|
virtual ~GenericLexer(); |
||||||
|
|
||||||
|
// Position
|
||||||
|
|
||||||
|
size_t tell() const; |
||||||
|
size_t tellRemaining() const; |
||||||
|
bool isEOF() const; |
||||||
|
|
||||||
|
// Access
|
||||||
|
|
||||||
|
char peek(size_t offset = 0) const; |
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
|
||||||
|
void ignore(size_t count = 1); |
||||||
|
void retreat(size_t count = 1); |
||||||
|
char consume(); |
||||||
|
bool consumeSpecific(const char& character); |
||||||
|
|
||||||
|
protected: |
||||||
|
size_t m_index { 0 }; |
||||||
|
std::string_view m_input; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,26 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "util/json/array.h" |
||||||
|
#include "util/json/value.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
void Array::emplace_back(Value element) |
||||||
|
{ |
||||||
|
m_elements.emplace_back(std::move(element)); |
||||||
|
} |
||||||
|
|
||||||
|
Value& Array::operator[](size_t index) |
||||||
|
{ |
||||||
|
if (index + 1 > m_elements.size()) { |
||||||
|
m_elements.resize(index + 1); |
||||||
|
} |
||||||
|
|
||||||
|
return m_elements[index]; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,56 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <utility> // move |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "util/json/parser.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
class Value; |
||||||
|
|
||||||
|
class Array { |
||||||
|
public: |
||||||
|
Array() {} |
||||||
|
virtual ~Array() {} |
||||||
|
|
||||||
|
Array(const std::vector<Value>& elements) |
||||||
|
: m_elements(elements) |
||||||
|
{} |
||||||
|
|
||||||
|
Array(const Array& other) |
||||||
|
: m_elements(other.m_elements) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
// Capacity
|
||||||
|
|
||||||
|
bool empty() const { return m_elements.empty(); } |
||||||
|
size_t size() const { return m_elements.size(); } |
||||||
|
void reserve(size_t size) { m_elements.reserve(size); } |
||||||
|
|
||||||
|
// Element access
|
||||||
|
|
||||||
|
Value& operator[](size_t index); |
||||||
|
|
||||||
|
Value& at(size_t index) { return m_elements.at(index); } |
||||||
|
const Value& at(size_t index) const { return m_elements.at(index); } |
||||||
|
|
||||||
|
const std::vector<Value>& elements() const { return m_elements; } |
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
|
||||||
|
void clear() { m_elements.clear(); } |
||||||
|
void emplace_back(Value element); |
||||||
|
|
||||||
|
private: |
||||||
|
std::vector<Value> m_elements; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,133 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <algorithm> // transform |
||||||
|
#include <cstddef> // nullptr_t |
||||||
|
#include <map> |
||||||
|
#include <string> |
||||||
|
#include <unordered_map> |
||||||
|
#include <utility> // forward |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "util/json/array.h" |
||||||
|
#include "util/json/object.h" |
||||||
|
#include "util/meta/assert.h" |
||||||
|
#include "util/meta/odr.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
namespace Detail { |
||||||
|
|
||||||
|
// Required for containers with Json::Value type
|
||||||
|
template<typename Json> |
||||||
|
void fromJson(const Json& json, Json& value) |
||||||
|
{ |
||||||
|
value = json; |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json> |
||||||
|
void fromJson(const Json& json, std::nullptr_t& null) |
||||||
|
{ |
||||||
|
VERIFY(json.type() == Json::Type::Null); |
||||||
|
null = nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json> |
||||||
|
void fromJson(const Json& json, bool& boolean) |
||||||
|
{ |
||||||
|
VERIFY(json.type() == Json::Type::Bool); |
||||||
|
boolean = json.asBool(); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json> |
||||||
|
void fromJson(const Json& json, int& number) |
||||||
|
{ |
||||||
|
VERIFY(json.type() == Json::Type::Number); |
||||||
|
number = (int)json.asDouble(); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json> |
||||||
|
void fromJson(const Json& json, double& number) |
||||||
|
{ |
||||||
|
VERIFY(json.type() == Json::Type::Number); |
||||||
|
number = json.asDouble(); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json> |
||||||
|
void fromJson(const Json& json, std::string& string) |
||||||
|
{ |
||||||
|
VERIFY(json.type() == Json::Type::String); |
||||||
|
string = json.asString(); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json, typename T> |
||||||
|
void fromJson(const Json& json, std::vector<T>& array) |
||||||
|
{ |
||||||
|
VERIFY(json.type() == Json::Type::Array); |
||||||
|
array.resize(json.size()); |
||||||
|
std::transform( |
||||||
|
json.asArray().elements().begin(), |
||||||
|
json.asArray().elements().end(), |
||||||
|
array.begin(), |
||||||
|
[](const Json& json) { |
||||||
|
return json.template get<T>(); // (missing-dependent-template-keyword)
|
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json, typename T> |
||||||
|
void fromJson(const Json& json, std::map<std::string, T>& object) |
||||||
|
{ |
||||||
|
VERIFY(json.type() == Json::Type::Object); |
||||||
|
object.clear(); |
||||||
|
for (const auto& [name, value] : json.asObject().members()) { |
||||||
|
object.emplace(name, value.template get<T>()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json, typename T> |
||||||
|
void fromJson(const Json& json, std::unordered_map<std::string, T>& object) |
||||||
|
{ |
||||||
|
VERIFY(json.type() == Json::Type::Object); |
||||||
|
object.clear(); |
||||||
|
for (const auto& [name, value] : json.asObject().members()) { |
||||||
|
object.emplace(name, value.template get<T>()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct fromJsonFunction { |
||||||
|
template<typename Json, typename T> |
||||||
|
auto operator()(const Json& json, T&& value) const |
||||||
|
{ |
||||||
|
return fromJson(json, std::forward<T>(value)); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Detail
|
||||||
|
|
||||||
|
// Anonymous namespace prevents multiple definition of the reference
|
||||||
|
namespace { |
||||||
|
// Function object
|
||||||
|
constexpr const auto& fromJson = Util::Detail::staticConst<Detail::fromJsonFunction>; // NOLINT(misc-definitions-in-headers,clang-diagnostic-unused-variable)
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace Util::JSON
|
||||||
|
|
||||||
|
// Customization Points
|
||||||
|
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
|
||||||
|
|
||||||
|
// Json::fromJson is a function object, the type of which is
|
||||||
|
// Json::Detail::fromJsonFunction. In the Json::Detail namespace are the
|
||||||
|
// fromJson free functions. The function call operator of fromJsonFunction makes
|
||||||
|
// an unqualified call to fromJson which, since it shares the Detail namespace
|
||||||
|
// with the fromJson free functions, will consider those in addition to any
|
||||||
|
// overloads that are found by argument-dependent lookup.
|
||||||
|
|
||||||
|
// Variable templates are linked externally, therefor every translation unit
|
||||||
|
// will see the same address for Detail::staticConst<Detail::fromJsonFunction>.
|
||||||
|
// Since Json::fromJson is a reference to the variable template, it too will
|
||||||
|
// have the same address in all translation units.
|
@ -0,0 +1,114 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <algorithm> // count |
||||||
|
#include <sstream> // istringstream |
||||||
|
#include <string> // getline |
||||||
|
|
||||||
|
#include "util/json/job.h" |
||||||
|
#include "util/json/lexer.h" |
||||||
|
#include "util/json/parser.h" |
||||||
|
#include "util/json/value.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
Job::Job(std::string_view input) |
||||||
|
: m_input(input) |
||||||
|
{ |
||||||
|
// FIXME: Make this work for all newline types: \n, \r, \r\n
|
||||||
|
m_lineNumbersWidth = std::count(m_input.begin(), m_input.end(), '\n'); |
||||||
|
m_lineNumbersWidth += m_input.back() == '\n' ? 0 : 1; |
||||||
|
m_lineNumbersWidth = std::to_string(m_lineNumbersWidth).length(); |
||||||
|
} |
||||||
|
|
||||||
|
Job::~Job() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
Value Job::fire() |
||||||
|
{ |
||||||
|
Lexer lexer(this); |
||||||
|
lexer.analyze(); |
||||||
|
|
||||||
|
if (!m_success) { |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
Parser parser(this); |
||||||
|
Value value = parser.parse(); |
||||||
|
|
||||||
|
if (!m_success) { |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
void Job::printErrorLine(Token token, const char* message) |
||||||
|
{ |
||||||
|
m_success = false; |
||||||
|
|
||||||
|
// Error message
|
||||||
|
std::string errorFormat = "\033[;1m" // Bold
|
||||||
|
"JSON:%zu:%zu: " |
||||||
|
"\033[31;1m" // Bold red
|
||||||
|
"error: " |
||||||
|
"\033[0m" // Reset
|
||||||
|
"%s" |
||||||
|
"\n"; |
||||||
|
fprintf(stderr, |
||||||
|
errorFormat.c_str(), |
||||||
|
token.line + 1, |
||||||
|
token.column + 1, |
||||||
|
message); |
||||||
|
|
||||||
|
// Get the JSON line that caused the error
|
||||||
|
std::istringstream input(m_input.data()); |
||||||
|
std::string line; |
||||||
|
for (size_t i = 0; std::getline(input, line); ++i) { |
||||||
|
if (i == token.line) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
// Replace tab indentation with spaces
|
||||||
|
size_t oldLineLength = line.length(); |
||||||
|
size_t tabs = line.find_first_not_of('\t'); |
||||||
|
if (tabs > 0 && tabs < line.size()) { |
||||||
|
line = std::string(tabs * 4, ' ') + line.substr(tabs); |
||||||
|
} |
||||||
|
token.column += line.length() - oldLineLength; |
||||||
|
|
||||||
|
// JSON line
|
||||||
|
std::string lineFormat = " %" |
||||||
|
+ std::to_string(m_lineNumbersWidth) |
||||||
|
+ "zu | " |
||||||
|
"%s" |
||||||
|
"\033[31;1m" // Bold red
|
||||||
|
"%s" |
||||||
|
"\033[0m" // Reset
|
||||||
|
"\n"; |
||||||
|
fprintf(stderr, |
||||||
|
lineFormat.c_str(), |
||||||
|
token.line + 1, |
||||||
|
line.substr(0, token.column).c_str(), |
||||||
|
line.substr(token.column).c_str()); |
||||||
|
|
||||||
|
// Arrow pointer
|
||||||
|
std::string arrowFormat = " %s | " |
||||||
|
"\033[31;1m" // Bold red
|
||||||
|
"%s^%s" |
||||||
|
"\033[0m" // Reset
|
||||||
|
"\n"; |
||||||
|
fprintf(stderr, |
||||||
|
arrowFormat.c_str(), |
||||||
|
std::string(m_lineNumbersWidth, ' ').c_str(), |
||||||
|
std::string(token.column, ' ').c_str(), |
||||||
|
std::string(line.length() - token.column, '~').c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,41 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <string_view> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "util/json/lexer.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
class Value; |
||||||
|
|
||||||
|
class Job { |
||||||
|
public: |
||||||
|
Job(std::string_view input); |
||||||
|
virtual ~Job(); |
||||||
|
|
||||||
|
Value fire(); |
||||||
|
|
||||||
|
void printErrorLine(Token token, const char* message); |
||||||
|
|
||||||
|
bool success() const { return m_success; } |
||||||
|
std::string_view input() const { return m_input; } |
||||||
|
std::vector<Token>* tokens() { return &m_tokens; } |
||||||
|
|
||||||
|
private: |
||||||
|
bool m_success { true }; |
||||||
|
|
||||||
|
std::string_view m_input; |
||||||
|
size_t m_lineNumbersWidth { 0 }; |
||||||
|
|
||||||
|
std::vector<Token> m_tokens; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,15 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "util/json/value.h" |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
using Json = Util::JSON::Value; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,217 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <cstddef> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "util/json/job.h" |
||||||
|
#include "util/json/lexer.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
Lexer::Lexer(Job* job) |
||||||
|
: GenericLexer(job->input()) |
||||||
|
, m_job(job) |
||||||
|
, m_tokens(job->tokens()) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
Lexer::~Lexer() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
void Lexer::analyze() |
||||||
|
{ |
||||||
|
while (m_index < m_input.length()) { |
||||||
|
switch (peek()) { |
||||||
|
case '{': |
||||||
|
m_tokens->push_back({ Token::Type::BraceOpen, m_line, m_column, "{" }); |
||||||
|
break; |
||||||
|
case '}': |
||||||
|
m_tokens->push_back({ Token::Type::BraceClose, m_line, m_column, "}" }); |
||||||
|
break; |
||||||
|
case '[': |
||||||
|
m_tokens->push_back({ Token::Type::BracketOpen, m_line, m_column, "[" }); |
||||||
|
break; |
||||||
|
case ']': |
||||||
|
m_tokens->push_back({ Token::Type::BracketClose, m_line, m_column, "]" }); |
||||||
|
break; |
||||||
|
case ':': |
||||||
|
m_tokens->push_back({ Token::Type::Colon, m_line, m_column, ":" }); |
||||||
|
break; |
||||||
|
case ',': |
||||||
|
m_tokens->push_back({ Token::Type::Comma, m_line, m_column, "," }); |
||||||
|
break; |
||||||
|
case '"': |
||||||
|
if (!consumeString()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
break; |
||||||
|
case '-': |
||||||
|
case '0': |
||||||
|
case '1': |
||||||
|
case '2': |
||||||
|
case '3': |
||||||
|
case '4': |
||||||
|
case '5': |
||||||
|
case '6': |
||||||
|
case '7': |
||||||
|
case '8': |
||||||
|
case '9': |
||||||
|
if (!consumeNumber()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'a': |
||||||
|
case 'b': |
||||||
|
case 'c': |
||||||
|
case 'd': |
||||||
|
case 'e': |
||||||
|
case 'f': |
||||||
|
case 'g': |
||||||
|
case 'h': |
||||||
|
case 'i': |
||||||
|
case 'j': |
||||||
|
case 'k': |
||||||
|
case 'l': |
||||||
|
case 'm': |
||||||
|
case 'n': |
||||||
|
case 'o': |
||||||
|
case 'p': |
||||||
|
case 'q': |
||||||
|
case 'r': |
||||||
|
case 's': |
||||||
|
case 't': |
||||||
|
case 'u': |
||||||
|
case 'v': |
||||||
|
case 'w': |
||||||
|
case 'x': |
||||||
|
case 'y': |
||||||
|
case 'z': |
||||||
|
if (!consumeLiteral()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ' ': |
||||||
|
case '\t': |
||||||
|
break; |
||||||
|
case '\r': |
||||||
|
if (peek(1) == '\n') { // CRLF \r\n
|
||||||
|
break; |
||||||
|
} |
||||||
|
m_column = -1; |
||||||
|
m_line++; |
||||||
|
break; |
||||||
|
case '\n': |
||||||
|
m_column = -1; |
||||||
|
m_line++; |
||||||
|
break; |
||||||
|
default: |
||||||
|
// Error!
|
||||||
|
m_tokens->push_back({ Token::Type::None, m_line, m_column, std::string(1, peek()) }); |
||||||
|
m_job->printErrorLine(m_tokens->back(), |
||||||
|
(std::string() + "unexpected character '" + peek() + "'").c_str()); |
||||||
|
return; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
ignore(); |
||||||
|
m_column++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
bool Lexer::consumeString() |
||||||
|
{ |
||||||
|
size_t column = m_column; |
||||||
|
std::string symbol = ""; |
||||||
|
|
||||||
|
bool escape = false; |
||||||
|
char character = consume(); |
||||||
|
for (;;) { |
||||||
|
character = peek(); |
||||||
|
|
||||||
|
if (!escape && character == '\\') { |
||||||
|
symbol += '\\'; |
||||||
|
ignore(); |
||||||
|
escape = true; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (!escape |
||||||
|
&& (character == '"' |
||||||
|
|| character == '\r' |
||||||
|
|| character == '\n' |
||||||
|
|| character == '\0')) { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
symbol += character; |
||||||
|
ignore(); |
||||||
|
|
||||||
|
if (escape) { |
||||||
|
escape = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
m_tokens->push_back({ Token::Type::String, m_line, column, symbol }); |
||||||
|
|
||||||
|
if (character != '"') { |
||||||
|
m_job->printErrorLine(m_job->tokens()->back(), "strings should be wrapped in double quotes"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool Lexer::consumeNumberOrLiteral(Token::Type type) |
||||||
|
{ |
||||||
|
size_t index = m_index; |
||||||
|
size_t column = m_column; |
||||||
|
|
||||||
|
for (char character;;) { |
||||||
|
character = peek(); |
||||||
|
|
||||||
|
if (character == '{' |
||||||
|
|| character == '}' |
||||||
|
|| character == '[' |
||||||
|
|| character == ']' |
||||||
|
|| character == ':' |
||||||
|
|| character == ',' |
||||||
|
|| character == '"' |
||||||
|
|| character == ' ' |
||||||
|
|| character == '\t' |
||||||
|
|| character == '\r' |
||||||
|
|| character == '\n' |
||||||
|
|| character == '\0') { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
ignore(); |
||||||
|
} |
||||||
|
|
||||||
|
m_tokens->push_back({ type, m_line, column, |
||||||
|
std::string(m_input.substr(index, m_index - index)) }); |
||||||
|
|
||||||
|
retreat(); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool Lexer::consumeNumber() |
||||||
|
{ |
||||||
|
return consumeNumberOrLiteral(Token::Type::Number); |
||||||
|
} |
||||||
|
|
||||||
|
bool Lexer::consumeLiteral() |
||||||
|
{ |
||||||
|
return consumeNumberOrLiteral(Token::Type::Literal); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,65 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
// The JavaScript Object Notation (JSON) Data Interchange Format
|
||||||
|
// https://www.rfc-editor.org/rfc/pdfrfc/rfc8259.txt.pdf
|
||||||
|
|
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdint> // uint8_t |
||||||
|
#include <string> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "util/genericlexer.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
class Job; |
||||||
|
|
||||||
|
struct Token { |
||||||
|
enum class Type : uint8_t { |
||||||
|
None, |
||||||
|
BraceOpen, // {
|
||||||
|
BraceClose, // }
|
||||||
|
BracketOpen, // [
|
||||||
|
BracketClose, // ]
|
||||||
|
Colon, // :
|
||||||
|
Comma, // ,
|
||||||
|
String, // "foobar"
|
||||||
|
Number, // 123.456
|
||||||
|
Literal, // false/null/true (case sensitive)
|
||||||
|
}; |
||||||
|
|
||||||
|
Type type { Type::None }; |
||||||
|
size_t line { 0 }; |
||||||
|
size_t column { 0 }; |
||||||
|
std::string symbol; |
||||||
|
}; |
||||||
|
|
||||||
|
// Lexical analyzer
|
||||||
|
class Lexer final : public Util::GenericLexer { |
||||||
|
public: |
||||||
|
Lexer(Job* job); |
||||||
|
virtual ~Lexer(); |
||||||
|
|
||||||
|
void analyze(); |
||||||
|
|
||||||
|
private: |
||||||
|
bool consumeString(); |
||||||
|
bool consumeNumberOrLiteral(Token::Type type); |
||||||
|
bool consumeNumber(); |
||||||
|
bool consumeLiteral(); |
||||||
|
|
||||||
|
Job* m_job { nullptr }; |
||||||
|
|
||||||
|
size_t m_column { 0 }; |
||||||
|
size_t m_line { 0 }; |
||||||
|
|
||||||
|
std::vector<Token>* m_tokens { nullptr }; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,26 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "util/json/object.h" |
||||||
|
#include "util/json/value.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
void Object::emplace(const std::string& name, Value value) |
||||||
|
{ |
||||||
|
m_members.emplace(name, std::move(value)); |
||||||
|
} |
||||||
|
|
||||||
|
Value& Object::operator[](const std::string& name) |
||||||
|
{ |
||||||
|
if (m_members.find(name) == m_members.end()) { |
||||||
|
emplace(name, {}); |
||||||
|
} |
||||||
|
|
||||||
|
return m_members.at(name); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,52 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <map> |
||||||
|
#include <string> |
||||||
|
#include <utility> // move |
||||||
|
|
||||||
|
#include "util/json/parser.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
class Value; |
||||||
|
|
||||||
|
class Object { |
||||||
|
public: |
||||||
|
Object() {} |
||||||
|
virtual ~Object() {} |
||||||
|
|
||||||
|
Object(const Object& other) |
||||||
|
: m_members(other.m_members) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
// Capacity
|
||||||
|
|
||||||
|
bool empty() const { return m_members.empty(); } |
||||||
|
size_t size() const { return m_members.size(); } |
||||||
|
|
||||||
|
// Member access
|
||||||
|
|
||||||
|
Value& operator[](const std::string& name); |
||||||
|
|
||||||
|
Value& at(const std::string& name) { return m_members.at(name); } |
||||||
|
const Value& at(const std::string& name) const { return m_members.at(name); } |
||||||
|
|
||||||
|
const std::map<std::string, Value>& members() const { return m_members; } |
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
|
||||||
|
void clear() { m_members.clear(); } |
||||||
|
void emplace(const std::string& name, Value value); |
||||||
|
|
||||||
|
private: |
||||||
|
std::map<std::string, Value> m_members; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,497 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <algorithm> // count |
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdint> // uint8_t |
||||||
|
#include <cstdio> // printf |
||||||
|
#include <map> |
||||||
|
#include <string> // stod |
||||||
|
|
||||||
|
#include "util/json/array.h" |
||||||
|
#include "util/json/job.h" |
||||||
|
#include "util/json/lexer.h" |
||||||
|
#include "util/json/object.h" |
||||||
|
#include "util/json/parser.h" |
||||||
|
#include "util/json/value.h" |
||||||
|
#include "util/meta/assert.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
Parser::Parser(Job* job) |
||||||
|
: m_job(job) |
||||||
|
, m_tokens(m_job->tokens()) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
Parser::~Parser() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
Value Parser::parse() |
||||||
|
{ |
||||||
|
Value result; |
||||||
|
|
||||||
|
if (m_tokens->size() == 0) { |
||||||
|
m_job->printErrorLine({}, "expecting token, not 'EOF'"); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
Token token = peek(); |
||||||
|
switch (token.type) { |
||||||
|
case Token::Type::Literal: |
||||||
|
result = consumeLiteral(); |
||||||
|
break; |
||||||
|
case Token::Type::Number: |
||||||
|
result = consumeNumber(); |
||||||
|
break; |
||||||
|
case Token::Type::String: |
||||||
|
result = consumeString(); |
||||||
|
break; |
||||||
|
case Token::Type::BracketOpen: |
||||||
|
result = consumeArray(); |
||||||
|
break; |
||||||
|
case Token::Type::BraceOpen: |
||||||
|
result = consumeObject(); |
||||||
|
break; |
||||||
|
case Token::Type::BracketClose: |
||||||
|
m_job->printErrorLine(token, "expecting value, not ']'"); |
||||||
|
m_index++; |
||||||
|
break; |
||||||
|
case Token::Type::BraceClose: |
||||||
|
m_job->printErrorLine(token, "expecting string, not '}'"); |
||||||
|
m_index++; |
||||||
|
break; |
||||||
|
default: |
||||||
|
m_job->printErrorLine(token, "multiple root elements"); |
||||||
|
m_index++; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if (!isEOF()) { |
||||||
|
m_job->printErrorLine(peek(), "multiple root elements"); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
bool Parser::isEOF() |
||||||
|
{ |
||||||
|
return m_index >= m_tokens->size(); |
||||||
|
} |
||||||
|
|
||||||
|
Token Parser::peek() |
||||||
|
{ |
||||||
|
VERIFY(!isEOF()); |
||||||
|
return (*m_tokens)[m_index]; |
||||||
|
} |
||||||
|
|
||||||
|
Token Parser::consume() |
||||||
|
{ |
||||||
|
VERIFY(!isEOF()); |
||||||
|
return (*m_tokens)[m_index++]; |
||||||
|
} |
||||||
|
|
||||||
|
void Parser::ignoreUntil(Token::Type type) |
||||||
|
{ |
||||||
|
while (!isEOF() && peek().type != type) { |
||||||
|
++m_index; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Value Parser::consumeLiteral() |
||||||
|
{ |
||||||
|
Token token = consume(); |
||||||
|
|
||||||
|
if (token.symbol == "null") { |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
else if (token.symbol == "true") { |
||||||
|
return true; |
||||||
|
} |
||||||
|
else if (token.symbol == "false") { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
m_job->printErrorLine(token, "invalid literal"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
Value Parser::consumeNumber() |
||||||
|
{ |
||||||
|
Token token = consume(); |
||||||
|
|
||||||
|
auto reportError = [this](Token token, const std::string& message) -> void { |
||||||
|
m_job->printErrorLine(token, message.c_str()); |
||||||
|
}; |
||||||
|
|
||||||
|
// Validation
|
||||||
|
// number = [ minus ] int [ frac ] [ exp ]
|
||||||
|
|
||||||
|
size_t minusPrefix = token.symbol[0] == '-' ? 1 : 0; |
||||||
|
|
||||||
|
// Leading 0s
|
||||||
|
if (token.symbol[minusPrefix] == '0' |
||||||
|
&& token.symbol[minusPrefix + 1] > '0' && token.symbol[minusPrefix + 1] < '9') { |
||||||
|
reportError(token, "invalid leading zero"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
enum class State : uint8_t { |
||||||
|
Int, |
||||||
|
Fraction, |
||||||
|
Exponent |
||||||
|
}; |
||||||
|
|
||||||
|
State state = State::Int; |
||||||
|
|
||||||
|
#define CHECK_IF_VALID_NUMBER \ |
||||||
|
if (character < 48 || character > 57) { \
|
||||||
|
reportError(token, std::string() + "invalid number, unexpected '" + character + '\''); \
|
||||||
|
return nullptr; \
|
||||||
|
} |
||||||
|
|
||||||
|
size_t fractionPosition = 0; |
||||||
|
size_t exponentPosition = 0; |
||||||
|
size_t length = token.symbol.length(); |
||||||
|
for (size_t i = 0; i < length; ++i) { |
||||||
|
char character = token.symbol[i]; |
||||||
|
|
||||||
|
// Int -> Fraction
|
||||||
|
if (character == '.' && state == State::Int) { |
||||||
|
state = State::Fraction; |
||||||
|
fractionPosition = i; |
||||||
|
continue; |
||||||
|
} |
||||||
|
// Int/Fraction -> Exponent
|
||||||
|
else if ((character == 'e' || character == 'E') && state != State::Exponent) { |
||||||
|
state = State::Exponent; |
||||||
|
exponentPosition = i; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (state == State::Int) { |
||||||
|
if (character == '-') { |
||||||
|
if (i == length - 1) { |
||||||
|
reportError(token, "expected number after minus"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
if (i != 0) { |
||||||
|
reportError(token, "invalid minus"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
CHECK_IF_VALID_NUMBER; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (state == State::Fraction) { |
||||||
|
CHECK_IF_VALID_NUMBER; |
||||||
|
} |
||||||
|
else if (state == State::Exponent) { |
||||||
|
if (character == '-' || character == '+') { |
||||||
|
if (i == length - 1) { |
||||||
|
reportError(token, "expected number after plus/minus"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
if (i > exponentPosition + 1) { |
||||||
|
reportError(token, "invalid plus/minus"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
CHECK_IF_VALID_NUMBER; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (fractionPosition != 0 || exponentPosition != 0) { |
||||||
|
if (fractionPosition == exponentPosition - 1) { |
||||||
|
reportError(token, "invalid exponent sign, expected number"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
if (fractionPosition == length - 1 || exponentPosition == length - 1) { |
||||||
|
reportError(token, "invalid number"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return std::stod(token.symbol); |
||||||
|
} |
||||||
|
|
||||||
|
Value Parser::consumeString() |
||||||
|
{ |
||||||
|
Token token = consume(); |
||||||
|
|
||||||
|
auto reportError = [this](Token token, const std::string& message) -> void { |
||||||
|
m_job->printErrorLine(token, message.c_str()); |
||||||
|
}; |
||||||
|
|
||||||
|
// FIXME: support \u Unicode character escape sequence
|
||||||
|
auto getPrintableString = [](char character) -> std::string { |
||||||
|
if (character == '"' || character == '\\' || character == '/' |
||||||
|
|| (character >= 0 && character <= 31)) { |
||||||
|
switch (character) { |
||||||
|
case '"': |
||||||
|
return "\\\""; |
||||||
|
break; |
||||||
|
case '\\': |
||||||
|
return "\\\\"; |
||||||
|
break; |
||||||
|
case '/': |
||||||
|
return "/"; |
||||||
|
break; |
||||||
|
case '\b': |
||||||
|
return "\\b"; |
||||||
|
break; |
||||||
|
case '\f': |
||||||
|
return "\\f"; |
||||||
|
break; |
||||||
|
case '\n': |
||||||
|
return "\\n"; |
||||||
|
break; |
||||||
|
case '\r': |
||||||
|
return "\\r"; |
||||||
|
break; |
||||||
|
case '\t': |
||||||
|
return "\\t"; |
||||||
|
break; |
||||||
|
default: |
||||||
|
char buffer[7]; |
||||||
|
sprintf(buffer, "\\u%0.4X", character); |
||||||
|
return std::string(buffer); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return std::string() + character; |
||||||
|
}; |
||||||
|
|
||||||
|
std::string string; |
||||||
|
|
||||||
|
bool escape = false; |
||||||
|
for (char character : token.symbol) { |
||||||
|
if (!escape) { |
||||||
|
if (character == '\\') { |
||||||
|
escape = true; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (character == '"' || (character >= 0 && character <= 31)) { |
||||||
|
reportError(token, "invalid string, unescaped character found"); |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
string += getPrintableString(character); |
||||||
|
|
||||||
|
if (escape) { |
||||||
|
escape = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return string; |
||||||
|
} |
||||||
|
|
||||||
|
Value Parser::consumeArray() |
||||||
|
{ |
||||||
|
m_index++; |
||||||
|
|
||||||
|
auto reportError = [this](Token token, const std::string& message) -> void { |
||||||
|
m_job->printErrorLine(token, message.c_str()); |
||||||
|
|
||||||
|
// After an error, try to find the closing bracket
|
||||||
|
ignoreUntil(Token::Type::BracketClose); |
||||||
|
m_index++; |
||||||
|
}; |
||||||
|
|
||||||
|
Value array = Value::Type::Array; |
||||||
|
Token token; |
||||||
|
for (;;) { |
||||||
|
// EOF
|
||||||
|
if (isEOF()) { |
||||||
|
reportError(m_tokens->at(m_index - 1), "expecting closing ']' at end"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
token = peek(); |
||||||
|
if (token.type == Token::Type::Literal) { |
||||||
|
array.emplace_back(consumeLiteral()); |
||||||
|
} |
||||||
|
else if (token.type == Token::Type::Number) { |
||||||
|
array.emplace_back(consumeNumber()); |
||||||
|
} |
||||||
|
else if (token.type == Token::Type::String) { |
||||||
|
array.emplace_back(consumeString()); |
||||||
|
} |
||||||
|
else if (token.type == Token::Type::BracketOpen) { |
||||||
|
array.emplace_back(consumeArray()); |
||||||
|
} |
||||||
|
else if (token.type == Token::Type::BraceOpen) { |
||||||
|
array.emplace_back(consumeObject()); |
||||||
|
} |
||||||
|
else if (token.type == Token::Type::BracketClose) { |
||||||
|
// Trailing comma
|
||||||
|
if (array.m_value.array->size() > 0) { |
||||||
|
reportError(m_tokens->at(m_index - 1), "invalid comma, expecting ']'"); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
reportError(token, "expecting value or ']', not '" + token.symbol + "'"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// EOF
|
||||||
|
if (isEOF()) { |
||||||
|
reportError(token, "expecting closing ']' at end"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Find , or ]
|
||||||
|
token = consume(); |
||||||
|
if (token.type == Token::Type::Comma) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
else if (token.type == Token::Type::BracketClose) { |
||||||
|
break; |
||||||
|
} |
||||||
|
else { |
||||||
|
reportError(m_tokens->at(m_index - 1), "expecting comma or ']', not '" + token.symbol + "'"); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return array; |
||||||
|
} |
||||||
|
|
||||||
|
Value Parser::consumeObject() |
||||||
|
{ |
||||||
|
m_index++; |
||||||
|
|
||||||
|
auto reportError = [this](Token token, const std::string& message) -> void { |
||||||
|
m_job->printErrorLine(token, message.c_str()); |
||||||
|
|
||||||
|
// After an error, try to find the closing brace
|
||||||
|
ignoreUntil(Token::Type::BraceClose); |
||||||
|
m_index++; |
||||||
|
}; |
||||||
|
|
||||||
|
Value object = Value::Type::Object; |
||||||
|
Token token; |
||||||
|
std::string name; |
||||||
|
std::map<std::string, uint8_t> unique; |
||||||
|
for (;;) { |
||||||
|
// EOF
|
||||||
|
if (isEOF()) { |
||||||
|
reportError(m_tokens->at(m_index - 1), "expecting closing '}' at end"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
token = consume(); |
||||||
|
if (token.type == Token::Type::BraceClose) { |
||||||
|
// Trailing comma
|
||||||
|
if (object.m_value.object->size() > 0) { |
||||||
|
reportError(m_tokens->at(m_index - 1), "invalid comma, expecting '}'"); |
||||||
|
} |
||||||
|
// Empty object
|
||||||
|
break; |
||||||
|
} |
||||||
|
if (token.type != Token::Type::String) { |
||||||
|
reportError(token, "expecting string or '}', not '" + token.symbol + "'"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Find member name
|
||||||
|
m_index--; |
||||||
|
Value tmpName = consumeString(); |
||||||
|
if (tmpName.m_type != Value::Type::String) { |
||||||
|
ignoreUntil(Token::Type::BraceClose); |
||||||
|
m_index++; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Check if name exists in hashmap
|
||||||
|
name = *tmpName.m_value.string; |
||||||
|
if (unique.find(name) != unique.end()) { |
||||||
|
reportError(token, "duplicate name '" + token.symbol + "', names should be unique"); |
||||||
|
break; |
||||||
|
} |
||||||
|
// Add name to hashmap
|
||||||
|
unique.insert({ name, 0 }); |
||||||
|
|
||||||
|
// EOF
|
||||||
|
if (isEOF()) { |
||||||
|
reportError(token, "expecting colon, not 'EOF'"); |
||||||
|
reportError(token, "expecting closing '}' at end"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Find :
|
||||||
|
token = consume(); |
||||||
|
if (token.type != Token::Type::Colon) { |
||||||
|
reportError(token, "expecting colon, not '" + token.symbol + "'"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// EOF
|
||||||
|
if (isEOF()) { |
||||||
|
reportError(token, "expecting value, not 'EOF'"); |
||||||
|
reportError(token, "expecting closing '}' at end"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Add member (name:value pair) to object
|
||||||
|
token = peek(); |
||||||
|
if (token.type == Token::Type::Literal) { |
||||||
|
object.emplace(name, consumeLiteral()); |
||||||
|
} |
||||||
|
else if (token.type == Token::Type::Number) { |
||||||
|
object.emplace(name, consumeNumber()); |
||||||
|
} |
||||||
|
else if (token.type == Token::Type::String) { |
||||||
|
object.emplace(name, consumeString()); |
||||||
|
} |
||||||
|
else if (token.type == Token::Type::BracketOpen) { |
||||||
|
object.emplace(name, consumeArray()); |
||||||
|
} |
||||||
|
else if (token.type == Token::Type::BraceOpen) { |
||||||
|
object.emplace(name, consumeObject()); |
||||||
|
} |
||||||
|
else { |
||||||
|
reportError(token, "expecting value, not '" + token.symbol + "'"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// EOF
|
||||||
|
if (isEOF()) { |
||||||
|
reportError(token, "expecting closing '}' at end"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Find , or }
|
||||||
|
token = consume(); |
||||||
|
if (token.type == Token::Type::Comma) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
else if (token.type == Token::Type::BraceClose) { |
||||||
|
break; |
||||||
|
} |
||||||
|
else { |
||||||
|
reportError(token, "expecting comma or '}', not '" + token.symbol + "'"); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return object; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,45 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "util/json/lexer.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
class Job; |
||||||
|
class Value; |
||||||
|
|
||||||
|
class Parser { |
||||||
|
public: |
||||||
|
Parser(Job* job); |
||||||
|
virtual ~Parser(); |
||||||
|
|
||||||
|
Value parse(); |
||||||
|
|
||||||
|
private: |
||||||
|
bool isEOF(); |
||||||
|
Token peek(); |
||||||
|
Token consume(); |
||||||
|
void ignoreUntil(Token::Type type); |
||||||
|
|
||||||
|
Value consumeLiteral(); |
||||||
|
Value consumeNumber(); |
||||||
|
Value consumeString(); |
||||||
|
Value consumeArray(); |
||||||
|
Value consumeObject(); |
||||||
|
|
||||||
|
Job* m_job { nullptr }; |
||||||
|
|
||||||
|
size_t m_index { 0 }; |
||||||
|
|
||||||
|
std::vector<Token>* m_tokens { nullptr }; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,155 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <cstdint> // uint32_t |
||||||
|
#include <iterator> // next |
||||||
|
#include <sstream> // ostringstream |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "util/json/array.h" |
||||||
|
#include "util/json/lexer.h" |
||||||
|
#include "util/json/object.h" |
||||||
|
#include "util/json/serializer.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
Serializer::Serializer(const uint32_t indent, const char indentCharacter) |
||||||
|
: m_indent(indent) |
||||||
|
, m_indentCharacter(indentCharacter) |
||||||
|
, m_compact(indent == 0) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
Serializer::~Serializer() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
std::string Serializer::dump(const Value& value) |
||||||
|
{ |
||||||
|
dumpHelper(value); |
||||||
|
return m_output; |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
void Serializer::dumpHelper(const Value& value, const uint32_t indentLevel) |
||||||
|
{ |
||||||
|
switch (value.m_type) { |
||||||
|
case Value::Type::Null: |
||||||
|
m_output += "null"; |
||||||
|
break; |
||||||
|
case Value::Type::Bool: |
||||||
|
m_output += value.m_value.boolean ? "true" : "false"; |
||||||
|
break; |
||||||
|
case Value::Type::Number: { |
||||||
|
std::ostringstream os; |
||||||
|
os << value.m_value.number; |
||||||
|
m_output += os.str(); |
||||||
|
break; |
||||||
|
} |
||||||
|
case Value::Type::String: |
||||||
|
m_output += "\"" + *value.m_value.string + "\""; |
||||||
|
break; |
||||||
|
case Value::Type::Array: |
||||||
|
dumpArray(value, indentLevel); |
||||||
|
break; |
||||||
|
case Value::Type::Object: |
||||||
|
dumpObject(value, indentLevel); |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Serializer::dumpArray(const Value& value, const uint32_t indentLevel) |
||||||
|
{ |
||||||
|
m_output += '['; |
||||||
|
if (!m_compact) { |
||||||
|
m_output += '\n'; |
||||||
|
} |
||||||
|
|
||||||
|
// Empty Array early return
|
||||||
|
if (value.m_value.array->empty()) { |
||||||
|
m_output += ']'; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
size_t i = 0; |
||||||
|
auto it = value.m_value.array->elements().cbegin(); |
||||||
|
if (!m_indent) { |
||||||
|
for (; i < value.m_value.array->size() - 1; ++i, ++it) { |
||||||
|
dumpHelper(*it, indentLevel + 1); |
||||||
|
m_output += ','; |
||||||
|
} |
||||||
|
dumpHelper(*it, indentLevel + 1); |
||||||
|
} |
||||||
|
else { |
||||||
|
std::string indentation = std::string(m_indent * (indentLevel + 1), m_indentCharacter); |
||||||
|
|
||||||
|
for (; i < value.m_value.array->size() - 1; ++i, ++it) { |
||||||
|
m_output += indentation; |
||||||
|
dumpHelper(*it, indentLevel + 1); |
||||||
|
m_output += ",\n"; |
||||||
|
} |
||||||
|
m_output += indentation; |
||||||
|
dumpHelper(*it, indentLevel + 1); |
||||||
|
m_output += '\n'; |
||||||
|
|
||||||
|
// Append indentation
|
||||||
|
m_output += std::string(m_indent * indentLevel, m_indentCharacter); |
||||||
|
} |
||||||
|
|
||||||
|
m_output += "]"; |
||||||
|
} |
||||||
|
|
||||||
|
void Serializer::dumpObject(const Value& value, const uint32_t indentLevel) |
||||||
|
{ |
||||||
|
m_output += '{'; |
||||||
|
if (!m_compact) { |
||||||
|
m_output += '\n'; |
||||||
|
} |
||||||
|
|
||||||
|
// Empty Object early return
|
||||||
|
if (value.m_value.object->empty()) { |
||||||
|
m_output += '}'; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
size_t i = 0; |
||||||
|
auto it = value.m_value.object->members().cbegin(); |
||||||
|
if (!m_indent) { |
||||||
|
for (; i < value.m_value.object->size() - 1; ++i, ++it) { |
||||||
|
m_output += '"' + it->first + "\":"; |
||||||
|
dumpHelper(it->second, indentLevel + 1); |
||||||
|
m_output += ','; |
||||||
|
} |
||||||
|
m_output += '"' + it->first + "\":"; |
||||||
|
dumpHelper(it->second, indentLevel + 1); |
||||||
|
} |
||||||
|
else { |
||||||
|
std::string indentation = std::string(m_indent * (indentLevel + 1), m_indentCharacter); |
||||||
|
|
||||||
|
for (; i < value.m_value.object->size() - 1; ++i, ++it) { |
||||||
|
m_output += indentation; |
||||||
|
m_output += '"' + it->first + "\": "; |
||||||
|
dumpHelper(it->second, indentLevel + 1); |
||||||
|
m_output += ",\n"; |
||||||
|
} |
||||||
|
m_output += indentation; |
||||||
|
m_output += '"' + it->first + "\": "; |
||||||
|
dumpHelper(it->second, indentLevel + 1); |
||||||
|
m_output += '\n'; |
||||||
|
|
||||||
|
// Append indentation
|
||||||
|
m_output += std::string(m_indent * indentLevel, m_indentCharacter); |
||||||
|
} |
||||||
|
|
||||||
|
m_output += '}'; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,35 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstdint> // uint32_t |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "util/json/value.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
class Serializer { |
||||||
|
public: |
||||||
|
Serializer(const uint32_t indent = 0, const char indentCharacter = ' '); |
||||||
|
virtual ~Serializer(); |
||||||
|
|
||||||
|
std::string dump(const Value& value); |
||||||
|
|
||||||
|
private: |
||||||
|
void dumpHelper(const Value& value, const uint32_t indentLevel = 0); |
||||||
|
void dumpArray(const Value& value, const uint32_t indentLevel = 0); |
||||||
|
void dumpObject(const Value& value, const uint32_t indentLevel = 0); |
||||||
|
|
||||||
|
std::string m_output; |
||||||
|
|
||||||
|
uint32_t m_indent { 0 }; |
||||||
|
char m_indentCharacter { ' ' }; |
||||||
|
bool m_compact { true }; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,153 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cassert> // assert |
||||||
|
#include <cstddef> // nullptr_t |
||||||
|
#include <map> |
||||||
|
#include <string> |
||||||
|
#include <unordered_map> |
||||||
|
#include <utility> // forward |
||||||
|
|
||||||
|
#include "util/json/array.h" |
||||||
|
#include "util/json/object.h" |
||||||
|
#include "util/meta/odr.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
namespace Detail { |
||||||
|
|
||||||
|
struct jsonConstructor { |
||||||
|
template<typename Json> |
||||||
|
static void construct(Json& json, bool boolean) |
||||||
|
{ |
||||||
|
json.destroy(); |
||||||
|
json.m_type = Json::Type::Bool; |
||||||
|
json.m_value.boolean = boolean; |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json> |
||||||
|
static void construct(Json& json, int number) |
||||||
|
{ |
||||||
|
json.destroy(); |
||||||
|
json.m_type = Json::Type::Number; |
||||||
|
json.m_value.number = (double)number; |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json> |
||||||
|
static void construct(Json& json, double number) |
||||||
|
{ |
||||||
|
json.destroy(); |
||||||
|
json.m_type = Json::Type::Number; |
||||||
|
json.m_value.number = number; |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json> |
||||||
|
static void construct(Json& json, const char* string) |
||||||
|
{ |
||||||
|
json.destroy(); |
||||||
|
json.m_type = Json::Type::String; |
||||||
|
json.m_value.string = new std::string(string); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json> |
||||||
|
static void construct(Json& json, const std::string& string) |
||||||
|
{ |
||||||
|
json.destroy(); |
||||||
|
json.m_type = Json::Type::String; |
||||||
|
json.m_value.string = new std::string(string); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json> |
||||||
|
static void construct(Json& json, const Array& array) |
||||||
|
{ |
||||||
|
json.destroy(); |
||||||
|
json.m_type = Json::Type::Array; |
||||||
|
json.m_value.array = new Array(array); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json, typename T> |
||||||
|
static void construct(Json& json, const std::vector<T>& array) |
||||||
|
{ |
||||||
|
json.destroy(); |
||||||
|
json.m_type = Json::Type::Array; |
||||||
|
json.m_value.array = new Array; |
||||||
|
json.m_value.array->reserve(array.size()); |
||||||
|
for (const T& value : array) { |
||||||
|
json.m_value.array->emplace_back(value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json> |
||||||
|
static void construct(Json& json, const Object& object) |
||||||
|
{ |
||||||
|
json.destroy(); |
||||||
|
json.m_type = Json::Type::Object; |
||||||
|
json.m_value.object = new Object(object); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json, typename T> |
||||||
|
static void construct(Json& json, const std::map<std::string, T>& object) |
||||||
|
{ |
||||||
|
json.destroy(); |
||||||
|
json.m_type = Json::Type::Object; |
||||||
|
json.m_value.object = new Object; |
||||||
|
for (const auto& [name, value] : object) { |
||||||
|
json.m_value.object->emplace(name, value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Json, typename T> |
||||||
|
static void construct(Json& json, const std::unordered_map<std::string, T>& object) |
||||||
|
{ |
||||||
|
json.destroy(); |
||||||
|
json.m_type = Json::Type::Object; |
||||||
|
json.m_value.object = new Object; |
||||||
|
for (const auto& [name, value] : object) { |
||||||
|
json.m_value.object->emplace(name, value); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
template<typename Json, typename T> |
||||||
|
void toJson(Json& json, const T& value) |
||||||
|
{ |
||||||
|
jsonConstructor::construct(json, value); |
||||||
|
} |
||||||
|
|
||||||
|
struct toJsonFunction { |
||||||
|
template<typename Json, typename T> |
||||||
|
auto operator()(Json& json, T&& value) const |
||||||
|
{ |
||||||
|
return toJson(json, std::forward<T>(value)); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Detail
|
||||||
|
|
||||||
|
// Anonymous namespace prevents multiple definition of the reference
|
||||||
|
namespace { |
||||||
|
// Function object
|
||||||
|
constexpr const auto& toJson = Util::Detail::staticConst<Detail::toJsonFunction>; // NOLINT(misc-definitions-in-headers,clang-diagnostic-unused-variable)
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace Util::JSON
|
||||||
|
|
||||||
|
// Customization Points
|
||||||
|
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
|
||||||
|
|
||||||
|
// Json::toJson is a function object, the type of which is
|
||||||
|
// Json::Detail::toJsonFunction. In the Json::Detail namespace are the toJson
|
||||||
|
// free functions. The function call operator of toJsonFunction makes an
|
||||||
|
// unqualified call to toJson which, since it shares the Detail namespace with
|
||||||
|
// the toJson free functions, will consider those in addition to any overloads
|
||||||
|
// that are found by argument-dependent lookup.
|
||||||
|
|
||||||
|
// Variable templates are linked externally, therefor every translation unit
|
||||||
|
// will see the same address for Detail::staticConst<Detail::toJsonFunction>.
|
||||||
|
// Since Json::toJson is a reference to the variable template, it too will have
|
||||||
|
// the same address in all translation units.
|
@ -0,0 +1,340 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <algorithm> // all_of |
||||||
|
#include <cstdint> // uint32_t |
||||||
|
#include <fstream> // >> |
||||||
|
#include <iostream> // istream, ostream |
||||||
|
#include <string> |
||||||
|
#include <utility> // move, swap |
||||||
|
|
||||||
|
#include "util/format/builder.h" |
||||||
|
#include "util/json/array.h" |
||||||
|
#include "util/json/job.h" |
||||||
|
#include "util/json/object.h" |
||||||
|
#include "util/json/serializer.h" |
||||||
|
#include "util/json/value.h" |
||||||
|
#include "util/meta/assert.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
Value::Value(std::nullptr_t) |
||||||
|
: Value(Type::Null) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
Value::Value(Type type) |
||||||
|
: m_type(type) |
||||||
|
{ |
||||||
|
switch (m_type) { |
||||||
|
case Type::Bool: |
||||||
|
m_value.boolean = false; |
||||||
|
break; |
||||||
|
case Type::Number: |
||||||
|
m_value.number = 0.0; |
||||||
|
break; |
||||||
|
case Type::String: |
||||||
|
m_value.string = new std::string; |
||||||
|
break; |
||||||
|
case Type::Array: |
||||||
|
m_value.array = new Array; |
||||||
|
break; |
||||||
|
case Type::Object: |
||||||
|
m_value.object = new Object; |
||||||
|
break; |
||||||
|
case Type::Null: |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Value::Value(const std::initializer_list<Value>& values) |
||||||
|
{ |
||||||
|
bool isObject = std::all_of(values.begin(), values.end(), [](const Value& value) { |
||||||
|
return value.type() == Type::Array |
||||||
|
&& value.size() == 2 |
||||||
|
&& value[0].m_type == Type::String; |
||||||
|
}); |
||||||
|
|
||||||
|
if (!isObject) { |
||||||
|
m_type = Type::Array; |
||||||
|
m_value.array = new Array(values); |
||||||
|
} |
||||||
|
else { |
||||||
|
m_type = Type::Object; |
||||||
|
m_value.object = new Object; |
||||||
|
|
||||||
|
for (auto& value : values) { |
||||||
|
m_value.object->emplace(std::move(*value[0].m_value.string), |
||||||
|
std::move(value[1])); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Copy constructor
|
||||||
|
Value::Value(const Value& other) |
||||||
|
: m_type(other.m_type) |
||||||
|
{ |
||||||
|
switch (m_type) { |
||||||
|
case Type::Bool: |
||||||
|
m_value.boolean = other.m_value.boolean; |
||||||
|
break; |
||||||
|
case Type::Number: |
||||||
|
m_value.number = other.m_value.number; |
||||||
|
break; |
||||||
|
case Type::String: |
||||||
|
m_value.string = new std::string(*other.m_value.string); |
||||||
|
break; |
||||||
|
case Type::Array: |
||||||
|
m_value.array = new Array(*other.m_value.array); |
||||||
|
break; |
||||||
|
case Type::Object: |
||||||
|
m_value.object = new Object(*other.m_value.object); |
||||||
|
break; |
||||||
|
case Type::Null: |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Move constructor
|
||||||
|
Value::Value(Value&& other) noexcept |
||||||
|
: Value(Type::Null) // Initialize via default construction
|
||||||
|
{ |
||||||
|
// Allow std::swap as a fallback on ADL failure
|
||||||
|
using std::swap; |
||||||
|
// Unqualified call to swap, allow ADL to operate and find best match
|
||||||
|
swap(*this, other); |
||||||
|
} |
||||||
|
|
||||||
|
// Copy assignment
|
||||||
|
// Move assignment
|
||||||
|
Value& Value::operator=(Value other) |
||||||
|
{ |
||||||
|
// Allow std::swap as a fallback on ADL failure
|
||||||
|
using std::swap; |
||||||
|
// Unqualified call to swap, allow ADL to operate and find best match
|
||||||
|
swap(*this, other); |
||||||
|
|
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
void swap(Value& left, Value& right) noexcept |
||||||
|
{ |
||||||
|
std::swap(left.m_type, right.m_type); |
||||||
|
std::swap(left.m_value, right.m_value); |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
void Value::clear() |
||||||
|
{ |
||||||
|
switch (m_type) { |
||||||
|
case Type::Bool: |
||||||
|
m_value.boolean = false; |
||||||
|
break; |
||||||
|
case Type::Number: |
||||||
|
m_value.number = 0.0; |
||||||
|
break; |
||||||
|
case Type::String: |
||||||
|
m_value.string->clear(); |
||||||
|
break; |
||||||
|
case Type::Array: |
||||||
|
m_value.array->clear(); |
||||||
|
break; |
||||||
|
case Type::Object: |
||||||
|
m_value.object->clear(); |
||||||
|
break; |
||||||
|
case Type::Null: |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Value Value::parse(std::string_view input) |
||||||
|
{ |
||||||
|
return Job(input).fire(); |
||||||
|
} |
||||||
|
|
||||||
|
Value Value::parse(std::ifstream& file) |
||||||
|
{ |
||||||
|
Value value; |
||||||
|
file >> value; |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
std::string Value::dump(const uint32_t indent, const char indentCharacter) const |
||||||
|
{ |
||||||
|
Serializer serializer(indent, indentCharacter); |
||||||
|
return serializer.dump(*this); |
||||||
|
} |
||||||
|
|
||||||
|
void Value::emplace_back(Value value) |
||||||
|
{ |
||||||
|
// Implicitly convert null to an array
|
||||||
|
if (m_type == Type::Null) { |
||||||
|
m_type = Type::Array; |
||||||
|
m_value.array = new Array; |
||||||
|
} |
||||||
|
|
||||||
|
VERIFY(m_type == Type::Array); |
||||||
|
m_value.array->emplace_back(value); |
||||||
|
} |
||||||
|
|
||||||
|
void Value::emplace(const std::string& key, Value value) |
||||||
|
{ |
||||||
|
// Implicitly convert null to an object
|
||||||
|
if (m_type == Type::Null) { |
||||||
|
m_type = Type::Object; |
||||||
|
m_value.object = new Object; |
||||||
|
} |
||||||
|
|
||||||
|
VERIFY(m_type == Type::Object); |
||||||
|
m_value.object->emplace(key, value); |
||||||
|
} |
||||||
|
|
||||||
|
bool Value::exists(size_t index) const |
||||||
|
{ |
||||||
|
return index < size(); |
||||||
|
} |
||||||
|
|
||||||
|
bool Value::exists(const std::string& key) const |
||||||
|
{ |
||||||
|
VERIFY(m_type == Type::Object); |
||||||
|
return m_value.object->members().find(key) != m_value.object->members().end(); |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
Value& Value::operator[](size_t index) |
||||||
|
{ |
||||||
|
// Implicitly convert null to an array
|
||||||
|
if (m_type == Type::Null) { |
||||||
|
m_type = Type::Array; |
||||||
|
m_value.array = new Array; |
||||||
|
} |
||||||
|
|
||||||
|
VERIFY(m_type == Type::Array); |
||||||
|
return (*m_value.array)[index]; |
||||||
|
} |
||||||
|
|
||||||
|
Value& Value::operator[](const std::string& key) |
||||||
|
{ |
||||||
|
// Implicitly convert null to an object
|
||||||
|
if (m_type == Type::Null) { |
||||||
|
m_type = Type::Object; |
||||||
|
m_value.object = new Object; |
||||||
|
} |
||||||
|
|
||||||
|
VERIFY(m_type == Type::Object); |
||||||
|
return (*m_value.object)[key]; |
||||||
|
} |
||||||
|
|
||||||
|
const Value& Value::operator[](size_t index) const |
||||||
|
{ |
||||||
|
VERIFY(m_type == Type::Array); |
||||||
|
return (*m_value.array)[index]; |
||||||
|
} |
||||||
|
|
||||||
|
const Value& Value::operator[](const std::string& key) const |
||||||
|
{ |
||||||
|
VERIFY(m_type == Type::Object); |
||||||
|
return (*m_value.object)[key]; |
||||||
|
} |
||||||
|
|
||||||
|
Value& Value::at(size_t index) |
||||||
|
{ |
||||||
|
VERIFY(m_type == Type::Array); |
||||||
|
return m_value.array->at(index); |
||||||
|
} |
||||||
|
|
||||||
|
Value& Value::at(const std::string& key) |
||||||
|
{ |
||||||
|
VERIFY(m_type == Type::Object); |
||||||
|
return m_value.object->at(key); |
||||||
|
} |
||||||
|
|
||||||
|
const Value& Value::at(size_t index) const |
||||||
|
{ |
||||||
|
VERIFY(m_type == Type::Array); |
||||||
|
return m_value.array->at(index); |
||||||
|
} |
||||||
|
|
||||||
|
const Value& Value::at(const std::string& key) const |
||||||
|
{ |
||||||
|
VERIFY(m_type == Type::Object); |
||||||
|
return m_value.object->at(key); |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
size_t Value::size() const |
||||||
|
{ |
||||||
|
switch (m_type) { |
||||||
|
case Type::Null: |
||||||
|
return 0; |
||||||
|
case Type::Array: |
||||||
|
return m_value.array->size(); |
||||||
|
case Type::Object: |
||||||
|
return m_value.object->size(); |
||||||
|
case Type::Bool: |
||||||
|
case Type::Number: |
||||||
|
case Type::String: |
||||||
|
default: |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
void Value::destroy() |
||||||
|
{ |
||||||
|
switch (m_type) { |
||||||
|
case Type::String: |
||||||
|
delete m_value.string; |
||||||
|
break; |
||||||
|
case Type::Array: |
||||||
|
delete m_value.array; |
||||||
|
break; |
||||||
|
case Type::Object: |
||||||
|
delete m_value.object; |
||||||
|
break; |
||||||
|
case Type::Null: |
||||||
|
case Type::Bool: |
||||||
|
case Type::Number: |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
|
||||||
|
std::istream& operator>>(std::istream& input, Value& value) |
||||||
|
{ |
||||||
|
std::string inputString; |
||||||
|
|
||||||
|
char buffer[4096]; |
||||||
|
while (input.read(buffer, sizeof(buffer))) { |
||||||
|
inputString.append(buffer, sizeof(buffer)); |
||||||
|
} |
||||||
|
inputString.append(buffer, input.gcount()); |
||||||
|
|
||||||
|
value = Job(inputString).fire(); |
||||||
|
|
||||||
|
return input; |
||||||
|
} |
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& output, const Value& value) |
||||||
|
{ |
||||||
|
return output << value.dump(4); |
||||||
|
} |
||||||
|
|
||||||
|
void format(Util::Format::Builder& builder, const Value& value) |
||||||
|
{ |
||||||
|
builder.putString(value.dump(4)); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
@ -0,0 +1,165 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> // nullptr_t, size_t |
||||||
|
#include <cstdint> // uint8_t, uint32_t |
||||||
|
#include <initializer_list> |
||||||
|
#include <iostream> // istream, ostream |
||||||
|
#include <string> |
||||||
|
#include <utility> // forward |
||||||
|
|
||||||
|
#include "util/format/builder.h" |
||||||
|
#include "util/json/fromjson.h" |
||||||
|
#include "util/json/tojson.h" |
||||||
|
|
||||||
|
namespace Util::JSON { |
||||||
|
|
||||||
|
class Array; |
||||||
|
class Object; |
||||||
|
|
||||||
|
class Value { |
||||||
|
private: |
||||||
|
friend Detail::jsonConstructor; |
||||||
|
friend class Parser; |
||||||
|
friend class Serializer; |
||||||
|
|
||||||
|
public: |
||||||
|
enum class Type : uint8_t { |
||||||
|
Null, // null (case sensitive!)
|
||||||
|
Bool, // true/false (case sensitive!)
|
||||||
|
Number, // 123
|
||||||
|
String, // ""
|
||||||
|
Array, // []
|
||||||
|
Object, // {}
|
||||||
|
}; |
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
Value(std::nullptr_t = nullptr); |
||||||
|
Value(Type type); |
||||||
|
Value(const std::initializer_list<Value>& values); |
||||||
|
template<typename T> |
||||||
|
Value(T value) |
||||||
|
{ |
||||||
|
toJson(*this, std::forward<T>(value)); |
||||||
|
} |
||||||
|
|
||||||
|
// Rule of Five:
|
||||||
|
// Copy constructor
|
||||||
|
Value(const Value& other); |
||||||
|
// Move constructor
|
||||||
|
Value(Value&& other) noexcept; |
||||||
|
// Copy assignment
|
||||||
|
// Move assignment
|
||||||
|
Value& operator=(Value other); |
||||||
|
// Destructor
|
||||||
|
virtual ~Value() { destroy(); } |
||||||
|
|
||||||
|
friend void swap(Value& left, Value& right) noexcept; |
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
static Value parse(std::string_view input); |
||||||
|
static Value parse(std::ifstream& file); |
||||||
|
std::string dump(const uint32_t indent = 0, const char indentCharacter = ' ') const; |
||||||
|
|
||||||
|
void clear(); |
||||||
|
|
||||||
|
void emplace_back(Value value); |
||||||
|
void emplace(const std::string& key, Value value); |
||||||
|
|
||||||
|
bool exists(size_t index) const; |
||||||
|
bool exists(const std::string& key) const; |
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
// Array index operator
|
||||||
|
Value& operator[](size_t index); |
||||||
|
Value& operator[](const std::string& key); |
||||||
|
const Value& operator[](size_t index) const; |
||||||
|
const Value& operator[](const std::string& key) const; |
||||||
|
|
||||||
|
Value& at(size_t index); |
||||||
|
Value& at(const std::string& key); |
||||||
|
const Value& at(size_t index) const; |
||||||
|
const Value& at(const std::string& key) const; |
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
template<typename T> |
||||||
|
T get() const |
||||||
|
{ |
||||||
|
T type; |
||||||
|
fromJson(*this, type); |
||||||
|
return type; |
||||||
|
} |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
void getTo(T& type) const |
||||||
|
{ |
||||||
|
fromJson(*this, type); |
||||||
|
} |
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
Type type() const { return m_type; } |
||||||
|
size_t size() const; |
||||||
|
|
||||||
|
bool asBool() const { return m_value.boolean; } |
||||||
|
double asDouble() const { return m_value.number; } |
||||||
|
const std::string& asString() const { return *m_value.string; } |
||||||
|
const Array& asArray() const { return *m_value.array; } |
||||||
|
const Object& asObject() const { return *m_value.object; } |
||||||
|
|
||||||
|
private: |
||||||
|
void destroy(); |
||||||
|
|
||||||
|
Type m_type { Type::Null }; |
||||||
|
|
||||||
|
union { |
||||||
|
bool boolean; |
||||||
|
double number; |
||||||
|
std::string* string; |
||||||
|
Array* array; |
||||||
|
Object* object; |
||||||
|
} m_value {}; |
||||||
|
}; |
||||||
|
|
||||||
|
std::istream& operator>>(std::istream& input, Value& value); |
||||||
|
std::ostream& operator<<(std::ostream& output, const Value& value); |
||||||
|
|
||||||
|
void format(Util::Format::Builder& builder, const Value& value); |
||||||
|
|
||||||
|
} // namespace Util::JSON
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-defined string literal |
||||||
|
* |
||||||
|
* Example usage: auto json = "[ 3.14, true, null ]"_json; |
||||||
|
*/ |
||||||
|
// inline Util::JSON::Value operator"" _json(const char* input, size_t length)
|
||||||
|
// {
|
||||||
|
// return Util::JSON::Value::parse(std::string(input, length));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - find
|
||||||
|
// - custom iterator
|
||||||
|
// - begin
|
||||||
|
// - end
|
||||||
|
// v parse function that accepts ifstream > redirect to operator>>
|
||||||
|
// - dump(-1), 0 inserts only newlines, -1 is full compact(?)
|
||||||
|
// v add timer pause and unpause functions
|
||||||
|
// v std::prev -> std::next in serializer
|
||||||
|
// v serializer: output as member, does this speed it up?
|
||||||
|
// - add Badge pattern, ex: Serializer constructor isnt needed publically
|
||||||
|
// v Rename namespace Json -> Util::Json, create easy include json.h
|
||||||
|
|
||||||
|
// - look into this for correctness
|
||||||
|
// https://www.json.org/json-en.html
|
@ -0,0 +1,96 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdint> // uint32_t |
||||||
|
#include <cstring> // strlen |
||||||
|
#include <fstream> // ifstream |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/format/format.h" |
||||||
|
#include "util/meta/compiler.h" |
||||||
|
|
||||||
|
#define CRASH() asm volatile("int $0x03"); |
||||||
|
|
||||||
|
#ifndef NDEBUG |
||||||
|
#define VERIFY(expr, ...) (static_cast<bool>(expr) ? (void)0 : Util::__assertion_failed(#expr, __FILE__, __LINE__, FUNCTION_MACRO __VA_OPT__(, ) __VA_ARGS__)) |
||||||
|
#define VERIFY_NOT_REACHED() VERIFY(false) |
||||||
|
#else |
||||||
|
#define VERIFY(expr, ...) (static_cast<bool>(expr) ? (void)0 : CRASH() |
||||||
|
#define VERIFY_NOT_REACHED() CRASH() |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifndef NDEBUG |
||||||
|
namespace Util { |
||||||
|
|
||||||
|
template<typename... Parameters> |
||||||
|
inline void __assertion_failed(const char* assertion, const char* file, uint32_t line, const char* function, const Parameters&... parameters) |
||||||
|
{ |
||||||
|
// Get the line that caused the error
|
||||||
|
std::ifstream source(file); |
||||||
|
std::string content; |
||||||
|
if (source.is_open()) { |
||||||
|
for (uint32_t i = 0; std::getline(source, content); ++i) { |
||||||
|
if (i == line - 1) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// Replace tab indentation with spaces
|
||||||
|
size_t tabs = content.find_first_not_of('\t'); |
||||||
|
if (tabs > 0 && tabs < content.size()) { |
||||||
|
content = std::string(tabs * 4, ' ') + content.substr(tabs); |
||||||
|
} |
||||||
|
|
||||||
|
// Find the assertion in the line
|
||||||
|
size_t column = content.find(assertion); |
||||||
|
size_t assertionLength = strlen(assertion); |
||||||
|
if (column == std::string::npos) { |
||||||
|
column = content.find_first_not_of(' '); |
||||||
|
assertionLength = content.length() - column; |
||||||
|
} |
||||||
|
|
||||||
|
// Error message
|
||||||
|
fprintf(stderr, |
||||||
|
"\033[;1m%s:%u:%zu " |
||||||
|
"\033[31;1merror: " |
||||||
|
"\033[0massertion failed", |
||||||
|
file, line, column + 1); |
||||||
|
if constexpr (sizeof...(Parameters) > 0) { |
||||||
|
fprintf(stderr, ": "); |
||||||
|
// Cant use the formatting library to print asserts caused by the formatting library
|
||||||
|
std::string_view functionString = function; |
||||||
|
if (functionString.find("Util::Format::") != std::string_view::npos |
||||||
|
&& functionString.find("Util::GenericLexer::") != std::string_view::npos) { |
||||||
|
std::string message; |
||||||
|
formatTo(message, parameters...); |
||||||
|
fprintf(stderr, "%s", message.c_str()); |
||||||
|
} |
||||||
|
else { |
||||||
|
fprintf(stderr, parameters...); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Code line
|
||||||
|
fprintf(stderr, "\n %u | %s\033[31;1m%s\033[0m%s\n", line, |
||||||
|
content.substr(0, column).c_str(), // Whitespace at front
|
||||||
|
content.substr(column, assertionLength).c_str(), // Error portion
|
||||||
|
content.substr(column + assertionLength).c_str()); // Rest of the line
|
||||||
|
|
||||||
|
// Arrow pointer
|
||||||
|
fprintf(stderr, " %s | %s\033[31;1m^%s\033[0m\n", |
||||||
|
std::string(std::to_string(line).length(), ' ').c_str(), // Line number spacing
|
||||||
|
std::string(column, ' ').c_str(), // Content spacing
|
||||||
|
std::string(assertionLength - 1, '~').c_str()); // Arrow pointer
|
||||||
|
|
||||||
|
CRASH(); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util
|
||||||
|
#endif |
@ -0,0 +1,27 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
// Compiler
|
||||||
|
#if defined(__clang__) |
||||||
|
#define GCC |
||||||
|
#elif defined(__GNUG__) || (defined(__GNUC__) && defined(__cplusplus)) |
||||||
|
#define GCC |
||||||
|
#elif defined(__INTEL_COMPILER) // Supports some GCC extensions
|
||||||
|
#define GCC |
||||||
|
#elif defined(_MSC_VER) |
||||||
|
#define MSVC |
||||||
|
#endif |
||||||
|
|
||||||
|
// Non-standard function macro
|
||||||
|
#ifdef GCC |
||||||
|
#define FUNCTION_MACRO __PRETTY_FUNCTION__ // GCC extension
|
||||||
|
#elif MSVC |
||||||
|
#define FUNCTION_MACRO __FUNCSIG__ |
||||||
|
#else |
||||||
|
#define FUNCTION_MACRO __func__ // C99
|
||||||
|
#endif |
@ -0,0 +1,22 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <type_traits> // is_integral_v, floating_point_v |
||||||
|
|
||||||
|
namespace Util::Concepts { |
||||||
|
|
||||||
|
template<class T> |
||||||
|
concept Integral = std::is_integral_v<T>; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
concept FloatingPoint = std::is_floating_point_v<T>; |
||||||
|
|
||||||
|
} // namespace Util::Concepts
|
||||||
|
|
||||||
|
using Util::Concepts::FloatingPoint; |
||||||
|
using Util::Concepts::Integral; |
@ -0,0 +1,20 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
namespace Detail { |
||||||
|
|
||||||
|
// Avoid ODR (One Definition Rule) violations,
|
||||||
|
// variable templates are required to have external linkage
|
||||||
|
template<typename T> |
||||||
|
constexpr T staticConst {}; |
||||||
|
|
||||||
|
} // namespace Detail
|
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,53 @@ |
|||||||
|
#include <cstdio> // pclose, perror, popen |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
#include "util/shell.h" |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
Shell::Shell() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
Shell::Shell(const std::string& output, int status) |
||||||
|
: m_output(output) |
||||||
|
, m_status(status) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
Shell Shell::operator()(const char* command) |
||||||
|
{ |
||||||
|
FILE* shell = popen(command, "r"); |
||||||
|
if (!shell) { |
||||||
|
perror("\033[31;1mError:\033[0m popen"); |
||||||
|
return { "", -1 }; |
||||||
|
} |
||||||
|
|
||||||
|
std::string output; |
||||||
|
|
||||||
|
constexpr int bufferSize = 4096; |
||||||
|
char buffer[bufferSize]; |
||||||
|
while (fgets(buffer, sizeof(buffer), shell)) { |
||||||
|
output.append(buffer); |
||||||
|
} |
||||||
|
|
||||||
|
int status = pclose(shell); |
||||||
|
if (status < 0) { |
||||||
|
perror("\033[31;1mError:\033[0m pclose"); |
||||||
|
} |
||||||
|
|
||||||
|
return { output, status }; |
||||||
|
} |
||||||
|
|
||||||
|
Shell Shell::operator()(std::string command) |
||||||
|
{ |
||||||
|
return operator()(command.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
Shell Shell::operator()(std::string_view command) |
||||||
|
{ |
||||||
|
return operator()(command.data()); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,33 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
class Shell { |
||||||
|
public: |
||||||
|
Shell(); |
||||||
|
virtual ~Shell() {} |
||||||
|
|
||||||
|
Shell operator()(const char* command); |
||||||
|
Shell operator()(std::string command); |
||||||
|
Shell operator()(std::string_view command); |
||||||
|
|
||||||
|
std::string output() const { return m_output; } |
||||||
|
int status() const { return m_status; } |
||||||
|
|
||||||
|
private: |
||||||
|
Shell(const std::string& output, int status); |
||||||
|
|
||||||
|
std::string m_output; |
||||||
|
int m_status { 0 }; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,53 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cassert> |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
class Singleton { |
||||||
|
public: |
||||||
|
static inline T& the() |
||||||
|
{ |
||||||
|
if (s_instance == nullptr) { |
||||||
|
s_instance = new T { s {} }; |
||||||
|
} |
||||||
|
|
||||||
|
return *s_instance; |
||||||
|
} |
||||||
|
|
||||||
|
static inline void destroy() |
||||||
|
{ |
||||||
|
if (s_instance) { |
||||||
|
delete s_instance; |
||||||
|
} |
||||||
|
|
||||||
|
s_instance = nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
// Remove copy constructor and copy assignment operator
|
||||||
|
Singleton(const Singleton&) = delete; |
||||||
|
Singleton& operator=(const Singleton&) = delete; |
||||||
|
Singleton(Singleton&&) = delete; |
||||||
|
Singleton& operator=(Singleton&&) = delete; |
||||||
|
|
||||||
|
protected: |
||||||
|
Singleton() {} |
||||||
|
|
||||||
|
// Constructor token
|
||||||
|
struct s {}; |
||||||
|
|
||||||
|
private: |
||||||
|
static T* s_instance; |
||||||
|
}; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
T* Singleton<T>::s_instance = nullptr; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,328 @@ |
|||||||
|
#include <cerrno> // errno, EAGAIN, EINTR |
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdio> // perror, ssize_t |
||||||
|
#include <cstdlib> // exit, WEXITSTATUS |
||||||
|
#include <cstring> // strcpy, strtok |
||||||
|
#include <functional> // function |
||||||
|
#include <sstream> // istringstream |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
#include <sys/wait.h> // waitpid |
||||||
|
#include <unistd.h> // close, dup2, execvp, fork, pipe, read |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "util/system.h" |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
System::System() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
System::System(const std::vector<std::string>& arguments) |
||||||
|
: m_arguments(arguments) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
System System::operator()() |
||||||
|
{ |
||||||
|
return exec(); |
||||||
|
} |
||||||
|
|
||||||
|
System System::operator()(const char* command) |
||||||
|
{ |
||||||
|
return operator()(std::string { command }); |
||||||
|
} |
||||||
|
|
||||||
|
System System::operator()(std::string command) |
||||||
|
{ |
||||||
|
std::vector<std::string> arguments; |
||||||
|
|
||||||
|
size_t index = 0; |
||||||
|
while (index != std::string::npos) { |
||||||
|
index = command.find_first_of(" "); |
||||||
|
arguments.push_back(command.substr(0, index)); |
||||||
|
command = command.substr(index + 1); |
||||||
|
} |
||||||
|
|
||||||
|
return { arguments }; |
||||||
|
} |
||||||
|
|
||||||
|
System System::operator()(std::string_view command) |
||||||
|
{ |
||||||
|
return operator()(std::string { command }); |
||||||
|
} |
||||||
|
|
||||||
|
System System::operator()(const std::vector<const char*>& arguments) |
||||||
|
{ |
||||||
|
std::vector<std::string> stringArguments(arguments.size(), ""); |
||||||
|
for (size_t i = 0; i < arguments.size(); ++i) { |
||||||
|
stringArguments[i] = arguments[i]; |
||||||
|
} |
||||||
|
|
||||||
|
return { stringArguments }; |
||||||
|
} |
||||||
|
|
||||||
|
System System::operator()(const std::vector<std::string>& arguments) |
||||||
|
{ |
||||||
|
return { arguments }; |
||||||
|
} |
||||||
|
|
||||||
|
System System::operator()(const std::vector<std::string_view>& arguments) |
||||||
|
{ |
||||||
|
std::vector<std::string> stringArguments(arguments.size(), ""); |
||||||
|
for (size_t i = 0; i < arguments.size(); ++i) { |
||||||
|
stringArguments[i] = arguments[i]; |
||||||
|
} |
||||||
|
|
||||||
|
return { stringArguments }; |
||||||
|
} |
||||||
|
|
||||||
|
// Shell equivalent ;
|
||||||
|
System System::operator+(System rhs) |
||||||
|
{ |
||||||
|
auto lhs = *this; |
||||||
|
|
||||||
|
lhs.exec(); |
||||||
|
rhs.m_output.append(lhs.m_output); |
||||||
|
rhs.m_error.append(lhs.m_error); |
||||||
|
rhs.exec(); |
||||||
|
|
||||||
|
return rhs; |
||||||
|
} |
||||||
|
|
||||||
|
System System::operator|(System rhs) |
||||||
|
{ |
||||||
|
auto lhs = *this; |
||||||
|
|
||||||
|
lhs.exec(); |
||||||
|
rhs.exec(lhs.m_output); |
||||||
|
|
||||||
|
return rhs; |
||||||
|
} |
||||||
|
|
||||||
|
System System::operator&&(System rhs) |
||||||
|
{ |
||||||
|
auto lhs = *this; |
||||||
|
|
||||||
|
lhs.exec(); |
||||||
|
if (lhs.m_status > 0) { |
||||||
|
return lhs; |
||||||
|
} |
||||||
|
|
||||||
|
rhs.m_output.append(lhs.m_output); |
||||||
|
rhs.m_error.append(lhs.m_error); |
||||||
|
rhs.exec(); |
||||||
|
|
||||||
|
return rhs; |
||||||
|
} |
||||||
|
|
||||||
|
System System::operator||(System rhs) |
||||||
|
{ |
||||||
|
auto lhs = *this; |
||||||
|
|
||||||
|
lhs.exec(); |
||||||
|
if (lhs.m_status == 0) { |
||||||
|
return lhs; |
||||||
|
} |
||||||
|
|
||||||
|
rhs.m_output.append(lhs.m_output); |
||||||
|
rhs.m_error.append(lhs.m_error); |
||||||
|
rhs.exec(); |
||||||
|
|
||||||
|
return rhs; |
||||||
|
} |
||||||
|
|
||||||
|
// cut -f -d
|
||||||
|
System& System::cut(uint32_t field, char delimiter) |
||||||
|
{ |
||||||
|
exec(); |
||||||
|
|
||||||
|
return apply([&field, &delimiter](std::vector<std::string>& lines) { |
||||||
|
for (auto& line : lines) { |
||||||
|
size_t count = 1; |
||||||
|
size_t index = 0; |
||||||
|
while (index != std::string::npos) { |
||||||
|
if (count == field) { |
||||||
|
line = line.substr(0, line.find_first_of(delimiter)); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
index = line.find_first_of(delimiter); |
||||||
|
line = line.substr(index + 1); |
||||||
|
count++; |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
System& System::sort(bool unique) |
||||||
|
{ |
||||||
|
exec(); |
||||||
|
|
||||||
|
return apply([&unique](std::vector<std::string>& lines) { |
||||||
|
std::sort(lines.begin(), lines.end()); |
||||||
|
|
||||||
|
if (unique) { |
||||||
|
auto last = std::unique(lines.begin(), lines.end()); |
||||||
|
lines.erase(last, lines.end()); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// tail -n
|
||||||
|
System& System::tail(int32_t number, bool starting) |
||||||
|
{ |
||||||
|
exec(); |
||||||
|
|
||||||
|
return apply([&number, &starting](std::vector<std::string>& lines) { |
||||||
|
number = abs(number); |
||||||
|
if (!starting) { |
||||||
|
lines.erase(lines.begin(), lines.end() - number); |
||||||
|
} |
||||||
|
else { |
||||||
|
lines.erase(lines.begin(), lines.begin() + number - 1); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
System& System::apply(LineCallback callback) |
||||||
|
{ |
||||||
|
exec(); |
||||||
|
|
||||||
|
std::vector<std::string> lines; |
||||||
|
|
||||||
|
auto stream = std::istringstream(m_output); |
||||||
|
std::string line; |
||||||
|
while (std::getline(stream, line)) { |
||||||
|
lines.push_back(line); |
||||||
|
} |
||||||
|
|
||||||
|
callback(lines); |
||||||
|
|
||||||
|
m_output.clear(); |
||||||
|
for (size_t i = 0; i < lines.size(); ++i) { |
||||||
|
m_output.append(lines.at(i) + '\n'); |
||||||
|
} |
||||||
|
|
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
void System::print(const std::vector<std::string>& arguments) |
||||||
|
{ |
||||||
|
if (!arguments.size()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
printf("----------\n"); |
||||||
|
printf("size: %zu\n", arguments.size()); |
||||||
|
printf("command: "); |
||||||
|
for (size_t i = 0; i < arguments.size(); ++i) { |
||||||
|
printf("%s ", arguments.at(i).c_str()); |
||||||
|
} |
||||||
|
printf("\n"); |
||||||
|
printf("----------\n"); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
System System::exec(std::string input) |
||||||
|
{ |
||||||
|
if (m_arguments.empty()) { |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
int stdinFd[2]; |
||||||
|
int stdoutFd[2]; |
||||||
|
int stderrFd[2]; |
||||||
|
if (pipe(stdinFd) < 0) { |
||||||
|
perror("\033[31;1mError:\033[0m pipe"); |
||||||
|
} |
||||||
|
if (pipe(stdoutFd) < 0) { |
||||||
|
perror("\033[31;1mError:\033[0m pipe"); |
||||||
|
} |
||||||
|
if (pipe(stderrFd) < 0) { |
||||||
|
perror("\033[31;1mError:\033[0m pipe"); |
||||||
|
} |
||||||
|
|
||||||
|
pid_t pid = fork(); |
||||||
|
switch (pid) { |
||||||
|
// Failed
|
||||||
|
case -1: |
||||||
|
perror("\033[31;1mError:\033[0m fork"); |
||||||
|
break; |
||||||
|
// Child
|
||||||
|
case 0: { |
||||||
|
close(stdinFd[WriteFileDescriptor]); |
||||||
|
dup2(stdinFd[ReadFileDescriptor], fileno(stdin)); |
||||||
|
close(stdinFd[ReadFileDescriptor]); |
||||||
|
|
||||||
|
close(stdoutFd[ReadFileDescriptor]); |
||||||
|
dup2(stdoutFd[WriteFileDescriptor], fileno(stdout)); |
||||||
|
close(stdoutFd[WriteFileDescriptor]); |
||||||
|
|
||||||
|
close(stderrFd[ReadFileDescriptor]); |
||||||
|
dup2(stderrFd[WriteFileDescriptor], fileno(stderr)); |
||||||
|
close(stderrFd[WriteFileDescriptor]); |
||||||
|
|
||||||
|
std::vector<char*> charArguments(m_arguments.size() + 1, 0); |
||||||
|
for (size_t i = 0; i < m_arguments.size(); ++i) { |
||||||
|
charArguments[i] = const_cast<char*>(m_arguments[i].c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
execvp(charArguments[0], &charArguments[0]); |
||||||
|
exit(0); |
||||||
|
} |
||||||
|
// Parent
|
||||||
|
default: |
||||||
|
m_arguments.clear(); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
close(stdinFd[ReadFileDescriptor]); |
||||||
|
if (!input.empty()) { |
||||||
|
write(stdinFd[WriteFileDescriptor], input.c_str(), input.size()); |
||||||
|
} |
||||||
|
close(stdinFd[WriteFileDescriptor]); |
||||||
|
|
||||||
|
readFromFileDescriptor(stdoutFd, m_output); |
||||||
|
readFromFileDescriptor(stderrFd, m_error); |
||||||
|
|
||||||
|
int result; |
||||||
|
do { |
||||||
|
result = waitpid(pid, &m_status, 0); |
||||||
|
} while (result == -1 && errno == EINTR); |
||||||
|
m_status = WEXITSTATUS(m_status); |
||||||
|
|
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
void System::readFromFileDescriptor(int fileDescriptor[2], std::string& output) |
||||||
|
{ |
||||||
|
close(fileDescriptor[WriteFileDescriptor]); |
||||||
|
|
||||||
|
constexpr int bufferSize = 4096; |
||||||
|
char buffer[bufferSize]; |
||||||
|
|
||||||
|
for (;;) { |
||||||
|
const ssize_t result = read(fileDescriptor[ReadFileDescriptor], buffer, bufferSize); |
||||||
|
if (result > 0) { |
||||||
|
output.append(buffer, result); |
||||||
|
} |
||||||
|
// EOF
|
||||||
|
if (result == 0) { |
||||||
|
break; |
||||||
|
} |
||||||
|
// Error
|
||||||
|
else if (result == -1) { |
||||||
|
if (errno != EAGAIN && errno != EINTR) { |
||||||
|
perror("\033[31;1mError:\033[0m read"); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
close(fileDescriptor[ReadFileDescriptor]); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,68 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstdint> // int32_t, uint32_t |
||||||
|
#include <functional> // function |
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
using LineCallback = std::function<void(std::vector<std::string>&)>; |
||||||
|
|
||||||
|
class System { |
||||||
|
public: |
||||||
|
System(); |
||||||
|
virtual ~System() {} |
||||||
|
|
||||||
|
enum FileDescriptor { |
||||||
|
ReadFileDescriptor, |
||||||
|
WriteFileDescriptor, |
||||||
|
}; |
||||||
|
|
||||||
|
System operator()(); |
||||||
|
System operator()(const char* command); |
||||||
|
System operator()(std::string command); |
||||||
|
System operator()(std::string_view command); |
||||||
|
System operator()(const std::vector<const char*>& arguments); |
||||||
|
System operator()(const std::vector<std::string>& arguments); |
||||||
|
System operator()(const std::vector<std::string_view>& arguments); |
||||||
|
|
||||||
|
// Operator order
|
||||||
|
// + -> | -> && -> ||
|
||||||
|
System operator+(System rhs); |
||||||
|
System operator|(System rhs); |
||||||
|
System operator&&(System rhs); |
||||||
|
System operator||(System rhs); |
||||||
|
|
||||||
|
System& cut(uint32_t field, char delimiter = '\t'); |
||||||
|
System& sort(bool unique = false); |
||||||
|
System& tail(int32_t number, bool starting = false); |
||||||
|
System& apply(LineCallback callback); |
||||||
|
|
||||||
|
void print(const std::vector<std::string>& arguments); |
||||||
|
|
||||||
|
const std::vector<std::string>& arguments() const { return m_arguments; } |
||||||
|
std::string output() const { return m_output; } |
||||||
|
std::string error() const { return m_error; } |
||||||
|
int status() const { return m_status; } |
||||||
|
|
||||||
|
private: |
||||||
|
System(const std::vector<std::string>& arguments); |
||||||
|
|
||||||
|
System exec(std::string input = ""); |
||||||
|
void readFromFileDescriptor(int fileDescriptor[2], std::string& output); |
||||||
|
|
||||||
|
std::vector<std::string> m_arguments; |
||||||
|
std::string m_output; |
||||||
|
std::string m_error; |
||||||
|
int m_status { 0 }; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,131 @@ |
|||||||
|
#include <chrono> // high_resolution_clock, seconds, milliseconds, microseconds, nanoseconds |
||||||
|
#include <cstdint> // uint64_t |
||||||
|
#include <cstdio> // printf |
||||||
|
|
||||||
|
#include "util/timer.h" |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
Timer::Timer() |
||||||
|
: m_running(true) |
||||||
|
, m_accumulated(TimePoint::min()) |
||||||
|
, m_start(now()) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
Timer::Timer(const TimePoint& timePoint) |
||||||
|
: m_running(true) |
||||||
|
, m_accumulated(TimePoint::min()) |
||||||
|
, m_start(timePoint) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
Timer Timer::operator-(const Timer& timer) |
||||||
|
{ |
||||||
|
return Timer(TimePoint { m_start - timer.start() }); |
||||||
|
} |
||||||
|
|
||||||
|
TimePoint Timer::now() |
||||||
|
{ |
||||||
|
return std::chrono::high_resolution_clock::now(); |
||||||
|
} |
||||||
|
|
||||||
|
void Timer::pause() |
||||||
|
{ |
||||||
|
if (!m_running) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
m_accumulated += now() - m_start; |
||||||
|
m_running = false; |
||||||
|
} |
||||||
|
|
||||||
|
void Timer::resume() |
||||||
|
{ |
||||||
|
if (m_running) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
m_running = true; |
||||||
|
m_start = now(); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename To, typename From> |
||||||
|
To Timer::to(From from) |
||||||
|
{ |
||||||
|
return std::chrono::duration_cast<To>(from); |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t Timer::toSeconds() |
||||||
|
{ |
||||||
|
return to<std::chrono::seconds>(m_start.time_since_epoch()).count(); |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t Timer::toMilliseconds() |
||||||
|
{ |
||||||
|
return to<std::chrono::milliseconds>(m_start.time_since_epoch()).count(); |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t Timer::toMicroseconds() |
||||||
|
{ |
||||||
|
return to<std::chrono::microseconds>(m_start.time_since_epoch()).count(); |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t Timer::toNanoseconds() |
||||||
|
{ |
||||||
|
return to<std::chrono::nanoseconds>(m_start.time_since_epoch()).count(); |
||||||
|
} |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
uint64_t Timer::elapsed() |
||||||
|
{ |
||||||
|
uint64_t elapsed = 0; |
||||||
|
|
||||||
|
if (m_running) { |
||||||
|
elapsed += std::chrono::duration_cast<T>(now() - m_start).count(); |
||||||
|
} |
||||||
|
|
||||||
|
elapsed += std::chrono::duration_cast<T>(m_accumulated - TimePoint::min()).count(); |
||||||
|
|
||||||
|
return elapsed; |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t Timer::elapsedSeconds() |
||||||
|
{ |
||||||
|
return elapsed<std::chrono::seconds>(); |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t Timer::elapsedMilliseconds() |
||||||
|
{ |
||||||
|
return elapsed<std::chrono::milliseconds>(); |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t Timer::elapsedMicroseconds() |
||||||
|
{ |
||||||
|
return elapsed<std::chrono::microseconds>(); |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t Timer::elapsedNanoseconds() |
||||||
|
{ |
||||||
|
return elapsed<std::chrono::nanoseconds>(); |
||||||
|
} |
||||||
|
|
||||||
|
void Timer::fancyPrint(uint64_t nanoseconds) |
||||||
|
{ |
||||||
|
if (nanoseconds > 999999999) { |
||||||
|
printf("%.3fs", nanoseconds / 1000000000.0); |
||||||
|
} |
||||||
|
else if (nanoseconds > 999999) { |
||||||
|
printf("%.0fms", nanoseconds / 1000000.0); |
||||||
|
} |
||||||
|
else if (nanoseconds > 999) { |
||||||
|
printf("%.0fμs", nanoseconds / 1000.0); |
||||||
|
} |
||||||
|
else { |
||||||
|
printf("%luns", nanoseconds); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,51 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <chrono> // high_resolution_clock |
||||||
|
#include <cstdint> // uint64_t |
||||||
|
|
||||||
|
namespace Util { |
||||||
|
|
||||||
|
using TimePoint = std::chrono::high_resolution_clock::time_point; |
||||||
|
|
||||||
|
class Timer { |
||||||
|
public: |
||||||
|
Timer(); |
||||||
|
Timer(const TimePoint& timePoint); |
||||||
|
|
||||||
|
Timer operator-(const Timer& timer); |
||||||
|
|
||||||
|
static TimePoint now(); |
||||||
|
void pause(); |
||||||
|
void resume(); |
||||||
|
|
||||||
|
template<typename To, typename From> |
||||||
|
To to(From from); |
||||||
|
uint64_t toSeconds(); |
||||||
|
uint64_t toMilliseconds(); |
||||||
|
uint64_t toMicroseconds(); |
||||||
|
uint64_t toNanoseconds(); |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
uint64_t elapsed(); |
||||||
|
uint64_t elapsedSeconds(); |
||||||
|
uint64_t elapsedMilliseconds(); |
||||||
|
uint64_t elapsedMicroseconds(); |
||||||
|
uint64_t elapsedNanoseconds(); |
||||||
|
|
||||||
|
static void fancyPrint(uint64_t nanoseconds); |
||||||
|
|
||||||
|
const TimePoint& start() const { return m_start; } |
||||||
|
|
||||||
|
private: |
||||||
|
bool m_running { true }; |
||||||
|
TimePoint m_accumulated; |
||||||
|
TimePoint m_start; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Util
|
@ -0,0 +1,79 @@ |
|||||||
|
#ifndef TEST_H |
||||||
|
#define TEST_H |
||||||
|
|
||||||
|
#include <cstdio> // fprintf |
||||||
|
#include <iostream> // cerr |
||||||
|
|
||||||
|
#define GET_2TH_ARG(arg1, arg2, ...) arg2 |
||||||
|
#define GET_3TH_ARG(arg1, arg2, arg3, ...) arg3 |
||||||
|
#define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4 |
||||||
|
#define MACRO_CHOOSER_1(macro, ...) \ |
||||||
|
GET_2TH_ARG(__VA_ARGS__, macro##_1, ) |
||||||
|
#define MACRO_CHOOSER_2(macro, ...) \ |
||||||
|
GET_3TH_ARG(__VA_ARGS__, macro##_2, macro##_1, ) |
||||||
|
#define MACRO_CHOOSER_3(macro, ...) \ |
||||||
|
GET_4TH_ARG(__VA_ARGS__, macro##_3, macro##_2, macro##_1, ) |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
#define EXPECT_IMPL(x, result) \ |
||||||
|
if (!(x)) { \
|
||||||
|
fprintf(stderr, " \033[31;1mFAIL:\033[0m %s:%d: EXPECT(%s) failed\n", \
|
||||||
|
__FILE__, __LINE__, #x); \
|
||||||
|
Test::TestSuite::the().currentTestCaseFailed(); \
|
||||||
|
result; \
|
||||||
|
} |
||||||
|
|
||||||
|
#define EXPECT_1(x) \ |
||||||
|
EXPECT_IMPL(x, (void)0) |
||||||
|
|
||||||
|
#define EXPECT_2(x, result) \ |
||||||
|
EXPECT_IMPL(x, result) |
||||||
|
|
||||||
|
#define EXPECT(...) \ |
||||||
|
MACRO_CHOOSER_2(EXPECT, __VA_ARGS__) \
|
||||||
|
(__VA_ARGS__) |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
#define EXPECT_EQ_IMPL(a, b, result) \ |
||||||
|
if (a != b) { \
|
||||||
|
std::cerr << " \033[31;1mFAIL:\033[0m " << __FILE__ << ":" << __LINE__ \
|
||||||
|
<< ": EXPECT_EQ(" << #a << ", " << #b ") failed with" \
|
||||||
|
<< " lhs='" << a << "' and rhs='" << b << "'" << std::endl; \
|
||||||
|
Test::TestSuite::the().currentTestCaseFailed(); \
|
||||||
|
result; \
|
||||||
|
} |
||||||
|
|
||||||
|
#define EXPECT_EQ_2(a, b) \ |
||||||
|
EXPECT_EQ_IMPL(a, b, (void)0) |
||||||
|
|
||||||
|
#define EXPECT_EQ_3(a, b, result) \ |
||||||
|
EXPECT_EQ_IMPL(a, b, result) |
||||||
|
|
||||||
|
#define EXPECT_EQ(...) \ |
||||||
|
MACRO_CHOOSER_3(EXPECT_EQ, __VA_ARGS__) \
|
||||||
|
(__VA_ARGS__) |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
#define EXPECT_NE_IMPL(a, b, result) \ |
||||||
|
if (a == b) { \
|
||||||
|
std::cerr << " \033[31;1mFAIL:\033[0m " << __FILE__ << ":" << __LINE__ \
|
||||||
|
<< ": EXPECT_NE(" << #a << ", " << #b ") failed with" \
|
||||||
|
<< " lhs='" << a << "' and rhs='" << b << "'" << std::endl; \
|
||||||
|
Test::TestSuite::the().currentTestCaseFailed(); \
|
||||||
|
result; \
|
||||||
|
} |
||||||
|
|
||||||
|
#define EXPECT_NE_2(a, b) \ |
||||||
|
EXPECT_NE_IMPL(a, b, (void)0) |
||||||
|
|
||||||
|
#define EXPECT_NE_3(a, b, result) \ |
||||||
|
EXPECT_NE_IMPL(a, b, result) |
||||||
|
|
||||||
|
#define EXPECT_NE(...) \ |
||||||
|
MACRO_CHOOSER_3(EXPECT_NE, __VA_ARGS__) \
|
||||||
|
(__VA_ARGS__) |
||||||
|
|
||||||
|
#endif // TEST_H
|
@ -0,0 +1,8 @@ |
|||||||
|
#include "testsuite.h" |
||||||
|
|
||||||
|
int main(int, const char*[]) |
||||||
|
{ |
||||||
|
Test::TestSuite::the().run(); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
#ifndef TEST_CASE_H |
||||||
|
#define TEST_CASE_H |
||||||
|
|
||||||
|
#include <functional> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#define __TEST_CASE_FUNCTION(x) __test##x |
||||||
|
#define __TEST_CASE_STRUCT(x) __testStruct##x |
||||||
|
|
||||||
|
#define TEST_CASE(x) \ |
||||||
|
static void __TEST_CASE_FUNCTION(x)(); \
|
||||||
|
struct __TEST_CASE_STRUCT(x) { \
|
||||||
|
__TEST_CASE_STRUCT(x) \
|
||||||
|
() \
|
||||||
|
{ \
|
||||||
|
Test::TestSuite::the().addCase( \
|
||||||
|
{ #x, __TEST_CASE_FUNCTION(x) }); \
|
||||||
|
} \
|
||||||
|
}; \
|
||||||
|
static struct __TEST_CASE_STRUCT(x) __TEST_CASE_STRUCT(x); \
|
||||||
|
static void __TEST_CASE_FUNCTION(x)() |
||||||
|
|
||||||
|
namespace Test { |
||||||
|
|
||||||
|
using TestFunction = std::function<void()>; |
||||||
|
|
||||||
|
class TestCase { |
||||||
|
public: |
||||||
|
TestCase(const char* name, TestFunction&& function) |
||||||
|
: m_name(name) |
||||||
|
, m_function(function) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
const char* name() const { return m_name; } |
||||||
|
const TestFunction& function() const { return m_function; } |
||||||
|
|
||||||
|
private: |
||||||
|
const char* m_name { nullptr }; |
||||||
|
TestFunction m_function; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Test
|
||||||
|
|
||||||
|
#endif // TEST_CASE_H
|
@ -0,0 +1,76 @@ |
|||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdio> // fclose, fopen, printf, stdout |
||||||
|
#include <string> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "testsuite.h" |
||||||
|
#include "util/timer.h" |
||||||
|
|
||||||
|
namespace Test { |
||||||
|
|
||||||
|
TestSuite::TestSuite(s) |
||||||
|
{ |
||||||
|
m_outputStd = stdout; |
||||||
|
m_outputErr = stderr; |
||||||
|
m_outputNull = fopen("/dev/null", "w"); // Windows: nul
|
||||||
|
} |
||||||
|
|
||||||
|
TestSuite::~TestSuite() |
||||||
|
{ |
||||||
|
fclose(m_outputNull); |
||||||
|
} |
||||||
|
|
||||||
|
void TestSuite::run() |
||||||
|
{ |
||||||
|
const char* escapePass = "\033[42;30;1m"; |
||||||
|
const char* escapeFail = "\033[41;1m"; |
||||||
|
const char* escapeGreen = "\033[32m"; |
||||||
|
const char* escapeGrey = "\033[37m"; |
||||||
|
const char* escapeRed = "\033[31m"; |
||||||
|
const char* escapeReset = "\033[0m"; |
||||||
|
|
||||||
|
printf("\n"); |
||||||
|
printf("---- Running %zu Test Cases ----\n", m_cases.size()); |
||||||
|
|
||||||
|
Util::Timer totalTimer; |
||||||
|
|
||||||
|
size_t caseFailedCount = 0; |
||||||
|
for (size_t i = 0; i < m_cases.size(); ++i) { |
||||||
|
|
||||||
|
printf(" START %s (%zu/%zu)\n", m_cases.at(i).name(), i + 1, m_cases.size()); |
||||||
|
m_currentTestCasePassed = true; |
||||||
|
|
||||||
|
Util::Timer caseTimer; |
||||||
|
m_cases.at(i).function()(); |
||||||
|
double elapsed = caseTimer.elapsedNanoseconds(); |
||||||
|
|
||||||
|
std::string state; |
||||||
|
if (m_currentTestCasePassed) { |
||||||
|
state.append(escapePass); |
||||||
|
state.append(" PASS "); |
||||||
|
state.append(escapeReset); |
||||||
|
} |
||||||
|
else { |
||||||
|
caseFailedCount++; |
||||||
|
state.append(escapeFail); |
||||||
|
state.append(" FAIL "); |
||||||
|
state.append(escapeReset); |
||||||
|
} |
||||||
|
|
||||||
|
printf("%s %s %s(", state.c_str(), m_cases.at(i).name(), escapeGrey); |
||||||
|
Util::Timer::fancyPrint(elapsed); |
||||||
|
printf(")%s\n", escapeReset); |
||||||
|
} |
||||||
|
|
||||||
|
printf("\n"); |
||||||
|
printf("Tests: %s%zu failed%s, %s%zu passed%s, %zu total\n", |
||||||
|
escapeRed, caseFailedCount, escapeReset, |
||||||
|
escapeGreen, m_cases.size() - caseFailedCount, escapeReset, |
||||||
|
m_cases.size()); |
||||||
|
|
||||||
|
printf("Time: "); |
||||||
|
Util::Timer::fancyPrint(totalTimer.elapsedNanoseconds()); |
||||||
|
printf("\n"); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace Test
|
@ -0,0 +1,36 @@ |
|||||||
|
#ifndef TEST_SUITE_H |
||||||
|
#define TEST_SUITE_H |
||||||
|
|
||||||
|
#include <cstdio> // FILE |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "testcase.h" |
||||||
|
#include "util/singleton.h" |
||||||
|
|
||||||
|
namespace Test { |
||||||
|
|
||||||
|
class TestSuite final : public Util::Singleton<TestSuite> { |
||||||
|
public: |
||||||
|
TestSuite(s); |
||||||
|
virtual ~TestSuite(); |
||||||
|
|
||||||
|
void run(); |
||||||
|
void addCase(const TestCase& testCase) { m_cases.push_back(testCase); } |
||||||
|
void currentTestCaseFailed() { m_currentTestCasePassed = false; } |
||||||
|
|
||||||
|
FILE* outputStd() const { return m_outputStd; } |
||||||
|
FILE* outputErr() const { return m_outputErr; } |
||||||
|
FILE* outputNull() const { return m_outputNull; } |
||||||
|
|
||||||
|
private: |
||||||
|
bool m_currentTestCasePassed { true }; |
||||||
|
FILE* m_outputStd { nullptr }; |
||||||
|
FILE* m_outputErr { nullptr }; |
||||||
|
FILE* m_outputNull { nullptr }; |
||||||
|
|
||||||
|
std::vector<TestCase> m_cases; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace Test
|
||||||
|
|
||||||
|
#endif // TEST_SUITE_H
|
@ -0,0 +1,949 @@ |
|||||||
|
#include <functional> // function |
||||||
|
#include <string> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "macro.h" |
||||||
|
#include "testcase.h" |
||||||
|
#include "testsuite.h" |
||||||
|
#include "util/argparser.h" |
||||||
|
|
||||||
|
bool runParser(std::vector<const char*> arguments, std::function<void(Util::ArgParser&)> initializer = {}) |
||||||
|
{ |
||||||
|
stdout = Test::TestSuite::the().outputNull(); |
||||||
|
|
||||||
|
Util::ArgParser parser; |
||||||
|
if (initializer) { |
||||||
|
initializer(parser); |
||||||
|
} |
||||||
|
|
||||||
|
arguments.insert(arguments.begin(), "app"); |
||||||
|
auto result = parser.parse(arguments.size(), arguments.data()); |
||||||
|
|
||||||
|
stdout = Test::TestSuite::the().outputStd(); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(NoArguments) |
||||||
|
{ |
||||||
|
auto result = runParser({}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(NonExistentArguments) |
||||||
|
{ |
||||||
|
auto result = runParser({ "-n", "-e" }); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
|
||||||
|
result = runParser({ "--non", "--existent" }); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
|
||||||
|
result = runParser({ "-n", "-e", "--non", "--existent" }); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
|
||||||
|
result = runParser({ "no", "handling" }); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(RequiredStringArguments) |
||||||
|
{ |
||||||
|
// Single required string argument
|
||||||
|
std::string stringArg1 = ""; |
||||||
|
auto result = runParser({ "my-required-argument" }, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringArg1, "my-required-argument"); |
||||||
|
|
||||||
|
// Single required string argument, not given
|
||||||
|
stringArg1 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(stringArg1, ""); |
||||||
|
|
||||||
|
// Multiple required string arguments
|
||||||
|
stringArg1 = ""; |
||||||
|
std::string stringArg2 = ""; |
||||||
|
std::string stringArg3 = ""; |
||||||
|
result = runParser({ "my", "required", "argument" }, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringArg1, "my"); |
||||||
|
EXPECT_EQ(stringArg2, "required"); |
||||||
|
EXPECT_EQ(stringArg3, "argument"); |
||||||
|
|
||||||
|
// Multiple required string arguments, not given
|
||||||
|
stringArg1 = ""; |
||||||
|
stringArg2 = ""; |
||||||
|
stringArg3 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(stringArg1, ""); |
||||||
|
EXPECT_EQ(stringArg2, ""); |
||||||
|
EXPECT_EQ(stringArg3, ""); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(OptionalStringArguments) |
||||||
|
{ |
||||||
|
// Single optional string argument
|
||||||
|
std::string stringArg1 = ""; |
||||||
|
auto result = runParser({ "my-optional-argument" }, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringArg1, "my-optional-argument"); |
||||||
|
|
||||||
|
// Single optional string argument, not given
|
||||||
|
stringArg1 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringArg1, ""); |
||||||
|
|
||||||
|
// Multiple optional string arguments
|
||||||
|
stringArg1 = ""; |
||||||
|
std::string stringArg2 = ""; |
||||||
|
std::string stringArg3 = ""; |
||||||
|
result = runParser({ "my", "optional", "argument" }, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringArg1, "my"); |
||||||
|
EXPECT_EQ(stringArg2, "optional"); |
||||||
|
EXPECT_EQ(stringArg3, "argument"); |
||||||
|
|
||||||
|
// Multiple optional string arguments, not given
|
||||||
|
stringArg1 = ""; |
||||||
|
stringArg2 = ""; |
||||||
|
stringArg3 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringArg1, ""); |
||||||
|
EXPECT_EQ(stringArg2, ""); |
||||||
|
EXPECT_EQ(stringArg3, ""); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(NonRequiredStringArguments) |
||||||
|
{ |
||||||
|
// Single non-required string argument
|
||||||
|
std::string stringArg1 = ""; |
||||||
|
auto result = runParser({ "my-non-required-argument" }, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringArg1, "my-non-required-argument"); |
||||||
|
|
||||||
|
// Single non-required string argument, not given
|
||||||
|
stringArg1 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringArg1, ""); |
||||||
|
|
||||||
|
// Multiple non-required string arguments
|
||||||
|
stringArg1 = ""; |
||||||
|
std::string stringArg2 = ""; |
||||||
|
std::string stringArg3 = ""; |
||||||
|
result = runParser({ "my", "non-required", "argument" }, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringArg1, "my"); |
||||||
|
EXPECT_EQ(stringArg2, "non-required"); |
||||||
|
EXPECT_EQ(stringArg3, "argument"); |
||||||
|
|
||||||
|
// Multiple non-required string arguments, not given
|
||||||
|
stringArg1 = ""; |
||||||
|
stringArg2 = ""; |
||||||
|
stringArg3 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringArg1, ""); |
||||||
|
EXPECT_EQ(stringArg2, ""); |
||||||
|
EXPECT_EQ(stringArg3, ""); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(VectorStringArguments) |
||||||
|
{ |
||||||
|
// Required vector string argument, not given
|
||||||
|
std::vector<std::string> vectorArg1 = {}; |
||||||
|
auto result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 0); |
||||||
|
|
||||||
|
// Required vector string argument, one given
|
||||||
|
vectorArg1 = {}; |
||||||
|
result = runParser({ "foo" }, [&](auto& parser) { |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 1); |
||||||
|
if (vectorArg1.size() == 1) { |
||||||
|
EXPECT_EQ(vectorArg1[0], "foo"); |
||||||
|
} |
||||||
|
|
||||||
|
// Required vector string argument, two given
|
||||||
|
vectorArg1 = {}; |
||||||
|
result = runParser({ "hello", "world" }, [&](auto& parser) { |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 2); |
||||||
|
if (vectorArg1.size() == 2) { |
||||||
|
EXPECT_EQ(vectorArg1[0], "hello"); |
||||||
|
EXPECT_EQ(vectorArg1[1], "world"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(CombinationOfNonRequiredArguments) |
||||||
|
{ |
||||||
|
// Optional arguments, one given
|
||||||
|
int intArg1 = 0; |
||||||
|
double doubleArg1 = 0; |
||||||
|
std::string stringArg1 = ""; |
||||||
|
auto result = runParser({ "optional argument" }, [&](auto& parser) { |
||||||
|
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(intArg1, 0); |
||||||
|
EXPECT_EQ(doubleArg1, 0); |
||||||
|
EXPECT_EQ(stringArg1, "optional argument"); |
||||||
|
|
||||||
|
// Optional arguments, two given
|
||||||
|
intArg1 = 0; |
||||||
|
doubleArg1 = 0; |
||||||
|
stringArg1 = ""; |
||||||
|
result = runParser({ "999.999", "optional argument" }, [&](auto& parser) { |
||||||
|
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(intArg1, 0); |
||||||
|
EXPECT_EQ(doubleArg1, 999.999); |
||||||
|
EXPECT_EQ(stringArg1, "optional argument"); |
||||||
|
|
||||||
|
// Optional arguments, two given, one valid
|
||||||
|
intArg1 = 0; |
||||||
|
doubleArg1 = 0; |
||||||
|
stringArg1 = ""; |
||||||
|
result = runParser({ "999,999", "optional argument" }, [&](auto& parser) { |
||||||
|
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(intArg1, 0); |
||||||
|
EXPECT_EQ(doubleArg1, 0); |
||||||
|
EXPECT_EQ(stringArg1, "999,999"); |
||||||
|
|
||||||
|
// Optional arguments, two given, both valid but wrong order
|
||||||
|
stringArg1 = ""; |
||||||
|
intArg1 = 0; |
||||||
|
doubleArg1 = 0; |
||||||
|
result = runParser({ "999.999", "optional argument" }, [&](auto& parser) { |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(stringArg1, "999.999"); |
||||||
|
EXPECT_EQ(intArg1, 0); |
||||||
|
EXPECT_EQ(doubleArg1, 0); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(BoolOptions) |
||||||
|
{ |
||||||
|
// Short option
|
||||||
|
bool boolOpt1 = false; |
||||||
|
auto result = runParser({ "-b" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
|
||||||
|
// Short option, not given
|
||||||
|
boolOpt1 = false; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, false); |
||||||
|
|
||||||
|
// Long option
|
||||||
|
boolOpt1 = false; |
||||||
|
result = runParser({ "--bool" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
|
||||||
|
// Long option, not given
|
||||||
|
boolOpt1 = false; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, false); |
||||||
|
|
||||||
|
// Allow both short and long option, provide short
|
||||||
|
boolOpt1 = false; |
||||||
|
result = runParser({ "-b" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
|
||||||
|
// Allow both short and long option, provide long
|
||||||
|
boolOpt1 = false; |
||||||
|
result = runParser({ "--bool" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
|
||||||
|
// Allow both short and long option, provide both
|
||||||
|
boolOpt1 = false; |
||||||
|
result = runParser({ "-b", "--bool" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(SingleRequiredStringOptions) |
||||||
|
{ |
||||||
|
// Single required string short option
|
||||||
|
std::string stringOpt1 = ""; |
||||||
|
auto result = runParser({ "-s", "my-required-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, "my-required-argument"); |
||||||
|
|
||||||
|
// Single required string short option, given directly after
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "-smy-required-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, "my-required-argument"); |
||||||
|
|
||||||
|
// Single required string short option, empty given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "-s" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
|
||||||
|
// Single required string short option, not given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
|
||||||
|
// Single required string long option
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "--string", "my-required-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, "my-required-argument"); |
||||||
|
|
||||||
|
// Single required string long option, given directly after
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "--string=my-required-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, "my-required-argument"); |
||||||
|
|
||||||
|
// Single required string long option, empty given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "--string" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
|
||||||
|
// Single required string long option, not given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(SingleOptionalStringOptions) |
||||||
|
{ |
||||||
|
// Single optional string short option
|
||||||
|
std::string stringOpt1 = ""; |
||||||
|
std::string stringArg1 = ""; |
||||||
|
auto result = runParser({ "-s", "my-optional-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
EXPECT_EQ(stringArg1, "my-optional-argument"); |
||||||
|
|
||||||
|
// Single optional string short option, given directly after
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "-smy-optional-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, "my-optional-argument"); |
||||||
|
|
||||||
|
// Single optional string short option, empty given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "-s" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
|
||||||
|
// Single optional string short option, not given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
|
||||||
|
// Single optional string long option
|
||||||
|
stringOpt1 = ""; |
||||||
|
stringArg1 = ""; |
||||||
|
result = runParser({ "--string", "my-optional-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
EXPECT_EQ(stringArg1, "my-optional-argument"); |
||||||
|
|
||||||
|
// Single optional string long option, given directly after
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "--string=my-optional-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, "my-optional-argument"); |
||||||
|
|
||||||
|
// Single optional string long option, empty given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "--string" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
|
||||||
|
// Single optional string long option, not given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(SingleNonRequiredStringOptions) |
||||||
|
{ |
||||||
|
// Single non-required string short option
|
||||||
|
std::string stringOpt1 = ""; |
||||||
|
std::string stringArg1 = ""; |
||||||
|
auto result = runParser({ "-s", "my-non-required-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
EXPECT_EQ(stringArg1, "my-non-required-argument"); |
||||||
|
|
||||||
|
// Single non-required string short option, given directly after
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "-smy-non-required-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
|
||||||
|
// Single non-required string short option, empty given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "-s" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
|
||||||
|
// Single non-required string short option, not given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
|
||||||
|
// Single non-required string long option
|
||||||
|
stringOpt1 = ""; |
||||||
|
stringArg1 = ""; |
||||||
|
result = runParser({ "--string", "my-non-required-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
EXPECT_EQ(stringArg1, "my-non-required-argument"); |
||||||
|
|
||||||
|
// Single non-required string long option, given directly after
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "--string=my-non-required-argument" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
|
||||||
|
// Single non-required string long option, empty given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({ "--string" }, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
|
||||||
|
// Single non-required string long option, not given
|
||||||
|
stringOpt1 = ""; |
||||||
|
result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(stringOpt1, ""); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(NumberOptions) |
||||||
|
{ |
||||||
|
// Required int short option
|
||||||
|
int intOpt1 = 0; |
||||||
|
auto result = runParser({ "-i", "2147483647" }, [&](auto& parser) { |
||||||
|
parser.addOption(intOpt1, 'i', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(intOpt1, 2147483647); |
||||||
|
|
||||||
|
// Required int short option, overflown value given
|
||||||
|
intOpt1 = 0; |
||||||
|
result = runParser({ "-i", "2147483648" }, [&](auto& parser) { |
||||||
|
parser.addOption(intOpt1, 'i', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(intOpt1, 0); |
||||||
|
|
||||||
|
// Required int short option, empty given
|
||||||
|
intOpt1 = 0; |
||||||
|
result = runParser({ "-i" }, [&](auto& parser) { |
||||||
|
parser.addOption(intOpt1, 'i', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(intOpt1, 0); |
||||||
|
|
||||||
|
// Required unsigned int short option
|
||||||
|
unsigned int unsignedIntOpt1 = 0; |
||||||
|
result = runParser({ "-u", "4294967295" }, [&](auto& parser) { |
||||||
|
parser.addOption(unsignedIntOpt1, 'u', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(unsignedIntOpt1, 4294967295); |
||||||
|
|
||||||
|
// Required unsigned int short option, overflown value given
|
||||||
|
unsignedIntOpt1 = 0; |
||||||
|
result = runParser({ "-u", "4294967296" }, [&](auto& parser) { |
||||||
|
parser.addOption(unsignedIntOpt1, 'u', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(unsignedIntOpt1, 0); |
||||||
|
|
||||||
|
// Required unsigned int short option, empty given
|
||||||
|
unsignedIntOpt1 = 0; |
||||||
|
result = runParser({ "-u" }, [&](auto& parser) { |
||||||
|
parser.addOption(unsignedIntOpt1, 'u', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(unsignedIntOpt1, 0); |
||||||
|
|
||||||
|
// Required double short option
|
||||||
|
double doubleOpt1 = 0; |
||||||
|
result = runParser({ "-d", "999.999" }, [&](auto& parser) { |
||||||
|
parser.addOption(doubleOpt1, 'd', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(doubleOpt1, 999.999); |
||||||
|
|
||||||
|
// Required double short option, empty given
|
||||||
|
doubleOpt1 = 0; |
||||||
|
result = runParser({ "-d" }, [&](auto& parser) { |
||||||
|
parser.addOption(doubleOpt1, 'd', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(doubleOpt1, 0); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(VectorStringOptions) |
||||||
|
{ |
||||||
|
// Required vector string short option, not given
|
||||||
|
std::vector<std::string> vectorOpt1 = {}; |
||||||
|
auto result = runParser({}, [&](auto& parser) { |
||||||
|
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(vectorOpt1.size(), 0); |
||||||
|
|
||||||
|
// Required vector string short option, empty given
|
||||||
|
vectorOpt1 = {}; |
||||||
|
result = runParser({ "-v" }, [&](auto& parser) { |
||||||
|
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(vectorOpt1.size(), 0); |
||||||
|
|
||||||
|
// Required vector string short option, one given
|
||||||
|
vectorOpt1 = {}; |
||||||
|
result = runParser({ "-v", "a vector argument!" }, [&](auto& parser) { |
||||||
|
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(vectorOpt1.size(), 1); |
||||||
|
if (vectorOpt1.size() == 1) { |
||||||
|
EXPECT_EQ(vectorOpt1[0], "a vector argument!"); |
||||||
|
} |
||||||
|
|
||||||
|
// Required vector string short option, two given
|
||||||
|
vectorOpt1 = {}; |
||||||
|
result = runParser({ "-v", "hello", "-v", "world" }, [&](auto& parser) { |
||||||
|
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(vectorOpt1.size(), 2); |
||||||
|
if (vectorOpt1.size() == 2) { |
||||||
|
EXPECT_EQ(vectorOpt1[0], "hello"); |
||||||
|
EXPECT_EQ(vectorOpt1[1], "world"); |
||||||
|
} |
||||||
|
|
||||||
|
// Required vector string short option, two given directly after
|
||||||
|
vectorOpt1 = {}; |
||||||
|
result = runParser({ "-vhello", "-vworld" }, [&](auto& parser) { |
||||||
|
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(vectorOpt1.size(), 2); |
||||||
|
if (vectorOpt1.size() == 2) { |
||||||
|
EXPECT_EQ(vectorOpt1[0], "hello"); |
||||||
|
EXPECT_EQ(vectorOpt1[1], "world"); |
||||||
|
} |
||||||
|
|
||||||
|
// Required vector string long option, empty given
|
||||||
|
vectorOpt1 = {}; |
||||||
|
result = runParser({ "--vector" }, [&](auto& parser) { |
||||||
|
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(vectorOpt1.size(), 0); |
||||||
|
|
||||||
|
// Required vector string long option, one given
|
||||||
|
vectorOpt1 = {}; |
||||||
|
result = runParser({ "--vector", "a vector argument!" }, [&](auto& parser) { |
||||||
|
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(vectorOpt1.size(), 1); |
||||||
|
if (vectorOpt1.size() == 1) { |
||||||
|
EXPECT_EQ(vectorOpt1[0], "a vector argument!"); |
||||||
|
} |
||||||
|
|
||||||
|
// Required vector string long option, two given
|
||||||
|
vectorOpt1 = {}; |
||||||
|
result = runParser({ "--vector", "hello", "--vector", "world" }, [&](auto& parser) { |
||||||
|
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(vectorOpt1.size(), 2); |
||||||
|
if (vectorOpt1.size() == 2) { |
||||||
|
EXPECT_EQ(vectorOpt1[0], "hello"); |
||||||
|
EXPECT_EQ(vectorOpt1[1], "world"); |
||||||
|
} |
||||||
|
|
||||||
|
// Required vector string long option, two given directly after
|
||||||
|
vectorOpt1 = {}; |
||||||
|
result = runParser({ "--vector=hello", "--vector=world" }, [&](auto& parser) { |
||||||
|
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(vectorOpt1.size(), 2); |
||||||
|
if (vectorOpt1.size() == 2) { |
||||||
|
EXPECT_EQ(vectorOpt1.at(0), "hello"); |
||||||
|
EXPECT_EQ(vectorOpt1.at(1), "world"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(MultipleOptions) |
||||||
|
{ |
||||||
|
// Both short options, second is required, with a non-option parameter in-between
|
||||||
|
bool boolOpt1 = false; |
||||||
|
std::string stringOpt1 = ""; |
||||||
|
std::vector<std::string> vectorArg1 = {}; |
||||||
|
auto result = runParser({ "-b", "something", "-s", "a-string-value" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes); |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
EXPECT_EQ(stringOpt1, "a-string-value"); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 1); |
||||||
|
if (vectorArg1.size() == 1) { |
||||||
|
EXPECT_EQ(vectorArg1.at(0), "something"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(StopOnDoubleDashOption) |
||||||
|
{ |
||||||
|
// Bool short options, missing
|
||||||
|
// Expected: The bool options are interpreted as non-option parameters
|
||||||
|
bool boolOpt1 = false; |
||||||
|
bool boolOpt2 = false; |
||||||
|
std::vector<std::string> vectorArg1 = {}; |
||||||
|
auto result = runParser({ "--", "-b", "-c" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr); |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, false); |
||||||
|
EXPECT_EQ(boolOpt2, false); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 2); |
||||||
|
if (vectorArg1.size() == 2) { |
||||||
|
EXPECT_EQ(vectorArg1.at(0), "-b"); |
||||||
|
EXPECT_EQ(vectorArg1.at(1), "-c"); |
||||||
|
} |
||||||
|
|
||||||
|
// Bool short options, one given
|
||||||
|
// Expected: boolOpt1 is set, one non-option parameter
|
||||||
|
boolOpt1 = false; |
||||||
|
boolOpt2 = false; |
||||||
|
vectorArg1 = {}; |
||||||
|
result = runParser({ "-b", "--", "-c" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr); |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
EXPECT_EQ(boolOpt2, false); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 1); |
||||||
|
if (vectorArg1.size() == 1) { |
||||||
|
EXPECT_EQ(vectorArg1.at(0), "-c"); |
||||||
|
} |
||||||
|
|
||||||
|
// Bool long options, missing
|
||||||
|
// Expected: The bool options are interpreted as non-option parameters
|
||||||
|
boolOpt1 = false; |
||||||
|
boolOpt2 = false; |
||||||
|
vectorArg1 = {}; |
||||||
|
result = runParser({ "--", "--bool", "--cool" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr); |
||||||
|
parser.addOption(boolOpt2, '\0', "cool", nullptr, nullptr); |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, false); |
||||||
|
EXPECT_EQ(boolOpt2, false); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 2); |
||||||
|
if (vectorArg1.size() == 2) { |
||||||
|
EXPECT_EQ(vectorArg1.at(0), "--bool"); |
||||||
|
EXPECT_EQ(vectorArg1.at(1), "--cool"); |
||||||
|
} |
||||||
|
|
||||||
|
// Bool long options, one given
|
||||||
|
// Expected: boolOpt1 is set, one non-option parameter
|
||||||
|
boolOpt1 = false; |
||||||
|
boolOpt2 = false; |
||||||
|
vectorArg1 = {}; |
||||||
|
result = runParser({ "--bool", "--", "--cool" }, [&](auto& parser) { |
||||||
|
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr); |
||||||
|
parser.addOption(boolOpt2, '\0', "cool", nullptr, nullptr); |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
EXPECT_EQ(boolOpt2, false); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 1); |
||||||
|
if (vectorArg1.size() == 1) { |
||||||
|
EXPECT_EQ(vectorArg1.at(0), "--cool"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(StopOnFirstNonOption) |
||||||
|
{ |
||||||
|
// Do not stop on first non-option; arguments are in correct order
|
||||||
|
// Expected: The bool options are set and one non-option parameter
|
||||||
|
bool boolOpt1 = false; |
||||||
|
bool boolOpt2 = false; |
||||||
|
std::vector<std::string> vectorArg1 = {}; |
||||||
|
auto result = runParser({ "-b", "-c", "stopping" }, [&](auto& parser) { |
||||||
|
parser.setStopParsingOnFirstNonOption(false); |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr); |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
EXPECT_EQ(boolOpt2, true); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 1); |
||||||
|
if (vectorArg1.size() == 1) { |
||||||
|
EXPECT_EQ(vectorArg1.at(0), "stopping"); |
||||||
|
} |
||||||
|
|
||||||
|
// Do not stop on first non-option; arguments are in wrong order
|
||||||
|
// Expected: The bool options are set and one non-option parameter
|
||||||
|
boolOpt1 = false; |
||||||
|
boolOpt2 = false; |
||||||
|
vectorArg1 = {}; |
||||||
|
result = runParser({ "-b", "stopping", "-c" }, [&](auto& parser) { |
||||||
|
parser.setStopParsingOnFirstNonOption(false); |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr); |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
EXPECT_EQ(boolOpt2, true); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 1); |
||||||
|
if (vectorArg1.size() == 1) { |
||||||
|
EXPECT_EQ(vectorArg1.at(0), "stopping"); |
||||||
|
} |
||||||
|
|
||||||
|
// Stop on first non option, arguments are in correct order
|
||||||
|
// Expected: The bool options are set and one non-option parameter
|
||||||
|
boolOpt1 = false; |
||||||
|
boolOpt2 = false; |
||||||
|
vectorArg1 = {}; |
||||||
|
result = runParser({ "-b", "-c", "stopping" }, [&](auto& parser) { |
||||||
|
parser.setStopParsingOnFirstNonOption(true); |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr); |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
EXPECT_EQ(boolOpt2, true); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 1); |
||||||
|
if (vectorArg1.size() == 1) { |
||||||
|
EXPECT_EQ(vectorArg1.at(0), "stopping"); |
||||||
|
} |
||||||
|
|
||||||
|
// Stop on first non option, arguments are in wrong order
|
||||||
|
// Expected: boolOpt1 is set and the rest are non-option parameters
|
||||||
|
boolOpt1 = false; |
||||||
|
boolOpt2 = false; |
||||||
|
vectorArg1 = {}; |
||||||
|
result = runParser({ "-b", "stopping", "-c" }, [&](auto& parser) { |
||||||
|
parser.setStopParsingOnFirstNonOption(true); |
||||||
|
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr); |
||||||
|
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr); |
||||||
|
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
EXPECT_EQ(boolOpt2, false); |
||||||
|
EXPECT_EQ(vectorArg1.size(), 2); |
||||||
|
if (vectorArg1.size() == 2) { |
||||||
|
EXPECT_EQ(vectorArg1.at(0), "stopping"); |
||||||
|
EXPECT_EQ(vectorArg1.at(1), "-c"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(ExitOnFirstError) |
||||||
|
{ |
||||||
|
// Do not stop on first error, one non-existing given
|
||||||
|
// Expected: parsing fails, boolOpt1 is set
|
||||||
|
bool boolOpt1 = false; |
||||||
|
auto result = runParser({ "--this-doesnt-exist", "--this-exist" }, [&](auto& parser) { |
||||||
|
parser.setExitOnFirstError(false); |
||||||
|
parser.addOption(boolOpt1, '\0', "this-exist", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(boolOpt1, true); |
||||||
|
|
||||||
|
// Stop on first error, one non-existing given
|
||||||
|
// Expected: parsing fails, boolOpt1 is not set
|
||||||
|
boolOpt1 = false; |
||||||
|
result = runParser({ "--this-doesnt-exist", "--this-exist" }, [&](auto& parser) { |
||||||
|
parser.setExitOnFirstError(true); |
||||||
|
parser.addOption(boolOpt1, '\0', "this-exist", nullptr, nullptr); |
||||||
|
}); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(boolOpt1, false); |
||||||
|
} |
@ -0,0 +1,517 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <cstddef> // size_t |
||||||
|
#include <cstdint> // int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t |
||||||
|
#include <map> |
||||||
|
#include <sstream> // stringstream |
||||||
|
#include <string> |
||||||
|
#include <unordered_map> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "macro.h" |
||||||
|
#include "testcase.h" |
||||||
|
#include "testsuite.h" |
||||||
|
#include "util/format/format.h" |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(FormatIntegral) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
// Signed
|
||||||
|
|
||||||
|
int8_t i8 = 127; // char
|
||||||
|
result = Util::format("{}", i8); |
||||||
|
EXPECT_EQ(result, "127"); |
||||||
|
|
||||||
|
int16_t i16 = 32767; |
||||||
|
result = Util::format("{}", i16); |
||||||
|
EXPECT_EQ(result, "32767"); |
||||||
|
|
||||||
|
int32_t i32 = 68766; // int
|
||||||
|
result = Util::format("{}", i32); |
||||||
|
EXPECT_EQ(result, "68766"); |
||||||
|
|
||||||
|
int64_t i64 = 237942768427; // long int
|
||||||
|
result = Util::format("{}", i64); |
||||||
|
EXPECT_EQ(result, "237942768427"); |
||||||
|
|
||||||
|
// Unsigned
|
||||||
|
|
||||||
|
uint8_t u8 = 255; // unsigned char
|
||||||
|
result = Util::format("{}", u8); |
||||||
|
EXPECT_EQ(result, "255"); |
||||||
|
|
||||||
|
uint16_t u16 = 65535; |
||||||
|
result = Util::format("{}", u16); |
||||||
|
EXPECT_EQ(result, "65535"); |
||||||
|
|
||||||
|
uint32_t u32 = 4294967295; // unsigned int
|
||||||
|
result = Util::format("{}", u32); |
||||||
|
EXPECT_EQ(result, "4294967295"); |
||||||
|
|
||||||
|
size_t u64 = 18446744073709551615; // long unsigned int
|
||||||
|
result = Util::format("{}", u64); |
||||||
|
EXPECT_EQ(result, "18446744073709551615"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(FormatFloatingPoint) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
float f32R = 245789.70000; |
||||||
|
result = Util::format("{}", f32R); |
||||||
|
EXPECT_EQ(result, "245789.703125"); |
||||||
|
|
||||||
|
float f32 = 45645.3233; |
||||||
|
result = Util::format("{}", f32); |
||||||
|
EXPECT_EQ(result, "45645.324219"); |
||||||
|
|
||||||
|
double f64 = 87522.300000000; |
||||||
|
result = Util::format("{}", f64); |
||||||
|
EXPECT_EQ(result, "87522.300000"); |
||||||
|
|
||||||
|
double pi = 3.14159265359; |
||||||
|
result = Util::format("{}", pi); |
||||||
|
EXPECT_EQ(result, "3.141593"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(FormatChar) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
char character = 'A'; |
||||||
|
result = Util::format("{}", character); |
||||||
|
EXPECT_EQ(result, "A"); |
||||||
|
|
||||||
|
bool boolean = true; |
||||||
|
result = Util::format("{}", boolean); |
||||||
|
EXPECT_EQ(result, "true"); |
||||||
|
|
||||||
|
boolean = false; |
||||||
|
result = Util::format("{}", boolean); |
||||||
|
EXPECT_EQ(result, "false"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(FormatString) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
result = Util::format(""); |
||||||
|
EXPECT_EQ(result, ""); |
||||||
|
|
||||||
|
const char* cString = "C string"; |
||||||
|
result = Util::format("{}", cString); |
||||||
|
EXPECT_EQ(result, "C string"); |
||||||
|
|
||||||
|
std::string string = "string"; |
||||||
|
result = Util::format("{}", string); |
||||||
|
EXPECT_EQ(result, "string"); |
||||||
|
|
||||||
|
std::string_view stringView = "string_view"; |
||||||
|
result = Util::format("{}", stringView); |
||||||
|
EXPECT_EQ(result, "string_view"); |
||||||
|
|
||||||
|
result = Util::format("{} {}", "Hello", "World"); |
||||||
|
EXPECT_EQ(result, "Hello World"); |
||||||
|
|
||||||
|
result = Util::format("{{escaped braces}}"); |
||||||
|
EXPECT_EQ(result, "{escaped braces}"); |
||||||
|
|
||||||
|
result = Util::format("{{braces{}}}", "Something"); |
||||||
|
EXPECT_EQ(result, "{bracesSomething}"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(FormatPointer) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
result = Util::format("{}", nullptr); |
||||||
|
EXPECT_EQ(result, "0x0"); |
||||||
|
|
||||||
|
int integer = 42; |
||||||
|
std::stringstream stream; |
||||||
|
stream << &integer; |
||||||
|
std::string pointer = stream.str(); |
||||||
|
|
||||||
|
result = Util::format("{}", &integer); |
||||||
|
EXPECT_EQ(result, pointer); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(FormatSpecifierIntegral) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
// Fill and Align
|
||||||
|
result = Util::format("{:+<}", 12345); |
||||||
|
EXPECT_EQ(result, "12345"); |
||||||
|
result = Util::format("{:+^}", 12345); |
||||||
|
EXPECT_EQ(result, "12345"); |
||||||
|
result = Util::format("{:+>}", 12345); |
||||||
|
EXPECT_EQ(result, "12345"); |
||||||
|
|
||||||
|
// Sign
|
||||||
|
result = Util::format("{:+}", 12345); |
||||||
|
EXPECT_EQ(result, "+12345"); |
||||||
|
result = Util::format("{:+}", -12345); |
||||||
|
EXPECT_EQ(result, "-12345"); |
||||||
|
result = Util::format("{:-}", 12345); |
||||||
|
EXPECT_EQ(result, "12345"); |
||||||
|
result = Util::format("{:-}", -12345); |
||||||
|
EXPECT_EQ(result, "-12345"); |
||||||
|
result = Util::format("{: }", 12345); |
||||||
|
EXPECT_EQ(result, " 12345"); |
||||||
|
result = Util::format("{: }", -12345); |
||||||
|
EXPECT_EQ(result, "-12345"); |
||||||
|
|
||||||
|
// AlternativeForm
|
||||||
|
result = Util::format("{:#}", 12345); |
||||||
|
EXPECT_EQ(result, "12345"); |
||||||
|
|
||||||
|
// ZeroPadding
|
||||||
|
result = Util::format("{:0}", 12345); |
||||||
|
EXPECT_EQ(result, "12345"); |
||||||
|
|
||||||
|
// Width
|
||||||
|
result = Util::format("{:10}", 12345); |
||||||
|
EXPECT_EQ(result, " 12345"); |
||||||
|
|
||||||
|
// Width + Fill and Align
|
||||||
|
result = Util::format("{:+<10}", 12345); |
||||||
|
EXPECT_EQ(result, "12345+++++"); |
||||||
|
result = Util::format("{:+^10}", 12345); |
||||||
|
EXPECT_EQ(result, "++12345+++"); |
||||||
|
result = Util::format("{:+>10}", 12345); |
||||||
|
EXPECT_EQ(result, "+++++12345"); |
||||||
|
|
||||||
|
// Width + ZeroPadding
|
||||||
|
result = Util::format("{:010}", 12345); |
||||||
|
EXPECT_EQ(result, "0000012345"); |
||||||
|
|
||||||
|
// Precision
|
||||||
|
// Not possible on integral types
|
||||||
|
|
||||||
|
// Type
|
||||||
|
result = Util::format("{:b}", 12345); |
||||||
|
EXPECT_EQ(result, "11000000111001"); |
||||||
|
result = Util::format("{:B}", 12345); |
||||||
|
EXPECT_EQ(result, "11000000111001"); |
||||||
|
result = Util::format("{:c}", 65); |
||||||
|
EXPECT_EQ(result, "A"); |
||||||
|
result = Util::format("{:o}", 12345); |
||||||
|
EXPECT_EQ(result, "30071"); |
||||||
|
result = Util::format("{:x}", 62432); |
||||||
|
EXPECT_EQ(result, "f3e0"); |
||||||
|
result = Util::format("{:X}", 62432); |
||||||
|
EXPECT_EQ(result, "F3E0"); |
||||||
|
|
||||||
|
// Type + AlternativeForm
|
||||||
|
result = Util::format("{:#b}", 12345); |
||||||
|
EXPECT_EQ(result, "0b11000000111001"); |
||||||
|
result = Util::format("{:#B}", 12345); |
||||||
|
EXPECT_EQ(result, "0B11000000111001"); |
||||||
|
result = Util::format("{:#c}", 65); |
||||||
|
EXPECT_EQ(result, "A"); |
||||||
|
result = Util::format("{:#o}", 12345); |
||||||
|
EXPECT_EQ(result, "030071"); |
||||||
|
result = Util::format("{:#x}", 62432); |
||||||
|
EXPECT_EQ(result, "0xf3e0"); |
||||||
|
result = Util::format("{:#X}", 62432); |
||||||
|
EXPECT_EQ(result, "0XF3E0"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(FormatSpecifierIntegralCombination) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
// AlternativeForm + ZeroPadding + Width + Type
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
result = Util::format("{:-#010d}", 402); |
||||||
|
EXPECT_EQ(result, "0000000402"); |
||||||
|
|
||||||
|
// AlternativeForm + Width + Type
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
result = Util::format("{:#10x}", 402); |
||||||
|
EXPECT_EQ(result, " 0x192"); |
||||||
|
|
||||||
|
// + Fill and Align
|
||||||
|
|
||||||
|
result = Util::format("{:^<#10x}", 402); |
||||||
|
EXPECT_EQ(result, "0x192^^^^^"); |
||||||
|
|
||||||
|
result = Util::format("{:^^#10x}", 402); |
||||||
|
EXPECT_EQ(result, "^^0x192^^^"); |
||||||
|
|
||||||
|
result = Util::format("{:^>#10x}", 402); |
||||||
|
EXPECT_EQ(result, "^^^^^0x192"); |
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
// + Sign
|
||||||
|
|
||||||
|
result = Util::format("{:+#10x}", 402); |
||||||
|
EXPECT_EQ(result, " +0x192"); |
||||||
|
|
||||||
|
// + Fill and Align + Sign
|
||||||
|
|
||||||
|
result = Util::format("{:^<+#10x}", 402); |
||||||
|
EXPECT_EQ(result, "+0x192^^^^"); |
||||||
|
|
||||||
|
result = Util::format("{:^^+#10x}", 402); |
||||||
|
EXPECT_EQ(result, "^^+0x192^^"); |
||||||
|
|
||||||
|
result = Util::format("{:^>+#10x}", 402); |
||||||
|
EXPECT_EQ(result, "^^^^+0x192"); |
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
// + ZeroPadding
|
||||||
|
|
||||||
|
result = Util::format("{:#010x}", 402); |
||||||
|
EXPECT_EQ(result, "0x00000192"); |
||||||
|
|
||||||
|
// Fill and Align + ZeroPadding
|
||||||
|
|
||||||
|
result = Util::format("{:^<#010x}", 402); |
||||||
|
EXPECT_EQ(result, "0x19200000"); |
||||||
|
|
||||||
|
result = Util::format("{:^^#010x}", 402); |
||||||
|
EXPECT_EQ(result, "000x192000"); |
||||||
|
|
||||||
|
result = Util::format("{:^>#010x}", 402); |
||||||
|
EXPECT_EQ(result, "000000x192"); |
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
// + Sign + ZeroPadding
|
||||||
|
|
||||||
|
result = Util::format("{:+#010x}", 402); |
||||||
|
EXPECT_EQ(result, "+0x0000192"); |
||||||
|
|
||||||
|
// + Fill and Align + Sign + ZeroPadding
|
||||||
|
|
||||||
|
result = Util::format("{:^<+#010x}", 402); |
||||||
|
EXPECT_EQ(result, "+0x1920000"); |
||||||
|
|
||||||
|
result = Util::format("{:^^+#010x}", 402); |
||||||
|
EXPECT_EQ(result, "00+0x19200"); |
||||||
|
|
||||||
|
result = Util::format("{:^>+#010x}", 402); |
||||||
|
EXPECT_EQ(result, "0000+0x192"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(FormatSpecifierFloatingPoint) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
double f64 = 87522.300000000; |
||||||
|
result = Util::format("{:.1}", f64); |
||||||
|
EXPECT_EQ(result, "87522.3"); |
||||||
|
|
||||||
|
double pi = 3.14159265359; |
||||||
|
result = Util::format("{}", pi); |
||||||
|
EXPECT_EQ(result, "3.141593"); |
||||||
|
|
||||||
|
result = Util::format("{:.15}", pi); |
||||||
|
EXPECT_EQ(result, "3.141592653590000"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(FormatSpecifierChar) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
char character = 65; |
||||||
|
result = Util::format("{:b}", character); |
||||||
|
EXPECT_EQ(result, "1000001"); |
||||||
|
result = Util::format("{:B}", character); |
||||||
|
EXPECT_EQ(result, "1000001"); |
||||||
|
result = Util::format("{:d}", character); |
||||||
|
EXPECT_EQ(result, "65"); |
||||||
|
result = Util::format("{:o}", character); |
||||||
|
EXPECT_EQ(result, "101"); |
||||||
|
result = Util::format("{:x}", character); |
||||||
|
EXPECT_EQ(result, "41"); |
||||||
|
result = Util::format("{:X}", character); |
||||||
|
EXPECT_EQ(result, "41"); |
||||||
|
|
||||||
|
bool boolean = true; |
||||||
|
result = Util::format("{:b}", boolean); |
||||||
|
EXPECT_EQ(result, "1"); |
||||||
|
result = Util::format("{:B}", boolean); |
||||||
|
EXPECT_EQ(result, "1"); |
||||||
|
result = Util::format("{:d}", boolean); |
||||||
|
EXPECT_EQ(result, "1"); |
||||||
|
result = Util::format("{:o}", boolean); |
||||||
|
EXPECT_EQ(result, "1"); |
||||||
|
result = Util::format("{:x}", boolean); |
||||||
|
EXPECT_EQ(result, "1"); |
||||||
|
result = Util::format("{:X}", boolean); |
||||||
|
EXPECT_EQ(result, "1"); |
||||||
|
|
||||||
|
boolean = false; |
||||||
|
result = Util::format("{:b}", boolean); |
||||||
|
EXPECT_EQ(result, "0"); |
||||||
|
result = Util::format("{:B}", boolean); |
||||||
|
EXPECT_EQ(result, "0"); |
||||||
|
result = Util::format("{:d}", boolean); |
||||||
|
EXPECT_EQ(result, "0"); |
||||||
|
result = Util::format("{:o}", boolean); |
||||||
|
EXPECT_EQ(result, "0"); |
||||||
|
result = Util::format("{:x}", boolean); |
||||||
|
EXPECT_EQ(result, "0"); |
||||||
|
result = Util::format("{:X}", boolean); |
||||||
|
EXPECT_EQ(result, "0"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(FormatSpecifierString) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
std::string string = "my string"; |
||||||
|
|
||||||
|
// Fill and Align
|
||||||
|
result = Util::format("{:+<}", string); |
||||||
|
EXPECT_EQ(result, "my string"); |
||||||
|
result = Util::format("{:+^}", string); |
||||||
|
EXPECT_EQ(result, "my string"); |
||||||
|
result = Util::format("{:+>}", string); |
||||||
|
EXPECT_EQ(result, "my string"); |
||||||
|
|
||||||
|
// Sign
|
||||||
|
// Not possible on string types
|
||||||
|
|
||||||
|
// AlternativeForm
|
||||||
|
// Not possible on string types
|
||||||
|
|
||||||
|
// ZeroPadding
|
||||||
|
// Not possible on string types
|
||||||
|
|
||||||
|
// Width
|
||||||
|
result = Util::format("{:15}", string); |
||||||
|
EXPECT_EQ(result, "my string "); |
||||||
|
|
||||||
|
// Width + Fill and Align
|
||||||
|
result = Util::format("{:+<15}", string); |
||||||
|
EXPECT_EQ(result, "my string++++++"); |
||||||
|
result = Util::format("{:+^15}", string); |
||||||
|
EXPECT_EQ(result, "+++my string+++"); |
||||||
|
result = Util::format("{:+>15}", string); |
||||||
|
EXPECT_EQ(result, "++++++my string"); |
||||||
|
|
||||||
|
// Precision
|
||||||
|
// Not possible on string types
|
||||||
|
|
||||||
|
// Type
|
||||||
|
result = Util::format("{:s}", string); |
||||||
|
EXPECT_EQ(result, "my string"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(FormatSpecifierPointer) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
int integer = 42; |
||||||
|
std::stringstream stream; |
||||||
|
stream << &integer; |
||||||
|
std::string pointer = stream.str(); |
||||||
|
|
||||||
|
// Fill and Align
|
||||||
|
result = Util::format("{:+<}", &integer); |
||||||
|
EXPECT_EQ(result, pointer); |
||||||
|
result = Util::format("{:+^}", &integer); |
||||||
|
EXPECT_EQ(result, pointer); |
||||||
|
result = Util::format("{:+>}", &integer); |
||||||
|
EXPECT_EQ(result, pointer); |
||||||
|
|
||||||
|
// Sign
|
||||||
|
// Not possible on string types
|
||||||
|
|
||||||
|
// AlternativeForm
|
||||||
|
// Not possible on string types
|
||||||
|
|
||||||
|
// ZeroPadding
|
||||||
|
// Not possible on string types
|
||||||
|
|
||||||
|
// Width
|
||||||
|
result = Util::format("{:24}", &integer); |
||||||
|
EXPECT_EQ(result, std::string(24 - pointer.length(), ' ') + pointer); |
||||||
|
|
||||||
|
// Width + Fill and Align
|
||||||
|
result = Util::format("{:+<24}", &integer); |
||||||
|
EXPECT_EQ(result, pointer + std::string(24 - pointer.length(), '+')); |
||||||
|
result = Util::format("{:+^24}", &integer); |
||||||
|
EXPECT_EQ(result, std::string((24 - pointer.length()) / 2, '+') + pointer + std::string((24 - pointer.length()) / 2, '+')); |
||||||
|
result = Util::format("{:+>24}", &integer); |
||||||
|
EXPECT_EQ(result, std::string(24 - pointer.length(), '+') + pointer); |
||||||
|
|
||||||
|
// Precision
|
||||||
|
// Not possible on string types
|
||||||
|
|
||||||
|
// Type
|
||||||
|
result = Util::format("{:p}", &integer); |
||||||
|
EXPECT_EQ(result, pointer); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(FormatContainers) |
||||||
|
{ |
||||||
|
std::string result; |
||||||
|
|
||||||
|
std::vector<std::string> vector { "thing1", "thing2", "thing3" }; |
||||||
|
result = Util::format("{}", vector); |
||||||
|
EXPECT_EQ(result, "{thing1,thing2,thing3}"); |
||||||
|
result = Util::format("{:1}", vector); |
||||||
|
EXPECT_EQ(result, "{ thing1, thing2, thing3 }"); |
||||||
|
result = Util::format("{:#4}", vector); |
||||||
|
EXPECT_EQ(result, R"({ |
||||||
|
thing1, |
||||||
|
thing2, |
||||||
|
thing3 |
||||||
|
})"); |
||||||
|
result = Util::format("{:\t<#1}", vector); |
||||||
|
EXPECT_EQ(result, R"({ |
||||||
|
thing1, |
||||||
|
thing2, |
||||||
|
thing3 |
||||||
|
})"); |
||||||
|
|
||||||
|
std::map<std::string, int> map { { "thing3", 3 }, { "thing2", 2 }, { "thing1", 1 } }; |
||||||
|
result = Util::format("{}", map); |
||||||
|
EXPECT_EQ(result, R"({"thing1":1,"thing2":2,"thing3":3})"); |
||||||
|
result = Util::format("{:1}", map); |
||||||
|
EXPECT_EQ(result, R"({ "thing1": 1, "thing2": 2, "thing3": 3 })"); |
||||||
|
result = Util::format("{:#4}", map); |
||||||
|
EXPECT_EQ(result, R"({ |
||||||
|
"thing1": 1, |
||||||
|
"thing2": 2, |
||||||
|
"thing3": 3 |
||||||
|
})"); |
||||||
|
result = Util::format("{:\t<#1}", map); |
||||||
|
EXPECT_EQ(result, R"({ |
||||||
|
"thing1": 1, |
||||||
|
"thing2": 2, |
||||||
|
"thing3": 3 |
||||||
|
})"); |
||||||
|
|
||||||
|
// Multidimensional containers arent supported,
|
||||||
|
// the user should write a customization point
|
||||||
|
std::vector<std::vector<std::string>> twoDimensionalVector { |
||||||
|
{ "thing1", "thing2", "thing3" }, |
||||||
|
{ "thing1", "thing2", "thing3" } |
||||||
|
}; |
||||||
|
result = Util::format("{:#4}", twoDimensionalVector); |
||||||
|
EXPECT_EQ(result, R"({ |
||||||
|
{thing1,thing2,thing3}, |
||||||
|
{thing1,thing2,thing3} |
||||||
|
})"); |
||||||
|
} |
||||||
|
|
||||||
|
// Local Variables:
|
||||||
|
// lsp-in-cpp-project-cache: nil
|
||||||
|
// End:
|
@ -0,0 +1,570 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <cstddef> // nullptr_t |
||||||
|
#include <cstdint> // uint32_t |
||||||
|
#include <functional> // function |
||||||
|
#include <map> |
||||||
|
#include <string> |
||||||
|
#include <unordered_map> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "macro.h" |
||||||
|
#include "testcase.h" |
||||||
|
#include "testsuite.h" |
||||||
|
#include "util/json/array.h" |
||||||
|
#include "util/json/job.h" |
||||||
|
#include "util/json/json.h" |
||||||
|
#include "util/json/lexer.h" |
||||||
|
#include "util/json/parser.h" |
||||||
|
#include "util/json/serializer.h" |
||||||
|
|
||||||
|
#define DONT_PRINT_PARSER_ERRORS |
||||||
|
|
||||||
|
#ifndef DONT_PRINT_PARSER_ERRORS |
||||||
|
#define EXEC(x) x |
||||||
|
#else |
||||||
|
#define EXEC(x) \ |
||||||
|
stderr = Test::TestSuite::the().outputNull(); \
|
||||||
|
x; \
|
||||||
|
stderr = Test::TestSuite::the().outputErr(); |
||||||
|
#endif |
||||||
|
|
||||||
|
std::vector<Util::JSON::Token> lex(const std::string& input) |
||||||
|
{ |
||||||
|
EXEC( |
||||||
|
Util::JSON::Job job(input); |
||||||
|
Util::JSON::Lexer lexer(&job); |
||||||
|
lexer.analyze();); |
||||||
|
return *job.tokens(); |
||||||
|
} |
||||||
|
|
||||||
|
Util::Json parse(const std::string& input) |
||||||
|
{ |
||||||
|
EXEC( |
||||||
|
Util::JSON::Job job(input); |
||||||
|
Util::JSON::Lexer lexer(&job); |
||||||
|
lexer.analyze();); |
||||||
|
|
||||||
|
if (!job.success()) { |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
EXEC( |
||||||
|
Util::JSON::Parser parser(&job); |
||||||
|
Util::Json json = parser.parse();); |
||||||
|
|
||||||
|
if (!job.success()) { |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
return json; |
||||||
|
} |
||||||
|
|
||||||
|
std::string serialize(const std::string& input, uint32_t indent = 0) |
||||||
|
{ |
||||||
|
EXEC( |
||||||
|
auto json = Util::Json::parse(input);); |
||||||
|
return json.dump(indent); |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(JsonLexer) |
||||||
|
{ |
||||||
|
std::vector<Util::JSON::Token> tokens; |
||||||
|
|
||||||
|
// Literal
|
||||||
|
|
||||||
|
tokens = lex("true"); |
||||||
|
EXPECT_EQ(tokens.size(), 1); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "true"); |
||||||
|
EXPECT(tokens[0].type == Util::JSON::Token::Type::Literal); |
||||||
|
|
||||||
|
tokens = lex("false"); |
||||||
|
EXPECT_EQ(tokens.size(), 1); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "false"); |
||||||
|
EXPECT(tokens[0].type == Util::JSON::Token::Type::Literal); |
||||||
|
|
||||||
|
tokens = lex("null"); |
||||||
|
EXPECT_EQ(tokens.size(), 1); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "null"); |
||||||
|
EXPECT(tokens[0].type == Util::JSON::Token::Type::Literal); |
||||||
|
|
||||||
|
// Number
|
||||||
|
|
||||||
|
tokens = lex("3.14"); |
||||||
|
EXPECT_EQ(tokens.size(), 1); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "3.14"); |
||||||
|
EXPECT(tokens[0].type == Util::JSON::Token::Type::Number); |
||||||
|
|
||||||
|
tokens = lex("-3.14e+2"); |
||||||
|
EXPECT_EQ(tokens.size(), 1); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "-3.14e+2"); |
||||||
|
EXPECT(tokens[0].type == Util::JSON::Token::Type::Number); |
||||||
|
|
||||||
|
tokens = lex("+3.14"); |
||||||
|
EXPECT_EQ(tokens.size(), 1); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "+"); |
||||||
|
EXPECT(tokens[0].type == Util::JSON::Token::Type::None); |
||||||
|
|
||||||
|
// String
|
||||||
|
|
||||||
|
tokens = lex(R"("a string")"); |
||||||
|
EXPECT_EQ(tokens.size(), 1); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "a string"); |
||||||
|
EXPECT(tokens[0].type == Util::JSON::Token::Type::String); |
||||||
|
|
||||||
|
tokens = lex(R"("a string""another string")"); |
||||||
|
EXPECT_EQ(tokens.size(), 2); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "a string"); |
||||||
|
EXPECT_EQ(tokens[1].symbol, "another string"); |
||||||
|
EXPECT(tokens[0].type == Util::JSON::Token::Type::String); |
||||||
|
|
||||||
|
tokens = lex("\"a string\nwill break on the newline symbol\""); |
||||||
|
EXPECT_EQ(tokens.size(), 1); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "a string"); |
||||||
|
EXPECT(tokens[0].type == Util::JSON::Token::Type::String); |
||||||
|
|
||||||
|
// Array
|
||||||
|
|
||||||
|
tokens = lex("[]"); |
||||||
|
EXPECT_EQ(tokens.size(), 2); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "["); |
||||||
|
EXPECT_EQ(tokens[1].symbol, "]"); |
||||||
|
|
||||||
|
tokens = lex("[\n\n\n]"); |
||||||
|
EXPECT_EQ(tokens.size(), 2); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "["); |
||||||
|
EXPECT_EQ(tokens[1].symbol, "]"); |
||||||
|
|
||||||
|
// Object
|
||||||
|
|
||||||
|
tokens = lex("{}"); |
||||||
|
EXPECT_EQ(tokens.size(), 2); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "{"); |
||||||
|
EXPECT_EQ(tokens[1].symbol, "}"); |
||||||
|
|
||||||
|
tokens = lex("{\n\n\n}"); |
||||||
|
EXPECT_EQ(tokens.size(), 2); |
||||||
|
EXPECT_EQ(tokens[0].symbol, "{"); |
||||||
|
EXPECT_EQ(tokens[1].symbol, "}"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(JsonParser) |
||||||
|
{ |
||||||
|
Util::Json json; |
||||||
|
|
||||||
|
json = parse("null"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse("true"); |
||||||
|
EXPECT_EQ(json.size(), 1); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Bool); |
||||||
|
|
||||||
|
json = parse("false"); |
||||||
|
EXPECT_EQ(json.size(), 1); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Bool); |
||||||
|
|
||||||
|
json = parse("3.14"); |
||||||
|
EXPECT_EQ(json.size(), 1); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Number); |
||||||
|
|
||||||
|
json = parse(R"("a string")"); |
||||||
|
EXPECT_EQ(json.size(), 1); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::String); |
||||||
|
|
||||||
|
// Array
|
||||||
|
|
||||||
|
json = parse("["); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse("[ 123"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse("[ 123,"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse("[ 123, ]"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse("[ 123 456 ]"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse("[]"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Array); |
||||||
|
|
||||||
|
json = parse(R"([ "element", 3.14 ])"); |
||||||
|
EXPECT_EQ(json.size(), 2); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Array); |
||||||
|
|
||||||
|
// Object
|
||||||
|
|
||||||
|
json = parse("{"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse(R"({ "name")"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse(R"({ "name":)"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse(R"({ "name":,)"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse(R"({ "name":"value")"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse(R"({ "name":"value",)"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse(R"({ "name":"value", })"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse(R"({ "name" "value" })"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse(R"({ 123 })"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse("{}"); |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Object); |
||||||
|
|
||||||
|
json = parse(R"({ "name": "value", "name2": 3.14 })"); |
||||||
|
EXPECT_EQ(json.size(), 2); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Object); |
||||||
|
|
||||||
|
// Multiple root elements
|
||||||
|
|
||||||
|
json = parse("54 false"); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse("3.14, 666"); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = parse("true\nfalse"); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(JsonToJsonValue) |
||||||
|
{ |
||||||
|
Util::Json json; |
||||||
|
|
||||||
|
json = {}; |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = nullptr; |
||||||
|
EXPECT_EQ(json.size(), 0); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Null); |
||||||
|
|
||||||
|
json = true; |
||||||
|
EXPECT_EQ(json.size(), 1); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Bool); |
||||||
|
|
||||||
|
json = false; |
||||||
|
EXPECT_EQ(json.size(), 1); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Bool); |
||||||
|
|
||||||
|
json = 666; |
||||||
|
EXPECT_EQ(json.size(), 1); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Number); |
||||||
|
|
||||||
|
json = 3.14; |
||||||
|
EXPECT_EQ(json.size(), 1); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Number); |
||||||
|
|
||||||
|
const char* characters = "my string"; |
||||||
|
json = characters; |
||||||
|
EXPECT_EQ(json.size(), 1); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::String); |
||||||
|
|
||||||
|
std::string string = "my string"; |
||||||
|
json = string; |
||||||
|
EXPECT_EQ(json.size(), 1); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::String); |
||||||
|
|
||||||
|
// Nested Array with multiple types
|
||||||
|
json = { "element", 3.14, true, nullptr, { "nested element", { "more nesting", { 1, 2, 3, "yes" } } } }; |
||||||
|
EXPECT_EQ(json.size(), 5); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Array); |
||||||
|
|
||||||
|
// Nested Object with multiple types
|
||||||
|
json = { { "name", "value" }, { "name2", 3.14 }, { "name3", true }, { "name4", nullptr }, { "name5", { { "nested name", "value" } } } }; |
||||||
|
EXPECT_EQ(json.size(), 5); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Object); |
||||||
|
|
||||||
|
// Array with singular type
|
||||||
|
std::vector<std::string> vector = { "element", "element2", "element3" }; |
||||||
|
json = vector; |
||||||
|
EXPECT_EQ(json.size(), 3); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Array); |
||||||
|
|
||||||
|
// Object with singular type
|
||||||
|
std::map<std::string, std::string> map = { { "name", "value" }, { "name2", "value2" } }; |
||||||
|
json = map; |
||||||
|
EXPECT_EQ(json.size(), 2); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Object); |
||||||
|
|
||||||
|
// Object with singular type
|
||||||
|
std::unordered_map<std::string, std::string> unorderedMap = { { "name", "value" }, { "name2", "value2" } }; |
||||||
|
json = unorderedMap; |
||||||
|
EXPECT_EQ(json.size(), 2); |
||||||
|
EXPECT_EQ(json.type(), Util::Json::Type::Object); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(JsonFromJsonValue) |
||||||
|
{ |
||||||
|
Util::Json json; |
||||||
|
|
||||||
|
json = nullptr; |
||||||
|
EXPECT_EQ(json.get<std::nullptr_t>(), nullptr); |
||||||
|
|
||||||
|
json = true; |
||||||
|
EXPECT_EQ(json.get<bool>(), true); |
||||||
|
|
||||||
|
json = false; |
||||||
|
EXPECT_EQ(json.get<bool>(), false); |
||||||
|
|
||||||
|
json = 666; |
||||||
|
EXPECT_EQ(json.get<int>(), 666); |
||||||
|
|
||||||
|
json = 3.14; |
||||||
|
EXPECT_EQ(json.get<double>(), 3.14); |
||||||
|
|
||||||
|
std::string string; |
||||||
|
json = "my string"; |
||||||
|
json.getTo(string); |
||||||
|
EXPECT_EQ(string, "my string"); |
||||||
|
EXPECT_EQ(json.get<std::string>(), "my string"); |
||||||
|
|
||||||
|
// Array with singular type
|
||||||
|
json = { "element", "element2" }; |
||||||
|
EXPECT_EQ(json[0].get<std::string>(), "element"); |
||||||
|
EXPECT_EQ(json.at(1).get<std::string>(), "element2"); |
||||||
|
auto array = json.get<std::vector<std::string>>(); |
||||||
|
EXPECT_EQ(array.size(), 2); |
||||||
|
EXPECT_EQ(array[0], "element"); |
||||||
|
EXPECT_EQ(array[1], "element2"); |
||||||
|
|
||||||
|
// Array with multiple types
|
||||||
|
json = { "string", 3.14, true, nullptr }; |
||||||
|
EXPECT_EQ(json[0].get<std::string>(), "string"); |
||||||
|
EXPECT_EQ(json.at(1).get<double>(), 3.14); |
||||||
|
EXPECT_EQ(json[2].get<bool>(), true); |
||||||
|
EXPECT_EQ(json[3].get<std::nullptr_t>(), nullptr); |
||||||
|
auto valueArray = json.get<std::vector<Util::Json>>(); |
||||||
|
EXPECT_EQ(valueArray.size(), 4); |
||||||
|
EXPECT_EQ(valueArray[0].get<std::string>(), "string"); |
||||||
|
EXPECT_EQ(valueArray[1].get<double>(), 3.14); |
||||||
|
EXPECT_EQ(valueArray[2].get<bool>(), true); |
||||||
|
EXPECT_EQ(valueArray[3].get<std::nullptr_t>(), nullptr); |
||||||
|
|
||||||
|
// Nested Array with multiple types
|
||||||
|
json = { |
||||||
|
"value", |
||||||
|
{ |
||||||
|
"thing", |
||||||
|
666, |
||||||
|
}, |
||||||
|
{ |
||||||
|
{ |
||||||
|
3.14, |
||||||
|
}, |
||||||
|
} |
||||||
|
}; |
||||||
|
EXPECT_EQ(json[0].get<std::string>(), "value"); |
||||||
|
EXPECT_EQ(json.at(1)[0].get<std::string>(), "thing"); |
||||||
|
EXPECT_EQ(json[1].at(1).get<int>(), 666); |
||||||
|
EXPECT_EQ(json[2][0][0].get<double>(), 3.14); |
||||||
|
|
||||||
|
// Object with singular type
|
||||||
|
json = { { "name", "value" }, { "name2", "value2" } }; |
||||||
|
EXPECT_EQ(json["name"].get<std::string>(), "value"); |
||||||
|
EXPECT_EQ(json.at("name2").get<std::string>(), "value2"); |
||||||
|
auto object = json.get<std::map<std::string, std::string>>(); |
||||||
|
EXPECT_EQ(object.size(), 2); |
||||||
|
EXPECT_EQ(object["name"], "value"); |
||||||
|
EXPECT_EQ(object["name2"], "value2"); |
||||||
|
auto unorderedObject = json.get<std::unordered_map<std::string, std::string>>(); |
||||||
|
EXPECT_EQ(unorderedObject.size(), 2); |
||||||
|
EXPECT_EQ(unorderedObject["name"], "value"); |
||||||
|
EXPECT_EQ(unorderedObject["name2"], "value2"); |
||||||
|
|
||||||
|
// Object with multiple types
|
||||||
|
json = { { "name", "value" }, { "name2", 3.14 }, { "name3", true }, { "name4", nullptr } }; |
||||||
|
EXPECT_EQ(json["name"].get<std::string>(), "value"); |
||||||
|
EXPECT_EQ(json.at("name2").get<double>(), 3.14); |
||||||
|
EXPECT_EQ(json["name3"].get<bool>(), true); |
||||||
|
EXPECT_EQ(json["name4"].get<std::nullptr_t>(), nullptr); |
||||||
|
auto valueObject = json.get<std::map<std::string, Util::Json>>(); |
||||||
|
EXPECT_EQ(valueObject.size(), 4); |
||||||
|
EXPECT_EQ(valueObject["name"].get<std::string>(), "value"); |
||||||
|
EXPECT_EQ(valueObject["name2"].get<double>(), 3.14); |
||||||
|
EXPECT_EQ(valueObject["name3"].get<bool>(), true); |
||||||
|
EXPECT_EQ(valueObject["name4"].get<std::nullptr_t>(), nullptr); |
||||||
|
|
||||||
|
// Nested Object with multiple types
|
||||||
|
json = { |
||||||
|
{ |
||||||
|
"name", |
||||||
|
"value", |
||||||
|
}, |
||||||
|
{ |
||||||
|
"nest 1-deep", |
||||||
|
{ { |
||||||
|
"number", |
||||||
|
1, |
||||||
|
} }, |
||||||
|
}, |
||||||
|
{ |
||||||
|
"nest 2-deep", |
||||||
|
{ { |
||||||
|
"nest 1-deep", |
||||||
|
{ { |
||||||
|
"bool", |
||||||
|
true, |
||||||
|
} }, |
||||||
|
} }, |
||||||
|
}, |
||||||
|
}; |
||||||
|
EXPECT_EQ(json["name"].get<std::string>(), "value"); |
||||||
|
EXPECT_EQ(json["nest 1-deep"]["number"].get<int>(), 1); |
||||||
|
EXPECT_EQ(json["nest 2-deep"]["nest 1-deep"]["bool"].get<bool>(), true); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(JsonImplicitConversion) |
||||||
|
{ |
||||||
|
Util::Json array; |
||||||
|
array[0]; |
||||||
|
EXPECT_EQ(array.type(), Util::Json::Type::Array); |
||||||
|
|
||||||
|
Util::Json arrayEmplace; |
||||||
|
arrayEmplace.emplace_back("element"); |
||||||
|
arrayEmplace.emplace_back({ "nested element" }); |
||||||
|
EXPECT_EQ(arrayEmplace.type(), Util::Json::Type::Array); |
||||||
|
EXPECT_EQ(arrayEmplace[1].type(), Util::Json::Type::Array); |
||||||
|
|
||||||
|
Util::Json object; |
||||||
|
object[""]; |
||||||
|
EXPECT_EQ(object.type(), Util::Json::Type::Object); |
||||||
|
|
||||||
|
Util::Json objectEmplace; |
||||||
|
objectEmplace.emplace("name", "value"); |
||||||
|
objectEmplace.emplace("name2", { { "nested name", "value" } }); |
||||||
|
EXPECT_EQ(objectEmplace.type(), Util::Json::Type::Object); |
||||||
|
EXPECT_EQ(objectEmplace["name2"].type(), Util::Json::Type::Object); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE(JsonSerializer) |
||||||
|
{ |
||||||
|
EXPECT_EQ(serialize(""), "null"); |
||||||
|
EXPECT_EQ(serialize("null"), "null"); |
||||||
|
EXPECT_EQ(serialize("true"), "true"); |
||||||
|
EXPECT_EQ(serialize("false"), "false"); |
||||||
|
EXPECT_EQ(serialize("3.14"), "3.14"); |
||||||
|
EXPECT_EQ(serialize(R"("string")"), R"("string")"); |
||||||
|
|
||||||
|
EXPECT_EQ(serialize("\n\n\n"), "null"); |
||||||
|
EXPECT_EQ(serialize("null\n"), "null"); |
||||||
|
EXPECT_EQ(serialize("true\n"), "true"); |
||||||
|
EXPECT_EQ(serialize("false\n"), "false"); |
||||||
|
EXPECT_EQ(serialize("3.14\n"), "3.14"); |
||||||
|
// clang-format off
|
||||||
|
EXPECT_EQ(serialize(R"("string")" "\n"), R"("string")"); |
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
EXPECT_EQ(serialize("[\n\n\n]"), "[]"); |
||||||
|
EXPECT_EQ(serialize("[null]"), "[null]"); |
||||||
|
EXPECT_EQ(serialize("[true]"), "[true]"); |
||||||
|
EXPECT_EQ(serialize("[false]"), "[false]"); |
||||||
|
EXPECT_EQ(serialize("[3.14]"), "[3.14]"); |
||||||
|
EXPECT_EQ(serialize(R"(["string"])"), R"(["string"])"); |
||||||
|
|
||||||
|
EXPECT_EQ(serialize("[\n\n\n]", 4), "[\n]"); |
||||||
|
EXPECT_EQ(serialize("[null]", 4), "[\n null\n]"); |
||||||
|
EXPECT_EQ(serialize("[true]", 4), "[\n true\n]"); |
||||||
|
EXPECT_EQ(serialize("[false]", 4), "[\n false\n]"); |
||||||
|
EXPECT_EQ(serialize("[3.14]", 4), "[\n 3.14\n]"); |
||||||
|
// clang-format off
|
||||||
|
EXPECT_EQ(serialize(R"(["string"])", 4), "[\n " R"("string")" "\n]"); |
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// Check for trailing comma on last array element
|
||||||
|
EXPECT_EQ(serialize(R"([1])"), R"([1])"); |
||||||
|
EXPECT_EQ(serialize(R"([1,2])"), R"([1,2])"); |
||||||
|
EXPECT_EQ(serialize(R"([1,2,3])"), R"([1,2,3])"); |
||||||
|
|
||||||
|
// Check for trailing comma on last object member
|
||||||
|
EXPECT_EQ(serialize(R"({"n1":"v1"})"), R"({"n1":"v1"})"); |
||||||
|
EXPECT_EQ(serialize(R"({"n1":"v1", "n2":"v2"})"), R"({"n1":"v1","n2":"v2"})"); |
||||||
|
EXPECT_EQ(serialize(R"({"n1":"v1", "n2":"v2", "n3":"v3"})"), R"({"n1":"v1","n2":"v2","n3":"v3"})"); |
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
EXPECT_EQ(serialize(R"({ |
||||||
|
"object member one": [ |
||||||
|
"array element one" |
||||||
|
], |
||||||
|
"object member two": [ |
||||||
|
"array element one", |
||||||
|
"array element two" |
||||||
|
], |
||||||
|
"object member three": [ |
||||||
|
"array element one", |
||||||
|
2, |
||||||
|
3.0, |
||||||
|
4.56, |
||||||
|
true, |
||||||
|
false, |
||||||
|
null |
||||||
|
], |
||||||
|
"object member four": 3.14, |
||||||
|
"object member five": "value five", |
||||||
|
"object member six": null, |
||||||
|
"object member seven": { "no": 0 } |
||||||
|
})", 4), R"({ |
||||||
|
"object member five": "value five", |
||||||
|
"object member four": 3.14, |
||||||
|
"object member one": [ |
||||||
|
"array element one" |
||||||
|
], |
||||||
|
"object member seven": { |
||||||
|
"no": 0 |
||||||
|
}, |
||||||
|
"object member six": null, |
||||||
|
"object member three": [ |
||||||
|
"array element one", |
||||||
|
2, |
||||||
|
3, |
||||||
|
4.56, |
||||||
|
true, |
||||||
|
false, |
||||||
|
null |
||||||
|
], |
||||||
|
"object member two": [ |
||||||
|
"array element one", |
||||||
|
"array element two" |
||||||
|
] |
||||||
|
})"); |
||||||
|
// clang-format on
|
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "macro.h" |
||||||
|
#include "testcase.h" |
||||||
|
#include "testsuite.h" |
||||||
|
#include "util/shell.h" |
||||||
|
|
||||||
|
bool runShell(const char* command, std::string* output = nullptr) |
||||||
|
{ |
||||||
|
stdout = Test::TestSuite::the().outputNull(); |
||||||
|
|
||||||
|
Util::Shell $; |
||||||
|
auto exec = $(command); |
||||||
|
if (output) { |
||||||
|
*output = exec.output(); |
||||||
|
} |
||||||
|
|
||||||
|
stdout = Test::TestSuite::the().outputStd(); |
||||||
|
return !exec.status() ? true : false; |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(ShellCommand) |
||||||
|
{ |
||||||
|
// Pipe test, grep section of the echo command, return true
|
||||||
|
std::string output = ""; |
||||||
|
auto result = runShell("echo 'Hello World!' | grep --only-matching 'Hello'", &output); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(output, "Hello\n"); |
||||||
|
|
||||||
|
// Return false
|
||||||
|
result = runShell("exit 1"); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
|
||||||
|
// Return false
|
||||||
|
result = runShell("failure() { return 1; }; failure"); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
} |
@ -0,0 +1,98 @@ |
|||||||
|
#include <functional> // function |
||||||
|
#include <string> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "macro.h" |
||||||
|
#include "testcase.h" |
||||||
|
#include "testsuite.h" |
||||||
|
#include "util/system.h" |
||||||
|
|
||||||
|
bool runSystem(std::function<Util::System(Util::System&)> commands, std::string* output = nullptr, std::string* error = nullptr) |
||||||
|
{ |
||||||
|
Util::System system; |
||||||
|
auto exec = commands(system); |
||||||
|
if (output) { |
||||||
|
*output = exec.output(); |
||||||
|
} |
||||||
|
if (error) { |
||||||
|
*error = exec.error(); |
||||||
|
} |
||||||
|
|
||||||
|
return !exec.status() ? true : false; |
||||||
|
} |
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(SystemCommand) |
||||||
|
{ |
||||||
|
// Regular echo command, return true
|
||||||
|
std::string output = ""; |
||||||
|
std::string error = ""; |
||||||
|
auto result = runSystem([&](auto& $) { |
||||||
|
return $("echo Hello World!")(); |
||||||
|
}, &output, &error); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(output, "Hello World!\n"); |
||||||
|
EXPECT_EQ(error, ""); |
||||||
|
|
||||||
|
// Apend output of two echo commands, return true
|
||||||
|
output = ""; |
||||||
|
error = ""; |
||||||
|
result = runSystem([&](auto& $) { |
||||||
|
return $("echo -n Hello ") + $("echo -n World!"); |
||||||
|
}, &output, &error); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(output, "Hello World!"); |
||||||
|
EXPECT_EQ(error, ""); |
||||||
|
|
||||||
|
// Pipe test, grep section of the echo command, return true
|
||||||
|
output = ""; |
||||||
|
error = ""; |
||||||
|
result = runSystem([&](auto& $) { |
||||||
|
return $("echo Hello World!") | $("grep --only-matching Hello"); |
||||||
|
}, &output, &error); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(output, "Hello\n"); |
||||||
|
EXPECT_EQ(error, ""); |
||||||
|
|
||||||
|
// Run all commands until first false, return true
|
||||||
|
output = ""; |
||||||
|
error = ""; |
||||||
|
result = runSystem([&](auto& $) { |
||||||
|
return $("echo Hello") && $("echo World!"); |
||||||
|
}, &output, &error); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(output, "Hello\nWorld!\n"); |
||||||
|
EXPECT_EQ(error, ""); |
||||||
|
|
||||||
|
// Run all commands until first true, return true
|
||||||
|
output = ""; |
||||||
|
error = ""; |
||||||
|
result = runSystem([&](auto& $) { |
||||||
|
return $("echo Hello") || $("echo World!"); |
||||||
|
}, &output, &error); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(output, "Hello\n"); |
||||||
|
EXPECT_EQ(error, ""); |
||||||
|
|
||||||
|
// And plus pipe test, grep the middle word, return true
|
||||||
|
output = ""; |
||||||
|
error = ""; |
||||||
|
result = runSystem([&](auto& $) { |
||||||
|
return ($("echo -n one ") && $("echo -n two ") && $("echo -n three")) | $("grep --only-matching two"); |
||||||
|
}, &output, &error); |
||||||
|
EXPECT_EQ(result, true); |
||||||
|
EXPECT_EQ(output, "two\n"); |
||||||
|
EXPECT_EQ(error, ""); |
||||||
|
|
||||||
|
// FIXME waitpid does not seem to get the right exit status here
|
||||||
|
// Return false
|
||||||
|
output = ""; |
||||||
|
error = ""; |
||||||
|
result = runSystem([&](auto& $) { |
||||||
|
return $("exit 1")(); |
||||||
|
}, &output, &error); |
||||||
|
EXPECT_EQ(result, false); |
||||||
|
EXPECT_EQ(output, ""); |
||||||
|
EXPECT_EQ(error, ""); |
||||||
|
} |
Loading…
Reference in new issue