diff --git a/Makefile b/Makefile index 7af7d8d5..aa3782f4 100644 --- a/Makefile +++ b/Makefile @@ -84,17 +84,17 @@ $(BUILD_DIR)/ir_emit_$(DASM_ARCH).h: $(SRC_DIR)/ir_$(DASM_ARCH).dasc $(SRC_DIR)/ $(OBJS_COMMON) $(OBJS_IR) $(OBJS_IR_TEST): $(BUILD_DIR)/$(notdir %.o): $(SRC_DIR)/$(notdir %.c) $(CC) $(CFLAGS) -I$(BUILD_DIR) -o $@ -c $< -$(BUILD_DIR)/ir-test: $(SRC_DIR)/ir-test.cxx - $(CXX) -O3 -std=c++17 $(SRC_DIR)/ir-test.cxx -o $(BUILD_DIR)/ir-test +$(BUILD_DIR)/ir-test: $(SRC_DIR)/misc-utils/ir-test.cxx + $(CXX) -O3 -std=c++17 $(SRC_DIR)/misc-utils/ir-test.cxx -o $(BUILD_DIR)/ir-test test: $(BUILD_DIR)/ir $(BUILD_DIR)/ir-test $(BUILD_DIR)/ir $(SRC_DIR)/test.ir --dump --save 2>$(BUILD_DIR)/test.log $(BUILD_DIR)/ir $(SRC_DIR)/test.ir --dot $(BUILD_DIR)/ir.dot dot -Tpdf $(BUILD_DIR)/ir.dot -o $(BUILD_DIR)/ir.pdf - BUILD_DIR=$(BUILD_DIR) SRC_DIR=$(SRC_DIR) $(BUILD_DIR)/ir-test + $(BUILD_DIR)/ir-test --build-dir $(BUILD_DIR) --src-dir $(SRC_DIR) test-ci: $(BUILD_DIR)/ir $(BUILD_DIR)/ir-test - BUILD_DIR=$(BUILD_DIR) SRC_DIR=$(SRC_DIR) $(BUILD_DIR)/ir-test --show-diff + $(BUILD_DIR)/ir-test --show-diff --build-dir $(BUILD_DIR) --src-dir $(SRC_DIR) clean: rm -rf $(BUILD_DIR)/ir $(BUILD_DIR)/ir_test $(BUILD_DIR)/*.o \ diff --git a/ir-test.cxx b/misc-utils/ir-test.cxx similarity index 76% rename from ir-test.cxx rename to misc-utils/ir-test.cxx index 541ecd0d..8f5fbdd0 100644 --- a/ir-test.cxx +++ b/misc-utils/ir-test.cxx @@ -15,6 +15,8 @@ #include #include +#include "popl.hpp" + #ifdef _WIN32 # include # define popen _popen @@ -232,27 +234,80 @@ namespace ir { return 1 <= b.compare(a); }); } -} -int main(int argc, char **argv) { - for (int i = 1; i < argc; i++) { - // XXX use some cleaner arg parsing solution - if (!std::string(argv[i]).compare("--show-diff")) { - ::show_diff = true; - } else if (!std::string(argv[i]).compare("--no-color")) { - ::colorize = false; + void usage(popl::OptionParser& op) { + std::cerr << "Usage: ir-test [options]" << std::endl; + std::cerr << std::endl; + std::cerr << "Execute IR tests" << std::endl; + std::cerr << std::endl; + std::cout << op << std::endl; + } + + enum init_status { INIT_EXIT_OK, INIT_EXIT_ERR, INIT_OK = UINT8_MAX }; + + init_status init(int argc, char **argv) { + popl::OptionParser op{}; + + auto diff_opt = op.add("", "show-diff", "Print failed test diff to stdout"); + auto no_color_opt = op.add("", "no-color", "Disable output colorization"); + auto build_dir_opt = op.add>("", "build-dir", "Bulid dir path. " \ + "When passed, overrides the BUILD_DIR environment variable", "."); + auto src_dir_opt = op.add>("", "src-dir", "Source dir path. " \ + "When passed, overrides the SRC_DIR environment variable", "."); + // XXX support multiple test dirs? + auto test_dir_opt = op.add>("", "test-dir", "Test dir path. ", "." PATH_SEP "tests"); + auto ir_exe_opt = op.add>("", "ir-exe", "Path to the `ir` test app. "); + auto help_opt = op.add("h", "help", "Display help text and exit"); + + try { + op.parse(argc, argv); + + if (op.unknown_options().size() > 0) { + for (const auto& unknown_option: op.unknown_options()) + std::cout << "\r" << ir::colorize("ERROR", ir::RED) << ": unknown option '" << + unknown_option << "'" << std::endl; + std::cerr << "\rTry 'ir-test --help' for more information." << std::endl; + return INIT_EXIT_ERR; + } + + ir::init_console(); + + if (help_opt->is_set()) { + usage(op); + return INIT_EXIT_OK; + } + + ::show_diff = diff_opt->is_set(); + ::colorize = !no_color_opt->is_set(); + ::build_dir = build_dir_opt->is_set() ? build_dir_opt->value() : ir::get_dir_from_env("BUILD_DIR"); + ::src_dir = src_dir_opt->is_set() ? src_dir_opt->value() : ir::get_dir_from_env("SRC_DIR"); + ::test_dir = test_dir_opt->is_set() ? test_dir_opt->value() : ::src_dir + PATH_SEP + "tests"; + ::ir_exe = ir_exe_opt->is_set() ? ir_exe_opt->value() : ::build_dir + PATH_SEP + "ir" + EXE_SUF; + + ::ir_target = ir::trim(ir::exec(::ir_exe + " --target")); + } catch (const popl::invalid_option& e) { + std::cout << "\r" << ir::colorize("ERROR", ir::RED) << ": " << e.what() << '\n'; + std::cerr << "\rTry 'ir-test --help' for more information." << std::endl; + return INIT_EXIT_ERR; + } catch (const std::exception& e) { + std::cout << "\r" << ir::colorize("ERROR", ir::RED) << ": " << e.what() << '\n'; + return INIT_EXIT_ERR; } + return INIT_OK; } - ir::init_console(); +} - ::build_dir = ir::get_dir_from_env("BUILD_DIR"); - ::src_dir = ir::get_dir_from_env("SRC_DIR"); - ::test_dir = ::src_dir + PATH_SEP + "tests"; - ::ir_exe = ::build_dir + PATH_SEP + "ir" + EXE_SUF; - ::ir_target = ir::trim(ir::exec(::ir_exe + " --target")); - std::vector irt_files; +int main(int argc, char **argv) { + auto init_status = ir::init(argc, argv); + if (ir::INIT_OK != init_status) return init_status; - ir::find_tests_in_dir(::test_dir, irt_files); + std::vector irt_files; + try { + ir::find_tests_in_dir(::test_dir, irt_files); + } catch (const std::exception& e) { + std::cout << "\r" << ir::colorize("ERROR", ir::RED) << ": " << e.what() << '\n'; + std::exit(3); + } for (const std::string& test_fl : irt_files) { try { auto test = ir::test(test_fl); @@ -288,12 +343,12 @@ int main(int argc, char **argv) { f.close(); } } - } catch (ir::broken_test_exception& e) { + } catch (const ir::broken_test_exception& e) { std::cout << "\r" << ir::colorize("BROK", ir::RED) << ": [" << test_fl << "]\n"; ::bad_list.push_back(test_fl); - } catch (std::string& s) { + } catch (const std::string& s) { std::cout << "\r" << ir::colorize("ERROR", ir::RED) << ": " << s << '\n'; - } catch (std::exception& e) { + } catch (const std::exception& e) { std::cout << "\r" << ir::colorize("ERROR", ir::RED) << ": " << e.what() << '\n'; } diff --git a/misc-utils/popl.hpp b/misc-utils/popl.hpp new file mode 100644 index 00000000..23cbb128 --- /dev/null +++ b/misc-utils/popl.hpp @@ -0,0 +1,1257 @@ +/*** + ____ __ ____ __ + ( _ \ / \( _ \( ) + ) __/( O )) __// (_/\ + (__) \__/(__) \____/ + version 1.2.0 + https://github.com/badaix/popl + + This file is part of popl (program options parser lib) + Copyright (C) 2015-2018 Johannes Pohl + + This software may be modified and distributed under the terms + of the MIT license. See the LICENSE file for details. +***/ + +/// checked with clang-tidy: +/// run-clang-tidy-3.8.py -header-filter='.*' -checks='*,-misc-definitions-in-headers,-google-readability-braces-around-statements,-readability-braces-around-statements,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-google-build-using-namespace,-google-build-using-namespace' + +#ifndef POPL_HPP +#define POPL_HPP + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace popl +{ + +#define POPL_VERSION "1.2.0" + + +/// Option's argument type +/** + * Switch has "no" argument + * Value has "required" argument + * Implicit has "optional" argument + */ +enum class Argument +{ + no = 0, // option never takes an argument + required, // option always requires an argument + optional // option may take an argument +}; + + +/// Option's attribute +/** + * inactive: Option is not set and will not be parsed + * hidden: Option is active, but will not show up in the help message + * required: Option must be set on the command line. Otherwise an exception will be thrown + * optional: Option must not be set. Default attribute. + * advanced: Option is advanced and will only show up in the advanced help message + * expoert: Option is expert and will only show up in the expert help message + */ +enum class Attribute +{ + inactive = 0, + hidden = 1, + required = 2, + optional = 3, + advanced = 4, + expert = 5 +}; + + +/// Option name type. Used in invalid_option exception. +/** + * unspecified: not specified + * short_name: The option's short name + * long_name: The option's long name + */ +enum class OptionName +{ + unspecified, + short_name, + long_name +}; + + +/// Abstract Base class for Options +/** + * Base class for Options + * holds just configuration data, no runtime data. + * Option is not bound to a special type "T" + */ +class Option +{ +friend class OptionParser; +public: + /// Construct an Option + /// @param short_name the options's short name. Must be empty or one character. + /// @param long_name the option's long name. Can be empty. + /// @param description the Option's description that will be shown in the help message + Option(const std::string& short_name, const std::string& long_name, std::string description); + + /// Destructor + virtual ~Option() = default; + + /// default copy constructor + Option(const Option&) = default; + + /// default move constructor + Option(Option&&) = default; + + /// default assignement operator + Option& operator=(const Option&) = default; + + /// default move assignement operator + Option& operator=(Option&&) = default; + + /// Get the Option's short name + /// @return character of the options's short name or 0 if no short name is defined + char short_name() const; + + /// Get the Option's long name + /// @return the long name of the Option. Empty string if no long name is defined + std::string long_name() const; + + /// Get the Option's long or short name + /// @param what_name the option's name to return + /// @param what_hyphen preced the returned name with (double-)hypen + /// @return the requested name of the Option. Empty string if not defined. + std::string name(OptionName what_name, bool with_hypen = false) const; + + /// Get the Option's description + /// @return the description + std::string description() const; + + /// Get the Option's default value + /// @param out stream to write the default value to + /// @return true if a default value is available, false if not + virtual bool get_default(std::ostream& out) const = 0; + + /// Set the Option's attribute + /// @param attribute + void set_attribute(const Attribute& attribute); + + /// Get the Option's attribute + /// @return the Options's attribute + Attribute attribute() const; + + /// Get the Option's argument type + /// @return argument type (no, required, optional) + virtual Argument argument_type() const = 0; + + /// Check how often the Option is set on command line + /// @return the Option's count on command line + virtual size_t count() const = 0; + + /// Check if the Option is set + /// @return true if set at least once + virtual bool is_set() const = 0; + +protected: + /// Parse the command line option and fill the internal data structure + /// @param what_name short or long option name + /// @param value the value as given on command line + virtual void parse(OptionName what_name, const char* value) = 0; + + /// Clear the internal data structure + virtual void clear() = 0; + + std::string short_name_; + std::string long_name_; + std::string description_; + Attribute attribute_; +}; + + + + +/// Value option with optional default value +/** + * Value option with optional default value + * If set, it requires an argument + */ +template +class Value : public Option +{ +public: + /// Construct an Value Option + /// @param short_name the option's short name. Must be empty or one character. + /// @param long_name the option's long name. Can be empty. + /// @param description the Option's description that will be shown in the help message + Value(const std::string& short_name, const std::string& long_name, const std::string& description); + + /// Construct an Value Option + /// @param short_name the option's short name. Must be empty or one character. + /// @param long_name the option's long name. Can be empty. + /// @param description the Option's description that will be shown in the help message + /// @param default_val the Option's default value + /// @param assign_to pointer to a variable to assign the parsed command line value to + Value(const std::string& short_name, const std::string& long_name, const std::string& description, const T& default_val, T* assign_to = nullptr); + + size_t count() const override; + bool is_set() const override; + + /// Assign the last parsed command line value to "var" + /// @param var pointer to the variable where is value is written to + void assign_to(T* var); + + /// Manually set the Option's value. Deletes current value(s) + /// @param value the new value of the option + void set_value(const T& value); + + /// Get the Option's value. Will throw if option at index idx is not available + /// @param idx the zero based index of the value (if set multiple times) + /// @return the Option's value at index "idx" + T value(size_t idx = 0) const; + + /// Set the Option's default value + /// @param value the default value if not specified on command line + void set_default(const T& value); + + /// Check if the Option has a default value + /// @return true if the Option has a default value + bool has_default() const; + + /// Get the Option's default value. Will throw if no default is set. + /// @return the Option's default value + T get_default() const; + bool get_default(std::ostream& out) const override; + + Argument argument_type() const override; + +protected: + void parse(OptionName what_name, const char* value) override; + std::unique_ptr default_; + + virtual void update_reference(); + virtual void add_value(const T& value); + void clear() override; + + T* assign_to_; + std::vector values_; +}; + + + + +/// Value option with implicit default value +/** + * Value option with implicit default value + * If set, an argument is optional + * -without argument it carries the implicit default value + * -with argument it carries the explicit value + */ +template +class Implicit : public Value +{ +public: + Implicit(const std::string& short_name, const std::string& long_name, const std::string& description, const T& implicit_val, T* assign_to = nullptr); + + Argument argument_type() const override; + +protected: + void parse(OptionName what_name, const char* value) override; +}; + + + + +/// Value option without value +/** + * Value option without value + * Does not require an argument + * Can be either set or not set + */ +class Switch : public Value +{ +public: + Switch(const std::string& short_name, const std::string& long_name, const std::string& description, bool* assign_to = nullptr); + + void set_default(const bool& value) = delete; + Argument argument_type() const override; + +protected: + void parse(OptionName what_name, const char* value) override; +}; + + + + +using Option_ptr = std::shared_ptr