commit 166d7e74f5d7a5c3adc525595375c5fe34ab36b6
parent 36fd7278d9c884f92d36fa9a886e0c420dc36551
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date: Thu, 6 Jun 2024 04:00:57 +0200
Add uncomplete matching to long options
* parse is a member function instread of static
* cleaner error handling and messaging
Diffstat:
M | Makefile | | | 2 | +- |
M | args.hpp | | | 140 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- |
M | demo.cpp | | | 14 | +++++++++----- |
3 files changed, 110 insertions(+), 46 deletions(-)
diff --git a/Makefile b/Makefile
@@ -2,7 +2,7 @@
all: demo
demo: demo.cpp args.hpp
- g++ -o $@ $< -std=c++20 -Wall -Werror
+ g++ -o $@ $< -std=c++20 -Wall -Werror -ggdb
clean:
rm -rf demo
diff --git a/args.hpp b/args.hpp
@@ -1,7 +1,9 @@
#ifndef ARGS_HPP
#define ARGS_HPP
+#include <cstdint>
#include <cstring>
+#include <exception>
#include <format>
#include <iostream>
@@ -20,70 +22,128 @@ class Parser {
const parse_f parser;
};
- static int parse(argp_t *argp, int argc, char *argv[], void *input) {
- for (int i = 1; i < argc; i++) {
- bool opt_short = false, opt_long = false;
+ Parser(const argp_t *argp) : argp(argp) {
+ for (int i = 0; argp->options[i].key; i++) {
+ const auto &option = argp->options[i];
+ const uint8_t idx = option.key - 'a';
+ if (options[idx]) {
+ std::cerr << std::format("duplicate key {}\n", option.key);
+ throw new std::runtime_error("duplicate key");
+ }
+
+ options[idx] = &option;
+ if (option.name) trie.insert(option.name, option.key);
+ }
+ }
+ int parse(int argc, char *argv[], void *input) {
+ const char *arg = nullptr;
+ char key;
+ int i;
+
+ for (i = 1; i < argc; i++) {
if (argv[i][0] != '-') {
argp->parser(-1, argv[i], input);
continue;
}
- if (argv[i][1] != '-') opt_short = true;
- else opt_long = true;
-
- const char *opt = argv[i] + opt_long + 1;
+ if (argv[i][1] != '-') {
+ const char *opt = argv[i] + 1;
+ key = opt[0];
- bool found = false;
- for (int j = 0; argp->options[j].key; j++) {
- const auto &option = argp->options[j];
- const char *arg = 0;
+ const auto *option = options[key - 'a'];
+ if (!option) goto unknown;
- if (opt_short && opt[0] != option.key) continue;
+ if (option->arg) {
+ if (i == argc) goto missing;
+ arg = argv[++i];
+ }
+ } else {
+ const char *opt = argv[i] + 2;
+ const auto eq = std::strchr(opt, '=');
- if (opt_long) {
- if(!option.name) continue;
+ key = trie.get(!eq ? opt : std::string(opt, eq - opt));
- const auto n = std::strlen(option.name);
- if (std::strncmp(argv[i] + 2, option.name, n)) continue;
+ if (!key) goto unknown;
- if (opt[n] == '=') {
- if (!option.arg) {
- std::cerr << "option doesn't require a value\n";
- exit(1);
- }
+ const auto *option = options[key - 'a'];
- arg = opt + n + 1;
- }
+ if (eq) {
+ if (!option->arg) goto excess;
+ arg = eq + 1;
+ } else if (option->arg) {
+ if (i == argc) goto missing;
+ arg = argv[++i];
}
+ }
- if (option.arg && !arg) {
- if (i == argc) {
- std::cerr << "option missing a value\n";
- exit(1);
- }
+ argp->parser(key, arg, input);
+ }
- arg = argv[++i];
- }
+ return 0;
- argp->parser(option.key, arg, input);
+ unknown:
+ std::cerr << std::format("unknown option {}\n", argv[i]);
+ return 1;
- found = true;
- break;
+ missing:
+ std::cerr << std::format("option {} missing a value\n", argv[i]);
+ return 2;
+
+ excess:
+ std::cerr << std::format("option {} don't require a value\n", argv[i]);
+ return 3;
+ }
+
+ private:
+ class trie_t {
+ public:
+ ~trie_t() noexcept {
+ for (uint8_t i = 0; i < 26; i++) {
+ delete children[i];
}
+ }
+
+ void insert(const std::string &option, char key) {
+ trie_t *crnt = this;
- if (found) continue;
+ for (const char c : option) {
+ crnt->count++;
+ if (!crnt->terminal) crnt->key = key;
- if (argv[i][0] == '-') {
- std::cerr << std::format("unknown option {}\n", argv[i]);
- return 1;
+ const uint8_t idx = c - 'a';
+ if (!crnt->children[idx]) crnt->children[idx] = new trie_t();
+ crnt = crnt->children[idx];
}
+
+ crnt->terminal = true;
+ crnt->key = key;
}
- return 0;
- }
+ char get(const std::string &option) const {
+ const trie_t *crnt = this;
- private:
+ for (const char c : option) {
+ const uint8_t idx = c - 'a';
+ if (!crnt->children[idx]) return 0;
+ crnt = crnt->children[idx];
+ }
+
+ if (!crnt->terminal && crnt->count > 1) return 0;
+ return crnt->key;
+ }
+
+ private:
+ trie_t *children[26] = {0};
+ uint8_t count = 0;
+ char key = 0;
+ bool terminal = false;
+ };
+
+ const argp_t *argp;
+
+ const option_t *options[26] = {0};
+ trie_t trie;
};
#endif
diff --git a/demo.cpp b/demo.cpp
@@ -3,13 +3,11 @@
#include <cstdint>
#include <vector>
-void error(const std::string &message) {
- std::cerr << message << std::endl;
- exit(1);
-}
+void error(const std::string &message) { std::cerr << message << std::endl; }
struct arguments_t {
const char *output_file = 0;
+ const char *input_file = 0;
bool debug = 0;
bool hex = 0;
@@ -32,6 +30,7 @@ int parse_opt(int key, const char *arg, void *input) {
arguments->relocatable = true;
break;
case 'o': arguments->output_file = arg; break;
+ case 'i': arguments->input_file = arg; break;
default: arguments->args.push_back(arg);
}
@@ -41,6 +40,7 @@ int parse_opt(int key, const char *arg, void *input) {
// clang-format off
static const Parser::option_t options[] = {
{ 0, 'o', "file"},
+ { "input", 'i', "file"},
{ "debug", 'd', 0},
{ "hex", 'h', 0},
{"relocatable", 'r', 0},
@@ -50,14 +50,18 @@ static const Parser::option_t options[] = {
int main(int argc, char *argv[]) {
Parser::argp_t argp = {options, parse_opt};
+ Parser parser(&argp);
+
arguments_t arguments;
- if (Parser::parse(&argp, argc, argv, &arguments)) {
+ if (parser.parse(argc, argv, &arguments)) {
error("There was an error while parsing arguments");
+ return 1;
}
std::cout << "Command line options: " << std::endl;
+ std::cout << "\t input: " << arguments.input_file << std::endl;
std::cout << "\t output: " << arguments.output_file << std::endl;
std::cout << "\t hex: " << arguments.hex << std::endl;
std::cout << "\t debug: " << arguments.debug << std::endl;