poaflocParser Of Arguments For Lines Of Commands |
git clone git://git.dimitrijedobrota.com/poafloc.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING |
commit | 5f5f0f004741116ad48e455e4326c7c39bf46e6f |
parent | e1b1257101cf4f047291177b21caaa39fccb368c |
author | Dimitrije Dobrota < mail@dimitrijedobrota.com > |
date | Sun, 25 May 2025 20:55:54 +0200 |
Clenup option validation and character mapping
M | include/poafloc/error.hpp | | | ++ ---- |
M | include/poafloc/poafloc.hpp | | | +++++++++++++++++++++++++++++++++ ------------------------------------------------- |
M | test/source/parser.cpp | | | +++++++++++++++++++++++++++++++++++ -- |
3 files changed, 94 insertions(+), 92 deletions(-)
diff --git a/ include/poafloc/error.hpp b/ include/poafloc/error.hpp
@@ -11,8 +11,8 @@
namespace poafloc
{
#define ENUM_ERROR \
invalid_char, invalid_option, missing_argument, superfluous_argument, \
unknown_option, duplicate_option
invalid_option, missing_argument, superfluous_argument, unknown_option, \
duplicate_option
BASED_DECLARE_ENUM(error_code, based::bu8, 0, ENUM_ERROR)
BASED_DEFINE_ENUM(error_code, based::bu8, 0, ENUM_ERROR)
#undef ENUM_ERROR
@@ -20,8 +20,6 @@
BASED_DEFINE_ENUM(error_code, based::bu8, 0, ENUM_ERROR)
static constexpr const char* error_get_message(error_code::enum_type error)
{
switch (error()) {
case error_code::invalid_char():
return "Invalid char in option: {}";
case error_code::invalid_option():
return "Invalid option name: {}";
case error_code::missing_argument():
diff --git a/ include/poafloc/poafloc.hpp b/ include/poafloc/poafloc.hpp
@@ -90,142 +90,108 @@
namespace detail
struct option_base
{
using size_t = std::size_t;
using value_type = std::size_t;
using optional_type = std::optional<value_type>;
static constexpr const auto sentinel = value_type {0xFFFFFFFFFFFFFFFF};
static constexpr bool is_alnum(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9');
}
static constexpr bool is_digit(char c) { return c >= '0' && c <= '9'; }
static constexpr bool is_alpha_lower(char c) { return c >= 'a' && c <= 'z'; }
static constexpr bool is_alpha_upper(char c) { return c >= 'A' && c <= 'Z'; }
static constexpr bool is_alpha(char c)
static constexpr auto convert(char chr)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
static constexpr const auto size_char = 256;
static constexpr const auto size = []()
{
std::size_t count = 0;
for (std::size_t idx = 0; idx < size_char; ++idx) {
const char c = static_cast<char>(idx);
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9'))
{
count++;
continue;
}
}
return count;
}();
static constexpr const auto mapping = []()
{
std::array<std::size_t, size_char> res = {};
std::size_t count = 0;
for (std::size_t idx = 0; idx < std::size(res); ++idx) {
const char c = static_cast<char>(idx);
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9'))
{
res[idx] = count++;
continue;
}
res[idx] = sentinel;
}
return res;
}();
using container_type = std::array<value_type, size>;
static auto convert(char chr)
{
return mapping[static_cast<container_type::size_type>(
static_cast<unsigned char>(chr)
)];
return static_cast<size_t>(static_cast<unsigned char>(chr));
}
};
class option_short : option_base
{
container_type m_opts = {};
static constexpr auto size = static_cast<size_t>(2 * 26);
std::array<value_type, size> m_opts = {};
static auto convert(char chr)
static constexpr auto map(char chr)
{
if (!is_alpha(chr)) {
throw error<error_code::invalid_char>(chr);
if (is_alpha_lower(chr)) {
return convert(chr) - convert('a');
}
return option_base::convert(chr);
return convert(chr) - convert('A') + 26; // NOLINT(*magic*)
}
[[nodiscard]] bool has(char chr) const
{
return m_opts[convert(chr)] != sentinel;
return m_opts[map(chr)] != sentinel;
}
public:
option_short() { m_opts.fill(sentinel); }
static bool is_valid(char chr) { return option_base::is_alpha(chr); }
static constexpr bool is_valid(char chr)
{
return is_alpha_lower(chr) || is_alpha_upper(chr);
}
[[nodiscard]] bool set(char chr, value_type idx)
{
if (!is_valid(chr)) {
throw error<error_code::invalid_option>(chr);
}
if (has(chr)) {
return false;
}
m_opts[convert(chr)] = idx;
m_opts[map(chr)] = idx;
return true;
}
[[nodiscard]] optional_type get(char chr) const
{
if (!is_valid(chr)) {
throw error<error_code::invalid_option>(chr);
}
if (!has(chr)) {
return {};
}
return m_opts[convert(chr)];
return m_opts[map(chr)];
}
};
class option_long : option_base
{
static auto convert(char chr)
{
if (!is_alnum(chr)) {
throw error<error_code::invalid_char>(chr);
}
return option_base::convert(chr);
}
class trie_t
{
static constexpr auto size = static_cast<size_t>(26ULL + 10ULL);
std::array<std::unique_ptr<trie_t>, size> m_children = {};
value_type m_value = sentinel;
std::size_t m_count = 0;
bool m_terminal = false;
static constexpr auto map(char chr)
{
if (is_alpha_lower(chr)) {
return convert(chr) - convert('a');
}
return convert(chr) - convert('0') + 26; // NOLINT(*magic*)
}
public:
static bool set(trie_t& trie, std::string_view key, value_type value)
{
trie_t* crnt = ≜
for (const auto c : key) {
crnt->m_count++;
if (!crnt->m_terminal) {
crnt->m_value = value;
}
const auto idx = convert(c);
const auto idx = map(c);
if (crnt->m_children[idx] == nullptr) {
crnt->m_children[idx] = std::make_unique<trie_t>();
}
@@ -246,7 +212,7 @@
class option_long : option_base
const trie_t* crnt = ≜
for (const auto c : key) {
const auto idx = convert(c);
const auto idx = map(c);
if (crnt->m_children[idx] == nullptr) {
return {};
}
@@ -264,18 +230,33 @@
class option_long : option_base
trie_t m_trie;
public:
static bool is_valid(std::string_view opt)
static constexpr bool is_valid(std::string_view opt)
{
return std::ranges::all_of(opt, option_base::is_alnum);
return is_alpha_lower(opt.front())
&& std::ranges::all_of(
opt,
[](const char chr)
{
return is_alpha_lower(chr) || is_digit(chr);
}
);
}
[[nodiscard]] bool set(std::string_view opt, value_type idx)
{
if (!is_valid(opt)) {
throw error<error_code::invalid_option>(opt);
}
return trie_t::set(m_trie, opt, idx);
}
[[nodiscard]] optional_type get(std::string_view opt) const
{
if (!is_valid(opt)) {
throw error<error_code::invalid_option>(opt);
}
return trie_t::get(m_trie, opt);
}
};
@@ -299,21 +280,11 @@
class parser
while (std::getline(istr, str, ' ')) {
if (std::size(str) == 1) {
const auto& opt = str[0];
if (!detail::option_short::is_valid(opt)) {
throw error<error_code::invalid_option>(opt);
}
if (!m_opt_short.set(opt, std::size(m_options))) {
throw error<error_code::duplicate_option>(opt);
}
} else {
const auto& opt = str;
if (!detail::option_long::is_valid(opt)) {
throw error<error_code::invalid_option>(opt);
}
if (!m_opt_long.set(opt, std::size(m_options))) {
throw error<error_code::duplicate_option>(opt);
}
diff --git a/ test/source/parser.cpp b/ test/source/parser.cpp
@@ -21,7 +21,40 @@
TEST_CASE("invalid", "[poafloc/parser]")
int value;
};
SECTION("duplicate short")
SECTION("short number")
{
const auto construct = []()
{
return parser<arguments> {
option {"1", &arguments::flag},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);
}
SECTION("long upper")
{
const auto construct = []()
{
return parser<arguments> {
option {"FLAG", &arguments::flag},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);
}
SECTION("long number start")
{
const auto construct = []()
{
return parser<arguments> {
option {"1value", &arguments::value},
};
};
REQUIRE_THROWS_AS(construct(), error<error_code::invalid_option>);
}
SECTION("short duplicate")
{
const auto construct = []()
{
@@ -33,7 +66,7 @@
TEST_CASE("invalid", "[poafloc/parser]")
REQUIRE_THROWS_AS(construct(), error<error_code::duplicate_option>);
}
SECTION("duplicate long")
SECTION("long duplicate")
{
const auto construct = []()
{