diff --git a/Makefile b/Makefile index 6a0da441..990fa64f 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,8 @@ rgbasm_obj := \ src/extern/utf8decoder.o \ src/linkdefs.o \ src/opmath.o \ - src/util.o + src/util.o \ + src/verbosity.o src/asm/lexer.o src/asm/main.o: src/asm/parser.hpp @@ -95,7 +96,8 @@ rgblink_obj := \ src/extern/utf8decoder.o \ src/linkdefs.o \ src/opmath.o \ - src/util.o + src/util.o \ + src/verbosity.o src/link/lexer.o src/link/main.o: src/link/script.hpp @@ -117,7 +119,8 @@ rgbgfx_obj := \ src/gfx/reverse.o \ src/gfx/rgba.o \ src/gfx/warning.o \ - src/util.o + src/util.o \ + src/verbosity.o rgbasm: ${rgbasm_obj} $Q${CXX} ${REALLDFLAGS} -o $@ ${rgbasm_obj} ${REALCXXFLAGS} src/version.cpp diff --git a/contrib/zsh_compl/_rgbasm b/contrib/zsh_compl/_rgbasm index e7492b3f..35a44aff 100644 --- a/contrib/zsh_compl/_rgbasm +++ b/contrib/zsh_compl/_rgbasm @@ -39,7 +39,7 @@ local args=( '(- : * options)'{-h,--help}'[Print help text and exit]' '(-E --export-all)'{-E,--export-all}'[Export all symbols]' - '(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]' + '(-v --verbose)'{-v,--verbose}'[Enable verbose output]' -w'[Disable all warnings]' '(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:' diff --git a/include/asm/fstack.hpp b/include/asm/fstack.hpp index 4c116c72..ab2dea0f 100644 --- a/include/asm/fstack.hpp +++ b/include/asm/fstack.hpp @@ -49,6 +49,8 @@ struct FileStackNode { struct MacroArgs; +void fstk_VerboseOutputConfig(); + bool fstk_DumpCurrent(); std::shared_ptr fstk_GetFileStack(); std::shared_ptr fstk_GetUniqueIDStr(); diff --git a/include/asm/main.hpp b/include/asm/main.hpp index 75330651..02775829 100644 --- a/include/asm/main.hpp +++ b/include/asm/main.hpp @@ -7,6 +7,8 @@ #include #include +#include "verbosity.hpp" + enum MissingInclude { INC_ERROR, // A missing included file is an error that halts assembly GEN_EXIT, // A missing included file is assumed to be generated; exit normally @@ -14,11 +16,11 @@ enum MissingInclude { }; struct Options { + bool exportAll = false; // -E uint8_t fixPrecision = 16; // -Q size_t maxRecursionDepth = 64; // -r char binDigits[2] = {'0', '1'}; // -b char gfxDigits[4] = {'0', '1', '2', '3'}; // -g - bool verbose = false; // -v FILE *dependFile = nullptr; // -M std::string targetFileName; // -MQ, -MT MissingInclude missingIncludeState = INC_ERROR; // -MC, -MG @@ -42,11 +44,4 @@ struct Options { extern Options options; -#define verbosePrint(...) \ - do { \ - if (options.verbose) { \ - fprintf(stderr, __VA_ARGS__); \ - } \ - } while (0) - #endif // RGBDS_ASM_MAIN_HPP diff --git a/include/asm/symbol.hpp b/include/asm/symbol.hpp index aa831b8f..2c6c83ae 100644 --- a/include/asm/symbol.hpp +++ b/include/asm/symbol.hpp @@ -71,7 +71,6 @@ struct Symbol { void sym_ForEach(void (*callback)(Symbol &)); -void sym_SetExportAll(bool set); Symbol *sym_AddLocalLabel(std::string const &symName); Symbol *sym_AddLabel(std::string const &symName); Symbol *sym_AddAnonLabel(); diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index fe8b57f5..83af6838 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -11,6 +11,7 @@ #include #include "helpers.hpp" +#include "verbosity.hpp" #include "gfx/rgba.hpp" @@ -20,7 +21,6 @@ struct Options { bool allowMirroringX = false; // -X, -m bool allowMirroringY = false; // -Y, -m bool columnMajor = false; // -Z - uint8_t verbosity = 0; // -v std::string attrmap{}; // -a, -A std::optional bgColor{}; // -B @@ -42,6 +42,7 @@ struct Options { uint16_t height; uint32_t right() const { return left + width * 8; } uint32_t bottom() const { return top + height * 8; } + bool specified() const { return left || top || width || height; } } inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS) uint8_t basePalID = 0; // -l std::array maxNbTiles{UINT16_MAX, 0}; // -N @@ -56,18 +57,6 @@ struct Options { std::string input{}; // positional arg - // clang-format off: vertically align values - static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output - static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options - static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them - static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results - static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged - static constexpr uint8_t VERB_TRACE = 5; // Step-by-step algorithm details - static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun? - // clang-format on - [[gnu::format(printf, 3, 4)]] - void verbosePrint(uint8_t level, char const *fmt, ...) const; - mutable bool hasTransparentPixels = false; uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; } diff --git a/include/link/main.hpp b/include/link/main.hpp index 11124cb9..86389e60 100644 --- a/include/link/main.hpp +++ b/include/link/main.hpp @@ -10,6 +10,7 @@ #include #include "linkdefs.hpp" +#include "verbosity.hpp" struct Options { bool isDmgMode; // -d @@ -25,20 +26,12 @@ struct Options { uint16_t scrambleWRAMX; uint16_t scrambleSRAM; bool is32kMode; // -t - bool beVerbose; // -v bool isWRAM0Mode; // -w bool disablePadding; // -x }; extern Options options; -#define verbosePrint(...) \ - do { \ - if (options.beVerbose) { \ - fprintf(stderr, __VA_ARGS__); \ - } \ - } while (0) - struct FileStackNode { FileStackNodeType type; std::variant< diff --git a/include/verbosity.hpp b/include/verbosity.hpp new file mode 100644 index 00000000..5b48c3ff --- /dev/null +++ b/include/verbosity.hpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +#ifndef RGBDS_VERBOSITY_HPP +#define RGBDS_VERBOSITY_HPP + +// This macro does not evaluate its arguments unless the condition is true. +#define verbosePrint(level, ...) \ + do { \ + if (checkVerbosity(level)) { \ + fprintf(stderr, __VA_ARGS__); \ + } \ + } while (0) + +enum Verbosity { + VERB_NONE, // 0. Default, no extra output + VERB_CONFIG, // 1. Basic configuration, after parsing CLI options + VERB_NOTICE, // 2. Before significant actions + VERB_INFO, // 3. Some intermediate action results + VERB_DEBUG, // 4. Internals useful for debugging + VERB_TRACE, // 5. Step-by-step algorithm details + VERB_VVVVVV, // 6. What, can't I have a little fun? +}; + +void incrementVerbosity(); +bool checkVerbosity(Verbosity level); + +void printVVVVVVerbosity(); + +#endif // RGBDS_VERBOSITY_HPP diff --git a/man/rgbasm.1 b/man/rgbasm.1 index ff285507..ca725dac 100644 --- a/man/rgbasm.1 +++ b/man/rgbasm.1 @@ -233,6 +233,24 @@ below). Print the version of the program and exit. .It Fl v , Fl \-verbose Be verbose. +The verbosity level is increased by one each time the flag is specified, with each level including the previous: +.Bl -enum -compact +.It +Print the +.Nm +configuration before taking actions. +.It +Print a notice before significant actions. +.It +Print some of the actions' intermediate results. +.It +Print some internal debug information. +.It +Print detailed internal information. +.El +The verbosity level does not go past 6. +.Pp +Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised. .It Fl W Ar warning , Fl \-warning Ar warning Set warning flag .Ar warning . diff --git a/man/rgbgfx.1 b/man/rgbgfx.1 index fee9d2a0..db4c3cf1 100644 --- a/man/rgbgfx.1 +++ b/man/rgbgfx.1 @@ -382,14 +382,17 @@ Be verbose. The verbosity level is increased by one each time the flag is specified, with each level including the previous: .Bl -enum -compact .It +Print the .Nm -prints out its configuration before doing anything. +configuration before taking actions. .It -A generic message is printed before doing most actions. +Print a notice before significant actions. .It -Some of the actions' intermediate results are printed. +Print some of the actions' intermediate results. .It -Some internal debug printing is enabled. +Print some internal debug information. +.It +Print detailed internal information. .El The verbosity level does not go past 6. .Pp diff --git a/man/rgblink.1 b/man/rgblink.1 index edbd376a..8bd1c72d 100644 --- a/man/rgblink.1 +++ b/man/rgblink.1 @@ -114,7 +114,25 @@ Useful for ROMs that fit in 32 KiB. .It Fl V , Fl \-version Print the version of the program and exit. .It Fl v , Fl \-verbose -Verbose: enable printing more information to standard error. +Be verbose. +The verbosity level is increased by one each time the flag is specified, with each level including the previous: +.Bl -enum -compact +.It +Print the +.Nm +configuration before taking actions. +.It +Print a notice before significant actions. +.It +Print some of the actions' intermediate results. +.It +Print some internal debug information. +.It +Print detailed internal information. +.El +The verbosity level does not go past 6. +.Pp +Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised. .It Fl W Ar warning , Fl \-warning Ar warning Set warning flag .Ar warning . diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5d69c7ef..59a6dcf1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,6 +53,7 @@ set(rgbasm_src "linkdefs.cpp" "opmath.cpp" "util.cpp" + "verbosity.cpp" ) set(rgblink_src @@ -72,6 +73,7 @@ set(rgblink_src "linkdefs.cpp" "opmath.cpp" "util.cpp" + "verbosity.cpp" ) set(rgbfix_src @@ -92,6 +94,7 @@ set(rgbgfx_src "gfx/rgba.cpp" "gfx/warning.cpp" "util.cpp" + "verbosity.cpp" ) foreach(PROG "asm" "fix" "gfx" "link") diff --git a/src/asm/fstack.cpp b/src/asm/fstack.cpp index 14f7ac86..0583024a 100644 --- a/src/asm/fstack.cpp +++ b/src/asm/fstack.cpp @@ -15,6 +15,7 @@ #include "helpers.hpp" #include "linkdefs.hpp" #include "platform.hpp" // S_ISDIR (stat macro) +#include "verbosity.hpp" #include "asm/lexer.hpp" #include "asm/macro.hpp" @@ -88,6 +89,28 @@ bool fstk_DumpCurrent() { return true; } +// LCOV_EXCL_START +void fstk_VerboseOutputConfig() { + assume(checkVerbosity(VERB_CONFIG)); + // -I/--include + if (includePaths.size() > 1) { + fputs("\tInclude file paths:\n", stderr); + for (std::string const &path : includePaths) { + if (!path.empty()) { + fprintf(stderr, "\t - %s\n", path.c_str()); + } + } + } + // -P/--preinclude + if (!preIncludeNames.empty()) { + fputs("\tPreincluded files:\n", stderr); + for (std::string const &name : preIncludeNames) { + fprintf(stderr, "\t - %s\n", name.c_str()); + } + } +} +// LCOV_EXCL_STOP + std::shared_ptr fstk_GetFileStack() { return contextStack.empty() ? nullptr : contextStack.top().fileInfo; } @@ -124,7 +147,6 @@ void fstk_AddIncludePath(std::string const &path) { void fstk_AddPreIncludeFile(std::string const &path) { preIncludeNames.emplace_front(path); - verbosePrint("Pre-included filename %s\n", path.c_str()); // LCOV_EXCL_LINE } static bool isValidFilePath(std::string const &path) { @@ -308,7 +330,11 @@ bool fstk_FileError(std::string const &path, char const *functionName) { // LCOV_EXCL_START if (options.missingIncludeState == GEN_EXIT) { verbosePrint( - "Aborting (-MG) on %s file '%s' (%s)\n", functionName, path.c_str(), strerror(errno) + VERB_NOTICE, + "Aborting (-MG) on %s file '%s' (%s)\n", + functionName, + path.c_str(), + strerror(errno) ); return true; } diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index 8fd23faf..4111e7ad 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -18,6 +18,7 @@ #include "helpers.hpp" #include "util.hpp" +#include "verbosity.hpp" #include "asm/fixpoint.hpp" #include "asm/format.hpp" @@ -325,7 +326,7 @@ void LexerState::setFileAsNextState(std::string const &filePath, bool updateStat if (filePath == "-") { path = ""; content.emplace(STDIN_FILENO); - verbosePrint("Opening stdin\n"); // LCOV_EXCL_LINE + verbosePrint(VERB_INFO, "Opening stdin\n"); // LCOV_EXCL_LINE } else { struct stat statBuf; if (stat(filePath.c_str(), &statBuf) != 0) { @@ -352,13 +353,17 @@ void LexerState::setFileAsNextState(std::string const &filePath, bool updateStat } content.emplace(ptr, size); - verbosePrint("File \"%s\" is fully read\n", path.c_str()); // LCOV_EXCL_LINE + // LCOV_EXCL_START + verbosePrint(VERB_INFO, "File \"%s\" is fully read\n", path.c_str()); + // LCOV_EXCL_STOP } else { // LCOV_EXCL_START if (statBuf.st_size == 0) { - verbosePrint("File \"%s\" is empty\n", path.c_str()); + verbosePrint(VERB_INFO, "File \"%s\" is empty\n", path.c_str()); } else { - verbosePrint("Failed to stat file \"%s\": %s\n", path.c_str(), strerror(errno)); + verbosePrint( + VERB_INFO, "Failed to stat file \"%s\": %s\n", path.c_str(), strerror(errno) + ); } // LCOV_EXCL_STOP @@ -371,7 +376,7 @@ void LexerState::setFileAsNextState(std::string const &filePath, bool updateStat } content.emplace(fd); - verbosePrint("File \"%s\" is opened\n", path.c_str()); // LCOV_EXCL_LINE + verbosePrint(VERB_INFO, "File \"%s\" is opened\n", path.c_str()); // LCOV_EXCL_LINE } } diff --git a/src/asm/main.cpp b/src/asm/main.cpp index a4c717fd..eb6781ad 100644 --- a/src/asm/main.cpp +++ b/src/asm/main.cpp @@ -17,6 +17,7 @@ #include "parser.hpp" // Generated from parser.y #include "usage.hpp" #include "util.hpp" // UpperMap +#include "verbosity.hpp" #include "version.hpp" #include "asm/charmap.hpp" @@ -28,23 +29,8 @@ Options options; -// Escapes Make-special chars from a string -static std::string make_escape(std::string &str) { - std::string escaped; - size_t pos = 0; - for (;;) { - // All dollars needs to be doubled - size_t nextPos = str.find('$', pos); - if (nextPos == std::string::npos) { - break; - } - escaped.append(str, pos, nextPos - pos); - escaped.append("$$"); - pos = nextPos + literal_strlen("$"); - } - escaped.append(str, pos, str.length() - pos); - return escaped; -} +static char const *dependFileName = nullptr; // -M +static std::unordered_map> stateFileSpecs; // -s // Short options static char const *optstring = "b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:"; @@ -105,6 +91,148 @@ static Usage usage( ); // clang-format on +// LCOV_EXCL_START +static void verboseOutputConfig(int argc, char *argv[]) { + if (!checkVerbosity(VERB_CONFIG)) { + return; + } + + fprintf(stderr, "rgbasm %s\n", get_package_version_string()); + + printVVVVVVerbosity(); + + fputs("Options:\n", stderr); + // -E/--export-all + if (options.exportAll) { + fputs("\tExport all labels by default\n", stderr); + } + // -b/--binary-digits + if (options.binDigits[0] != '0' || options.binDigits[1] != '1') { + fprintf( + stderr, "\tBinary digits: '%c', '%c'\n", options.binDigits[0], options.binDigits[1] + ); + } + // -g/--gfx-chars + if (options.gfxDigits[0] != '0' || options.gfxDigits[1] != '1' || options.gfxDigits[2] != '2' + || options.gfxDigits[3] != '3') { + fprintf( + stderr, + "\tGraphics characters: '%c', '%c', '%c', '%c'\n", + options.gfxDigits[0], + options.gfxDigits[1], + options.gfxDigits[2], + options.gfxDigits[3] + ); + } + // -Q/--q-precision + fprintf( + stderr, + "\tFixed-point precision: Q%d.%" PRIu8 "\n", + 32 - options.fixPrecision, + options.fixPrecision + ); + // -p/--pad-value + fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padByte); + // -r/--recursion-depth + fprintf(stderr, "\tMaximum recursion depth %zu\n", options.maxRecursionDepth); + // -X/--max-errors + if (options.maxErrors) { + fprintf(stderr, "\tMaximum %" PRIu64 " errors\n", options.maxErrors); + } + // -D/--define + static bool hasDefines = false; // `static` so `sym_ForEach` callback can see it + sym_ForEach([](Symbol &sym) { + if (!sym.isBuiltin && sym.type == SYM_EQUS) { + if (!hasDefines) { + fputs("\tDefinitions:\n", stderr); + hasDefines = true; + } + fprintf(stderr, "\t - def %s equs \"%s\"\n", sym.name.c_str(), sym.getEqus()->c_str()); + } + }); + // -s/--state + if (!stateFileSpecs.empty()) { + fputs("\tOutput state files:\n", stderr); + static char const *featureNames[NB_STATE_FEATURES] = { + "equ", + "var", + "equs", + "char", + "macro", + }; + for (auto [name, features] : stateFileSpecs) { + fprintf(stderr, "\t - %s: ", name == "-" ? "" : name.c_str()); + for (size_t i = 0; i < features.size(); ++i) { + if (i > 0) { + fputs(", ", stderr); + } + fputs(featureNames[features[i]], stderr); + } + putc('\n', stderr); + } + } + // asmfile + if (musl_optind < argc) { + fprintf(stderr, "\tInput asm file: %s", argv[musl_optind]); + if (musl_optind + 1 < argc) { + fprintf(stderr, " (and %d more)", argc - musl_optind - 1); + } + putc('\n', stderr); + } + // -o/--output + if (!options.objectFileName.empty()) { + fprintf(stderr, "\tOutput object file: %s\n", options.objectFileName.c_str()); + } + fstk_VerboseOutputConfig(); + if (dependFileName) { + fprintf( + stderr, + "\tOutput dependency file: %s\n", + strcmp(dependFileName, "-") ? dependFileName : "" + ); + // -MT or -MQ + if (!options.targetFileName.empty()) { + fprintf(stderr, "\tTarget file(s): %s\n", options.targetFileName.c_str()); + } + // -MG or -MC + switch (options.missingIncludeState) { + case INC_ERROR: + fputs("\tExit with an error on a missing dependency\n", stderr); + break; + case GEN_EXIT: + fputs("\tExit normally on a missing dependency\n", stderr); + break; + case GEN_CONTINUE: + fputs("\tContinue processing after a missing dependency\n", stderr); + break; + } + // -MP + if (options.generatePhonyDeps) { + fputs("\tGenerate phony dependencies\n", stderr); + } + // [-MG] [-MC] + } + fputs("Ready.\n", stderr); +} +// LCOV_EXCL_STOP + +static std::string escapeMakeChars(std::string &str) { + std::string escaped; + size_t pos = 0; + for (;;) { + // All dollars needs to be doubled + size_t nextPos = str.find('$', pos); + if (nextPos == std::string::npos) { + break; + } + escaped.append(str, pos, nextPos - pos); + escaped.append("$$"); + pos = nextPos + literal_strlen("$"); + } + escaped.append(str, pos, str.length() - pos); + return escaped; +} + // Parse a comma-separated string of '-s/--state' features static std::vector parseStateFeatures(char *str) { std::vector features; @@ -164,10 +292,6 @@ int main(int argc, char *argv[]) { options.maxErrors = 100; } - // Local options - char const *dependFileName = nullptr; // -M - std::unordered_map> stateFileSpecs; // -s - for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { switch (ch) { char *endptr; @@ -192,7 +316,7 @@ int main(int argc, char *argv[]) { break; case 'E': - sym_SetExportAll(true); + options.exportAll = true; break; case 'g': @@ -211,21 +335,13 @@ int main(int argc, char *argv[]) { break; case 'M': - if (options.dependFile) { - warnx("Overriding dependfile %s", dependFileName); - } - if (strcmp("-", musl_optarg)) { - options.dependFile = fopen(musl_optarg, "w"); - dependFileName = musl_optarg; - } else { - options.dependFile = stdout; - dependFileName = ""; - } - if (options.dependFile == nullptr) { - // LCOV_EXCL_START - fatal("Failed to open dependfile \"%s\": %s", dependFileName, strerror(errno)); - // LCOV_EXCL_STOP + if (dependFileName) { + warnx( + "Overriding dependency file %s", + strcmp(dependFileName, "-") ? dependFileName : "" + ); } + dependFileName = musl_optarg; break; case 'o': @@ -233,7 +349,6 @@ int main(int argc, char *argv[]) { warnx("Overriding output filename %s", options.objectFileName.c_str()); } options.objectFileName = musl_optarg; - verbosePrint("Output filename %s\n", options.objectFileName.c_str()); // LCOV_EXCL_LINE break; case 'P': @@ -295,7 +410,6 @@ int main(int argc, char *argv[]) { if (stateFileSpecs.find(name) != stateFileSpecs.end()) { warnx("Overriding state filename %s", name); } - verbosePrint("State filename %s\n", name); // LCOV_EXCL_LINE stateFileSpecs.emplace(name, std::move(features)); break; } @@ -306,7 +420,7 @@ int main(int argc, char *argv[]) { case 'v': // LCOV_EXCL_START - options.verbose = true; + incrementVerbosity(); break; // LCOV_EXCL_STOP @@ -352,7 +466,7 @@ int main(int argc, char *argv[]) { case 'T': { std::string newTarget = musl_optarg; if (depType == 'Q') { - newTarget = make_escape(newTarget); + newTarget = escapeMakeChars(newTarget); } if (!options.targetFileName.empty()) { options.targetFileName += ' '; @@ -373,6 +487,8 @@ int main(int argc, char *argv[]) { options.targetFileName = options.objectFileName; } + verboseOutputConfig(argc, argv); + if (argc == musl_optind) { usage.printAndExit("Please specify an input file (pass `-` to read from standard input)"); } else if (argc != musl_optind + 1) { @@ -381,7 +497,20 @@ int main(int argc, char *argv[]) { std::string mainFileName = argv[musl_optind]; - verbosePrint("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE + verbosePrint(VERB_NOTICE, "Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE + + if (dependFileName) { + if (strcmp("-", dependFileName)) { + options.dependFile = fopen(dependFileName, "w"); + if (options.dependFile == nullptr) { + // LCOV_EXCL_START + fatal("Failed to open dependency file \"%s\": %s", dependFileName, strerror(errno)); + // LCOV_EXCL_STOP + } + } else { + options.dependFile = stdout; + } + } if (options.dependFile && options.targetFileName.empty()) { fatal("Dependency files can only be created if a target file is specified with either " diff --git a/src/asm/symbol.cpp b/src/asm/symbol.cpp index 59aaccae..4dc23a8c 100644 --- a/src/asm/symbol.cpp +++ b/src/asm/symbol.cpp @@ -17,6 +17,7 @@ #include "asm/fstack.hpp" #include "asm/lexer.hpp" #include "asm/macro.hpp" +#include "asm/main.hpp" #include "asm/output.hpp" #include "asm/warning.hpp" @@ -39,8 +40,6 @@ static char savedDATE[256]; static char savedTIMESTAMP_ISO8601_LOCAL[256]; static char savedTIMESTAMP_ISO8601_UTC[256]; -static bool exportAll = false; // -E - bool sym_IsPC(Symbol const *sym) { return sym == PCSymbol; } @@ -497,7 +496,7 @@ static Symbol *addLabel(std::string const &symName) { sym->type = SYM_LABEL; sym->data = static_cast(sect_GetSymbolOffset()); // Don't export anonymous labels - if (exportAll && !symName.starts_with('!')) { + if (options.exportAll && !symName.starts_with('!')) { sym->isExported = true; } sym->section = sect_GetSymbolSection(); @@ -635,11 +634,6 @@ Symbol *sym_Ref(std::string const &symName) { return sym; } -// Set whether to export all relocatable symbols by default -void sym_SetExportAll(bool set) { - exportAll = set; -} - // Define the built-in symbols void sym_Init(time_t now) { PCSymbol = &createSymbol("@"s); diff --git a/src/fix/main.cpp b/src/fix/main.cpp index 89a962a9..132a24dd 100644 --- a/src/fix/main.cpp +++ b/src/fix/main.cpp @@ -65,13 +65,14 @@ static Usage usage( " [-n ] [-p ] [-r ] [-t ]\n" " [-W warning] ...\n" "Useful options:\n" - " -m, --mbc-type set the MBC type byte to this value; refer\n" - " to the man page for a list of values\n" - " -p, --pad-value pad to the next valid size using this value\n" - " -r, --ram-size set the cart RAM size byte to this value\n" - " -o, --output set the output file\n" - " -V, --version print RGBFIX version and exit\n" - " -v, --validate fix the header logo and both checksums (-f lhg)\n" + " -m, --mbc-type set the MBC type byte to this value; `-m help'\n" + " or `-m list' prints the accepted values\n" + " -p, --pad-value pad to the next valid size using this value\n" + " -r, --ram-size set the cart RAM size byte to this value\n" + " -o, --output set the output file\n" + " -V, --version print RGBFIX version and exit\n" + " -v, --validate fix the header logo and both checksums (`-f lhg')\n" + " -W, --warning enable or disable warnings\n" "\n" "For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n" ); diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index 28ef741e..d1ffbec1 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -19,6 +19,7 @@ #include "file.hpp" #include "platform.hpp" #include "usage.hpp" +#include "verbosity.hpp" #include "version.hpp" #include "gfx/pal_spec.hpp" @@ -40,18 +41,6 @@ static struct LocalOptions { bool reverse; } localOptions; -void Options::verbosePrint(uint8_t level, char const *fmt, ...) const { - // LCOV_EXCL_START - if (verbosity >= level) { - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - } - // LCOV_EXCL_STOP -} - // Short options static char const *optstring = "-Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:wXx:YZ"; @@ -107,11 +96,12 @@ static Usage usage( " [-o ] [-p | -P] [-q | -Q]\n" " [-s ] [-t | -T] [-x ] \n" "Useful options:\n" - " -m, --mirror-tiles optimize out mirrored tiles\n" - " -o, --output output the tile data to this path\n" - " -t, --tilemap output the tile map to this path\n" - " -u, --unique-tiles optimize out identical tiles\n" - " -V, --version print RGBGFX version and exit\n" + " -m, --mirror-tiles optimize out mirrored tiles\n" + " -o, --output output the tile data to this path\n" + " -t, --tilemap output the tile map to this path\n" + " -u, --unique-tiles optimize out identical tiles\n" + " -V, --version print RGBGFX version and exit\n" + " -W, --warning enable or disable warnings\n" "\n" "For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n" ); @@ -536,9 +526,7 @@ static char *parseArgv(int argc, char *argv[]) { // LCOV_EXCL_STOP case 'v': // LCOV_EXCL_START - if (options.verbosity < Options::VERB_VVVVVV) { - ++options.verbosity; - } + incrementVerbosity(); break; // LCOV_EXCL_STOP case 'W': @@ -580,93 +568,66 @@ static char *parseArgv(int argc, char *argv[]) { return nullptr; // Done processing this argv } +// LCOV_EXCL_START static void verboseOutputConfig() { - fprintf(stderr, "rgbgfx %s\n", get_package_version_string()); - - if (options.verbosity >= Options::VERB_VVVVVV) { - putc('\n', stderr); - // clang-format off: vertically align values - static std::array gfx{ - 0b0111111110, - 0b1111111111, - 0b1110011001, - 0b1110011001, - 0b1111111111, - 0b1111111111, - 0b1110000001, - 0b1111000011, - 0b0111111110, - 0b0001111000, - 0b0111111110, - 0b1111111111, - 0b1111111111, - 0b1111111111, - 0b1101111011, - 0b1101111011, - 0b0011111100, - 0b0011001100, - 0b0111001110, - 0b0111001110, - 0b0111001110, - }; - // clang-format on - static std::array textbox{ - " ,----------------------------------------.", - " | Augh, dimensional interference again?! |", - " `----------------------------------------'", - }; - for (size_t i = 0; i < gfx.size(); ++i) { - uint16_t row = gfx[i]; - for (uint8_t _ = 0; _ < 10; ++_) { - unsigned char c = row & 1 ? '0' : ' '; - putc(c, stderr); - // Double the pixel horizontally, otherwise the aspect ratio looks wrong - putc(c, stderr); - row >>= 1; - } - if (i < textbox.size()) { - fputs(textbox[i], stderr); - } - putc('\n', stderr); - } - putc('\n', stderr); + if (!checkVerbosity(VERB_CONFIG)) { + return; } + fprintf(stderr, "rgbgfx %s\n", get_package_version_string()); + + printVVVVVVerbosity(); + fputs("Options:\n", stderr); + // -Z/--columns if (options.columnMajor) { fputs("\tVisit image in column-major order\n", stderr); } + // -u/--unique-tiles if (options.allowDedup) { - fputs("\tAllow deduplicating tiles\n", stderr); + fputs("\tAllow deduplicating identical tiles\n", stderr); } - if (options.allowMirroringX) { + // -m/--mirror-tiles + if (options.allowMirroringX && options.allowMirroringY) { + fputs("\tAllow deduplicating mirrored tiles\n", stderr); + } + // -X/--mirror-x + else if (options.allowMirroringX) { fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr); } - if (options.allowMirroringY) { + // -Y/--mirror-y + else if (options.allowMirroringY) { fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr); } + // -C/--color-curve if (options.useColorCurve) { fputs("\tUse color curve\n", stderr); } + // -d/--depth fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth); + // -x/--trim-end if (options.trim != 0) { fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim); } + // -n/--nb-palettes fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes); + // -s/--palette-size fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal); - fprintf(stderr, "\t%s palette spec\n", [] { - switch (options.palSpecType) { - case Options::NO_SPEC: - return "No"; - case Options::EXPLICIT: - return "Explicit"; - case Options::EMBEDDED: - return "Embedded"; - case Options::DMG: - return "DMG"; - } - return "???"; - }()); + // -c/--colors + if (options.palSpecType != Options::NO_SPEC) { + fprintf(stderr, "\t%s palette spec\n", [] { + switch (options.palSpecType) { + case Options::EXPLICIT: + return "Explicit"; + case Options::EMBEDDED: + return "Embedded"; + case Options::DMG: + return "DMG"; + default: + return "???"; + } + }()); + } if (options.palSpecType == Options::EXPLICIT) { fputs("\t[\n", stderr); for (auto const &pal : options.palSpec) { @@ -682,40 +643,65 @@ static void verboseOutputConfig() { } fputs("\t]\n", stderr); } + // -L/--slice + if (options.inputSlice.specified()) { + fprintf( + stderr, + "\tInput image slice: %" PRIu16 "x%" PRIu16 " pixels starting at (%" PRIu16 ", %" PRIu16 + ")\n", + options.inputSlice.width, + options.inputSlice.height, + options.inputSlice.left, + options.inputSlice.top + ); + } + // -b/--base-tiles + if (options.baseTileIDs[0] || options.baseTileIDs[1]) { + fprintf( + stderr, + "\tBase tile IDs: bank 0 = 0x%02" PRIx8 ", bank 1 = 0x%02" PRIx8 "\n", + options.baseTileIDs[0], + options.baseTileIDs[1] + ); + } + // -l/--base-palette + if (options.basePalID) { + fprintf(stderr, "\tBase palette ID: %" PRIu8 "\n", options.basePalID); + } + // -N/--nb-tiles fprintf( stderr, - "\tInput image slice: %" PRIu16 "x%" PRIu16 " pixels starting at (%" PRIu16 ", %" PRIu16 - ")\n", - options.inputSlice.width, - options.inputSlice.height, - options.inputSlice.left, - options.inputSlice.top - ); - fprintf( - stderr, - "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", - options.baseTileIDs[0], - options.baseTileIDs[1] - ); - fprintf(stderr, "\tBase palette ID: %" PRIu8 "\n", options.basePalID); - fprintf( - stderr, - "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n", + "\tMaximum %" PRIu16 " tiles in bank 0, and %" PRIu16 " in bank 1\n", options.maxNbTiles[0], options.maxNbTiles[1] ); + // -O/--group-outputs (influences other options) auto printPath = [](char const *name, std::string const &path) { if (!path.empty()) { fprintf(stderr, "\t%s: %s\n", name, path.c_str()); } }; + // file printPath("Input image", options.input); + // -i/--input-tileset + printPath("Input tileset", options.inputTileset); + // -o/--output printPath("Output tile data", options.output); + // -t/--tilemap or -T/--auto-tilemap printPath("Output tilemap", options.tilemap); + // -a/--attrmap or -A/--auto-attrmap printPath("Output attrmap", options.attrmap); + // -p/--palette or -P/--auto-palette printPath("Output palettes", options.palettes); + // -q/--palette-map or -Q/--auto-palette-map + printPath("Output palette map", options.palmap); + // -r/--reverse + if (localOptions.reverse) { + fprintf(stderr, "\tReverse image width: %" PRIu16 " tiles\n", options.reversedWidth); + } fputs("Ready.\n", stderr); } +// LCOV_EXCL_STOP // Manual implementation of std::filesystem::path.replace_extension(). // macOS <10.15 did not support std::filesystem::path. @@ -839,11 +825,7 @@ int main(int argc, char *argv[]) { parseExternalPalSpec(localOptions.externalPalSpec); } - // LCOV_EXCL_START - if (options.verbosity >= Options::VERB_CFG) { - verboseOutputConfig(); - } - // LCOV_EXCL_STOP + verboseOutputConfig(); // LCOV_EXCL_LINE // Do not do anything if option parsing went wrong. requireZeroErrors(); diff --git a/src/gfx/pal_packing.cpp b/src/gfx/pal_packing.cpp index d2322232..aea9aa67 100644 --- a/src/gfx/pal_packing.cpp +++ b/src/gfx/pal_packing.cpp @@ -14,6 +14,7 @@ #include #include "helpers.hpp" +#include "verbosity.hpp" #include "gfx/color_set.hpp" #include "gfx/main.hpp" @@ -247,6 +248,10 @@ public: static void verboseOutputAssignments( std::vector const &assignments, std::vector const &colorSets ) { + if (!checkVerbosity(VERB_INFO)) { + return; + } + for (AssignedSets const &assignment : assignments) { fputs("{ ", stderr); for (ColorSetAttrs const &attrs : assignment) { @@ -284,9 +289,7 @@ static void decant(std::vector &assignments, std::vector } }; - options.verbosePrint( - Options::VERB_DEBUG, "%zu palettes before decanting\n", assignments.size() - ); + verbosePrint(VERB_DEBUG, "%zu palettes before decanting\n", assignments.size()); // Decant on palettes decantOn([&colorSets](AssignedSets &to, AssignedSets &from) { @@ -298,9 +301,7 @@ static void decant(std::vector &assignments, std::vector from.clear(); } }); - options.verbosePrint( - Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n", assignments.size() - ); + verbosePrint(VERB_DEBUG, "%zu palettes after decanting on palettes\n", assignments.size()); // Decant on "components" (color sets sharing colors) decantOn([&colorSets](AssignedSets &to, AssignedSets &from) { @@ -344,8 +345,8 @@ static void decant(std::vector &assignments, std::vector } } }); - options.verbosePrint( - Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", assignments.size() + verbosePrint( + VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", assignments.size() ); // Decant on individual color sets @@ -357,15 +358,11 @@ static void decant(std::vector &assignments, std::vector } } }); - options.verbosePrint( - Options::VERB_DEBUG, "%zu palettes after decanting on color sets\n", assignments.size() - ); + verbosePrint(VERB_DEBUG, "%zu palettes after decanting on color sets\n", assignments.size()); } std::tuple, size_t> overloadAndRemove(std::vector const &colorSets) { - options.verbosePrint( - Options::VERB_LOG_ACT, "Paginating palettes using \"overload-and-remove\" strategy...\n" - ); + verbosePrint(VERB_NOTICE, "Paginating palettes using \"overload-and-remove\" strategy...\n"); // Sort the color sets by size, which improves the packing algorithm's efficiency auto const indexOfLargestColorSetFirst = [&colorSets](size_t left, size_t right) { @@ -389,7 +386,7 @@ std::tuple, size_t> overloadAndRemove(std::vector !queue.empty(); queue.pop()) { ColorSetAttrs const &attrs = queue.front(); // Valid until the `queue.pop()` - options.verbosePrint(Options::VERB_TRACE, "Handling color set %zu\n", attrs.colorSetIndex); + verbosePrint(VERB_TRACE, "Handling color set %zu\n", attrs.colorSetIndex); ColorSet const &colorSet = colorSets[attrs.colorSetIndex]; size_t bestPalIndex = assignments.size(); @@ -404,8 +401,8 @@ std::tuple, size_t> overloadAndRemove(std::vector } uint32_t relSize = assignments[i].relSizeOf(colorSet); - options.verbosePrint( - Options::VERB_TRACE, + verbosePrint( + VERB_TRACE, " Relative size to palette %zu (of %zu): %" PRIu32 " (size = %zu)\n", i, assignments.size(), @@ -420,8 +417,8 @@ std::tuple, size_t> overloadAndRemove(std::vector if (bestPalIndex == assignments.size()) { // Found nowhere to put it, create a new page containing just that one - options.verbosePrint( - Options::VERB_TRACE, + verbosePrint( + VERB_TRACE, "Assigning color set %zu to new palette %zu\n", attrs.colorSetIndex, bestPalIndex @@ -430,8 +427,8 @@ std::tuple, size_t> overloadAndRemove(std::vector continue; } - options.verbosePrint( - Options::VERB_TRACE, + verbosePrint( + VERB_TRACE, "Assigning color set %zu to palette %zu\n", attrs.colorSetIndex, bestPalIndex @@ -448,8 +445,8 @@ std::tuple, size_t> overloadAndRemove(std::vector uint32_t relSize1 = bestPal.relSizeOf(colorSet1); uint32_t relSize2 = bestPal.relSizeOf(colorSet2); - options.verbosePrint( - Options::VERB_TRACE, + verbosePrint( + VERB_TRACE, " Color sets %zu <=> %zu: Efficiency: %zu / %" PRIu32 " <=> %zu / " "%" PRIu32 "\n", attrs1.colorSetIndex, @@ -470,8 +467,8 @@ std::tuple, size_t> overloadAndRemove(std::vector // If this overloads the palette, get it back to normal (if possible) while (bestPal.volume() > options.maxOpaqueColors()) { - options.verbosePrint( - Options::VERB_TRACE, + verbosePrint( + VERB_TRACE, "Palette %zu is overloaded! (%zu > %" PRIu8 ")\n", bestPalIndex, bestPal.volume(), @@ -488,13 +485,13 @@ std::tuple, size_t> overloadAndRemove(std::vector // All efficiencies are identical iff min equals max if (compareEfficiency(*minEfficiencyIter, *maxEfficiencyIter) == 0) { - options.verbosePrint(Options::VERB_TRACE, " All efficiencies are identical\n"); + verbosePrint(VERB_TRACE, " All efficiencies are identical\n"); break; } // Remove the color set with minimal efficiency - options.verbosePrint( - Options::VERB_TRACE, " Removing color set %zu\n", minEfficiencyIter->colorSetIndex + verbosePrint( + VERB_TRACE, " Removing color set %zu\n", minEfficiencyIter->colorSetIndex ); queue.emplace(std::move(*minEfficiencyIter)); queue.back().banFrom(bestPalIndex); // Ban it from this palette @@ -526,16 +523,16 @@ std::tuple, size_t> overloadAndRemove(std::vector return pal.canFit(colorSet); }); if (iter == assignments.end()) { // No such page, create a new one - options.verbosePrint( - Options::VERB_DEBUG, + verbosePrint( + VERB_DEBUG, "Adding new palette (%zu) for overflowing color set %zu\n", assignments.size(), attrs.colorSetIndex ); assignments.emplace_back(colorSets, std::move(attrs)); } else { - options.verbosePrint( - Options::VERB_DEBUG, + verbosePrint( + VERB_DEBUG, "Assigning overflowing color set %zu to palette %zu\n", attrs.colorSetIndex, iter - assignments.begin() @@ -544,21 +541,13 @@ std::tuple, size_t> overloadAndRemove(std::vector } } - // LCOV_EXCL_START - if (options.verbosity >= Options::VERB_INTERM) { - verboseOutputAssignments(assignments, colorSets); - } - // LCOV_EXCL_STOP + verboseOutputAssignments(assignments, colorSets); // LCOV_EXCL_LINE // "Decant" the result decant(assignments, colorSets); // Note that the result does not contain any empty palettes - // LCOV_EXCL_START - if (options.verbosity >= Options::VERB_INTERM) { - verboseOutputAssignments(assignments, colorSets); - } - // LCOV_EXCL_STOP + verboseOutputAssignments(assignments, colorSets); // LCOV_EXCL_LINE std::vector mappings(colorSets.size()); for (size_t i = 0; i < assignments.size(); ++i) { diff --git a/src/gfx/pal_sorting.cpp b/src/gfx/pal_sorting.cpp index 08878777..dadefcb4 100644 --- a/src/gfx/pal_sorting.cpp +++ b/src/gfx/pal_sorting.cpp @@ -5,11 +5,12 @@ #include #include "helpers.hpp" +#include "verbosity.hpp" #include "gfx/main.hpp" void sortIndexed(std::vector &palettes, std::vector const &embPal) { - options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes using embedded palette...\n"); + verbosePrint(VERB_NOTICE, "Sorting palettes using embedded palette...\n"); for (Palette &pal : palettes) { std::sort(RANGE(pal), [&](uint16_t lhs, uint16_t rhs) { @@ -35,7 +36,7 @@ void sortIndexed(std::vector &palettes, std::vector const &embPal void sortGrayscale( std::vector &palettes, std::array, NB_COLOR_SLOTS> const &colors ) { - options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palette by grayscale bins...\n"); + verbosePrint(VERB_NOTICE, "Sorting palette by grayscale bins...\n"); // This method is only applicable if there are at most as many colors as colors per palette, so // we should only have a single palette. @@ -59,7 +60,7 @@ static unsigned int luminance(uint16_t color) { } void sortRgb(std::vector &palettes) { - options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by luminance...\n"); + verbosePrint(VERB_NOTICE, "Sorting palettes by luminance...\n"); for (Palette &pal : palettes) { // Sort from lightest to darkest diff --git a/src/gfx/png.cpp b/src/gfx/png.cpp index efcbd978..4ba777b8 100644 --- a/src/gfx/png.cpp +++ b/src/gfx/png.cpp @@ -4,6 +4,8 @@ #include +#include "verbosity.hpp" + #include "gfx/main.hpp" #include "gfx/rgba.hpp" #include "gfx/warning.hpp" @@ -47,7 +49,7 @@ static void readData(png_structp png, png_bytep data, size_t length) { Png::Png(char const *filename, std::streambuf &file) { Input input(filename, file); - options.verbosePrint(Options::VERB_LOG_ACT, "Reading PNG file \"%s\"\n", input.filename); + verbosePrint(VERB_NOTICE, "Reading PNG file \"%s\"\n", input.filename); std::array pngHeader; if (input.file.sgetn(reinterpret_cast(pngHeader.data()), pngHeader.size()) @@ -56,7 +58,7 @@ Png::Png(char const *filename, std::streambuf &file) { fatal("File \"%s\" is not a valid PNG image", input.filename); // LCOV_EXCL_LINE } - options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n"); + verbosePrint(VERB_INFO, "PNG header signature is OK\n"); png_structp png = png_create_read_struct( PNG_LIBPNG_VER_STRING, static_cast(&input), handleError, handleWarning @@ -110,8 +112,8 @@ Png::Png(char const *filename, std::streambuf &file) { return "unknown interlace type"; } }; - options.verbosePrint( - Options::VERB_INTERM, + verbosePrint( + VERB_INFO, "PNG image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n", width, height, @@ -139,18 +141,15 @@ Png::Png(char const *filename, std::streambuf &file) { ); } - options.verbosePrint( - Options::VERB_INTERM, "Embedded PNG palette has %d colors: [", nbColors - ); - for (int i = 0; i < nbColors; ++i) { - if (i > 0) { - options.verbosePrint(Options::VERB_INTERM, ", "); + if (checkVerbosity(VERB_INFO)) { + fprintf(stderr, "Embedded PNG palette has %d colors: [", nbColors); + for (int i = 0; i < nbColors; ++i) { + fprintf(stderr, "%s#%08x", i > 0 ? ", " : "", palette[i].toCSS()); } - options.verbosePrint(Options::VERB_INTERM, "#%08x", palette[i].toCSS()); + fprintf(stderr, "]\n"); } - options.verbosePrint(Options::VERB_INTERM, "]\n"); } else { - options.verbosePrint(Options::VERB_INTERM, "No embedded PNG palette\n"); + verbosePrint(VERB_INFO, "No embedded PNG palette\n"); } // Set up transformations to turn everything into RGBA8888 for simplicity of handling diff --git a/src/gfx/process.cpp b/src/gfx/process.cpp index 7d2bd539..df0f4668 100644 --- a/src/gfx/process.cpp +++ b/src/gfx/process.cpp @@ -19,6 +19,7 @@ #include "file.hpp" #include "helpers.hpp" #include "itertools.hpp" +#include "verbosity.hpp" #include "gfx/color_set.hpp" #include "gfx/main.hpp" @@ -79,8 +80,8 @@ struct Image { bool isSuitableForGrayscale() const { // Check that all of the grays don't fall into the same "bin" if (colors.size() > options.maxOpaqueColors()) { // Apply the Pigeonhole Principle - options.verbosePrint( - Options::VERB_DEBUG, + verbosePrint( + VERB_DEBUG, "Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n", colors.size(), options.maxOpaqueColors() @@ -93,8 +94,8 @@ struct Image { continue; } if (!color->isGray()) { - options.verbosePrint( - Options::VERB_DEBUG, + verbosePrint( + VERB_DEBUG, "Found non-gray color #%08x, not using grayscale sorting\n", color->toCSS() ); @@ -102,8 +103,8 @@ struct Image { } uint8_t mask = 1 << color->grayIndex(); if (bins & mask) { // Two in the same bin! - options.verbosePrint( - Options::VERB_DEBUG, + verbosePrint( + VERB_DEBUG, "Color #%08x conflicts with another one, not using grayscale sorting\n", color->toCSS() ); @@ -336,7 +337,7 @@ static std::tuple, std::vector> assume(mappings.size() == colorSets.size()); // LCOV_EXCL_START - if (options.verbosity >= Options::VERB_INTERM) { + if (checkVerbosity(VERB_INFO)) { fprintf( stderr, "Color set mappings: (%zu palette%s)\n", nbPalettes, nbPalettes != 1 ? "s" : "" ); @@ -438,7 +439,7 @@ static std::tuple, std::vector> static void outputPalettes(std::vector const &palettes) { // LCOV_EXCL_START - if (options.verbosity >= Options::VERB_INTERM) { + if (checkVerbosity(VERB_INFO)) { for (Palette const &palette : palettes) { fputs("{ ", stderr); for (uint16_t colorIndex : palette) { @@ -897,7 +898,7 @@ static void } void processPalettes() { - options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); + verbosePrint(VERB_CONFIG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); std::vector colorSets; std::vector palettes; @@ -907,13 +908,13 @@ void processPalettes() { } void process() { - options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); + verbosePrint(VERB_CONFIG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); - options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n"); + verbosePrint(VERB_NOTICE, "Reading tiles...\n"); Image image(options.input); // This also sets `hasTransparentPixels` as a side effect // LCOV_EXCL_START - if (options.verbosity >= Options::VERB_INTERM) { + if (checkVerbosity(VERB_INFO)) { fputs("Image colors: [ ", stderr); for (std::optional const &slot : image.colors) { if (!slot.has_value()) { @@ -1025,14 +1026,14 @@ void process() { continue_visiting_tiles:; } - options.verbosePrint( - Options::VERB_INTERM, + verbosePrint( + VERB_INFO, "Image contains %zu color set%s\n", colorSets.size(), colorSets.size() != 1 ? "s" : "" ); // LCOV_EXCL_START - if (options.verbosity >= Options::VERB_INTERM) { + if (checkVerbosity(VERB_INFO)) { for (ColorSet const &colorSet : colorSets) { fputs("[ ", stderr); for (uint16_t color : colorSet) { @@ -1074,20 +1075,19 @@ continue_visiting_tiles:; } if (!options.output.empty()) { - options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n"); + verbosePrint(VERB_NOTICE, "Generating unoptimized tile data...\n"); outputUnoptimizedTileData(image, attrmap, palettes, mappings); } if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) { - options.verbosePrint( - Options::VERB_LOG_ACT, - "Generating unoptimized tilemap and/or attrmap and/or palmap...\n" + verbosePrint( + VERB_NOTICE, "Generating unoptimized tilemap and/or attrmap and/or palmap...\n" ); outputUnoptimizedMaps(attrmap, mappings); } } else { // All of these require the deduplication process to be performed to be output - options.verbosePrint(Options::VERB_LOG_ACT, "Deduplicating tiles...\n"); + verbosePrint(VERB_NOTICE, "Deduplicating tiles...\n"); UniqueTiles tiles = dedupTiles(image, attrmap, palettes, mappings); if (size_t nbTiles = tiles.size(); @@ -1101,22 +1101,22 @@ continue_visiting_tiles:; } if (!options.output.empty()) { - options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tile data...\n"); + verbosePrint(VERB_NOTICE, "Generating optimized tile data...\n"); outputTileData(tiles); } if (!options.tilemap.empty()) { - options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tilemap...\n"); + verbosePrint(VERB_NOTICE, "Generating optimized tilemap...\n"); outputTilemap(attrmap); } if (!options.attrmap.empty()) { - options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n"); + verbosePrint(VERB_NOTICE, "Generating optimized attrmap...\n"); outputAttrmap(attrmap, mappings); } if (!options.palmap.empty()) { - options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized palmap...\n"); + verbosePrint(VERB_NOTICE, "Generating optimized palmap...\n"); outputPalmap(attrmap, mappings); } } diff --git a/src/gfx/reverse.cpp b/src/gfx/reverse.cpp index a8daa2db..5cdc97de 100644 --- a/src/gfx/reverse.cpp +++ b/src/gfx/reverse.cpp @@ -15,6 +15,7 @@ #include "diagnostics.hpp" #include "file.hpp" #include "helpers.hpp" // assume +#include "verbosity.hpp" #include "gfx/main.hpp" #include "gfx/warning.hpp" @@ -98,7 +99,7 @@ static void printPalette(std::array, 4> const &palette) { } void reverse() { - options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); + verbosePrint(VERB_CONFIG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); // Check for weird flag combinations @@ -127,7 +128,7 @@ void reverse() { ); } - options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n"); + verbosePrint(VERB_NOTICE, "Reading tiles...\n"); std::vector const tiles = readInto(options.output); uint8_t tileSize = 8 * options.bitDepth; if (tiles.size() % tileSize != 0) { @@ -140,13 +141,13 @@ void reverse() { // By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles size_t const nbTiles = tiles.size() / tileSize; - options.verbosePrint(Options::VERB_INTERM, "Read %zu tiles.\n", nbTiles); + verbosePrint(VERB_INFO, "Read %zu tiles.\n", nbTiles); size_t mapSize = nbTiles + options.trim; // Image size in tiles std::optional> tilemap; if (!options.tilemap.empty()) { tilemap = readInto(options.tilemap); mapSize = tilemap->size(); - options.verbosePrint(Options::VERB_INTERM, "Read %zu tilemap entries.\n", mapSize); + verbosePrint(VERB_INFO, "Read %zu tilemap entries.\n", mapSize); } if (mapSize == 0) { @@ -172,7 +173,7 @@ void reverse() { break; } } - options.verbosePrint(Options::VERB_INTERM, "Picked reversing width of %zu tiles\n", width); + verbosePrint(VERB_INFO, "Picked reversing width of %zu tiles\n", width); } if (mapSize % width != 0) { if (options.trim == 0 && !tilemap) { @@ -192,9 +193,7 @@ void reverse() { } height = mapSize / width; - options.verbosePrint( - Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height - ); + verbosePrint(VERB_INFO, "Reversed image dimensions: %zux%zu tiles\n", width, height); Rgba const grayColors[4] = { Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF) @@ -320,8 +319,8 @@ void reverse() { } } - options.verbosePrint( - Options::VERB_INTERM, + verbosePrint( + VERB_INFO, "Number of tiles in bank {0: %" PRIu16 ", 1: %" PRIu16 "}\n", nbTilesInBank[0], nbTilesInBank[1] @@ -404,7 +403,7 @@ void reverse() { } } - options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n"); + verbosePrint(VERB_NOTICE, "Writing image...\n"); File pngFile; if (!pngFile.open(options.input, std::ios::out | std::ios::binary)) { // LCOV_EXCL_START diff --git a/src/link/assign.cpp b/src/link/assign.cpp index e1ce2560..ac2dbf1e 100644 --- a/src/link/assign.cpp +++ b/src/link/assign.cpp @@ -14,6 +14,7 @@ #include "itertools.hpp" #include "linkdefs.hpp" #include "platform.hpp" +#include "verbosity.hpp" #include "link/main.hpp" #include "link/output.hpp" @@ -405,7 +406,7 @@ static std::vector
checkOverlayCompat() { } void assign_AssignSections() { - verbosePrint("Beginning assignment...\n"); + verbosePrint(VERB_NOTICE, "Beginning assignment...\n"); // Initialize assignment initFreeSpace(); @@ -443,7 +444,7 @@ void assign_AssignSections() { // Assign sections in decreasing constraint order for (uint8_t constraints = std::size(unassignedSections); constraints--;) { if (char const *constraintName = constraintNames[constraints]; constraintName) { - verbosePrint("Assigning %sconstrained sections...\n", constraintName); + verbosePrint(VERB_INFO, "Assigning %sconstrained sections...\n", constraintName); } else { assume(unassignedSections[constraints].empty()); } diff --git a/src/link/main.cpp b/src/link/main.cpp index 1cd8c5cf..bceb34d9 100644 --- a/src/link/main.cpp +++ b/src/link/main.cpp @@ -18,6 +18,7 @@ #include "script.hpp" // Generated from script.y #include "usage.hpp" #include "util.hpp" // UpperMap, printChar +#include "verbosity.hpp" #include "version.hpp" #include "link/assign.hpp" @@ -31,28 +32,7 @@ Options options; -std::string const &FileStackNode::dump(uint32_t curLineNo) const { - if (std::holds_alternative>(data)) { - assume(parent); // REPT nodes use their parent's name - std::string const &lastName = parent->dump(lineNo); - fputs(" -> ", stderr); - fputs(lastName.c_str(), stderr); - for (uint32_t iter : iters()) { - fprintf(stderr, "::REPT~%" PRIu32, iter); - } - fprintf(stderr, "(%" PRIu32 ")", curLineNo); - return lastName; - } else { - if (parent) { - parent->dump(lineNo); - fputs(" -> ", stderr); - } - std::string const &nodeName = name(); - fputs(nodeName.c_str(), stderr); - fprintf(stderr, "(%" PRIu32 ")", curLineNo); - return nodeName; - } -} +static char const *linkerScriptName = nullptr; // -l // Short options static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvW:wx"; @@ -96,18 +76,131 @@ static Usage usage( " -o, --output set the output file\n" " -p, --pad set the value to pad between sections with\n" " -x, --nopad disable padding of output binary\n" - " -V, --version print RGBLINK version and exits\n" + " -V, --version print RGBLINK version and exit\n" + " -W, --warning enable or disable warnings\n" "\n" "For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n" ); // clang-format on -static void parseScrambleSpec(char *spec) { - static UpperMap> scrambleSpecs{ - {"ROMX", std::pair{&options.scrambleROMX, 65535}}, - {"SRAM", std::pair{&options.scrambleSRAM, 255} }, - {"WRAMX", std::pair{&options.scrambleWRAMX, 7} }, +// LCOV_EXCL_START +static void verboseOutputConfig(int argc, char *argv[]) { + if (!checkVerbosity(VERB_CONFIG)) { + return; + } + + fprintf(stderr, "rgblink %s\n", get_package_version_string()); + + printVVVVVVerbosity(); + + fputs("Options:\n", stderr); + // -d/--dmg + if (options.isDmgMode) { + fputs("\tDMG mode prohibits non-DMG section types\n", stderr); + } + // -t/--tiny + if (options.is32kMode) { + fputs("\tROM0 covers the full 32 KiB of ROM\n", stderr); + } + // -w/--wramx + if (options.isWRAM0Mode) { + fputs("\tWRAM0 covers the full 8 KiB of WRAM\n", stderr); + } + // -x/--nopad + if (options.disablePadding) { + fputs("\tNo padding at the end of the ROM file\n", stderr); + } + // -p/--pad + fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padValue); + // -S/--scramble + if (options.scrambleROMX || options.scrambleWRAMX || options.scrambleSRAM) { + fputs("\tScramble: ", stderr); + if (options.scrambleROMX) { + fprintf(stderr, "ROMX = %" PRIu16, options.scrambleROMX); + if (options.scrambleWRAMX || options.scrambleSRAM) { + fputs(", ", stderr); + } + } + if (options.scrambleWRAMX) { + fprintf(stderr, "WRAMX = %" PRIu16, options.scrambleWRAMX); + if (options.scrambleSRAM) { + fputs(", ", stderr); + } + } + if (options.scrambleSRAM) { + fprintf(stderr, "SRAM = %" PRIu16, options.scrambleSRAM); + } + putc('\n', stderr); + } + // file ... + if (musl_optind < argc) { + fprintf(stderr, "\tInput object files: "); + for (int i = musl_optind; i < argc; ++i) { + if (i > musl_optind) { + fputs(", ", stderr); + } + if (i - musl_optind == 10) { + fprintf(stderr, "and %d more", argc - i); + break; + } + fputs(argv[i], stderr); + } + putc('\n', stderr); + } + auto printPath = [](char const *name, char const *path) { + if (path) { + fprintf(stderr, "\t%s: %s\n", name, path); + } }; + // -O/--overlay + printPath("Overlay file", options.overlayFileName); + // -l/--linkerscript + printPath("Linker script", linkerScriptName); + // -o/--output + printPath("Output ROM file", options.outputFileName); + // -m/--map + printPath("Output map file", options.mapFileName); + // -M/--no-sym-in-map + if (options.mapFileName && options.noSymInMap) { + fputs("\tNo symbols in map file\n", stderr); + } + // -n/--sym + printPath("Output sym file", options.symFileName); + fputs("Ready.\n", stderr); +} +// LCOV_EXCL_STOP + +std::string const &FileStackNode::dump(uint32_t curLineNo) const { + if (std::holds_alternative>(data)) { + assume(parent); // REPT nodes use their parent's name + std::string const &lastName = parent->dump(lineNo); + fputs(" -> ", stderr); + fputs(lastName.c_str(), stderr); + for (uint32_t iter : iters()) { + fprintf(stderr, "::REPT~%" PRIu32, iter); + } + fprintf(stderr, "(%" PRIu32 ")", curLineNo); + return lastName; + } else { + if (parent) { + parent->dump(lineNo); + fputs(" -> ", stderr); + } + std::string const &nodeName = name(); + fputs(nodeName.c_str(), stderr); + fprintf(stderr, "(%" PRIu32 ")", curLineNo); + return nodeName; + } +} + +static void parseScrambleSpec(char *spec) { + // clang-format off: vertically align nested initializers + static UpperMap> scrambleSpecs{ + {"ROMX", std::pair{&options.scrambleROMX, 65535}}, + {"SRAM", std::pair{&options.scrambleSRAM, 255 }}, + {"WRAMX", std::pair{&options.scrambleWRAMX, 7 }}, + }; + // clang-format on // Skip leading whitespace before the regions. spec += strspn(spec, " \t"); @@ -211,8 +304,6 @@ static void parseScrambleSpec(char *spec) { } int main(int argc, char *argv[]) { - char const *linkerScriptName = nullptr; // -l - // Parse options for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { switch (ch) { @@ -283,7 +374,7 @@ int main(int argc, char *argv[]) { // LCOV_EXCL_STOP case 'v': // LCOV_EXCL_START - options.beVerbose = true; + incrementVerbosity(); break; // LCOV_EXCL_STOP case 'W': @@ -302,10 +393,10 @@ int main(int argc, char *argv[]) { } } - int curArgIndex = musl_optind; + verboseOutputConfig(argc, argv); // If no input files were specified, the user must have screwed up - if (curArgIndex == argc) { + if (musl_optind == argc) { usage.printAndExit("Please specify an input file (pass `-` to read from standard input)"); } @@ -323,13 +414,14 @@ int main(int argc, char *argv[]) { } // Read all object files first, - for (obj_Setup(argc - curArgIndex); curArgIndex < argc; ++curArgIndex) { - obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1); + obj_Setup(argc - musl_optind); + for (int i = musl_optind; i < argc; ++i) { + obj_ReadFile(argv[i], argc - i - 1); } // apply the linker script's modifications, if (linkerScriptName) { - verbosePrint("Reading linker script...\n"); + verbosePrint(VERB_NOTICE, "Reading linker script...\n"); if (lexer_Init(linkerScriptName)) { yy::parser parser; diff --git a/src/link/object.cpp b/src/link/object.cpp index ac267d2d..dd5dc362 100644 --- a/src/link/object.cpp +++ b/src/link/object.cpp @@ -17,6 +17,7 @@ #include "helpers.hpp" #include "linkdefs.hpp" #include "platform.hpp" +#include "verbosity.hpp" #include "version.hpp" #include "link/assign.hpp" @@ -480,7 +481,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) { fatal("%s: Not a RGBDS object file", fileName); } - verbosePrint("Reading object file %s\n", fileName); + verbosePrint(VERB_NOTICE, "Reading object file %s\n", fileName); uint32_t revNum; tryReadLong(revNum, file, "%s: Cannot read revision number: %s", fileName); @@ -506,7 +507,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) { uint32_t nbNodes; tryReadLong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName); nodes[fileID].resize(nbNodes); - verbosePrint("Reading %u nodes...\n", nbNodes); + verbosePrint(VERB_INFO, "Reading %u nodes...\n", nbNodes); for (uint32_t i = nbNodes; i--;) { readFileStackNode(file, nodes[fileID], i, fileName); } @@ -515,7 +516,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) { std::vector &fileSymbols = symbolLists.emplace_front(nbSymbols); std::vector nbSymPerSect(nbSections, 0); - verbosePrint("Reading %" PRIu32 " symbols...\n", nbSymbols); + verbosePrint(VERB_INFO, "Reading %" PRIu32 " symbols...\n", nbSymbols); for (uint32_t i = 0; i < nbSymbols; ++i) { // Read symbol Symbol &symbol = fileSymbols[i]; @@ -531,7 +532,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) { // This file's sections, stored in a table to link symbols to them std::vector> fileSections(nbSections); - verbosePrint("Reading %" PRIu32 " sections...\n", nbSections); + verbosePrint(VERB_INFO, "Reading %" PRIu32 " sections...\n", nbSections); for (uint32_t i = 0; i < nbSections; ++i) { // Read section fileSections[i] = std::make_unique
(); @@ -543,7 +544,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) { uint32_t nbAsserts; tryReadLong(nbAsserts, file, "%s: Cannot read number of assertions: %s", fileName); - verbosePrint("Reading %" PRIu32 " assertions...\n", nbAsserts); + verbosePrint(VERB_INFO, "Reading %" PRIu32 " assertions...\n", nbAsserts); for (uint32_t i = 0; i < nbAsserts; ++i) { Assertion &assertion = patch_AddAssertion(); diff --git a/src/link/patch.cpp b/src/link/patch.cpp index df5628b5..796633e4 100644 --- a/src/link/patch.cpp +++ b/src/link/patch.cpp @@ -10,6 +10,7 @@ #include "helpers.hpp" // assume, clz, ctz #include "linkdefs.hpp" #include "opmath.hpp" +#include "verbosity.hpp" #include "link/main.hpp" #include "link/section.hpp" @@ -462,7 +463,7 @@ Assertion &patch_AddAssertion() { } void patch_CheckAssertions() { - verbosePrint("Checking assertions...\n"); + verbosePrint(VERB_NOTICE, "Checking assertions...\n"); for (Assertion &assert : assertions) { int32_t value = computeRPNExpr(assert.patch, *assert.fileSymbols); @@ -505,7 +506,7 @@ void patch_CheckAssertions() { // Applies all of a section's patches to a data section static void applyFilePatches(Section §ion, Section &dataSection) { - verbosePrint("Patching section \"%s\"...\n", section.name.c_str()); + verbosePrint(VERB_INFO, "Patching section \"%s\"...\n", section.name.c_str()); for (Patch &patch : section.patches) { int32_t value = computeRPNExpr(patch, *section.fileSymbols); uint16_t offset = patch.offset + section.offset; diff --git a/src/verbosity.cpp b/src/verbosity.cpp new file mode 100644 index 00000000..0ac97604 --- /dev/null +++ b/src/verbosity.cpp @@ -0,0 +1,69 @@ +#include "verbosity.hpp" + +#include +#include +#include +#include +#include + +static Verbosity verbosity = VERB_NONE; + +void incrementVerbosity() { + if (verbosity < VERB_VVVVVV) { + verbosity = static_cast(verbosity + 1); + } +} + +bool checkVerbosity(Verbosity level) { + return verbosity >= level; +} + +void printVVVVVVerbosity() { + if (!checkVerbosity(VERB_VVVVVV)) { + return; + } + + putc('\n', stderr); + // clang-format off: vertically align values + static std::array, 21> gfx{ + 0b0111111110, + 0b1111111111, + 0b1110011001, + 0b1110011001, + 0b1111111111, + 0b1111111111, + 0b1110000001, + 0b1111000011, + 0b0111111110, + 0b0001111000, + 0b0111111110, + 0b1111111111, + 0b1111111111, + 0b1111111111, + 0b1101111011, + 0b1101111011, + 0b0011111100, + 0b0011001100, + 0b0111001110, + 0b0111001110, + 0b0111001110, + }; + // clang-format on + static std::array textbox{ + " ,----------------------------------------.", + " | Augh, dimensional interference again?! |", + " `----------------------------------------'", + }; + for (size_t i = 0; i < gfx.size(); ++i) { + std::bitset<10> const &row = gfx[i]; + for (uint8_t j = row.size(); j--;) { + // Double the pixel horizontally, otherwise the aspect ratio looks wrong + fputs(row[j] ? "00" : " ", stderr); + } + if (i < textbox.size()) { + fputs(textbox[i], stderr); + } + putc('\n', stderr); + } + putc('\n', stderr); +}