diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-06-24 18:54:13 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-06-24 18:54:13 +0100 |
| commit | 0fc4b95e63370b9131c4e76f5f7137a4764dc341 (patch) | |
| tree | f0002f3dfc194b6fd55bfa7e52ec8948d2e872e8 /nihil/command_map.cc | |
| parent | b6cb331523e4b4969f587293468d532d2bf3ef9c (diff) | |
| download | nihil-0fc4b95e63370b9131c4e76f5f7137a4764dc341.tar.gz nihil-0fc4b95e63370b9131c4e76f5f7137a4764dc341.tar.bz2 | |
rework command_map a little
Diffstat (limited to 'nihil/command_map.cc')
| -rw-r--r-- | nihil/command_map.cc | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/nihil/command_map.cc b/nihil/command_map.cc new file mode 100644 index 0000000..1cc5081 --- /dev/null +++ b/nihil/command_map.cc @@ -0,0 +1,217 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <cstdio> +#include <functional> +#include <iostream> +#include <map> +#include <ranges> +#include <string> +#include <utility> + +module nihil; + +/* + * command_map represents a hierarchical list of commands. At each level, + * a command is mapped to a handler, which can either be a function, in + * which case we execute the function, or another command_map, in which + * case we invoke the new map + */ + +namespace nihil { + +/* + * The string tree we store our commands in. This is sort of like a very + * basic hierarchical std::map. Keys are provided as a range of values, + * typically from argv. + */ + +template<typename T> +struct string_tree_node final { + string_tree_node() + : _this_word("") + { + } + + string_tree_node(std::string_view this_word) + : _this_word(this_word) + { + } + + string_tree_node(std::string_view this_word, T value) + : _this_word(this_word) + , _value(std::move(value)) + { + } + + /* + * Return a child node, or NULL if the child doesn't exist. + */ + auto get_child(this string_tree_node const &self, + std::string_view child) + -> string_tree_node const * + { + if (auto it = self.children.find(std::string(child)); + it != self.children.end()) + return &it->second; + + return nullptr; + } + + auto get_child(this string_tree_node &self, + std::string_view child) + -> string_tree_node * + { + if (auto it = self.children.find(std::string(child)); + it != self.children.end()) + return &it->second; + + return nullptr; + } + + /* + * Return a child node if it exists, or insert a new empty node. + */ + auto get_or_create_child(this string_tree_node &self, + std::string_view child) + -> string_tree_node * + { + if (auto ptr = self.get_child(child); ptr != nullptr) + return ptr; + + auto [it, ok] = self.children.emplace( + child, string_tree_node(child)); + return &it->second; + } + + /* + * Return this node's value. + */ + auto value(this string_tree_node const &self) + -> std::optional<T> const & + { + return self._value; + } + + /* + * Set this node's value. + */ + auto value(this string_tree_node &self, T value) -> void + { + self._value = std::move(value); + } + +private: + std::string _this_word; + std::optional<T> _value; + std::unordered_map<std::string, string_tree_node> children; +}; + +template<typename T> +struct string_tree { + /* + * Add a node to the tree. Returns false if the node already exists. + */ + auto insert(this string_tree &self, + std::ranges::range auto &&path, + T value) + -> bool + { + auto *this_node = &self._root_node; + + // Find the node for this key. + for (auto &&next : path) { + auto this_word = std::string_view(next); + this_node = this_node->get_or_create_child(this_word); + } + + if (this_node->value()) { + // The value already exists. + return false; + } + + // Set the new value. + this_node->value(std::move(value)); + return true; + } + + /* + * Find a node in the tree. Unlike insert(), iteration stops when + * we find any node with a value. + */ + auto find(this string_tree const &self, + std::ranges::range auto &&path) + -> std::optional<T> + { + auto *this_node = &self._root_node; +{} + // Find the node for this key. + for (auto &&next : path) { + this_node = this_node->get_child(next); + + if (this_node == nullptr) + // The node doesn't exist. + return {}; + + if (this_node->value()) + // This node has a value, so return it. + return {this_node->value()}; + } + + // We didn't find a value, so the key was incomplete. + return {}; + } + +private: + string_tree_node<T> _root_node; +}; + +/* + * The global command map. + */ +auto get_commands() -> string_tree<command_base *> & { + static auto commands = string_tree<command_base *>(); + return commands; +} + +auto register_command(std::string_view path, command_base *cmd) noexcept + -> void +try { +{ + auto &commands = get_commands(); + if (commands.insert(path | std::views::split(' '), cmd) == false) { + std::printf("command registration failed\n"); + std::abort(); + } +} +} catch (...) { + std::printf("command registration failed\n"); + std::abort(); +} + + +auto dispatch_command(int argc, char **argv) -> int +{ + if (argc == 0) { + std::print("not enough arguments\n"); + return 1; + } + + auto &commands = get_commands(); + auto node = commands.find(std::span(argv, argv + argc)); + if (node) + return (**node).invoke(argc, argv); + + std::print("unknown command\n"); + return 1; +} + +void print_usage(std::string_view) +{ +// get_root_node().print_usage(std::string(prefix)); +} + +} // namespace nihil |
