diff --git a/Makefile b/Makefile index abe9395b..a171b211 100644 --- a/Makefile +++ b/Makefile @@ -84,6 +84,7 @@ rgblink_obj := \ src/link/sdas_obj.o \ src/link/section.o \ src/link/symbol.o \ + src/link/warning.o \ src/extern/getopt.o \ src/extern/utf8decoder.o \ src/error.o \ @@ -107,6 +108,7 @@ rgbgfx_obj := \ src/gfx/proto_palette.o \ src/gfx/reverse.o \ src/gfx/rgba.o \ + src/gfx/warning.o \ src/extern/getopt.o \ src/error.o diff --git a/include/asm/warning.hpp b/include/asm/warning.hpp index 9b202f36..2bfc24f5 100644 --- a/include/asm/warning.hpp +++ b/include/asm/warning.hpp @@ -74,4 +74,11 @@ void fatalerror(char const *fmt, ...); [[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...); +// Used for errors that make it impossible to assemble correctly, but don't +// affect the following code. The code will fail to assemble but the user will +// get a list of all errors at the end, making it easier to fix all of them at +// once. +[[gnu::format(printf, 1, 2)]] +void errorNoNewline(char const *fmt, ...); + #endif // RGBDS_ASM_WARNING_HPP diff --git a/include/error.hpp b/include/error.hpp index b353fd54..d5b657eb 100644 --- a/include/error.hpp +++ b/include/error.hpp @@ -3,16 +3,10 @@ #ifndef RGBDS_ERROR_HPP #define RGBDS_ERROR_HPP -extern "C" { - [[gnu::format(printf, 1, 2)]] - void warn(char const *fmt...); - [[gnu::format(printf, 1, 2)]] - void warnx(char const *fmt, ...); +[[gnu::format(printf, 1, 2)]] +void warnx(char const *fmt, ...); - [[gnu::format(printf, 1, 2), noreturn]] - void err(char const *fmt, ...); - [[gnu::format(printf, 1, 2), noreturn]] - void errx(char const *fmt, ...); -} +[[gnu::format(printf, 1, 2), noreturn]] +void errx(char const *fmt, ...); #endif // RGBDS_ERROR_HPP diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index f1c9f26d..16aa930f 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -85,16 +85,9 @@ extern Options options; void giveUp(); // If any error has been emitted thus far, calls `giveUp()`. void requireZeroErrors(); -// Prints a warning, and does not change the error count -[[gnu::format(printf, 1, 2)]] -void warning(char const *fmt, ...); // Prints an error, and increments the error count [[gnu::format(printf, 1, 2)]] void error(char const *fmt, ...); -// Prints an error, and increments the error count -// Does not take format arguments so `format_` and `-Wformat-security` won't complain about -// calling `errorMessage(msg)`. -void errorMessage(char const *msg); // Prints a fatal error, increments the error count, and gives up [[gnu::format(printf, 1, 2), noreturn]] void fatal(char const *fmt, ...); diff --git a/include/gfx/warning.hpp b/include/gfx/warning.hpp new file mode 100644 index 00000000..d40e989d --- /dev/null +++ b/include/gfx/warning.hpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +#ifndef RGBDS_GFX_WARNING_HPP +#define RGBDS_GFX_WARNING_HPP + +// Prints the error count, and exits with failure +[[noreturn]] +void giveUp(); + +// If any error has been emitted thus far, calls `giveUp()` +void requireZeroErrors(); + +// Prints an error, and increments the error count +[[gnu::format(printf, 1, 2)]] +void error(char const *fmt, ...); + +// Prints a fatal error, increments the error count, and gives up +[[gnu::format(printf, 1, 2), noreturn]] +void fatal(char const *fmt, ...); + +#endif // RGBDS_GFX_WARNING_HPP diff --git a/include/link/main.hpp b/include/link/main.hpp index 4634f9b3..ab8ccbd7 100644 --- a/include/link/main.hpp +++ b/include/link/main.hpp @@ -59,11 +59,4 @@ struct FileStackNode { std::string const &dump(uint32_t curLineNo) const; }; -[[gnu::format(printf, 3, 4)]] -void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...); -[[gnu::format(printf, 3, 4)]] -void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...); -[[gnu::format(printf, 3, 4), noreturn]] -void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...); - #endif // RGBDS_LINK_MAIN_HPP diff --git a/include/link/warning.hpp b/include/link/warning.hpp new file mode 100644 index 00000000..9a532521 --- /dev/null +++ b/include/link/warning.hpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +#ifndef RGBDS_LINK_WARNING_HPP +#define RGBDS_LINK_WARNING_HPP + +#include + +struct FileStackNode; + +[[gnu::format(printf, 3, 4)]] +void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...); +[[gnu::format(printf, 3, 4)]] +void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...); +[[gnu::format(printf, 1, 2)]] +void errorNoDump(char const *fmt, ...); +[[gnu::format(printf, 2, 3)]] +void argErr(char flag, char const *fmt, ...); +[[gnu::format(printf, 3, 4), noreturn]] +void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...); + +void requireZeroErrors(); + +#endif // RGBDS_LINK_WARNING_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0ba9518b..9287fe9a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -47,30 +47,15 @@ set(rgbasm_src "asm/section.cpp" "asm/symbol.cpp" "asm/warning.cpp" + "extern/getopt.cpp" "extern/utf8decoder.cpp" "diagnostics.cpp" + "error.cpp" "linkdefs.cpp" "opmath.cpp" "util.cpp" ) -set(rgbfix_src - "fix/main.cpp" - ) - -set(rgbgfx_src - "gfx/main.cpp" - "gfx/pal_packing.cpp" - "gfx/pal_sorting.cpp" - "gfx/pal_spec.cpp" - "gfx/process.cpp" - "gfx/proto_palette.cpp" - "gfx/reverse.cpp" - "gfx/rgba.cpp" - "extern/getopt.cpp" - "error.cpp" - ) - set(rgblink_src "${BISON_LINKER_SCRIPT_PARSER_OUTPUT_SOURCE}" "link/assign.cpp" @@ -81,12 +66,35 @@ set(rgblink_src "link/sdas_obj.cpp" "link/section.cpp" "link/symbol.cpp" + "link/warning.cpp" + "extern/getopt.cpp" "extern/utf8decoder.cpp" + "error.cpp" "linkdefs.cpp" "opmath.cpp" "util.cpp" ) +set(rgbfix_src + "fix/main.cpp" + "extern/getopt.cpp" + "error.cpp" + ) + +set(rgbgfx_src + "gfx/main.cpp" + "gfx/pal_packing.cpp" + "gfx/pal_sorting.cpp" + "gfx/pal_spec.cpp" + "gfx/process.cpp" + "gfx/proto_palette.cpp" + "gfx/reverse.cpp" + "gfx/rgba.cpp" + "gfx/warning.cpp" + "extern/getopt.cpp" + "error.cpp" + ) + foreach(PROG "asm" "fix" "gfx" "link") add_executable(rgb${PROG} ${rgb${PROG}_src} diff --git a/src/asm/charmap.cpp b/src/asm/charmap.cpp index 0f5d76d8..fa064eda 100644 --- a/src/asm/charmap.cpp +++ b/src/asm/charmap.cpp @@ -86,14 +86,14 @@ void charmap_New(std::string const &name, std::string const *baseName) { if (baseName != nullptr) { if (auto search = charmapMap.find(*baseName); search == charmapMap.end()) { - error("Base charmap '%s' doesn't exist\n", baseName->c_str()); + error("Base charmap '%s' doesn't exist", baseName->c_str()); } else { baseIdx = search->second; } } if (charmapMap.find(name) != charmapMap.end()) { - error("Charmap '%s' already exists\n", name.c_str()); + error("Charmap '%s' already exists", name.c_str()); return; } @@ -114,7 +114,7 @@ void charmap_New(std::string const &name, std::string const *baseName) { void charmap_Set(std::string const &name) { if (auto search = charmapMap.find(name); search == charmapMap.end()) { - error("Charmap '%s' doesn't exist\n", name.c_str()); + error("Charmap '%s' doesn't exist", name.c_str()); } else { currentCharmap = &charmapList[search->second]; } @@ -126,7 +126,7 @@ void charmap_Push() { void charmap_Pop() { if (charmapStack.empty()) { - error("No entries in the charmap stack\n"); + error("No entries in the charmap stack"); return; } @@ -136,13 +136,13 @@ void charmap_Pop() { void charmap_CheckStack() { if (!charmapStack.empty()) { - warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHC` without corresponding `POPC`\n"); + warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHC` without corresponding `POPC`"); } } void charmap_Add(std::string const &mapping, std::vector &&value) { if (mapping.empty()) { - error("Cannot map an empty string\n"); + error("Cannot map an empty string"); return; } @@ -168,7 +168,7 @@ void charmap_Add(std::string const &mapping, std::vector &&value) { CharmapNode &node = charmap.nodes[nodeIdx]; if (node.isTerminal()) { - warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n"); + warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping"); } std::swap(node.value, value); @@ -268,7 +268,7 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector *output // This will write the codepoint's value to `output`, little-endian for (uint32_t state = 0, codepoint = 0; inputIdx + codepointLen < input.length();) { if (decode(&state, &codepoint, input[inputIdx + codepointLen]) == 1) { - error("Input string is not valid UTF-8\n"); + error("Input string is not valid UTF-8"); codepointLen = 1; break; } @@ -286,11 +286,11 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector *output // Warn if this character is not mapped but any others are if (int firstChar = input[inputIdx]; charmap.nodes.size() > 1) { - warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s\n", printChar(firstChar)); + warning(WARNING_UNMAPPED_CHAR_1, "Unmapped character %s", printChar(firstChar)); } else if (charmap.name != DEFAULT_CHARMAP_NAME) { warning( WARNING_UNMAPPED_CHAR_2, - "Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap\n", + "Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap", printChar(firstChar) ); } diff --git a/src/asm/format.cpp b/src/asm/format.cpp index 3e7ee154..7599010e 100644 --- a/src/asm/format.cpp +++ b/src/asm/format.cpp @@ -162,19 +162,19 @@ void FormatSpec::appendString(std::string &str, std::string const &value) const } if (sign) { - error("Formatting string with sign flag '%c'\n", sign); + error("Formatting string with sign flag '%c'", sign); } if (padZero) { - error("Formatting string with padding flag '0'\n"); + error("Formatting string with padding flag '0'"); } if (hasFrac) { - error("Formatting string with fractional width\n"); + error("Formatting string with fractional width"); } if (hasPrec) { - error("Formatting string with fractional precision\n"); + error("Formatting string with fractional precision"); } if (useType != 's') { - error("Formatting string as type '%c'\n", useType); + error("Formatting string as type '%c'", useType); } std::string useValue = exact ? escapeString(value) : value; @@ -203,16 +203,16 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const { if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f' && useExact) { - error("Formatting type '%c' with exact flag '#'\n", useType); + error("Formatting type '%c' with exact flag '#'", useType); } if (useType != 'f' && hasFrac) { - error("Formatting type '%c' with fractional width\n", useType); + error("Formatting type '%c' with fractional width", useType); } if (useType != 'f' && hasPrec) { - error("Formatting type '%c' with fractional precision\n", useType); + error("Formatting type '%c' with fractional precision", useType); } if (useType == 's') { - error("Formatting number as type 's'\n"); + error("Formatting number as type 's'"); } char signChar = sign; // 0 or ' ' or '+' @@ -254,7 +254,7 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const { // Default fractional width (C++'s is 6 for "%f"; here 5 is enough for Q16.16) size_t useFracWidth = hasFrac ? fracWidth : 5; if (useFracWidth > 255) { - error("Fractional width %zu too long, limiting to 255\n", useFracWidth); + error("Fractional width %zu too long, limiting to 255", useFracWidth); useFracWidth = 255; } @@ -262,7 +262,7 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const { size_t usePrec = hasPrec ? precision : defaultPrec; if (usePrec < 1 || usePrec > 31) { error( - "Fixed-point constant precision %zu invalid, defaulting to %zu\n", + "Fixed-point constant precision %zu invalid, defaulting to %zu", usePrec, defaultPrec ); diff --git a/src/asm/fstack.cpp b/src/asm/fstack.cpp index 17e30296..6a1bd78b 100644 --- a/src/asm/fstack.cpp +++ b/src/asm/fstack.cpp @@ -160,7 +160,7 @@ bool yywrap() { if (ifDepth != 0) { fatalerror( - "Ended block with %" PRIu32 " unterminated IF construct%s\n", + "Ended block with %" PRIu32 " unterminated IF construct%s", ifDepth, ifDepth == 1 ? "" : "s" ); @@ -188,7 +188,7 @@ bool yywrap() { // This error message will refer to the current iteration if (sym->type != SYM_VAR) { - fatalerror("Failed to update FOR symbol value\n"); + fatalerror("Failed to update FOR symbol value"); } } // Advance to the next iteration @@ -211,7 +211,7 @@ bool yywrap() { static void checkRecursionDepth() { if (contextStack.size() > maxRecursionDepth) { - fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth); + fatalerror("Recursion limit (%zu) exceeded", maxRecursionDepth); } } @@ -317,13 +317,13 @@ void fstk_RunInclude(std::string const &path, bool preInclude) { // LCOV_EXCL_STOP failedOnMissingInclude = true; } else { - error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno)); + error("Unable to open included file '%s': %s", path.c_str(), strerror(errno)); } return; } if (!newFileContext(*fullPath, false)) { - fatalerror("Failed to set up lexer for file include\n"); // LCOV_EXCL_LINE + fatalerror("Failed to set up lexer for file include"); // LCOV_EXCL_LINE } } @@ -332,14 +332,14 @@ void fstk_RunMacro(std::string const ¯oName, std::shared_ptr macr if (!macro) { if (sym_IsPurgedExact(macroName)) { - error("Macro \"%s\" not defined; it was purged\n", macroName.c_str()); + error("Macro \"%s\" not defined; it was purged", macroName.c_str()); } else { - error("Macro \"%s\" not defined\n", macroName.c_str()); + error("Macro \"%s\" not defined", macroName.c_str()); } return; } if (macro->type != SYM_MACRO) { - error("\"%s\" is not a macro\n", macroName.c_str()); + error("\"%s\" is not a macro", macroName.c_str()); return; } @@ -372,13 +372,11 @@ void fstk_RunFor( } else if (step < 0 && stop < start) { count = (static_cast(start) - stop - 1) / -static_cast(step) + 1; } else if (step == 0) { - error("FOR cannot have a step value of 0\n"); + error("FOR cannot have a step value of 0"); } if ((step > 0 && start > stop) || (step < 0 && start < stop)) { - warning( - WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n", start, stop, step - ); + warning(WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d", start, stop, step); } if (count == 0) { @@ -394,7 +392,7 @@ void fstk_RunFor( bool fstk_Break() { if (contextStack.top().fileInfo->type != NODE_REPT) { - error("BREAK can only be used inside a REPT/FOR block\n"); + error("BREAK can only be used inside a REPT/FOR block"); return false; } @@ -404,14 +402,14 @@ bool fstk_Break() { void fstk_NewRecursionDepth(size_t newDepth) { if (contextStack.size() > newDepth + 1) { - fatalerror("Recursion limit (%zu) exceeded\n", newDepth); + fatalerror("Recursion limit (%zu) exceeded", newDepth); } maxRecursionDepth = newDepth; } void fstk_Init(std::string const &mainPath, size_t maxDepth) { if (!newFileContext(mainPath, true)) { - fatalerror("Failed to open main file\n"); + fatalerror("Failed to open main file"); } maxRecursionDepth = maxDepth; diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index a86f83f8..52fe37ea 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -384,7 +384,7 @@ void lexer_IncIFDepth() { void lexer_DecIFDepth() { if (lexerState->ifStack.empty()) { - fatalerror("Found ENDC outside of an IF construct\n"); + fatalerror("Found ENDC outside of an IF construct"); } lexerState->ifStack.pop_front(); @@ -423,7 +423,7 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat struct stat statBuf; if (stat(filePath.c_str(), &statBuf) != 0) { // LCOV_EXCL_START - error("Failed to stat file \"%s\": %s\n", filePath.c_str(), strerror(errno)); + error("Failed to stat file \"%s\": %s", filePath.c_str(), strerror(errno)); return false; // LCOV_EXCL_STOP } @@ -432,7 +432,7 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat int fd = open(path.c_str(), O_RDONLY); if (fd < 0) { // LCOV_EXCL_START - error("Failed to open file \"%s\": %s\n", path.c_str(), strerror(errno)); + error("Failed to open file \"%s\": %s", path.c_str(), strerror(errno)); return false; // LCOV_EXCL_STOP } @@ -565,7 +565,7 @@ size_t BufferedContent::readMore(size_t startIndex, size_t nbChars) { if (nbReadChars == -1) { // LCOV_EXCL_START - fatalerror("Error while reading \"%s\": %s\n", lexerState->path.c_str(), strerror(errno)); + fatalerror("Error while reading \"%s\": %s", lexerState->path.c_str(), strerror(errno)); // LCOV_EXCL_STOP } @@ -600,7 +600,7 @@ static void beginExpansion(std::shared_ptr str, std::optionalexpansions.size() > maxRecursionDepth + 1) { - fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth); + fatalerror("Recursion limit (%zu) exceeded", maxRecursionDepth); } } @@ -637,7 +637,7 @@ static uint32_t readBracketedMacroArgNum() { if (c >= '0' && c <= '9') { uint32_t n = readDecimalNumber(0); if (n > INT32_MAX) { - error("Number in bracketed macro argument is too large\n"); + error("Number in bracketed macro argument is too large"); return 0; } num = negative ? -n : static_cast(n); @@ -646,7 +646,7 @@ static uint32_t readBracketedMacroArgNum() { shiftChar(); c = peek(); if (!startsIdentifier(c)) { - error("Empty raw symbol in bracketed macro argument\n"); + error("Empty raw symbol in bracketed macro argument"); return 0; } } @@ -662,14 +662,14 @@ static uint32_t readBracketedMacroArgNum() { if (!sym) { if (sym_IsPurgedScoped(symName)) { - error("Bracketed symbol \"%s\" does not exist; it was purged\n", symName.c_str()); + error("Bracketed symbol \"%s\" does not exist; it was purged", symName.c_str()); } else { - error("Bracketed symbol \"%s\" does not exist\n", symName.c_str()); + error("Bracketed symbol \"%s\" does not exist", symName.c_str()); } num = 0; symbolError = true; } else if (!sym->isNumeric()) { - error("Bracketed symbol \"%s\" is not numeric\n", symName.c_str()); + error("Bracketed symbol \"%s\" is not numeric", symName.c_str()); num = 0; symbolError = true; } else { @@ -682,13 +682,13 @@ static uint32_t readBracketedMacroArgNum() { c = peek(); shiftChar(); if (c != '>') { - error("Invalid character in bracketed macro argument %s\n", printChar(c)); + error("Invalid character in bracketed macro argument %s", printChar(c)); return 0; } else if (empty) { - error("Empty bracketed macro argument\n"); + error("Empty bracketed macro argument"); return 0; } else if (num == 0 && !symbolError) { - error("Invalid bracketed macro argument '\\<0>'\n"); + error("Invalid bracketed macro argument '\\<0>'"); return 0; } else { return num; @@ -699,13 +699,13 @@ static std::shared_ptr readMacroArg(char name) { if (name == '@') { auto str = fstk_GetUniqueIDStr(); if (!str) { - error("'\\@' cannot be used outside of a macro or REPT/FOR block\n"); + error("'\\@' cannot be used outside of a macro or REPT/FOR block"); } return str; } else if (name == '#') { MacroArgs *macroArgs = fstk_GetCurrentMacroArgs(); if (!macroArgs) { - error("'\\#' cannot be used outside of a macro\n"); + error("'\\#' cannot be used outside of a macro"); return nullptr; } @@ -721,13 +721,13 @@ static std::shared_ptr readMacroArg(char name) { MacroArgs *macroArgs = fstk_GetCurrentMacroArgs(); if (!macroArgs) { - error("'\\<%" PRIu32 ">' cannot be used outside of a macro\n", num); + error("'\\<%" PRIu32 ">' cannot be used outside of a macro", num); return nullptr; } auto str = macroArgs->getArg(num); if (!str) { - error("Macro argument '\\<%" PRId32 ">' not defined\n", num); + error("Macro argument '\\<%" PRId32 ">' not defined", num); } return str; } else { @@ -735,13 +735,13 @@ static std::shared_ptr readMacroArg(char name) { MacroArgs *macroArgs = fstk_GetCurrentMacroArgs(); if (!macroArgs) { - error("'\\%c' cannot be used outside of a macro\n", name); + error("'\\%c' cannot be used outside of a macro", name); return nullptr; } auto str = macroArgs->getArg(name - '0'); if (!str) { - error("Macro argument '\\%c' not defined\n", name); + error("Macro argument '\\%c' not defined", name); } return str; } @@ -945,7 +945,7 @@ static void discardBlockComment() { switch (c) { case EOF: - error("Unterminated block comment\n"); + error("Unterminated block comment"); return; case '\r': handleCRLF(c); @@ -957,7 +957,7 @@ static void discardBlockComment() { continue; case '/': if (peek() == '*') { - warning(WARNING_NESTED_COMMENT, "/* in block comment\n"); + warning(WARNING_NESTED_COMMENT, "/* in block comment"); } continue; case '*': @@ -999,7 +999,7 @@ static void discardLineContinuation() { } else if (c == ';') { discardComment(); } else { - error("Begun line continuation, but encountered character %s\n", printChar(c)); + error("Begun line continuation, but encountered character %s", printChar(c)); break; } } @@ -1041,7 +1041,7 @@ static uint32_t readFractionalPart(uint32_t integer) { break; } if (divisor > (UINT32_MAX - (c - '0')) / 10) { - warning(WARNING_LARGE_CONSTANT, "Precision of fixed-point constant is too large\n"); + warning(WARNING_LARGE_CONSTANT, "Precision of fixed-point constant is too large"); // Discard any additional digits shiftChar(); while (c = peek(), (c >= '0' && c <= '9') || c == '_') { @@ -1064,16 +1064,16 @@ static uint32_t readFractionalPart(uint32_t integer) { if (precision == 0) { if (state >= READFRACTIONALPART_PRECISION) { - error("Invalid fixed-point constant, no significant digits after 'q'\n"); + error("Invalid fixed-point constant, no significant digits after 'q'"); } precision = fixPrecision; } else if (precision > 31) { - error("Fixed-point constant precision must be between 1 and 31\n"); + error("Fixed-point constant precision must be between 1 and 31"); precision = fixPrecision; } if (integer >= (1ULL << (32 - precision))) { - warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n"); + warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large"); } // Cast to unsigned avoids undefined overflow behavior @@ -1096,18 +1096,18 @@ static bool checkDigitErrors(char const *digits, size_t n, char const *type) { char c = digits[i]; if (!isValidDigit(c)) { - error("Invalid digit for %s constant %s\n", type, printChar(c)); + error("Invalid digit for %s constant %s", type, printChar(c)); return false; } if (c >= '0' && c < static_cast(n + '0') && c != static_cast(i + '0')) { - error("Changed digit for %s constant %s\n", type, printChar(c)); + error("Changed digit for %s constant %s", type, printChar(c)); return false; } for (size_t j = i + 1; j < n; j++) { if (c == digits[j]) { - error("Repeated digit for %s constant %s\n", type, printChar(c)); + error("Repeated digit for %s constant %s", type, printChar(c)); return false; } } @@ -1146,7 +1146,7 @@ static uint32_t readBinaryNumber() { break; } if (value > (UINT32_MAX - bit) / 2) { - warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n"); + warning(WARNING_LARGE_CONSTANT, "Integer constant is too large"); } value = value * 2 + bit; @@ -1154,7 +1154,7 @@ static uint32_t readBinaryNumber() { } if (empty) { - error("Invalid integer constant, no digits after '%%'\n"); + error("Invalid integer constant, no digits after '%%'"); } return value; @@ -1176,7 +1176,7 @@ static uint32_t readOctalNumber() { } if (value > (UINT32_MAX - c) / 8) { - warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n"); + warning(WARNING_LARGE_CONSTANT, "Integer constant is too large"); } value = value * 8 + c; @@ -1184,7 +1184,7 @@ static uint32_t readOctalNumber() { } if (empty) { - error("Invalid integer constant, no digits after '&'\n"); + error("Invalid integer constant, no digits after '&'"); } return value; @@ -1206,7 +1206,7 @@ static uint32_t readDecimalNumber(int initial) { } if (value > (UINT32_MAX - c) / 10) { - warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n"); + warning(WARNING_LARGE_CONSTANT, "Integer constant is too large"); } value = value * 10 + c; @@ -1214,7 +1214,7 @@ static uint32_t readDecimalNumber(int initial) { } if (empty) { - error("Invalid integer constant, no digits\n"); + error("Invalid integer constant, no digits"); } return value; @@ -1240,7 +1240,7 @@ static uint32_t readHexNumber() { } if (value > (UINT32_MAX - c) / 16) { - warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n"); + warning(WARNING_LARGE_CONSTANT, "Integer constant is too large"); } value = value * 16 + c; @@ -1248,7 +1248,7 @@ static uint32_t readHexNumber() { } if (empty) { - error("Invalid integer constant, no digits after '$'\n"); + error("Invalid integer constant, no digits after '$'"); } return value; @@ -1286,11 +1286,10 @@ static uint32_t readGfxConstant() { } if (width == 0) { - error("Invalid graphics constant, no digits after '`'\n"); + error("Invalid graphics constant, no digits after '`'"); } else if (width == 9) { warning( - WARNING_LARGE_CONSTANT, - "Graphics constant is too long, only first 8 pixels considered\n" + WARNING_LARGE_CONSTANT, "Graphics constant is too long, only first 8 pixels considered" ); } @@ -1320,7 +1319,7 @@ static Token readIdentifier(char firstChar, bool raw) { if (!raw) { if (auto search = keywordDict.find(identifier); search != keywordDict.end()) { if (search == ldio) { - warning(WARNING_OBSOLETE, "LDIO is deprecated; use LDH\n"); + warning(WARNING_OBSOLETE, "LDIO is deprecated; use LDH"); } return Token(search->second); } @@ -1338,7 +1337,7 @@ static Token readIdentifier(char firstChar, bool raw) { static std::shared_ptr readInterpolation(size_t depth) { if (depth > maxRecursionDepth) { - fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth); + fatalerror("Recursion limit (%zu) exceeded", maxRecursionDepth); } std::string fmtBuf; @@ -1360,7 +1359,7 @@ static std::shared_ptr readInterpolation(size_t depth) { } continue; // Restart, reading from the new buffer } else if (c == EOF || c == '\r' || c == '\n' || c == '"') { - error("Missing }\n"); + error("Missing }"); break; } else if (c == '}') { shiftChar(); @@ -1372,7 +1371,7 @@ static std::shared_ptr readInterpolation(size_t depth) { } fmt.finishCharacters(); if (!fmt.isValid()) { - error("Invalid format spec '%s'\n", fmtBuf.c_str()); + error("Invalid format spec '%s'", fmtBuf.c_str()); } fmtBuf.clear(); // Now that format has been set, restart at beginning of string } else { @@ -1391,7 +1390,7 @@ static std::shared_ptr readInterpolation(size_t depth) { // Don't allow symbols that alias keywords without a '#' prefix. error( "Interpolated symbol \"%s\" is a reserved keyword; add a '#' prefix to use it as a raw " - "symbol\n", + "symbol", fmtBuf.c_str() ); return nullptr; @@ -1401,9 +1400,9 @@ static std::shared_ptr readInterpolation(size_t depth) { if (!sym || !sym->isDefined()) { if (sym_IsPurgedScoped(fmtBuf)) { - error("Interpolated symbol \"%s\" does not exist; it was purged\n", fmtBuf.c_str()); + error("Interpolated symbol \"%s\" does not exist; it was purged", fmtBuf.c_str()); } else { - error("Interpolated symbol \"%s\" does not exist\n", fmtBuf.c_str()); + error("Interpolated symbol \"%s\" does not exist", fmtBuf.c_str()); } } else if (sym->type == SYM_EQUS) { auto buf = std::make_shared(); @@ -1414,7 +1413,7 @@ static std::shared_ptr readInterpolation(size_t depth) { fmt.appendNumber(*buf, sym->getConstantValue()); return buf; } else { - error("Interpolated symbol \"%s\" is not a numeric or string symbol\n", fmtBuf.c_str()); + error("Interpolated symbol \"%s\" is not a numeric or string symbol", fmtBuf.c_str()); } return nullptr; } @@ -1472,7 +1471,7 @@ static std::string readString(bool raw) { // '\r', '\n' or EOF ends a single-line string early if (c == EOF || (!multiline && (c == '\r' || c == '\n'))) { - error("Unterminated string\n"); + error("Unterminated string"); return str; } @@ -1560,12 +1559,12 @@ static std::string readString(bool raw) { continue; // Do not copy an additional character case EOF: // Can't really print that one - error("Illegal character escape at end of input\n"); + error("Illegal character escape at end of input"); c = '\\'; break; default: - error("Illegal character escape %s\n", printChar(c)); + error("Illegal character escape %s", printChar(c)); shiftChar(); break; } @@ -1616,7 +1615,7 @@ static void appendStringLiteral(std::string &str, bool raw) { // '\r', '\n' or EOF ends a single-line string early if (c == EOF || (!multiline && (c == '\r' || c == '\n'))) { - error("Unterminated string\n"); + error("Unterminated string"); return; } @@ -1697,12 +1696,12 @@ static void appendStringLiteral(std::string &str, bool raw) { } case EOF: // Can't really print that one - error("Illegal character escape at end of input\n"); + error("Illegal character escape at end of input"); c = '\\'; break; default: - error("Illegal character escape %s\n", printChar(c)); + error("Illegal character escape %s", printChar(c)); shiftChar(); break; } @@ -2077,9 +2076,9 @@ static Token yylex_NORMAL() { garbage += ", "; garbage += printChar(c); } - error("Unknown characters %s\n", garbage.c_str()); + error("Unknown characters %s", garbage.c_str()); } else { - error("Unknown character %s\n", printChar(c)); + error("Unknown character %s", printChar(c)); } } } @@ -2203,7 +2202,7 @@ backslash: continue; case EOF: // Can't really print that one - error("Illegal character escape at end of input\n"); + error("Illegal character escape at end of input"); c = '\\'; break; @@ -2211,7 +2210,7 @@ backslash: // '\#', and '\0'-'\9' should not occur here. default: - error("Illegal character escape %s\n", printChar(c)); + error("Illegal character escape %s", printChar(c)); break; } [[fallthrough]]; @@ -2293,7 +2292,7 @@ static Token skipIfBlock(bool toEndc) { case T_(POP_ELIF): if (lexer_ReachedELSEBlock()) { // This should be redundant, as the parser handles this error first. - fatalerror("Found ELIF after an ELSE block\n"); // LCOV_EXCL_LINE + fatalerror("Found ELIF after an ELSE block"); // LCOV_EXCL_LINE } if (!toEndc && lexer_GetIFDepth() == startingDepth) { return token; @@ -2302,7 +2301,7 @@ static Token skipIfBlock(bool toEndc) { case T_(POP_ELSE): if (lexer_ReachedELSEBlock()) { - fatalerror("Found ELSE after an ELSE block\n"); + fatalerror("Found ELSE after an ELSE block"); } lexer_ReachELSEBlock(); if (!toEndc && lexer_GetIFDepth() == startingDepth) { @@ -2552,7 +2551,7 @@ Capture lexer_CaptureRept() { // Just consume characters until EOL or EOF for (;; c = nextChar()) { if (c == EOF) { - error("Unterminated REPT/FOR block\n"); + error("Unterminated REPT/FOR block"); endCapture(capture); capture.span.ptr = nullptr; // Indicates that it reached EOF before an ENDR return capture; @@ -2595,7 +2594,7 @@ Capture lexer_CaptureMacro() { // Just consume characters until EOL or EOF for (;; c = nextChar()) { if (c == EOF) { - error("Unterminated macro definition\n"); + error("Unterminated macro definition"); endCapture(capture); capture.span.ptr = nullptr; // Indicates that it reached EOF before an ENDM return capture; diff --git a/src/asm/macro.cpp b/src/asm/macro.cpp index db214785..57816d9c 100644 --- a/src/asm/macro.cpp +++ b/src/asm/macro.cpp @@ -52,7 +52,7 @@ std::shared_ptr MacroArgs::getAllArgs() const { void MacroArgs::appendArg(std::shared_ptr arg) { if (arg->empty()) { - warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n"); + warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument"); } args.push_back(arg); } @@ -60,10 +60,10 @@ void MacroArgs::appendArg(std::shared_ptr arg) { void MacroArgs::shiftArgs(int32_t count) { if (size_t nbArgs = args.size(); count > 0 && (static_cast(count) > nbArgs || shift > nbArgs - count)) { - warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end\n"); + warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end"); shift = nbArgs; } else if (count < 0 && shift < static_cast(-count)) { - warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their beginning\n"); + warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their beginning"); shift = 0; } else { shift += count; diff --git a/src/asm/main.cpp b/src/asm/main.cpp index 08db0170..6c8e3450 100644 --- a/src/asm/main.cpp +++ b/src/asm/main.cpp @@ -3,8 +3,10 @@ #include "asm/main.hpp" #include +#include #include #include +#include #include #include #include @@ -109,6 +111,19 @@ static void printUsage() { } // LCOV_EXCL_STOP +[[gnu::format(printf, 1, 2), noreturn]] +static void fatalWithUsage(char const *fmt, ...) { + va_list ap; + fputs("FATAL: ", stderr); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + putc('\n', stderr); + + printUsage(); + exit(1); +} + int main(int argc, char *argv[]) { time_t now = time(nullptr); // Support SOURCE_DATE_EPOCH for reproducible builds @@ -198,7 +213,9 @@ int main(int argc, char *argv[]) { dependFileName = ""; } if (dependFile == nullptr) { - err("Failed to open dependfile \"%s\"", dependFileName); // LCOV_EXCL_LINE + // LCOV_EXCL_START + errx("Failed to open dependfile \"%s\": %s", dependFileName, strerror(errno)); + // LCOV_EXCL_STOP } break; @@ -386,15 +403,9 @@ int main(int argc, char *argv[]) { } if (argc == musl_optind) { - fputs( - "FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr - ); - printUsage(); - exit(1); + fatalWithUsage("Please specify an input file (pass `-` to read from standard input)"); } else if (argc != musl_optind + 1) { - fputs("FATAL: More than one input file specified\n", stderr); - printUsage(); - exit(1); + fatalWithUsage("More than one input file specified"); } std::string mainFileName = argv[musl_optind]; diff --git a/src/asm/opt.cpp b/src/asm/opt.cpp index dc189a7d..ce7e17c3 100644 --- a/src/asm/opt.cpp +++ b/src/asm/opt.cpp @@ -47,7 +47,7 @@ void opt_R(size_t newDepth) { void opt_W(char const *flag) { if (warnings.processWarningFlag(flag) == "numeric-string") { - warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n"); + warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated"); } } @@ -57,7 +57,7 @@ void opt_Parse(char const *s) { if (strlen(&s[1]) == 2) { opt_B(&s[1]); } else { - error("Must specify exactly 2 characters for option 'b'\n"); + error("Must specify exactly 2 characters for option 'b'"); } break; @@ -65,7 +65,7 @@ void opt_Parse(char const *s) { if (strlen(&s[1]) == 4) { opt_G(&s[1]); } else { - error("Must specify exactly 4 characters for option 'g'\n"); + error("Must specify exactly 4 characters for option 'g'"); } break; @@ -76,14 +76,14 @@ void opt_Parse(char const *s) { result = sscanf(&s[1], "%x", &padByte); if (result != 1) { - error("Invalid argument for option 'p'\n"); + error("Invalid argument for option 'p'"); } else if (padByte > 0xFF) { - error("Argument for option 'p' must be between 0 and 0xFF\n"); + error("Argument for option 'p' must be between 0 and 0xFF"); } else { opt_P(padByte); } } else { - error("Invalid argument for option 'p'\n"); + error("Invalid argument for option 'p'"); } break; @@ -99,14 +99,14 @@ void opt_Parse(char const *s) { result = sscanf(precisionArg, "%u", &precision); if (result != 1) { - error("Invalid argument for option 'Q'\n"); + error("Invalid argument for option 'Q'"); } else if (precision < 1 || precision > 31) { - error("Argument for option 'Q' must be between 1 and 31\n"); + error("Argument for option 'Q' must be between 1 and 31"); } else { opt_Q(precision); } } else { - error("Invalid argument for option 'Q'\n"); + error("Invalid argument for option 'Q'"); } break; @@ -117,7 +117,7 @@ void opt_Parse(char const *s) { } if (s[0] == '\0') { - error("Missing argument to option 'r'\n"); + error("Missing argument to option 'r'"); break; } @@ -125,9 +125,9 @@ void opt_Parse(char const *s) { unsigned long newDepth = strtoul(s, &endptr, 10); if (*endptr != '\0') { - error("Invalid argument to option 'r' (\"%s\")\n", s); + error("Invalid argument to option 'r' (\"%s\")", s); } else if (errno == ERANGE) { - error("Argument to 'r' is out of range (\"%s\")\n", s); + error("Argument to 'r' is out of range (\"%s\")", s); } else { opt_R(newDepth); } @@ -138,12 +138,12 @@ void opt_Parse(char const *s) { if (strlen(&s[1]) > 0) { opt_W(&s[1]); } else { - error("Must specify an argument for option 'W'\n"); + error("Must specify an argument for option 'W'"); } break; default: - error("Unknown option '%c'\n", s[0]); + error("Unknown option '%c'", s[0]); break; } } @@ -168,7 +168,7 @@ void opt_Push() { void opt_Pop() { if (stack.empty()) { - error("No entries in the option stack\n"); + error("No entries in the option stack"); return; } @@ -187,6 +187,6 @@ void opt_Pop() { void opt_CheckStack() { if (!stack.empty()) { - warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHO` without corresponding `POPO`\n"); + warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHO` without corresponding `POPO`"); } } diff --git a/src/asm/output.cpp b/src/asm/output.cpp index 699f5539..dcc4a7c5 100644 --- a/src/asm/output.cpp +++ b/src/asm/output.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -72,7 +73,7 @@ static uint32_t getSectIDIfAny(Section *sect) { } // Every section that exists should be in `sectionMap` - fatalerror("Unknown section '%s'\n", sect->name.c_str()); // LCOV_EXCL_LINE + fatalerror("Unknown section '%s'", sect->name.c_str()); // LCOV_EXCL_LINE } static void writePatch(Patch const &patch, FILE *file) { @@ -324,7 +325,9 @@ void out_WriteObject() { file = stdout; } if (!file) { - err("Failed to open object file '%s'", objectFileName.c_str()); // LCOV_EXCL_LINE + // LCOV_EXCL_START + errx("Failed to open object file '%s': %s", objectFileName.c_str(), strerror(errno)); + // LCOV_EXCL_STOP } Defer closeFile{[&] { fclose(file); }}; @@ -524,7 +527,9 @@ void out_WriteState(std::string name, std::vector const &features) file = stdout; } if (!file) { - err("Failed to open state file '%s'", name.c_str()); // LCOV_EXCL_LINE + // LCOV_EXCL_START + errx("Failed to open state file '%s': %s", name.c_str(), strerror(errno)); + // LCOV_EXCL_STOP } Defer closeFile{[&] { fclose(file); }}; diff --git a/src/asm/parser.y b/src/asm/parser.y index 3712b3df..6404add3 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -438,12 +438,12 @@ diff_mark: %empty // OK | OP_ADD { ::error( - "syntax error, unexpected + at the beginning of the line (is it a leftover diff mark?)\n" + "syntax error, unexpected + at the beginning of the line (is it a leftover diff mark?)" ); } | OP_SUB { ::error( - "syntax error, unexpected - at the beginning of the line (is it a leftover diff mark?)\n" + "syntax error, unexpected - at the beginning of the line (is it a leftover diff mark?)" ); } ; @@ -488,11 +488,11 @@ if: elif: POP_ELIF iconst NEWLINE { if (lexer_GetIFDepth() == 0) { - fatalerror("Found ELIF outside of an IF construct\n"); + fatalerror("Found ELIF outside of an IF construct"); } if (lexer_RanIFBlock()) { if (lexer_ReachedELSEBlock()) { - fatalerror("Found ELIF after an ELSE block\n"); + fatalerror("Found ELIF after an ELSE block"); } lexer_SetMode(LEXER_SKIP_TO_ENDC); } else if ($2) { @@ -506,11 +506,11 @@ elif: else: POP_ELSE NEWLINE { if (lexer_GetIFDepth() == 0) { - fatalerror("Found ELSE outside of an IF construct\n"); + fatalerror("Found ELSE outside of an IF construct"); } if (lexer_RanIFBlock()) { if (lexer_ReachedELSEBlock()) { - fatalerror("Found ELSE after an ELSE block\n"); + fatalerror("Found ELSE after an ELSE block"); } lexer_SetMode(LEXER_SKIP_TO_ENDC); } else { @@ -695,7 +695,7 @@ align: align_spec: uconst { if ($1 > 16) { - ::error("Alignment must be between 0 and 16, not %u\n", $1); + ::error("Alignment must be between 0 and 16, not %u", $1); $$.alignment = $$.alignOfs = 0; } else { $$.alignment = $1; @@ -704,11 +704,11 @@ align_spec: } | uconst COMMA iconst { if ($1 > 16) { - ::error("Alignment must be between 0 and 16, not %u\n", $1); + ::error("Alignment must be between 0 and 16, not %u", $1); $$.alignment = $$.alignOfs = 0; } else if ($3 <= -(1 << $1) || $3 >= 1 << $1) { ::error( - "The absolute alignment offset (%" PRIu32 ") must be less than alignment size (%d)\n", + "The absolute alignment offset (%" PRIu32 ") must be less than alignment size (%d)", static_cast($3 < 0 ? -$3 : $3), 1 << $1 ); @@ -779,13 +779,13 @@ endsection: fail: POP_FAIL string { - fatalerror("%s\n", $2.c_str()); + fatalerror("%s", $2.c_str()); } ; warn: POP_WARN string { - warning(WARNING_USER, "%s\n", $2.c_str()); + warning(WARNING_USER, "%s", $2.c_str()); } ; @@ -836,7 +836,7 @@ shift: if (MacroArgs *macroArgs = fstk_GetCurrentMacroArgs(); macroArgs) { macroArgs->shiftArgs($2); } else { - ::error("Cannot shift macro arguments outside of a macro\n"); + ::error("Cannot shift macro arguments outside of a macro"); } } ; @@ -1577,7 +1577,7 @@ relocexpr_no_str: | OP_CHARSIZE LPAREN string RPAREN { size_t charSize = charmap_CharSize($3); if (charSize == 0) { - ::error("CHARSIZE: No character mapping for \"%s\"\n", $3.c_str()); + ::error("CHARSIZE: No character mapping for \"%s\"", $3.c_str()); } $$.makeNumber(charSize); } @@ -1589,13 +1589,13 @@ relocexpr_no_str: } else { warning( WARNING_BUILTIN_ARG, - "CHARVAL: Index %" PRIu32 " is past the end of the character mapping\n", + "CHARVAL: Index %" PRIu32 " is past the end of the character mapping", idx ); $$.makeNumber(0); } } else { - ::error("CHARVAL: No character mapping for \"%s\"\n", $3.c_str()); + ::error("CHARVAL: No character mapping for \"%s\"", $3.c_str()); $$.makeNumber(0); } } @@ -1608,7 +1608,7 @@ uconst: iconst { $$ = $1; if ($$ < 0) { - fatalerror("Constant must not be negative: %d\n", $$); + fatalerror("Constant must not be negative: %d", $$); } } ; @@ -1626,7 +1626,7 @@ precision_arg: | COMMA iconst { $$ = $2; if ($$ < 1 || $$ > 31) { - ::error("Fixed-point precision must be between 1 and 31, not %" PRId32 "\n", $$); + ::error("Fixed-point precision must be between 1 and 31, not %" PRId32, $$); $$ = fix_Precision(); } } @@ -1675,9 +1675,9 @@ string_literal: bool unique; $$ = charmap_Reverse($3, unique); if (!unique) { - ::error("REVCHAR: Multiple character mappings to values\n"); + ::error("REVCHAR: Multiple character mappings to values"); } else if ($$.empty()) { - ::error("REVCHAR: No character mapping to values\n"); + ::error("REVCHAR: No character mapping to values"); } } | OP_STRCAT LPAREN RPAREN { @@ -1705,15 +1705,15 @@ string_literal: if (!sym) { if (sym_IsPurgedScoped($3)) { - fatalerror("Unknown symbol \"%s\"; it was purged\n", $3.c_str()); + fatalerror("Unknown symbol \"%s\"; it was purged", $3.c_str()); } else { - fatalerror("Unknown symbol \"%s\"\n", $3.c_str()); + fatalerror("Unknown symbol \"%s\"", $3.c_str()); } } Section const *section = sym->getSection(); if (!section) { - fatalerror("\"%s\" does not belong to any section\n", sym->name.c_str()); + fatalerror("\"%s\" does not belong to any section", sym->name.c_str()); } // Section names are capped by rgbasm's maximum string length, // so this currently can't overflow. @@ -1729,7 +1729,7 @@ string: if (Symbol *sym = sym_FindScopedSymbol($1); sym && sym->type == SYM_EQUS) { $$ = *sym->getEqus(); } else { - ::error("'%s' is not a string symbol\n", $1.c_str()); + ::error("'%s' is not a string symbol", $1.c_str()); } } ; @@ -1833,7 +1833,7 @@ sect_org: | LBRACK uconst RBRACK { $$ = $2; if ($$ < 0 || $$ > 0xFFFF) { - ::error("Address $%x is not 16-bit\n", $$); + ::error("Address $%x is not 16-bit", $$); $$ = -1; } } @@ -2088,7 +2088,7 @@ sm83_ldh: if ($4.makeCheckHRAM()) { warning( WARNING_OBSOLETE, - "LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF\n" + "LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF" ); } @@ -2099,7 +2099,7 @@ sm83_ldh: if ($2.makeCheckHRAM()) { warning( WARNING_OBSOLETE, - "LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF\n" + "LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF" ); } @@ -2126,7 +2126,7 @@ ff00_c_ind: LBRACK relocexpr OP_ADD MODE_C RBRACK { // This has to use `relocexpr`, not `iconst`, to avoid a shift/reduce conflict if ($2.getConstVal() != 0xFF00) { - ::error("Base value must be equal to $FF00 for $FF00+C\n"); + ::error("Base value must be equal to $FF00 for $FF00+C"); } } ; @@ -2148,7 +2148,7 @@ sm83_ld_hl: sect_RelByte($5, 1); } | SM83_LD MODE_HL COMMA MODE_SP { - ::error("LD HL, SP is not a valid instruction; use LD HL, SP + 0\n"); + ::error("LD HL, SP is not a valid instruction; use LD HL, SP + 0"); } | SM83_LD MODE_HL COMMA reloc_16bit { sect_ConstByte(0x01 | (REG_HL << 4)); @@ -2156,7 +2156,7 @@ sm83_ld_hl: } | SM83_LD MODE_HL COMMA reg_tt_no_af { ::error( - "LD HL, %s is not a valid instruction; use LD H, %s and LD L, %s\n", + "LD HL, %s is not a valid instruction; use LD H, %s and LD L, %s", reg_tt_names[$4], reg_tt_high_names[$4], reg_tt_low_names[$4] @@ -2169,7 +2169,7 @@ sm83_ld_sp: sect_ConstByte(0xF9); } | SM83_LD MODE_SP COMMA reg_bc_or_de { - ::error("LD SP, %s is not a valid instruction\n", reg_tt_names[$4]); + ::error("LD SP, %s is not a valid instruction", reg_tt_names[$4]); } | SM83_LD MODE_SP COMMA reloc_16bit { sect_ConstByte(0x01 | (REG_SP << 4)); @@ -2193,7 +2193,7 @@ sm83_ld_c_ind: sect_ConstByte(0xE2); } | SM83_LD c_ind COMMA MODE_A { - warning(WARNING_OBSOLETE, "LD [C], A is deprecated; use LDH [C], A\n"); + warning(WARNING_OBSOLETE, "LD [C], A is deprecated; use LDH [C], A"); sect_ConstByte(0xE2); } ; @@ -2211,7 +2211,7 @@ sm83_ld_r_no_a: } | SM83_LD reg_r_no_a COMMA reg_r { if ($2 == REG_HL_IND && $4 == REG_HL_IND) { - ::error("LD [HL], [HL] is not a valid instruction\n"); + ::error("LD [HL], [HL] is not a valid instruction"); } else { sect_ConstByte(0x40 | ($2 << 3) | $4); } @@ -2230,7 +2230,7 @@ sm83_ld_a: sect_ConstByte(0xF2); } | SM83_LD reg_a COMMA c_ind { - warning(WARNING_OBSOLETE, "LD A, [C] is deprecated; use LDH A, [C]\n"); + warning(WARNING_OBSOLETE, "LD A, [C] is deprecated; use LDH A, [C]"); sect_ConstByte(0xF2); } | SM83_LD reg_a COMMA reg_rr { @@ -2249,7 +2249,7 @@ sm83_ld_ss: } | SM83_LD reg_bc_or_de COMMA reg_tt_no_af { ::error( - "LD %s, %s is not a valid instruction; use LD %s, %s and LD %s, %s\n", + "LD %s, %s is not a valid instruction; use LD %s, %s and LD %s, %s", reg_tt_names[$2], reg_tt_names[$4], reg_tt_high_names[$2], @@ -2644,7 +2644,7 @@ hl_ind_dec: /******************** Semantic actions ********************/ void yy::parser::error(std::string const &str) { - ::error("%s\n", str.c_str()); + ::error("%s", str.c_str()); } static uint32_t strToNum(std::vector const &s) { @@ -2656,7 +2656,7 @@ static uint32_t strToNum(std::vector const &s) { return static_cast(s[0]); } - warning(WARNING_OBSOLETE, "Treating multi-unit strings as numbers is deprecated\n"); + warning(WARNING_OBSOLETE, "Treating multi-unit strings as numbers is deprecated"); for (int32_t v : s) { if (!checkNBit(v, 8, "All character units")) { @@ -2675,7 +2675,7 @@ static uint32_t strToNum(std::vector const &s) { } static void errorInvalidUTF8Byte(uint8_t byte, char const *functionName) { - error("%s: Invalid UTF-8 byte 0x%02hhX\n", functionName, byte); + error("%s: Invalid UTF-8 byte 0x%02hhX", functionName, byte); } static size_t strlenUTF8(std::string const &str, bool printErrors) { @@ -2702,7 +2702,7 @@ static size_t strlenUTF8(std::string const &str, bool printErrors) { // Check for partial code point. if (state != 0) { if (printErrors) { - error("STRLEN: Incomplete UTF-8 character\n"); + error("STRLEN: Incomplete UTF-8 character"); } len++; } @@ -2736,7 +2736,7 @@ static std::string strsliceUTF8(std::string const &str, uint32_t start, uint32_t if (!ptr[index] && start > curIdx) { warning( WARNING_BUILTIN_ARG, - "STRSLICE: Start index %" PRIu32 " is past the end of the string\n", + "STRSLICE: Start index %" PRIu32 " is past the end of the string", start ); } @@ -2759,14 +2759,14 @@ static std::string strsliceUTF8(std::string const &str, uint32_t start, uint32_t // Check for partial code point. if (state != 0) { - error("STRSLICE: Incomplete UTF-8 character\n"); + error("STRSLICE: Incomplete UTF-8 character"); curIdx++; } if (curIdx < stop) { warning( WARNING_BUILTIN_ARG, - "STRSLICE: Stop index %" PRIu32 " is past the end of the string\n", + "STRSLICE: Stop index %" PRIu32 " is past the end of the string", stop ); } @@ -2799,7 +2799,7 @@ static std::string strsubUTF8(std::string const &str, uint32_t pos, uint32_t len // "Length too big" warning below if the length is nonzero. if (!ptr[index] && pos > curPos) { warning( - WARNING_BUILTIN_ARG, "STRSUB: Position %" PRIu32 " is past the end of the string\n", pos + WARNING_BUILTIN_ARG, "STRSUB: Position %" PRIu32 " is past the end of the string", pos ); } @@ -2822,12 +2822,12 @@ static std::string strsubUTF8(std::string const &str, uint32_t pos, uint32_t len // Check for partial code point. if (state != 0) { - error("STRSUB: Incomplete UTF-8 character\n"); + error("STRSUB: Incomplete UTF-8 character"); curLen++; } if (curLen < len) { - warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %" PRIu32 "\n", len); + warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %" PRIu32, len); } return std::string(ptr + startIndex, ptr + index); @@ -2856,7 +2856,7 @@ static std::string strcharUTF8(std::string const &str, uint32_t idx) { if (!charmap_ConvertNext(view, nullptr)) { warning( WARNING_BUILTIN_ARG, - "STRCHAR: Index %" PRIu32 " is past the end of the string\n", + "STRCHAR: Index %" PRIu32 " is past the end of the string", idx ); } @@ -2879,7 +2879,7 @@ static std::string charsubUTF8(std::string const &str, uint32_t pos) { if (!charmap_ConvertNext(view, nullptr)) { warning( WARNING_BUILTIN_ARG, - "CHARSUB: Position %" PRIu32 " is past the end of the string\n", + "CHARSUB: Position %" PRIu32 " is past the end of the string", pos ); } @@ -2922,7 +2922,7 @@ static uint32_t adjustNegativeIndex(int32_t idx, size_t len, char const *functio idx += len; } if (idx < 0) { - warning(WARNING_BUILTIN_ARG, "%s: Index starts at 0\n", functionName); + warning(WARNING_BUILTIN_ARG, "%s: Index starts at 0", functionName); idx = 0; } return static_cast(idx); @@ -2935,7 +2935,7 @@ static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionN pos += len + 1; } if (pos < 1) { - warning(WARNING_BUILTIN_ARG, "%s: Position starts at 1\n", functionName); + warning(WARNING_BUILTIN_ARG, "%s: Position starts at 1", functionName); pos = 1; } return static_cast(pos); @@ -2943,7 +2943,7 @@ static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionN static std::string strrpl(std::string_view str, std::string const &old, std::string const &rep) { if (old.empty()) { - warning(WARNING_EMPTY_STRRPL, "STRRPL: Cannot replace an empty string\n"); + warning(WARNING_EMPTY_STRRPL, "STRRPL: Cannot replace an empty string"); return std::string(str); } @@ -2994,13 +2994,13 @@ static std::string } if (fmt.isEmpty()) { - error("STRFMT: Illegal '%%' at end of format string\n"); + error("STRFMT: Illegal '%%' at end of format string"); str += '%'; break; } if (!fmt.isValid()) { - error("STRFMT: Invalid format spec for argument %zu\n", argIndex + 1); + error("STRFMT: Invalid format spec for argument %zu", argIndex + 1); str += '%'; } else if (argIndex >= args.size()) { // Will warn after formatting is done. @@ -3015,10 +3015,10 @@ static std::string } if (argIndex < args.size()) { - error("STRFMT: %zu unformatted argument(s)\n", args.size() - argIndex); + error("STRFMT: %zu unformatted argument(s)", args.size() - argIndex); } else if (argIndex > args.size()) { error( - "STRFMT: Not enough arguments for format spec, got: %zu, need: %zu\n", + "STRFMT: Not enough arguments for format spec, got: %zu, need: %zu", args.size(), argIndex ); @@ -3041,12 +3041,12 @@ static void compoundAssignment(std::string const &symName, RPNCommand op, int32_ static void failAssert(AssertionType type) { switch (type) { case ASSERT_FATAL: - fatalerror("Assertion failed\n"); + fatalerror("Assertion failed"); case ASSERT_ERROR: - error("Assertion failed\n"); + error("Assertion failed"); break; case ASSERT_WARN: - warning(WARNING_ASSERT, "Assertion failed\n"); + warning(WARNING_ASSERT, "Assertion failed"); break; } } @@ -3054,12 +3054,12 @@ static void failAssert(AssertionType type) { static void failAssertMsg(AssertionType type, std::string const &message) { switch (type) { case ASSERT_FATAL: - fatalerror("Assertion failed: %s\n", message.c_str()); + fatalerror("Assertion failed: %s", message.c_str()); case ASSERT_ERROR: - error("Assertion failed: %s\n", message.c_str()); + error("Assertion failed: %s", message.c_str()); break; case ASSERT_WARN: - warning(WARNING_ASSERT, "Assertion failed: %s\n", message.c_str()); + warning(WARNING_ASSERT, "Assertion failed: %s", message.c_str()); break; } } diff --git a/src/asm/rpn.cpp b/src/asm/rpn.cpp index 865816e7..9a1f9c49 100644 --- a/src/asm/rpn.cpp +++ b/src/asm/rpn.cpp @@ -39,7 +39,7 @@ uint8_t *Expression::reserveSpace(uint32_t size, uint32_t patchSize) { int32_t Expression::getConstVal() const { if (!isKnown()) { - error("Expected constant expression: %s\n", data.get().c_str()); + error("Expected constant expression: %s", data.get().c_str()); return 0; } return value(); @@ -73,10 +73,10 @@ void Expression::makeNumber(uint32_t value) { void Expression::makeSymbol(std::string const &symName) { clear(); if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) { - error("PC has no value outside of a section\n"); + error("PC has no value outside of a section"); data = 0; } else if (sym && !sym->isNumeric() && !sym->isLabel()) { - error("'%s' is not a numeric symbol\n", symName.c_str()); + error("'%s' is not a numeric symbol", symName.c_str()); data = 0; } else if (!sym || !sym->isConstant()) { isSymbol = true; @@ -103,7 +103,7 @@ void Expression::makeBankSymbol(std::string const &symName) { if (Symbol const *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym)) { // The @ symbol is treated differently. if (!currentSection) { - error("PC has no bank outside of a section\n"); + error("PC has no bank outside of a section"); data = 1; } else if (currentSection->bank == UINT32_MAX) { data = "Current section's bank is not known"; @@ -114,7 +114,7 @@ void Expression::makeBankSymbol(std::string const &symName) { } return; } else if (sym && !sym->isLabel()) { - error("BANK argument must be a label\n"); + error("BANK argument must be a label"); data = 1; } else { sym = sym_Ref(symName); @@ -394,43 +394,37 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const break; case RPN_SHL: if (rval < 0) { - warning( - WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32 "\n", rval - ); + warning(WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32, rval); } if (rval >= 32) { - warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32 "\n", rval); + warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32, rval); } data = op_shift_left(lval, rval); break; case RPN_SHR: if (lval < 0) { - warning(WARNING_SHIFT, "Shifting right negative value %" PRId32 "\n", lval); + warning(WARNING_SHIFT, "Shifting right negative value %" PRId32, lval); } if (rval < 0) { - warning( - WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval - ); + warning(WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32, rval); } if (rval >= 32) { - warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval); + warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32, rval); } data = op_shift_right(lval, rval); break; case RPN_USHR: if (rval < 0) { - warning( - WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32 "\n", rval - ); + warning(WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32, rval); } if (rval >= 32) { - warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32 "\n", rval); + warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32, rval); } data = op_shift_right_unsigned(lval, rval); @@ -440,13 +434,13 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const break; case RPN_DIV: if (rval == 0) { - fatalerror("Division by zero\n"); + fatalerror("Division by zero"); } if (lval == INT32_MIN && rval == -1) { warning( WARNING_DIV, - "Division of %" PRId32 " by -1 yields %" PRId32 "\n", + "Division of %" PRId32 " by -1 yields %" PRId32, INT32_MIN, INT32_MIN ); @@ -457,7 +451,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const break; case RPN_MOD: if (rval == 0) { - fatalerror("Modulo by zero\n"); + fatalerror("Modulo by zero"); } if (lval == INT32_MIN && rval == -1) { @@ -468,7 +462,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const break; case RPN_EXP: if (rval < 0) { - fatalerror("Exponentiation by negative power\n"); + fatalerror("Exponentiation by negative power"); } data = op_exponent(lval, rval); @@ -551,7 +545,7 @@ bool Expression::makeCheckHRAM() { // That range is valid, but deprecated return true; } else { - error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", val); + error("Source address $%" PRIx32 " not between $FF00 to $FFFF", val); } return false; } @@ -561,7 +555,7 @@ void Expression::makeCheckRST() { *reserveSpace(1) = RPN_RST; } else if (int32_t val = value(); val & ~0x38) { // A valid RST address must be masked with 0x38 - error("Invalid address $%" PRIx32 " for RST\n", val); + error("Invalid address $%" PRIx32 " for RST", val); } } @@ -575,7 +569,7 @@ void Expression::makeCheckBitIndex(uint8_t mask) { } else if (int32_t val = value(); val & ~0x07) { // A valid bit index must be masked with 0x07 static char const *instructions[4] = {"instruction", "BIT", "RES", "SET"}; - error("Invalid bit index %" PRId32 " for %s\n", val, instructions[mask >> 6]); + error("Invalid bit index %" PRId32 " for %s", val, instructions[mask >> 6]); } } @@ -593,20 +587,20 @@ bool checkNBit(int32_t v, uint8_t n, char const *name) { if (v < -(1 << n) || v >= 1 << n) { warning( WARNING_TRUNCATION_1, - n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n" - : "%s must be %u-bit\n", + "%s must be %u-bit%s", name ? name : "Expression", - n + n, + n == 8 && !name ? "; use LOW() to force 8-bit" : "" ); return false; } if (v < -(1 << (n - 1))) { warning( WARNING_TRUNCATION_2, - n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n" - : "%s must be %u-bit\n", + "%s must be %u-bit%s", name ? name : "Expression", - n + n, + n == 8 && !name ? "; use LOW() to force 8-bit" : "" ); return false; } diff --git a/src/asm/section.cpp b/src/asm/section.cpp index b44f21f8..afb636ad 100644 --- a/src/asm/section.cpp +++ b/src/asm/section.cpp @@ -55,7 +55,7 @@ static bool requireSection() { return true; } - error("Cannot output data outside of a SECTION\n"); + error("Cannot output data outside of a SECTION"); return false; } @@ -72,8 +72,7 @@ static bool requireCodeSection() { } error( - "Section '%s' cannot contain code or data (not ROM0 or ROMX)\n", - currentSection->name.c_str() + "Section '%s' cannot contain code or data (not ROM0 or ROMX)", currentSection->name.c_str() ); return false; } @@ -82,8 +81,7 @@ void sect_CheckSizes() { for (Section const § : sectionList) { if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize) { error( - "Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32 - ")\n", + "Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32 ")", sect.name.c_str(), maxSize, sect.size @@ -113,18 +111,18 @@ static unsigned int mergeSectUnion( // Unionized sections only need "compatible" constraints, and they end up with the strictest // combination of both. if (sect_HasData(type)) { - sectError("Cannot declare ROM sections as UNION\n"); + sectError("Cannot declare ROM sections as UNION"); } if (org != UINT32_MAX) { // If both are fixed, they must be the same if (sect.org != UINT32_MAX && sect.org != org) { sectError( - "Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org + "Section already declared as fixed at different address $%04" PRIx32, sect.org ); } else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs))) { sectError( - "Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n", + "Section already declared as aligned to %u bytes (offset %" PRIu16 ")", 1U << sect.align, sect.alignOfs ); @@ -138,15 +136,14 @@ static unsigned int mergeSectUnion( if (sect.org != UINT32_MAX) { if ((sect.org - alignOffset) & mask(alignment)) { sectError( - "Section already declared as fixed at incompatible address $%04" PRIx32 "\n", + "Section already declared as fixed at incompatible address $%04" PRIx32, sect.org ); } // Check if alignment offsets are compatible } else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) { sectError( - "Section already declared with incompatible %u" - "-byte alignment (offset %" PRIu16 ")\n", + "Section already declared with incompatible %u-byte alignment (offset %" PRIu16 ")", 1U << sect.align, sect.alignOfs ); @@ -174,12 +171,11 @@ static unsigned int // If both are fixed, they must be the same if (sect.org != UINT32_MAX && sect.org != curOrg) { sectError( - "Section already declared as fixed at incompatible address $%04" PRIx32 "\n", - sect.org + "Section already declared as fixed at incompatible address $%04" PRIx32, sect.org ); } else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) { sectError( - "Section already declared as aligned to %u bytes (offset %" PRIu16 ")\n", + "Section already declared as aligned to %u bytes (offset %" PRIu16 ")", 1U << sect.align, sect.alignOfs ); @@ -199,15 +195,14 @@ static unsigned int if (sect.org != UINT32_MAX) { if ((sect.org - curOfs) & mask(alignment)) { sectError( - "Section already declared as fixed at incompatible address $%04" PRIx32 "\n", + "Section already declared as fixed at incompatible address $%04" PRIx32, sect.org ); } // Check if alignment offsets are compatible } else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) { sectError( - "Section already declared with incompatible %u" - "-byte alignment (offset %" PRIu16 ")\n", + "Section already declared with incompatible %u-byte alignment (offset %" PRIu16 ")", 1U << sect.align, sect.alignOfs ); @@ -234,12 +229,12 @@ static void mergeSections( if (type != sect.type) { sectError( - "Section already exists but with type %s\n", sectionTypeInfo[sect.type].name.c_str() + "Section already exists but with type %s", sectionTypeInfo[sect.type].name.c_str() ); } if (sect.modifier != mod) { - sectError("Section already declared as SECTION %s\n", sectionModNames[sect.modifier]); + sectError("Section already declared as SECTION %s", sectionModNames[sect.modifier]); } else { switch (mod) { case SECTION_UNION: @@ -256,21 +251,22 @@ static void mergeSections( } // If both specify a bank, it must be the same one else if (bank != UINT32_MAX && sect.bank != bank) { - sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank); + sectError("Section already declared with different bank %" PRIu32, sect.bank); } break; case SECTION_NORMAL: - sectError("Section already defined previously at "); + errorNoNewline("Section already defined previously at "); sect.src->dump(sect.fileLine); putc('\n', stderr); + nbSectErrors++; break; } } if (nbSectErrors) { fatalerror( - "Cannot create section \"%s\" (%u error%s)\n", + "Cannot create section \"%s\" (%u error%s)", sect.name.c_str(), nbSectErrors, nbSectErrors == 1 ? "" : "s" @@ -332,11 +328,11 @@ static Section *getSection( if (bank != UINT32_MAX) { if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX) { - error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n"); + error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections"); } else if (bank < sectionTypeInfo[type].firstBank || bank > sectionTypeInfo[type].lastBank) { error( - "%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")\n", + "%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04" PRIx32 ")", sectionTypeInfo[type].name.c_str(), bank, sectionTypeInfo[type].firstBank, @@ -350,7 +346,7 @@ static Section *getSection( if (alignOffset >= 1 << alignment) { error( - "Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)\n", + "Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)", alignOffset, 1U << alignment ); @@ -361,7 +357,7 @@ static Section *getSection( if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) { error( "Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16 - "; $%04" PRIx16 "]\n", + "; $%04" PRIx16 "]", name.c_str(), org, sectionTypeInfo[type].startAddr, @@ -372,7 +368,7 @@ static Section *getSection( if (alignment != 0) { if (alignment > 16) { - error("Alignment must be between 0 and 16, not %u\n", alignment); + error("Alignment must be between 0 and 16, not %u", alignment); alignment = 16; } // It doesn't make sense to have both alignment and org set @@ -380,12 +376,12 @@ static Section *getSection( if (org != UINT32_MAX) { if ((org - alignOffset) & mask) { - error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str()); + error("Section \"%s\"'s fixed address doesn't match its alignment", name.c_str()); } alignment = 0; // Ignore it if it's satisfied } else if (sectionTypeInfo[type].startAddr & mask) { error( - "Section \"%s\"'s alignment cannot be attained in %s\n", + "Section \"%s\"'s alignment cannot be attained in %s", name.c_str(), sectionTypeInfo[type].name.c_str() ); @@ -415,7 +411,7 @@ static Section *getSection( // Set the current section static void changeSection() { if (!currentUnionStack.empty()) { - fatalerror("Cannot change the section within a UNION\n"); + fatalerror("Cannot change the section within a UNION"); } sym_ResetCurrentLabelScopes(); @@ -452,7 +448,7 @@ void sect_NewSection( ) { for (SectionStackEntry &entry : sectionStack) { if (entry.section && entry.section->name == name) { - fatalerror("Section '%s' is already on the stack\n", name.c_str()); + fatalerror("Section '%s' is already on the stack", name.c_str()); } } @@ -486,7 +482,7 @@ void sect_SetLoadSection( } if (sect_HasData(type)) { - error("`LOAD` blocks cannot create a ROM section\n"); + error("`LOAD` blocks cannot create a ROM section"); return; } @@ -505,13 +501,11 @@ void sect_SetLoadSection( void sect_EndLoadSection(char const *cause) { if (cause) { - warning( - WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`\n", cause - ); + warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by `%s`", cause); } if (!currentLoadSection) { - error("Found `ENDL` outside of a `LOAD` block\n"); + error("Found `ENDL` outside of a `LOAD` block"); return; } @@ -524,7 +518,7 @@ void sect_EndLoadSection(char const *cause) { void sect_CheckLoadClosed() { if (currentLoadSection) { - warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n"); + warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF"); } } @@ -575,7 +569,7 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) { if (uint32_t actualOffset = (sect->org + curOffset) % alignSize; actualOffset != offset) { error( "Section is misaligned (at PC = $%04" PRIx32 ", expected ALIGN[%" PRIu32 - ", %" PRIu32 "], got ALIGN[%" PRIu32 ", %" PRIu32 "])\n", + ", %" PRIu32 "], got ALIGN[%" PRIu32 ", %" PRIu32 "])", sect->org + curOffset, alignment, offset, @@ -590,7 +584,7 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) { error( "Section is misaligned ($%04" PRIx32 " bytes into the section, expected ALIGN[%" PRIu32 ", %" PRIu32 - "], got ALIGN[%" PRIu32 ", %" PRIu32 "])\n", + "], got ALIGN[%" PRIu32 ", %" PRIu32 "])", curOffset, alignment, offset, @@ -601,7 +595,7 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) { // Treat an alignment large enough as fixing the address. // Note that this also ensures that a section's alignment never becomes 16 or greater. if (alignment > 16) { - error("Alignment must be between 0 and 16, not %u\n", alignment); + error("Alignment must be between 0 and 16, not %u", alignment); } sect->align = 0; // Reset the alignment, since we're fixing the address. sect->org = offset - curOffset; @@ -615,7 +609,7 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) { static void growSection(uint32_t growth) { if (growth > 0 && curOffset > UINT32_MAX - growth) { - fatalerror("Section size would overflow internal counter\n"); + fatalerror("Section size would overflow internal counter"); } curOffset += growth; if (uint32_t outOffset = sect_GetOutputOffset(); outOffset > currentSection->size) { @@ -656,11 +650,11 @@ void sect_StartUnion() { // your own peril! ^^ if (!currentSection) { - error("UNIONs must be inside a SECTION\n"); + error("UNIONs must be inside a SECTION"); return; } if (sect_HasData(currentSection->type)) { - error("Cannot use UNION inside of ROM0 or ROMX sections\n"); + error("Cannot use UNION inside of ROM0 or ROMX sections"); return; } @@ -679,7 +673,7 @@ static void endUnionMember() { void sect_NextUnionMember() { if (currentUnionStack.empty()) { - error("Found NEXTU outside of a UNION construct\n"); + error("Found NEXTU outside of a UNION construct"); return; } endUnionMember(); @@ -687,7 +681,7 @@ void sect_NextUnionMember() { void sect_EndUnion() { if (currentUnionStack.empty()) { - error("Found ENDU outside of a UNION construct\n"); + error("Found ENDU outside of a UNION construct"); return; } endUnionMember(); @@ -697,7 +691,7 @@ void sect_EndUnion() { void sect_CheckUnionClosed() { if (!currentUnionStack.empty()) { - error("Unterminated UNION construct\n"); + error("Unterminated UNION construct"); } } @@ -767,7 +761,7 @@ void sect_Skip(uint32_t skip, bool ds) { if (!ds) { warning( WARNING_EMPTY_DATA_DIRECTIVE, - "%s directive without data in ROM\n", + "%s directive without data in ROM", (skip == 4) ? "DL" : (skip == 2) ? "DW" : "DB" @@ -862,7 +856,7 @@ void sect_PCRelByte(Expression const &expr, uint32_t pcShift) { if (offset < -128 || offset > 127) { error( "JR target must be between -128 and 127 bytes away, not %" PRId16 - "; use JP instead\n", + "; use JP instead", offset ); writeByte(0); @@ -875,7 +869,7 @@ void sect_PCRelByte(Expression const &expr, uint32_t pcShift) { // Output a binary file void sect_BinaryFile(std::string const &name, int32_t startPos) { if (startPos < 0) { - error("Start position cannot be negative (%" PRId32 ")\n", startPos); + error("Start position cannot be negative (%" PRId32 ")", startPos); startPos = 0; } if (!requireCodeSection()) { @@ -895,7 +889,7 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) { // LCOV_EXCL_STOP failedOnMissingInclude = true; } else { - error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno)); + error("Error opening INCBIN file '%s': %s", name.c_str(), strerror(errno)); } return; } @@ -903,23 +897,19 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) { if (fseek(file, 0, SEEK_END) != -1) { if (startPos > ftell(file)) { - error("Specified start position is greater than length of file '%s'\n", name.c_str()); + error("Specified start position is greater than length of file '%s'", name.c_str()); return; } // The file is seekable; skip to the specified start position fseek(file, startPos, SEEK_SET); } else { if (errno != ESPIPE) { - error( - "Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno) - ); + error("Error determining size of INCBIN file '%s': %s", name.c_str(), strerror(errno)); } // The file isn't seekable, so we'll just skip bytes one at a time while (startPos--) { if (fgetc(file) == EOF) { - error( - "Specified start position is greater than length of file '%s'\n", name.c_str() - ); + error("Specified start position is greater than length of file '%s'", name.c_str()); return; } } @@ -930,18 +920,18 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) { } if (ferror(file)) { - error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno)); + error("Error reading INCBIN file '%s': %s", name.c_str(), strerror(errno)); } } // Output a slice of a binary file void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length) { if (startPos < 0) { - error("Start position cannot be negative (%" PRId32 ")\n", startPos); + error("Start position cannot be negative (%" PRId32 ")", startPos); startPos = 0; } if (length < 0) { - error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length); + error("Number of bytes to read cannot be negative (%" PRId32 ")", length); length = 0; } if (!requireCodeSection()) { @@ -964,7 +954,7 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len // LCOV_EXCL_STOP failedOnMissingInclude = true; } else { - error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno)); + error("Error opening INCBIN file '%s': %s", name.c_str(), strerror(errno)); } return; } @@ -972,12 +962,12 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len if (fseek(file, 0, SEEK_END) != -1) { if (int32_t fsize = ftell(file); startPos > fsize) { - error("Specified start position is greater than length of file '%s'\n", name.c_str()); + error("Specified start position is greater than length of file '%s'", name.c_str()); return; } else if (startPos + length > fsize) { error( "Specified range in INCBIN file '%s' is out of bounds (%" PRIu32 " + %" PRIu32 - " > %" PRIu32 ")\n", + " > %" PRIu32 ")", name.c_str(), startPos, length, @@ -989,16 +979,12 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len fseek(file, startPos, SEEK_SET); } else { if (errno != ESPIPE) { - error( - "Error determining size of INCBIN file '%s': %s\n", name.c_str(), strerror(errno) - ); + error("Error determining size of INCBIN file '%s': %s", name.c_str(), strerror(errno)); } // The file isn't seekable, so we'll just skip bytes one at a time while (startPos--) { if (fgetc(file) == EOF) { - error( - "Specified start position is greater than length of file '%s'\n", name.c_str() - ); + error("Specified start position is greater than length of file '%s'", name.c_str()); return; } } @@ -1008,10 +994,10 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len if (int byte = fgetc(file); byte != EOF) { writeByte(byte); } else if (ferror(file)) { - error("Error reading INCBIN file '%s': %s\n", name.c_str(), strerror(errno)); + error("Error reading INCBIN file '%s': %s", name.c_str(), strerror(errno)); } else { error( - "Premature end of INCBIN file '%s' (%" PRId32 " bytes left to read)\n", + "Premature end of INCBIN file '%s' (%" PRId32 " bytes left to read)", name.c_str(), length + 1 ); @@ -1039,7 +1025,7 @@ void sect_PushSection() { void sect_PopSection() { if (sectionStack.empty()) { - fatalerror("No entries in the section stack\n"); + fatalerror("No entries in the section stack"); } if (currentLoadSection) { @@ -1060,17 +1046,17 @@ void sect_PopSection() { void sect_CheckStack() { if (!sectionStack.empty()) { - warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHS` without corresponding `POPS`\n"); + warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHS` without corresponding `POPS`"); } } void sect_EndSection() { if (!currentSection) { - fatalerror("Cannot end the section outside of a SECTION\n"); + fatalerror("Cannot end the section outside of a SECTION"); } if (!currentUnionStack.empty()) { - fatalerror("Cannot end the section within a UNION\n"); + fatalerror("Cannot end the section within a UNION"); } if (currentLoadSection) { diff --git a/src/asm/symbol.cpp b/src/asm/symbol.cpp index 9510a8d8..9476854a 100644 --- a/src/asm/symbol.cpp +++ b/src/asm/symbol.cpp @@ -3,6 +3,7 @@ #include "asm/symbol.hpp" #include +#include #include #include #include @@ -51,14 +52,14 @@ static int32_t NARGCallback() { if (MacroArgs const *macroArgs = fstk_GetCurrentMacroArgs(); macroArgs) { return macroArgs->nbArgs(); } else { - error("_NARG has no value outside of a macro\n"); + error("_NARG has no value outside of a macro"); return 0; } } static std::shared_ptr globalScopeCallback() { if (!globalScope) { - error("\".\" has no value outside of a label scope\n"); + error("\".\" has no value outside of a label scope"); return std::make_shared(""); } return std::make_shared(globalScope->name); @@ -66,7 +67,7 @@ static std::shared_ptr globalScopeCallback() { static std::shared_ptr localScopeCallback() { if (!localScope) { - error("\"..\" has no value outside of a local label scope\n"); + error("\"..\" has no value outside of a local label scope"); return std::make_shared(""); } return std::make_shared(localScope->name); @@ -111,6 +112,7 @@ std::shared_ptr Symbol::getEqus() const { } static void dumpFilename(Symbol const &sym) { + fputs(" at ", stderr); if (sym.src) { sym.src->dump(sym.fileLine); putc('\n', stderr); @@ -140,13 +142,12 @@ static bool isValidIdentifier(std::string const &s) { static void alreadyDefinedError(Symbol const &sym, char const *asType) { if (sym.isBuiltin && !sym_FindScopedValidSymbol(sym.name)) { // `DEF()` would return false, so we should not claim the symbol is already defined - error("'%s' is reserved for a built-in symbol\n", sym.name.c_str()); + error("'%s' is reserved for a built-in symbol", sym.name.c_str()); } else { - error("'%s' already defined", sym.name.c_str()); + errorNoNewline("'%s' already defined", sym.name.c_str()); if (asType) { fprintf(stderr, " as %s", asType); } - fputs(" at ", stderr); dumpFilename(sym); if (sym.type == SYM_EQUS) { if (std::string const &contents = *sym.getEqus(); isValidIdentifier(contents)) { @@ -164,9 +165,9 @@ static void redefinedError(Symbol const &sym) { assume(sym.isBuiltin); if (!sym_FindScopedValidSymbol(sym.name)) { // `DEF()` would return false, so we should not imply the symbol is already defined - error("'%s' is reserved for a built-in symbol\n", sym.name.c_str()); + error("'%s' is reserved for a built-in symbol", sym.name.c_str()); } else { - error("Built-in symbol '%s' cannot be redefined\n", sym.name.c_str()); + error("Built-in symbol '%s' cannot be redefined", sym.name.c_str()); } } @@ -215,12 +216,12 @@ static bool isAutoScoped(std::string const &symName) { // Check for nothing after the dot if (dotPos == symName.length() - 1) { - fatalerror("'%s' is a nonsensical reference to an empty local label\n", symName.c_str()); + fatalerror("'%s' is a nonsensical reference to an empty local label", symName.c_str()); } // Check for more than one dot if (symName.find('.', dotPos + 1) != std::string::npos) { - fatalerror("'%s' is a nonsensical reference to a nested local label\n", symName.c_str()); + fatalerror("'%s' is a nonsensical reference to a nested local label", symName.c_str()); } // Check for already-qualified local label @@ -230,7 +231,7 @@ static bool isAutoScoped(std::string const &symName) { // Check for unqualifiable local label if (!globalScope) { - fatalerror("Unqualified local label '%s' in main scope\n", symName.c_str()); + fatalerror("Unqualified local label '%s' in main scope", symName.c_str()); } return true; @@ -279,19 +280,19 @@ void sym_Purge(std::string const &symName) { if (!sym) { if (sym_IsPurgedScoped(symName)) { - error("'%s' was already purged\n", symName.c_str()); + error("'%s' was already purged", symName.c_str()); } else { - error("'%s' not defined\n", symName.c_str()); + error("'%s' not defined", symName.c_str()); } } else if (sym->isBuiltin) { - error("Built-in symbol '%s' cannot be purged\n", symName.c_str()); + error("Built-in symbol '%s' cannot be purged", symName.c_str()); } else if (sym->ID != UINT32_MAX) { - error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str()); + error("Symbol \"%s\" is referenced and thus cannot be purged", symName.c_str()); } else { if (sym->isExported) { - warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"\n", symName.c_str()); + warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"", symName.c_str()); } else if (sym->isLabel()) { - warning(WARNING_PURGE_2, "Purging a label \"%s\"\n", symName.c_str()); + warning(WARNING_PURGE_2, "Purging a label \"%s\"", symName.c_str()); } // Do not keep a reference to the label after purging it if (sym == globalScope) { @@ -331,12 +332,12 @@ uint32_t Symbol::getConstantValue() const { if (sym_IsPC(this)) { if (!getSection()) { - error("PC has no value outside of a section\n"); + error("PC has no value outside of a section"); } else { - error("PC does not have a constant value; the current section is not fixed\n"); + error("PC does not have a constant value; the current section is not fixed"); } } else { - error("\"%s\" does not have a constant value\n", name.c_str()); + error("\"%s\" does not have a constant value", name.c_str()); } return 0; } @@ -371,7 +372,7 @@ static Symbol *createNonrelocSymbol(std::string const &symName, bool numeric) { return nullptr; // Don't allow overriding the symbol, that'd be bad! } else if (!numeric) { // The symbol has already been referenced, but it's not allowed - error("'%s' already referenced at ", symName.c_str()); + errorNoNewline("'%s' already referenced", symName.c_str()); dumpFilename(*sym); return nullptr; // Don't allow overriding the symbol, that'd be bad! } @@ -437,7 +438,7 @@ Symbol *sym_RedefString(std::string const &symName, std::shared_ptr if (sym->isDefined()) { alreadyDefinedError(*sym, "non-EQUS"); } else { - error("'%s' already referenced at ", symName.c_str()); + errorNoNewline("'%s' already referenced", symName.c_str()); dumpFilename(*sym); } return nullptr; @@ -493,7 +494,7 @@ static Symbol *addLabel(std::string const &symName) { sym->section = sect_GetSymbolSection(); if (sym && !sym->section) { - error("Label \"%s\" created outside of a SECTION\n", symName.c_str()); + error("Label \"%s\" created outside of a SECTION", symName.c_str()); } return sym; @@ -549,7 +550,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) { if (ofs > anonLabelID) { error( "Reference to anonymous label %" PRIu32 " before, when only %" PRIu32 - " ha%s been created so far\n", + " ha%s been created so far", ofs, anonLabelID, anonLabelID == 1 ? "s" : "ve" @@ -563,7 +564,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) { // LCOV_EXCL_START error( "Reference to anonymous label %" PRIu32 " after, when only %" PRIu32 - " may still be created\n", + " may still be created", ofs + 1, UINT32_MAX - anonLabelID ); @@ -580,7 +581,7 @@ void sym_Export(std::string const &symName) { if (symName.starts_with('!')) { // LCOV_EXCL_START // The parser does not accept anonymous labels for an `EXPORT` directive - error("Anonymous labels cannot be exported\n"); + error("Anonymous labels cannot be exported"); return; // LCOV_EXCL_STOP } @@ -666,7 +667,7 @@ void sym_Init(time_t now) { // LCOV_EXCL_START if (now == static_cast(-1)) { - warn("Failed to determine current time"); + warnx("Failed to determine current time: %s", strerror(errno)); // Fall back by pretending we are at the Epoch now = 0; } diff --git a/src/asm/warning.cpp b/src/asm/warning.cpp index bb6636d3..63ebac30 100644 --- a/src/asm/warning.cpp +++ b/src/asm/warning.cpp @@ -65,7 +65,7 @@ Diagnostics warnings = { }; // clang-format on -void printDiag( +static void printDiag( char const *fmt, va_list args, char const *type, char const *flagfmt, char const *flag ) { fputs(type, stderr); @@ -74,6 +74,7 @@ void printDiag( fprintf(stderr, flagfmt, flag); fputs("\n ", stderr); vfprintf(stderr, fmt, args); + putc('\n', stderr); lexer_DumpStringExpansions(); } @@ -96,6 +97,28 @@ void error(char const *fmt, ...) { } } +void errorNoNewline(char const *fmt, ...) { + va_list args; + + fputs("error: ", stderr); + fstk_DumpCurrent(); + fputs(":\n ", stderr); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + // This intentionally makes 0 act as "unlimited" (or at least "limited to sizeof(unsigned)") + nbErrors++; + if (nbErrors == maxErrors) { + errx( + "The maximum of %u error%s was reached (configure with \"-X/--max-errors\"); assembly " + "aborted!", + maxErrors, + maxErrors == 1 ? "" : "s" + ); + } +} + [[noreturn]] void fatalerror(char const *fmt, ...) { va_list args; diff --git a/src/error.cpp b/src/error.cpp index 99add990..4f169c1c 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -8,68 +8,22 @@ #include #include -static void vwarn(char const *fmt, va_list ap) { - char const *error = strerror(errno); - - fprintf(stderr, "warning: "); - vfprintf(stderr, fmt, ap); - fprintf(stderr, ": %s\n", error); -} - -static void vwarnx(char const *fmt, va_list ap) { - fprintf(stderr, "warning: "); - vfprintf(stderr, fmt, ap); - putc('\n', stderr); -} - -[[noreturn]] -static void verr(char const *fmt, va_list ap) { - char const *error = strerror(errno); - - fprintf(stderr, "error: "); - vfprintf(stderr, fmt, ap); - fprintf(stderr, ": %s\n", error); - va_end(ap); - exit(1); -} - -[[noreturn]] -static void verrx(char const *fmt, va_list ap) { - fprintf(stderr, "error: "); - vfprintf(stderr, fmt, ap); - putc('\n', stderr); - va_end(ap); - exit(1); -} - -void warn(char const *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - vwarn(fmt, ap); - va_end(ap); -} - void warnx(char const *fmt, ...) { va_list ap; - + fputs("warning: ", stderr); va_start(ap, fmt); - vwarnx(fmt, ap); + vfprintf(stderr, fmt, ap); va_end(ap); -} - -[[noreturn]] -void err(char const *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - verr(fmt, ap); + putc('\n', stderr); } [[noreturn]] void errx(char const *fmt, ...) { va_list ap; - + fputs("error: ", stderr); va_start(ap, fmt); - verrx(fmt, ap); + vfprintf(stderr, fmt, ap); + va_end(ap); + putc('\n', stderr); + exit(1); } diff --git a/src/fix/main.cpp b/src/fix/main.cpp index b75382f7..e4ebb809 100644 --- a/src/fix/main.cpp +++ b/src/fix/main.cpp @@ -12,6 +12,7 @@ #include #include +#include "error.hpp" #include "extern/getopt.hpp" #include "helpers.hpp" #include "platform.hpp" @@ -77,17 +78,32 @@ static void printUsage() { } // LCOV_EXCL_STOP -static uint8_t nbErrors; +static uint32_t nbErrors; [[gnu::format(printf, 1, 2)]] -static void report(char const *fmt, ...) { +static void error(char const *fmt, ...) { va_list ap; - + fputs("error: ", stderr); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); + putc('\n', stderr); - if (nbErrors != UINT8_MAX) { + if (nbErrors != UINT32_MAX) { + nbErrors++; + } +} + +[[gnu::format(printf, 1, 2)]] +static void fatal(char const *fmt, ...) { + va_list ap; + fputs("FATAL: ", stderr); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + putc('\n', stderr); + + if (nbErrors != UINT32_MAX) { nbErrors++; } } @@ -164,6 +180,7 @@ enum MbcType { }; static void printAcceptedMBCNames() { + fputs("Accepted MBC names:\n", stderr); fputs("\tROM ($00) [aka ROM_ONLY]\n", stderr); fputs("\tMBC1 ($01), MBC1+RAM ($02), MBC1+RAM+BATTERY ($03)\n", stderr); fputs("\tMBC2 ($05), MBC2+BATTERY ($06)\n", stderr); @@ -212,7 +229,6 @@ static bool readMBCSlice(char const *&name, char const *expected) { static MbcType parseMBC(char const *name) { if (!strcasecmp(name, "help")) { - fputs("Accepted MBC names:\n", stderr); printAcceptedMBCNames(); exit(0); } @@ -343,12 +359,12 @@ static MbcType parseMBC(char const *name) { unsigned long val = strtoul(ptr, &endptr, 10); if (endptr == ptr) { - report("error: Failed to parse TPP1 major revision number\n"); + error("Failed to parse TPP1 major revision number"); return MBC_BAD_TPP1; } ptr = endptr; if (val != 1) { - report("error: RGBFIX only supports TPP1 version 1.0\n"); + error("RGBFIX only supports TPP1 version 1.0"); return MBC_BAD_TPP1; } tpp1Rev[0] = val; @@ -356,12 +372,12 @@ static MbcType parseMBC(char const *name) { // Minor val = strtoul(ptr, &endptr, 10); if (endptr == ptr) { - report("error: Failed to parse TPP1 minor revision number\n"); + error("Failed to parse TPP1 minor revision number"); return MBC_BAD_TPP1; } ptr = endptr; if (val > 0xFF) { - report("error: TPP1 minor revision number must be 8-bit\n"); + error("TPP1 minor revision number must be 8-bit"); return MBC_BAD_TPP1; } tpp1Rev[1] = val; @@ -509,7 +525,7 @@ static MbcType parseMBC(char const *name) { // Handle timer, which also requires battery if (features & TIMER) { if (!(features & BATTERY)) { - fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n"); + warnx("MBC3+TIMER implies BATTERY"); } features &= ~(TIMER | BATTERY); // Reset those bits mbc = MBC3_TIMER_BATTERY; @@ -571,9 +587,7 @@ static MbcType parseMBC(char const *name) { case TPP1: if (features & RAM) { - fprintf( - stderr, "warning: TPP1 requests RAM implicitly if given a non-zero RAM size" - ); + warnx("TPP1 requests RAM implicitly if given a non-zero RAM size"); } if (features & BATTERY) { mbc |= 0x08; @@ -864,7 +878,7 @@ static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char uint8_t origByte = rom0[addr]; if (!overwriteRom && origByte != 0 && origByte != fixedByte) { - fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName); + warnx("Overwrote a non-zero byte in the %s", areaName); } rom0[addr] = fixedByte; @@ -878,7 +892,7 @@ static void overwriteBytes( uint8_t origByte = rom0[i + startAddr]; if (origByte != 0 && origByte != fixed[i]) { - fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName); + warnx("Overwrote a non-zero byte in the %s", areaName); break; } } @@ -902,12 +916,12 @@ static void if (rom0Len == -1) { // LCOV_EXCL_START - report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno)); + fatal("Failed to read \"%s\"'s header: %s", name, strerror(errno)); return; // LCOV_EXCL_STOP } else if (rom0Len < headerSize) { - report( - "FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n", + fatal( + "\"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd", name, static_cast(headerSize), static_cast(headerSize), @@ -990,11 +1004,7 @@ static void if (oldLicensee != UNSPECIFIED) { overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code"); } else if (sgb && rom0[0x14B] != 0x33) { - fprintf( - stderr, - "warning: SGB compatibility enabled, but old licensee was 0x%02x, not 0x33\n", - rom0[0x14B] - ); + warnx("SGB compatibility enabled, but old licensee was 0x%02x, not 0x33", rom0[0x14B]); } if (romVersion != UNSPECIFIED) { @@ -1019,7 +1029,7 @@ static void // Handle ROMX if (input == output) { if (fileSize >= 0x10000 * BANK_SIZE) { - report("FATAL: \"%s\" has more than 65536 banks\n", name); + fatal("\"%s\" has more than 65536 banks", name); return; } // This should be guaranteed from the size cap... @@ -1041,7 +1051,7 @@ static void 0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS" ); if (nbBanks == 0x10000) { - report("FATAL: \"%s\" has more than 65536 banks\n", name); + fatal("\"%s\" has more than 65536 banks", name); return; } nbBanks++; @@ -1143,7 +1153,7 @@ static void if (input == output) { if (lseek(output, 0, SEEK_SET) == static_cast(-1)) { // LCOV_EXCL_START - report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno)); + fatal("Failed to rewind \"%s\": %s", name, strerror(errno)); return; // LCOV_EXCL_STOP } @@ -1157,13 +1167,13 @@ static void if (writeLen == -1) { // LCOV_EXCL_START - report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno)); + fatal("Failed to write \"%s\"'s ROM0: %s", name, strerror(errno)); return; // LCOV_EXCL_STOP } else if (writeLen < rom0Len) { // LCOV_EXCL_START - report( - "FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n", + fatal( + "Could only write %jd of \"%s\"'s %jd ROM0 bytes", static_cast(writeLen), name, static_cast(rom0Len) @@ -1179,13 +1189,13 @@ static void writeLen = writeBytes(output, romx.data(), totalRomxLen); if (writeLen == -1) { // LCOV_EXCL_START - report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno)); + fatal("Failed to write \"%s\"'s ROMX: %s", name, strerror(errno)); return; // LCOV_EXCL_STOP } else if (static_cast(writeLen) < totalRomxLen) { // LCOV_EXCL_START - report( - "FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n", + fatal( + "Could only write %jd of \"%s\"'s %zu ROMX bytes", static_cast(writeLen), name, totalRomxLen @@ -1200,7 +1210,7 @@ static void if (input == output) { if (lseek(output, 0, SEEK_END) == static_cast(-1)) { // LCOV_EXCL_START - report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno)); + fatal("Failed to seek to end of \"%s\": %s", name, strerror(errno)); return; // LCOV_EXCL_STOP } @@ -1217,7 +1227,7 @@ static void // so it's fine to cast to `size_t` if (static_cast(ret) != thisLen) { // LCOV_EXCL_START - report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno)); + fatal("Failed to write \"%s\"'s padding: %s", name, strerror(errno)); break; // LCOV_EXCL_STOP } @@ -1243,9 +1253,7 @@ static bool processFilename(char const *name, char const *outputName) { } else { output = open(outputName, O_WRONLY | O_BINARY | O_CREAT, 0600); if (output == -1) { - report( - "FATAL: Failed to open \"%s\" for writing: %s\n", outputName, strerror(errno) - ); + fatal("Failed to open \"%s\" for writing: %s", outputName, strerror(errno)); return true; } openedOutput = true; @@ -1268,26 +1276,23 @@ static bool processFilename(char const *name, char const *outputName) { // Thus, we're going to hope that either the `open` fails, or it succeeds but IO // operations may fail, all of which we handle. if (int input = open(name, (outputName ? O_RDONLY : O_RDWR) | O_BINARY); input == -1) { - report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", name, strerror(errno)); + fatal("Failed to open \"%s\" for reading+writing: %s", name, strerror(errno)); } else { Defer closeInput{[&] { close(input); }}; struct stat stat; if (fstat(input, &stat) == -1) { // LCOV_EXCL_START - report("FATAL: Failed to stat \"%s\": %s\n", name, strerror(errno)); + fatal("Failed to stat \"%s\": %s", name, strerror(errno)); // LCOV_EXCL_STOP } else if (!S_ISREG(stat.st_mode)) { // We do not support FIFOs or symlinks // LCOV_EXCL_START - report( - "FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n", - name - ); + fatal("\"%s\" is not a regular file, and thus cannot be modified in-place", name); // LCOV_EXCL_STOP } else if (stat.st_size < 0x150) { // This check is in theory redundant with the one in `processFile`, but it // prevents passing a file size of 0, which usually indicates pipes - report( - "FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n", + fatal( + "\"%s\" too short, expected at least 336 ($150) bytes, got only %jd", name, static_cast(stat.st_size) ); @@ -1314,7 +1319,7 @@ static bool processFilename(char const *name, char const *outputName) { static void parseByte(uint16_t &output, char name) { if (musl_optarg[0] == 0) { - report("error: Argument to option '%c' may not be empty\n", name); + error("Argument to option '%c' may not be empty", name); } else { char *endptr; unsigned long value; @@ -1325,11 +1330,9 @@ static void parseByte(uint16_t &output, char name) { value = strtoul(musl_optarg, &endptr, 0); } if (*endptr) { - report( - "error: Expected number as argument to option '%c', got %s\n", name, musl_optarg - ); + error("Expected number as argument to option '%c', got %s", name, musl_optarg); } else if (value > 0xFF) { - report("error: Argument to option '%c' is larger than 255: %lu\n", name, value); + error("Argument to option '%c' is larger than 255: %lu", name, value); } else { output = value; } @@ -1349,7 +1352,7 @@ int main(int argc, char *argv[]) { model = ch == 'c' ? BOTH : CGB; if (titleLen > 15) { titleLen = 15; - fprintf(stderr, "warning: Truncating title \"%s\" to 15 chars\n", title); + warnx("Truncating title \"%s\" to 15 chars", title); } break; @@ -1360,7 +1363,7 @@ int main(int argc, char *argv[]) { #define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \ case STR(cur)[0]: \ if (fixSpec & badFlag) { \ - fprintf(stderr, "warning: '" STR(cur) "' overriding '" STR(bad) "' in fix spec\n"); \ + warnx("'" STR(cur) "' overriding '" STR(bad) "' in fix spec"); \ } \ fixSpec = (fixSpec & ~badFlag) | curFlag; \ break @@ -1374,7 +1377,7 @@ int main(int argc, char *argv[]) { #undef overrideSpecs default: - fprintf(stderr, "warning: Ignoring '%c' in fix spec\n", *musl_optarg); + warnx("Ignoring '%c' in fix spec", *musl_optarg); } musl_optarg++; } @@ -1391,12 +1394,12 @@ int main(int argc, char *argv[]) { len = strlen(gameID); if (len > 4) { len = 4; - fprintf(stderr, "warning: Truncating game ID \"%s\" to 4 chars\n", gameID); + warnx("Truncating game ID \"%s\" to 4 chars", gameID); } gameIDLen = len; if (titleLen > 11) { titleLen = 11; - fprintf(stderr, "warning: Truncating title \"%s\" to 11 chars\n", title); + warnx("Truncating title \"%s\" to 11 chars", title); } break; @@ -1409,9 +1412,7 @@ int main(int argc, char *argv[]) { len = strlen(newLicensee); if (len > 2) { len = 2; - fprintf( - stderr, "warning: Truncating new licensee \"%s\" to 2 chars\n", newLicensee - ); + warnx("Truncating new licensee \"%s\" to 2 chars", newLicensee); } newLicenseeLen = len; break; @@ -1427,22 +1428,15 @@ int main(int argc, char *argv[]) { case 'm': cartridgeType = parseMBC(musl_optarg); if (cartridgeType == MBC_BAD) { - report("error: Unknown MBC \"%s\"\nAccepted MBC names:\n", musl_optarg); + error("Unknown MBC \"%s\"", musl_optarg); printAcceptedMBCNames(); } else if (cartridgeType == MBC_WRONG_FEATURES) { - report( - "error: Features incompatible with MBC (\"%s\")\nAccepted combinations:\n", - musl_optarg - ); + error("Features incompatible with MBC (\"%s\")", musl_optarg); printAcceptedMBCNames(); } else if (cartridgeType == MBC_BAD_RANGE) { - report("error: Specified MBC ID out of range 0-255: %s\n", musl_optarg); + error("Specified MBC ID out of range 0-255: %s", musl_optarg); } else if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) { - fprintf( - stderr, - "warning: MBC \"%s\" is under-specified and poorly supported\n", - musl_optarg - ); + warnx("MBC \"%s\" is under-specified and poorly supported", musl_optarg); } break; @@ -1477,7 +1471,7 @@ int main(int argc, char *argv[]) { if (len > maxLen) { len = maxLen; - fprintf(stderr, "warning: Truncating title \"%s\" to %u chars\n", title, maxLen); + warnx("Truncating title \"%s\" to %u chars", title, maxLen); } titleLen = len; break; @@ -1502,52 +1496,30 @@ int main(int argc, char *argv[]) { } if ((cartridgeType & 0xFF00) == TPP1 && !japanese) { - fprintf( - stderr, - "warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n" - ); + warnx("TPP1 overwrites region flag for its identification code, ignoring `-j`"); } // Check that RAM size is correct for "standard" mappers if (ramSize != UNSPECIFIED && (cartridgeType & 0xFF00) == 0) { if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) { if (ramSize != 1) { - fprintf( - stderr, - "warning: MBC \"%s\" should have 2 KiB of RAM (-r 1)\n", - mbcName(cartridgeType) - ); + warnx("MBC \"%s\" should have 2 KiB of RAM (-r 1)", mbcName(cartridgeType)); } } else if (hasRAM(cartridgeType)) { if (!ramSize) { - fprintf( - stderr, - "warning: MBC \"%s\" has RAM, but RAM size was set to 0\n", - mbcName(cartridgeType) - ); + warnx("MBC \"%s\" has RAM, but RAM size was set to 0", mbcName(cartridgeType)); } else if (ramSize == 1) { - fprintf( - stderr, - "warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n", - mbcName(cartridgeType) - ); + warnx("RAM size 1 (2 KiB) was specified for MBC \"%s\"", mbcName(cartridgeType)); } } else if (ramSize) { - fprintf( - stderr, - "warning: MBC \"%s\" has no RAM, but RAM size was set to %u\n", - mbcName(cartridgeType), - ramSize + warnx( + "MBC \"%s\" has no RAM, but RAM size was set to %u", mbcName(cartridgeType), ramSize ); } } if (sgb && oldLicensee != UNSPECIFIED && oldLicensee != 0x33) { - fprintf( - stderr, - "warning: SGB compatibility enabled, but old licensee is 0x%02x, not 0x33\n", - oldLicensee - ); + warnx("SGB compatibility enabled, but old licensee is 0x%02x, not 0x33", oldLicensee); } argv += musl_optind; @@ -1563,19 +1535,14 @@ int main(int argc, char *argv[]) { logoFile = stdin; } if (!logoFile) { - fprintf( - stderr, - "FATAL: Failed to open \"%s\" for reading: %s\n", - logoFilename, - strerror(errno) - ); + fatal("Failed to open \"%s\" for reading: %s", logoFilename, strerror(errno)); exit(1); } Defer closeLogo{[&] { fclose(logoFile); }}; uint8_t logoBpp[sizeof(logo)]; if (size_t nbRead = fread(logoBpp, 1, sizeof(logoBpp), logoFile); nbRead != sizeof(logo) || fgetc(logoFile) != EOF || ferror(logoFile)) { - fprintf(stderr, "FATAL: \"%s\" is not %zu bytes\n", logoFilename, sizeof(logo)); + fatal("\"%s\" is not %zu bytes", logoFilename, sizeof(logo)); exit(1); } auto highs = [&logoBpp](size_t i) { @@ -1605,15 +1572,13 @@ int main(int argc, char *argv[]) { } if (!*argv) { - fputs( - "FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr - ); + fatal("Please specify an input file (pass `-` to read from standard input)"); printUsage(); exit(1); } if (outputFilename && argc != musl_optind + 1) { - fputs("FATAL: If `-o` is set then only a single input file may be specified\n", stderr); + fatal("If `-o` is set then only a single input file may be specified"); printUsage(); exit(1); } diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index 4bc41403..ac618dd1 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -14,6 +14,7 @@ #include #include +#include "error.hpp" #include "extern/getopt.hpp" #include "file.hpp" #include "platform.hpp" @@ -22,6 +23,7 @@ #include "gfx/pal_spec.hpp" #include "gfx/process.hpp" #include "gfx/reverse.hpp" +#include "gfx/warning.hpp" using namespace std::literals::string_view_literals; @@ -37,69 +39,6 @@ static struct LocalOptions { bool reverse; } localOptions; -static uintmax_t nbErrors; - -[[noreturn]] -void giveUp() { - fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s"); - exit(1); -} - -void requireZeroErrors() { - if (nbErrors != 0) { - giveUp(); - } -} - -void warning(char const *fmt, ...) { - va_list ap; - - fputs("warning: ", stderr); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - putc('\n', stderr); -} - -void error(char const *fmt, ...) { - va_list ap; - - fputs("error: ", stderr); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - putc('\n', stderr); - - if (nbErrors != std::numeric_limits::max()) { - nbErrors++; - } -} - -void errorMessage(char const *msg) { - fprintf(stderr, "error: %s\n", msg); - - if (nbErrors != std::numeric_limits::max()) { - nbErrors++; - } -} - -[[noreturn]] -void fatal(char const *fmt, ...) { - va_list ap; - - fputs("FATAL: ", stderr); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - putc('\n', stderr); - - if (nbErrors != std::numeric_limits::max()) { - nbErrors++; - } - - giveUp(); -} - void Options::verbosePrint(uint8_t level, char const *fmt, ...) const { // LCOV_EXCL_START if (verbosity >= level) { @@ -179,6 +118,19 @@ static void printUsage() { } // LCOV_EXCL_STOP +[[gnu::format(printf, 1, 2), noreturn]] +static void fatalWithUsage(char const *fmt, ...) { + va_list ap; + fputs("FATAL: ", stderr); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + putc('\n', stderr); + + printUsage(); + exit(1); +} + // Parses a number at the beginning of a string, moving the pointer to skip the parsed characters. // Returns the provided errVal on error. static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) { @@ -256,19 +208,13 @@ static void skipWhitespace(char *&arg) { static void registerInput(char const *arg) { if (!options.input.empty()) { - fprintf( - stderr, - "FATAL: input image specified more than once! (first \"%s\", then " - "\"%s\")\n", + fatalWithUsage( + "Input image specified more than once! (first \"%s\", then \"%s\")", options.input.c_str(), arg ); - printUsage(); - exit(1); } else if (arg[0] == '\0') { // Empty input path - fprintf(stderr, "FATAL: input image path cannot be empty\n"); - printUsage(); - exit(1); + fatalWithUsage("Input image path cannot be empty"); } else { options.input = arg; } @@ -361,7 +307,7 @@ static char *parseArgv(int argc, char *argv[]) { case 'a': localOptions.autoAttrmap = false; if (!options.attrmap.empty()) { - warning("Overriding attrmap file %s", options.attrmap.c_str()); + warnx("Overriding attrmap file %s", options.attrmap.c_str()); } options.attrmap = musl_optarg; break; @@ -438,7 +384,7 @@ static char *parseArgv(int argc, char *argv[]) { // LCOV_EXCL_STOP case 'i': if (!options.inputTileset.empty()) { - warning("Overriding input tileset file %s", options.inputTileset.c_str()); + warnx("Overriding input tileset file %s", options.inputTileset.c_str()); } options.inputTileset = musl_optarg; break; @@ -548,7 +494,7 @@ static char *parseArgv(int argc, char *argv[]) { break; case 'o': if (!options.output.empty()) { - warning("Overriding tile data file %s", options.output.c_str()); + warnx("Overriding tile data file %s", options.output.c_str()); } options.output = musl_optarg; break; @@ -558,7 +504,7 @@ static char *parseArgv(int argc, char *argv[]) { case 'p': localOptions.autoPalettes = false; if (!options.palettes.empty()) { - warning("Overriding palettes file %s", options.palettes.c_str()); + warnx("Overriding palettes file %s", options.palettes.c_str()); } options.palettes = musl_optarg; break; @@ -568,7 +514,7 @@ static char *parseArgv(int argc, char *argv[]) { case 'q': localOptions.autoPalmap = false; if (!options.palmap.empty()) { - warning("Overriding palette map file %s", options.palmap.c_str()); + warnx("Overriding palette map file %s", options.palmap.c_str()); } options.palmap = musl_optarg; break; @@ -596,7 +542,7 @@ static char *parseArgv(int argc, char *argv[]) { case 't': localOptions.autoTilemap = false; if (!options.tilemap.empty()) { - warning("Overriding tilemap file %s", options.tilemap.c_str()); + warnx("Overriding tilemap file %s", options.tilemap.c_str()); } options.tilemap = musl_optarg; break; @@ -728,13 +674,10 @@ int main(int argc, char *argv[]) { if (autoOptEnabled) { auto &image = localOptions.groupOutputs ? options.output : options.input; if (image.empty()) { - fprintf( - stderr, - "FATAL: No %s specified\n", + fatalWithUsage( + "No %s specified", localOptions.groupOutputs ? "output tile data file" : "input image" ); - printUsage(); - exit(1); } // Manual implementation of std::filesystem::path.replace_extension(). @@ -921,9 +864,7 @@ int main(int argc, char *argv[]) { && !localOptions.reverse) { processPalettes(); } else { - fputs("FATAL: No input image specified\n", stderr); - printUsage(); - exit(1); + fatalWithUsage("No input image specified"); } requireZeroErrors(); diff --git a/src/gfx/pal_spec.cpp b/src/gfx/pal_spec.cpp index 23113550..8998415f 100644 --- a/src/gfx/pal_spec.cpp +++ b/src/gfx/pal_spec.cpp @@ -16,10 +16,12 @@ #include #include +#include "error.hpp" #include "helpers.hpp" #include "platform.hpp" #include "gfx/main.hpp" +#include "gfx/warning.hpp" using namespace std::string_view_literals; @@ -60,7 +62,7 @@ void parseInlinePalSpec(char const * const rawArg) { assume(ofs <= arg.length()); assume(len <= arg.length()); - errorMessage(msg); + error("%s", msg); // `format_` and `-Wformat-security` would complain about `error(msg);` fprintf( stderr, "In inline palette spec: %s\n" @@ -286,7 +288,7 @@ static void parsePSPFile(std::filebuf &file) { if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; *nbColors > nbPalColors) { - warning( + warnx( "PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16 "; ignoring extra", *nbColors, @@ -368,7 +370,7 @@ static void parseGPLFile(std::filebuf &file) { } if (nbColors > maxNbColors) { - warning( + warnx( "GPL file contains %" PRIu16 " colors, but there can only be %" PRIu16 "; ignoring extra", nbColors, @@ -416,7 +418,7 @@ static void parseHEXFile(std::filebuf &file) { } if (nbColors > maxNbColors) { - warning( + warnx( "HEX file contains %" PRIu16 " colors, but there can only be %" PRIu16 "; ignoring extra", nbColors, @@ -445,7 +447,7 @@ static void parseACTFile(std::filebuf &file) { if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; nbColors > nbPalColors) { - warning( + warnx( "ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16 "; ignoring extra", nbColors, @@ -499,7 +501,7 @@ static void parseACOFile(std::filebuf &file) { if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; nbColors > nbPalColors) { - warning( + warnx( "ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16 "; ignoring extra", nbColors, diff --git a/src/gfx/process.cpp b/src/gfx/process.cpp index 1bcc7f3d..8be732fe 100644 --- a/src/gfx/process.cpp +++ b/src/gfx/process.cpp @@ -16,6 +16,7 @@ #include #include "defaultinitvec.hpp" +#include "error.hpp" #include "file.hpp" #include "helpers.hpp" #include "itertools.hpp" @@ -24,6 +25,7 @@ #include "gfx/pal_packing.hpp" #include "gfx/pal_sorting.hpp" #include "gfx/proto_palette.hpp" +#include "gfx/warning.hpp" static bool isBgColorTransparent() { return options.bgColor.has_value() && options.bgColor->isTransparent(); @@ -92,7 +94,7 @@ class Png { static void handleWarning(png_structp png, char const *msg) { Png *self = reinterpret_cast(png_get_error_ptr(png)); - warning("In input image (\"%s\"): %s", self->c_str(), msg); + warnx("In input image (\"%s\"): %s", self->c_str(), msg); } static void readData(png_structp png, png_bytep data, size_t length) { @@ -385,7 +387,7 @@ public: std::tuple conflicting{color.toCSS(), other->toCSS()}; // Do not report combinations twice if (std::find(RANGE(conflicts), conflicting) == conflicts.end()) { - warning( + warnx( "Fusing colors #%08x and #%08x into Game Boy color $%04x [first seen " "at x: %" PRIu32 ", y: %" PRIu32 "]", std::get<0>(conflicting), diff --git a/src/gfx/reverse.cpp b/src/gfx/reverse.cpp index b577264e..35ca53d7 100644 --- a/src/gfx/reverse.cpp +++ b/src/gfx/reverse.cpp @@ -13,10 +13,12 @@ #include #include "defaultinitvec.hpp" +#include "error.hpp" #include "file.hpp" #include "helpers.hpp" // assume #include "gfx/main.hpp" +#include "gfx/warning.hpp" static DefaultInitVec readInto(std::string const &path) { File file; @@ -59,7 +61,7 @@ static void pngError(png_structp png, char const *msg) { } static void pngWarning(png_structp png, char const *msg) { - warning( + warnx( "While writing reversed image (\"%s\"): %s", static_cast(png_get_error_ptr(png)), msg @@ -106,19 +108,19 @@ void reverse() { } if (options.allowDedup && options.tilemap.empty()) { - warning("Tile deduplication is enabled, but no tilemap is provided?"); + warnx("Tile deduplication is enabled, but no tilemap is provided?"); } if (options.useColorCurve) { - warning("The color curve is not yet supported in reverse mode..."); + warnx("The color curve is not yet supported in reverse mode..."); } if (options.inputSlice.left != 0 || options.inputSlice.top != 0 || options.inputSlice.height != 0) { - warning("\"Sliced-off\" pixels are ignored in reverse mode"); + warnx("\"Sliced-off\" pixels are ignored in reverse mode"); } if (options.inputSlice.width != 0 && options.inputSlice.width != options.reversedWidth * 8) { - warning( + warnx( "Specified input slice width (%" PRIu16 ") doesn't match provided reversing width (%" PRIu16 " * 8)", options.inputSlice.width, @@ -152,7 +154,7 @@ void reverse() { fatal("Cannot generate empty image"); } if (mapSize > options.maxNbTiles[0] + options.maxNbTiles[1]) { - warning( + warnx( "Total number of tiles (%zu) is more than the limit of %" PRIu16 " + %" PRIu16, mapSize, options.maxNbTiles[0], @@ -234,7 +236,7 @@ void reverse() { } while (nbRead != 0); if (palettes.size() > options.nbPalettes) { - warning( + warnx( "Read %zu palettes, more than the specified limit of %" PRIu16, palettes.size(), options.nbPalettes @@ -242,7 +244,7 @@ void reverse() { } if (options.palSpecType == Options::EXPLICIT && palettes != options.palSpec) { - warning("Colors in the palette file do not match those specified with `-c`!"); + warnx("Colors in the palette file do not match those specified with `-c`!"); // This spacing aligns "...versus with `-c`" above the column of `-c` palettes fputs("Colors specified in the palette file: ...versus with `-c`:\n", stderr); for (size_t i = 0; i < palettes.size() && i < options.palSpec.size(); ++i) { @@ -263,8 +265,8 @@ void reverse() { palettes[0][i] = grayColors[options.dmgValue(i)]; } } else if (options.palSpecType == Options::EMBEDDED) { - warning("An embedded palette was requested, but no palette file was specified; ignoring " - "request."); + warnx("An embedded palette was requested, but no palette file was specified; ignoring " + "request."); } else if (options.palSpecType == Options::EXPLICIT) { palettes = std::move(options.palSpec); // We won't be using it again. } @@ -303,7 +305,7 @@ void reverse() { if (!tilemap) { if (bank) { - warning( + warnx( "Attribute map assigns tile at (%zu, %zu) to bank 1, but no tilemap " "specified; " "ignoring the bank bit", diff --git a/src/gfx/warning.cpp b/src/gfx/warning.cpp new file mode 100644 index 00000000..d161e773 --- /dev/null +++ b/src/gfx/warning.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT + +#include "gfx/warning.hpp" + +#include +#include +#include +#include +#include + +static uintmax_t nbErrors; + +[[noreturn]] +void giveUp() { + fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s"); + exit(1); +} + +void requireZeroErrors() { + if (nbErrors != 0) { + giveUp(); + } +} + +void error(char const *fmt, ...) { + va_list ap; + fputs("error: ", stderr); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + putc('\n', stderr); + + if (nbErrors != std::numeric_limits::max()) { + nbErrors++; + } +} + +[[noreturn]] +void fatal(char const *fmt, ...) { + va_list ap; + fputs("FATAL: ", stderr); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + putc('\n', stderr); + + if (nbErrors != std::numeric_limits::max()) { + nbErrors++; + } + + giveUp(); +} diff --git a/src/link/assign.cpp b/src/link/assign.cpp index 6bfa45a3..2da27b8a 100644 --- a/src/link/assign.cpp +++ b/src/link/assign.cpp @@ -19,6 +19,7 @@ #include "link/output.hpp" #include "link/section.hpp" #include "link/symbol.hpp" +#include "link/warning.hpp" struct MemoryLocation { uint16_t address; diff --git a/src/link/main.cpp b/src/link/main.cpp index 56a9046a..1c1025f9 100644 --- a/src/link/main.cpp +++ b/src/link/main.cpp @@ -23,6 +23,7 @@ #include "link/patch.hpp" #include "link/section.hpp" #include "link/symbol.hpp" +#include "link/warning.hpp" bool isDmgMode; // -d char const *linkerScriptName; // -l @@ -44,8 +45,6 @@ bool disablePadding; // -x FILE *linkerScript; -static uint32_t nbErrors = 0; - std::string const &FileStackNode::dump(uint32_t curLineNo) const { if (data.holds>()) { assume(parent); // REPT nodes use their parent's name @@ -69,72 +68,6 @@ std::string const &FileStackNode::dump(uint32_t curLineNo) const { } } -void printDiag( - char const *fmt, va_list args, char const *type, FileStackNode const *where, uint32_t lineNo -) { - fputs(type, stderr); - fputs(": ", stderr); - if (where) { - where->dump(lineNo); - fputs(": ", stderr); - } - vfprintf(stderr, fmt, args); - putc('\n', stderr); -} - -void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) { - va_list args; - - va_start(args, fmt); - printDiag(fmt, args, "warning", where, lineNo); - va_end(args); -} - -void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) { - va_list args; - - va_start(args, fmt); - printDiag(fmt, args, "error", where, lineNo); - va_end(args); - - if (nbErrors != UINT32_MAX) { - nbErrors++; - } -} - -[[gnu::format(printf, 2, 3)]] -void argErr(char flag, char const *fmt, ...) { - va_list args; - - fprintf(stderr, "error: Invalid argument for option '%c': ", flag); - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - putc('\n', stderr); - - if (nbErrors != UINT32_MAX) { - nbErrors++; - } -} - -[[noreturn]] -void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) { - va_list args; - - va_start(args, fmt); - printDiag(fmt, args, "FATAL", where, lineNo); - va_end(args); - - if (nbErrors != UINT32_MAX) { - nbErrors++; - } - - fprintf( - stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s" - ); - exit(1); -} - // Short options static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvwx"; @@ -185,6 +118,19 @@ static void printUsage() { } // LCOV_EXCL_STOP +[[gnu::format(printf, 1, 2), noreturn]] +static void fatalWithUsage(char const *fmt, ...) { + va_list ap; + fputs("FATAL: ", stderr); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + putc('\n', stderr); + + printUsage(); + exit(1); +} + enum ScrambledRegion { SCRAMBLE_ROMX, SCRAMBLE_SRAM, @@ -327,14 +273,6 @@ next: // Can't `continue` a `for` loop with this nontrivial iteration logic } } -[[noreturn]] -void reportErrors() { - fprintf( - stderr, "Linking failed with %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s" - ); - exit(1); -} - int main(int argc, char *argv[]) { // Parse options for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { @@ -429,11 +367,7 @@ int main(int argc, char *argv[]) { // If no input files were specified, the user must have screwed up if (curArgIndex == argc) { - fputs( - "FATAL: Please specify an input file (pass `-` to read from standard input)\n", stderr - ); - printUsage(); - exit(1); + fatalWithUsage("Please specify an input file (pass `-` to read from standard input)"); } // Patch the size array depending on command-line options @@ -461,23 +395,17 @@ int main(int argc, char *argv[]) { script_ProcessScript(linkerScriptName); // If the linker script produced any errors, some sections may be in an invalid state - if (nbErrors != 0) { - reportErrors(); - } + requireZeroErrors(); } // then process them, sect_DoSanityChecks(); - if (nbErrors != 0) { - reportErrors(); - } + requireZeroErrors(); assign_AssignSections(); patch_CheckAssertions(); // and finally output the result. patch_ApplyPatches(); - if (nbErrors != 0) { - reportErrors(); - } + requireZeroErrors(); out_WriteFiles(); } diff --git a/src/link/object.cpp b/src/link/object.cpp index ba805f90..36cef587 100644 --- a/src/link/object.cpp +++ b/src/link/object.cpp @@ -24,6 +24,7 @@ #include "link/sdas_obj.hpp" #include "link/section.hpp" #include "link/symbol.hpp" +#include "link/warning.hpp" static std::deque> symbolLists; static std::vector> nodes; @@ -445,7 +446,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) { file = stdin; } if (!file) { - err("Failed to open file \"%s\"", fileName); + errx("Failed to open file \"%s\": %s", fileName, strerror(errno)); } Defer closeFile{[&] { fclose(file); }}; diff --git a/src/link/output.cpp b/src/link/output.cpp index 13a69756..8bd6cc19 100644 --- a/src/link/output.cpp +++ b/src/link/output.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include "link/main.hpp" #include "link/section.hpp" #include "link/symbol.hpp" +#include "link/warning.hpp" static constexpr size_t BANK_SIZE = 0x4000; @@ -211,7 +213,7 @@ static void writeROM() { outputFile = stdout; } if (!outputFile) { - err("Failed to open output file \"%s\"", outputFileName); + errx("Failed to open output file \"%s\": %s", outputFileName, strerror(errno)); } } Defer closeOutputFile{[&] { @@ -229,7 +231,7 @@ static void writeROM() { overlayFile = stdin; } if (!overlayFile) { - err("Failed to open overlay file \"%s\"", overlayFileName); + errx("Failed to open overlay file \"%s\": %s", overlayFileName, strerror(errno)); } } Defer closeOverlayFile{[&] { @@ -572,7 +574,7 @@ static void writeSym() { symFile = stdout; } if (!symFile) { - err("Failed to open sym file \"%s\"", symFileName); + errx("Failed to open sym file \"%s\": %s", symFileName, strerror(errno)); } Defer closeSymFile{[&] { fclose(symFile); }}; @@ -623,7 +625,7 @@ static void writeMap() { mapFile = stdout; } if (!mapFile) { - err("Failed to open map file \"%s\"", mapFileName); + errx("Failed to open map file \"%s\": %s", mapFileName, strerror(errno)); } Defer closeMapFile{[&] { fclose(mapFile); }}; diff --git a/src/link/patch.cpp b/src/link/patch.cpp index 334878a7..09e8d0b3 100644 --- a/src/link/patch.cpp +++ b/src/link/patch.cpp @@ -14,6 +14,7 @@ #include "link/main.hpp" #include "link/section.hpp" #include "link/symbol.hpp" +#include "link/warning.hpp" std::deque assertions; diff --git a/src/link/script.y b/src/link/script.y index 5e18c62c..c00e68bf 100644 --- a/src/link/script.y +++ b/src/link/script.y @@ -30,6 +30,7 @@ #include "link/main.hpp" #include "link/section.hpp" + #include "link/warning.hpp" using namespace std::literals; diff --git a/src/link/sdas_obj.cpp b/src/link/sdas_obj.cpp index 46ba4abc..f1f06483 100644 --- a/src/link/sdas_obj.cpp +++ b/src/link/sdas_obj.cpp @@ -17,6 +17,7 @@ #include "link/main.hpp" #include "link/section.hpp" #include "link/symbol.hpp" +#include "link/warning.hpp" enum NumberType { HEX = 16, // X @@ -307,7 +308,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector getToken(nullptr, "'A' line is too short"); tmp = parseNumber(where, lineNo, token, numberType); if (tmp & (1 << AREA_PAGING)) { - fatal(&where, lineNo, "Internal error: paging is not supported"); + fatal(&where, lineNo, "Paging is not supported"); } curSection->isAddressFixed = tmp & (1 << AREA_ISABS); curSection->isBankFixed = curSection->isAddressFixed; @@ -430,11 +431,8 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector || (symbolSection && !symbolSection->isAddressFixed)) { sym_AddSymbol(symbol); // This will error out } else if (otherValue != symbolValue) { - fprintf( - stderr, - "error: \"%s\" is defined as %" PRId32 " at ", - symbol.name.c_str(), - symbolValue + errorNoDump( + "\"%s\" is defined as %" PRId32 " at ", symbol.name.c_str(), symbolValue ); symbol.src->dump(symbol.lineNo); fprintf(stderr, ", but as %" PRId32 " at ", otherValue); diff --git a/src/link/section.cpp b/src/link/section.cpp index 215819ca..76332b81 100644 --- a/src/link/section.cpp +++ b/src/link/section.cpp @@ -10,6 +10,8 @@ #include "error.hpp" #include "helpers.hpp" +#include "link/warning.hpp" + std::vector> sectionList; std::unordered_map sectionMap; // Indexes into `sectionList` @@ -22,9 +24,8 @@ void sect_ForEach(void (*callback)(Section &)) { static void checkAgainstFixedAddress(Section const &target, Section const &other, uint16_t org) { if (target.isAddressFixed) { if (target.org != org) { - fprintf( - stderr, - "error: Section \"%s\" is defined with address $%04" PRIx16 " at ", + errorNoDump( + "Section \"%s\" is defined with address $%04" PRIx16 " at ", target.name.c_str(), target.org ); @@ -36,9 +37,8 @@ static void checkAgainstFixedAddress(Section const &target, Section const &other } } else if (target.isAlignFixed) { if ((org - target.alignOfs) & target.alignMask) { - fprintf( - stderr, - "error: Section \"%s\" is defined with %d-byte alignment (offset %" PRIu16 ") at ", + errorNoDump( + "Section \"%s\" is defined with %d-byte alignment (offset %" PRIu16 ") at ", target.name.c_str(), target.alignMask + 1, target.alignOfs @@ -55,9 +55,8 @@ static void checkAgainstFixedAddress(Section const &target, Section const &other static bool checkAgainstFixedAlign(Section const &target, Section const &other, int32_t ofs) { if (target.isAddressFixed) { if ((target.org - ofs) & other.alignMask) { - fprintf( - stderr, - "error: Section \"%s\" is defined with address $%04" PRIx16 " at ", + errorNoDump( + "Section \"%s\" is defined with address $%04" PRIx16 " at ", target.name.c_str(), target.org ); @@ -75,9 +74,8 @@ static bool checkAgainstFixedAlign(Section const &target, Section const &other, return false; } else if (target.isAlignFixed && (other.alignMask & target.alignOfs) != (target.alignMask & ofs)) { - fprintf( - stderr, - "error: Section \"%s\" is defined with %d-byte alignment (offset %" PRIu16 ") at ", + errorNoDump( + "Section \"%s\" is defined with %d-byte alignment (offset %" PRIu16 ") at ", target.name.c_str(), target.alignMask + 1, target.alignOfs @@ -131,9 +129,8 @@ static void checkFragmentCompat(Section &target, Section &other) { static void mergeSections(Section &target, std::unique_ptr
&&other) { if (target.modifier != other->modifier) { - fprintf( - stderr, - "error: Section \"%s\" is defined as SECTION %s at ", + errorNoDump( + "Section \"%s\" is defined as SECTION %s at ", target.name.c_str(), sectionModNames[target.modifier] ); @@ -143,16 +140,15 @@ static void mergeSections(Section &target, std::unique_ptr
&&other) { putc('\n', stderr); exit(1); } else if (other->modifier == SECTION_NORMAL) { - fprintf(stderr, "error: Section \"%s\" is defined at ", target.name.c_str()); + errorNoDump("Section \"%s\" is defined at ", target.name.c_str()); target.src->dump(target.lineNo); fputs(", but also at ", stderr); other->src->dump(other->lineNo); putc('\n', stderr); exit(1); } else if (target.type != other->type) { - fprintf( - stderr, - "error: Section \"%s\" is defined with type %s at ", + errorNoDump( + "Section \"%s\" is defined with type %s at ", target.name.c_str(), sectionTypeInfo[target.type].name.c_str() ); @@ -168,9 +164,8 @@ static void mergeSections(Section &target, std::unique_ptr
&&other) { target.isBankFixed = true; target.bank = other->bank; } else if (target.bank != other->bank) { - fprintf( - stderr, - "error: Section \"%s\" is defined with bank %" PRIu32 " at ", + errorNoDump( + "Section \"%s\" is defined with bank %" PRIu32 " at ", target.name.c_str(), target.bank ); diff --git a/src/link/symbol.cpp b/src/link/symbol.cpp index 88adaead..52dca05c 100644 --- a/src/link/symbol.cpp +++ b/src/link/symbol.cpp @@ -11,6 +11,7 @@ #include "link/main.hpp" #include "link/section.hpp" +#include "link/warning.hpp" std::unordered_map symbols; std::unordered_map> localSymbols; @@ -36,7 +37,7 @@ void sym_AddSymbol(Symbol &symbol) { // Check if the symbol already exists with a different value if (other && !(symValue && otherValue && *symValue == *otherValue)) { - fprintf(stderr, "error: \"%s\" is defined as ", symbol.name.c_str()); + errorNoDump("\"%s\" is defined as ", symbol.name.c_str()); if (symValue) { fprintf(stderr, "%" PRId32, *symValue); } else { diff --git a/src/link/warning.cpp b/src/link/warning.cpp new file mode 100644 index 00000000..6674fa9b --- /dev/null +++ b/src/link/warning.cpp @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT + +#include "link/warning.hpp" + +#include +#include + +#include "link/main.hpp" + +static uint32_t nbErrors = 0; + +static void printDiag( + char const *fmt, va_list args, char const *type, FileStackNode const *where, uint32_t lineNo +) { + fputs(type, stderr); + fputs(": ", stderr); + if (where) { + where->dump(lineNo); + fputs(": ", stderr); + } + vfprintf(stderr, fmt, args); + putc('\n', stderr); +} + +void warning(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) { + va_list args; + va_start(args, fmt); + printDiag(fmt, args, "warning", where, lineNo); + va_end(args); +} + +void error(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) { + va_list args; + va_start(args, fmt); + printDiag(fmt, args, "error", where, lineNo); + va_end(args); + + if (nbErrors != UINT32_MAX) { + nbErrors++; + } +} + +void errorNoDump(char const *fmt, ...) { + va_list args; + fputs("error: ", stderr); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + if (nbErrors != UINT32_MAX) { + nbErrors++; + } +} + +void argErr(char flag, char const *fmt, ...) { + va_list args; + fprintf(stderr, "error: Invalid argument for option '%c': ", flag); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + putc('\n', stderr); + + if (nbErrors != UINT32_MAX) { + nbErrors++; + } +} + +[[noreturn]] +void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) { + va_list args; + va_start(args, fmt); + printDiag(fmt, args, "FATAL", where, lineNo); + va_end(args); + + if (nbErrors != UINT32_MAX) { + nbErrors++; + } + + fprintf( + stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s" + ); + exit(1); +} + +void requireZeroErrors() { + if (nbErrors != 0) { + fprintf( + stderr, "Linking failed with %" PRIu32 " error%s\n", nbErrors, nbErrors == 1 ? "" : "s" + ); + exit(1); + } +} diff --git a/test/fix/incompatible-features.err b/test/fix/incompatible-features.err index 1b6848c7..dc5893fe 100644 --- a/test/fix/incompatible-features.err +++ b/test/fix/incompatible-features.err @@ -1,5 +1,5 @@ error: Features incompatible with MBC ("mbc1+multirumble") -Accepted combinations: +Accepted MBC names: ROM ($00) [aka ROM_ONLY] MBC1 ($01), MBC1+RAM ($02), MBC1+RAM+BATTERY ($03) MBC2 ($05), MBC2+BATTERY ($06)