Brett 2023-08-01 13:43:00 -04:00
parent e702a651df
commit a0492df393
2 changed files with 219 additions and 88 deletions

View File

@ -12,10 +12,11 @@
#include <initializer_list> #include <initializer_list>
#include <optional> #include <optional>
#include <blt/std/hashmap.h> #include <blt/std/hashmap.h>
#include <variant>
namespace blt::parser { namespace blt::parser {
enum class arg_action { enum class arg_action_t {
STORE, STORE,
STORE_CONST, STORE_CONST,
STORE_TRUE, STORE_TRUE,
@ -28,14 +29,13 @@ namespace blt::parser {
EXTEND EXTEND
}; };
class arg_t { enum class arg_result_t {
private: BOOL,
VALUE,
public: VECTOR
}; };
class arg_vector { class arg_vector_t {
private: private:
std::vector<std::string> names; std::vector<std::string> names;
std::vector<std::string> flags; std::vector<std::string> flags;
@ -43,23 +43,23 @@ namespace blt::parser {
void insertAndSort(const std::string& str); void insertAndSort(const std::string& str);
public: public:
arg_vector() = default; arg_vector_t() = default;
arg_vector(const std::vector<std::string>& args); arg_vector_t(const std::vector<std::string>& args);
arg_vector(std::initializer_list<std::string> args); arg_vector_t(std::initializer_list<std::string> args);
arg_vector(const std::string& arg); arg_vector_t(const std::string& arg);
arg_vector(const char* arg); arg_vector_t(const char* arg);
arg_vector& operator=(const std::string& arg); arg_vector_t& operator=(const std::string& arg);
arg_vector& operator=(const char* arg); arg_vector_t& operator=(const char* arg);
arg_vector& operator=(std::initializer_list<std::string>& args); arg_vector_t& operator=(std::initializer_list<std::string>& args);
arg_vector& operator=(std::vector<std::string>& args); arg_vector_t& operator=(std::vector<std::string>& args);
[[nodiscard]] inline std::vector<std::string>& getNames() { [[nodiscard]] inline std::vector<std::string>& getNames() {
return names; return names;
@ -76,44 +76,84 @@ namespace blt::parser {
[[nodiscard]] inline const std::vector<std::string>& getFlags() const { [[nodiscard]] inline const std::vector<std::string>& getFlags() const {
return flags; return flags;
} }
struct equals {
bool operator()(const arg_vector_t& a1, const arg_vector_t& a2) const {
// arg cannot have both
if (!a1.names.empty()) {
// match all pos arg
return std::ranges::all_of(a1.names, [&a2](const auto& n) -> bool {
if (std::find(a2.names.begin(), a2.names.end(), n) == a2.names.end())
return false;
return true;
});
} else {
// match any flag (--foo or -f)
return std::ranges::all_of(a1.flags, [&a2](const auto& f) -> bool {
if (std::find(a2.flags.begin(), a2.flags.end(), f) != a2.flags.end())
return true;
return false;
});
}
}
}; };
class arg_nargs { struct hash {
size_t operator()(const arg_vector_t& a) const {
size_t v = 0;
std::hash<std::string> hash;
for (const auto& n : a.names) {
v >>= 8;
v += hash(n);
}
for (const auto& f : a.flags) {
v >>= 8;
v += hash(f);
}
return v;
}
};
};
class arg_nargs_t {
private: private:
friend class argparse;
static constexpr int UNKNOWN = 0x1; static constexpr int UNKNOWN = 0x1;
static constexpr int ALL = 0x2; static constexpr int ALL = 0x2;
static constexpr int ALL_REQUIRED = 0x4; static constexpr int ALL_REQUIRED = 0x4;
// 0 means default behaviour (consume 1 if presented otherwise ignore)
int args = 0; int args = 0;
// 0 indicates args is used
int flags = 0; int flags = 0;
void decode(char c); void decode(char c);
public: public:
arg_nargs() = default; arg_nargs_t() = default;
arg_nargs(int args): args(args) {} arg_nargs_t(int args): args(args) {}
arg_nargs(char c); arg_nargs_t(char c);
arg_nargs(std::string s); arg_nargs_t(std::string s);
arg_nargs(const char* s); arg_nargs_t(const char* s);
arg_nargs& operator=(const std::string& s); arg_nargs_t& operator=(const std::string& s);
arg_nargs& operator=(const char* s); arg_nargs_t& operator=(const char* s);
arg_nargs& operator=(char c); arg_nargs_t& operator=(char c);
arg_nargs& operator=(int args); arg_nargs_t& operator=(int args);
}; };
struct arg_properties { struct arg_properties_t {
private: private:
public: public:
arg_vector a_flags; arg_vector_t a_flags;
arg_action a_action = arg_action::STORE; arg_action_t a_action = arg_action_t::STORE;
arg_nargs a_nargs = 0; arg_nargs_t a_nargs = 0;
std::optional<std::string> a_const{}; std::optional<std::string> a_const{};
std::string a_default{}; std::string a_default{};
std::string a_def{}; std::string a_def{};
@ -122,22 +162,24 @@ namespace blt::parser {
bool a_required = false; bool a_required = false;
}; };
class arg_tokenizer { class arg_tokenizer_t {
private: private:
static constexpr char FLAG = '-'; static constexpr char FLAG = '-';
std::vector<std::string> args; std::vector<std::string> args;
size_t nextIndex = 0; size_t nextIndex = 0;
inline const std::string& get(size_t i){ inline const std::string& get(size_t i) {
return args[i]; return args[i];
} }
inline bool hasNext(size_t i){
return (size_t)i < args.size();
}
public:
arg_tokenizer() = default;
arg_tokenizer(size_t argc, const char** argv); inline bool hasNext(size_t i) {
return (size_t) i < args.size();
}
public:
arg_tokenizer_t() = default;
arg_tokenizer_t(size_t argc, const char** argv);
inline void forward() { inline void forward() {
nextIndex++; nextIndex++;
@ -155,7 +197,7 @@ namespace blt::parser {
return hasNext(nextIndex); return hasNext(nextIndex);
} }
inline bool isFlag(size_t i){ inline bool isFlag(size_t i) {
return get(i).starts_with(FLAG); return get(i).starts_with(FLAG);
} }
@ -165,27 +207,41 @@ namespace blt::parser {
}; };
class argparse { class argparse {
public:
typedef std::variant<std::string, bool, std::vector<std::string>> arg_data_t;
private: private:
arg_tokenizer tokenizer;
struct { struct {
std::vector<arg_properties> argStorage; friend argparse;
HASHMAP<std::string, arg_properties*> flagAssociations; private:
std::vector<std::pair<std::string, arg_properties*>> nameAssociations; std::vector<arg_properties_t> arg_storage;
public:
std::vector<std::pair<std::string, arg_properties_t*>> name_associations;
HASHMAP<std::string, arg_properties_t*> flag_associations;
HASHSET<arg_vector_t, arg_vector_t::hash, arg_vector_t::equals> required_vars;
} user_args; } user_args;
struct arg_results { struct arg_results {
std::string programName; friend argparse;
HASHMAP<std::string, std::string> positionalArgs; private:
HASHSET<arg_vector_t, arg_vector_t::hash, arg_vector_t::equals> found_required;
public:
std::string program_name;
HASHMAP <arg_vector_t, arg_data_t, arg_vector_t::hash, arg_vector_t::equals> positional_args;
HASHMAP <arg_vector_t, arg_data_t, arg_vector_t::hash, arg_vector_t::equals> flag_args;
} loaded_args; } loaded_args;
private:
static bool validateArgument(const arg_properties& args); static bool validateArgument(const arg_properties_t& args);
bool consumeFlagArguments(arg_tokenizer_t& arg_tokenizer, const arg_properties_t& properties, arg_data_t& arg_data);
void handlePositionalArgument(arg_tokenizer_t& arg_tokenizer, size_t& last_pos);
void handleFlagArgument(arg_tokenizer_t& arg_tokenizer);
public: public:
argparse() = default; argparse() = default;
void addArgument(const arg_properties& args); void addArgument(const arg_properties_t& args);
const arg_results& parse_args(int argc, const char** argv); const arg_results& parse_args(int argc, const char** argv);
void printHelp();
}; };
} }

View File

@ -8,140 +8,215 @@
namespace blt::parser { namespace blt::parser {
arg_vector::arg_vector(const std::vector<std::string>& args) { arg_vector_t::arg_vector_t(const std::vector<std::string>& args) {
for (auto& arg : args) for (auto& arg : args)
insertAndSort(arg); insertAndSort(arg);
} }
arg_vector::arg_vector(std::initializer_list<std::string> args) { arg_vector_t::arg_vector_t(std::initializer_list<std::string> args) {
for (auto& arg : args) for (auto& arg : args)
insertAndSort(arg); insertAndSort(arg);
} }
arg_vector::arg_vector(const std::string& arg) { arg_vector_t::arg_vector_t(const std::string& arg) {
insertAndSort(arg); insertAndSort(arg);
} }
arg_vector::arg_vector(const char* arg) { arg_vector_t::arg_vector_t(const char* arg) {
insertAndSort(arg); insertAndSort(arg);
} }
arg_vector& arg_vector::operator=(const std::string& arg) { arg_vector_t& arg_vector_t::operator=(const std::string& arg) {
insertAndSort(arg); insertAndSort(arg);
return *this; return *this;
} }
arg_vector& arg_vector::operator=(const char* arg) { arg_vector_t& arg_vector_t::operator=(const char* arg) {
insertAndSort(arg); insertAndSort(arg);
return *this; return *this;
} }
arg_vector& arg_vector::operator=(std::initializer_list<std::string>& args) { arg_vector_t& arg_vector_t::operator=(std::initializer_list<std::string>& args) {
for (auto& arg : args) for (auto& arg : args)
insertAndSort(arg); insertAndSort(arg);
return *this; return *this;
} }
arg_vector& arg_vector::operator=(std::vector<std::string>& args) { arg_vector_t& arg_vector_t::operator=(std::vector<std::string>& args) {
for (auto& arg : args) for (auto& arg : args)
insertAndSort(arg); insertAndSort(arg);
return *this; return *this;
} }
void arg_vector::insertAndSort(const std::string& str) { void arg_vector_t::insertAndSort(const std::string& str) {
if (str.starts_with('-')) if (str.starts_with('-'))
flags.push_back(str); flags.push_back(str);
else else
names.push_back(str); names.push_back(str);
} }
arg_nargs::arg_nargs(char c) { arg_nargs_t::arg_nargs_t(char c) {
decode(c); decode(c);
} }
arg_nargs::arg_nargs(std::string s) { arg_nargs_t::arg_nargs_t(std::string s) {
decode(s[0]); decode(s[0]);
} }
arg_nargs::arg_nargs(const char* s) { arg_nargs_t::arg_nargs_t(const char* s) {
decode(*s); decode(*s);
} }
arg_nargs& arg_nargs::operator=(const std::string& s) { arg_nargs_t& arg_nargs_t::operator=(const std::string& s) {
decode(s[0]); decode(s[0]);
return *this; return *this;
} }
arg_nargs& arg_nargs::operator=(char c) { arg_nargs_t& arg_nargs_t::operator=(char c) {
decode(c); decode(c);
return *this; return *this;
} }
arg_nargs& arg_nargs::operator=(int a) { arg_nargs_t& arg_nargs_t::operator=(int a) {
args = a; args = a;
return *this; return *this;
} }
arg_nargs& arg_nargs::operator=(const char* s) { arg_nargs_t& arg_nargs_t::operator=(const char* s) {
decode(*s); decode(*s);
return *this; return *this;
} }
void arg_nargs::decode(char c) { void arg_nargs_t::decode(char c) {
if (c == '?') if (c == '?')
flags |= UNKNOWN; flags = UNKNOWN;
if (c == '+') else if (c == '+')
flags |= ALL_REQUIRED; flags = ALL_REQUIRED;
if (c == '*') else if (c == '*')
flags |= ALL; flags = ALL;
else
flags = 0;
} }
arg_tokenizer::arg_tokenizer(size_t argc, const char** argv) { arg_tokenizer_t::arg_tokenizer_t(size_t argc, const char** argv) {
for (size_t i = 0; i < argc; i++) for (size_t i = 0; i < argc; i++)
args.emplace_back(argv[i]); args.emplace_back(argv[i]);
} }
bool argparse::validateArgument(const arg_properties& args) { bool argparse::validateArgument(const arg_properties_t& args) {
return !args.a_flags.getFlags().empty() ^ !args.a_flags.getNames().empty(); return !args.a_flags.getFlags().empty() ^ !args.a_flags.getNames().empty();
} }
void argparse::addArgument(const arg_properties& args) { void argparse::addArgument(const arg_properties_t& args) {
if (!validateArgument(args)) { if (!validateArgument(args)) {
BLT_WARN("Argument contains both flags and positional names, this is not allowed!"); BLT_WARN("Argument contains both flags and positional names, this is not allowed!");
BLT_WARN("(Discarding argument)"); BLT_WARN("(Discarding argument)");
return; return;
} }
// store one copy of the properties (arg properties could in theory be very large!) // store one copy of the properties (arg properties could in theory be very large!)
auto pos = user_args.argStorage.size(); auto pos = user_args.arg_storage.size();
user_args.argStorage.push_back(args); user_args.arg_storage.push_back(args);
auto& arg = user_args.argStorage[pos]; auto& arg = user_args.arg_storage[pos];
// associate the arg properties per name and flag to allow for quick access when parsing // associate the arg properties per name and flag to allow for quick access when parsing
auto& names = args.a_flags.getNames(); auto& names = args.a_flags.getNames();
for (const auto& name : names) for (const auto& name : names)
user_args.nameAssociations.emplace_back(name, &arg); user_args.name_associations.emplace_back(name, &arg);
auto& flags = args.a_flags.getFlags(); auto& flags = args.a_flags.getFlags();
for (const auto& flag : flags) for (const auto& flag : flags)
user_args.flagAssociations[flag] = &arg; user_args.flag_associations[flag] = &arg;
if (args.a_required)
user_args.required_vars.insert(args.a_flags);
} }
const argparse::arg_results& argparse::parse_args(int argc, const char** argv) { const argparse::arg_results& argparse::parse_args(int argc, const char** argv) {
loaded_args = {}; loaded_args = {};
arg_tokenizer asToken(argc, argv); arg_tokenizer_t arg_tokenizer(argc, argv);
loaded_args.programName = asToken.next(); loaded_args.program_name = arg_tokenizer.next();
BLT_TRACE("Loading args for %s", loaded_args.programName.c_str()); BLT_TRACE("Loading args for %s", loaded_args.program_name.c_str());
size_t lastPositional; size_t last_positional;
while (asToken.hasNext()) { while (arg_tokenizer.hasNext()) {
if (!asToken.isFlag()){ // a token isn't a flag it must be a positional arg as flags will consume nargs
loaded_args.positionalArgs[user_args.nameAssociations[lastPositional].first] = asToken.next(); if (!arg_tokenizer.isFlag()){
handlePositionalArgument(arg_tokenizer, last_positional);
continue; continue;
} }
handleFlagArgument(arg_tokenizer);
} }
return loaded_args; return loaded_args;
} }
void argparse::printHelp() {
}
void argparse::handlePositionalArgument(arg_tokenizer_t& arg_tokenizer, size_t& last_pos) {
// TODO:
loaded_args.positional_args[user_args.name_associations[last_pos++].first] = arg_tokenizer.next();
}
void argparse::handleFlagArgument(arg_tokenizer_t& arg_tokenizer) {
// token is a flag, find out special information about it
auto flag = arg_tokenizer.next();
auto loc = user_args.flag_associations.find(flag);
if (loc == user_args.flag_associations.end()){
BLT_WARN("Flag '%s' not an option!");
printHelp();
return;
}
auto flag_properties = loc->second;
arg_data_t data;
switch(flag_properties->a_action){
case arg_action_t::HELP:
printHelp();
break;
case arg_action_t::STORE:
break;
case arg_action_t::STORE_CONST:
data = flag_properties->a_const;
break;
case arg_action_t::STORE_FALSE:
data = false;
break;
case arg_action_t::STORE_TRUE:
data = true;
break;
case arg_action_t::APPEND:
if (holds_alternative<std::vector<std::string>>(data)){
auto& l = get<std::vector<std::string>>(data);
} else
BLT_WARN("Requested append to data which doesn't contain a list!");
break;
case arg_action_t::APPEND_CONST:
break;
case arg_action_t::COUNT:
break;
case arg_action_t::EXTEND:
break;
case arg_action_t::VERSION:
break;
}
loaded_args.flag_args[flag_properties->a_flags] = data;
}
bool argparse::consumeFlagArguments(arg_tokenizer_t& arg_tokenizer, const arg_properties_t& properties, arg_data_t& arg_data) {
// since the function is called after the flag is consumed, isFlag()/ will return information about the next arg
if (properties.a_nargs.flags) {
} else {
if (arg_tokenizer.isFlag())
return false;
}
return false;
}
} }