Skip to content

Commit

Permalink
add Arguments::get_multi
Browse files Browse the repository at this point in the history
  • Loading branch information
fuzziqersoftware committed Nov 25, 2023
1 parent 6ec0926 commit 23c1e67
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 33 deletions.
28 changes: 19 additions & 9 deletions src/Arguments.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,36 @@ void Arguments::assert_none_unused() const {
throw invalid_argument(string_printf("(@%zu) excess argument", z));
}
}
for (const auto& it : this->named) {
if (!it.second.used) {
throw invalid_argument(string_printf("(--%s) excess argument", it.first.c_str()));
for (const auto& named_it : this->named) {
size_t index = 0;
for (const auto& instance_it : named_it.second) {
if (!instance_it.used) {
throw invalid_argument(string_printf("(--%s#%zu) excess argument", named_it.first.c_str(), index));
}
index++;
}
}
}

void Arguments::parse(vector<string>&& args) {
for (string& arg : args) {
if (!arg.empty() && (arg[0] == '-')) {
if (arg[1] == '-') {
size_t equal_pos = arg.find('=', 2);
if (equal_pos != string::npos) {
this->named.emplace(arg.substr(2, equal_pos - 2), arg.substr(equal_pos + 1));
if (arg.size() == 1) {
this->positional.emplace_back(std::move(arg));
} else if (arg[1] == '-') {
if (arg.size() == 2) {
this->positional.emplace_back(std::move(arg));
} else {
this->named.emplace(arg.substr(2), "");
size_t equal_pos = arg.find('=', 2);
if (equal_pos != string::npos) {
this->named[arg.substr(2, equal_pos - 2)].emplace_back(arg.substr(equal_pos + 1));
} else {
this->named[arg.substr(2)].emplace_back("");
}
}
} else {
for (size_t z = 1; arg[z] != '\0'; z++) {
this->named.emplace(arg.substr(z, 1), "");
this->named[arg.substr(z, 1)].emplace_back("");
}
}
} else {
Expand Down
93 changes: 69 additions & 24 deletions src/Arguments.hh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

using namespace std;

struct Arguments {
class Arguments {
public:
Arguments(const char* const* argv, size_t num_args);
Arguments(const std::vector<std::string>& args);
Arguments(std::vector<std::string>&& args);
Expand All @@ -19,23 +20,41 @@ struct Arguments {

// Strings
static const std::string empty_string;
static const std::vector<std::string> empty_string_vec;

template <typename RetT>
requires(std::is_same_v<RetT, std::string>)
std::vector<RetT> get_multi(const string& name) {
std::vector<std::string> ret;
for (auto& value : this->get_values_multi(name)) {
ret.emplace_back(value.text);
value.used = true;
}
return ret;
}
template <typename RetT>
requires(std::is_same_v<RetT, std::string>)
const RetT& get(const string& name, bool throw_if_missing = false) {
try {
auto& arg = this->named.at(name);
arg.used = true;
return arg.text;
auto& values = this->named.at(name);
if (values.empty()) {
// This should be impossible; there should just be no key in this->named
// if the argument wasn't specified at all
throw std::logic_error(Arguments::exc_prefix(name) + "argument exists but value is missing");
}
if (values.size() > 1) {
throw std::out_of_range(Arguments::exc_prefix(name) + "argument has multiple values");
}
values[0].used = true;
return values[0].text;
} catch (const out_of_range&) {
if (throw_if_missing) {
throw out_of_range(Arguments::exc_prefix(name) + "argument is missing");
throw std::out_of_range(Arguments::exc_prefix(name) + "argument is missing");
} else {
return empty_string;
}
}
}

template <typename RetT>
requires(std::is_same_v<RetT, std::string>)
const RetT& get(size_t position, bool throw_if_missing = true) {
Expand All @@ -45,7 +64,7 @@ struct Arguments {
return arg.text;
} catch (const out_of_range&) {
if (throw_if_missing) {
throw out_of_range(Arguments::exc_prefix(position) + "argument is missing");
throw std::out_of_range(Arguments::exc_prefix(position) + "argument is missing");
} else {
return empty_string;
}
Expand All @@ -71,13 +90,21 @@ struct Arguments {
DECIMAL,
OCTAL,
};

template <typename RetT>
requires(std::is_integral_v<RetT>)
std::vector<RetT> get_multi(const std::string& name, IntFormat format = IntFormat::DEFAULT) {
std::vector<RetT> ret;
for (auto& v : this->get_values_multi(name)) {
ret.emplace_back(this->parse_int<RetT>(name, v.text, format));
v.used = true;
}
return ret;
}
template <typename RetT, typename IdentT>
requires(std::is_integral_v<RetT>)
RetT get(const IdentT& id, IntFormat format = IntFormat::DEFAULT) {
return this->parse_int<RetT>(id, this->get<std::string>(id, true), format);
}

template <typename RetT, typename IdentT>
requires(std::is_integral_v<RetT>)
RetT get(const IdentT& id, RetT default_value, IntFormat format = IntFormat::DEFAULT) {
Expand All @@ -89,6 +116,16 @@ struct Arguments {
}

// Floats
template <typename RetT>
requires(std::is_floating_point_v<RetT>)
std::vector<RetT> get_multi(const std::string& name) {
std::vector<RetT> ret;
for (auto& v : this->get_values_multi(name)) {
ret.emplace_back(this->parse_float<RetT>(name, v.text));
v.used = true;
}
return ret;
}
template <typename RetT, typename IdentT>
requires(std::is_floating_point_v<RetT>)
RetT get(const IdentT& id, std::optional<RetT> default_value = std::nullopt) {
Expand All @@ -105,19 +142,28 @@ struct Arguments {
return this->parse_float<RetT>(id, *text);
}

private:
struct ArgText {
std::string text;
bool used;

explicit ArgText(std::string&& text);
};

std::unordered_map<std::string, ArgText> named;
std::unordered_map<std::string, std::vector<ArgText>> named;
std::vector<ArgText> positional;

private:
void parse(std::vector<std::string>&& args);

inline std::vector<ArgText>& get_values_multi(const string& name) {
try {
return this->named.at(name);
} catch (const out_of_range&) {
static std::vector<ArgText> empty_vec;
return empty_vec;
}
}

static inline std::string exc_prefix(const std::string& name) {
string ret = "(";
ret += name;
Expand All @@ -137,44 +183,43 @@ private:
case IntFormat::DEFAULT:
v = strtoull(text.c_str(), &conversion_end, 0);
if (conversion_end == text.c_str()) {
throw invalid_argument(exc_prefix(id) + "value is not an integer");
throw std::invalid_argument(exc_prefix(id) + "value is not an integer");
}
break;
case IntFormat::HEX:
v = strtoull(text.c_str(), &conversion_end, 16);
if (conversion_end == text.c_str()) {
throw invalid_argument(exc_prefix(id) + "value is not a hex integer");
throw std::invalid_argument(exc_prefix(id) + "value is not a hex integer");
}
break;
case IntFormat::DECIMAL:
v = strtoull(text.c_str(), &conversion_end, 10);
if (conversion_end == text.c_str()) {
throw invalid_argument(exc_prefix(id) + "value is not a decimal integer");
throw std::invalid_argument(exc_prefix(id) + "value is not a decimal integer");
}
break;
case IntFormat::OCTAL:
v = strtoull(text.c_str(), &conversion_end, 8);
if (conversion_end == text.c_str()) {
throw invalid_argument(exc_prefix(id) + "value is not an octal integer");
throw std::invalid_argument(exc_prefix(id) + "value is not an octal integer");
}
break;
default:
throw logic_error(exc_prefix(id) + "invalid integer format");
throw std::logic_error(exc_prefix(id) + "invalid integer format");
}
if (*conversion_end != '\0') {
throw invalid_argument(exc_prefix(id) + "extra data after integer");
throw std::invalid_argument(exc_prefix(id) + "extra data after integer");
}

uint64_t uv = static_cast<uint64_t>(v);
if (std::is_unsigned_v<RetT>) {
uint64_t uv = static_cast<uint64_t>(v);
if (uv & (~mask_for_type<RetT>)) {
throw invalid_argument(exc_prefix(id) + "unsigned value out of range");
throw std::invalid_argument(exc_prefix(id) + "unsigned value out of range");
}
return uv;
} else {
if ((v < sign_extend<int64_t, RetT>(msb_for_type<RetT>)) ||
(v > sign_extend<int64_t, RetT>(~msb_for_type<RetT>))) {
throw invalid_argument(exc_prefix(id) + "signed value out of range");
if (((uv & (~mask_for_type<RetT>)) != 0) && ((uv & (~mask_for_type<RetT>)) != (~mask_for_type<RetT>))) {
throw std::invalid_argument(exc_prefix(id) + "signed value out of range");
}
return v;
}
Expand All @@ -186,10 +231,10 @@ private:
char* conversion_end;
double v = strtod(text.c_str(), &conversion_end);
if (conversion_end == text.c_str()) {
throw invalid_argument(exc_prefix(id) + "value is not a number");
throw std::invalid_argument(exc_prefix(id) + "value is not a number");
}
if (*conversion_end != '\0') {
throw invalid_argument(exc_prefix(id) + "extra data after number");
throw std::invalid_argument(exc_prefix(id) + "extra data after number");
}
return v;
}
Expand Down

0 comments on commit 23c1e67

Please sign in to comment.