diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index e7f89f6f..2e2c39cb 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -1735,6 +1735,23 @@ static bool isGarbageCharacter(int c) { && (c == '\0' || !strchr("; \t~[](),+-*/|^=!<>:&%`\"\r\n\\", c)); } +static void reportGarbageCharacters(int c) { + // '#' can be garbage if it doesn't start a raw string or identifier + assume(isGarbageCharacter(c) || c == '#'); + if (isGarbageCharacter(peek())) { + // At least two characters are garbage; group them into one error report + std::string garbage = printChar(c); + while (isGarbageCharacter(peek())) { + c = nextChar(); + garbage += ", "; + garbage += printChar(c); + } + error("Unknown characters %s", garbage.c_str()); + } else { + error("Unknown character %s", printChar(c)); + } +} + static Token yylex_NORMAL() { if (int nextToken = lexerState->nextToken; nextToken) { lexerState->nextToken = 0; @@ -2083,19 +2100,7 @@ static Token yylex_NORMAL() { // Do not report weird characters when capturing, it'll be done later if (!lexerState->capturing) { - assume(isGarbageCharacter(c) || c == '#'); - if (isGarbageCharacter(peek())) { - // At least two characters are garbage; group them into one error report - std::string garbage = printChar(c); - while (isGarbageCharacter(peek())) { - c = nextChar(); - garbage += ", "; - garbage += printChar(c); - } - error("Unknown characters %s", garbage.c_str()); - } else { - error("Unknown character %s", printChar(c)); - } + reportGarbageCharacters(c); } } lexerState->atLineStart = false; diff --git a/src/asm/main.cpp b/src/asm/main.cpp index 6f28f8a9..7d9014fd 100644 --- a/src/asm/main.cpp +++ b/src/asm/main.cpp @@ -124,6 +124,51 @@ static void fatalWithUsage(char const *fmt, ...) { exit(1); } +// Parse a comma-separated string of '-s/--state' features +static std::vector parseStateFeatures(char *str) { + std::vector features; + for (char *feature = str; feature;) { + // Split "," so `feature` is "" and `next` is "" + char *next = strchr(feature, ','); + if (next) { + *next++ = '\0'; + } + // Trim whitespace from the beginning of `feature`... + feature += strspn(feature, " \t"); + // ...and from the end + if (char *end = strpbrk(feature, " \t"); end) { + *end = '\0'; + } + // A feature must be specified + if (*feature == '\0') { + fatal("Empty feature for option 's'"); + } + // Parse the `feature` and update the `features` list + if (!strcasecmp(feature, "all")) { + if (!features.empty()) { + warnx("Redundant feature before \"%s\" for option 's'", feature); + } + features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO}); + } else { + StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU + : !strcasecmp(feature, "var") ? STATE_VAR + : !strcasecmp(feature, "equs") ? STATE_EQUS + : !strcasecmp(feature, "char") ? STATE_CHAR + : !strcasecmp(feature, "macro") ? STATE_MACRO + : NB_STATE_FEATURES; + if (value == NB_STATE_FEATURES) { + fatal("Invalid feature for option 's': \"%s\"", feature); + } else if (std::find(RANGE(features), value) != features.end()) { + warnx("Ignoring duplicate feature for option 's': \"%s\"", feature); + } else { + features.push_back(value); + } + } + feature = next; + } + return features; +} + int main(int argc, char *argv[]) { time_t now = time(nullptr); // Support SOURCE_DATE_EPOCH for reproducible builds @@ -278,46 +323,7 @@ int main(int argc, char *argv[]) { } *name++ = '\0'; - std::vector features; - for (char *feature = musl_optarg; feature;) { - // Split "," so `feature` is "" and `next` is "" - char *next = strchr(feature, ','); - if (next) { - *next++ = '\0'; - } - // Trim whitespace from the beginning of `feature`... - feature += strspn(feature, " \t"); - // ...and from the end - if (char *end = strpbrk(feature, " \t"); end) { - *end = '\0'; - } - // A feature must be specified - if (*feature == '\0') { - fatal("Empty feature for option 's'"); - } - // Parse the `feature` and update the `features` list - if (!strcasecmp(feature, "all")) { - if (!features.empty()) { - warnx("Redundant feature before \"%s\" for option 's'", feature); - } - features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO}); - } else { - StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU - : !strcasecmp(feature, "var") ? STATE_VAR - : !strcasecmp(feature, "equs") ? STATE_EQUS - : !strcasecmp(feature, "char") ? STATE_CHAR - : !strcasecmp(feature, "macro") ? STATE_MACRO - : NB_STATE_FEATURES; - if (value == NB_STATE_FEATURES) { - fatal("Invalid feature for option 's': \"%s\"", feature); - } else if (std::find(RANGE(features), value) != features.end()) { - warnx("Ignoring duplicate feature for option 's': \"%s\"", feature); - } else { - features.push_back(value); - } - } - feature = next; - } + std::vector features = parseStateFeatures(musl_optarg); if (stateFileSpecs.find(name) != stateFileSpecs.end()) { warnx("Overriding state filename %s", name); diff --git a/src/extern/getopt.cpp b/src/extern/getopt.cpp index d8bf383b..ade5f079 100644 --- a/src/extern/getopt.cpp +++ b/src/extern/getopt.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -// This implementation was taken from musl and modified for RGBDS +// This implementation was taken from musl and modified for RGBDS. #include "extern/getopt.hpp" diff --git a/src/extern/utf8decoder.cpp b/src/extern/utf8decoder.cpp index b6b45f6b..116f50c8 100644 --- a/src/extern/utf8decoder.cpp +++ b/src/extern/utf8decoder.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT -// UTF-8 decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ +// This implementation was taken from +// http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ +// and modified for RGBDS. #include "extern/utf8decoder.hpp" @@ -33,10 +35,8 @@ static uint8_t const utf8d[] = { }; uint32_t decode(uint32_t *state, uint32_t *codep, uint8_t byte) { - uint32_t type = utf8d[byte]; - - *codep = (*state != 0) ? (byte & 0x3FU) | (*codep << 6) : (0xFF >> type) & (byte); - - *state = utf8d[256 + *state * 16 + type]; + uint8_t type = utf8d[byte]; + *codep = *state != 0 ? (byte & 0b111111) | (*codep << 6) : byte & (0xFF >> type); + *state = utf8d[0x100 + *state * 0x10 + type]; return *state; } diff --git a/src/fix/main.cpp b/src/fix/main.cpp index dae2a4e2..b3e62b1b 100644 --- a/src/fix/main.cpp +++ b/src/fix/main.cpp @@ -461,10 +461,7 @@ static MbcType parseMBC(char const *name) { break; case 'A': case 'a': - if (*ptr != 'M' && *ptr != 'm') { - return MBC_BAD; - } - ptr++; + tryReadSlice("M"); features |= RAM; break; default: diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index 1adf8735..903eb6ab 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -594,6 +594,143 @@ static char *parseArgv(int argc, char *argv[]) { return nullptr; // Done processing this argv } +static void verboseOutputConfig() { + fprintf(stderr, "rgbgfx %s\n", get_package_version_string()); + + if (options.verbosity >= Options::VERB_VVVVVV) { + putc('\n', stderr); + // clang-format off: vertically align values + static std::array gfx{ + 0b0111111110, + 0b1111111111, + 0b1110011001, + 0b1110011001, + 0b1111111111, + 0b1111111111, + 0b1110000001, + 0b1111000011, + 0b0111111110, + 0b0001111000, + 0b0111111110, + 0b1111111111, + 0b1111111111, + 0b1111111111, + 0b1101111011, + 0b1101111011, + 0b0011111100, + 0b0011001100, + 0b0111001110, + 0b0111001110, + 0b0111001110, + }; + // clang-format on + static std::array textbox{ + " ,----------------------------------------.", + " | Augh, dimensional interference again?! |", + " `----------------------------------------'", + }; + for (size_t i = 0; i < gfx.size(); ++i) { + uint16_t row = gfx[i]; + for (uint8_t _ = 0; _ < 10; ++_) { + unsigned char c = row & 1 ? '0' : ' '; + putc(c, stderr); + // Double the pixel horizontally, otherwise the aspect ratio looks wrong + putc(c, stderr); + row >>= 1; + } + if (i < textbox.size()) { + fputs(textbox[i], stderr); + } + putc('\n', stderr); + } + putc('\n', stderr); + } + + fputs("Options:\n", stderr); + if (options.columnMajor) { + fputs("\tVisit image in column-major order\n", stderr); + } + if (options.allowDedup) { + fputs("\tAllow deduplicating tiles\n", stderr); + } + if (options.allowMirroringX) { + fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr); + } + if (options.allowMirroringY) { + fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr); + } + if (options.useColorCurve) { + fputs("\tUse color curve\n", stderr); + } + fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth); + if (options.trim != 0) { + fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim); + } + fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes); + fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal); + fprintf(stderr, "\t%s palette spec\n", [] { + switch (options.palSpecType) { + case Options::NO_SPEC: + return "No"; + case Options::EXPLICIT: + return "Explicit"; + case Options::EMBEDDED: + return "Embedded"; + case Options::DMG: + return "DMG"; + } + return "???"; + }()); + if (options.palSpecType == Options::EXPLICIT) { + fputs("\t[\n", stderr); + for (auto const &pal : options.palSpec) { + fputs("\t\t", stderr); + for (auto &color : pal) { + if (color) { + fprintf(stderr, "#%06x, ", color->toCSS() >> 8); + } else { + fputs("#none, ", stderr); + } + } + putc('\n', stderr); + } + fputs("\t]\n", stderr); + } + fprintf( + stderr, + "\tInput image slice: %" PRIu16 "x%" PRIu16 " pixels starting at (%" PRIu16 ", %" PRIu16 + ")\n", + options.inputSlice.width, + options.inputSlice.height, + options.inputSlice.left, + options.inputSlice.top + ); + fprintf( + stderr, + "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", + options.baseTileIDs[0], + options.baseTileIDs[1] + ); + fprintf(stderr, "\tBase palette ID: %" PRIu8 "\n", options.basePalID); + fprintf( + stderr, + "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n", + options.maxNbTiles[0], + options.maxNbTiles[1] + ); + auto printPath = [](char const *name, std::string const &path) { + if (!path.empty()) { + fprintf(stderr, "\t%s: %s\n", name, path.c_str()); + } + }; + printPath("Input image", options.input); + printPath("Output tile data", options.output); + printPath("Output tilemap", options.tilemap); + printPath("Output attrmap", options.attrmap); + printPath("Output palettes", options.palettes); + fputs("Ready.\n", stderr); +} + int main(int argc, char *argv[]) { struct AtFileStackEntry { int parentInd; // Saved offset into parent argv @@ -714,140 +851,7 @@ int main(int argc, char *argv[]) { // LCOV_EXCL_START if (options.verbosity >= Options::VERB_CFG) { - fprintf(stderr, "rgbgfx %s\n", get_package_version_string()); - - if (options.verbosity >= Options::VERB_VVVVVV) { - putc('\n', stderr); - // clang-format off: vertically align values - static std::array gfx{ - 0b0111111110, - 0b1111111111, - 0b1110011001, - 0b1110011001, - 0b1111111111, - 0b1111111111, - 0b1110000001, - 0b1111000011, - 0b0111111110, - 0b0001111000, - 0b0111111110, - 0b1111111111, - 0b1111111111, - 0b1111111111, - 0b1101111011, - 0b1101111011, - 0b0011111100, - 0b0011001100, - 0b0111001110, - 0b0111001110, - 0b0111001110, - }; - // clang-format on - static std::array textbox{ - " ,----------------------------------------.", - " | Augh, dimensional interference again?! |", - " `----------------------------------------'", - }; - for (size_t i = 0; i < gfx.size(); ++i) { - uint16_t row = gfx[i]; - for (uint8_t _ = 0; _ < 10; ++_) { - unsigned char c = row & 1 ? '0' : ' '; - putc(c, stderr); - // Double the pixel horizontally, otherwise the aspect ratio looks wrong - putc(c, stderr); - row >>= 1; - } - if (i < textbox.size()) { - fputs(textbox[i], stderr); - } - putc('\n', stderr); - } - putc('\n', stderr); - } - - fputs("Options:\n", stderr); - if (options.columnMajor) { - fputs("\tVisit image in column-major order\n", stderr); - } - if (options.allowDedup) { - fputs("\tAllow deduplicating tiles\n", stderr); - } - if (options.allowMirroringX) { - fputs("\tAllow deduplicating horizontally mirrored tiles\n", stderr); - } - if (options.allowMirroringY) { - fputs("\tAllow deduplicating vertically mirrored tiles\n", stderr); - } - if (options.useColorCurve) { - fputs("\tUse color curve\n", stderr); - } - fprintf(stderr, "\tBit depth: %" PRIu8 "bpp\n", options.bitDepth); - if (options.trim != 0) { - fprintf(stderr, "\tTrim the last %" PRIu64 " tiles\n", options.trim); - } - fprintf(stderr, "\tMaximum %" PRIu16 " palettes\n", options.nbPalettes); - fprintf(stderr, "\tPalettes contain %" PRIu8 " colors\n", options.nbColorsPerPal); - fprintf(stderr, "\t%s palette spec\n", [] { - switch (options.palSpecType) { - case Options::NO_SPEC: - return "No"; - case Options::EXPLICIT: - return "Explicit"; - case Options::EMBEDDED: - return "Embedded"; - case Options::DMG: - return "DMG"; - } - return "???"; - }()); - if (options.palSpecType == Options::EXPLICIT) { - fputs("\t[\n", stderr); - for (auto const &pal : options.palSpec) { - fputs("\t\t", stderr); - for (auto &color : pal) { - if (color) { - fprintf(stderr, "#%06x, ", color->toCSS() >> 8); - } else { - fputs("#none, ", stderr); - } - } - putc('\n', stderr); - } - fputs("\t]\n", stderr); - } - fprintf( - stderr, - "\tInput image slice: %" PRIu16 "x%" PRIu16 " pixels starting at (%" PRIu16 ", %" PRIu16 - ")\n", - options.inputSlice.width, - options.inputSlice.height, - options.inputSlice.left, - options.inputSlice.top - ); - fprintf( - stderr, - "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", - options.baseTileIDs[0], - options.baseTileIDs[1] - ); - fprintf(stderr, "\tBase palette ID: %" PRIu8 "\n", options.basePalID); - fprintf( - stderr, - "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n", - options.maxNbTiles[0], - options.maxNbTiles[1] - ); - auto printPath = [](char const *name, std::string const &path) { - if (!path.empty()) { - fprintf(stderr, "\t%s: %s\n", name, path.c_str()); - } - }; - printPath("Input image", options.input); - printPath("Output tile data", options.output); - printPath("Output tilemap", options.tilemap); - printPath("Output attrmap", options.attrmap); - printPath("Output palettes", options.palettes); - fputs("Ready.\n", stderr); + verboseOutputConfig(); } // LCOV_EXCL_STOP diff --git a/src/link/assign.cpp b/src/link/assign.cpp index ba852656..f66f8ef1 100644 --- a/src/link/assign.cpp +++ b/src/link/assign.cpp @@ -122,49 +122,49 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location) { if (spaceIdx < bankMem.size()) { location.address = bankMem[spaceIdx].address; + } - // Process locations in that bank - while (spaceIdx < bankMem.size()) { - // If that location is OK, return it - if (isLocationSuitable(section, bankMem[spaceIdx], location)) { - return spaceIdx; - } - - // Go to the next *possible* location - if (section.isAddressFixed) { - // If the address is fixed, there can be only - // one candidate block per bank; if we already - // reached it, give up. - if (location.address < section.org) { - location.address = section.org; - } else { - break; // Try again in next bank - } - } else if (section.isAlignFixed) { - // Move to next aligned location - // Move back to alignment boundary - location.address -= section.alignOfs; - // Ensure we're there (e.g. on first check) - location.address &= ~section.alignMask; - // Go to next align boundary and add offset - location.address += section.alignMask + 1 + section.alignOfs; - } else { - // Any location is fine, so, next free block - spaceIdx++; - if (spaceIdx < bankMem.size()) { - location.address = bankMem[spaceIdx].address; - } - } - - // If that location is past the current block's end, - // go forwards until that is no longer the case. - while (spaceIdx < bankMem.size() - && location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size) { - spaceIdx++; - } - - // Try again with the new location/free space combo + // Process locations in that bank + while (spaceIdx < bankMem.size()) { + // If that location is OK, return it + if (isLocationSuitable(section, bankMem[spaceIdx], location)) { + return spaceIdx; } + + // Go to the next *possible* location + if (section.isAddressFixed) { + // If the address is fixed, there can be only + // one candidate block per bank; if we already + // reached it, give up. + if (location.address < section.org) { + location.address = section.org; + } else { + break; // Try again in next bank + } + } else if (section.isAlignFixed) { + // Move to next aligned location + // Move back to alignment boundary + location.address -= section.alignOfs; + // Ensure we're there (e.g. on first check) + location.address &= ~section.alignMask; + // Go to next align boundary and add offset + location.address += section.alignMask + 1 + section.alignOfs; + } else { + // Any location is fine, so, next free block + spaceIdx++; + if (spaceIdx < bankMem.size()) { + location.address = bankMem[spaceIdx].address; + } + } + + // If that location is past the current block's end, + // go forwards until that is no longer the case. + while (spaceIdx < bankMem.size() + && location.address >= bankMem[spaceIdx].address + bankMem[spaceIdx].size) { + spaceIdx++; + } + + // Try again with the new location/free space combo } // Try again in the next bank, if one is available. @@ -208,22 +208,22 @@ static ssize_t getPlacement(Section const §ion, MemoryLocation &location) { // Places a section in a suitable location, or error out if it fails to. // Due to the implemented algorithm, this should be called with sections of decreasing size! static void placeSection(Section §ion) { - MemoryLocation location; - // Specially handle 0-byte SECTIONs, as they can't overlap anything if (section.size == 0) { // Unless the SECTION's address was fixed, the starting address // is fine for any alignment, as checked in sect_DoSanityChecks. - location.address = - section.isAddressFixed ? section.org : sectionTypeInfo[section.type].startAddr; - location.bank = - section.isBankFixed ? section.bank : sectionTypeInfo[section.type].firstBank; + MemoryLocation location = { + .address = + section.isAddressFixed ? section.org : sectionTypeInfo[section.type].startAddr, + .bank = section.isBankFixed ? section.bank : sectionTypeInfo[section.type].firstBank, + }; assignSection(section, location); return; } // Place section using first-fit decreasing algorithm // https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm + MemoryLocation location; if (ssize_t spaceIdx = getPlacement(section, location); spaceIdx != -1) { std::deque &bankMem = memory[section.type][location.bank - sectionTypeInfo[section.type].firstBank]; diff --git a/src/link/output.cpp b/src/link/output.cpp index 5d97de6b..1ee40d27 100644 --- a/src/link/output.cpp +++ b/src/link/output.cpp @@ -168,7 +168,6 @@ static uint8_t getNextFillByte() { return padValue; } -// Write a ROM bank's sections, ordered by increasing address, to the output file. static void writeBank(std::deque
*bankSections, uint16_t baseOffset, uint16_t size) { uint16_t offset = 0; @@ -202,7 +201,6 @@ static void } } -// Writes a ROM file to the output. static void writeROM() { if (outputFileName) { if (strcmp(outputFileName, "-")) { @@ -263,9 +261,7 @@ static void writeROM() { } } -// Prints a symbol's name to a file, assuming that the first character is legal. -// Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as '\u'/'\U'. -static void printSymName(std::string const &name, FILE *file) { +static void writeSymName(std::string const &name, FILE *file) { for (char const *ptr = name.c_str(); *ptr != '\0';) { char c = *ptr; @@ -274,7 +270,7 @@ static void printSymName(std::string const &name, FILE *file) { putc(c, file); ++ptr; } else { - // Output illegal characters using Unicode escapes + // Output illegal characters using Unicode escapes ('\u' or '\U') // Decode the UTF-8 codepoint; or at least attempt to uint32_t state = 0, codepoint; @@ -323,27 +319,26 @@ static bool compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2) { return sym1_name < sym2_name; } -// Write a bank's contents to the sym file -static void writeSymBank(SortedSections const &bankSections, SectionType type, uint32_t bank) { -#define forEachSortedSection(sect, ...) \ - do { \ - for (auto it = bankSections.zeroLenSections.begin(); \ - it != bankSections.zeroLenSections.end(); \ - it++) { \ - for (Section const *sect = *it; sect; sect = sect->nextu.get()) { \ - __VA_ARGS__ \ - } \ - } \ - for (auto it = bankSections.sections.begin(); it != bankSections.sections.end(); it++) { \ - for (Section const *sect = *it; sect; sect = sect->nextu.get()) { \ - __VA_ARGS__ \ - } \ - } \ - } while (0) +template +static void forEachSortedSection(SortedSections const &bankSections, F callback) { + for (Section const *sect : bankSections.zeroLenSections) { + for (; sect; sect = sect->nextu.get()) { + callback(*sect); + } + } + for (Section const *sect : bankSections.sections) { + for (; sect; sect = sect->nextu.get()) { + callback(*sect); + } + } +} +static void writeSymBank(SortedSections const &bankSections, SectionType type, uint32_t bank) { uint32_t nbSymbols = 0; - forEachSortedSection(sect, { nbSymbols += sect->symbols.size(); }); + forEachSortedSection(bankSections, [&](Section const §) { + nbSymbols += sect.symbols.size(); + }); if (!nbSymbols) { return; @@ -353,36 +348,35 @@ static void writeSymBank(SortedSections const &bankSections, SectionType type, u symList.reserve(nbSymbols); - forEachSortedSection(sect, { - for (Symbol const *sym : sect->symbols) { + forEachSortedSection(bankSections, [&](Section const §) { + for (Symbol const *sym : sect.symbols) { // Don't output symbols that begin with an illegal character - if (!sym->name.empty() && startsIdentifier(sym->name[0])) { - uint16_t addr = static_cast(sym->label().offset + sect->org); - uint16_t parentAddr = addr; - if (auto pos = sym->name.find('.'); pos != std::string::npos) { - std::string parentName = sym->name.substr(0, pos); - if (Symbol const *parentSym = sym_GetSymbol(parentName); - parentSym && std::holds_alternative