From bcbdb8282b68cd55ef5b1b6ddf8c367f0d0e493f Mon Sep 17 00:00:00 2001 From: Marc Auberer Date: Thu, 20 Feb 2025 23:19:58 +0100 Subject: [PATCH] Correctly print CLI option aliases (#791) --- src-bootstrap/driver.spice | 16 ++--- src/driver/Driver.cpp | 10 +-- src/symboltablebuilder/Scope.h | 2 +- src/typechecker/MacroDefs.h | 8 +-- std/io/cli-option.spice | 38 ++++++++++- std/io/cli-subcommand.spice | 118 ++++++++++++++++++--------------- std/text/print.spice | 10 ++- 7 files changed, 125 insertions(+), 77 deletions(-) diff --git a/src-bootstrap/driver.spice b/src-bootstrap/driver.spice index 5e1acf8bb..cf75505d3 100644 --- a/src-bootstrap/driver.spice +++ b/src-bootstrap/driver.spice @@ -310,10 +310,8 @@ p Driver.addInstallSubcommand() { */ p Driver.addUninstallSubcommand() { // Create sub-command itself - CliSubcommand& subCmd = this.cliParser.addSubcommand("uninstall", "Builds your Spice program and runs it immediately"); + CliSubcommand& subCmd = this.cliParser.addSubcommand("uninstall", "Uninstalls a Spice program from the system"); subCmd.addAlias("u"); - - this.addCompileSubcommandOptions(subCmd); } p Driver.addCompileSubcommandOptions(CliSubcommand& subCmd) { @@ -344,12 +342,12 @@ p Driver.addCompileSubcommandOptions(CliSubcommand& subCmd) { subCmd.addFlag("--use-lifetime-markers", this.cliOptions.useLifetimeMarkers, "Generate lifetime markers to enhance optimizations"); // Opt levels - subCmd.addOption("-O0", p(string& _v) { this.cliOptions.optLevel = OptLevel::O0; }, "Disable optimization for the output executable."); - subCmd.addOption("-O1", p(string& _v) { this.cliOptions.optLevel = OptLevel::O1; }, "Optimization level 1. Only basic optimization is executed."); - subCmd.addOption("-O2", p(string& _v) { this.cliOptions.optLevel = OptLevel::O2; }, "Optimization level 2. More advanced optimization is applied."); - subCmd.addOption("-O3", p(string& _v) { this.cliOptions.optLevel = OptLevel::O3; }, "Optimization level 3. Aggressive optimization for best performance."); - subCmd.addOption("-Os", p(string& _v) { this.cliOptions.optLevel = OptLevel::Os; }, "Optimization level s. Size optimization for output executable."); - subCmd.addOption("-Oz", p(string& _v) { this.cliOptions.optLevel = OptLevel::Oz; }, "Optimization level z. Aggressive optimization for best size."); + subCmd.addFlag("-O0", p(bool& _v) { this.cliOptions.optLevel = OptLevel::O0; }, "Disable optimization for the output executable."); + subCmd.addFlag("-O1", p(bool& _v) { this.cliOptions.optLevel = OptLevel::O1; }, "Optimization level 1. Only basic optimization is applied."); + subCmd.addFlag("-O2", p(bool& _v) { this.cliOptions.optLevel = OptLevel::O2; }, "Optimization level 2. More advanced optimization is applied."); + subCmd.addFlag("-O3", p(bool& _v) { this.cliOptions.optLevel = OptLevel::O3; }, "Optimization level 3. Aggressive optimization for best performance."); + subCmd.addFlag("-Os", p(bool& _v) { this.cliOptions.optLevel = OptLevel::Os; }, "Optimization level s. Size optimization for output executable."); + subCmd.addFlag("-Oz", p(bool& _v) { this.cliOptions.optLevel = OptLevel::Oz; }, "Optimization level z. Aggressive optimization for best size."); subCmd.addFlag("-lto", this.cliOptions.useLTO, "Enable link time optimization (LTO)"); // --debug-output diff --git a/src/driver/Driver.cpp b/src/driver/Driver.cpp index 61499e796..c9ff86c7d 100644 --- a/src/driver/Driver.cpp +++ b/src/driver/Driver.cpp @@ -279,8 +279,7 @@ void Driver::addTestSubcommand() { */ void Driver::addInstallSubcommand() { // Create sub-command itself - CLI::App *subCmd = - app.add_subcommand("install", "Builds your Spice program and installs it to a directory in the PATH variable"); + CLI::App *subCmd = app.add_subcommand("install", "Builds your Spice program and installs it to a directory in the PATH"); subCmd->alias("i"); subCmd->ignore_case(); subCmd->callback([&] { @@ -297,7 +296,7 @@ void Driver::addInstallSubcommand() { */ void Driver::addUninstallSubcommand() { // Create sub-command itself - CLI::App *subCmd = app.add_subcommand("uninstall", "Builds your Spice program and runs it immediately"); + CLI::App *subCmd = app.add_subcommand("uninstall", "Uninstalls a Spice program from the system"); subCmd->alias("u"); subCmd->ignore_case(); subCmd->callback([&] { @@ -342,7 +341,7 @@ void Driver::addCompileSubcommandOptions(CLI::App *subCmd) { // Opt levels subCmd->add_flag_callback("-O0", [&] { cliOptions.optLevel = O0; }, "Disable optimization for the output executable."); - subCmd->add_flag_callback("-O1", [&] { cliOptions.optLevel = O1; }, "Only basic optimization is executed."); + subCmd->add_flag_callback("-O1", [&] { cliOptions.optLevel = O1; }, "Only basic optimization is applied."); subCmd->add_flag_callback("-O2", [&] { cliOptions.optLevel = O2; }, "More advanced optimization is applied."); subCmd->add_flag_callback("-O3", [&] { cliOptions.optLevel = O3; }, "Aggressive optimization for best performance."); subCmd->add_flag_callback("-Os", [&] { cliOptions.optLevel = Os; }, "Size optimization for output executable."); @@ -360,7 +359,8 @@ void Driver::addCompileSubcommandOptions(CLI::App *subCmd) { // --dump-types subCmd->add_flag("--dump-types,-types", cliOptions.dumpSettings.dumpTypes, "Dump all used types"); // --dump-cache-stats - subCmd->add_flag("--dump-cache-stats,-cache-stats", cliOptions.dumpSettings.dumpCacheStats, "Dump stats for compiler-internal lookup caches"); + subCmd->add_flag("--dump-cache-stats,-cache-stats", cliOptions.dumpSettings.dumpCacheStats, + "Dump stats for compiler-internal lookup caches"); // --dump-ir subCmd->add_flag("--dump-ir,-ir", cliOptions.dumpSettings.dumpIR, "Dump LLVM-IR"); // --dump-assembly diff --git a/src/symboltablebuilder/Scope.h b/src/symboltablebuilder/Scope.h index 47cb7486c..7aaee86a2 100644 --- a/src/symboltablebuilder/Scope.h +++ b/src/symboltablebuilder/Scope.h @@ -89,7 +89,7 @@ class Scope { [[nodiscard]] bool doesAllowUnsafeOperations() const; [[nodiscard]] bool isImportedBy(const Scope *askingScope) const; [[nodiscard]] nlohmann::json getSymbolTableJSON() const; - ALWAYS_INLINE [[nodiscard]] bool isRootScope() const { return parent == nullptr; } + [[nodiscard]] ALWAYS_INLINE bool isRootScope() const { return parent == nullptr; } // Wrapper methods for symbol table ALWAYS_INLINE SymbolTableEntry *insert(const std::string &name, ASTNode *declNode) { diff --git a/src/typechecker/MacroDefs.h b/src/typechecker/MacroDefs.h index b98acb8c3..ccb975fe5 100644 --- a/src/typechecker/MacroDefs.h +++ b/src/typechecker/MacroDefs.h @@ -43,13 +43,13 @@ } \ } -#define HANDLE_UNRESOLVED_TYPE_QT(qualType) \ - if ((qualType).is(TY_UNRESOLVED)) { \ +#define HANDLE_UNRESOLVED_TYPE_QT(type) \ + if ((type).is(TY_UNRESOLVED)) { \ if constexpr (std::is_base_of()) { \ const auto exprNode = spice_pointer_cast(node); \ - return exprNode->setEvaluatedSymbolType(qualType, manIdx); \ + return exprNode->setEvaluatedSymbolType(type, manIdx); \ } else { \ - return qualType; \ + return type; \ } \ } diff --git a/std/io/cli-option.spice b/std/io/cli-option.spice index 8c86e7c93..e285f38d1 100644 --- a/std/io/cli-option.spice +++ b/std/io/cli-option.spice @@ -1,12 +1,17 @@ import "std/os/os"; import "std/data/vector"; import "std/io/filepath"; +import "std/text/print"; +import "std/text/stringstream"; // Generic types type T bool|string|String|int|long|short|FilePath; // Enums -type CliOptionMode enum { SET_VALUE, CALL_CALLBACK } +type CliOptionMode enum { + SET_VALUE, + CALL_CALLBACK +} public type CliOption struct { String name @@ -62,4 +67,33 @@ public p CliOption.callCallback(T& value) { p(T&) cb = this.callback; cb(value); } -} \ No newline at end of file +} + +public f CliOption.isPositional() { + if this.name.startsWith("-") { + return false; + } + foreach const String& optionAlias : this.aliases { + if optionAlias.startsWith("-") { + return false; + } + } + return true; +} + +public p CliOption.printHelpItem() { + // Print name and aliases + StringStream ss; + ss << " " << this.name; + foreach const String& optionAlias : this.aliases { + ss << ',' << optionAlias; + } + // For all options, that are not bool flags, print an additional placeholder + if sizeof() > 1 { + ss << " "; + } + printFixedWidth(ss.str(), 35, true); + // Description + printFixedWidth(this.description, 85, true); + lineBreak(); +} diff --git a/std/io/cli-subcommand.spice b/std/io/cli-subcommand.spice index e68974331..78300fcea 100644 --- a/std/io/cli-subcommand.spice +++ b/std/io/cli-subcommand.spice @@ -4,6 +4,7 @@ import "std/type/string"; import "std/data/vector"; import "std/os/os"; import "std/io/filepath"; +import "std/text/stringstream"; public type CliSubcommand struct { String name @@ -83,10 +84,14 @@ public f CliSubcommand.parse(unsigned int argc, string[] argv, int layer = } } foreach const CliOption& stringOption : this.stringOptions { - if this.nameOrAliasMatches(stringOption.getName(), stringOption.getAliases(), arg) { + if this.nameOrAliasMatches(stringOption.getName(), stringOption.getAliases(), arg) { // get the argument value - arg = argv[++argNo]; - + argNo++; + if argNo >= argc { + printf("Option %s requires a value.\n", arg); + return EXIT_CODE_ERROR; + } + arg = argv[argNo]; stringOption.setTargetValue(arg); stringOption.callCallback(arg); continue 2; // Continue with next argument @@ -95,9 +100,13 @@ public f CliSubcommand.parse(unsigned int argc, string[] argv, int layer = foreach const CliOption& stringObjOption : this.stringObjOptions { if this.nameOrAliasMatches(stringObjOption.getName(), stringObjOption.getAliases(), arg) { // get the argument value - arg = argv[++argNo]; + argNo++; + if argNo >= argc { + printf("Option %s requires a value.\n", arg); + return EXIT_CODE_ERROR; + } + arg = argv[argNo]; String parsedArg = String(arg); - stringObjOption.setTargetValue(parsedArg); stringObjOption.callCallback(parsedArg); continue 2; // Continue with next argument @@ -106,9 +115,13 @@ public f CliSubcommand.parse(unsigned int argc, string[] argv, int layer = foreach const CliOption& intOption : this.intOptions { if this.nameOrAliasMatches(intOption.getName(), intOption.getAliases(), arg) { // get the argument value - arg = argv[++argNo]; + argNo++; + if argNo >= argc { + printf("Option %s requires a value.\n", arg); + return EXIT_CODE_ERROR; + } + arg = argv[argNo]; int parsedArg = toInt(arg); - intOption.setTargetValue(parsedArg); intOption.callCallback(parsedArg); continue 2; // Continue with next argument @@ -117,9 +130,13 @@ public f CliSubcommand.parse(unsigned int argc, string[] argv, int layer = foreach const CliOption& filePathOption : this.filePathOptions { if this.nameOrAliasMatches(filePathOption.getName(), filePathOption.getAliases(), arg) { // get the argument value - arg = argv[++argNo]; + argNo++; + if argNo >= argc { + printf("Option %s requires a value.\n", arg); + return EXIT_CODE_ERROR; + } + arg = argv[argNo]; FilePath parsedArg = FilePath(arg); - filePathOption.setTargetValue(parsedArg); filePathOption.callCallback(parsedArg); continue 2; // Continue with next argument @@ -189,8 +206,7 @@ public f&> CliSubcommand.addOption(string name, string& target #[ignoreUnusedReturnValue] public f&> CliSubcommand.addOption(const String& name, string& targetVariable, const String& description) { - this.stringOptions.pushBack(CliOption(name, targetVariable, description)); - return this.stringOptions.back(); + return this.addOption(name.getRaw(), targetVariable, description.getRaw()); } #[ignoreUnusedReturnValue] @@ -201,8 +217,7 @@ public f&> CliSubcommand.addOption(string name, p(string&) cal #[ignoreUnusedReturnValue] public f&> CliSubcommand.addOption(const String& name, p(string&) callback, const String& description) { - this.stringOptions.pushBack(CliOption(name, callback, description)); - return this.stringOptions.back(); + return this.addOption(name.getRaw(), callback, description.getRaw()); } #[ignoreUnusedReturnValue] @@ -213,8 +228,7 @@ public f&> CliSubcommand.addOption(string name, String& target #[ignoreUnusedReturnValue] public f&> CliSubcommand.addOption(const String& name, String& targetVariable, const String& description) { - this.stringObjOptions.pushBack(CliOption(name, targetVariable, description)); - return this.stringObjOptions.back(); + return this.addOption(name.getRaw(), targetVariable, description.getRaw()); } #[ignoreUnusedReturnValue] @@ -225,8 +239,7 @@ public f&> CliSubcommand.addOption(string name, p(String&) cal #[ignoreUnusedReturnValue] public f&> CliSubcommand.addOption(const String& name, p(String&) callback, const String& description) { - this.stringObjOptions.pushBack(CliOption(name, callback, description)); - return this.stringObjOptions.back(); + return this.addOption(name.getRaw(), callback, description.getRaw()); } #[ignoreUnusedReturnValue] @@ -237,8 +250,7 @@ public f&> CliSubcommand.addOption(string name, int& targetVariab #[ignoreUnusedReturnValue] public f&> CliSubcommand.addOption(const String& name, int& targetVariable, const String& description) { - this.intOptions.pushBack(CliOption(name, targetVariable, description)); - return this.intOptions.back(); + return this.addOption(name.getRaw(), targetVariable, description.getRaw()); } #[ignoreUnusedReturnValue] @@ -249,8 +261,7 @@ public f&> CliSubcommand.addOption(string name, p(int&) callback, #[ignoreUnusedReturnValue] public f&> CliSubcommand.addOption(const String& name, p(int&) callback, const String& description) { - this.intOptions.pushBack(CliOption(name, callback, description)); - return this.intOptions.back(); + return this.addOption(name.getRaw(), callback, description.getRaw()); } #[ignoreUnusedReturnValue] @@ -261,8 +272,7 @@ public f&> CliSubcommand.addOption(string name, FilePath& ta #[ignoreUnusedReturnValue] public f&> CliSubcommand.addOption(const String& name, FilePath& targetVariable, const String& description) { - this.filePathOptions.pushBack(CliOption(name, targetVariable, description)); - return this.filePathOptions.back(); + return this.addOption(name.getRaw(), targetVariable, description.getRaw()); } #[ignoreUnusedReturnValue] @@ -273,8 +283,7 @@ public f&> CliSubcommand.addOption(string name, p(FilePath&) #[ignoreUnusedReturnValue] public f&> CliSubcommand.addOption(const String& name, p(FilePath&) callback, const String& description) { - this.filePathOptions.pushBack(CliOption(name, callback, description)); - return this.filePathOptions.back(); + return this.addOption(name.getRaw(), callback, description.getRaw()); } #[ignoreUnusedReturnValue] @@ -285,33 +294,33 @@ public f&> CliSubcommand.addFlag(string name, bool& targetVariab #[ignoreUnusedReturnValue] public f&> CliSubcommand.addFlag(const String& name, bool& targetVariable, const String& description) { - this.boolOptions.pushBack(CliOption(name, targetVariable, description)); - return this.boolOptions.back(); + return this.addFlag(name.getRaw(), targetVariable, description.getRaw()); } #[ignoreUnusedReturnValue] -public f&> CliSubcommand.addFlag(const string name, p(bool&) callback, string description) { +public f&> CliSubcommand.addFlag(string name, p(bool&) callback, string description) { this.boolOptions.pushBack(CliOption(String(name), callback, String(description))); return this.boolOptions.back(); } #[ignoreUnusedReturnValue] public f&> CliSubcommand.addFlag(const String& name, p(bool&) callback, const String& description) { - this.boolOptions.pushBack(CliOption(name, callback, description)); - return this.boolOptions.back(); + return this.addFlag(name.getRaw(), callback, description.getRaw()); } -p printHelpItem(string name, string description) { - printFixedWidth(name, 25, true); - printFixedWidth(description, 85, true); +public p CliSubcommand.printHelpItem() { + // Print name and aliases + StringStream ss; + ss << " " << this.name; + foreach const String& subcommandAlias : this.aliases { + ss << ", " << subcommandAlias; + } + printFixedWidth(ss.str(), 25, true); + // Description + printFixedWidth(this.description, 85, true); lineBreak(); } -p printHelpItemWithValue(string name, string description) { - String str = String(name) + " "; - printHelpItem(str.getRaw(), description); -} - p CliSubcommand.printHelp(string[] argv, int layer) { // Build subcommand string String subcommand = String(argv[0]); @@ -325,34 +334,33 @@ p CliSubcommand.printHelp(string[] argv, int layer) { if !this.subcommands.isEmpty() { printf("\nSubcommands:\n"); foreach const CliSubcommand& subCommand : this.subcommands { - const String& name = subCommand.getName(); - const String& description = subCommand.getDescription(); - printHelpItemWithValue(name.getRaw(), description.getRaw()); + subCommand.printHelpItem(); } } // Print options printf("\nOptions:\n"); - foreach const CliOption& option : this.stringOptions { - const String& name = option.getName(); - const String& description = option.getDescription(); - printHelpItemWithValue(name.getRaw(), description.getRaw()); - } - foreach const CliOption& option : this.intOptions { - const String& name = option.getName(); - const String& description = option.getDescription(); - printHelpItemWithValue(name.getRaw(), description.getRaw()); + if this.stringOptions.getSize() + this.stringObjOptions.getSize() + this.intOptions.getSize() + this.filePathOptions.getSize() > 0 { + foreach const CliOption& option : this.stringOptions { + option.printHelpItem(); + } + foreach const CliOption& option : this.intOptions { + option.printHelpItem(); + } } // Print flags printf("\nFlags:\n"); foreach const CliOption& flag : this.boolOptions { - const String& name = flag.getName(); - const String& description = flag.getDescription(); - printHelpItem(name.getRaw(), description.getRaw()); + flag.printHelpItem(); } - printHelpItem("--help,-h", "Print this help message"); - printHelpItem("--version,-v", "Print the version of the application"); + + // Print help and version flags + bool target; + const dyn helpFlag = CliOption(String("--help,-h"), target, String("Print this help message")); + helpFlag.printHelpItem(); + const dyn versionFlag = CliOption(String("--version,-v"), target, String("Print the version of the application")); + versionFlag.printHelpItem(); // Print footer if !this.footerString.isEmpty() { diff --git a/std/text/print.spice b/std/text/print.spice index de9ea73c4..b53af1b1e 100644 --- a/std/text/print.spice +++ b/std/text/print.spice @@ -48,6 +48,10 @@ public p printColumn(string text, unsigned int width) { } } +public p printColumn(const String& text, unsigned int width) { + printColumn(text.getRaw(), width); +} + public p printFixedWidth(string text, unsigned int width, bool ellipsis = false) { unsigned long length = len(text); const bool printEllipsis = ellipsis & length > width; @@ -62,4 +66,8 @@ public p printFixedWidth(string text, unsigned int width, bool ellipsis = false) printf(" "); } } -} \ No newline at end of file +} + +public p printFixedWidth(const String& text, unsigned int width, bool ellipsis = false) { + printFixedWidth(text.getRaw(), width, ellipsis); +}