poafloc

poafloc - Parser Of Arguments For Lines Of Commands
git clone git://git.dimitrijedobrota.com/poafloc.git
Log | Files | Refs | README | LICENSE

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:
MMakefile | 2+-
Margs.hpp | 140++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mdemo.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;